Jump to content
  • Advertisement

GC : Explosive Balls (game state)

GoliathForge

1858 views

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


6 Comments


Recommended Comments

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. 

Share this comment


Link to comment

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.

 

Share this comment


Link to comment

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

Share this comment


Link to comment

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...

Edited by GoliathForge

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By phil67rpg
      I have an idea for a RISK like game. I am going to use the united states as a world map. I break it into six regions I also use the playing cards that are one for each state only the 48 states will be used. the symbols on the cards will be ship plane and tank. I broke the regions into northwest, southwest, northmidwest, southmidwest, northeast and southeast. I am using the same combat principles and armies, also if a player gets a region they will get bonus armies and if the player gets three of a kind in playing cards they get bonus armies. I was thinking about using c++ and directx but I am unsure of how to proceed, what language and graphical library. I do have experience in c# and  c++ and opengl and directx.let me know of what you think of my idea.
    • By Goyira
      I've reading about game/engine initialization both in Game Engine Architecture and Game Coding Complete. I have understood there is a problem with C++ and initialization, because initialization of global variables is non-deterministic, and the order your initialize modules of engine is critic since some modules depends other modules. They explains some approaches to avoid the undefined behavior, but I wonder something. Why must I initialize modules like global variables? Why don't initialize like members of Application object? Maybe last would lead to annoying scope issues, but, why don't initialize in main() scope? Would they have "global" scope in main() and deterministic initialization?
      Thank you and sorry for my English.
       
    • By tamlam
      I have a vector saving 5 cube coordinates in oxy plan.
      Task: 1st cube roll 90 degrees, then disappear then 2nd cube roll 90 dg … until the last cube in the vector.
      Problem: whenever the program runs, all the cube will appear and roll the same time.
      Q: How can I solve this problem?
      I checked the value in for loop (temp), and value of (count) as below:
      angle 1 count 1
      temp0
      temp1
      temp2
      temp3
      temp4
      temp5
      temp6
      temp7
      angle 2 count 2
      temp0
      temp1
      temp2
      temp3
      temp4
      temp5
      temp6
      temp7 => I do not know why the for loop does not wait for for the “presskey” action to increase the angle value before move to the next temp value.
      int Count(0); void Display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); SetLight(); glPushMatrix(); ConclusiveAxis(); DrawGrid(); for (int temp = 0; temp < cube.coordinates.size(); temp++) { cout<< "temp" << temp << endl; if ((cube.coordinates[temp] == true)) { sDirection = true; if (Count <= 90) { glPushMatrix(); glTranslatef(cube.coordinates[temp].x - 0.5, cube.coordinates[temp].y - 0.5, 0.0); glRotatef(angle, 0, 1, 0); glTranslatef(-0.5, 0.5, 0.5); glColor3f(0.5, 1.5, 1.0); glutSolidCube(1); glPopMatrix(); } else if (Count > 90) { angle = 0; Count = 0; } else { sDirection = false; } } else { } } glPopMatrix(); glutSwapBuffers(); } void SpecialKey(unsigned char key, int x, int y) { switch (key) { case 'r': //condition for next check if (sDirection == true) { angle++; glutPostRedisplay(); Count++; cout << "angle " << angle << " count " << Count << endl; } else { glutPostRedisplay(NULL); } break; } } void OpenGLCallBackAnimation(void) { GLSettings0.PickObject = PickObject0; OpenGLInitialize(0, GLSettings0, 300, 150, 1000, 650, "window"); glutDisplayFunc(Display); glutMouseFunc(MouseButton); glutMotionFunc(OnMouseMotion); glutPassiveMotionFunc(PassiveMotion); glutKeyboardFunc(SpecialKey); //keyboard call glutMouseWheelFunc(OpenGLMouseWheel0); glutReshapeFunc(OpenGLReshape0); //glutTimerFunc(0, time_callback, 0); OpenGLPostprocessor(GLSettings0); }  
    • By LiamBBonney
      Hi, my name is Liam, founder of TheIndieGamesLab! The hub of gaming experimentation. We believe that testing and experimenting with new and unique ideas is incredible important at TheIndieGamesLab. We also understand that when people have created something so different it is important to bring it into the spotlight, whether that be a new mechanic in a game, a new type of visual or even a streamer play testing and experimenting with new games. It is important to break pattern!
      If you feel your work is something unique and fresh we are here to help you not only promote your work but share ideas and experiment further with you. The idea of experimentation is fascinating to us. This is why the name TheIndieGamesLab has been chosen. We are fully aware of the importance of bringing great work and discoveries into the eyes of the public, ESPECIALLY when it’s a new and unique idea!
      Not only will we do our very best to help Indie Game Developers, we are helping any gamer with a new and fresh idea that they want to bring to the table or be displayed in front of the gaming community. This means we are here to help Indie Gamers, Indie Game Devs / Studios and so on! As well as helping the gaming scientists out there trying to bring something fresh to the lab, we want to inspire gamers to start experimenting and create something memorable.
      This is the start of something special. Together we can make TheIndieGamesLab the hub that gaming content creators can come to gain the recognition and support their work deserves! And become a movement! A movement to inspire and create!
      I have also created a discord server providing a platform for indie game developers to communicate with one another to help support each other and upcoming and existing projects. This server lets people get instant support from indie game devs that are just as passionate about it as you! You can also promote you new projects or post updates to your current projects, please com along, I look forward to seeing you there. Let's support each other in this crazy world that is indie games!
      Join now at https://discord.gg/efa6j3a

    • By _WeirdCat_
      So i am about to read a 8bit pixel and want to convert the value to float (0..1) range
      Like this
      unsigned char r = get_pixel_from_img(x, y);
      Now do i divide it by 255 or 256?
      float fval = float ( r ) / x;
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!