🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

GC : Explosive Balls (game state)

posted in A Hobbyist Tale
Published May 03, 2019
C++
Advertisement

In order to stay out of trouble, I'm going back to work. :) 

Previous Blog Entry for this game challenge round.

Today's topic is my drop in game state manager system for moving between game screens such as an introduction sequence, main menu hierarchy, game play and first say on app termination. I don't remember where I initially found this gem because it has been quite a number of years that I've been reusing this. The concept is simple though. Manage screens as a stack. Push a screen pointer to the back to become active and pop to activate the previous. So how do we get a system like this up and running? The following are sloppy source grab copies. Two objects start us out, First a manager and an abstract base class. From the abstract, we derive the game screen objects. Each screen object handles their own behavior not knowing anything about the others.


// file : GameState.cpp

#include "olcPixelGameEngine.h" // <-- yours renderer may be different
#include "GameState.h"
#include "States/intro.h"
#include "States/game.h"
#include "States/menu.h"

using namespace Core;

std::shared_ptr<States::Intro>  introState;
std::shared_ptr<States::Menu>   menuState;
std::shared_ptr<States::Game>   gameState;

void StateManager::initialize(olc::PixelGameEngine* engine, vec2 clientSize)
{
	this->engine = engine;
	introState = std::make_shared<States::Intro>(this, clientSize);
	menuState = std::make_shared<States::Menu>(this, clientSize);
	gameState = std::make_shared<States::Game>(this, clientSize);

	states.push_back(menuState);

#ifdef _RELEASE
	states.push_back(introState);
#endif
}


void StateManager::shutdown()
{
	introState->shutdown();
	menuState->shutdown();
	gameState->shutdown();
}

void StateManager::changeState(States::Base_state* _state) { }
void StateManager::pushState(States::Base_state* _state)   { }
void StateManager::popState() { states.pop_back(); }
void StateManager::draw()     { states.back()->draw(); }
void StateManager::update(float deltaTime) { states.back()->update(deltaTime); }

void StateManager::handleEvents(eStateAction _action)
{
	switch (_action) {
	case eStateAction::pause:
		if (states.back()->name == "game_state")
			states.pop_back();
		break;
	case eStateAction::play:
		if (states.back()->name == "menu_state") {
			int w = engine->GetDrawTargetWidth();
			int h = engine->GetDrawTargetHeight();
			
			RECT rect = { 0, 0, w, h }; // <-- I know --^
			states.push_back(gameState);
		}
		break;
	case eStateAction::win:
		if (states.back()->name == "game_state")
		{   MessageBox(0, "you win", "", 0);
			//winState->winnerName = gameState->getWinnerName();
			//winState->winningTime = gameState->getTimeString();
			//states.push_back(winState);
		}
		break;
	case eStateAction::loose:
		if (states.back()->name == "game_state") {
		    MessageBox(0, "you loose", "", 0);
			//states.push_back(scoreState);
		}
		break;
	case eStateAction::quit:
		if (states.back()->name == "menu_state")
			PostQuitMessage(0);
		if (states.back()->name == "game_state") {
			::ShowCursor(true); // umm, you're counting me aren't you
			::ClipCursor(nullptr);
			states.pop_back();
		}
		if (states.back()->name == "score_state")
			states.pop_back();
		if (states.back()->name == "win_state") {
			// todo : record score
			gameState->reset();
			states.pop_back();
		}
		break;
	} // end switch(action)
}

 

 

 

 

 

 

 

 

 

With the manager out of the way, the base class for the stack-able objects.


// file : base_state.h

#ifndef _engine_core_states_base_state_h_
#define _engine_core_states_base_state_h_

#include <olcpixelgameengine.h>
#include <string>

namespace Core {
	namespace States {

		// abstract
		class Base_state
		{
		public:
			Base_state(void* _manager, std::string _name, vec2 _clientSize)
			{
				pManager = _manager;
				name = _name;
				clientSize = _clientSize;
			}

			virtual void initialize() = 0;
			virtual void shutdown() = 0;

			virtual void pause() = 0;
			virtual void resume() = 0;

			virtual void handleEvents() = 0;
			virtual void update(float _deltaTime) = 0;
			virtual void draw() = 0;s

			void* pManager;   // que sara sara
			std::string name; // todo : refactor string to numeric id? :)
			vec2 clientSize;
          
		protected:
			Base_state() { /* empty constructor */ }
		};
	}
}

#endif

Correct me if I'm wrong (please) but of all the reasons to use polymorphism, this case is one of the better (subtyping). To be able to keep like items that are different in a common container.  The derived class header would be as expected. Base class overloads in place plus specific functions and variables that would be required to make the new object behave as it will. Nothing new. I'll admit, I'm trapped in the common cycle that most online tutorials mold where the loop is divided into update / draw / check state / repeat. Is it correct? <shrug> So all this may be familiar and you hate it, or it's new and reader be cautious. The basic take away here is the stack concept.

On the main loop side, we declare a Core::StateManager instance and initialize. During the loop, update and draw calls to the manager fire. I feel I don't need to list any additional modules but will list an example derived menu class.


#include "menu.h"
#include "../GameState.h"

using namespace Core::States;


void Menu::initialize()
{
	rectPlay = { 360, 140, 440, 160 }; // 80 x 20
	rectQuit = { 360, 165, 440, 185 }; // need more data or buttons are a known size
}

void Menu::shutdown() { }
void Menu::pause() { }
void Menu::resume() { }
void Menu::handleEvents() { }
void Menu::update(float _deltaTime) { }

void Menu::draw()
{
	olc::Pixel colorDefault = olc::Pixel(255, 255, 255);
	olc::Pixel colorHover = olc::Pixel(255, 255, 0);

	//                                                                                           _
	//  .---  todo : don't like handling user input inside a draw routine...fix me  ----.    \_(O,O)_
	//  V                                                                               V        ~   \
	

	// ------------------- Game Menu -------------------------
	Core::StateManager* manager = (StateManager*)pManager;
	olc::PixelGameEngine* e = manager->engine;

	POINT cursorPos;
	cursorPos.x = manager->engine->GetMouseX();
	cursorPos.y = manager->engine->GetMouseY();

	e->DrawRect(rectPlay.left, rectPlay.top, rectPlay.right - rectPlay.left, rectPlay.bottom - rectPlay.top);
	e->DrawRect(rectQuit.left, rectQuit.top, rectQuit.right - rectQuit.left, rectQuit.bottom - rectQuit.top);

	std::string cursorInfo = "CursorPos ";
	char buf[16] = "";
	_itoa_s(int(cursorPos.x), buf, 10);
	e->DrawString(20, 20, std::string(buf));
	_itoa_s(int(cursorPos.y), buf, 10);
	e->DrawString(70, 20, std::string(buf));

	vec2 textOffset = { 25.f, 6.f };
	int x = int(rectPlay.left + textOffset.x);
	int y = int(rectPlay.top + textOffset.y);

	// Play -------------------------------------------------
	olc::Pixel textColor = colorDefault;
	if (::PtInRect(&rectPlay, cursorPos))
	{	textColor = colorHover;
		if(e->GetMouse(0).bPressed)
			manager->handleEvents(Core::eStateAction::play);
	}
	e->DrawString(x, y, "Play", textColor);
		
	// Quit --------------------------------------------------
	x = int(rectQuit.left + textOffset.x);
	y = int(rectQuit.top + textOffset.y);
	textColor = colorDefault;
	if (::PtInRect(&rectQuit, cursorPos))
	{	textColor = colorHover;
		if(manager->engine->GetMouse(0).bPressed)
		{	if (MessageBox(nullptr, "Are you sure you want to quit?", "Serious?", MB_YESNO) == IDYES)
				exit(0);
		}
	}
	manager->engine->DrawString(x, y, "Quit", textColor);
}

The game screen state would be similar but behave as what the main game play would be. Every valid screen pointer lives the life of the application but only the one at the back of the stack is active at a given time. This has served me well for adding game state transitions in custom work. 

Input and drawing are engine responsibilities so there is a member pointer to contend with in the manager. Perhaps not the best approach, but I don't know of alternatives other than a hard global or worse, singleton. But a subclass gets this functionality through the manager held engine pointer. Crappy note to end on perhaps, but that's where I am. It works, I called it good and have been reusing a number of times. Tips from the leet are always appreciated assuming my words are better than a hodgepodge of nonsense.   

 

edit log

  • 2019_05_03 Code cleanup
2 likes 6 comments

Comments

Rutin

??

April 21, 2019 05:23 AM
Choo Wagga Choo Choo
♗☘
April 21, 2019 05:29 AM
Choo Wagga Choo Choo

It turns out the pixel api thing has some neat extension headers dealing with sprite transforms. The 'engine' has static access so the necessity to pass an engine pointer to draw above can be refactored out. What these tools don't have is sprite animation. I'm happy for this. Don't try to do everything, do that put pixel thing you do and do it well. Modulate this... LOL...I'm on it. 

April 21, 2019 07:57 AM
Choo Wagga Choo Choo

I'm going to skip a blog week and just throw up a video progress. This week was character/animation controller. Nice to start thinking about the action bits. Thanks for playing.

 

April 25, 2019 12:59 AM
Rutin

Wow very cool! I like how your movement adapts to the terrain changes. :) Great work!

April 25, 2019 01:16 AM
Choo Wagga Choo Choo

Lots to do. I feel I have a decent setup ready now and some basic communication going on. For the sound system, I went with a companion module in the series I'm currently following. It was nice after cleaning up the implementation and tweaking it slightly to act as I wanted. Copied and tweaked a couple of instruments, wrote two bars of music that worked well wrapped, turned an instrument concept into a game effect and we were off and running. Still focusing on the challenge requirements because, well my game idea is a little weak but that is not the point. :) Thank you for the challenge opportunity. I know some decent game play will come out if I stick with the basic ground rules. (and maybe a twist in there somewhere) head scratching over here. :D  

But here's what my input strings look like for my music format.


// Author : mark kughler (goliath forge)
	// Lick : Do Diddley      |_............_||_............_||_............_||_............_|
	std::string strKick    = "X..X....X.......X..X.....X......X........X......";
	std::string strSnare   = "......X.......X.......X.......X.......X.......X.";
	std::string strHiHat   = "..X.X............XX.........X......X........X...";
	std::string strCymb    = ".........................e..X..................X";
	std::string strBell    = "......c...e...g.................................";  
	std::string strHarmon3 = "b.............c.......b...............b.......e.";
	std::string strHarmon2 = "e.....................e.....d...........g...c.c.";
	std::string strHarmon  = "............................a.......a.b.........";
	// lower case notes (c major) - upper case sharp
	// c = c d e f g a b c
	// d = d e F g a b C d
	// e = e F G a b C D e
	// f = f g a A c d e f
	// g = g a b c d e F g
	// a = a b C d e F G a
	// b = b C D e F G A b

Moving Forward...

May 04, 2019 08:55 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement