This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Game logic tips & tricks

Game logic tips & tricks
by on (#56588)
Once again I'm working on an 8-bit project. This time around we decided to keep things simple from the outset and do a simple flick-screen platformer. Yet we're already three quarters of they way to the original deadline and it'd be a stretch to call what we've got so far playable.

I find the real problem is that I can never quite seem to get around to actually writing the actual game logic. I'll happily work for days writing elegant system-y type stuff but whenever I try to write game code it virtually always ends up a big stinking pile of unreadable special-cases.

Hence the reason why I created this thread. What tricks do you guys use to make the logic simple and efficient? Is application logic inherently inelegant or can it be handled with style?

To kick the discussion off I'll start by sharing a simple yet useful trick I (independently re-)discovered during the project.

My original version of the player's collision detection and movement code ended up a fragile mess just as I described above, as I kept having to add in new collision points and handle more and more edge cases yet player could still slip through the cracks in certain cases. The solution, in our case at least, was to offload the problem onto the level designer and stick with a single collision point at player's feet rather than trying giving the player a collision area. Then we merely had to widen the collision map around the edges to account for the extent of the player (which was straightforward since we had a separate collision/attribute map.)

What other tricks, great or small, do *you* know? Perhaps you've found a nice way of doing animation, a nifty method to get emergent behaviour out of simple enemy logic, how to make that big player state-machine manageable, etc.

by on (#56664)
Hmm, this is a hard question to answer clearly. I guess I try to program everything so that it works with virtual "slots" or "stacks". For example, I have a metasprite stack that the animation handler for each object (which works with animation "slots") uses to "push" metasprites on to be drawn all in the end. I have it set up so that each object handled with AI is given one segment of RAM (or a "slot", as I like to call it) that they can store the address of an animation "filmstrip" in as well as some other information. So for logic, I really try to make things simple and organized so that I can easily accomplish a certain task. Everything is pretty separate and true to its given responsibility.

I also try and make a lot of functions that are general purpose. Object-to-Player collision detection, for example, is all handled with the same code. All an enemy has to do is feed the address of RAM that its coordinates are located in as well as a width and height loaded in X and A into this routine. And actually, since AI is handled using indirect addressing to access variables specific to that enemy/object, all I have to feed into most universal routines for AI is a Y value, as all the routines use "lda ($xx),y" to access objects' variables. So with all these universal routines, like collision detection, displacement, jumping, falling, etc., unique AI scripts are really short, and mostly a couple of JSRs. True, there are some if/then statements, but for the most part it re-uses a lot of predefined code.

So I guess I could look at this type of system, and call it somewhat "elegant". Game logic can definitely be neat. I can't think of a system outside of mine that would work as cleanly off the top of my head, but I'm sure there is one. Again, I think this question is hard for me to answer clearly. It's possible everything I just said is a bunch of mumbo-jumbo to everyone!

by on (#56666)
Well I do it like Celius said pretty much. I have object-player, object-object collision, object-background collision and player-background collision. I have a routine that stores all 4 edges' to ZP variables and another routine that compare the variables and returns indicating whenever a collision occured with the carry flag.

I also have another routine that does anything an enemy would "normally" do for collisions, and call it "standard collision detection". It damages the player if he's not already invincible, and get the enemy damaged if it's in the player attack range. Of course whenever I want anything special, like having the enemy being transparent, or having a shielded side, etc... I shouldn't call this routine but do a variant of it.

I also have a routine that moves an enemy a variable number of (sub-)pixels, and takes background in account. But if I want to move the enemy while ignoring the background I don't call it and just do the addition right away.

Also now that I think about it I have every enemy comes with it's variables (H/V position, animation frame, walking counter, and 3 general purpose variables), but they can be made to other uses if their normal uses makes no sense (for example the I use the Hit Point variable on chests as an open/closed flag or something like that I don't remember exactly).

So to be sure nothing is messed up I made a clear list of what variable an object's AI can touch and what it can't. I'll just copy what I take in my notes about that :

Because enemies' programm is so unlimited, there is still things that I'll consider "legal" to do, and things I'll consider "illegal", similar to what is done in a object-oriented programming.
What is "legal" for a given object is :
- Change it's own set of variables (position, direction, screen position, custom vars, etc...)
- Create GFXs at a relative position of his self
- Attack the player and make him damaged, but only through a routine that "officially" calculate the damage
- Be attacked by the player, usually (but not compulary) by the standard collision routines
- Be attacked by player's projectile, and destroy his self in any way he likes
- Reincarnate into another object (enemy, items)
- Create a new item of any form if (and only if) the spot was previously free
- Change the set of variables of another object that the object have previously created. The way to be sure that it was effectively created that way is free.
- Request to the main programm to change the background tiles by the regular buffers, and only if those aren't already used for another reason.

Anything that isn't inculded on this list is "illegal", and should be avoided when coding custom objects programms (a.k.a. enemy's AI).

by on (#56677)
One trick I have found useful lately is to think in terms of states, sub states, and sub states of sub states, etc. As a specific example of how it cleaned up my game logic... my character has two ways he can die. He can die in battle, in which case a certain animation is played, or he can die by falling into a pit in which case no animation is played. I coded the "bottemless pit death" logic as a "sibling" to the "die in battle" logic, at first, and got some pretty weird bugs. Then I decided to basically have one overarching state of "dying," and define sub states of dying: dying in battle and dying by falling. This way I can always cleanly branch between differing states. It helps simplify the code if you can figure out what states can co exist and which ones are really refinements of a certain top level state.