CodeCombat 游戏系统与动态更新

Update 事件与 Positional Hacks(1-11 & 47-55 综合)

第 1 关

方形规则 (The Square Rule)

战术思路分析:每帧决策

我们需要实现一个“石头剪刀布”式的战斗逻辑。这次不用 `while True`,而是使用全新的 **`update` 事件**。

Update 事件 vs While True

`unit.on("update", fn)`:该函数每秒运行约 30 次(每帧),这比 `while True` 更加精确和高效。

双重逻辑:攻击或移动

`if shouldAttack(...): attack() else: move()`。单位需要不断检查是否应该攻击它的“克星”,否则就向边界移动(最终销毁,避免性能浪费)。

Game Loop: game.on("update")
Unit Logic: unit.on("update")

Python 程序解法

1
def shouldAttack(who, target):
定义攻击克星的函数
2
# "scout"不喜欢"soldier"。
3
if who.type == "scout" and target.type == "soldier":
侦察兵攻击士兵
4-8
# ... 其他三组克制关系 ...
士兵攻击投矛手,投矛手攻击弓箭手,弓箭手攻击侦察兵
9
return False
默认不攻击
10
11
def onUpdateUnit(event):
单位的每帧更新处理器
12
unit = event.target
获取当前单位
13
enemy = unit.findNearestEnemy()
14
if enemy and shouldAttack(unit, enemy):
如果找到克星,则攻击
15
unit.attack(enemy)
16
else:
否则,向边界移动
17-21
# ... 敌对阵营向左,友方阵营向右移动 ...
移动 unit.pos.x +/- 3
22
# 我们不需要屏幕之外的单位,因此对它们使用destroy()。
23
if unit.pos.x > 80 or unit.pos.x < 0:
如果单位移出地图
24
unit.destroy()
销毁单位
25
26-29
# ... 绑定 update 事件到所有单位 ...
设置所有单位使用 onUpdateUnit 处理器
30-33
# ... 游戏设置和 spawnRandomUnit 函数 ...
设置计时器和随机生成函数
34
def onUpdateGame(event):
主游戏循环 (Update)
35-39
# ... 检查计时器并生成敌人 ...
按时间间隔生成友方和敌方单位
40
41
game.on("update", onUpdateGame)
将 onUpdateGame 绑定到游戏的 "update" 事件
第 2 关

大家伙 (Big Guy)

战术思路分析:遇强则强

英雄是一个巨人,当他受伤越重时(血量低于 1/3),他就会越愤怒,变得更强大!

Update 事件的另一个用法

`player.on("update", onUpdatePlayer)`:这个事件处理器用于持续检查英雄的**内在状态**(生命值)。

关键:状态增强

判断条件:`if unit.health < unit.maxHealth / 3:`
如果满足,英雄的 `maxHealth`,`health`,`scale`,`attackDamage` 都会增加(*遇强则强*)。

IF HP < 1/3:
  Scale *= 1.2

Python 程序解法

1-11
# ... 游戏设置、计时器和目标 ...
设置初始分数和目标
12
def onDefeat(event):
食人魔被击败时,增加计数
13
game.ogresDefeated += 1
14
15
game.setActionFor("ogre", "defeat", onDefeat)
16
17
def onUpdatePlayer(event):
玩家的每帧更新逻辑
18
unit = event.target
19
# 如果玩家的生命值不到三分之一。
20
if unit.health < unit.maxHealth / 3:
判断:血量低于 1/3
21
unit.maxHealth *= 1.2
最大血量增加 20%
22
unit.health = unit.maxHealth
当前血量补满
23
unit.scale *= 1.2
体型增大 20%
24
unit.attackDamage *= 1.2
伤害增加 20%
25
unit.say("粉碎!")
26
27
player.on("update", onUpdatePlayer)
绑定事件
28-41
# ... checkTimers, checkGoals 等函数 ...
负责按时间生成单位和检查目标状态
42
def onUpdateGame(event):
主游戏循环处理器
43
checkTimers()
检查生成计时器
44
checkGoals()
检查目标状态
45
46
game.on("update", onUpdateGame)
绑定主循环事件
第 3 关

量子跃迁 (Quantum Leap)

战术思路分析:瞬移控制

我们不使用 `moveXY`,而是直接修改单位的 `pos.x` 和 `pos.y` 属性来实现**即时定位**,甚至可以瞬移围栏!

修改位置属性

- **连续移动(每帧):** `unit.pos.y += 5`
- **瞬间传送:** `unit.pos.y = 0` (当单位超出屏幕时)
- **碰撞跳跃:** `unit.pos.x += 6` (英雄碰到围栏时横向瞬移)

unit.pos.y = 0
unit.pos.x += 6

Python 程序解法

1-21
# ... 游戏设置和单位生成 ...
生成玩家、敌人和特殊的围栏
22
def onUpdateMunchkin(event):
矮人的每帧逻辑
23
unit = event.target
24
# 矮人总是往上移动。
25
unit.pos.y += 5
向上移动 5 (连续)
26
# 如果单位已经移动超出了地图的顶部。
27
if unit.pos.y > 68:
如果移出顶部
28
# 把它移到地图的底部。
29
unit.pos.y = 0
瞬移到底部
30
def onUpdateScout(event):
侦察兵的每帧逻辑
31-35
# ... 侦察兵向下移动并循环 ...
向下移动,移出底部后瞬移到顶部 (pos.y = 68)
36-40
# ... 绑定 spawn 和 update 事件 ...
41
def onCollide(event):
玩家碰撞事件
42-47
# ... 玩家被敌人击败的逻辑 ...
48
if other.type == "fence":
如果撞到围栏
49
# 使用unit.pos.x来改变玩家的x坐标。
50
unit.pos.x += 6
玩家向右瞬移 6 (跳过围栏)
51
52-54
# ... 绑定碰撞事件和目标 ...
第 4 关

循环森林 (The Looping Forest)

战术思路分析:地图折回

食人魔逃跑时可能会跑出地图。我们需要实现一个 **地图折回(Wraparound)** 机制:单位跑到一边,就从对面的另一边出现。

四向传送逻辑

对 X 和 Y 坐标都需要进行检查和重置。
- `if unit.pos.x < 10`: `unit.pos.x = 68` (左边 → 传送到右边)
- `if unit.pos.y > 58`: `unit.pos.y = 12` (顶边 → 传送到底边)

IF X < 10: X = 68
IF Y > 58: Y = 12

Python 程序解法

1-14
# ... 游戏设置、生成器、目标和 UI ...
15
def onUpdate(event):
单位更新处理器
16
unit = event.target
17
# 如果单位通过左边太远。
18
if unit.pos.x < 10:
如果移出左边界
19
# 那就把它传送到右边。
20
unit.pos.x = 68
传送到右侧
21
# 如果unit.pos.x大于70:
22
if unit.pos.x > 70:
如果移出右边界
23
# 将unit.pos.x设为12:
24
unit.pos.x = 12
传送到左侧
25
# 如果单位的Y坐标小于10:
26
if unit.pos.y < 10:
如果移出下边界
27
# 将unit.pos.y设为56:
28
unit.pos.y = 56
传送到顶部
29
# 如果单位的Y坐标大于58:
30
if unit.pos.y > 58:
如果移出上边界
31
# 将单位的Y坐标设为12:
32
unit.pos.y = 12
传送到底部
33
34-37
# ... 绑定 update 和 defeat 事件 ...
第 5 关

平稳移动 (Smooth Moving)

战术思路分析:米每帧

要让物体平滑移动,需要在每帧(`update`)中只移动一个非常小的距离。

关键公式:位置 + (速度 * 方向)

`unit.pos.y += fenceSpeed * unit.dir`
其中 `fenceSpeed` 是一个小数(米/帧),`unit.dir` 是 `1` 或 `-1`(方向)。当物体碰到边界时,将 `unit.dir *= -1` 来实现方向反转。

pos.y += speed * dir
IF Boundary: dir *= -1

Python 程序解法

1-21
# ... 游戏设置和围栏/喷火器生成 ...
设置了 fenceSpeed = 0.4 和 spewerSpeed = 0.5
22
def onUpdateSpewer(event):
喷火器横向移动逻辑
23
spewer = event.target
24
dist = spewerSpeed * spewer.dir
计算移动距离(速度 * 方向)
25
spewer.pos.x += dist
更新 X 坐标
26
if spewer.pos.x > 70 or spewer.pos.x < 10:
如果超出边界
27
spewer.dir *= -1
反转方向
28
29
game.setActionFor("fire-spewer", "update", onUpdateSpewer)
30
31
def onUpdateFence(event):
围栏纵向移动逻辑
32
fence = event.target
33
dist = fenceSpeed * fence.dir
计算移动距离(速度 * 方向)
34
fence.pos.y += dist
更新 Y 坐标
35
if fence.pos.y > 56 or fence.pos.y < 10:
如果超出边界
36
fence.dir *= -1
反转方向
37
38
game.setActionFor("fence", "update", onUpdateFence)
39-44
# ... 玩家碰撞和目标设置 ...
第 6 关

发狂的宝石 (Crazy Gems)

战术思路分析:台球运动

宝石会像台球一样在地图上反弹。我们需要分别管理 X 轴和 Y 轴的运动。

X/Y 独立控制

1. **计算位移:** `diffX = item.dirX * gemSpeed`
2. **更新位置:** `item.pos.x += diffX`
3. **边界反弹:** 当 X 坐标超出边界时,`item.dirX *= -1`
对 Y 轴进行完全相同的处理。

X-Axis AND Y-Axis
Independent Bounce

Python 程序解法

1-13
# ... 宝石生成函数 spawnRandomGem ...
随机生成宝石,并初始化 gem.dirX 和 gem.dirY (-1, 0, 或 1)
14
gemSpeed = 0.6
15
def onUpdate(event):
宝石的更新逻辑
16
item = event.target
17
diffX = item.dirX * gemSpeed
计算 X 轴位移
18
diffY = item.dirY * gemSpeed
计算 Y 轴位移
19
item.pos.x += diffX
更新 X 坐标
20
item.pos.y += diffY
更新 Y 坐标
21
# X 轴边界检查
22
if item.pos.x > 70 or item.pos.x < 10:
如果超出 X 边界
23
item.dirX *= -1
反转 X 方向
24
# Y 轴边界检查
25
if item.pos.y > 58 or item.pos.y < 10:
如果超出 Y 边界
26
item.dirY *= -1
反转 Y 方向
27
28
game.setActionFor("gem", "update", onUpdate)
29-32
# ... 玩家和目标设置 ...
第 7 关

跑酷第1步:环境 (Runner Environment)

战术思路分析:制作无尽地图

要制作无尽跑酷游戏,玩家不动,但**整个世界都在向后移动**。

关键:背景折回

1. **背景移动:** 每帧 `thing.pos.x -= 0.8`。
2. **背景重生:** 当森林图块移出左边界 (`pos.x < -4`) 时,将其传送到右边界 (`pos.x = 84`),形成无缝循环。
3. **障碍物销毁:** 围栏移出边界后直接 `thing.destroy()`,等待重生。

IF X < -4: X = 84

Python 程序解法

1-26
# ... 边界森林生成 ...
生成顶部和底部边界(部分在屏幕外)
27
def onUpdateStatic(event):
环境对象的更新逻辑
28
thing = event.target
29
thing.pos.x -= 0.8
每帧向左移动 0.8
30
if thing.pos.x < -4:
如果移出左边界
31
if thing.type == "forest":
如果是森林(背景)
32
thing.pos.x = 84
传送到右边界
33
else:
否则(是围栏等障碍)
34
thing.destroy()
销毁障碍物
35
36-54
# ... 障碍物生成和游戏循环设置 ...
设置玩家位置锁定、障碍物生成和碰撞逻辑
55
def checkPlayer():
56
# 玩家应该沿着X轴静止,因此我们将其X位置设为一个常数。
57
player.pos.x = 20
锁定玩家的 X 轴位置
58-64
# ... checkTimers 和 onUpdateGame ...
主循环调用 checkPlayer 和 checkTimers
第 8 关

跑酷步骤2:得分 (Runner Scoring)

战术思路分析:动态计分

分数系统由两部分组成:**生存时间** 和 **收集宝石**。

关键:`event.deltaTime`

在 `onUpdateGame(event)` 中,使用 `game.score += event.deltaTime` 可以确保分数根据游戏流逝的**实际时间**准确增加,而不是每帧固定增加。

Score += event.deltaTime
Gem Collect: Score += 5

Python 程序解法

1-41
# ... 环境和生成函数 ...
42
game.score = 0
初始化分数
43-50
# ... UI Track 和目标设置 ...
跟踪最高分、设置手动生存目标
51
def onCollect(event):
收集事件处理器
52
item = event.other
53
if item.type == "gem":
如果是宝石
54
game.score += 5
分数 +5
55-73
# ... 碰撞、玩家失败、检查和生成计时器等函数 ...
74
def onUpdateGame(event):
主游戏循环处理器
75
checkPlayer()
76
checkSpawns()
77
# 得分随着游戏时间的增加而增加。event.deltaTime是自上次"update"更新发生后所经历的时间量。
78
game.score += event.deltaTime
分数随时间准确增加
79
80
game.on("update", onUpdateGame)
绑定事件
第 9 关

跑酷步骤3:敌人 (Runner Enemies)

战术思路分析:追逐与引诱

玩家不能停下,但敌人会追上来。玩家必须将敌人**引诱**到移动的围栏上,才能消灭它们。

关键:锁定位置 (`unit.baseX`)

敌人追击玩家,但不能跑得太靠前。我们使用自定义属性 `unit.baseX` 来跟踪敌人应有的 X 轴位置,然后每帧让它向玩家靠近一点:
`unit.baseX += 0.1`,并确保 `unit.pos.x = unit.baseX`。

Ogre.X += 0.1
Goal X: Player.BaseX - 2

Python 程序解法

1-45
# ... 环境和生成函数 (与步骤2相同) ...
46
game.spawnOgreTime = 1
新增食人魔生成计时器
47-66
# ... 游戏设置和玩家/碰撞函数 (与步骤2相同) ...
67
def onDefeatOgre(event):
食人魔被击败的逻辑
68
unit = event.target
69
game.score += 10
得分 +10
70
# 被击败的食人魔应该向左移动,就像其他环境对象一样。
71
unit.on("update", onUpdateStatic)
让它加入背景移动序列(尸体移动)
72
def onUpdateOgre(event):
食人魔追逐逻辑
73
unit = event.target
74
if unit.health > 0:
只对活着的食人魔执行
75
unit.baseX += 0.1
每帧向右侧(玩家方向)移动 0.1
76
# 限制食人魔不要超过玩家
77
if unit.baseX > 18:
如果跑得太靠前
78
unit.baseX = 18
锁定最大 X 坐标 (在玩家身后)
79
unit.pos.x = unit.baseX
更新实际 X 坐标
80-92
# ... spawnOgre 函数和主循环 checkSpawns ...
设置食人魔初始 X=0,并绑定 update 事件
第 10 关

跑酷步骤4:平衡 (Runner Balance)

战术思路分析:参数调优

游戏功能已完成,现在进入最后的**平衡阶段**。所有影响难度的数值都被抽象为顶部的**常量**(大写字母)。

通过常量控制难度

例如:`GAME_SPEED = 0.6`,`OGRE_SPEED_INC = 0.1`。调整这些常量可以改变玩家和世界的相对速度、得分奖励和生成频率,从而找到游戏的最佳挑战点。

CONSTANTS at Top

Clean Global Control

Python 程序解法

1
# 跑酷游戏教程的步骤4。
2
# 游戏参数
3
GAME_SPEED = 0.6 # ∆ 静态对象运动的速度有多快。
游戏世界移动速度
4
PLAYER_SPEED = 20 # ∆ 玩家速度。
5
OGRE_SPEED_INC = 0.1 # ∆ 食人魔接近玩家的速度有多快。
食人魔的追击速度
6
PLAYER_BASE_X = 20 # ∆ 将玩家放置到离左边多远。
锁定玩家 X 轴位置
7
GEM_SCORE = 5 # ∆ 收集一枚宝石得多少分。
8
DEFEATED_OGRE_SCORE = 10 # ∆ 击败一个食人魔得多少分。
9
WIN_TIME = 20 # ∆ 最小存活时间。
10
FENCE_INTERVAL = 1 # ∆ 生成围栏之间的时间间隔。
11
GEM_INTERVAL = 1 # ∆ 生成宝石之间的时间间隔。
12
OGRE_INTERVAL = 2 # ∆ 生成食人魔之间的时间间隔。
13-107
# ... 所有函数和环境生成,均已替换为常量 ...
所有数值变量已替换为顶部的常量
108
def checkPlayer():
109
if game.time == WIN_TIME:
使用 WIN_TIME
110
player.say("获得胜利!奖励时间!")
111
player.pos.x = PLAYER_BASE_X
使用 PLAYER_BASE_X
112-127
# ... 其他函数和主循环 ...
所有计时器和分数逻辑均使用顶部常量
第 11 关

游戏开发3最终的项目 (Final Project)

项目分析:整合与创意

这是你自己的舞台!学生应运用所有学过的概念(函数、事件、`and`/`or`/`not`、`break`/`continue`、`pos.x`修改、状态检查)来创建一个独特的游戏体验。

  • **挑战:** 创造一个独特的目标或机制(例如:一个只有在夜晚才出现的 Boss,一个只有用特定顺序才能收集的物品)。
  • **目标:** 代码的清晰度和创造力是评估的重点。

GO BUILD YOUR GAME!

Python 程序解法

1
# 这一关是自由创作。学生应该在这里写下自己的独特游戏代码。
2
# 鼓励学生使用所有的编程技巧。
3
# 示例代码 (通常留白供学生填写):
4
# ... 创造性的代码示例 ...