第七章:敌人生成:挑战的来源
在本章节,我们将通过动态生成敌人来增加游戏的挑战性。以下内容将基于前几章的代码,重点讲解如何设计和实现敌人的生成逻辑及行为。
1. 敌人生成的设计思路
在游戏中, 敌人是挑战的来源。我们希望实现以下功能:
- 随机生成敌人:敌人从不同方向生成(上、下、左、右)。
- 动态调整敌人速度:随着得分的增加,敌人的速度逐渐提升。
- 碰撞处理:敌人与玩家碰撞时结束游戏。
2. 实现敌人生成逻辑
首先,为了动态生成敌人,我们需要编写一个生成函数 Enemy
。以下是代码逻辑的分解:
- 生成位置:随机选择从上下左右四个边界生成敌人。
- 运动方向:基于生成位置随机生成敌人的运动方向。
- 速度调整:根据当前得分动态增加速度。
以下是完整的 Enemy
函数代码:
dodge_the_creeps/init.tsx
const Enemy = (world: PhysicsWorld.Type, score: number) => {
const dir = math.random(0, 3); // 随机选择方向
const angle = math.random(dir * 90 + 25, dir * 90 + 180 - 25); // 确保敌人不会直接沿着轴线生成
let pos = Vec2.zero;
const minW = -hw - 40; const maxW = hw + 40;
const minH = -hh - 40; const maxH = hh + 40;
const randW = math.random(minW, maxW);
const randH = math.random(minH, maxH);
// 根据生成方向选择初始位置
switch (dir) {
case 0: pos = Vec2(minW, randH); break; // 左边生成
case 1: pos = Vec2(randW, maxH); break; // 下方生成
case 2: pos = Vec2(maxW, randH); break; // 右边生成
case 3: pos = Vec2(randW, minH); break; // 上方生成
}
const radian = math.rad(angle); // 角度转弧度
const velocity = Vec2(math.sin(radian), math.cos(radian))
.normalize()
.mul(200 + score * 2); // 速度与得分相关
// 创建敌人实体
toNode(
<body world={world} group={0} type={BodyMoveType.Dynamic} linearAcceleration={Vec2.zero}
x={pos.x} y={pos.y} velocityX={velocity.x} velocityY={velocity.y} angle={angle}
onMount={node => {
const enemys = [Animation.enemyFlyingAlt, Animation.enemySwimming, Animation.enemyWalking];
playAnimation(node, enemys[math.random(0, 2)]); // 随机选择一种动画
}}>
<disk-fixture radius={40}/> {/* 设置碰撞体积 */}
</body>
)?.addTo(world);
};
3. 动态生成敌人
在主游戏逻辑中,我们需要定时生成敌人。以下代码展示了如何使用 world.loop
来实现每 隔一段时间,并根据玩家的积分来生成敌人:
示例代码
world.loop(() => {
sleep(0.5); // 每 0.5 秒生成一个敌人
Enemy(world, score); // 传入当前得分
return false; // 持续循环
});
4. 调整游戏平衡性
为了优化游戏体验,可以尝试调整敌人的以下参数:
- 初始速度:通过修改
200 + score * 2
的 200 初始值。 - 生成频率:通过调整
sleep(0.5)
的时间间隔。 - 得分对速度的影响:改变速度公式中的得分权重,例如
200 + score * 4
。
5. 游戏效果验证
在完成敌人生成后,可以运行游戏测试效果:
- 敌人生成方向和速度是否符合预期。
- 碰撞检测是否正常工作。
- 随着得分的增加,游戏是否变得更具挑战性。
6. 敌人离开场景逻辑
设计思路:
- 为了避免场景中堆积过多的敌人,我们需要检测敌人是否离开场景边界。
- 离开场景的敌人会被移除。
要检测敌人是否离开场景,我们可以通过在 <physics-world>
添加一个静态 body
,使用一个覆盖整个场景的矩形感应区域(Sensor)。任何进入该区域的敌人在离开时都会触发离开事件。
以下是实现代码:
示例代码
<physics-world>
<body type={BodyMoveType.Static} group={1} onBodyLeave={() => {
// 敌人离开场景时记分
score++;
}}>
<rect-fixture sensorTag={0} width={width} height={height}/> {/* 感应区域大小与场景一致 */}
</body>
</physics-world>
代码解释:
onBodyLeave
是一个回调,当敌人离开场景时会触发。score++
用于增加分数,并通过label.current.text
实时更新得分显示。
7. 记分显示
设计思路:
- 玩家通过避免与敌人碰撞并让敌人离开场景来得分。
- 每个敌人离开场景时,玩家得分增加。
在游戏界面中,我们使用一个 Label
来显示当前得分。确保标签在玩家看到 "Get Ready!" 提示后才显示。
示例代码
const label = useRef<Label.Type>(); // 创建得分标签的引用
<label ref={label} fontName='Xolonium-Regular' fontSize={60} text='0' y={300} visible={false}/>
代码解释:
useRef
用于动态更新标签。- 初始
visible
属性设为false
,直到游戏正式开始时显示。
在游戏开始并且玩家看到 "Get Ready!" 提示后,得分标签通过以下逻辑开始显示:
示例代码
world.once(() => {
const msg = toNode(
<label fontName='Xolonium-Regular' fontSize={80} text='Get Ready!' y={200}/>
);
sleep(1); // 等待 1 秒后移除提示
msg?.removeFromParent();
if (label.current) {
label.current.visible = true; // 显示得分标签
}
// 开始定期生成敌人
// ...
});
8. 完整敌人逻辑整合
以下是整合后的核心逻辑代码段:
dodge_the_creeps/init.tsx
const Game = () => {
inputManager.popContext();
inputManager.pushContext('Game');
let score = 0;
const label = useRef<Label.Type>(); // 创建得分标签的引用
Audio.playStream('Audio/House In a Forest Loop.ogg', true);
return (
<clip-node stencil={<Rect/>}>
<physics-world onMount={world => {
Player(world); // 创建玩家
world.once(() => {
// 显示提示
const msg = toNode(
<label fontName='Xolonium-Regular' fontSize={80} text='Get Ready!' y={200}/>
);
sleep(1);
msg?.removeFromParent();
if (label.current) {
label.current.visible = true;
}
// 定期生成敌人
world.loop(() => {
sleep(0.5);
Enemy(world, score);
return false;
});
});
}}>
<contact groupA={0} groupB={0} enabled={false}/> {/* 敌人间不发生碰撞 */}
<contact groupA={0} groupB={1} enabled/> {/* 玩家与敌人碰撞检测 */}
{/* 感应器,用于检测敌人离开场景 */}
<body type={BodyMoveType.Static} group={1} onBodyLeave={() => {
score++;
if (label.current) {
label.current.text = score.toString();
}
}}>
<rect-fixture sensorTag={0} width={width} height={height}/>
</body>
</physics-world>
<label ref={label} fontName='Xolonium-Regular' fontSize={60} text='0' y={300} visible={false}/>
</clip-node>
);
};
9. 调试与验证
完成后,运行游戏测试以下功能:
- 敌人离开场景是否触发得分。
- 得分是否正确更新。
- 得分标签显示逻辑是否与游戏提示配合良好。
10. 本节小结
通过本节内容,我们实现了敌人离开场景的检测和玩家得分机制,使得游戏更加完整和有趣。接下来,我们将在下一章中学习如何设计游戏界面,包括添加暂停功能、重新开始按钮等。