Tempest: Basic progress
In order to start making progress on this game, the initial work will:
- Ignore the third dimension
- Ignore enemies
- Focus on the break down of levels into segments in a loop
- Allow the player to move around these segments
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:
First three levels #
The first three commits were to add the first three levels.
Each level is broken into a list of Segment
s, where each Segment
represents a start and an end point on the screen using a pair of libgdx Vector2
s:
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).
Player position and movement #
By recording which segment the current player is on, it becomes straightforward to:
- Highlight this segment differently during rendering.
- Respond to input by changing the segment to the next one (with the exception that we need to support wrapping around the loop).
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).