Jump to content

  • Log In with Google      Sign In   
  • Create Account


#ActualMatias Goldberg

Posted 17 July 2012 - 01:16 PM

But it may be posible that some of these objects must, for some reason, stay persistent throughout levels or even game sessions. So, you can't delete them and create new ones, but you'll probably need to reinitialize most of it's variables to a default value.
For these cases a two-step approach is much better suited, where you just call init( bool firstTime ) rather than having to refactor everything or resort to copy-pasting the constructor into a reinit() function, then debug why reinit is failing (because you pasted more code than you should, or forgot to copy something).
I also use this two-step approach when suitable.


And that is, in my opinion, where your design went bad. It only seems that the two-step approach is "much better suited" because earlier on, you failed to separate those parts of your game object that survive a map change from those that are map specific.

By knitting them both into the same class, you created a weird amalgamation which will cause raised eyebrows in many situations: if you want to save the game's state, you would have to carfully check what is permanent (= you save it) and what is level-specific (= don't save it, but pull it from somewhere after the object was loaded). If the map is unloaded, only a subset of the methods of those game objects may be used (= be careful to document which methods you may use when displaying eg. player stats in the menu without a loaded map).

Whether you implement such two-stage initialization with Init()/Shutdown() or Reinit() are just details - it's both two-stage initialization. Code duplication would be avoidable in both cases.

I won't deny that went wrong. But unless you're coding Tetris or Pac Man, design issues will always come out given enough scope. I do actually separate persistent from non-persistent data; but combinations were trickier than I anticipated.

These are all different kinds of reinits you should take into consideration (although some may not apply to all kinds of games). They're not the same and are treated differently:
  • Level reloading. Player died. Reloading should be very quick to prevent frustration. Of course, a well balanced game should prevent a player dieing often, but that's not technical issue. Anyway, YOU are going to die very often while balancing the game, and high reloading times don't help
  • Object reloading because a new level was started or a different area was reached. This is usually taken into consideration.
  • Object reloading for memory & performance: It's still the same level/area/whatever, but memory is going out of the charts and your engine is capable of destroying objects which are no longer needed until the player goes closer again to them. This is usually taken into consideration but rarely implemented the right way.
  • Reloading for in-place editing: This is often the most overlooked, the most versatile one (which is what makes it hard), and one of the most relevant! Iteration becomes very important into making a great fun game. And real-time editing is key for improving iteration. The point of this kind of "reload" is to prevent the designer from closing and opening the program again each time he makes a change. This could be a GUI modification, a stat value change, a different placement of an object, a change of size. It can go worse, it could be a change to a value used to precompute something at level-loading time. And you need to implement this kind of reload to be faster than closing and opening the game, not crashing the game (i.e. dangling pointers? div. by zero?), inconsistent states (most objects still using the old values)
These are all reloads that may be treated differently (specially the last one). And given enough complexity they start to become a bit contradictory, just in the same way that GPUs are faster when sorted front to back, traversing by shadowbuffer to save switching rendertargets, traversing by surface type to save switching shaders, and traversing by skeleton to keep the animation caches nice and warm, supposedly all at the same time (Yes, I just quoted TomF's blog's Scene Graphs article).

Oh, and I forgot... keep it FAST. In place editing can be done the right way. But then you get Blender-like or Maya-like performance. It's good, but nowhere near good for a real-time game. Or you can build your game to run very efficiently, but then there's a lot to preprocess or tag as "read only".
And make sure your reloading for "memory & performance" is done in the background. Framerate spikes are very bad for gameplay experience.

This is, among many reasons, why some engines opt for two different executables for the game editor and the game itself rather than one. That's ok, but just make sure you have the resources (namely time & money) to keep two different projects up to date. A brilliant design can minimize the effort to keep them both synchronized, but who said it was easy?

And like you said, avoiding code duplication is the key. And I can't make more emphasis on it. May be that part was missunderstood from my post? I never vowed in favour of duplicating code or tried to implied I ended up doing that. It's the other way around, I was trying to imply how to prevent it.

Cheers
Matias

#1Matias Goldberg

Posted 17 July 2012 - 12:00 PM

But it may be posible that some of these objects must, for some reason, stay persistent throughout levels or even game sessions. So, you can't delete them and create new ones, but you'll probably need to reinitialize most of it's variables to a default value.
For these cases a two-step approach is much better suited, where you just call init( bool firstTime ) rather than having to refactor everything or resort to copy-pasting the constructor into a reinit() function, then debug why reinit is failing (because you pasted more code than you should, or forgot to copy something).
I also use this two-step approach when suitable.


And that is, in my opinion, where your design went bad. It only seems that the two-step approach is "much better suited" because earlier on, you failed to separate those parts of your game object that survive a map change from those that are map specific.

By knitting them both into the same class, you created a weird amalgamation which will cause raised eyebrows in many situations: if you want to save the game's state, you would have to carfully check what is permanent (= you save it) and what is level-specific (= don't save it, but pull it from somewhere after the object was loaded). If the map is unloaded, only a subset of the methods of those game objects may be used (= be careful to document which methods you may use when displaying eg. player stats in the menu without a loaded map).

Whether you implement such two-stage initialization with Init()/Shutdown() or Reinit() are just details - it's both two-stage initialization. Code duplication would be avoidable in both cases.

I won't deny that went wrong. But unless you're coding Tetris or Pac Man, design issues will always come out given enough scope. I do actually separate persistent from non-persistent data; but combinations were trickier than I anticipated.

These are all different kinds of reinits you should take into consideration (although some may not apply to all kinds of games). They're not the same and are treated differently:
  • Level reloading. Player died. Reloading should be very quick to prevent frustration. Of course, a well balanced game should prevent a player dieing often, but that's not technical issue. Anyway, YOU are going to die very often while balancing the game, and high reloading times don't help
  • Object reloading because a new level was started or a different area was reached. This is usually taken into consideration.
  • Object reloading for memory & performance: It's still the same level/area/whatever, but memory is going out of the charts and your engine is capable of destroying objects which are no longer needed until the player goes closer again to them. This is usually taken into consideration but rarely implemented the right way.
  • Reloading for in-place editing: This is often the most overlooked, the most versatile one (which is what makes it hard), and one of the most relevant! Iteration becomes very important into making a great fun game. And real-time editing is key for improving iteration. The point of this kind of "reload" is to prevent the designer from closing and opening the program again each time he makes a change. This could be a GUI modification, a stat value change, a different placement of an object, a change of size. It can go worse, it could be a change to a value used to precompute something at level-loading time. And you need to implement this kind of reload to be faster than closing and opening the game, not crashing the game (i.e. dangling pointers? div. by zero?), inconsistent states (most objects still using the old values)
These are all reloads that may be treated differently (specially the last one). And given enough complexity they start to become a bit contradictory, just in the same way that GPUs are faster when sorted front to back, traversing by shadowbuffer to save switching rendertargets, traversing by surface type to save switching shaders, and traversing by skeleton to keep the animation caches nice and warm, supposedly all at the same time (Yes, I just quoted TomF's blog's Scene Graphs article).

Oh, and I forgot... keep it FAST. In place editing can be done the right way. But then you get Blender-like or Maya-like performance. It's good, but nowhere near good for a real-time game. Or you can build your game to run very efficiently, but then there's a lot to preprocess or tag as "read only".
And make sure your reloading for "memory & performance" is done in the background. Framerate spikes are very bad for gameplay experience.

This is, among many reasons, why some engines opt for two different executables for the game editor and the game itself rather than one. That's ok, but just make sure you have the resources (namely time & money) to keep two different projects up to date. A brilliant design can minimize the effort to keep them both synchronized, but who said it was easy?

And like you said, avoiding code duplication is the key. And I can't make more emphasis on it. May be that part was missunderstood from my post? I never vowed in favour of duplicating code or tried to implied I ended up doing that. It's the other way around, I was trying to imply how to prevent it.

Cheers
Matias

PARTNERS