2.2.8 Cycle 8 Wind

Design

The aim of this cycle is to add randomisation to the game even further in the form of a wind force which affects the trajectory of the bullet when it is in the air.

Objectives

Key functions

Function Name
Use

addWind

Generate a random force between 2 boundaries to be used as a wind force

Development

Outcome

main.ts
import "kaboom/global"
import kaboom from "kaboom"
import Terrain from "./terrain-generate.js"

export const WIDTH = 1920
export const HEIGHT = 1080
const GRAVITY = 450

kaboom({
  width: WIDTH,
  height: HEIGHT,
  background: [69, 65, 65]
})

/*
const recording = record()

onKeyDown(".", () => {
  recording.download({filename: "recording"})
})
*/

setGravity(GRAVITY)

loadBean()
loadPedit("barrel", "sprites/barrel.pedit")
loadPedit("bullet", "sprites/bullet.pedit")
loadPedit("arrow", "sprites/arrow.pedit")

function addBtn(txt, p, f) {
  // add a parent background object
  const btn = add([
    rect(240, 80, { radius: 8 }),
    pos(p),
    area(),
    scale(1),
    anchor("center"),
    outline(4),
  ])

  // add a child object that displays the text
  btn.add([
    text(txt),
    anchor("center"),
    color(0, 0, 0),
  ])

  // onHoverUpdate() comes from area() component
  // it runs every frame when the object is being hovered
  btn.onHoverUpdate(() => {
    btn.scale = vec2(1.2)
    setCursor("pointer")
  })

  // onHoverEnd() comes from area() component
  // it runs once when the object stopped being hovered
  btn.onHoverEnd(() => {
    btn.scale = vec2(1)
    setCursor("default")
  })

  // onClick() comes from area() component
  // it runs once when the object is clicked
  btn.onClick(f)

  return btn

}

const addWind = () => {
  //generate random number (+ or -) for wind force
  //display wind 
  //add the force to the bullet either through the move command of the bullet or by detecting when the bullet is high up
  const minWind = -2
  const windVariance = 5
  let windForce = minWind + Math.floor(Math.random() * windVariance)
  
  add([
    text(Math.abs(windForce).toString()),
    pos(176, 24)
  ])

  add([
    sprite("arrow"),
    pos(186, 20),
    rotate(windForce < 0 ? 180 : 0),
    anchor("center")
  ])

  return windForce
}

const newBullet = (position, angle, power) => {
  return add([
    sprite("bullet"),
    pos(position),
    //offscreen({ destroy: true }),
    area(),
    anchor("center"),
    body(),
    rotate(angle),
    move(vec2(Math.cos(angle / 180 * Math.PI), Math.sin(angle / 180 *     Math.PI)), (power + 20) * 8.3),              //Calculating movement vector
    "bullet"
  ])
}

scene("practice", () => {

  onDraw(() => {
    Terrain.drawTerrain()
  })

  Terrain.seedTerrain()
  Terrain.interpolateLinear()
  Terrain.tCollision()
  const wind = addWind()
  
  onLoad(() => {
    add([
      pos(250, 250),
      text("Press \"esc\" to return to menu"),
      lifespan(2, { fade: 0.5 })
    ])
  })

  const bean = add([
    sprite("bean"),
    pos(80, HEIGHT - (Terrain.points[80] + 27)),
    rotate(0),
    anchor("center"),
    {
      SPEED_HIGH: 150,
      SPEED_LOW: 45,
      power: 0
    }
  ])

  const target = add([
    sprite("bean"),
    pos(1500, HEIGHT - (Terrain.points[1500] + 27)),
    area(),
    anchor("center")
  ])

  bean.add([
    sprite("barrel"),
    scale(3, 3),
    pos(0, -60)
  ])

  target.onCollide("bullet", () => {
    addKaboom(target.pos)
    destroy(target)
    destroyAll("bullet")
    go("practice")
  })

  onCollide("bullet", "ground", () => {
    destroyAll("bullet")
  })

  target.onCollide("nuke", () => {
    addKaboom(target.pos, {scale: 15})
    destroy(target)
    destroyAll("nuke")
    play("explosion")
    wait(3, () => {go("practice")})
  })

  const angle = add([            //UI for displaying the angle of player
    text(Math.floor(bean.angle).toString()),
    pos(24, 24)
  ])

  const power = add([          //UI to display power of the shot
    text(Math.floor(bean.power).toString()),
    pos(100, 24)
  ])

  let bullet
  onKeyDown("space", () => {
    if (get("bullet").length == 0) {
      let endOfBarrel = vec2(bean.pos.x + 85 * Math.cos(-0.15 + bean.angle / 180 * Math.PI), bean.pos.y + 92 * Math.sin(-0.15 + bean.angle / 180 * Math.PI)) //Find the position of the end of the barrel to add bullets
      bullet = newBullet(endOfBarrel, bean.angle, bean.power)
    }
  })

  onUpdate(() => {
    angle.text = Math.abs(Math.floor(bean.angle)).toString()  //Update player angle UI
    power.text = Math.floor(bean.power).toString()

    if (get("bullet").length > 0) {
      if (bullet.pos.x > WIDTH || bullet.pos.x < 0) {
        destroy(bullet)
      }
      if (bullet.pos.y < 300) {
        bullet.pos.x += wind
      }
    }
  })

  let rotationSpeed = bean.SPEED_HIGH

  onKeyDown("alt", () => {
    rotationSpeed = bean.SPEED_LOW  //Slow rotation when holding alt
  })

  onKeyRelease("alt", () => {
    rotationSpeed = bean.SPEED_HIGH  //Normal when alt released
  })

  onKeyDown("a", () => {
    if (bean.pos.x > 0) {
      bean.pos.x--
      bean.pos.y = HEIGHT - Terrain.points[bean.pos.x] - 27 
    }
  })
  
  onKeyDown("d", () => {
    if (bean.pos.x < WIDTH / 3) {
      bean.pos.x++
      bean.pos.y = HEIGHT - Terrain.points[bean.pos.x] - 27
    }
  })
  
  onKeyDown("left", () => {
    if (bean.angle > -70) {
      bean.angle += -rotationSpeed * dt() //Rotate left
    }
    else {
      bean.angle = -70  //Stop at 70 degrees anticlockwise
    }
  })

  onKeyDown("right", () => {
    if (bean.angle < 0) {
      bean.angle += rotationSpeed * dt() //Rotate right
    }
    else {
      bean.angle = 0  //Prevent tank rotating the wrong direction
    }
  })

  let smooth = 3;

  onKeyDown("up", () => {
    smooth += 1.2 * dt()
    if (bean.power < 100) {
      bean.power += 0.7 * smooth ** 2 * dt() //Increase power smoothly
    }
    else {
      bean.power = 100  //Maximum power 100
    }
  })

  onKeyRelease("up", () => {
    smooth = 3
  })

  onKeyDown("down", () => {
    smooth += 1.2 * dt()
    if (bean.power > 1) {
      bean.power -= 0.7 * smooth ** 2 * dt()  //Decrease power smoothly
    }
    else {
      bean.power = 0  //Minimum power 0
    }
  })

  onKeyRelease("down", () => {
    smooth = 3
  })

  const cheatInputs = ["up","up","down","down","left","right","left","right","space"]
  let inputs = []
  const cheatCode = (key) => {
    inputs.push(key)
    if (cheatInputs.slice(0,inputs.length).toString() == inputs.toString()) {
      if (inputs.length == cheatInputs.length){
        inputs = []
        dropNuke(0, -300)
      }
    }
    else {
      inputs = []
    }
  }

  const dropNuke = (x, y) => {
    loadSprite("nuke", "/sprites/nuke.png")
    loadSound("explosion", "/sounds/explosion.mp3")
    const nuke = add([
      sprite("nuke"),
      rotate(target.pos.angle(vec2(x,y))),
      scale(0.7),
      pos(x, y),
      anchor("center"),
      area(),
      move(target.pos.angle(vec2(x,y)), 1200),
      "nuke"
    ])
  }
  
  onKeyPress((key) => {
    cheatCode(key)
  })
    
  /*onKeyPress("up", () => {})
  onKeyPress("down", () => {})
  onKeyPress("left", () => {})
  onKeyPress("right", () => {})*/
  
  onKeyDown("escape", () => {
    go("main-menu")
  })

})

scene("multiplayer-menu", () => {
  addBtn("Join Game", vec2(250, 150), () => { })
  addBtn("Create Game", vec2(250, 250), () => { })
  addBtn("Return", vec2(250, 350), () => { go("main-menu") })
})

scene("main-menu", () => {
  addBtn("Practice", vec2(250, 250), () => { go("practice") })
  addBtn("Multiplayer", vec2(250, 350), () => { go("multiplayer-menu") })
})

go("main-menu")

Challenges

The one challenge I had encountered while developing this cycle is that some levels seemed impossible during playthroughs so I had to make the player able to move along the terrain to make shots easier.

Testing

Tests

Test
Instructions
What I expect
What actually happens
Pass/Fail

1

Run code

Kaboom should run

Kaboom ran normally

Pass

2

Enter "practice"

Wind should be displayed in the UI

Wind force is shown with an arrow to show direction

Pass

3

Shoot a bullet in the air

Bullet should be affected by wind when in the air

Works as expected

Pass

4

Press "a" and "d" keys

Player should move left and right along the terrain

Works as expected

Pass

Evidence

Wind UI shown above player

Last updated