游戏中的子弹发射与碰撞系统

1. 基础组件

1.1 基础碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
class Circle {
x: number;
y: number;
radius: number;

intersects(other: Circle): boolean {
const dx = this.x - other.x;
const dy = this.y - other.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < (this.radius + other.radius);
}
}

1.2 游戏对象基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class GameObject {
x: number;
y: number;
bounds: Circle;
health: number = 100;

takeDamage(amount: number): void {
this.health -= amount;
if (this.health <= 0) {
this.destroy();
}
}

destroy(): void {
// 对象销毁逻辑
}
}

2. 子弹系统实现

2.1 子弹类

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
31
32
class Bullet extends GameObject {
damage: number = 10;
speed: number = 500;
angle: number;
isDestroyed: boolean = false;

constructor(x: number, y: number, angle: number, speed: number, damage: number) {
super();
this.x = x;
this.y = y;
this.angle = angle;
this.speed = speed;
this.damage = damage;
this.bounds = new Circle(x, y, 2); // 2px radius
}

update(deltaTime: number): void {
// 更新子弹位置
this.x += Math.cos(this.angle) * this.speed * deltaTime;
this.y += Math.sin(this.angle) * this.speed * deltaTime;

// 更新碰撞边界
this.bounds.x = this.x;
this.bounds.y = this.y;
}

// 处理子弹击中目标
onHit(target: GameObject): void {
target.takeDamage(this.damage);
this.destroy();
}
}

2.2 玩家类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 武器配置
interface WeaponConfig {
fireRate: number;
bulletSpeed: number;
damage: number;
spread: number; // 子弹散布角度
bulletCount: number;// 一次发射的子弹数量
}

// 武器类型
const Weapons = {
PISTOL: {
fireRate: 0.2,
bulletSpeed: 500,
damage: 10,
spread: 0,
bulletCount: 1
},
SHOTGUN: {
fireRate: 0.8,
bulletSpeed: 400,
damage: 8,
spread: Math.PI / 8, // 45度散布
bulletCount: 5
},
MACHINE_GUN: {
fireRate: 0.05,
bulletSpeed: 600,
damage: 5,
spread: Math.PI / 32,
bulletCount: 1
}
} as const;

class Player extends GameObject {
private bullets: Bullet[] = [];
private lastFireTime: number = 0;
private currentWeapon: WeaponConfig = Weapons.PISTOL;

constructor(x: number, y: number) {
super();
this.x = x;
this.y = y;
this.bounds = new Circle(x, y, 16); // 16px radius
this.health = 100;
}

// 切换武器
switchWeapon(weapon: WeaponConfig): void {
this.currentWeapon = weapon;
}

// 处理射击输入
handleInput(input: Input, currentTime: number): void {
// 检查是否可以发射子弹(冷却时间)
if (input.isMouseDown && currentTime - this.lastFireTime >= this.currentWeapon.fireRate) {
this.shoot(input.mouseX, input.mouseY);
this.lastFireTime = currentTime;
}

// 处理武器切换
if (input.is1Pressed) this.switchWeapon(Weapons.PISTOL);
if (input.is2Pressed) this.switchWeapon(Weapons.SHOTGUN);
if (input.is3Pressed) this.switchWeapon(Weapons.MACHINE_GUN);
}

// 发射子弹
shoot(targetX: number, targetY: number): void {
const baseAngle = Math.atan2(
targetY - this.y,
targetX - this.x
);

// 发射多发子弹
for (let i = 0; i < this.currentWeapon.bulletCount; i++) {
// 计算散布角度
const spread = (Math.random() - 0.5) * this.currentWeapon.spread;
const angle = baseAngle + spread;

const bullet = new Bullet(
this.x,
this.y,
angle,
this.currentWeapon.bulletSpeed,
this.currentWeapon.damage
);
this.bullets.push(bullet);
}
}

// 更新所有子弹
updateBullets(deltaTime: number, enemies: Enemy[]): void {
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i];
bullet.update(deltaTime);

// 检查子弹是否击中敌人
for (const enemy of enemies) {
if (bullet.bounds.intersects(enemy.bounds)) {
bullet.onHit(enemy);
this.bullets.splice(i, 1);
break;
}
}

// 移除超出屏幕的子弹
if (this.isOutOfBounds(bullet)) {
this.bullets.splice(i, 1);
}
}
}

private isOutOfBounds(bullet: Bullet): boolean {
return bullet.x < 0 || bullet.x > 800 || bullet.y < 0 || bullet.y > 600;
}

// 受到伤害
takeDamage(amount: number): void {
super.takeDamage(amount);
console.log(`Player took ${amount} damage, health: ${this.health}`);
}

// 获取当前武器信息
getCurrentWeapon(): WeaponConfig {
return this.currentWeapon;
}

// 获取当前子弹数量
getBulletCount(): number {
return this.bullets.length;
}
}

2.3 敌人类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class Enemy extends GameObject {
private target: Player;
private moveSpeed: number = 100;
private attackRange: number = 200;
private attackCooldown: number = 1;
private lastAttackTime: number = 0;
private bullets: Bullet[] = [];

// 攻击属性
private attackDamage: number = 10;
private bulletSpeed: number = 300;
private burstCount: number = 3; // 一次发射多少子弹
private burstDelay: number = 0.1; // 子弹间隔时间
private currentBurst: number = 0;
private nextBurstTime: number = 0;

constructor(x: number, y: number, target: Player) {
super();
this.x = x;
this.y = y;
this.bounds = new Circle(x, y, 16);
this.health = 50;
this.target = target;
}

update(deltaTime: number): void {
const currentTime = performance.now() / 1000;

// 计算到目标的距离
const dx = this.target.x - this.x;
const dy = this.target.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);

if (distance > this.attackRange) {
// 在攻击范围外,移动向目标
const angle = Math.atan2(dy, dx);
this.x += Math.cos(angle) * this.moveSpeed * deltaTime;
this.y += Math.sin(angle) * this.moveSpeed * deltaTime;

// 更新碰撞边界
this.bounds.x = this.x;
this.bounds.y = this.y;

// 重置攻击状态
this.currentBurst = 0;
this.nextBurstTime = 0;
} else {
// 在攻击范围内,检查是否可以攻击
if (currentTime - this.lastAttackTime >= this.attackCooldown) {
this.startAttack(currentTime);
}
}

// 处理连发子弹
if (this.currentBurst > 0 && currentTime >= this.nextBurstTime) {
this.fireBullet();
this.currentBurst--;
this.nextBurstTime = currentTime + this.burstDelay;
}

// 更新子弹
this.updateBullets(deltaTime);
}

private startAttack(currentTime: number): void {
this.currentBurst = this.burstCount;
this.nextBurstTime = currentTime;
this.lastAttackTime = currentTime;
}

private fireBullet(): void {
// 计算射击角度(带一点随机偏移)
const baseAngle = Math.atan2(
this.target.y - this.y,
this.target.x - this.x
);
const spread = (Math.random() - 0.5) * (Math.PI / 16); // 添加一些随机散布
const angle = baseAngle + spread;

// 创建子弹
const bullet = new Bullet(
this.x,
this.y,
angle,
this.bulletSpeed,
this.attackDamage
);
this.bullets.push(bullet);
}

private updateBullets(deltaTime: number): void {
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i];
bullet.update(deltaTime);

// 检查子弹是否击中玩家
if (bullet.bounds.intersects(this.target.bounds)) {
bullet.onHit(this.target);
this.bullets.splice(i, 1);
continue;
}

// 检查子弹是否超出屏幕
if (this.isOutOfBounds(bullet)) {
this.bullets.splice(i, 1);
}
}
}

private isOutOfBounds(bullet: Bullet): boolean {
return bullet.x < 0 || bullet.x > 800 || bullet.y < 0 || bullet.y > 600;
}

takeDamage(amount: number): void {
super.takeDamage(amount);
console.log(`Enemy took ${amount} damage, health: ${this.health}`);
}

destroy(): void {
super.destroy();
console.log('Enemy destroyed');
}
}

3. 游戏系统架构

3.1 游戏状态

1
2
3
4
5
6
enum GameState {
MENU,
PLAYING,
PAUSED,
GAME_OVER
}

3.2 游戏主类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class Game {
private ctx: CanvasRenderingContext2D;
private lastTime: number = 0;
private state: GameState = GameState.MENU;

// 游戏对象
private player: Player;
private enemies: Enemy[] = [];
private effects: Effect[] = [];

// 游戏配置
private readonly CANVAS_WIDTH = 800;
private readonly CANVAS_HEIGHT = 600;
private readonly MAX_ENEMIES = 5;
private readonly ENEMY_SPAWN_INTERVAL = 3000; // 3秒
private lastEnemySpawn: number = 0;

constructor(canvas: HTMLCanvasElement) {
this.ctx = canvas.getContext('2d')!;
this.player = new Player(400, 300);
this.setupEventListeners();
}

private setupEventListeners(): void {
// 键盘事件监听
document.addEventListener('keydown', (e) => this.handleKeyDown(e));
document.addEventListener('keyup', (e) => this.handleKeyUp(e));

// 鼠标事件监听
document.addEventListener('mousemove', (e) => this.handleMouseMove(e));
document.addEventListener('mousedown', (e) => this.handleMouseDown(e));
document.addEventListener('mouseup', (e) => this.handleMouseUp(e));
}

start(): void {
this.state = GameState.PLAYING;
this.lastTime = performance.now();
this.gameLoop();
}

private gameLoop(): void {
if (this.state !== GameState.PLAYING) return;

const currentTime = performance.now();
const deltaTime = (currentTime - this.lastTime) / 1000;
this.lastTime = currentTime;

this.update(deltaTime);
this.render();

requestAnimationFrame(() => this.gameLoop());
}

private update(deltaTime: number): void {
// 更新玩家
this.player.update(deltaTime);

// 更新敌人
for (const enemy of this.enemies) {
enemy.update(deltaTime);
}

// 生成新敌人
this.spawnEnemies();

// 清理死亡的敌人
this.enemies = this.enemies.filter(enemy => enemy.health > 0);

// 更新特效
this.updateEffects(deltaTime);

// 检查游戏结束条件
this.checkGameOver();
}

private render(): void {
// 清空画布
this.ctx.clearRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT);

// 绘制背景
this.renderBackground();

// 绘制游戏对象
this.player.render(this.ctx);
this.enemies.forEach(enemy => enemy.render(this.ctx));
this.effects.forEach(effect => effect.render(this.ctx));

// 绘制UI
this.renderUI();
}

private spawnEnemies(): void {
const currentTime = performance.now();
if (currentTime - this.lastEnemySpawn >= this.ENEMY_SPAWN_INTERVAL
&& this.enemies.length < this.MAX_ENEMIES) {

const spawnPoint = this.getRandomSpawnPoint();
const enemy = new Enemy(spawnPoint.x, spawnPoint.y, this.player);
this.enemies.push(enemy);
this.lastEnemySpawn = currentTime;
}
}

private getRandomSpawnPoint(): Point {
// 在画布边缘随机生成敌人
const side = Math.floor(Math.random() * 4); // 0:上, 1:右, 2:下, 3:左
let x = 0, y = 0;

switch (side) {
case 0: // 上边
x = Math.random() * this.CANVAS_WIDTH;
y = -20;
break;
case 1: // 右边
x = this.CANVAS_WIDTH + 20;
y = Math.random() * this.CANVAS_HEIGHT;
break;
case 2: // 下边
x = Math.random() * this.CANVAS_WIDTH;
y = this.CANVAS_HEIGHT + 20;
break;
case 3: // 左边
x = -20;
y = Math.random() * this.CANVAS_HEIGHT;
break;
}

return { x, y };
}

private updateEffects(deltaTime: number): void {
this.effects = this.effects.filter(effect => !effect.isFinished());
this.effects.forEach(effect => effect.update(deltaTime));
}

private checkGameOver(): void {
if (this.player.health <= 0) {
this.state = GameState.GAME_OVER;
console.log('Game Over!');
}
}

private renderBackground(): void {
this.ctx.fillStyle = '#000';
this.ctx.fillRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT);
}

private renderUI(): void {
// 绘制生命值
this.ctx.fillStyle = '#fff';
this.ctx.font = '20px Arial';
this.ctx.fillText(`Health: ${this.player.health}`, 10, 30);

// 绘制击杀数
this.ctx.fillText(`Score: ${this.player.score}`, 10, 60);

// 游戏结束画面
if (this.state === GameState.GAME_OVER) {
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
this.ctx.fillRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT);

this.ctx.fillStyle = '#fff';
this.ctx.font = '48px Arial';
this.ctx.fillText('GAME OVER',
this.CANVAS_WIDTH / 2 - 100,
this.CANVAS_HEIGHT / 2);

this.ctx.font = '24px Arial';
this.ctx.fillText(`Final Score: ${this.player.score}`,
this.CANVAS_WIDTH / 2 - 70,
this.CANVAS_HEIGHT / 2 + 40);
}
}

// 输入处理方法
private handleKeyDown(e: KeyboardEvent): void {
if (this.state === GameState.PLAYING) {
this.player.handleKeyDown(e);
}
}

private handleKeyUp(e: KeyboardEvent): void {
if (this.state === GameState.PLAYING) {
this.player.handleKeyUp(e);
}
}

private handleMouseMove(e: MouseEvent): void {
if (this.state === GameState.PLAYING) {
this.player.handleMouseMove(e);
}
}

private handleMouseDown(e: MouseEvent): void {
if (this.state === GameState.PLAYING) {
this.player.handleMouseDown(e);
}
}

private handleMouseUp(e: MouseEvent): void {
if (this.state === GameState.PLAYING) {
this.player.handleMouseUp(e);
}
}
}