I have now resolved importing Blender data into my game (complete article with source code will be available next).
In this article I’m covering code structure changes required to migrate from a ’2D isometric view’ (CocoaTouch rendering) to a 3D isometric view (OpenGL-ES rendering). The calculations helped me understant glOrthof a little better.
My original plan was to provide first a ‘dummy view’ to allow level design without having to create dummy assets for every other character or tile. I’ll probably still do this, but because the dummy view required vector graphics, I dived head first into OpenGL ES – the downside being that this cannot integrate directly with my current view code.
Starting from the GLES2Sample example, I have done the following:
- modified it to remove GL ES2 support (at least for now)
- Added an ‘ESContentRenderer’ interface. This could be temporary; for now it’s just an acceptable way to separate GL setup/teardown from content rendering. ES1Renderer now calls [ESContentRenderer render] within its own render function.
- Arranged that EAGLView now extends my ActionView class (ActionView just converts simple taps into something like clicks).
- Created a StageView3D subclasser to EAGLView. StageView3D is like my 2D StageView class. This is for consistency.
- Added a StageViewRenderer class – this implements ESContentRenderer and is meant to do actual rendering work.
I somehow mean to define a common interface for StageView3D and StageView(2D). As it stands most methods in StageView were private – the view knows about the model, not the reverse – so there’s little gained in doing this.
With my new setup, I can instantiate StageView3D in place of StageView (duplicated the constructor). EAGLView owns the contentRenderer and passes it to ES1Renderer (could be renamed FrameRenderer). StageView3D extends EAGLView and provides StageViewRenderer as content renderer.
The above realizes two goals:
- Put GL drawing in a separate class, part of my view rendering – EAGLView and ES1Renderer are part of my GL boiler plate.
- Keep the new view setup compatible with my existing controllers.
I’ve been tempted to fire a paint message from ES1Renderer instead. I could handle that within StageView3D. which would keep it more like StageView. For now I don’t see the difference and feel a little suspicious about dispatching an event allowing classes to contribute drawing independently.
I still need to replicate the drawing of the actors, props and board, and (until I enable gl picking) I need to make sure that my isometric view is compatible with my existing (2D) picking code.
After I am finished with setting up my StageContentRenderer and StageView3D:
- StageView3D duplicates StageView code for 2D picking. I won’t try to factor this out just yet (I will eventually migrate this to 3D picking I guess…)
- StageContentRenderer graces me with a beautiful gray screen.
Drawing Actors, Props and Terrain with GL.
I already have a SpriteRenderer protocol. This was originally meant to support several implementations and hardly specifies anything, so I can likely create a new implementation for actor rendering; tile and prop rendering was too simple to deserve separate classes.
Pixels to World Coordinates
Previously, my sprite coordinates where defined in pixels. I have no reason to change that (and mess my 2D picking code – I still need it for now).
When rendering the first sprite, I just by-passed applying a translation.
I then use glPushMatrix, glPopMatrix to apply and remove the translation. First I directly use pixel coordinates (expecting to see that go out of the screen) – I do get a gray screen again (no sprite), but also get an interesting error in the console:
GLES2Sample[3130] <Error>: CGContextSaveGState: invalid context
Nothing happens when tracing GL calls. Aha! This is because I left 2D rendering code (calls to drawImage) as I’ve setup only one sprite. Since I’ve managed to run this a few times (without the transformation) and could render the model, this is something that can be ignored. Nice.
I learned a few things while trying to get this right; to setup my isometric view, I use:
glOrthof(-width/2, width/2,-height/2,height/2, -depth/2, depth/2);
Where:
- width is the height of the window/view we are rendering to.
- height is the height of the window/view we are rendering to.
- depth represents the bounds of the field of view. for now I use a parameter ‘like the width or height’
So what glOrthof does is fit a box taken from the 3D world into the target UIView.
I got a few gray screens while fiddling with that. Common causes:
- translating the target too much
- using too small a value for the depth parameter
- making the width/height too large (apparently if the model is too small, no pixel is rendered.
My test model is just a unit box. When the width and height match about 1 world unit for 1 pixel, this box is hardly visible. I think using 1 meter = 1 world unit is kind of nice (considering how it feels on the Blender grid), so I apply a scaling parameter for world unit to pixel conversion. For now I’d say 1 meter is about 32 pixels (my sprite for the player was 32×64 pixels).
Finally, here’s the code for my view/camera setup…
glMatrixMode(GL_PROJECTION); glLoadIdentity(); float width=320; float height=460; float depth=460; glOrthof(-width/2, width/2,-height/2,height/2, -depth/2, depth/2); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(60,1,0,0);
…and the code for my model transformation. Note that all this stuff is ‘nearly correct, not quite correct’ because the isometric view now tracks X coordinates correctly, whereas we have a 2:1 ratio on Y coordinates and I’ll need to correct that later.
glPushMatrix(); float tx=actor.location.x-originX; // (see note for 'origin') float ty=actor.location.y-originY; NSLog(@"translate to: x=%f,y=%f",tx,ty); glTranslatef(tx,0,ty); glScalef(32, 32, 32); // draw the model... glPopMatrix();
The ‘origin’ is the position of the player avatar because I like to keep it at the middle of the screen. Either way the isometric view needs to have a kind of ‘camera location’ so there would be an origin – but then that should be composed once and for all as part of view transform setup.
Uhoh. That took quite a while to get through – next time I’ll do the board and props and it should be a little easier.

