Page top

2011/02/03 (Villainmad): Object-Disoriented Design

Created:

Always use secure-HTTP / Secure HTTP / Permanent Link

So, with a more sensible solution to last week's problem out of the way, let's talk about objects in the game-world.

Preamble: I've decided not to obsess myself with making the game INFINITELY FLEXIBLE, but for certain things, it's actually easier to code in flexibility than to create different cases in the C# end of things. (Where it is less convenient, however, I will hardcode stuff in. For instance, the player is going to be a traditional shmup-player whom you move around with the arrow-keys, and the other buttons will be Esc, Z, X, C, Left-Shift, and Left-Ctrl1 by default. And I'll have to figure out an options-page with which you can change key-mappings ...)

Now: without wasting space and time going into too much technical detail: every object-type will have its own drawing-layer. The game-creator will define every type of game-object (i.e. enemies, bullets, items), and which ones they'll do collision-detection for, and what collision will entail (i.e. "if a bullet hits a player, the bullet disappears and the player takes damage according to the bullet's properties.").2

I will be using a variation on the linked-list method described in this thread on the XNA forum for handling all the game objects and their draw-order. I will be using a List instead of an Array to store the objects themselves, and add new bullets to the end and "if the last bullet in the list is 'deleted', remove it until the last one in the list isn't deleted anymore" as needed, in order to bypass that "capacity" deal. The game-entity structs themselves will contain:

  • The creation-frame (int)
  • The position (Vector2)
  • The velocity (Vector2)
  • A pointer to a data-table which contains data for that particular game-object (in the "LuaTable" format included by LuaInterface).3

And, as I said in the comment I mentioned, you will keep track of them using an object-ID struct, containing the creation-frame and its position in the list (again, the reason for this is so that, even if an object inherits a position in the list from a previously-deleted one, it can't inherit the creation-time), as well as what layer it goes in.

Now, the data-table (it can be an object-instance, but it doesn't have to be) will have some basic fields like "texture" and "health" and whatnot. However! You will also be able to optionally create functions on this table named init() (called once before it does anything else), draw() (called every frame and replacing "just show the image"), update() (called every frame before doing the drawing), and its own collision() (or proximity) function (called every frame, once for every extant object it can collide with, overriding the one in the data-table). These functions will each be passed the data in the object-ID structs (and that of the other object in the Proximity function), and will have getters and setters for the values in the game-entity structs.

This will be presumably enough that you can do pretty much anything with 'em. So much for not obsessing myself with flexibility ...4

1I have no idea what I'm going to do with C and Left-Ctrl. But someone (who wants to mod things) might!

2Technically, what's actually happening is, the so-called "collision" function would be better called a "proximity" function: rather than only firing if a bullet (or other "colliding-with-player object") is a certain distance away, it always fires, and the first thing the player-to-bullet collision does is, "If it's too far away to even graze, do nothing." I figure it doesn't matter whether it's the Lua-function or the C# that checks how close they are; I mean, in the grand scheme of things, what difference does it make?

3For the record: all references to Lua tables (including the LuaTable type in C#) are just pointers. If foo is a table, and you do "bar = foo" in Lua, bar isn't a duplicate of foo, it is foo by another name, so to speak. If you subsequently do "LuaTable baz = lua.GetTable("foo")" in the C#, anything you do to baz will also happen to both foo and bar.

4Technically, if I wanted to be less flexible, I could just have the C# hardcode the number of layers and their collision-relations. But that would just be stupid.

11 Comments (auto-closed) (rss feed)

sRc

im still kind of skeptical of it being a linked list, after the massive performance problems I got trying to use one in my test (which still could have been me just horribly misusing it but I haven't read anything more on them about any different way to use them since), but sounds like youre coming along well.

Kimiko Muffin

Yeah ... it'd be great if there was an easier way of reliably track of objects at an arbitrary position in a list even after you remove something earlier in the list, but ...

I wonder if I ought to head to the TigSource forums, which helped me out with another stupid thing I was doing.

sRc

well switching over to a regular indexed list instead of the linked list solved the problem I was having quite swiftly, because with an indexed list you can jump anywhere into the list you want at any time.

sRc

which wasnt particularly the problem I was having with the linked lists, the problem I got was because you couldnt just delete and move on, it had to start over from the beginning of the list whenever deleting something, which made iterating through it to perform multiple actions a lot more difficult by scales as the list grew in size

Kimiko Muffin

Aha. I'll try that, then~

MWchase

Man, flexibility. The theoretical capabilities of my planned architecture are insane. I really should be focusing on getting the stuff I actually need to work, working, though.

Then, once I do, it'll probably push 10FPS because I overloaded it with cleverness :/

("And then it resumes a coroutine and passes the result to a FSM, which may change the running coroutine. Also, there's a special bit of global state that may glitch if someone founds a civilization based upon playing a single stage of this game for a length of time an order of magnitude larger than recorded history.")

Although the implementation will be utterly insane, if I do notice anything generally helpful with implementing options, I'll let you know. (I suppose if it were really insane, I'd be using Lunatic, but I don't want to think about what that would entail.) Anyway, should probably get my own devblog, so I can horrify people with my weird code decisions in a centralized fashion.

Kimiko Muffin

I'm going to imagine that parenthetical bit in Crazy-Colonel's voice. Partly because my brain's first reaction to "FSM" is "Flying Spaghetti Monster" instead of "Finite State Machine." :3c

A chicken passeth by

Heh, when I took AI programming classes back in the day, we had a meme that went "Yo Dawg, I heard you like State Machines, so I put a State Machine in yo' State Machine so you can Idle while you Idle."

Zemyla

You should be using some kind of space partitioning method. The simplest one would be binning, which would reduce the search for intersections with the main character and the enemies by a factor of the number of bins involved.

Here's a link to someone who had a similar problem.

Kimiko Muffin

Ah, splendid! I shall have to look that over thoroughly to make sure I really understand it, and extend it to deal with multiple hitbox-sizes.

Wyrm

In the demonstration code, each object can belong to multiple partitions (pay attention to RegisterObject() — multiple cell IDs are registered for each object). Presumably because they are in fact extended and can straddle a boundary. It should work unmodified, as long as your bounding rectangle calculations are behaving properly.

(If you have a bunch of static/near-static objects, you can cache the cell IDs they belong to and only update those particular ones when they cross over a boundary. This can be tricky, however, and may not actually net you much savings. As always, profile your code.)