Przykłady
[object Object]
Przykład 1: scena z ruchem i kolizjami#
Kompletna scena: gracz sterowany akcjami, ściany immovable, granice świata i kamera podążająca.
tsCollectScene.ts
import { Scene, type GameObject } from '@engine'
const WORLD = { width: 2000, height: 1400 }
export class CollectScene extends Scene {
private player!: GameObject
private walls: GameObject[] = []
private score = 0
constructor() {
super('collect')
}
declareAssets() {
super.declareAssets([
{ id: 'hero', type: 'image', src: '/assets/hero.png' },
{ id: 'coin', type: 'image', src: '/assets/coin.png' },
{ id: 'pickup', type: 'audio', src: '/assets/pickup.ogg' }
])
}
async preload() {
await this.getAssets().load()
}
create() {
this.getPhysics().setWorldBounds(0, 0, WORLD.width, WORLD.height)
this.player = this.createSprite('hero', { x: 200, y: 300, tags: ['player'] })
this.getPhysics().attachBody(this.player)
this.getPhysics().setCollideWorldBounds(this.player, true)
const wallSpecs = [
{ x: 520, y: 420, width: 280, height: 24 },
{ x: 1200, y: 600, width: 24, height: 320 },
{ x: 900, y: 1050, width: 400, height: 24 }
]
for (const spec of wallSpecs) {
const wall = this.createRect(spec.width, spec.height, { x: spec.x, y: spec.y, fill: '#3a4256' })
this.getPhysics().attachBody(wall)
this.getPhysics().setImmovable(wall, true)
this.walls.push(wall)
}
this.getPhysics().addCollider(this.player, this.walls)
for (const pos of [{ x: 320, y: 220 }, { x: 780, y: 360 }, { x: 1650, y: 560 }]) {
const coin = this.createSprite('coin', { ...pos, tags: ['coin'] })
this.tween(coin, { y: coin.y - 8 }, { duration: 0.8, yoyo: true, repeat: -1, easing: 'sine.inOut' })
}
this.getPhysics().addOverlap(this.player, this.findByTag('coin'), (_p, coin) => {
this.collect(coin)
})
this.getCamera().setBounds(0, 0, WORLD.width, WORLD.height)
this.getCamera().follow(this.player, 0.08, 0.08)
this.createText('hud', { x: 16, y: 16, text: '0', fontSize: '20px', color: '#ffffff' })
.setScrollFactor(0)
.setDepth(1000)
}
fixedUpdate() {
const input = this.getInput()
const vx = (input.isActionDown('move-right') ? 240 : 0) - (input.isActionDown('move-left') ? 240 : 0)
const vy = (input.isActionDown('move-down') ? 240 : 0) - (input.isActionDown('move-up') ? 240 : 0)
this.getPhysics().setVelocity(this.player, vx, vy)
}
private collect(coin: GameObject) {
this.score += 1
this.playSound('pickup', { channel: 'sfx' })
this.getCamera().flash(80, 255, 215, 94)
this.tween(coin, { alpha: 0, scaleX: 2, scaleY: 2 }, {
duration: 0.25,
onComplete: () => this.destroyObject(coin)
})
this.getObject<GameObject>('hud')?.setRender({ type: 'text', text: String(this.score) })
this.getEvents().emit('score:changed', { score: this.score })
}
}
Przykład 2: komponent zachowania#
AI przeciwnika: patrol między punktami i pościg, gdy gracz jest blisko.
tsPatrolChaser.ts
import { Component, type GameObject } from '@engine'
export class PatrolChaser extends Component {
phase = 'behavior' as const
private dir = 1
private speed = 90
constructor(
private minX: number,
private maxX: number,
private target: GameObject,
private chaseRange = 220
) {
super()
}
onUpdate(dt: number) {
const owner = this.owner
const dist = Math.hypot(this.target.x - owner.x, this.target.y - owner.y)
if (dist < this.chaseRange) {
const dx = Math.sign(this.target.x - owner.x)
owner.translate(dx * this.speed * 1.6 * dt, 0)
owner.setTint(0xff6b6b)
return
}
owner.setTint(null)
owner.translate(this.dir * this.speed * dt, 0)
if (owner.x <= this.minX) this.dir = 1
if (owner.x >= this.maxX) this.dir = -1
}
}
ts
const enemy = this.createSprite('enemy', { x: 600, y: 300, tags: ['enemy'] })
enemy.add(new PatrolChaser(400, 900, this.player))
Przykład 3: menu z przejściem i fade#
tsMenuScene.ts
import { Scene } from '@engine'
export class MenuScene extends Scene {
constructor() {
super('menu')
}
create() {
this.getCamera().fadeIn(400)
this.playMusic('theme', { loop: true })
const title = this.createText('title', { x: 640, y: 240, text: 'MOJA GRA', fontSize: '48px', color: '#ffffff' })
.setOrigin(0.5, 0.5)
this.tweenFrom(title, { alpha: 0, y: 180 }, { duration: 0.6, easing: 'back.out' })
const start = this.createText('start', { x: 640, y: 420, text: 'Start', fontSize: '24px', color: '#8f96f8' })
.setOrigin(0.5, 0.5)
this.tween(start, { alpha: 0.5 }, { duration: 0.7, yoyo: true, repeat: -1 })
}
update() {
if (this.getInput().isActionPressed('interact') || this.getInput().getPointer().isDown) {
this.getAudio().unlock()
this.getCamera().fadeOut(350)
this.getScheduler().delay(0.4, () => {
this.getEvents().emit('request:scene', { to: 'collect' })
}, { owner: this.id })
}
}
}
Przykład 4: autosave do IndexedDB#
tssave.ts
import { IndexedDbStore, PlayerSaveRepository } from '@engine'
const saves = new PlayerSaveRepository(new IndexedDbStore({ database: 'mygame' }))
export async function autosave(playerId: string, state: PlayerState) {
await saves.save(playerId, 'autosave', { ...state, savedAt: Date.now() })
}
export async function loadAutosave(playerId: string) {
return saves.load<PlayerState>(playerId, 'autosave')
}
ts
class CollectScene extends Scene {
shutdown() {
void autosave('player-1', {
score: this.score,
position: { x: this.player.x, y: this.player.y }
})
}
}
Rejestracja wszystkiego#
tsmain.ts
import { createEngine } from '@engine'
import { MenuScene } from './scenes/MenuScene'
import { CollectScene } from './scenes/CollectScene'
const engine = createEngine({
parent: 'app',
width: 1280,
height: 720,
fixedStep: { hz: 60 },
seed: 20260
})
engine.registerScene(new MenuScene())
engine.registerScene(new CollectScene())
engine.on<{ to: string }>('request:scene', ({ to }) => engine.startScene(to))
engine.start('menu')
Wskazówka
Więcej działających przykładów znajdziesz w repozytorium: apps/playground/src/scenes/GameScene.ts (pełna gra z zapisami, colliderami i HUD) oraz apps/playground/src/components/ (PlayerController, Bobbing, CollectEffect, Sparkle).