主题
08 · 街机射击:玩家移动与射击
目标:键盘左右移动玩家;空格发射子弹并带冷却;子弹出屏回收。
前置:07 · Arcade 物理
玩法对齐:05 规格
1. 输入:状态表心智不变
阶段一用 Set 记按键;Phaser 用内置 cursors / keyboard:
ts
this.cursors = this.input.keyboard!.createCursorKeys()
this.wasd = this.input.keyboard!.addKeys('W,A,S,D,SPACE') as {
A: Phaser.Input.Keyboard.Key
D: Phaser.Input.Keyboard.Key
SPACE: Phaser.Input.Keyboard.Key
}1
2
3
4
5
6
2
3
4
5
6
每帧在 update 里读 isDown,等价于读 keys 状态表——不要在 keydown 里直接改坐标。
2. 玩家只改 velocity.x
ts
private player!: Phaser.Physics.Arcade.Sprite
private cursors!: Phaser.Types.Input.Keyboard.CursorKeys
private fireCd = 0
private readonly PLAYER_SPEED = 260
create() {
this.player = this.physics.add.sprite(240, 580, 'player')
this.player.setCollideWorldBounds(true)
this.cursors = this.input.keyboard!.createCursorKeys()
// ... wasd / SPACE
}
update(_t: number, delta: number) {
const left = this.cursors.left.isDown || this.wasd.A.isDown
const right = this.cursors.right.isDown || this.wasd.D.isDown
let vx = 0
if (left) vx -= this.PLAYER_SPEED
if (right) vx += this.PLAYER_SPEED
this.player.setVelocityX(vx)
this.player.setVelocityY(0)
this.fireCd -= delta / 1000
if (this.wasd.SPACE.isDown && this.fireCd <= 0) {
this.spawnBullet()
this.fireCd = 0.25
}
this.reclaimBullets()
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
冷却用秒,与 05 的 fireCd 一致;delta 记得除以 1000。
3. 子弹 Group
ts
private bullets!: Phaser.Physics.Arcade.Group
create() {
this.bullets = this.physics.add.group({
classType: Phaser.Physics.Arcade.Image,
maxSize: 40,
runChildUpdate: false,
})
}
private spawnBullet() {
const b = this.bullets.get(
this.player.x,
this.player.y - 20,
'bullet',
) as Phaser.Physics.Arcade.Image | null
if (!b) return
b.setActive(true).setVisible(true)
b.enableBody(true, this.player.x, this.player.y - 20, true, true)
b.setVelocity(0, -420)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
maxSize + get 是轻量复用;完整对象池心智见阶段三 16。没有空闲实例时 get 返回 null,直接 return 即可。
4. 出屏清理
ts
private reclaimBullets() {
this.bullets.children.each((child) => {
const b = child as Phaser.Physics.Arcade.Image
if (!b.active) return true
if (b.y < -20) {
this.bullets.killAndHide(b)
b.body?.stop()
}
return true
})
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
或 disableBody(true, true)。务必回收,否则长按射击会堆满屏幕外物体。
5. 本篇 GameScene 职责边界
本篇只做:玩家 + 子弹。敌人与 overlap 放 09,避免一篇塞爆。
验收清单
- [ ] A/D 或左右方向键可移动,松键即停
- [ ] 玩家撞左右边界不会出画布
- [ ] 空格射击有冷却(不是每帧一发)
- [ ] 子弹向上飞,出屏后被回收
- [ ] 长按射击约 30 秒,画面不卡死、子弹不无限堆积可见
