A game where things move around has equations of motion that describe how objects move. We need to solve these to produce the locations of the objects as a function of time, and we have to do it in lock step real time. Fortunately, we usually don’t need highly realistic models or highly accurate results. Game phyiscs, while inspired by real world physics, can be crude approximations.

If the velocity of an object changes over time, we need to integrate the velocity to determine the position. This is typically done with forward Euler integration: the current velocity is multiplied by a small Δt and this is added to the current position. In other words, we assume the object moves linearly over the amount of time represented by Δt. If Δt is small enough, the linear segments of motion will approximate the actual curve of motion.

In a game, we use the game loop, which runs every few milliseconds, as our source of time. We subtract the current time from the previous time the game loop was run to obtain our Δt. We update each object’s state using forward Euler integration over Δt.

Our game loop keeps track of the last time it was run and subtracts
that from the current time. It runs `entity-step!`

on
each entity in the game, passing in `dticks`

.

(let ((start-tick (sdl2:get-ticks))) (let ((last-tick start-tick)) (loop (let* ((tick (- (sdl2:get-ticks) start-tick)) (dticks (- tick last-tick))) (map nil (lambda (entity) (entity-step! entity dticks)) entities) (setq last-tick tick)))))

`entity-step!`

performs our forward Euler
integration:

(defun entity-step! (entity dticks) (incf (position entity) (* (velocity entity) dticks)))

Assuming the game loop runs reasonably frequently, the position of
the entity will be a reasonable approximation of the integral of the
velocity. If the game loop gets delayed for some
reason, *e.g.* garbage collection, the corresponding increase
in the value of dticks will make up for it.

But sometimes we have the fortune of having a closed form solution to the time integral. If this is the case, we can compute the entity’s state directly and we don’t need to integrate the state changes on every iteration of the game loop. Let me give two examples.

In the platformer game, there are potions that the player can take to
restore his health. A potion appears as a vial that floats above
the ground. A “bobbing” effect is achieved by changing
the height above the ground in a time varying manner. Now we could
implement this by having the game loop modify the y position of the
potion on each iteration, but there is a closed form solution. We
can compute the y position at any time by adding a constant base y
position to a factor of the sine of the current time. This is
easily done by specializing the `get-y`

method on
potions:

;;; Make potions bob up and down. (defmethod get-y ((object potion)) (+ (call-next-method) (* 5 (sin (* 2 pi (/ (sdl2:get-ticks) 1000))))))

`call-next-method`

invokes the next most specific
method — in this case, the `get-y`

method of the
entity class, which returns the base y position. We add a sine wave
based on the current time.

This is only thing we need to modify to make a potion bob up and
down. Values dependent on the y position, such as the attackbox,
will bob up and down with the potion because it uses
the `get-y`

method to determine what the potion is.

A second example is cannonball motion. Cannonballs travel linearly
away from the cannon at a constant velocity. Rather than stepping
the cannonball by adding its velocity to its position on each
update, we specialize the `get-x`

method on cannonballs

(defmethod get-x ((cannonball cannonball)) (+ (call-next-method) (* (get-speed cannonball) (- (sdl2:get-ticks) (get-start-tick cannonball)))))

If we specialize both the `get-x`

and `get-y`

methods we can easily get objects to trace out any desired parametric
curve, like a Lissajous figure.

## 2 comments:

If you are integrating a motion equation over a non-uniform time series, you may get into a trouble, if the right side of the equation depends from coordinates or the motion is accelerated. For example, if a ball is falling in gravity field, its velocity is increasing constantly. You cannot just multiple the current velocity to the time step and then update the velocity, because your error will be proportional to a square of time step. For non-uniform time steps it will lead to a ragged motion

Hemmi is correct. From what I have seen, games ignore the issue of non-uniform steps and just hope that the steps are frequent enough that the error isn't that bad. Again, for many games, it doesn't matter much if there is an error so long as it looks ok.

Post a Comment