Question about timers and objects

Started by
8 comments, last by StoneMask 10 years, 11 months ago

So I have a fairly simple game that runs with a constant framerate, and I have objects that all do their thing in the game loop.

My first question involves things like projectiles and like objects that are spawned by other objects. Should calling the projectile's functions be the job of the object that it belongs to? The way I have it, my player object has a projectile object called, let's say, "ball", as a member object, and the player receiving input calls the function that causes the ball to move. The ball is initialized with a constant pointer to the player's position so it knows where to spawn. I then call its move() function from the player object, so each time the player object iterates through the game loop, the ball also moves. I also have to have the player call the ball's die() function for when the player leaves a room, because otherwise the ball hangs in limbo until the next time I enter a similar room, where it continues where it left off.

The second question is about programming the objects' behaviors. When I want my objects to do specific actions for a set amount of time, such as traveling a set distance, or flashing for a set amount of time, I've utilized a timer variable that is exclusive to that object that counts up to a point and then calls all the functions it needs to based on what position in the timer it's in.

So should my game loop handle spawning objects that are dependent on other objects, or should the objects that use them? And is using timer variables clunky/is there a better way?

Also, how big should my game loop be before it gets ugly? Say, if I have an adventure RPG going on with the complexity of the original Legend of Zelda. Most of my questions here are about conventions, lol. What do you guys do/recommend I do, based on personal experience?

Advertisement

So I have a fairly simple game that runs with a constant framerate, and I have objects that all do their thing in the game loop.

No you don’t. You have a game that appears to be running at a steady framerate but in fact is always off by a few nanoseconds either way, more so on older machines, and possibly even on your machine when too many objects appear.
If you are basing time in your game off how many frames you have rendered you are doing it wrong.

My first question involves things like projectiles and like objects that are spawned by other objects. Should calling the projectile's functions be the job of the object that it belongs to? The way I have it, my player object has a projectile object called, let's say, "ball", as a member object, and the player receiving input calls the function that causes the ball to move. The ball is initialized with a constant pointer to the player's position so it knows where to spawn. I then call its move() function from the player object, so each time the player object iterates through the game loop, the ball also moves. I also have to have the player call the ball's die() function for when the player leaves a room, because otherwise the ball hangs in limbo until the next time I enter a similar room, where it continues where it left off.

You are far from the mark here. Both the ball and the player are world entities and are updated by the world manager. One does not update the other. The player class does not update the ball. The world manager updates all objects in the world. It’s that simple.
You’ve already encountered one of the most obvious reasons for this: If the player stops updating so does the ball.

You’ve made a connection between the player and the ball because you know from a high-level design-based view that the player owns the ball as its weapon.
That high-level design-based idea does not apply to the low-level implementation of the system in which their only relationship is that the ball came from the player’s position and travels in a line based on which way the player was facing. Aside from spawning the ball, there is no relationship to the player at all.

Spawning the ball is done at higher-level logic, specifically it would be scripted if scripts are available or coded into the “game” layer of C++ otherwise. The “engine” layer would handle updating and moving the ball.

The second question is about programming the objects' behaviors. When I want my objects to do specific actions for a set amount of time, such as traveling a set distance, or flashing for a set amount of time, I've utilized a timer variable that is exclusive to that object that counts up to a point and then calls all the functions it needs to based on what position in the timer it's in.

These kinds of timers are implemented by setting them to the amount of time before they need to trigger and counting down until they reach 0.
The reason is to save memory; with your implementation you need to store both the current time and the target time.
Just store the current time but have it always count down to 0.

So should my game loop handle spawning objects that are dependent on other objects, or should the objects that use them? And is using timer variables clunky/is there a better way?

As I mentioned, this is a high-level task which should be done at a scripting or equivalent level.
No timers are not clunky. If you want a time-delay before spawning, use a timer. Again, it could be as simple as something that always counts down to 0.

Also, how big should my game loop be before it gets ugly? Say, if I have an adventure RPG going on with the complexity of the original Legend of Zelda. Most of my questions here are about conventions, lol. What do you guys do/recommend I do, based on personal experience?

Your game loop should not contain any game logic. It gets ugly the second a single line of game logic is written into it.
General Game/Engine Structure

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

I was suffering a little mental interference at the time of your response, but I started typing a response and it was erased when I accidentally closed my browser. I'll respond with any questions and observations once I'm done reading and understanding your two articles on the subject. Thank you for your response. (:

I have a few questions, because I keep trying to proceed but keep stalling because I don't understand some of these things.

MSDN's MIDL 64-bit porting guide says not to use __int3264 unless completely unavoidable due to porting in some specific cases:

MIDL supports the 32-bit/64-bit polymorphism by means of the __int3264 intrinsic integral type described later in this document. However, this type should not be introduced to a remoted interface unless it is unavoidable because of porting. New interfaces should never use it. You should make up your mind as to the nature of data manipulated remotely and specify either an __int32 (a long) or an __int64 (a hyper) as appropriate. If the field or argument is really designed to hold a pointer, an appropriate pointer or context handle type should be used.

What is a "remoted interface"?

Can you explain __stdcall? I'm trying to make sense of MSDN's explanation, but I'm still not sure in plain words, what it's supposed to do.

As a side note, for the longest time I was confused as to why you preceded everything with "LS" (LSVOID, LSUINT32, etc.) but I realized after far too long that it because of the L. Spiro thing you have going on. I was also confused by the presence of CGame and why I had a class that inherited from it. Is this something that you made? Or is it something that I have to make?

When programs need to communicate remotely it is necessary for them to agree on the sizes of the data they will be transferring between them. It is talking about data that you are going to send to some other application, probably across a network, to be further manipulated/read.

There are 3 primary calling conventions (ignoring __thiscall and __clrcall) and understanding what the stack is is a precursor to understanding any of them, so I will assume you do:

__cdecl: If I call your function, I am responsible for setting the stack pointer back to where it should be for “my” own stack.

__stdcall: If I call your function, your function is responsible for setting the stack pointer back to where it should be before it returns for my own work to continue.

__fastcall: I will call your function by putting the first 2 arguments into ECX and EDX and it will put the stack back where it belongs before it returns for my own work to continue.

The purpose of hiding these behind macros is that not all compilers support these keywords (GCC), and in those cases the macros are simply left blank.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

So what about CGame? From what I've read, CStateMachine is a class belonging to CGame that manages one or more CStates. But here in your article it says that CGame is provided by "the engine". The engine I make or one that you've made? What is part of CGame? Also, what is the significance of returning LSENEW? Do I have to understand all your engine's definitions in order to understand this tutorial? Can you put it in plainer words?

Is the reason you have __stdcall in order to manually have the next line of code be called in one of the separate states?

If you are using my (for example) engine you don’t make your own engine. If you are using my engine, CGame is provided for you. You inherit from it as follows:
CMyGame : public CGame
As for LSENEW:
#define LSENEW new
No you don’t have to understand all of these terms. It doesn’t matter that they are __stdcall or using LSENEW instead of new. You only need to follow the concept.

No that is not the reason for __stdcall. You should forget about __stdcall. It is there for portability reasons and nothing else. It has nothing to do with states or the tutorial. I just removed it from the tutorial altogether.


As for what is part of CGame, that depends on your game. After you inherit from CGame, CMyGame will provide the game data, while your custom states will provide the game logic.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Once again, thank you for your help, and for putting up with my ignorance-- I'm not very familiar with naming conventions or many common programming practices, but you have helped me learn and pick some of them up. Thank you for setting time aside to respond to my comments, despite your impressively crowded résumé. (: I feel like I'm getting very close to having a new game engine up and running. So far yours is the only tutorial I could find detailing a state-driven kind of engine; all the others I found used singletons. Please put up with me for a little longer, and feel free to tell me exactly where I've screwed up or if I'm using any words wrong.

From what I've read of this, this is what I gather:

  • CGame is the game manager. It directs the objects that manipulate the game's resources, such as Graphics, Sound, and Input managers.
  • These would supposedly be in class objects owned by a CGame object (CMyGame), accessible by functions defined for CGame.
  • CGame doesn't own any CGameState objects-- it owns a StateMachine, which manages the Game States much like the Sound or Graphics managers manage their components.
  • CStateFactory is a simple class that creates an object that derives from the virtual base class CGameState (CMainMenu, CGameplay, CPauseScreen, etc.), and returns a pointer to the new object's location for the CStateMachine object to manage.
  • CGame calls whatever function that drives the game forward at every iteration through the game loop, which should not contain any game logic such as collisions, whether or not the player is dead, whether or not the room has changed, etc. etc.
  • This would supposedly be in, say, CGame's Run() function, which calls the Tick() function of CStateMachine, which would call the Tick() and Draw() function of whatever class derives from CGameState, where the game logic would be executed.
  • A pointer to CGame is passed to every CGameState class so that game logic that happens in each CGameState class could trigger a change in states, for instance, pressing the Pause button would call CGame->SetState(gsPause, X), the second parameter being either a simple information (level X, enemy script X) or a pointer to a more complex struct or class holding more information; possibly the player's stats, game world flags, etc.?
  • This is possible because INT_PTR/UINT_PTR is a pointer typecasted to an integer, so either the literal numerical address of the pointer or a plain integer are acceptable inputs
  • The CGame class would own a member object of the CGameState type, m_sNextState, which has a flag in it of whether the state needs changing or not, bSetNextState. This would be flipped to true if a state calls SetNextState from the CMyGame pointer.
  • This is necessary because if the state immediately changed in the middle of whichever CGameState's Tick() function with a more consequential change, such as from a Menu state to a Gameplay state, the rest of the Tick() function wouldn't execute, which may have been important in say, deallocating Menu's resources or playing an animation or tidying up or whatever.
  • The CGameState objects would contain other objects like players, enemies, buttons, etc. Only their positions, members, flags, etc. would be manipulated. The job of drawing them is up to the Graphics manager.
  • The job of allocating and deleting objects used in CGameStates is up to the CGameStates.

This is all I can think of off the top of my head-- I should have outlined this better, but is this pretty much the gist of it? Please tell me if I'm close to the mark.

I also have more questions, more about the structure rather than the syntax or conventions used but also about terminology:

1) Does the flag for changing the state on the next Tick() necessarily have to be in the m_sNextState object? I feel like it would be a little cluttered for the CGameState class, because it's not up to the CGameState as to when the state changes. I feel like that's more of the job of the CStateMachine, right?

2) What exactly is a script? You have as an example, EnemyScript3.ini as something that would be triggered by fitting 3 in SetNextState's second parameter. I have no idea what to think of scripts or how to implement them in C++.

3) Can you describe what a game engine is, exactly? What would this be in the code? The WinMain() function? The game loop? Some kind of class?

4) Say I have my CGame class all set up. So if I created five enemy objects in my Gameplay state, would I pass a pointer to an object that contains their location, sprite details (animation speed, current frame, etc.) to CGame's Graphics handler, via some function like AddObject()? I looked into it a little and it's been suggested that I use a hash map with a uniquely generated ID for each object of the same type so that they can all be drawn separately. Is this necessary, and does it sound like a good idea, or is there a better way?

Thank you so much for your help and responses.


  • CGame is the game manager. It directs the objects that manipulate the game's resources, such as Graphics, Sound, and Input managers.


I would describe it as the driving force behind the overall engine, as it dictates when logical and render updates occur and synchronizes other managers to these events. For a specific example, the sound thread will always be updating at a regular interval on its own, but to avoid delay on sound events the CGame class also tells it to update on each frame (this can be capped to once every logical-update duration).


  • These would supposedly be in class objects owned by a CGame object (CMyGame), accessible by functions defined for CGame.


Could be.


  • CGame doesn't own any CGameState objects-- it owns a StateMachine, which manages the Game States much like the Sound or Graphics managers manage their components.
  • CStateFactory is a simple class that creates an object that derives from the virtual base class CGameState (CMainMenu, CGameplay, CPauseScreen, etc.), and returns a pointer to the new object's location for the CStateMachine object to manage.
  • CGame calls whatever function that drives the game forward at every iteration through the game loop, which should not contain any game logic such as collisions, whether or not the player is dead, whether or not the room has changed, etc. etc.
  • This would supposedly be in, say, CGame's Run() function, which calls the Tick() function of CStateMachine, which would call the Tick() and Draw() function of whatever class derives from CGameState, where the game logic would be executed.


Correct.


  • A pointer to CGame is passed to every CGameState class so that game logic that happens in each CGameState class could trigger a change in states, for instance, pressing the Pause button would call CGame->SetState(gsPause, X), the second parameter being either a simple information (level X, enemy script X) or a pointer to a more complex struct or class holding more information; possibly the player's stats, game world flags, etc.?


If you had used any other example besides pausing this would all be correct. If you change states to a pause state you lose all current game information and it is not possible to perfectly resume the game then. Pause is handled manually as an extra layer you have to draw over your game by yourself. It is however possible to have the state machine manage a stack of states (as my engine does) and in this case you could still make the pause menu be its own state, just on top of the rest. This prevents the game state from being wiped from memory.


  • This is possible because INT_PTR/UINT_PTR is a pointer typecasted to an integer, so either the literal numerical address of the pointer or a plain integer are acceptable inputs


Typecast to an integer with the same number of bits as a pointer, yes. Meaning it is 64 bits on x64 machines.


  • The CGame class would own a member object of the CGameState type, m_sNextState, which has a flag in it of whether the state needs changing or not, bSetNextState. This would be flipped to true if a state calls SetNextState from the CMyGame pointer.


m_sNextState is not a CGameState class. It is a nested structure inside of CGame that holds the ID of the next state, data to pass to it, and the flag to indicate that the next state should be set.


  • This is necessary because if the state immediately changed in the middle of whichever CGameState's Tick() function with a more consequential change, such as from a Menu state to a Gameplay state, the rest of the Tick() function wouldn't execute, which may have been important in say, deallocating Menu's resources or playing an animation or tidying up or whatever.


Control would return to Tick(), but the object to which it “belongs” would have already been destroyed and you would likely get a bad access crash (if you are lucky).


  • The CGameState objects would contain other objects like players, enemies, buttons, etc. Only their positions, members, flags, etc. would be manipulated. The job of drawing them is up to the Graphics manager.


There are several ways to go about this. Any decent game engine should allow you enough control to have individual objects and manage/manipulate them freely, leaving you with that option, but they should also have a CSceneManager. Your state only needs to create one of these and then populate it with all the game objects and it will handle the rest.
Example:
bool CGameplayState::Tick( CGame * _pgGame ) {
    m_smScene.Tick( _pgGame );
    // Or-
    //m_smScene.Tick( _pgGame->DeltaTime() ); // Etc.
}
The scene manager would handle physics on everything in the world.
bool CGameplayState::Draw( CGame * _pgGame ) {
    m_smScene.PrepareToDraw( _pgGame );

    // <Insert Camera Updates Etc.>
    m_smScene.Draw( _pgGame, m_pcCurCamera );
}
And the scene manager will cull the objects, generate shadow maps, send objects to render queues, and perform overall high-level render management. Actual drawing later gets delegated to the objects themselves, not a render manager. Such thing does not exist in my engine, or exists only loosely in terminology.


  • The job of allocating and deleting objects used in CGameStates is up to the CGameStates.


It is but the use of smart pointers make this easy. My game states don’t actually have to release anything because all the objects are owned by the scene manager, and cleared easily via m_smScene.Reset().

1) Does the flag for changing the state on the next Tick() necessarily have to be in the m_sNextState object? I feel like it would be a little cluttered for the CGameState class, because it's not up to the CGameState as to when the state changes. I feel like that's more of the job of the CStateMachine, right?

States decide that they want to change to a new state.
The game class decides when that change happens.
The state machine (or state manager) actually makes that change.
m_sNextState is not related to CGameState.

2) What exactly is a script? You have as an example, EnemyScript3.ini as something that would be triggered by fitting 3 in SetNextState's second parameter. I have no idea what to think of scripts or how to implement them in C++.

Then you are not really ready, and they aren’t necessary for simple games. You can read about LUA for more information.

3) Can you describe what a game engine is, exactly? What would this be in the code? The WinMain() function? The game loop? Some kind of class?

Firstly, a definition: A game engine is a game framework in which the main loop is provided for you. A game framework is a collection of reusable game components that make it easy to build a game. Some argue that frameworks and engines are exactly the same thing, but there is a subtle difference—if the “engine” requires you to make your own game loop then all it is doing is providing you with a bunch of helper tools and classes for creating a game. That is a framework. It becomes an engine when it provides all of that but also provides the implementation for the game loop and causes you to work fully enclosed within it.

In code, of course it is not an engine unless you also provide the helpers for physics, rendering, characters, etc.
But to make it an actual engine would include WinMain(), the message pump, and the CGame class (which decides how to distribute calls to Cick() and Draw()).

4) Say I have my CGame class all set up. So if I created five enemy objects in my Gameplay state, would I pass a pointer to an object that contains their location, sprite details (animation speed, current frame, etc.) to CGame's Graphics handler, via some function like AddObject()? I looked into it a little and it's been suggested that I use a hash map with a uniquely generated ID for each object of the same type so that they can all be drawn separately. Is this necessary, and does it sound like a good idea, or is there a better way?

I don’t see the purpose.
But this question is too encompassing to be fully answered here.
Firstly, read back on what I said about the CSceneManager. It will answer must of this.
To repeat, the scene manager handles high-level orchestration of all of the game objects, and at the end, once the objects have been sorted by the render queue, each object is drawn in sorted order by telling the object to draw itself.
I chose this route because the way in which objects can be drawn varies to such an extreme that it would be too messy to try to generalize it all within a single manager. For example the way to draw GeoClipmap terrain is completely different from how standard models are drawn. And if you plan to support iOS you can’t (without hacks, which will get your app denied) use GeoClipmap so you need GeoMipmap as a fallback if you want to support that. Another totally different method of rendering.

This improves performance as well, since each object initially incurs the overhead of only a virtual function rather than every object having to go through a generalized graphics-manager call with if/else checks on every single possible render state, even if those render states are completely unrelated to the object being rendered.
That’s not to say that some things can’t be generalized. A lot of things can be boiled down to a collection of states that need to be set, and that can easily be wrapped into a structure for each object and passed off to some kind of manager, but I would stop there and let the actual draw calls be issued by the objects themselves. Later once you have enough different types of objects you can find out what they all have in common and push that off to the graphics library, but in order not to get stuck in a hole I would suggest doing that one step at a time.


As for the hash map, there is no purpose.
The model library loads shared models, and at a higher-level (inside the engine module itself in my case) there exists CModelInstance, which as the name suggests is an instance of a shared model. Only instances get loaded into the game world and since each instance is already unique, what would the purpose of a hash map be?


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

If you had used any other example besides pausing this would all be correct. If you change states to a pause state you lose all current game information and it is not possible to perfectly resume the game then.

I wondered about this; if you set a different class, the data in the last class would be lost. I've also read about the stack-of-states method. I remember it being a vector, which makes sense to me-- since the Gameplay state needs to start at the exact point it left off on, all its information would still be there in a component of a vector, just waiting to be switched to again after Pause is done.

And the scene manager will cull the objects, generate shadow maps, send objects to render queues, and perform overall high-level render management. Actual drawing later gets delegated to the objects themselves, not a render manager.

I assume these would be drawn to a backbuffer so the objects don't leave trails of themselves as they move, and the CSceneManager would have to have access to one so that it can draw the objects in order based on were they are in terms of their "depth" in the scene. So does the CSceneManager also have to consider the environment? In my case, bitmaps? Does the scene manager also handle game logic, or is it strictly for having the objects draw themselves? I can understand having the objects draw themselves-- a scene manager wouldn't know things like the flags an object has set for having certain animations. And since you say it would handle physics too, I assume that this is where the logic between objects happens? Not necessarily logic with input, but where objects interact in collisions and where they are drawn specifically-- you would call CGameState's Draw() which would call CSceneManager's Draw(), which would in turn have all the objects call their Draw()s.

Then you are not really ready, and they aren’t necessary for simple games. You can read about LUA for more information.

Not ready for what, exactly? To elaborate, the project I'm working on is a small 2D game my friend and I resolved to tackle over the summer, using C++ and DirectX. Not necessarily to make the most airtight and complete game, but one that forces us to learn new things and solve new problems-- we went into it not completely ready. But I like to ask questions in order to fill the gaps, because the learning experience and stuff. I will look into Lua. Is there a particular dialect you recommend?

Also, this just occurred to me, but is there a particular reason that you must pass a pointer to CGame to each function? Wouldn't it be more convenient to have the classes hold a constant member pointer that points to the CGame object at all times, or is there some vulnerability/is this considered bad practice?

This topic is closed to new replies.

Advertisement