- Lifecycle
- Besturing
- Collision
- Oefening
- Improving the game
- Fonts en geluid
- Een Actor heeft ingebouwde lifecycle functies, bv:
kill()
verwijdert de actor uit de game. - Een Actor heeft
lifecycle
methods. Dit zijn methods die automatisch worden aangeroepen als er een bepaald event gebeurt.- De
onInitialize
method wordt aangeroepen als de Actor in de Game is geplaatst. - De
onPostUpdate
enonPreUpdate
methods vinden 60 keer per seconde plaats (je framerate). - De
onPostKill
enonPreKill
methods worden aangeroepen als een actor uit de game wordt verwijderd.
- De
- Een Actor heeft
events
. Hier kan je listeners voor aanmaken viathis.on()
. Dit is vergelijkbaar met PRG3.- Het
exitviewport
event vindt plaats als de Actor buiten beeld beweegt. - Het
pointerup
event vindt plaats als je op de sprite klikt.
- Het
CODE VOORBEELD
class Car extends Actor {
constructor() {
super({width: 100, height: 100 })
}
onInitialize(engine){
this.enableCapturePointer = true
this.pointer.useGraphicsBounds = true
this.on("pointerup", () => this.kill())
this.on("exitviewport", () => this.resetCar())
}
onPostKill() {
console.log("car was removed")
}
resetCar(){
this.pos = new Vector(500,100)
}
onPostUpdate(){
if(this.pos.x < 100) {
console.log("de auto is links")
}
}
}
Om een karakter te kunnen besturen kan je luisteren naar toetsenbord input. Hieronder een voorbeeld van een Car die naar links en rechts kan bewegen. Als een key
is ingedrukt pas je de velocity
aan.
export class Car extends Actor {
onInitialize(engine) {
this.graphics.use(Resources.Car.toSprite());
this.pos = new Vector(400, 400);
this.vel = new Vector(0, 0);
}
onPreUpdate(engine) {
let xspeed = 0;
if (engine.input.keyboard.isHeld(Keys.Left)) {
xspeed = -1;
}
if (engine.input.keyboard.isHeld(Keys.Right)) {
xspeed = 1;
}
this.vel = new Vector(xspeed, 0);
}
}
De keyboard.isHeld
wordt 60 keer per seconde uitgevoerd, dit is dus niet handig voor schieten of springen. Dat moet maar 1 keer gebeuren op het moment dat een knop wordt ingedrukt:
export class Car extends Actor {
onPreUpdate(engine) {
if (engine.input.keyboard.wasPressed(Keys.Space)) {
console.log("shoot!")
}
}
}
Om te weten of een actor een andere actor raakt kan je het collisionstart
event gebruiken. 🚨 Om een "collision" event te kunnen afvuren moet een Actor altijd een width
en height
hebben.
class ActionHenk extends Actor {
constructor() {
super({width: 100, height: 100 })
// alternatief, een cirkel hitbox
// super({radius: 50})
}
onInitialize(engine){
this.on('collisionstart', (event) => this.hitSomething(event))
}
hitSomething(event) {
console.log(`we hit something! ${event.other}`)
}
}
Om te weten wat je hebt geraakt kan je instanceof
gebruiken.
import { Tree } from "./tree.js"
class Car extends Actor {
hitSomething(event) {
if(event.other instanceof Tree) {
console.log("the car hits the tree")
this.kill() // remove the car
event.other.kill() // remove the tree
}
}
}
- Voeg 10 of meer instances van een Actor class toe aan je game met een
for
loop, bv.new Fish()
. - De actors krijgen een random positie. Laat de actors door het beeld bewegen. Als ze uit de viewport gaan, verschijnen ze weer aan de linkerkant.
this.pos.x = Math.random() * 800
this.pos.y = Math.random() * 600
- Maak een player character Actor aan die je met WASD/Cursorkeys kan laten bewegen door het scherm
- Voeg een collision handler aan de player character toe. Als er een collision is, verwijder je het object waar je tegenaan botst via
event.other.kill()
.
Maak een nieuwe class voor een Actor waar je tegenaan kan botsen, bv. een coin. Een coin geeft punten, maar een enemy kost health. Zowel de pickup als enemy verwijder je uit de game na een collision. met event.other.kill()
Als je health 0 is verwijder je de player met this.kill()
.
class Player extends Actor {
onInitialize(){
this.score = 0
this.health = 100
}
hitSomething(event) {
if(...) { ... }
}
}
Als je tijd over hebt kan je naar eigen inzicht een of meer van de onderstaande verbeteringen toevoegen.
Een pickup of botsing met een vijand heeft meer impact als het geluid maakt. Zie hieronder voor het laden van geluid.
Om binnen beeld te blijven kan je in de keyboard
code controleren dat de pos
van de player niet buiten de afmeting van de game gaat.
class Player extends Actor {
onPreUpdate(engine){
if (engine.input.keyboard.isHeld(Keys.Left) && this.pos.x > 30) {
xspeed = -300
}
if (engine.input.keyboard.isHeld(Keys.Right) && this.pos.x < 770) {
xspeed = 300
}
}
}
Je kan de camera de speler laten volgen. Je level kan dan ineens veel groter zijn dan het scherm.
export class Game extends Engine {
constructor(){
super({width:800, height:450})
}
startGame(){
let player = new Player()
this.add(player)
this.currentScene.camera.strategy.lockToActor(player)
this.currentScene.camera.strategy.limitCameraBounds(new BoundingBox(0, 0, 2000, 1200))
}
}
Plaats images, fonts en sounds in de public
folder. Je kan daarbinnen submappen aanmaken:
RESOURCES.JS
import { ImageSource, Sound, Resource, Loader, FontSource } from 'excalibur'
const Resources = {
Ship: new ImageSource('images/ship.png'),
LevelStart: new Sound("sounds/LevelStart0.wav"),
PixelFont: new FontSource('fonts/PressStart2P-Regular.ttf', 'PressStart')
}
const ResourceLoader = new Loader()
for (let res of Object.values(Resources)) {
ResourceLoader.addResource(res)
}
Fonts en sounds gebruiken
import {Label, FontUnit, Color, Vector} from "excalibur"
import {Resources} from "./resources.js"
class Game extends Engine {
startGame() {
// speel een geluidje
Resources.LevelStart.play()
// gebruik een pixel font
const label = new Label({
text: 'Score: 0',
pos: new Vector(0, 0),
font: Resources.PixelFont.toFont({
unit: FontUnit.Px,
size: 20,
color: Color.White
})
})
this.add(label)
}
}