Game code organization

Member
Posts: 30
Joined: 2010.08
Post: #1
I'm making a 2d game with Cocoa (NSGL) and OpenGL. Right now I'm doing a pong clone but after I will try snake/tetris or come up with my own, reusing the same code. I know these are very simple, but I want to get it right. I have made all my OpenGL code legacy free/forward compatible, and have learnt a lot doing that. Here is how my code is organized:

GameData (.h/.m) (Not a class):
--------------
Here I #define compile-time game constants, and I also have 3 structs and a single instance declared each, globally available (so that I have only 3 global structs instead of making global the many variables they contain): GameValues - values that change often, such as sprite rects, current window width & height, the gamestate, etc. GameOptions - Options that can be changed by the user (e.g. in an options menu), that would otherwise be #define'd GameKeys BOOLs for keys I use.
Then there is a GameSetup() function to setup initial values, and a GameUpdate() to update them, called each frame.

GameView (.h/.m)
---------------
This is my custom NSView subclass that manages an NSOpenGLContext and NSPixelFormat, and uses a CVDisplayLink to render each frame (using Renderer). Also captures keyboard input and modifies GameKeys.

Renderer (.h/.m)
--------------
Manages VBOs, shaders, all the OpenGL stuff, and renders using GameValues to get positions.


I Also have MKGeometry.h, a file I wrote than provides points, vectors, rects and all static inlined functions for using them (because they are all simple one-liners). And very simple vertex and fragment shaders. The AppDelegate is only used to respond to delegate methods.

My Question: Is this a good way to organize things? Do you think this would scale well? The game logic is pretty well decoupled from rendering code, and with Renderer being separated I could easily add e.g. fullscreen mode, sharing the context.

Thanks.
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #2
It looks like a pretty good way to organize things for this size of a project. What won't scale well will probably be potential over-use of globals (or use of globals at all from others' points of view). We just had a little discussion about this the other day. Otherwise it sounds reasonable from the outside.

One thing I would suggest is to have an alternate NSTimer path instead of the display link so you can switch over to that if you're having threading issues. I've found that very helpful at times when debugging.
Quote this message in a reply
Member
Posts: 30
Joined: 2010.08
Post: #3
(Aug 20, 2010 10:15 PM)AnotherJake Wrote:  It looks like a pretty good way to organize things for this size of a project. What won't scale well will probably be potential over-use of globals (or use of globals at all from others' points of view). We just had a little discussion about this the other day. Otherwise it sounds reasonable from the outside.

One thing I would suggest is to have an alternate NSTimer path instead of the display link so you can switch over to that if you're having threading issues. I've found that very helpful at times when debugging.
I know globals are bad, but thats why I put them in structs, so for example I do GameValues.player, GameValues.gameState etc. I needed everything to have access to these, and it didn't make seem to make much sense to put it in a class. If I used an class than every time I wanted to access one of these variables I would have to do something like [[GameData sharedGameData] ballVelocity] for example. Then I would have those double message sends all over (in the rendering code at least).

If I shouldn't use globals, how else could I implement this, with everything having access to them?

About the alternate NSTimer path, the drawRect: method does render if the display link isn't running, and the threading is working fine. How would I implement that? Do you mean if the display link initialization fails at runtime, to instead use an NSTimer?
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #4
(Aug 21, 2010 08:40 AM)mk12 Wrote:  If I shouldn't use globals, how else could I implement this, with everything having access to them?
Personally, I'd just use the globals Wink ... Don't tell anyone you do that around here without someone screaming bloody murder though. That said, I've discovered that globals sometimes cause problems with scalability if you're not careful, so I'm inclined to pass on the advice to avoid them if possible. If it sounds like I'm talking out both sides of my mouth, then you understand me perfectly.

(Aug 21, 2010 08:40 AM)mk12 Wrote:  About the alternate NSTimer path, the drawRect: method does render if the display link isn't running, and the threading is working fine. How would I implement that? Do you mean if the display link initialization fails at runtime, to instead use an NSTimer?
No, not because of init failure, but because there are sometimes situations where you'll do something bone-headed and it'll freeze the machine sometimes if the gl context gets accessed from another point outside that loop. If you need to track down that issue, it can be much much easier to fall back to an NSTimer. It's also easier to identify problems as thread-related when you can optionally turn off threading, so again, the NSTimer can be handy. It's easy to set up in your initWithFrame method. You can do something like this, where useDisplayLink is a global:
Code:
if (useDisplayLink)
    {
        CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
        CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, self);
        CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
        CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
        CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
    }
    else
    {
        renderTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
                                                            //interval:0.001
                                                            interval:1.0 / 60.0
                                                            target:self
                                                            selector:@selector(renderTimerCallback:)
                                                            userInfo:nil
                                                            repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSEventTrackingRunLoopMode];
        [[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSModalPanelRunLoopMode];
        [renderTimer release];
    }
Quote this message in a reply
Member
Posts: 30
Joined: 2010.08
Post: #5
Oh, so the NSTimer would be just for debugging, because NSTimers don't create a new thread?

I know globals will work fine in a project of this size, but how would I do it without them? If with a class, how would everything else, particularly the renderer, access them?

EDIT: Actually, I think it would be cleaner code with a Game class. Because then in the update code, I don't need to do GameValues.* etc., and in the rendere all I need is the rects of the objects, and there are only three, so I think I will do it that way, with GameView owning an instance of Game, passing it to the renderer.
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #6
Yah, NSTimer purely for debugging purposes because it doesn't create a new thread.

There are various approaches without globals. The safest of course is to use accessors for everything. You'd init all your objects by passing them the instance of your central game class like, [[myObject alloc] initWithGame:myGame]; (or your shared data class, as you mentioned above) Then, as you iterate over your game objects to update them you'd probably just pass delta time as a parameter, since everything would need that, and it'd be non-writable, which would be ideal in terms of access restriction, like:
Code:
[myObject updateWithDeltaTime:dt];
Then in the object, if it needed access to input you'd either get input on an individual element basis via accessor, which would be ideal: BOOL leftArrowPressed = [[myGame input] leftArrowPressed]; or you could get a copy of the input struct MyInputStruct *input = [myGame input], which wouldn't be ideal since it would still be writable from the object, but at least you'd have a better idea of which objects have access to it, but it'd be faster if that's really necessary.

For something like a central renderer, you could do something similar but in reverse. From the renderer, maybe you'd do something like RenderData *rd = [myObject renderData]; which would get the object to pass a known struct back, which would be writable by the renderer, but in that case, avoiding accessor call overhead for each little detail would be desirable. There are a zillion ways to do all this. Pick your favorite poison Wink
Quote this message in a reply
Member
Posts: 30
Joined: 2010.08
Post: #7
Ok, thanks, here is how I think I'm going to do it: I'll have a Game class, which has rects for objects (the game is so simple the game objects have no class, the game update function will take care of both paddles and the ball), gamestate, etc. and will have methods -setup and -updateWithDeltaTime:. GameView will own a single instance of it, and will pass it to the render method. The Game class will have readonly accessors.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Some Game / Engine Source code in C++ and MSVC kiavash2k 1 5,077 Apr 12, 2012 11:35 PM
Last Post: DJyStyler
  Anyone has Cocoa tutorials or simple game sample code? Achenar 5 6,325 Nov 26, 2003 12:06 PM
Last Post: Sohta