gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Game Design > Actor (sub)system

#27368 - Steve++ - Mon Oct 11, 2004 6:35 pm

I'm designing a platform game and because I'm new to this, there are a number of things that are new to me. I've heard Miked0801 refer to an "actor system" in a game's engine/core. I can only assume this refers to management of non-static objects in a game. I'd really like someone to discuss the scope and role of such a system and some approaches to its design.

#27372 - sgeos - Mon Oct 11, 2004 8:33 pm

Quote:
I've heard Miked0801 refer to an "actor system" in a game's engine/core. I can only assume this refers to management of non-static objects in a game.

You can look at a game as if the whole thing is acted out by actors on a stage. What exactly the actors are and what exactly the stage is is usually clear cut, but I suppose it could become blurry when we start dealing with things like moving platforms. (I'd personally consider that an actor.)

Quote:
I'd really like someone to discuss the scope and role of such a system and some approaches to its design.

The role should be fairly obvious. You need to be able to manage all the critters that act on your game stage. The whole stage and actor thing is just an analogy. I tend not to think of things as actors and stages, but rather as more game specific entities- roaming monsters, treasure chests and scripts linked event tiles.

I suppose the actors would be anything that could be replaced in an area without it being a new area. Treasure chests and event tile scripts are part of the stage. Roaming monsters are not. Power are not really part of the stage, as they really want to be spawned.

Again, here things become blurry. How different is a chest from a power up? It depends on the game. Can we spawn a chest? Sure. Should we spawn a chest? Maybe. Certainly randomly generated chests and chests dropped by monsters are going to need to be spawned.

I'll leave it at that and hope that somebody with more focused thoughts picks up where I left off.

-Brendan

#27374 - sajiimori - Mon Oct 11, 2004 8:56 pm

We use our actors for things ranging from invisible triggers to menu cursors to monsters. There's some waste from the fact that we statically allocate a pool of rather large actor structs, and very light-weight actors such as invisible triggers won't be using many of the struct components.

The benefit of static allocation is that memory usage is predictable and there's no fragmentation. If the waste is your bigger concern, you could use an inheritance hierarchy to have actors that are as light as a rectangle or as heavy as a monster.

#27401 - Steve++ - Tue Oct 12, 2004 8:05 am

Thanks. This was helpful, although further discussion is certainly welcome. On the topic of memory allocation and fragmentation, I once heard a game developer say that all their memory is allocated in advance at the beginning of each level, then freed in just one function call at the end. They must be using some sort of custom memory allocation routines, which is what I'm leaning towards.

Back on topic... I would certainly appreciate a more detailed discussion on the actor system. Cheers.
- Steve

#27413 - poslundc - Tue Oct 12, 2004 2:22 pm

Steve++ wrote:
Thanks. This was helpful, although further discussion is certainly welcome. On the topic of memory allocation and fragmentation, I once heard a game developer say that all their memory is allocated in advance at the beginning of each level, then freed in just one function call at the end. They must be using some sort of custom memory allocation routines, which is what I'm leaning towards.


From the sound of it they were allocating and freeing only one large object at a time, which means they wouldn't have any fragmentation. But such a scheme would barely be considered dynamic anyway.

I really believe static allocation is the way to go 95% of the time on the GBA. Even for entities like linked lists that naturally lend themselves to dynamic allocation, I always use a statically allocated pool and assign resources from there.

The overhead of a memory manager and the heap fragmentation that could result over a long period of time are just things I have no need or inclination to deal with.

Quote:
Back on topic... I would certainly appreciate a more detailed discussion on the actor system.


Then perhaps you should pose a more specific question that would inspire something to be said. ;) Most of us have done the subject to death already.

Dan.

#27423 - Miked0801 - Tue Oct 12, 2004 8:14 pm

Think of an actor system as an abstraction to get you away from GBA hardware and closer to real world problems.

Problem: My hero in a game needs to walk around a world and pick up items by colliding with them and pressing 'B'.

Hardware solution:
Hand code a module that takes care of user input, animation, collision detection with target object, updating, SFX generation, game state updating, and whatever else for controling the hero. Do the same thing for the pick up item. Spend a week getting the 2 to cooperate with each other. When another item needs to be picked up, increase the complexity more until the code becomes unmanagable and breaks or you give up.

Actor System Solution:
Create a generic actor system that has built in abilities to handle animation through simple tables, basic behaviors through simple state machines or function pointers, collision ability through a seperate system it registers with, and allows each actor a unique function that is called each game tic to do unique, one-off type activities.

Create a generic actor system ability for picking up items.

Now, create a Hero actor with a basic animation table, collide function, and input reading.

Create a generic pickup item actor that knows if the hero collides with it, will kill itself and update inventory by calling the the actor system pcikup command.

Problem solved.

Now, if you want another pickup item, you init another pickup type and it works automatically. If you want an NPC to talk with, create a basic NPC actor that behaves very close o the pickup, but instead of dying on collide/B press, triggers a dialog or whatver.

Say you want to take control of the hero for a special scripted event, you direct the hero actor to ignore input, and walk to a specified location, and start a new animation when he gets there of whatever.

By creating the actor system, it is so much easier to extend what you want your code modules to do.

This can be applied to almost anything - not just RPG games. Imagine creating a GUI by creating a Text Box actor, a buttion actor, and cursor actor, and special effect actors. When the menu is created, you create the needed actors, tell them where to display (and what), then let then watch for input to move or update. Makes for creating simple menus very, very quickly (I've created a typcial title screen type interface in under an hour quite easily with this type of approach.)

Hope this helps!

#27441 - Steve++ - Wed Oct 13, 2004 2:00 am

Yes, this helps quite a bit. Thanks buddy.

I particularly like the idea of using finite state machines for decision making. Not sure which model to use (Moore/Mealey) so I'll have to read up on that. I've never coded one up, but I'm sure it wouldn't be too hard.

Thanks everyone for your help. All advice has been taken onboard.

#27542 - allenu - Fri Oct 15, 2004 2:54 am

In Project Hazuki (http://www3.telus.net/allenu/Wiki/ProjectHazuki.html), I use things called entities, which is the same thing we're talking about here. They're basically any sprite-based object that can appear on the screen. Every entity has a state, which corresponds to its current animation being played. These animations can branch off to other animations or can loop forever, until acted upon by outside forces.

The great thing about conceptualizing this is that it's clean. You know exactly what an entity is and what its role is. In my system, it's just a "dumb" object. To act independently, it requires an input from what I call a "manipulator." A manipulator is basically something providing commands to the entity. For instance, if the entity is your player in the game world, you may want him to move up the screen or down the screen. The manipulator, in this case, is you. You provide inputs to the entity via the gamepad and buttons. The game engine would then somehow relay this input to the entity.

Because the notion of a manipulator is also well-defined and conceptualized, we can swap out the human providing the inputs to the entity and swap in some sort of AI algorithm, or even a script.

Finally, there are additional game rules which have the final say. I think of these as the physics of the game world. They limit what the entity is phsycally allowed to do within the contraints of the virtual world. Even though the manipulator tells the entity to walk up the screen, there may be a wall there, which the game world deems inpassable. The rules would then "tell" the entity to override whatever the manipulator told it to do. In this case, it may mean the entity just stays in his current location if there is a wall in his path.

As you're designing your game engine, think about how you can break things up into neat objects that interact with each other and well-defined rules. If you design it well, it'll make it easier to create game content out of your engine down the road (read: it'll mean less hacking).

- Allen

#27543 - allenu - Fri Oct 15, 2004 3:03 am

poslundc wrote:
I really believe static allocation is the way to go 95% of the time on the GBA. Even for entities like linked lists that naturally lend themselves to dynamic allocation, I always use a statically allocated pool and assign resources from there.


I agree. For an embedded application with limited resources, such as the GBA, you will want to make sure your algorithms and overall system are fairly deterministic. What I mean is that you should know in advance what the maximum number of sprites, items, NPCs, whatever, will be and set up fixed-size variables to hold them. Because your resources are so limited, you may also want to self-impose limitations on your system to ensure it always works.

If you use dynamic allocation and you don't watch your allocation closely, you may end up running out of memory or fragmenting memory such that you cannot allocate new blocks. If this happens, it's very bad news as you will likely be unable to recover. And if it does happen, you're going to want to debug how it happened, in which case, you will probably be so very close to your code that you will know the ins and outs of its memory usage that you won't need dynamic allocation anyway.

#27544 - sajiimori - Fri Oct 15, 2004 3:17 am

Welcome, Allen. ^_^

One nice thing about C++ is that you can override the 'new' operator for each class, so you can transparently switch between heap allocation and pool allocation or anything else.

#27547 - Steve++ - Fri Oct 15, 2004 7:34 am

Quote:
One nice thing about C++ is that you can override the 'new' operator for each class, so you can transparently switch between heap allocation and pool allocation or anything else.
It depends how you define 'nice'.

#27554 - poslundc - Fri Oct 15, 2004 1:55 pm

sajiimori wrote:
Welcome, Allen. ^_^

One nice thing about C++ is that you can override the 'new' operator for each class, so you can transparently switch between heap allocation and pool allocation or anything else.


Interesting idea... but you'll still have to maintain some kind of memory manager, even if it's using a static pool with a granularity of the size of the object in question. And if you want a general-purpose memory manager (ie. used for more than one class) you're still going to have to deal with a fragmenting heap.

I'm still inclined to stick with static allocation. ;)

Dan.

#27564 - sajiimori - Fri Oct 15, 2004 6:15 pm

Dan: I wasn't trying to present it as a new kind of memory management.

Steve++: 'Nice' is a very broad term, but here I was referring to the fact that you could change the memory management implementation without breaking client code.