In part 2, I introduced how to make the serpentine movement. We continue this article by covering the turning mechanism for the snake.
Challenges in turning animation

As we want our snake looks as smooth as possible, the two challenges are:
- Firstly, the head should slowly change to the target direction rather than a sudden turn. As the head has a distinct shape, we need to make sure the accuracy of its heading direction. Figure 2 illustrates an easier case when the snake does not have a head.
- Secondly, its body should form a curved line when making a turn. We don’t want to see the snake breaks its neck/body with an immediate 90-degree turn as shown in many classic snake games (Fig 2).
Now, we will step by step address these two problems.
Make the head turn slowly

The principle is simple:
- Calculate the turning angle
- Divide the angle into serval equal arcs, the smaller arc, the smoother the transition
- Draw the head based on the arc
As we created a function to calculate the new position when rotating axes, the implementation of this is straightforward. Nevertheless, you can also approximate arc changing by the different of two movement vectors, i.e. currentMoveVector and TargetMoveVector.
val dx = (mTargetMoveVec.x - mCurrentMoveVector.x) / mAnimationFR
val dy = (mTargetMoveVec.y - mCurrentMoveVector.y) / mAnimationFR
If you use the approximate method, you may also need to compare two vectors to check if the head’s direction reaches the targeted direction. For this, I used the cosine similarity. If the value is -1, the two vectors have absolute opposite directions. If the value is 1, they follow an identical direction (Fig 4).

And the code for the cosine similarity:
fun CosineSim(v1: PointF, v2: PointF): Double {
return (v1.x * v2.x + v1.y * v2.y) / (VectorNorm(v1) *
VectorNorm(v2))
}
The snake will stop turning if the cosine similarity reaches a certain threshold. I used 0.98 in this case.
while (Maths.CosineSim(mCurrentMoveVector, mTargetMoveVec) < 0.98 && isRotatingHead) {
//code for changing the direction of the head
}
mCurrentMoveVector.set(mTargetMoveVec)
isRotatingHead = false
Make curved line turn
It is almost there. If you have completed the turning and the movement of the head. The curved line will magically appear. The explanation is simple as it is the result of vector addition:
- The snake’s head currently has two movements simultaneously, namely, the movement vector (mCurrentMoveVector) and the turning vector created from the arc.
- When adding up the 2 vectors, we have a new vector representing both the movement and turning direction.
- Fig 5 demonstrates the turning step of a snake. If the snake wants to turn slowly from
to
. It can change the direction by adding
,
,
,

The final step is to implement the the head turn animation in a separate thread. We can use Runnable interface as follow.
private var mRotateHeadRunnable = Runnable {
val dx = (mTargetMoveVec.x - mCurrentMoveVector.x) / mAnimationFR
val dy = (mTargetMoveVec.y - mCurrentMoveVector.y) / mAnimationFR
while (Maths.CosineSim(mCurrentMoveVector, mTargetMoveVec) < 0.98 && isRotatingHead) {
val rVec = PointF()
rVec.set(mCurrentMoveVector)
rVec.offset(dx, dy)
val sc = Maths.VecSinCos(rVec)
mCurrentMoveVector.set(worm_pace * sc.x, worm_pace * sc.y)
Log.d("rotate", mCurrentMoveVector.toString())
Thread.sleep(1000L / mAnimationFR)
}
mCurrentMoveVector.set(mTargetMoveVec)
isRotatingHead = false
}
The result
Let see our joyful snake wandering around the game view.

Source code: Github (continue update).
[…] [1]. Game loop, https://gameprogrammingpatterns.com/game-loop.html.%5B2%5D. Game programming, https://en.wikipedia.org/wiki/Game_programming.%5B3%5D. Smooth snake, https://petamind.com/smooth-nature-snake-game-redev-with-kotlin-part-3/. […]