A.I, Data and Software Engineering

Smooth nature snake – game redev with Kotlin (part 3)

S

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

Classic snake game design with blocks and grids
Fig 2: Classic snake game design with blocks and grids

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

divide angle to several arcs
Fig 3: divide angle to several arcs

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).

cosine similarity
Fig 4: cosine similarity based on the angle of two vectors

    \[\cos(\theta) = {\mathbf{A} \cdot \mathbf{B} \over |\mathbf{A}| |\mathbf{B}|}\]

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 \vec{A} to \vec{R}. It can change the direction by adding \vec{B}, \vec{C} , \vec{D} , \vec{E}
vector addition polygon
Fig 5: vector addition polygon

\vec{R}=\vec{A}+\vec{B}+\vec{C}+\vec{D}+\vec{E}

\vec{OE} = \vec{OA}+\vec{AB}+\vec{BC}+\vec{CD}+\vec{DE}

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.

Smooth nature snake turns - snake game
Fig 6: Smooth nature snake in action

Source code: Github (continue update).

1 comment

💬

A.I, Data and Software Engineering

PetaMinds focuses on developing the coolest topics in data science, A.I, and programming, and make them so digestible for everyone to learn and create amazing applications in a short time.

Categories