## Monday, May 6, 2024

### Motion without Integration

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.