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
Related
I am programming an LED Cube I have designed. The cube has a "pause" button and a "play/next" button. Unless the cube is paused, it will cycle through all of the different effects (animations) I've made for it. If you press the pause button, the cube will no longer transition between effects and will instead repeat the current effect. Pressing the 'play/next' button will unset the pause feature and will advance to the next effect immediately.
Some of these effects are pretty complex and require a large number of variables to be kept between frames of animation. In order to easily destroy all of these variables at a moment's notice (like when the next button is pressed), I'm instantiating the current animation as an object and destroying it when the effect is complete or the skip button is pressed.
I'm trying to set my main loop up as follows:
void loop() {
//create an effect object
switch(effectIndex){
case 0:
EF_GROWFRAME effect;
break;
case 1:
EF_RANDOMFILL effect;
break;
}
bool proceed;
do{
//returns false until the effect has completed
proceed=effect.step();
//push this cube update and wait for it to display
cube.update();
cube.waitForFrame();
}
while ((!proceed)&&(!skipflag));
//skipflag is set true during a timer interrupt if the skip button is freshly pressed
skipflag=false;
cube.clearPattern();
if (play) effectIndex++;
if (effectIndex=effectCount) effectIndex=0;
}
That fails because of my conflicting definitions of effect though. You can probably see what I'm going for, so what's the proper way to approach this?
This is a use case for polymorphism.
Define a base class, Animation that defines a shared interface and have your various animation types derive from it. For example:
class Animation {
public:
virtual ~Animation() {
// any generic cleanup shared by all animation types
}
virtual bool step() = 0;
};
class AnimationA : public Animation {
public:
bool step() override {
// logic for this type of animation
}
};
class AnimationB : public Animation {
public:
bool step() override {
// logic for this type of animation
}
};
void loop() {
std::unique_ptr<Animation> effect;
switch (effectIndex) {
case 0:
effect = std::make_unique<AnimationA>();
break;
case 1:
effect = std::make_unique<AnimationB>();
break;
}
//...
}
Live Demo
Since it seems like this may be an embedded environment, you could avoid the dynamic memory allocation from my first example by factoring your animation playing logic out into a separate function:
void playAnimation(Animation& effect) {
bool proceed;
do{
//returns false until the effect has completed
proceed=effect.step();
//push this cube update and wait for it to display
cube.update();
cube.waitForFrame();
} while (!proceed && !skipFlag);
//skipflag is set true during a timer interrupt if the skip button is freshly pressed
skipflag=false;
cube.clearPattern();
}
void loop() {
switch (effectIndex) {
case 0:
{
AnimationA effect;
playAnimation(effect);
break;
}
case 1:
{
AnimationB effect;
playAnimation(effect);
break;
}
}
if (play) effectIndex++;
if (effectIndex == effectCount) effectIndex=0;
}
Live Demo
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.
i am trying to programm Minesweeper with Qt in c++11.
If i press a button with 0 bombs, i want to check for buttons around this button and if they have 0 bombs, too. If they have 0 bombs, i want to check the buttons. (picture: red square)
This is my button-class:
#ifndef ABUTTON_H
#define ABUTTON_H
#include "QPushButton"
#include "QMouseEvent"
class AButton : public QPushButton
{
Q_OBJECT
public:
AButton(QWidget* parent);
//AButton();
~AButton();
bool& set(bool state); //set bomb state
bool get(); //get bomb state
void increment_counter(); //increment counter for surrounding bombs
int get_counter(); //get bomb counter
bool get_locked(); //is it locked (->flagged)
void set_locked(bool); //set it to locked
AButton* get_Button(char c); //get Button above, beneath, left, right
void set_Button(AButton* Button, char c); //set Button above, ...; char is for setting the right one
private:
bool bomb; //is button a bomb
int Nachbar_Bomben; // how many bombs around this button
bool locked; // is the button locked
AButton* b_links; //pointer to the button to the left
AButton* b_rechts; //pointer to the button to the right
AButton* b_oben; //pointer to the button above
AButton* b_unten; //pointer to the button beneath
public slots:
void mousePressEvent(QMouseEvent *event);
signals:
void rightclicked();
void leftclicked();
};
#endif // ABUTTON_H
This is what happens if a button is clicked:
void Layout::ButtonClicked()
{
char Buffer [50];
AButton *clickedButton = qobject_cast <AButton*>(sender()); //which button
if (!clickedButton->get_locked())
{
clickedButton->setChecked(1); //set button to checked
{
if (clickedButton->get()) //Is button a bomb?
{
clickedButton->setText(QString ("B"));
Fehlermeldung *Fehler = new Fehlermeldung(); //make error window
Fehler->show();
}
else
{
if(clickedButton->get_counter() == 0) //has this button 0 bombs?
{
check_for_surrounding_bombs(clickedButton); //start the recursiv check, if there are buttons with 0 bombs around
}
else
{
sprintf(Buffer, "%i", clickedButton->get_counter());
clickedButton->setText(QString (Buffer)); //write how many bombs are in this button
}}}}}
My problem is, that i get a SegFault by calling the function "check_for_surrounding_bombs".
void Layout::check_for_surrounding_bombs(AButton* clickedButton) //function doesnt work
{
if (clickedButton->get_Button('o')) //does the button above exist?
{
if (clickedButton->get_Button('o')->get_counter()== 0) //has this button 0 bombs
{
clickedButton->get_Button('o')->setText(QString ("")); // write nothing in it
if (!clickedButton->get_Button('o')->get_locked()) //if it isnt locked (= set flag)
{
clickedButton->get_Button('o')->setChecked(1); //set the button to checked
}
check_for_surrounding_bombs(clickedButton->get_Button('o')); //do the same thing for the button above
}
//... the function does the same with the buttons to the left, right, beneath
I am unsure if my recursiv-approach is the right way.
The debugger gives me this error when calling the function "check_for_surrounding_bombs(clickedButton);":
enter image description here
Implementation of the get_Button member fct.
AButton* AButton::get_Button(char c)
{
if (c == 'o')
return b_oben; //return button above
else if (c == 'u')
return b_unten; //return button beneath
else if (c == 'l')
return b_links; //return button to the left
else if (c == 'r')
return b_rechts; //return button to the right
else
return 0;
}
Any idea?
Thank you in advance.
The crash is probably caused by a stack overflow. This occurs because your recursive solution has no mechanism to simplify the problem.
If you change the state of each button you have visited before recursing, then the problem would become bounded and your problem mostly go away.
A recursive solution requires some stack for each step in the solution. This is a relatively limited resource, as the stack has a bound, and can't grow into memory used for another purpose. So this case, if you had a grid layout with 100x100 elements, you could have 10,000 steps.
As such I would look to another non-recursive solution, where you create a vector or list of items left to visit, and add items to that.
It is still imperative
Don't add a location twice to the list.
Don't visit a location again, after it has been visited.
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).
I am porting a game from cocos2d-iphone 2.x to cocos2d-x 3.x.
Have to solve a few problems, including a major crash - the subject of this post.
It has been determined that the crash happens because SOMETIMES, my replaceScene call results in a messed-up important public variable.
My class:
class Player : public cocos2d::Sprite
{
public:
....
cocos2d::Vec2 desiredPosition;
....
My Layer methods:
Scene* GameLevelLayer::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = GameLevelLayer::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
bool GameLevelLayer::init()
{
// super init first
if ( !Layer::init() )
{
return false;
}
....
player = (Player*) cocos2d::Sprite::create("sprite_idle_right#2x.png");
player->setPosition(Vec2(100, 50));
player->desiredPosition = player->getPosition();
....
this->schedule(schedule_selector(GameLevelLayer::update), 1.0/60.0);
....
return true;
}
void GameLevelLayer::endGame(bool won) {
....
MenuItem* display;
if (currentLevel < lastLevel && won) {
++currentLevel;
display = MenuItemImage::create("next.png" ,"next.png" ,"next.png",
CC_CALLBACK_1(GameLevelLayer::replaceSceneCallback, this));
} else {
// Lost the game
currentLevel = 1;
display = MenuItemImage::create("replay.png", "replay.png", "replay.png",
CC_CALLBACK_1(GameLevelLayer::replaceSceneCallback, this));
}
....
}
void GameLevelLayer::replaceSceneCallback(Ref* sender) {
Director::getInstance()->replaceScene(this->createScene());
}
The member being messed is the desiredPosition. It is changed inside update() method. The problem is that update() gets an already messed-up desired position. It is only messed-up after a scene was being replaced. The problem happens once in 10 runs, or so. It even appears that when update() is called first time after the scene has been replaced, desiredPosition set to some garbage. is I was unable to learn more.
My Player class does not have a separate constructor.
Please advise.
I forgot to initialize another instance variable. That instance variable is used to calculate the desiredPosition.