SFML - Rendering a character when the key is pressed? - c++

So I've got this program that is supposed to imitate a console (with a little coding help from this user):
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
sf::Color fontColor;
sf::Font mainFont;
sf::Clock myClock;
bool showCursor = true;
void LoadFont() {
mainFont.loadFromFile("dos.ttf");
fontColor.r = 0;
fontColor.g = 203;
fontColor.b = 0;
}
int main() {
sf::RenderWindow wnd(sf::VideoMode(1366, 768), "SFML Console");
wnd.setSize(sf::Vector2u(1366, 768));
LoadFont();
sf::Text myTxt;
myTxt.setColor(fontColor);
myTxt.setString("System Module:");
myTxt.setFont(mainFont);
myTxt.setCharacterSize(18);
myTxt.setStyle(sf::Text::Regular);
myTxt.setPosition(0, 0);
while(wnd.isOpen()) {
sf::Event myEvent;
while (wnd.pollEvent(myEvent)) {
if (myEvent.type == sf::Event::Closed) {
wnd.close();
}
if (myEvent.type == sf::Event::KeyPressed) {
if (myEvent.key.code == sf::Keyboard::Escape) {
wnd.close();
}
}
}
wnd.clear();
if (myClock.getElapsedTime() >= sf::milliseconds(500)) {
myClock.restart();
showCursor = !showCursor;
if(showCursor == true) {
myTxt.setString("System Module:_");
} else {
myTxt.setString("System Module:");
}
}
wnd.draw(myTxt);
wnd.display();
}
}
I need to be able to let the user type a key on the keyboard, and then render that key on the screen. I'm thinking about using an std::vector of sf::Keyboard::Key, and use a while loop to check what the key is (looping through the std::vector<sf::Keyboard::Key>) without using a whole bunch of if statements, but I don't exactly know how to handle that yet, so I'd like to know if there is an easier way to accomplish my main goal. Suggestions? Comments?
Thank you for your time,
~Mike

SFML has a nice feature for this, sf::Event::TextEntered (tutorial). That is typically what you want and it avoids you to do crazy things to interpret the text entered by the user.
Stock your text entered by adding every character into a sf::String (rather than std::string, it may deal better with sfml's unicode types ~ not sure, but that would need a little check) which is then the perfect type for sf::Text::setString !
Don't hesitate to look at the docs, it has further documentation in every classes' page.
Example:
sf::String userInput;
// ...
while( wnd.pollEvent(event))
{
if(event.type == sf::Event::TextEntered)
{
/* Choose one of the 2 following, and note that the insert method
may be more efficient, as it avoids creating a new string by
concatenating and then copying into userInput.
*/
// userInput += event.text.unicode;
userInput.insert(userInput.getSize(), event.text.unicode);
}
else if(event.type == sf::Event::KeyPressed)
{
if(event.key.code == sf::Keyboard::BackSpace) // delete the last character
{
userInput.erase(userInput.getSize() - 1);
}
}
}

Related

How do I assign a value in an array when a mouse is pressed SFML C++

I am trying to make a program where you are allowed to select between an option of shapes, and then drawing it. To allow for multiple shapes I created a vector of a class which creates shapes (Shapes are set up with the chosen function). My problem is the mouse click is too long, so it assigns it to everything in the vector, so you can't create a new shape. Is there a problem in my logic, or is there a problem in the code?
Here is my attempt:
for (auto& it : onCanvas) {
if (Mouse::isButtonPressed(Mouse::Left)) {
if (mousepointer.getGlobalBounds().intersects(circleOption.getGlobalBounds())) {
it.chosen(circles);
}
if (mousepointer.getGlobalBounds().intersects(rectOption.getGlobalBounds())) {
it.chosen(rectangle);
}
if (mousepointer.getGlobalBounds().intersects(triOption.getGlobalBounds())) {
it.chosen(triangles);
}
if (mousepointer.getGlobalBounds().intersects(it.shape.getGlobalBounds()) || it.dragging) {
it.shape.setPosition(mousepointer.getPosition());
it.dragging = true;
}
}
if (!Mouse::isButtonPressed) {
it.dragging = false;
}
win.draw(it.shape);
}
Your source-code is a bit incomplete (what is onCanvas and mousepointer). But I guess the problem is that this snippet is called multiple times while your mouse is clicked. To avoid that you can do two thing.
In the first solution you use events, so you only add shapes when the state of the mousebutton changes (you can additionally listen to the MouseButtonReleased to simulate a full click):
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.mouseButton.button == sf::Mouse::Left)
{
// Hit Detection
}
}
or second solution you remember the last state of the button (probably do the mouse check once outside of the for loop):
bool mouse_was_up = true;
if (mouse_was_up && Mouse::isButtonPressed(Mouse::Left)) {
mouse_was_up = false;
for (auto& it : onCanvas) {
// Hit Detection
}
}
else if (!Mouse::isButtonPressed(Mouse::Left))
mouse_was_up = true;
I would rather stick to the first solution because when your click is too short and your gameloop is in another part of the game logic, you can miss the click.

SDL_KEYDOWN triggering twice

I am following lazy foo's tutorial, however I realized every time I press press s or p, SDL_KEYDOWNtriggers twice. How can this be fixed?
Here is the code snippet:
while(SDL_PollEvent(&e) != 0) {
if(e.type == SDL_QUIT) {
quit = true;
}
else if(e.type == SDL_KEYDOWN) {
if(e.key.keysym.sym == SDLK_s) {
if(timer.isStarted()) {
timer.stop();
printf("stop\n");
}
else {
timer.start();
printf("start\n");
}
}
else if(e.key.keysym.sym == SDLK_p) {
if(timer.isPaused()) {
timer.unpause();
printf("unpause\n");
}
else {
timer.pause();
printf("pause\n");
}
}
}
}
Pressing s once:
start
stop
TL;DR: Check if e.key.repeat equals to 0 before handling the events.
SDL generates fake repeated keypresses if you hold a key long enough. This is used mostly for text input.
The original key press has .repeat == 0, and fake presses have .repeat == 1.
For convenience reasons probably (I'd argue that it's rather inconvenient), since SDL 2.0.5 the actual key press generates two events instead of one. One has .repeat set to 0, and other (new) one has it set to 1.

Handle mouse events SFML

I have some trouble with events in SFML. I am making a turnbased game, when the mouse has moved or left mouse button is clicked i check whos turn it is and then spawn a projectile at that objects position, when the projectile has collided with either terrain or opponent it gets destroyed and the turn is changed.
The behaviour is not what i am expecting though. When clicking shoot the projectile sometimes wont spawn at all (and it changes the turn immedeately). I have disabled all collisions so it cant be that. Im 90% sure that the issue is with how i handle events, so id really appretiate input on how i can make it better.
Of what ive learned is that you should not execute the functions in the while poll event, its only for registering what has happend most recently,so i put them outside instead. This does not solve my problem though...
sf::Event event;
sf::Vector2i mousePos;
while (_window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
_window.close();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) {
_window.close();
}
if(event.type == sf::Event::MouseMoved) { // <- this is how you check to see if an event
mousePos = sf::Mouse::getPosition();
moved = true;
}
if(event.type == sf::Event::MouseButtonPressed) { // <- this is how you check to see if an event
mousePressed = true;
}
}
if (mousePressed && tank1Turn)
{
sf::Vector2f spawnPos = _t1->getPos() + sf::Vector2f(0, -150);
sf::Vector2f initVel = _t1->getInitVel();
cout << endl;
_p = new Projectile(spawnPos, initVel);
tank1Turn = false;
tank1Firing = true;
mousePressed = false;
}
if (mousePressed && tank2Turn) {
sf::Vector2f spawnPos = _t2->getPos()+sf::Vector2f(0,-150);
sf::Vector2f initVel = _t2->getInitVel();
_p = new Projectile(spawnPos, initVel);
tank2Turn = false;
tank2Firing = true;
mousePressed = false;
}
if (tank1Turn && moved) {
_t1->aim(mousePos);
moved = false;
mousePressed = false;
}
if (tank2Turn && moved) {
_t2->aim(mousePos);
moved = false;
mousePressed = false;
}
}
Consider this a comment. This isn't an answer (I don't have enough reputation to comment so I have to make this an answer) but here are some things I noticed:
You should either replace the ifs in your event loop with if-elses, or use a switch
sf::Keyboard::isKeyPressed is for real time input (put it inside your game update loop). In your event loop it should be sf::Event::KeyPressed and check which key it was with evt.key.code
You aren't checking which mouse button was pressed. Do it with evt.mouseButton.button (sf::Mouse::Button::Left for the left mouse)
Of what ive learned is that you should not execute the functions in the while poll event, its only for registering what has happend most recently,so i put them outside instead.
Where did you read that? I don't think that's true
There shouldn't just be an array of ifs below the event loop. Code should be structured better

Draw loop and Lua (Luabridge)

I'm currently stuck on some issue and don't know how to fix it.
I started working on a simple 2D engine using SFML for rendering stuff and Lua as my scripting language.
The engine starts with a splash screen first before Lua code is loaded ...
My problem is I don't know how to write a "good" draw loop for my Lua objects.
Maybe you'll understand when we take a look on my code:
Main.cpp:
...
int draw_stuff()
{
sf::RenderWindow window(sf::VideoMode(640, 480), TITLE);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
if (!success) {
window.draw(sp_splash_screen);
}
if (clock.getElapsedTime().asSeconds() > 2) {
for (static bool first = true; first; first = false)
{
std::thread thr(&lua_module);
thr.detach();
success = true;
}
}
for (sf::CircleShape obj : CircleDrawList) {
window.draw(obj);
//window.draw(CircleDrawList.front());
}
}
return 0;
}
My CircleShape wrapper class for Lua:
/////shapes.h
extern std::list<sf::CircleShape> CircleDrawList;
class Circle
{
public:
Circle();
...
void draw();
private:
sf::CircleShape circleShape;
protected:
//~Circle();();
};
/////shapes.cpp
std::list<sf::CircleShape> CircleDrawList;
...
void Circle::draw()
{
//std::cout << "DRAW IN LIST" << std::endl;
CircleDrawList.push_front(circleShape);
//if (drawableList.size() == 4)
//drawableList.pop_front();
}
...
int luaopen_shapes(lua_State *L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Circle>("Circle")
.addConstructor <void(*) (void)>()
... "other stuff to register" ...
.addFunction("draw", &Circle::draw)
.endClass();
return 1;
}
and finally (if needed) my lua script:
local ball = Circle()
ball:setColor(255,0,0,255)
while true do
ball:draw()
end
Result when the Lua Circle is moving by increasing one of the Vector2 values:
Hope you can help and I described it good :x
Thanks :)
-- Updated code in main.cpp
I'm not sure what is happening, but I see some problems in the code.
First: you don't remove "old" CircleShapes from CircleDrawList.
In the function Circle::draw() you are pushing object to the front of the list with CircleDrawList.push_front(circleShape); but you never remove anything from it. Using CircleDrawList.front() gives you access to the first element but you have to use method pop_front() to remove it.
Second thing. Look at the code:
//iterating through whole list with range-for:
for (sf::CircleShape obj : CircleDrawList)
{
//why do you use CircleDrawList.front()?
//You should probably use obj here!
window.draw(CircleDrawList.front());
}
As for now this code draws first element of the list n times where n is the length of the CircleDrawList. Do you really want to do this?
Moreover you are drawing splash every frame. If you wish to display splash only at the beginning, then the line window.draw(splash_screen); // splash screen before Lua script is loaded should probably be in some sort of conditional instruction.
I don't know what is between drawing splash_screen and drawing circles. It could have some impact on what we see on the screen.
And one more thing: it is strongly recommended to avoid using global variables. CircleDrawList is a global variable and my advice would be to put it at least in some namespace, or even better to enclose it in some class.

Is there a good way to expose sf::Event to Lua with Luabridge?

According to the LuaBridge readme, LuaBridge does not support "Enumerated constants", which I assume is just enums. Since sf::Event is almost entirely enums, is there any way I can expose the class? Currently the only other solution I can come up with is detect key presses in C++, then send a string to Lua, that describes the event. Obviously, there are around 100+ keys on a modern keyboard, which would cause a massive, ugly segment of just if statements.
For those who haven't used SFML: Link to sf::Event class source code
UPDATE:
After attempting to create the function outlined in my question, I discovered that it don't work anyway, because you can't return more than one string in C++, so most events are ignored.
Example Source (doesn't work):
std::string getEvent()
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed) {window.close(); return "";}
else if (event.type == sf::Event::GainedFocus) {return "GainedFocus";}
else if (event.type == sf::Event::LostFocus) {return "LostFocus";}
else if (event.type == sf::Event::Resized) {return "Resized";}
else if (event.type == sf::Event::TextEntered)
{
if ((event.text.unicode < 128) && (event.text.unicode > 0)) {return "" + static_cast<char>(event.text.unicode);}
}
else if (event.type == sf::Event::KeyPressed)
{
//If else for all keys on keyboard
}
else if (event.type == sf::Event::KeyReleased)
{
//If else for all keys on keyboard
}
else {return "";}
}
return "";
}
UPDATE UPDATE:
Since this question has received zero comments or answers, I've decided not to rule out other libraries. So, if there is a C++ library that supports enums, I will accept it
Since this question has received zero comments or answers, I've decided not to rule out other libraries. So, if there is a C++ library that supports enums, I will accept it
The Thor library, an SFML extension, supports conversions between SFML key types and strings. This would help you serialize enumerators and pass them as strings to Lua -- and back if you need.
if you only want enum to number, consider this. <luabridge/detail/Vector.h> methods.
#include <LuaBridge/detail/Stack.h>
enum class LogLevels { LOG_1, LOG_2 }
namespace luabridge
{
template <>
struct Stack<LogLevels>
{
static void push(lua_State* L, LogLevels const& v) { lua_pushnumber( L, static_cast<int>(v) ); }
static LogLevels get(lua_State* L, int index) { return LuaRef::fromStack(L, index); }
};
}
Add this into Namespace class:
template<class T>
Namespace& addConstant(char const* name, T value)
{
if (m_stackSize == 1)
{
throw std::logic_error("addConstant () called on global namespace");
}
assert(lua_istable(L, -1)); // Stack: namespace table (ns)
Stack<T>::push(L,value); // Stack: ns, value
rawsetfield(L, -2, name); // Stack: ns
return *this;
}
Then use:
getGlobalNamespace(L)
.addNamespace("sf")
.addNamespace("Event")
.addConstant("KeyPressed",sf::Event::KeyPressed)
//....
.endNamespace()
.endNamespace();