
I want to share something I just finished implementing for my 3d graphics framework in Scheme which I am building my iPhone game off of. Keep in mind that I am not intentionally building a separate 3d graphics framework as a general platform, but I am building only what is needed to get my game out the door, so it is rough and the API is awkward and not well thought out.
Given my custom scene objects which abstract away OpenGL rendering, I built a system for interpolating these objects' properties (such as scale and color) over time, or "tweening." This turned out to be incredibly useful. Here's the story.
(Note: I'm not extremely confident I'm using the word "tween" correctly, but if not, I'm re-defining it.)
The problem was that I needed a system for presenting the title screen, level titles, and a 2d overlay for my 3d iPhone game. These 2d elements should smoothly fade in and out of the screen over time. I don't need a full GUI toolkit, but I'm more interested in smooth animations when levels change, text appears, etc.
This turned out to be a huge bear in my old system: because of the primitive nature of my Scheme graphics libraries, I had to manually do all the fading every frame for every object. Horrible.
I started re-hauling parts of my system. It started small, only supporting alpha fading of 2d objects. However, as I worked on it more and more, I realized all properties should be tweenable.
So I set out to implement tweening for any object properties, such as position, rotation, scale, color, and anything else. And why not let 3d objects be tweened as well? No reason not to!
It turns out that this is a good way to specify simplistic behaviors of objects. I'm sure anyone experienced in developing games is used to this. I've seen various forms of tweening in game frameworks, especially particle systems. I'm not sure if I've seen it expressed this way though. Is there a formal or established name for this, and any examples of how most engines do this?
Once the system was in place, I implemented many types of tweens such as "linear," "quadratic", "cubic", and "bounce", each defining a different interpolation function based on time. More details about these types, including graphics of each function, can be seen in this documentation.
Here are some examples from my tweening system. I will show you code and a video of what it does. I will not go into details about the code because it isn't public yet, and it's really rough, but hopefully it will help expose what I'm trying to achieve.
Here I show you all the types of tweens my system currently supports. A quick list would include linear, quadratic, cubic, bouncing, and all the variations of applying them at the end or the start of the interpolation.
(begin
(define (tween-type type)
(scene-list-clear!)
(scene-list-add
(make-tween
(make-2d-object
2d-perspective
position: (make-vec3d .1 (- .5 .025) 0.)
scale: (make-vec3d .05 (/ .05 1.5) 1.)
color: (make-vec4d 1. 1. 1. 1.))
position: (make-vec3d .65 (- .5 .025) 0.)
color: (make-vec4d (+ (* (random-real) .5) .5)
(+ (* (random-real) .5) .5)
(+ (* (random-real) .5) .5)
(+ (* (random-real) .5) .5))
scale: (make-vec3d .2 (/ .2 1.5) 1.)
length: .7
type: type)))
(tween-type 'linear))
You can queue up tweens as well so that one fires off as another one ends.
(begin
(scene-list-clear!)
(define tx (image-opengl-load "survived.png"))
(let ((obj (make-2d-object
2d-ratio-perspective
position: (make-vec3d (- .5 .025) .1 0.)
scale: (make-vec3d .05 .05 1.)
color: (make-vec4d 1. 1. 1. 1.)
rotation: (make-vec4d 0. 0. 1. 0.)
texture: tx
)))
(scene-list-add
(make-tween
obj
position: (make-vec3d (- .5 .025) .75 0.)
scale: (make-vec3d .4 .4 1.)
color: (make-vec4d 0. 1. 0. .5)
rotation: (make-vec4d 0. 0. 1. 90.)
type: 'ease-out-bounce
length: 1.5
on-finished:
(lambda ()
(scene-list-add
(make-tween
obj
type: 'ease-inout-cubic
position: (make-vec3d .8 .1 0.)
scale: (make-vec3d .1 .1 1.)
color: (make-vec4d 1. .5 0. 1.)
length: 1.))
#f)))))
There's no reason you can't apply tweens to a 3d object as well.
(begin
(scene-list-clear!)
(let ((obj (make-mesh-object
3d-perspective
mesh: sheep-mesh
position: (make-vec3d 0. 0. 10.)
rotation: (make-vec4d 0. 0. 1. 0.)
scale: (make-vec3d 2. 2. 2.))))
(scene-list-add
(make-tween
obj
scale: (make-vec3d 6. 6. 6.)
rotation: (make-vec4d 0. 0. 1. 90.)
length: 1.
type: 'ease-out-cubic
on-finished:
(lambda ()
(thread-sleep! 1.)
(scene-list-add
(make-tween
obj
scale: (make-vec3d .7 .7 .7)
type: 'ease-inout-cubic
on-finished:
(lambda ()
(scene-list-add
(make-tween
obj
position: (make-vec3d 0. 5. 10.)
type: 'ease-out-bounce
on-finished:
(lambda ()
(scene-list-add
(make-tween
obj
type: 'ease-out-bounce
position: (make-vec3d 0. -5. 10.)))
#f)))
#f)))
#f)))))
I'm still wrapping my head around how to implement a full motion system with this. As of now, if you specify a cubic tween between point (x, y) to (x1, y1), it will perform a cubic interpolation on both the x and y as a function of time. The result is that the object moves in a straight line from point A to point B, but the speed of the movement is cubic.
For example, here's a cubic curve:

Even though it is a curve, if I apply cubic interpolation to positions, which are 3-dimensional vectors with an x, y, and z property, the object moves between point A and point B in a straight line, with cubic-varying speed. If we tracked the movement, it would look like this:

What if I wanted the object to curve when moving from point A to point B? Surely I can express that with interpolations? The problem lies in the fact that I've bound my interpolations of multi-dimensional vectors to be constant across each dimension. I would need to support different kinds of interpolations across each x, y, and z axis. This sounds really neat, and I will have to extend my system to support this later.
Now that I think about it, I think the special case of interpolating movement should be handled with splines. In fact, it's probably downright inappropriate for me to interpolate position this way. Splines are on my todo list for research.
Having this simplistic tweening system will make it easy for me to apply smooth animations consistently across my iPhone game. It's feels a lot more polished when I use this to fade in items and slide them off the screen. I wouldn't really have thought of this unless I forced myself to build a real game.
Building a game from scratch has forced me to learn all kinds of things, and I find this experience invaluable. I'm not building something simply because the idea of it sounds cool, but I have to build my framework with certain features so that my game will not only run but look good too. Having this dictate my development forces me to drastically alter my ideals quickly as I learn how games are laid out.
Scheme is a huge support in this kind of simplistic agile development. Between its dynamic type system and functional simplicity, I can constantly re-shape huge parts of the system as if I'm a sculpter removing and adding huge chunks of clay. On top of that, The Lisp methodology of interactive/iterative development allows me to add details extremely quickly. The turn around time for feeling reward from intense coding sessions is really fast and extremely motivating!