Framer for Games: Tempest

Part 11 of Framer for Games series

Core mechanic: Rotating auto-generated layers with help from Utils.interval

Other games and/or game mechanics in this series

This is currently the last entry in this series. I’d love to read your suggestions in the comments.

  1. Raid HQ Character Selection [Thursday, June 23]
  2. Endless Runner [Thursday, June 30]
  3. Guitar Hero [Thursday, July 7]
  4. Card Match [Thursday, July 14]
  5. Snake [Thursday, July 21]
  6. Dropping Ball Catcher [Thursday, July 28]
  7. Arcade Joystick [Thursday, August 4]
  8. Sliding Blocks [Thursday, August 11]
  9. Vertical Platformer [Thursday, August 18]
  10. Pong [Thursday, August 25]
  11. Tempest [Thursday, September 1]

Link to Framer Prototype

Animation of game prototype showing player controlling a slider to move an object around the center of a circle, firing bullets at an enemy object

Game elements

  • Health gauge
  • Game rings
  • Player object
  • Player bullet
  • Enemy
  • Player control
  • Game loops

Health gauge

Made up on two layers, one the parent of the other. The child health meter will scale horizontally when the player is hit.

# Health gauge
damageMeter = new Layer
width: Screen.width
height: 50
health = new Layer
parent: damageMeter
width: Screen.width
height: 50
backgroundColor: "green"
originX: 0

Game rings

  • playerRing controls rotation of the player object
  • bulletRing controls rotation of the player (this is separate from the playerRing so that once the bullet is fired, it can maintain its own trajectory and will not rotate as the player object rotates)
  • enemyRing controls rotation of the enemy object. It is assigned a random value each game loop.
# Three game rings
playerRing = new Layer
width: 600
height: 600
x: Align.center
y: Align.center
borderRadius: "50%"
backgroundColor: "transparent"
bulletRing = new Layer
width: 600
height: 600
x: Align.center
y: Align.center
borderRadius: "50%"
backgroundColor: "transparent"
enemyRing = new Layer
width: 600
height: 600
x: Align.center
y: Align.center
borderRadius: "50%"
backgroundColor: "transparent"
rotation: Utils.randomNumber(0, 360)

Player object

Just a simple layer with one additional state to indicate it has been hit by an enemy object

# Player object
plane = new Layer
parent: playerRing
width: 100
height: 100
x: Align.center
y: Align.top(-50)
borderRadius: 10
backgroundColor: "white"
plane.states.add
damaged:
backgroundColor: "red"

Player control

The SliderComponent gives us a nicely mapped control for our player object. Since I’ve opted to start the player object at the top of the playerRing, I set the slider knob to the middle, and mapped the playerRing’s rotation to allow a player to move the knob left or right from the center.

# Primary control
control = new SliderComponent
x: Align.center
y: Align.bottom(-100)
width: Screen.width - 200
control.knobSize = 100
control.value = 0.5
control.on "change:value", ->
playerRing.rotation = Utils.modulate(control.value, [0,1], [-180,180])

Game loop: shooting bullets

Each second:

  • The bullet’s ring is set to match the player’s ring
  • A new bullet is created with two animations: one to move it towards the center and another to make it appear to explode. The first animation is initialized, and upon its end, the second animation will trigger. After the second animation ends, the bullet layer is destroyed as a form of clean-up.
# Game loop: shoot bullets
Utils.interval 1, ->
bulletRing.rotation = playerRing.rotation
bullet = new Layer
parent: bulletRing
width: 25
height: 25
x: Align.center
y: Align.top(50)
backgroundColor: "yellow"
borderRadius: "50%"
shoot = new Animation
layer: bullet
properties:
y: Align.center
opacity: .2
time: 1
curve: "linear"
explode = new Animation
layer: bullet
properties:
scale: 2
opacity: 0
time: 0.5
shoot.start()
shoot.onAnimationEnd ->
explode.start()
explode.onAnimationEnd ->
bullet.destroy()

Game loop: respawn enemy

Starting at an interval of 4 seconds:

  • the enemy’s ring is randomly rotated
  • A new enemy is created and assigned an animation that moves it from the center outwards toward the player object
  • As the enemy layer moves toward the player object, the game is watching it to detect a collision between either the player bullet or the player object
  • If it collides with the bullet, it is destroyed and the interval is decreased (to make the game go faster, thus increasing difficulty)
  • If it collides with the player object, the player appears to take damage, the health meter is decreased, and the bullet is destroyed.
# Game loop: respawn enemy object
time = 4
Utils.interval 4, ->
enemyRing.rotation = Utils.randomNumber(-180,180)
enemy = new Layer
parent: enemyRing
backgroundColor: "red"
width: 25
height: 25
borderRadius: "50%"
opacity: 0
x: Align.center
y: Align.center
move = new Animation
layer: enemy
properties:
y: Align.top
opacity: 1
time: time
curve: "linear"
move.start()
move.onAnimationEnd ->
enemy.destroy()
enemy.on "change:y", ->
for bullet in bulletRing.children
if (enemy.midX > bullet.x && enemy.midX < bullet.maxX) && (enemy.midY > bullet.y && enemy.midY < bullet.maxY) && (Utils.round(enemyRing.rotation, 0, 10) == Utils.round(bulletRing.rotation, 0, 10))
if time > 1
time -= 0.25
else if time < 1
time = 1
bullet.destroy()
enemy.destroy()
if (enemy.midX > plane.x && enemy.midX < plane.maxX) && (enemy.midY > plane.y && enemy.midY < plane.maxY) && (Utils.round(enemyRing.rotation, 0, 40) == Utils.round(playerRing.rotation, 0, 40))
if health.scaleX > 0.1
health.scaleX -= 0.1
else if Utils.round(health.scaleX, 1) == 0.1
print "You died"
plane.states.switchInstant "damaged"
plane.states.switch "default"
enemy.destroy()

Framer/CoffeeScript mechanics highlighted here

  • Event listeners
  • Layer states
  • Control flow
  • Loops
  • Global variables