About object creation, updating, interactions, Unity

Member
Posts: 749
Joined: 2003.01
Post: #1
This is a bit of a software design question/comment.

When programming a game with software like Unity, you have an object, like a spaceship or something, which you script with instructions to execute at every frame.

If you want it to interact with something other object you need to pass it a pointer to that object.

Most physics interactions are handled automatically by the system, preventing you to from having to use pointers to everything else.

Objects can be created at runtime and you can also avoid to keep track of them if they are individually scripted to destroy themselves at some point.

At first this all seems very modular, very pretty.

But then you start needing more control, having objects nested in groups of objects, having more and more interactions between objects and groups, so ultimately you end up passing so much stuff to each object (pointers to other stuff), that you wonder if all this modularity was a good idea in the end. Plus you kind of lose track of the order of interactions etc.

My design philosophy so far was very different: An object has to be handled and destroyed by the object that created it. If an object can live longer than his "parent" object, then it should not be "son" of that object, it should be handled at a higher level, up to the "game" object.

Every parent should keep track of its sons.

And I like to have interactions between objects handled by their common "parent".

So yeah, I think if you are doing a game where interactions and complexity of the hierarchy is low, an approach like unity's "every object take care of itself" works, when the hierarchy and interactions are complex I think it gets messier than a "top-down" approach.

What do you think? what approach do you like best for your games?

©h€ck øut µy stuƒƒ åt ragdollsoft.com
New game in development Rubber Ninjas - Mac Games Downloads
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #2
Once I spawn and object and it's not a child of anything, it's on it's own in the universe. I haven't had many problems with that. Back in the day though, I too did it where the parent was in control of everything. That broke when I started getting into 3rd party physics libs.
Quote this message in a reply
Moderator
Posts: 452
Joined: 2008.04
Post: #3
This is a great topic for discussion. Scott and I have been doing a lot of Unity stuff lately, so I have some comments on the component based approach. There are a few things I like, as you mentioned, you can often create an object in Unity and forget about it, since it is equipped to handle itself. Missiles can destroy themselves when their timer is up, etc.

As you create scripts in Unity, you can apply them to any set of objects in the game. I have found that it does help decently with reusability. A script to start an animation can be reused on a door that slides open and to start a running animation. We've had good experiences with sets of scripts that are triggered by buttons. They interact very nicely and can be combined to make complicated scripted behaviors.

There are a lot of downsides to Unity's approach also though. The hierarchy in our scenes simply gets too large to be manageable. Our most recent level has 1441 items in the hierarchy- it's just a complicated level with a lot of moving parts and scripted actions. Even putting them in a well organized tree ends up being a mess. I guess the problem in this case is the expectation that you're going to edit things via a presented list. That's probably not the case with whatever you roll yourself, regardless of the parenting methods.

A more severe problem with the update loops (and Start/Awake invocations) you mentioned in Unity is the lack of control over the order. You cannot depend on initialization order- if you need your game manager to be set up before you set up your player, you might be out of luck here. You can write your own solution to this problem to enforce setup order; the game manager could be in charge of initializing the player...but that sucks- you have to organize your code around that issue, and not put the calls where you'd otherwise feel they belong. It particularly feels awkward to me, since I've programmed in other environments where I have explicit control over what happens when, and now it's not a guaranteed order.

Unity does have a parent-child relationship system, so maybe it's possible to do what you're looking for Najdorf. The parent transform does apply to the children though, so I guess it only works if there's a physical link between the parent and the child... it's tracking location in physical space more than lifetime or owner, like you're describing.

On the other side of the issue, we've mostly done flat lists like AnotherJake mentioned for our objects. We might have a list of sprites, a background, a list of balls, and that's about it. I think that depends on how many different types of entities are in your game and how complicated scripting gets and the interactions between the objects.

We do organize our drawing in a scene graph in some of our projects. We had no problems keeping lists of objects in one place and also keeping a tree of draw calls in a scene graph.

Howling Moon Software - CrayonBall for Mac and iPhone, Contract Game Dev Work
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #4
I just realized that it's not strictly true that I let all objects take care of themselves. There's just one array of all the objects in the entire scene, and all objects are subclassed from the basic entity class (I say "class", which is a bit misleading since it's all in C, but that's the best terminology). The reason I mention this is that even though I typically let objects decide their fate on their own if they aren't parented to anything, they are in fact parented to that one array, and if I have a special handler that wants to search for all the bullets from some particular character and turn them into butterflies, I can.

The way Unity does it, I don't know. It sounds like some of the limitations you guys have run into are more specific to the way Unity ultimately handles things, rather than the general technique of allowing objects to take care of themselves.

This is indeed an interesting topic -- perhaps one of the most interesting topics in game development. One of the things that makes it so interesting to me is that I've tried things probably a dozen different ways over the years, now that I think of it, and I *still* haven't completely settled on one best way to do it. And now that I'm starting to peek into the world of network multiplayer, setting it up for that at the same time adds yet another challenge.
Quote this message in a reply
Member
Posts: 749
Joined: 2003.01
Post: #5
One simple example I'm thinking is a spaceship that shoots a bullet, which you want to have in the "universe" (as the spaceship).

My typical way of handling this is having a pointer to "universe" in the spaceship so I can access everything, creating a temporary bullet object with the desired parameters, then pushing (i.e. copying) this objects in the universe's bullet list or array.

Do you do this the same way? I understand I'm losing modularity 'cause now spaceship needs to have an universe object (and a bullet list) around it to work.

Also while I understand giving objects access to universe (hence everything else) is often inevitable, I usually prefer not to use it if I can.

A stupid example, I prefer doing something like

for (i=asteroids.length-1;i>=0;i--)
{
if (collision(spaceship, asteroids[i]))
{
asteroids.removeElementInPosition(i)
}
}

than making a method Spaceship.checkCollisionWithAsteroids(), having it access and destroy asteroids through its universe pointer.

©h€ck øut µy stuƒƒ åt ragdollsoft.com
New game in development Rubber Ninjas - Mac Games Downloads
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #6
I am increasingly of the opinion that "object oriented design" is not only not the right thing for games, but downright counterproductive.

Recently I've been treating game state more as a database, in that any piece of update code is allowed to inspect and update any piece of state, and partitioning the functionality however makes sense. That seems to work well.

As you all have found, with objects, you quickly end up having to keep track of a spaghetti graph of things, just so that the class where you choose to put the code actually has all the information it needs to do the work... and you still end up with O(n^2) interactions where a single-dynamic-dispatch language gives you no sensible class in which to put the code.
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #7
Najdorf Wrote:Do you do this the same way?

I used to at one point, but not anymore. I have a system I've come up with which uses no dynamically allocated memory after initialization. It's incredibly simple but very hard to describe. Basically it's all static arrays (not actually static C arrays but created from a special "class" (for lack of a better term) I created for handling arrays), and everything is referenced by ID (just an int). There is no proper object-oriented design pattern that I follow. Everything is available to anything else, anywhere, at almost anytime (with threading restrictions of course). So since everything is referenced by ID, there is no need to pass any pointers or copy any information from one place to the next. I'll have a central collision detector or physics lib that will enumerate through a master list of entities, which is a game "state", and I enumerate through all entities for update and dispatch via function pointer to each entity's custom update handler, then there's a central renderer which enumerates through whatever is still alive in the state and dispatches to the proper draw routine for each type, either via switch statement for known types or via function pointer if it's custom. That's pretty much my entire game engine design in a tiny nutshell.

A small collision example: if an asteroid was detected to have collided with the spaceship by the central collision detector (spaceships and asteroids don't do collision detection themselves), the asteroid's collision handler is called with the spaceship's ID and the spaceship's collision handler is called with the asteroid's ID. The spaceship would reference the ID in the master array and see that it got hit by a small asteroid and would either take damage or blow up, and the asteroid would reference the ID and see that it got hit by the spaceship and split into two smaller asteroids and spawn them and delete itself. So collision detection is handled centrally, but the detector cares nothing about what just collided with what, and the entities that were collided with individually figure out how they themselves are to respond to whatever object just hit them by referencing the ID passed to them by the detector and figuring out what just hit them. [adding] Basically the collision detector detects collisions and that's it and then tells two objects, "You guys just collided with each other. Here are your guy's numbers to get in touch with each other. Discuss amongst yourselves. Have fun!"
Quote this message in a reply
Member
Posts: 749
Joined: 2003.01
Post: #8
Wow, sounds like a cool system Smile

Good to hear different framework ideas, there are for sure many ways to set things up.

With C++ I'm fairly satisfied of my rough approach (though yeah, I'm not really reusing classes much between games, except the obvious ones (math, physics...)), while Unity with it's super-modular approach suggests you to not keep track of stuff but in the end you realize you actually should have. And accessing other objects from your object while possible looks quite messy.

I remember my first days of coding with TNT Basic, where you virtually had no functions, no structs, only a main function and global variables (also locals if needed). So you had a global array shipsx[100], shipsy[100], shipshealth[100]....

Amazingly you could still do games in it, you just did it, maybe you used quite a bit of copy paste but in the end stuff was working (mostly).

©h€ck øut µy stuƒƒ åt ragdollsoft.com
New game in development Rubber Ninjas - Mac Games Downloads
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #9
Well, my approach (and Jake's, if I've understood correctly) more or less boil down to that.

Code:
typedef struct State
{
    struct {
        float x;
        float y;
        uint32_t health;
    }
    ships[MAX_SHIPS];
}
State;
Quote this message in a reply
Member
Posts: 749
Joined: 2003.01
Post: #10
So OSC how would you spawn a bullet from your ship?

©h€ck øut µy stuƒƒ åt ragdollsoft.com
New game in development Rubber Ninjas - Mac Games Downloads
Quote this message in a reply
Member
Posts: 312
Joined: 2006.10
Post: #11
OneSadCookie Wrote:I am increasingly of the opinion that "object oriented design" is not only not the right thing for games, but downright counterproductive.

Recently I've been treating game state more as a database, in that any piece of update code is allowed to inspect and update any piece of state, and partitioning the functionality however makes sense. That seems to work well.

As you all have found, with objects, you quickly end up having to keep track of a spaghetti graph of things, just so that the class where you choose to put the code actually has all the information it needs to do the work... and you still end up with O(n^2) interactions where a single-dynamic-dispatch language gives you no sensible class in which to put the code.
Doesn't this lead to problems when (or if) you're trying to parallelize your program (I'm not exactly knowledgeable about parallel programming, so I could be off base here too).
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #12
Najdorf Wrote:So OSC how would you spawn a bullet from your ship?

In that case, state might look like this:

Code:
typedef struct State
{
    uint8_t shipCount;
    uint8_t bulletCount;
    uint8_t _padding0[2];
    struct
    {
        float x;
        float y;
        float vx;
        float vy;
        uint8_t health;
        uint8_t _padding1[3];
    }
    ships[MAX_SHIPS];
    struct
    {
        float x;
        float y;
        float vx;
        float vy;
        uint8_t lifespan;
        uint8_t _padding1[3];
    }
    bullets[MAX_BULLETS];
}
State;

in which case spawning a new bullet is as simple as

Code:
state->bullets[state->bulletCount].lifespan = BULLET_LIFESPAN;
state->bullets[state->bulletCount].x = state->ships[firingShip].x;
state->bullets[state->bulletCount].y = state->ships[firingShip].y;
...
++(state->bulletCount);

or state might look like:

Code:
enum EntityKind
{
    ENTITY_KIND_SHIP,
    ENTITY_KIND_BULLET,
};

typedef struct State
{
    uint8_t entityCount;
    uint8_t _padding0[3];
    struct
    {
        uint8_t entityKind;
        uint8_t _padding1[3];
        union
        {
            struct
            {
                uint8_t health;
                uint8_t _padding2[3];
            }
            ship;
            struct
            {
                uint8_t lifespan;
                uint8_t _padding3[3];
            }
            bullet;
        }
        extraInfo;
        float x;
        float y;
        float vx;
        float vy;
    }
    entities[MAX_ENTITIES];
}
State;

In which case spawning a bullet is almost identical, but this way you can have code which works generically for both ships and bullets.

bronxbomber92 Wrote:Doesn't this lead to problems when (or if) you're trying to parallelize your program (I'm not exactly knowledgeable about parallel programming, so I could be off base here too).

I always have one state object per frame, not one globally which gets mutated. That helps with networking and movie recording and Braid-style time rewind and all sorts of things, so it's worth doing in general. That means that even if you separate your graphics from your update, you don't have a threading problem. The question is then, how do you get from state N to state N + 1? Unfortunately, I haven't got a great answer to that.

Option A is you start with a completely empty state object, and as things are updated, you add them to the new state object. That has the advantage of feeling nice and clean, but if two interactions affect the state of an entity, how do you know to perform both before committing the new state of the entity? I don't have a good answer to that.

Option B is you start with a copy of state N, and update it incrementally. Unfortunately, then you end up in order-dependence hell too easily... but if you could fix order-dependence hell, you could fix the multiple interactions problem in option A...

In order to thread more finely than "graphics+update", you need to manage dependencies between interactions which affect particular entity, using some kind of queue structure which only allows one operation per entity in flight at a time. It's hard, and likely to result in more overheads than benefits, but not entirely out of the question.
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #13
OneSadCookie Wrote:
Code:
enum EntityKind
{
    ENTITY_KIND_SHIP,
    ENTITY_KIND_BULLET,
};

typedef struct State
{
    uint8_t entityCount;
    uint8_t _padding0[3];
    struct
    {
        uint8_t entityKind;
        uint8_t _padding0[3];
        union
        {
            struct
            {
                uint8_t health;
                uint8_t _padding1[3];
            }
            ship;
            struct
            {
                uint8_t lifespan;
                uint8_t _padding2[3];
            }
            bullet;
        }
        extraInfo;
        uint8_t health;
        uint8_t _padding0[2];
        float x;
        float y;
        float vx;
        float vy;
    }
    entities[MAX_ENTITIES];
}
State;

That's it. That's where I'm at. The only thing I've been doing differently is linking to arrays of entity types instead of the union. Also, as I mentioned, I am using a specialized "Array class" instead of generic C arrays, so that I can more easily manage the arrays for things like compaction on the fly, but it's all the same concept.

State interpolation is what I am now just trying to conceptualize. It'd be easy except that what do you do when an entity exists in state A but not in state B? Or doesn't exist in A and exists in B? One can simply fade for the frame interpolation, but what do you do about interpolation for stuff like physics where an entity can't *sort of* exist! Or what if I want an object to be at X and then instantly at X + 20 at some point between A and B, but I don't want it to animate between the points; I want it to instead instantly appear at X + 20 at some point in time?

I've been toying with the idea of what you call Option A where you build up the state each update. But the problem then persists that the indexes of the arrays won't be consistent if existence changes for any of the entities. One approach I've come up with is perhaps adding a state variable for each entity which tracks its state of existence, like specifically, when it was or was not existent. But then, when can that entity actually be removed from the game state? Another approach might be to have yet another separate array to track the state of existence of all entities but have it track at a specific time interval so that if an entity is known to exist at T1 but not T2 and the interpolation is greater than T2 then the entity can be safely disposed of from the state array.

Adding state interpolation is a real mind-rape. Wacko
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #14
Snort... Looking at the union, for some reason it didn't occur to me that perhaps my idea of linking to specific entity types might not be as memory efficient as I had first thought. I guess I was thinking of particles, but you can do a dynamic allocation for a particle generator as a special case if need-be... Or thinking of special huge types, but again, special-case. In practice, *most* entities aren't all that different in size from the others in terms of memory footprint. ... poor union gets dismissed too easily by me all the time. Sad
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #15
Another thing I haven't said here is that I typically have *two* state structures, one for "server-side" state, which participates in networking, movie recording, game, saving, etc.; and one for "client-side" state, which is just visual finesse and doesn't need to match between clients, or between saving and loading, like particles.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  updating to ios5 recorder doesn't work anymore sefiroths 3 3,988 Nov 21, 2011 03:56 AM
Last Post: sefiroths
  Updating Map Annotations - coordinates PHANTOMIAS 0 2,571 Dec 8, 2009 08:39 AM
Last Post: PHANTOMIAS
  Browser Based.. Updating turn times? guest_05 1 3,002 May 24, 2007 04:03 PM
Last Post: Tobs_
  Updating only parts of the screen Josh 1 3,056 Dec 18, 2002 02:38 AM
Last Post: kelvin