Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411709 Posts in 69403 Topics- by 58457 Members - Latest Member: FezzikTheGiant

May 20, 2024, 04:16:55 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Game coding best-practices
Pages: [1] 2 3
Print
Author Topic: Game coding best-practices  (Read 9151 times)
blundis
Level 0
**


View Profile WWW
« on: March 18, 2010, 01:46:05 AM »

I tend to get an idea for a great game, and then immediately start throwing something together (code-wise). Most of the time I end up not finishing the game but that's another topic..

I thought I'd ask you guys how you usually arrange things and see if there's a better way than what I'm currently doing.

I inevitably start up with the standard get-a-window-up main function à la:
Code:
int main()
{
   handle_events();

   clear_screen();
   draw_screen();
}

Then most of the times I'll move on to the player. The player gets his own class, with an update and draw method that I call from main.

After this it's either time for the "world" or enemies. Most of the time it's the world, so I'll add the world class. Same thing as for player, update and draw methods that get called from the main function. But now I realize the player needs to know some data that's inside the world, like collision data, since the player is handling it's own movement inside it's update method, so I end up passing in a reference of the world to the player. This is where things start to go south..

Now I add enemies. Basically like the World class, except split into two classes, Enemy and EnemyManager. EnemyManager gets updated and drawn from main function and in turn updates all the enemies. But of course the player needs to be able to shoot the enemies. So a reference to the EnemyManager gets passed into player.

After this I start refactoring (hmm, this code in player is replicated in enemy, should the inherit from a parent GameObject class?), most of the time end up breaking something bad or get bored with the whole thing and go watch some TV.

I guess this is the way most people do it, but I don't know, keeping references to every single thing in the game with the player just seems dirty somehow.

So tell me, is there a better way, a truer path? How do you guys normally set things up?
Logged
st33d
Guest
« Reply #1 on: March 18, 2010, 02:52:09 AM »

References to the player is normal. But it's a lot cleaner and flexible if you abstract what the player is.

The player is simply a set of coordinates in most cases. So if you have a class that is a set of coordinates, have the player extend from that class. Then your monster is reacting to a coordinate object.

Why is this good?

Because the monster can be sent to seek out any coordinate on the map, to do scripted actions or regroup with other monsters.

I currently have a Character class for the player, his minions and all the monsters. It's now simple to add a lot of behaviours to any of them, because they all follow the basic template. But they also inherit from the coordinate class. So when the camera is focusing on the player, it's just tracking a coordinate object - I could send it to track anything.
Logged
blundis
Level 0
**


View Profile WWW
« Reply #2 on: March 18, 2010, 03:22:44 AM »

Abstracting, and by doing that being able to reuse code by treating all objects the same, is a great idea and something I try to do (although I normally bodge everything together first and then end up redoing it).

But that doesn't change the fact that you need to pass references to all the different things you need access to, like level data and such.

The other way that I thought of doing it (and have tried) is putting the main game loop in a class, and creating all objects from there. Then you pass along a back-pointer into the object so it can do stuff like (this example could be in the player class):
Code:
parent->world->collision(x, y);

The problem then is you lose encapsulation. The Player object needs to know things about the internal state of the game class.

A lot of people seem to be saying just do it, if it works it works and not care about breaking encapsulation and what not and I agree to a point, but I'm still interested in knowing if there are better ways of doing this.

Anyone want to show examples of how you normally do it?
Logged
jotapeh
Level 10
*****


View Profile
« Reply #3 on: March 18, 2010, 04:38:18 AM »

So tell me, is there a better way, a truer path? How do you guys normally set things up?

'Better' is subjective. I'll lend you a little insight into how I do it, you can judge for yourself what you think of the mess upstairs.

Anyway, vague mind maps aside, the idea for me is generally that the Player object is an entity, just like Enemies. And in fact, a lot of things are entities that don't even move, like trees, doors, treasure chests, etc.

In most cases, the Player object has no idea what is happening in the world. Nor does he need to. He knows what he 'wants' to do by user input. So within the 'behaviour component' of each entity, they basically mark down stuff they 'want' to do. AI can be given a reference to, for example, the Player coordinates (as steed said,) but do not give them free reign to the whole Player object unless you have a good reason. Usually for pathing all you need is Player coords and the relevant chunk of terrain.

After that, the game loop iterates through each behaviour component and decides what is permitted. This includes the player, who may be mashing 'up' to walk into a wall, but he's still not allowed to. sorry!

Handling collisions could be done by the World class, or you could do it with the current game loop. It's a comparison between two very different types of objects so yes, some encapsulation will seem to be flouted. For my entities, they all carry a 'collision component', which is their position and width/height. This allows me to create a generic function for collisions vs. terrain, and it applies to everything, enemies, the player, flying projectiles, etc.

Obviously there's a lot of ways you can take this but if you're a stickler for encapsulation and DRY, that's one of the cleaner ways I've found. My 2¢
« Last Edit: March 18, 2010, 04:41:31 AM by jotapeh » Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #4 on: March 18, 2010, 05:03:20 AM »

My approach has always been to create a sort of state machine.  Each state inherits from an abstract type called Logic, which has a abstract method called Process.  Process does two things, it does whatever needs to happen in that state, and provides a reference to the next state (usually itself).

I then create concrete types for all the major states of the game, things like TitleScreen, Game, Ending, Introduction, Credits... etc.  The main loop just keeps calling Process and adjusts the current state accordingly.

Code:
function Main_Loop return Integer is
begin
    Tickables.Calculate_Delta;

    Renderer.Draw;

    declare
        Next_State: Logic_Access;
    begin
        Current_State.Process(Next_State);

        if Next_State /= Current_State then
            Free(Current_State);
            Current_State := Next_State;

            if Current_State = null then
                return 0;
            end if;
        end if;
    end;

    return 1;

exception
    when Error: others =>
        Front_End.Error_Box(New_String(Exception_Message(Error)));
        Front_End.Die;

        return 0;
end Main_Loop;

Now, the game state actually "owns" everything in the game.  The players, the enemies, the world, and so on.  It's responsible for checking collisions, telling things they were hit, and so on.  This way, everything is in one place and I don't have to pass references all over the damn place.  This project is still very early, and I just have the players, but it ends up looking like this:

Code:
package Logics.Game is
    type State is new Logic with private;

    overriding
    procedure Initialize(This: in out State);
    overriding
    procedure Finalize(This: in out State);
    overriding
    procedure Process(This: in out State; Next: out Logic_Access);

private
    use FPS_Counters;
    use Font_Manager;
    use Heroes;

    type State is new Logic with
    record
        FPS: FPS_Counter;
        FPS_Font: Font_ID;
        Torr: aliased Hero(Heroes.Torr);
        Tarra: aliased Hero(Heroes.Tarra);
        Current_Hero: access Hero;
    end record;
end Logics.Game;

You may be over object-orienting things, and that may be the source of your problems.  Like someone else said, your in-game entities should basically know what they want to do, and the overarching system that owns them will tell them what actually happened, and they will react to that.  The player ideally shouldn't know anything about enemies or items or the world or anything else.  That responsibility lies elsewhere.
Logged



What would John Carmack do?
george
Level 7
**



View Profile
« Reply #5 on: March 18, 2010, 07:20:43 AM »

The problem then is you lose encapsulation. The Player object needs to know things about the internal state of the game class.

In my opinion the goal of encapsulation is not to prevent things from knowing about other thing's state, but simply to provide common interfaces with which to do so. So don't get so hung up on passing references or hiding private state, as long as you're creating coherent and common interfaces to get that information.

Putting everything in a 'game' state is OK but it really isn't much different from making everything global to the program. Why have the game state at all? I think a game state is better off as its own thing along with other objects being their own thing. Remember that code objects and game objects need not map one to one -- there doesn't need to be a single Player object which is the same for all purposes as the player in game.

Anyway that's basically what I've worked out for myself on the subject. 
Logged
nikki
Level 10
*****


View Profile
« Reply #6 on: March 18, 2010, 07:47:40 AM »

write to an interface instead of an implementation !!

Is something smart i heard once, considering the connecting of data.
Logged
jpetrie
Level 0
*


View Profile WWW
« Reply #7 on: March 18, 2010, 08:02:46 AM »

There's a lot to say on this subject; entire books have been written about writing well-structured code (see Code Complete) or about the fundamental principles of OO (single responsibility, open-closed, liskov's substitution, et cetera).

At the end of the day though, probably the most important thing to learn is the ability to refactor your code: to be able to change, generally in small incremental steps, with the goal of improving the code while keeping it as functional as possible. Once you are comfortable with this technique, worrying about "good ways to structure X" become less of a hinderance to your ability to actually get things done, because you know that if you commit to a direction that turns out to be suboptimal a few hours in, you can always refactor it.

This is basically a fancy way to say "do it, and learn from your mistakes." The more you do that, the better you will become at conceptualizing mostly-correct designs in the first place. You also end up growing your reusable code base into something far more robust.

That said, some kind of 'state based' system for tracking gameplay is pretty common, and should be a reasonable starting point. How you implement that is largely up to what you feel most comfortable with, although I would personally caution against extensive use of globals and/or singletons in the implementation because they can make refactoring harder (especially since, due to their promiscuous nature, they can force you to touch more code at once than you might otherwise have to to impart some change, and you generally want to make small changes whenever possible).
Logged

Josh Petrie | Scientific Ninja | Twitter | SlimDX
st33d
Guest
« Reply #8 on: March 18, 2010, 08:16:26 AM »

I'd say the best way that works for me is to plow through making that hacky mess with loads of cross-referenced crap. Make sure it works. Then I start tidying up and encapsulating things that no longer need global access for debugging. It's not how I often do stuff at work, because it means a lot of rewriting that I don't have time for. But at home it means I'm developing a rather smart framework - which I can then take into work...
Logged
blundis
Level 0
**


View Profile WWW
« Reply #9 on: March 18, 2010, 09:02:12 AM »

Thanks for some great input! It seems this is stuff that can't really be imparted until it's been experienced but still nice to have some input on it. In the end I think I'll just do it the way I've always done it (a.k.a. the st33d method), but I guess just thinking about these things might let me write better code.

Quote from: st33d
I'd say the best way that works for me is to plow through making that hacky mess with loads of cross-referenced crap. Make sure it works. Then I start tidying up and encapsulating things that no longer need global access for debugging.
Logged
David Pittman
Level 2
**


MAEK GAEM


View Profile WWW
« Reply #10 on: March 18, 2010, 09:06:01 AM »

At a very high level, my data is structured such that everything is accessible through a reference to the Game object. The Game class, besides containing the main loop logic and such, is pretty much just an encapsulation of global data. I don't really mind that--I don't believe it's a problem if globally-used data is globally accessible.

Game
|-Clock
|-Input devices
|-Renderer
|-Misc. other subsystems
|-Player Entity* (special case)
|-World
  |-Array<Mesh*>
  |-Array<Entity*>
  |-Array<Sector>
    |-Array<Mesh*>
    |-Array<Entity*>
    |-NavMesh
    |-kd-Tree
      |-Array<Mesh*>

Everything generally has a way to get to the Game object, and then everything else is reachable from there. The Player entity is the only entity that is specifically referenced by the Game, simply because there is so much player-centric code that it's faster to grab the player that way than by iterating the entity array to find a player.

I have a few redundant arrays, as depicted in that tree. Three separate classes maintain arrays of static geometry meshes, each with a different purpose: the world, which ultimately owns everything and is responsible for allocating and freeing the memory for those meshes; the sectors, which have a list of all their contained meshes for rendering; and the kd-trees, which store mesh pointers in their nodes for collision detection. Likewise, the world has a flat array of all the entities, while sectors also contain a subset of their local physical entities for collision detection.
Logged

oahda
Level 10
*****



View Profile
« Reply #11 on: March 18, 2010, 09:44:39 AM »

I approach it a bit like st33d and you do, Blundis.

For example, I am currently working on a side-scrolling platformer. I think the first thing I did was a base class for things that should have a position, and from that, I derived a more advanced class for objects that should also be rotated, scaled or have their centers moved. Then, believe it or not, I actually made the class for one of the planned power-ups, healthbar and stuff like that. Before, I had obviously also created stuff for displaying images.

After that, I began to approach some of the more heavy stuff. In about a week's time, I had my trigonometric collision checking done, put in another superclass. After this, I was able to finally start working on the actual platformer engine, which I made in a cluttered manner inside of main(). When I had it working (character movement and ground collision as well as jumping and falling), I started wrapping it up and turning it into a proper framework with a couple of classes. Later on, I added the slope support, and I'm currently doing a lot of changes; I'm splitting up and abstracting stuff even better, such as creating a base class for physical objects, out of code that previously resided within the character class, I'm
reworking the physics engine a lot, making it far more advanced, as well as generalising the code a lot more with new classes and type definitions in case we want to port it to a platform where we won't be able to use the same library.

Long story short; during the entire development, I use main.cpp as a testing ground and eventually move the code from there into new classes, and eventually, I will be able to make the file just a few lines long, in time for the final thing. I constantly update, move and refactor code to give it a better structure.

In one sentence, I start simple and ugly, and from time to time, I refine and enhance.
« Last Edit: March 18, 2010, 09:49:51 AM by Skomakar'n » Logged

jotapeh
Level 10
*****


View Profile
« Reply #12 on: March 18, 2010, 09:56:06 AM »

Thanks for some great input! It seems this is stuff that can't really be imparted until it's been experienced but still nice to have some input on it. In the end I think I'll just do it the way I've always done it (a.k.a. the st33d method), but I guess just thinking about these things might let me write better code.

That is pretty much the jist of it, it takes time and experience. I started a thread on game engine architecture and it kind of exploded into a "there's no best way to do things" argument, but just reading the insights from those who replied seriously was very helpful. It'll affect your process subconsciously and things will look much simpler over time.
Logged
BlueSweatshirt
Level 10
*****

the void


View Profile WWW
« Reply #13 on: March 18, 2010, 04:14:03 PM »

At a very high level, my data is structured such that everything is accessible through a reference to the Game object. The Game class, besides containing the main loop logic and such, is pretty much just an encapsulation of global data. I don't really mind that--I don't believe it's a problem if globally-used data is globally accessible.

Game
|-Clock
[...]

This is exactly what I do.

Along with this, I've started creating classes I call Delegates.

Instead of interfacing with library-specific code, game objects interface with a Delegate class I write, which in turn interfaces with the library-specific code.

It makes it much easier using something like SFML, and drawing sprites. I can simply tell my Delegate to draw a sprite with whatever parameters I want, then it will do all my dirty work.

Although, I also use level states! Level states are just child-classes of a base class Level, which I use to easily separate different game states from each other. I keep global data in the Game class, which anything in my Level class can reference to by accessing it through my Game class reference.
As mentioned, things like a player object are mostly just a set of coordinates. I'm shifting into a new kind of thought of programming-- You only need to represent what is there, not have a whole entity for what is there. IE: All the player object really does is display itself.
Logged

drChengele
Level 2
**


if (status = UNDER_ATTACK) launch_nukes();


View Profile
« Reply #14 on: March 18, 2010, 04:49:31 PM »

I'm going to jump on the "start sloppy, refine later" bandwagon. The whole "absolute encapsulation" bit is only enforced because of large teams, basically you don't want someone who didn't write the class to have access to its guts. In case of the lone programmer, the only thing encapsulation can achieve is hide the code from the guy who wrote it, which doesn't sound like THAT good of an idea to me. I started my programming career in Visual Basic which used Public variables and was proud of it, and I still cant' shake that from my mindset. But I know my code and it works ok and debugging is easy. And that's all that matters. But I admit there is something beautiful in a compact, encapsulated class. Occasionally I get a craving to clean up the mess...

A trick I am using for my game entities is extensors. Almost everything in the game is an entity, and entity itself is a very light class. But it has a (growable) array of extensors. Extensor is a virtual class which has an update() and render() that are called when ever the entity's update and render are called. So every functionality that the entity needs (collision, target seeking, artificial intelligence, turrets, particle generators, player control processing) is derived from the base extensor class. An entity will have as many extensors as it needs, and can have multiple extensors of the same type. Indeed the majority of my game's functionality can be said to reside in these derived extensors classes.

Another thing that greatly eases the development for me is using what I believe is called property-based development. That is, I hardcode as little variables as I need into game classes (world, tile, entity etc.) and just provide them with a custom container of string-keyed integer PROPERTIES. I even put in a hash function to boost lookup times. This is insanely useful for fast game prototyping, as you can add new variables to any entity on the fly without changing the headers. Not to mention it eases game scripting tenfold. Another plus is that properties can always be replaced with in-code variables towards the end of the project if a speed boost is sorely needed (it usually isn't).
Logged

Praetor
Currently working on : tactical battles.
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #15 on: March 18, 2010, 04:52:10 PM »

The best game coding practice is probably not to code games at all.  Then you can't make any mistakes.
Logged



What would John Carmack do?
Triplefox
Level 9
****



View Profile WWW
« Reply #16 on: March 18, 2010, 08:00:54 PM »

I rewrite my architecture just about every time I start a new project. Which is less and less these days because the projects are bigger Big Laff

My current rewrite has an actual technical design doc behind it, and it outlines these layers(woo, I can copypaste a lot of this info):

  • Liberation: My Flash/haXe/Python asset server/swf-builder/preloader thing. I haven't really promoted it yet cause I want to do a video tutorial or something, but it has wiki docs. Anyway, this handles "getting resources into the game." Being meant for Flash, it holds all assets in memory all the time, but more importantly, it lets me write parsers for different extensions so I can decide how they're stored and organized. And since it updates any asset in real time, it allows for fast iteration on anything stored in a data file.
  • App: App provides low-level engine services related to timing, startup, shutdown, and basic input/output. I'm building it to be bitmap-oriented and readily portable outside of Flash. I hold a single instance of "Game" inside App.
  • Game instance: The Game instance is held within App; it contains all the game-specific services and global state. Underneath the game instance are service instances for inter-scene state, recording, etc.. Also underneath the game instance are entity containers Sim and Menu. Game is run as a finite state transition system, wherein major events like restarting the game entail a discrete transition that manages all the required state changes(clearing instances, resetting variables, spawning new instances, etc.)
  • The entity containers each hold services, entity instances, and events. Services hold any number of references to components(for example, a physics service would hold both “scene” and “actor” components which hook into different parts of the service, but when the service ticks it would update both components.) Services allow entities to find each other via component-level lookups, rather than (e.g.) lookups across all scene entities. Entity instances hold unique ids, components, and references to each component's destructor. The entity container can spawn and despawn entities whle maintaining component refs. Finally, events based on delta timing and tweening, “instant” (same-frame) events, and “next frame” events are  processed from within the entity container. Events are haXe enums; the idea is that each entity container can hold its own set of type-checked events, and is subclassed to provide new services, etc.
  • Sim contains the core gameplay, instances of a single scene, physics, display of the tile-based environment, etc. Menu contains the UI and menu functionality, managing UI widgets as entities, and allowing games to be started, loaded, etc.

Aside about state transitions: These are an invaluable convention. You can implement them very simply with function pointers, a while loop that calls the pointer function repeatedly, and a "finishTimestep" boolean for the while loop to determine whether another transition should be called, or to exit and continue to the next frame. It can really simplify the overall "gameflow management" side of things to add some transition steps for "start game" "end game" etc. - and they're great for actor AI, too, although the function pointer method probably isn't the most efficient way to build them.

I also wrote this "current opinion" about ent container services, something which is still sort of in progress and may get reworked further, as I think through how it will actually work in the case of UI:
Quote
   Services should, by convention, always be the targets of timing events as they will be more stable. Application of an event directly to entities or components is a good way to get in trouble, as entities may despawn and still have events in the queue that keep firing when their components are already destructed. Instead, we can fire off one “ScreenIntro()” or “ScreenOutro()” event and the service then tweens percentage to every widget as it processes the other events. Similarly, if we want to do a one-time thing for a specific widget(e.g. Push a button and it bounces) then we can tell the service “Pushed(id)” and the service will do a hash lookup and apply the push event to just that entity. I've forced each of the event types to point towards a EntService interface, so that this formalism is maintained.
   The key advantage of using tween timers is that the timing mechanism itself is handling the per-frame percentage aspects: it doesn't have to rest on the entity or on the service. The service can add filters so that only one push event fires at a time, or let them stack, etc. It's a nice simplification.
   Services can provide more guarantees of availability and formality than direct references. For example, in DSD1, there was a global “player” and “drone” reference, which caused endless trouble when the player died and something decided to look it up to get coordinate information(movement, shot targeting, camera, etc.). In the service architecture, we can go through a “EntInfos” service and call the function “getPos(id)”. With getPos(), null accesses can be either silently replaced with default data, or propogated into an enum with cases of “alive(x,y); dead” so that all lookups are properly handled.

Something to note about my use of enums is that in haXe you can define enums as abstract, parameterized types. You can return a concrete type from them for computational purposes by using a switch statement. In the switch, the compiler will check for complete enum coverage. Thus a useful application of enums is to make sure that you cover all possible result cases. In the past my coding style avoided enums because it added overhead to serialization(my last experience was that I had to shepherd the enums into concrete types and then go back to get it working correctly, but this was years ago and may have been a bug), but I'm now willing to take on any of the extra pain that might entail in exchange for removal of another class of runtime error.

One thing I discovered in this particular system is that unique entity ids are almost forced along for the ride in adding a component and event framework: you end up needing a way to identify entities without necessarily having a spawned, ready-to-go instance to reference. I wasn't sure until I tried looking into it, but if you don't rely on a unique id that is dereferenced only as needed, you are too likely to run into dangling references to an invalidated entity, which are death for a GC'd environment like Flash.

I'm also far, far away from "Actor as class, actor has one update() function" now. I'm splitting it up into "AI+physics+rendering services, each controlling various lists of components, entities are property lists of components." So the enemies might share one AI service, the AI service runs the update, and it maintains references to each type of enemy component, so it can just iterate through all the AI in one go, then move on to the physics, etc. all while never having to look at the entity as a whole. I got most of the way to this in DSD, and it eliminates the ugliness of "one thing does all of its processing, rendering, etc., then the next thing does" which causes timing and ordering issues. I still had render order issues with DSD so I'm also going to defer actual blits to a service that maintains draw lists repopulated every frame. This lets me control layering very finely without resorting to the Flash scenegraph(ARGH) or compositing multiple surfaces.

Anyway, I'm excited for this architecture. It's not done, but it's looking good Beer!
Logged

starsrift
Level 10
*****


Apparently I am a ruiner of worlds. Ooops.


View Profile WWW
« Reply #17 on: March 18, 2010, 08:07:40 PM »

Thanks, Average Software, that was a truly helpful and compelling opinion.


I tend to have a separate class for the player and player data and actions, and then the rest is in a game class and objects within it. This does tend to make non-object functions always carry references to a game and player objects which clutters up the code, but the encapsulation is more explicit and easier to mentally deal with. Additionally, it makes multiplayer code easier.
Logged

"Vigorous writing is concise." - William Strunk, Jr.
As is coding.

I take life with a grain of salt.
And a slice of lime, plus a shot of tequila.
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #18 on: March 19, 2010, 01:47:43 AM »

Thanks, Average Software, that was a truly helpful and compelling opinion.

Now be a good fellow and also thank all the other nice people that have used their personal time to reply to you.
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
Alex May
...is probably drunk right now.
Level 10
*


hen hao wan


View Profile WWW
« Reply #19 on: March 19, 2010, 03:04:10 AM »

easy, he's only joking about AS's joke post about not coding games at all, and he's not even the thread starter and that's his first post in the thread!
Logged

Pages: [1] 2 3
Print
Jump to:  

Theme orange-lt created by panic