Tempest: Basic progress

software-dev, retrowars, tempest

In order to start making progress on this game, the initial work will:

I must confess that I’ve never actually played before, but I do really like the videos I’ve seen, so please go easy on me :) My reference point for how the levels look and how the game is played is this YouTube video:

Link to YouTube video of Tempest gameplay

First three levels #

The first three commits were to add the first three levels.

Each level is broken into a list of Segments, where each Segment represents a start and an end point on the screen using a pair of libgdx Vector2s:

class TempestGameState {
    val level: Level
}

data class Level(
    val segments: List<Segment>,
)

data class Segment(
    val start: Vector2,
    val end: Vector2,
) 

And then each level is hand crafted using whatever necessary (e.g. hard coded start and end points for each segment in the rectangular second and third levels, but trigonometry in the first “round” level).

Screenshto of initial commit

Screenshto of initial commit

Screenshto of initial commit

Player position and movement #

By recording which segment the current player is on, it becomes straightforward to:

The player segment is recorded in the game state as a reference to one of the level segments. We could equally store an index to the segment in question, but this worked well for the Snake game and made a lot of comparisons much simpler.

class TempestGameState(private val worldWidth: Float, private val worldHeight: Float) {
    val level: Level = makeThirdLevel(worldWidth, worldHeight)

    var playerSegment = level.segments[0]
}

The input handling is taken from the tetris game, whereby we need to ensure that only one keypress is registered each frame. Otherwise, on faster devices, the game will respond too quickly.

Therefore, instead of asking whether a button is pressed at the start of each frame (as we do with Asteroids), we instead record a button status when the button is first pressed, and then clear that status each frame after processing it:

class TempestGameState {
    ...
    var moveCounterClockwise = ButtonState.Unpressed
    var moveClockwise = ButtonState.Unpressed
}

class TempestGameScreen {
    ...
    controller!!.listen(
            TempestSoftController.Buttons.MOVE_COUNTER_CLOCKWISE,
            { state.moveCounterClockwise = if (state.moveCounterClockwise == ButtonState.Unpressed) ButtonState.JustPressed else ButtonState.Held },
            { state.moveCounterClockwise = ButtonState.Unpressed })

    controller!!.listen(
            TempestSoftController.Buttons.MOVE_CLOCKWISE,
            { state.moveClockwise = if (state.moveClockwise == ButtonState.Unpressed) ButtonState.JustPressed else ButtonState.Held },
            { state.moveClockwise = ButtonState.Unpressed })

    override fun updateGame(delta: Float) {
        ...
        movePlayer()
        resetInput()
    }

    private fun movePlayer() {
        val currentIndex = state.level.segments.indexOf(state.playerSegment)

        if (state.moveClockwise == ButtonState.JustPressed) {
            state.playerSegment = state.level.segments[(currentIndex + 1) % state.level.segments.size]
        } else if (state.moveCounterClockwise == ButtonState.JustPressed) {
            state.playerSegment = state.level.segments[(state.level.segments.size + currentIndex - 1) % state.level.segments.size]
        }
    }

    private fun resetInput() {
        state.moveCounterClockwise = ButtonState.Unpressed
        state.moveClockwise = ButtonState.Unpressed
    }

Next steps #

Next will either be the addition of firing from the players current position, or the addition of a 3rd dimension (which will make it easier to see if the firing logic is working as expected).