Draw loop and Lua (Luabridge) - c++

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.

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.

SFML objects won't draw when its parent class is reinitialised

I'm working on a new project and an implementing a basic scene change. I have the different scenes setup as their own classes, with the intialisation function being used to create and reposition different SFML objects. I saw this answer and have written my scene switcher similarly:
// Create scene monitoring variable
int scene[2];
scene[0] = 0; // Set current scene to menu
scene[1] = 0; // Set scene change to no
...
// Check for scene change
if(scene[1] == 0) {
// Run tick function based on current scene
switch(scene[0]) {
case 0:
// Main menu - run tick function
menu.tick();
}
}
if(scene[1] == 1) {
// Reset scene that you've changed to
switch(scene[0]) {
case 0:
// Main menu - reset it
menu = Menu(window, scene); // <-- Reinitialise menu here
}
// Set change variable to 0
scene[1] = 0;
}
You can see the full code on the github repository.
However, this doesn't seem to work properly - as soon as a scene change is made, the screen goes blank. The class is reintialised (I added a cout to check), the draw function is still run and mouse clicks are still processed, yet nothing appears in the window.
Am I doing something wrong here?
Doing things that way can lead into leak memory errors. I suggest you a different approach: the StateStack
How this works?
The basics of having a StateStack object is store each possible state of your game/app into a stack. This way, you can process each one in the stack order.
What is an State?
An State is something that can be updated, drawn and handle events. We can make an interface or an abstract class to make our screens behave like a State.
Which are the advantages?
With a stack structure, you can easily control how your different scenes are going to handle the three different processing methods. For instance. If you have a mouse click while you're in a pause menu, you won't that click event to reach the menu state or the "game" state. To achieve this, the solution is really easy, simply return false in your handleEvent method if you don't want the event go further this particular state. Note that this idea is also expandable to draw or update methods. In your pause menu, you won't update your "game" state. In your "game" state you won't draw tour menu state.
Example
With this points in mind, this is one possible way of implementation. First, the State interface:
class State{
public:
virtual bool update() = 0;
virtual bool draw(sf::RenderTarget& target) const = 0;
// We will use a vector instead a stack because we can iterate vectors (for drawing, update, etc)
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack) = 0;
};
Following this interface we can have a example MenuState and PauseState:
MenuState
class MenuState : public State{
public:
MenuState(){
m_count = 0;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("MenuState: " + std::to_string(m_count));
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
m_count++;
m_text.setString("MenuState: " + std::to_string(m_count));
return true;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
return true;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::P){
stack.push_back(new PauseState());
return true;
}
}
return true;
}
private:
sf::Font m_font;
sf::Text m_text;
unsigned int m_count;
};
PauseState
class PauseState : public State{
public:
PauseState(){
sf::Font f;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("PauseState");
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
// By returning false, we prevent States UNDER Pause to update too
return false;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
// By returning false, we prevent States UNDER Pause to draw too
return false;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::Escape){
stack.pop_back();
return true;
}
}
return false;
}
private:
sf::Font m_font;
sf::Text m_text;
};
By the way, while I was doing this, I notice that you must have the fonts as an attribute of the class in order to keep the reference. If not, when your text is drawn, its font is lost ant then it fails. Another way to face this is using a resource holder, which is much more efficient and robust.
Said this, our main will look like:
Main
int main() {
// Create window object
sf::RenderWindow window(sf::VideoMode(720, 720), "OpenTMS");
// Set window frame rate
window.setFramerateLimit(60);
std::vector<State*> stack;
// Create menu
stack.push_back(new MenuState());
// Main window loops
while (window.isOpen()) {
// Create events object
sf::Event event;
// Loop through events
while (window.pollEvent(event)) {
// Close window
if (event.type == sf::Event::Closed) {
window.close();
}
handleEventStack(event, stack);
}
updateStack(stack);
// Clear window
window.clear(sf::Color::Black);
drawStack(window, stack);
// Display window contents
window.display();
}
return 0;
}
The stack functions are simple for-loop but, with the detail that iterate the vector backwards. This is the way to imitate that stack behavior, starting from top (size-1 index) and ending at 0.
Stack functions
void handleEventStack(sf::Event e, std::vector<State*> &stack){
for (int i = stack.size()-1; i >=0; --i){
if (!stack[i]->handleEvent(e, stack)){
break;
}
}
}
void updateStack(std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->update()){
break;
}
}
}
void drawStack(sf::RenderTarget &target, std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->draw(target)){
break;
}
}
}
You can learn more about StateStacks and gamedev in general with this book

C++ Implementing states

My question is:
I am trying to implement basic state management in my project and i stuck at changing states.
I have all my states in std::stack<State*> container, and push/pop them directly from Application class or from State class.
Problem is when i change current state from State class, it can be destroyed before render method called, whitch results in exeption. So how do i avoid this?
PS sorry for my english and please say me if something in my problem/code isn clear
Application class:
void Application::pushState(State* state)
{
this->m_states.push(state);
this->m_states.top()->open();//enter state
}
void Application::popState()
{
if (!this->m_states.empty())
{
this->m_states.top()->close();//leave state
delete this->m_states.top();
}
if (!this->m_states.empty())
this->m_states.pop();
}
void Application::changeState(State* state)
{
if (!this->m_states.empty())
popState();
pushState(state);
}
State* Application::peekState()
{
if (this->m_states.empty()) return nullptr;
return this->m_states.top();
}
void Application::mainLoop()
{
sf::Clock clock;
while (this->m_window.isOpen())
{
sf::Time elapsed = clock.restart();
float delta = elapsed.asSeconds();
if (this->peekState() == nullptr)
this->m_window.close();
this->peekState()->update(delta)//if i change state in State.update(), it may be that code below will now point to not existing state
if (this->peekState() == nullptr)
this->m_window.close();
this->peekState()->render(delta);
}
}
State class:
void EditorState::update(const float delta)
{
sf::Event event;
while (this->m_application->m_window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
this->m_application->popState();
return;
}
}
}
Okay maybe this is not really a problem, but something like "how to" question. As you can see in my code, i update and render states in mainLoop() method. What im tying to figure out is how to manage those updates, asuming that state can be changed from state itself, not only from stateManager (in my case Application class)
Ok, so I'm guessing this is for a game (but it doesn't have to be). Instead of doing what you're doing for switching between states, I use an enum.
enum class GameState {
MENU, PLAY, PAUSE
}
Then, in your main header
GameState m_gameState = GameState::MENU;
In your main loop, you can check what the current state is by simply doing
if (m_gameState == GameState::MENU)
{
...
}
or you can use a switch statement
switch (m_gameState)
{
case GameState::MENU:
...
break;
case GameState::PLAY:
...
break;
case GameState::PAUSE:
...
break;
}
And, if you ever want to switch the state, you can just do
m_gameState = GameState::PAUSE;
Hope this answered your question :D
If not, I must have misunderstood (sorry).

SFML sf::Text::setFillColor is broken or am I doing something wrong?

The code I've written displays the sf::Drawable objects only for the top state of the state stack. Rendering works fine for everything, except the sf::Text type, that does not change the color of the text when button.getText().setFillColor(sf::Color:Red) is called. However, when I construct a button with a red text, whenever I try to set another color to that button, I only get a white text, no matter what color I request.
Here's where I change the color of a button:
void GameState_MainMenu::handleRealTimeInput()
{
for each (TextButton button in mButtons)
{
if (button.isSpriteClicked())
{
button.getText().setFillColor(sf::Color::Red);
button.triggerAction();
sf::Clock wait;
sf::Time timer = sf::Time::Zero;
timer = sf::seconds(0.15f);
while (wait.getElapsedTime() < timer)
{
}
wait.restart();
}
}
}
and this is my Game::render() method:
void Game::render()
{
GameState *currentState = getActiveState();
if (currentState != nullptr)
{
mWindow.clear();
currentState->draw();
}
mWindow.display();
}
Lastly, this is the draw method of the MainMenu state:
void GameState_MainMenu::draw()
{
game->mWindow.draw(game->mBackground);
game->mWindow.draw(mSelector.mSprite);
for each (TextButton button in mButtons)
{
game->mWindow.draw(button.getText());
}
}
It's probably because you have a while loop in the GameState_MainMenu::handleRealTimeInput that the program is getting stuck in.
You can try to use threads, though that way could get pretty messy. I suggest revising your code.
Okay, so I figured out this had something to do with c++'s for each instruction. As soon as I switched to the classic array-like traversal, my buttons started changing colors. I'm not saying this is THE solution, just that it worked for me. If anyone has the same problem, you might want to check that.

SFML help (Using the Sleep function in <Windows.h>)

I need some help with my SFML/C++ code in Visual C++ 2010 Express Edition. I am trying to get my text (livesLeft) to "You Win!" Before the program sleeps. This is making the player know that he won. But instead, the program goes right to sleep and changes the text right as it is closing, so you only see it change for a few milliseconds. I can't even read it.
bool play = true;
bool win = false;
bool touchFinish = false;
int lives = 3;
sf::Font arial;
if(arial.loadFromFile("Fonts/arial.ttf") == 0)
{
return 1;
}
sf::Text livesLeft;
livesLeft.setFont(arial);
livesLeft.setCharacterSize(30);
livesLeft.setString("Lives: ");
livesLeft.setPosition(0, 0);
livesLeft.setColor(sf::Color::White);
sf::RectangleShape finish;
finish.setSize(sf::Vector2f(200, 600));
finish.setPosition(700, 0);
finish.setFillColor(sf::Color::Green);
Those are my variables in use and this is my code where I'm trying to change the text:
if(player.getGlobalBounds().intersects(finish.getGlobalBounds()))
{
livesLeft.setString("You Win!");
touchFinish = true;
}
if(touchFinish)
{
win = true;
}
if(win)
{
Sleep(5000);
play = false;
}
I also forgot to add in that I do have the rendering at the end:
window.clear();
window.draw(livesLeft);
window.draw(finish);
window.draw(player);
window.draw(obs1);
window.draw(obs2);
window.draw(obs3);
window.draw(obs4);
window.draw(obs5);
window.draw(obs6);
window.draw(obs7);
window.draw(obs8);
window.draw(obs9);
window.draw(obs10);
window.draw(obs11);
window.draw(obs12);
window.draw(obs13);
window.display();
This is most likely due to the fact that you aren't calling the display method for you window. Telling the text to change simply prepares the next framebuffer to display the new text, but because you never tell the window to update its display before sleeping, it never displays the new framebuffer.
Here is a quick example of a simple program using SFML. Notice the window.display() method at the end of the main game loop.
You effectively want to be doing this:
if(win)
{
window.display();
Sleep(5000);
play = false;
}
The reason why you need to update the display before hand again is because Sleep(5000); blocks the thread, essentially meaning that it sits at that call for 5000ms. Also, if you want to keep the previous items on the screen, you'll want to redraw those before window.display(); as well, since these won't be in the next framebuffer.