Well, I was supposed to provide better support for rendering ‘dummies’ – non-commital symbols representing game actors, to be used in game prototyping so we’re not tied to producing assets whenever we need to declare new characters and props.
Today I’m integrating realtime 3D character animations with my isometric view – how’s that for staying focused?
Isometrics again
While working out and testing Blender to GL export, I’ve setup my isometric view more reliably – the original setup was fiddly.
My unit conversions are getting complicated:
- First I worked in pixels. This is great for 2D sprites, with 32 pixels amounting to roughly 1 meter.
- Working with blender, I usually map 1 world unit to 1 meter.
- To save space, my models are encoded as signed short values. To do this, I premultiply everything by 1000 (smallest resolvable unit = 1 millimeter).
Time to tidy up. Here’s my new setup for the orthographic projection:
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, defaultFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);
// setup an orthographic projection (no perspective)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// The following indicate how many world units we fit
// in the width and height of the screen. So if you set this
// to say, 10×15, you show 15 x 10 world units.
//
// I just map pixels to world units, so at this point I ensure that
// 1 gl unit is 1 pixel.
//
// The depth parameter extrudes the screen into a box. Whatever’s
// inside the box is showing, the rest isn’t.
float width=GL_RENDER_AREA_WIDTH;
float height=GL_RENDER_AREA_HEIGHT;
float depth=GL_ISO_RENDER_DEPTH*5;
glOrthof(-width/2, width/2,-height/2,height/2, -depth/2, depth/2);
// Now we reset the ‘model view’. In other words we’re fitting the world
// inside our box.
// I rotate the world 30 degrees. This creates an isometric projection
// except I’m not rotating 45 degrees on the Y axis, which would give
// the classical ‘lozenge tile’ isometric effect.
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(30,1.0f,0,0);
// At this point I rescale by ‘pixels per metre’.
// By default, 32 pixels is now 1 metre.
glScalef(pixelsPerMetre,pixelsPerMetre,pixelsPerMetre);
// Here I setup/clear the depth buffer.
glDepthMask(GL_TRUE);
glClearDepthf(1.0f);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
// Clear with some non-descript dark gray
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render the world
[contentRenderer render];
// Display the result.
glBindRenderbufferOES(GL_RENDERBUFFER_OES, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
OK, now I need to correct everything down the chain. In passing, I removed a ’32′ scaling factor because both my actors and tiles already used world units. The data model still uses pixel units, I won’t touch that for now.
Blender Sprites?
I corrected the tile display – that’s not too important because tiles are really just make-does. I’m more excited about integrating my character animation from Blender. Pending the next serious article about the export/import code, drafted and tested this week-end, the main (and limited) interest of the integration code lies in the transformation sequence.
In the code below, ‘actor’ refers to the data model object. actor3d is a wrapper around geometry data. Since geometry data can be rendered using various methods, I’ve defined rendering code in a separate class, ActorRenderer3D.
This
// eval orientation angle
float angle=0;
Vector* u= actor.orientation;
if (u.length<0.001f) {
angle=0;
}else {
// here the angle is tested against the y axis because
// the orientation (from my legacy 2D isometrics) describes
// the actor’s direction in the model’s x-y plane, so I
// substitute y to z.
Vector* v= [[Vector alloc]initWithX:0.0f y:1.0f z:0.0f];
// [Vector angle::] returns the same values for x positive
// and x negative so I flip the angle to get the
// actual rotation.
angle=[Vector angle_deg:u with:v];
if (u.x<0) {
angle=-angle;
}
}
// transform and draw
glPushMatrix();
// we need to rescale by 32 because the model’s coordinates are in pixels
// (eventually the rescale factor should be passed down the render graph)
float tx=(actor.location.x-originX)/32.0f;
float ty=(actor.location.y-originY)/32.0f;
//
glTranslatef(tx,0,ty);
glRotatef(angle, 0, 1, 0);
[Actor3DRenderer renderActor:actor3d action:@"nod" frame:actor.phase];
glPopMatrix();
Right – that’s it for integration so far. I’ll have more to do later in two essential areas:
- Selecting the animation frame to be displayed. The model’s ‘phase’ should be determined by the view. This is counter-intuitive (the model should drive the view, not the reverse) but logical (the animator/designer determines the action’s duration). A this point, phase is still determined (arbitrarily) by the model.
I could export animation durations separately, but I don’t think this is a good idea. This is data duplication, and is kind of dishonest. On the other hand, it is likely that animation durations will require tuning later, so my bet is that I should (a)actually define animation durations somewhere else and (b) provide code that remaps animation frames to actual game loop frames.
- Mapping/Matching model actions (ids) to animation names (strings) – I’ve hardwired an action name for now because I have no safety guards against an undefined action – an action for which there is no matching animation.