强化学习实战7——用决策树打赢星际争霸II
本节内容学习如何用sc2库和决策树实现打星际争霸。
依赖库、游戏下载与安装
deepmind之前为此写了sc2的环境。由于版本问题,我们使用BurnySc2提供的环境
GitHub - BurnySc2/python-sc2: A StarCraft II bot api client library for Python 3 · GitHub
pip install --upgrade burnysc2
请先前往暴雪官网下载游戏,并注册通行证和账号。

下好之后必须体验一下人族 异虫 星灵族的教程,以了解之后策略的编写。
地图下载
感谢@气泡橙汁Zz 的提供:
地图的下载地址是:https://aiarena.net/wiki/maps/#wiki-toc-sc2-ai-arena-season-23的Sc2 AI Arena Season 2/3标签下的download,但是这个网站访问比较困难,
我把自己下载好的地图的网盘链接放在这里:链接:https://pan.baidu.com/s/1G3U-lPI2yi6agz2_jSOKoA?pwd=6fep 提取码:6fep
在下载StarCraft2的文件夹下创建或打开Maps文件夹,将刚刚的地图文件都考进去

运行示例
在刚刚的仓库中可以看到例子代码,

我们先运行一下这个例子
!pip install --upgrade burnysc2
!pip install tensorflow==2.12.0 d2l==1.0.3 numpy==1.23.5 matplotlib pandas
from sc2 import maps
from sc2.player import Bot, Computer
from sc2.main import run_game
from sc2.data import Race, Difficulty
from sc2.bot_ai import BotAI
import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
import nest_asyncio
nest_asyncio.apply()
class WorkerRushBot(BotAI):
async def on_step(self, iteration: int):
if iteration == 0:
for worker in self.workers:
worker.attack(self.enemy_start_locations[0])
run_game(maps.get("2000AtmospheresAIE"), [
Bot(Race.Protoss, WorkerRushBot()),
Computer(Race.Zerg, Difficulty.Medium)
], realtime=True)
run_game有三个参数,地图,和两个玩家,我们设置Agent代理星灵Protoss,电脑代理虫族Zerg,难度选择Medium,不加速。
目前Agent的操作只有一个,就是生成woker去攻击虫族,肯定会输。
关于己方 Bot 的信息
可以从这里看到sc2支持的信息
https://burnysc2.github.io/python-sc2/text_files/introduction.html

资源与人口
self.minerals: int # 晶体矿数量
self.vespene: int # 高能瓦斯数量
self.supply_army: int # 军队人口(游戏开局为 0)
self.supply_workers: int # 农民人口(开局为 12)
self.supply_cap: int # 人口上限(虫族开局 14,人/神开局 15)
self.supply_used: int # 已用人口(开局 12)
self.supply_left: int # 剩余人口(虫族开局 2,人/神开局 3)
单位相关
self.warp_gate_count: Units # 传送门数量(仅限神族)
self.idle_worker_count: int # 空闲农民数量
self.army_count: int # 军队单位总数
self.workers: Units # 所有农民单位
self.larva: Units # 所有幼虫(仅限虫族)
self.townhalls: Units # 主基地(星核/孵化场/指挥中心等)
self.gas_buildings: Units # 气矿建筑(精炼厂/萃取房/吸收体)
self.units: Units # 所有己方单位(含幼虫、农民)
self.structures: Units # 所有己方建筑(含主基地、气矿)
其他 Bot 信息
self.race: Race # 己方种族(选随机也会显示实际分配的种族)
self.player_id: int # 玩家 ID(双人局中为 1 或 2)
self.start_location: Point2 # 出生位置(第一个主基地位置)
self.main_base_ramp: Ramp # 主基地斜坡信息(人族堵口专用)
关于敌方玩家的信息
self.enemy_units: Units # 视野内的敌方单位(含隐形,不含钻地)
self.enemy_structures: Units # 视野内的敌方建筑
self.enemy_start_locations: list[Point2] # 敌方出生位置列表
self.blips: set[Blip] # 感应塔探测到的敌方单位信号
self.enemy_race: Race # 敌方种族(敌方选随机则永远显示随机)
其他地图 / 中立信息
self.mineral_field: Units # 地图上所有矿点
self.vespene_geyser: Units # 地图上所有气矿(包括已被占领的)
self.resources: Units # 所有资源点(矿点 + 气矿)
self.destructables: Units # 所有可摧毁岩石(不含主矿斜坡下的平台)
self.watchtowers: Units # 地图上所有瞭望塔
self.all_units: Units # 全场所有单位(己方 + 敌方 + 中立)
self.expansion_locations: dict[Point2, Units] # 所有可扩张点位
self.game_data: GameData # 单位、技能、升级数据
self.game_info: GameInfo # 地图信息:寻路、建造位置、地形高度、视野、菌毯
self.state: GameState # 每帧更新的游戏状态
额外信息
self.realtime: bool # 是否为实时模式(是则 Bot 每帧计算时间受限)
self.time: float # 当前游戏时间(秒)
self.time_formatted: str # 格式化时间:分:秒
Bot可以采取的行动
https://burnysc2.github.io/python-sc2/bot_ai/index.html
请务必读完这个网页,内容很多,但是不看完很难理解后续代码。
BotAI 核心函数总结
一、资源与成本检查
| 函数 | 作用 | 返回值 |
|---|---|---|
can_afford(unit_type/ability_id/upgrade_id) |
检查是否有足够资源(矿 / 气)+ 足够人口生产 / 研究 / 建造 | bool |
calculate_cost(item_id) |
计算单位 / 建筑 / 技能 / 升级的总消耗(矿 + 气) | Cost 对象 |
calculate_supply_cost(unit_type) |
计算单位占用的人口 | int |
二、建造 / 训练 / 升级(核心操作层)
| 函数 | 作用 | 关键参数 |
|---|---|---|
build(building, near, ...) |
自动找位 + 选农民 + 下建造命令 | building: 建筑类型near: 参考位置 / 单位max_distance: 最大搜索距离placement_step: 搜索步长 |
expand_now(building=None, ...) |
快速扩张(自动找分矿点位 + 建造主基地) | building: 主基地类型(默认自动)location: 指定扩张位置 |
train(unit_type, amount=1) |
自动训练单位(如农民 / 军队),自动扣资源与人口 | 单位类型、训练数量 |
research(upgrade_id, structure) |
研究升级(需指定研究建筑) | 升级 ID、研究建筑 |
三、单位 / 建筑状态检查
| 函数 | 作用 | 返回值 |
|---|---|---|
already_pending(unit_type) |
统计正在建造 / 训练 / 研究 + 正在路上 的数量 | float |
already_pending_upgrade(upgrade_id) |
检查升级研究进度 | float (0 = 未开始,0~1 = 研究中,1 = 完成) |
alert(alert_code) |
检测当前帧是否触发指定警报(如建筑完成、攻击等) | bool |
四、经济与工人管理
| 函数 | 作用 | 说明 |
|---|---|---|
distribute_workers(resource_ratio=2) |
自动分配工人采矿 / 采气 | 矿气比大于此值优先补气,反之优先补矿 |
assign_workers_to_gas(...) |
手动分配工人到气矿(需自行实现) | 库内无内置函数,需写逻辑 |
五、地图与位置相关
| 函数 | 作用 |
|---|---|
find_placement(unit_type, near, placement_step=2) |
在指定位置附近寻找合法建造位置(避障 / 地形 / 菌毯) |
find_expansion_locations(...) |
返回地图所有可扩张点位(结合资源与地形) |
六、聊天与信息
| 函数 | 作用 |
|---|---|
chat_send(message, team_only=False) |
发送聊天信息(用于调试 / 嘲讽) |
七、其他常用工具函数
| 函数 | 作用 |
|---|---|
calculate_unit_value(unit_type) |
计算单位价值(用于经济评估) |
can_cast(ability_id, unit) |
检查单位是否可释放某技能(冷却 / 能量 / 状态) |
can_attack(unit, target) |
检查单位是否可攻击目标(距离 / 类型 / 状态) |
机器人可执行的操作
游戏已经开始,现在你需要用采集到的资源建造单位或建筑。我假设你至少玩过一局《星际争霸 2》,了解基础操作,例如:虫族如何生产工蜂(Drone)(通过幼虫),人族如何生产SCV(通过指挥中心),神族如何生产探机(Probe)(通过星灵枢纽)。
我们接下来完成两个基本操作:如何建造?如何训练单位?

建造建筑
我们首先要建造大本营,那么建大本营需要用什么函数?
文档要确定
“要建造什么类型的建筑”
“是否负负担得起资源”
“有没有空闲工人来建造”
“建在哪里”
建筑类型可以写成 UnitTypeId.SPAWNINGPOOL(血池)。检查是否有足够资源可以用:if self.can_afford(UnitTypeId.SPAWNINGPOOL):
选择用来建造的工人会稍微复杂一些。你可以随机选一个农民:my_worker = self.workers.random
也可以选择距离目标建造位置最近的工人:my_worker = self.workers.closest_to(placement_position)
但这两种方式都有一个问题:选中的工人可能正在忙。通常我们会选择正在采矿或空闲的工人:my_worker = self.workers.filter(lambda worker: worker.is_collecting or worker.is_idle).random
最后是确定血池的建造位置。最简单的写法是:
map_center = self.game_info.map_center
placement_position = self.start_location.towards(map_center, distance=5)
但问题来了:你真的能把建筑放在那个位置吗?有没有菌毯?会不会被其他建筑或敌方单位挡住?
建筑布局其实非常难:你不想把建筑造在矿区里;你想留出足够空间让人族建筑能挂上附属挂件;你想在建筑之间留出 2x2 空间,防止执政官被卡住。
有一个函数可以自动检测合法位置:self.find_placement,它会在指定点附近寻找可建造区域。这个函数可能运行较慢:
map_center = self.game_info.map_center
position_towards_map_center = self.start_location.towards(map_center, distance=5)
placement_position = await self.find_placement(UnitTypeId.SPAWNINGPOOL, near=position_towards_map_center, placement_step=1)
# 如果找不到位置会返回 None
if placement_position:
还有一个关键点:你绝对不想造多个血池。为了避免重复建造,你可以检查正在建造 + 已完成的血池数量是否为 0:
if self.already_pending(UnitTypeId.SPAWNINGPOOL) + self.structures.filter(lambda structure: structure.type_id == UnitTypeId.SPAWNINGPOOL and structure.is_ready).amount == 0:
# 建造血池
完整推荐写法(朝向地图中心建造血池)
if self.can_afford(UnitTypeId.SPAWNINGPOOL) and self.already_pending(UnitTypeId.SPAWNINGPOOL) + self.structures.filter(lambda structure: structure.type_id == UnitTypeId.SPAWNINGPOOL and structure.is_ready).amount == 0:
# 筛选:采矿/空闲 + 未分配任务的农民
worker_candidates = self.workers.filter(lambda worker: (worker.is_collecting or worker.is_idle) and worker.tag not in self.unit_tags_received_action)
# 农民列表可能为空
if worker_candidates:
map_center = self.game_info.map_center
position_towards_map_center = self.start_location.towards(map_center, distance=5)
# 寻找可建造位置
placement_position = await self.find_placement(UnitTypeId.SPAWNINGPOOL, near=position_towards_map_center, placement_step=1)
if placement_position:
# 选最近的农民建造
build_worker = worker_candidates.closest_to(placement_position)
build_worker.build(UnitTypeId.SPAWNINGPOOL, placement_position)
简化写法(使用自带函数 self.build)
你可以用更方便的 self.build 实现完全一样的效果,它会自动选农民、自动找位置:
if self.can_afford(UnitTypeId.SPAWNINGPOOL) and self.already_pending(UnitTypeId.SPAWNINGPOOL) + self.structures.filter(lambda structure: structure.type_id == UnitTypeId.SPAWNINGPOOL and structure.is_ready).amount == 0:
map_center = self.game_info.map_center
position_towards_map_center = self.start_location.towards(map_center, distance=5)
await self.build(UnitTypeId.SPAWNINGPOOL, near=position_towards_map_center, placement_step=1)
生产单位
假设你为机器人选择了虫族,并想要生产一只工蜂。可用的幼虫存储在 self.larva 中,你的机器人开局会拥有3 只幼虫。若想指定其中一只幼虫执行生产工蜂的指令,需要先选中一只幼虫。最简单的方式是:my_larva = self.larva.random
选中幼虫后,必须下达变异为工蜂的指令。
你可以通过 __call__ 函数下达指令:unit(ability)。使用前必须导入技能 ID:from sc2.ids.ability_id import AbilityId
此时,执行动作的代码为:my_larva(AbilityId.LARVATRAIN_DRONE)
综上,完整代码如下:
from sc2.ids.ability_id import AbilityId
my_larva = self.larva.random
my_larva(AbilityId.LARVATRAIN_DRONE)
重要注意事项:
指令会在 on_step 函数执行完毕、机器人通过 API 与星际 2 客户端通信后才会生效。这可能导致不符合预期的状态:
- 幼虫数量仍显示为 3(
self.larva.amount == 3) - 晶体矿仍显示为 50(
self.minerals == 50) - 人口并未增加(
self.supply_used == 12)
但预期行为应该是:幼虫数量减为 2,晶体矿变为 0,人口变为 13(因为正在生产的工蜂会占用人口)。
后两个问题可以通过另一种调用方式修复:
self.larva.random(AbilityId.LARVATRAIN_DRONE, subtract_cost=True, subtract_supply=True)
这些关键字参数是可选的。因为大部分指令是移动或攻击指令,而非训练或建造指令,所以只在特定指令中检查资源消耗,能让机器人运行效率更高。
实现相同功能的另外两种写法:
写法 2(推荐,更直观)
from sc2.ids.unit_typeid import UnitTypeId
self.larva.random.train(UnitTypeId.DRONE)
该方法会自动将单位类型 ID转换为生产该单位所需的技能 ID。
写法 3(最简便,自动分配)
self.train(UnitTypeId.DRONE, amount=1)
该函数会自动寻找合适的生产单位(幼虫),并在下达训练指令后自动扣除资源与人口。
如果性能对你很重要,建议直接向空闲且资源充足的建筑 / 单位下达训练指令。
高效生产:尽可能多造工蜂
for loop_larva in self.larva:
# 检查是否有足够资源与人口
if self.can_afford(UnitTypeId.DRONE):
loop_larva.train(UnitTypeId.DRONE)
# 如果只想造一只,在这里加 break
else:
# 资源不足,停止生产
break
self.can_afford:检查是否拥有足够的资源和空闲人口来生产单位。self.do:会自动增加人口计数并扣除资源消耗。
最终正确写法
警告:必须避免在同一帧向同一只幼虫下达多次指令。
self.do 函数会自动将单位的标识加入 self.unit_tags_received_action(一个整数集合,每帧都会清空),用于标记已下达指令的单位,防止重复操作。
最终规范代码:
for loop_larva in self.larva:
# 如果该幼虫已收到指令,跳过
if loop_larva.tag in self.unit_tags_received_action:
continue
# 检查资源
if self.can_afford(UnitTypeId.DRONE):
loop_larva.train(UnitTypeId.DRONE)
else:
break
代码编写
我们用的是星灵,因此要修改代码,
首先看看是否存在主基地
import random
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId
class WorkerRushBot(BotAI):
async def on_step(self, iteration: int):
# 第一步:检查是否有主基地(开局就有,if 基本恒成立)
if self.townhalls:
# 随机选一个主基地(开局只有1个,就是出生的 Nexus)
nexus = self.townhalls.random
# 打印游戏状态(调试用,每帧输出资源、人口、单位数量)
print(f"{iteration},minerals:{self.minerals},vespene:{self.vespene},supply:{self.supply_used}/{self.supply_cap},workers:{self.workers.amount},idle_workers:{self.idle_worker_count},nexus:{self.structures(UnitTypeId.NEXUS).amount},probes:{self.units(UnitTypeId.PROBE).amount},pylon:{self.structures(UnitTypeId.PYLON).amount}")
# 🟣 极端情况:没有主基地了(比如被拆光)
else:
# 资源够就自动开分矿(重建主基地)
if self.can_afford(UnitTypeId.NEXUS):
self.expand_now()
其中,如果没有主基地,可以使用expand_now()重建:

- 获取你的种族:通过
self.race拿到当前 Bot 的种族(神族 / 人族 / 虫族) - 自动匹配对应主基地:
种族 自动匹配的主基地建筑 神族(Protoss) UnitTypeId.NEXUS(星核 / 枢纽)人族(Terran) UnitTypeId.COMMANDCENTER(指挥中心)虫族(Zerg) UnitTypeId.HATCHERY(孵化场) - 自动执行扩张流程:
- 调用
self.get_next_expansion()找到地图上最近的、未被占领的分矿点位 - 自动筛选空闲 / 采矿的农民
- 自动调用
self.find_placement()找合法建造位置 - 给农民下达建造主基地的命令
- 自动扣除资源、处理人口
- 调用
破局思路
有了主基地,就可以开始造各种单位了
游戏中星灵的战斗单位需要通过折跃建筑生产


而折跃建筑必须通过水晶塔供能,且增加单位容量也需要水晶塔

因此思路就是先向敌方出生点拓展水晶塔,再布置折跃单位,最后生产进攻单位。而这一切的前提就是有足够的矿、汽,需要这些资源就需要源源不断的Probe。需要Probe,就需要从主基地Nexus生产,生产Probe,就需要矿,需要矿,就需要布置初始的Probe采矿的任务:

资源生产与获取
Probe的生产与水晶塔的构建
思路明确开始写代码:
首先游戏初始会给一定量的Probe,我们全部投入采矿
# 自动分配农民采矿(内置函数,不用自己写分配逻辑,异步执行,所以加 await)
await self.distribute_workers()
然后,先从Nexus生产Probe,我们刚刚nexus=self.townhalls.random获取了主基地,可以判断是否负担得起can_afford(X),且主基地空闲is_idle然后生产Probe。
# 🔴 最高优先级:持续造探机(Probe)
if self.can_afford(UnitTypeId.PROBE) and nexus.is_idle:
nexus.train(UnitTypeId.PROBE) # 主基地直接训练,不用农民
然后开始建水晶塔,注意第一个水晶塔要在主基地附近建,在API中提供了build函数
其次参是修建的位置,我们填写near=nexus主基地附近。

然后要判断是否有足够的资源构建,也是can_afford()。
# 🟡 优先级2:造第一个水晶塔(Pylon)
elif not self.structures(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) == 0:
if self.can_afford(UnitTypeId.PYLON):
# 异步建造:自动选农民、找位置、下命令,必须加 await
await self.build(UnitTypeId.PYLON, near=nexus)
注意,要用await,因为self.build() 是异步函数,内部要做「选农民→找位置→发建造命令」一系列耗时操作,await 确保这些操作完成后,再进入下一轮 on_step,避免重复造塔、农民冲突。
然后我们预定向敌人方向建五座塔,间隔8-15格。逻辑是找离敌方出生点最近的一座塔,然后向前扩展8-15格,调用build(),同理await。
# 🟢 优先级3:向敌方方向持续铺水晶塔(最多5个)
elif self.structures(UnitTypeId.PYLON).amount < 5:
if self.can_afford(UnitTypeId.PYLON):
# 找离敌方出生点最近的已造水晶塔
target_pylon = self.structures(UnitTypeId.PYLON).closest_to(self.enemy_start_locations[0])
# 从这个水晶塔向敌方方向,走8-15格的随机距离,作为新水晶塔的位置
pos = target_pylon.position.towards(self.enemy_start_locations[0], random.randrange(8, 15))
# 异步建造新水晶塔,加 await
await self.build(UnitTypeId.PYLON, near=pos)
瓦斯的获取
瓦斯需要建造瓦斯吸收间获取。
在主基地15格内找气矿,然后判断是否负担得起且资源点上没有在建造的吸收间。
# 修复吸收间报错:去掉 already_pending,改用 pending_builds 判断
# elif self.structures(UnitTypeId.ASSIMILATOR) and not self.already_pending(UnitTypeId.ASSIMILATOR):
# for vespene in self.vespene_geyser.closer_than(15, nexus):
# if self.can_afford(UnitTypeId.ASSIMILATOR):
# await self.build(UnitTypeId.ASSIMILATOR, vespene)
elif self.structures(UnitTypeId.ASSIMILATOR).amount < 2:
for vespene in self.vespene_geyser.closer_than(15, nexus):
if self.can_afford(UnitTypeId.ASSIMILATOR):
worker = self.select_build_worker(vespene.position)
if worker is not None:
# 神族造气矿 官方唯一正确方法
worker.build_gas(vespene)
worker.stop(queue=True)
Protoss建吸收间【不可以】用老师的代码(注释段),会报错keyerror4444
KeyError: 4444
2026-04-11 19:10:40.101 | INFO | sc2.sc2process:_close_connection:243 - Closing connection at 62906...
2026-04-11 19:10:40.108 | INFO | sc2.sc2process:kill_all:36 - kill_switch: Process cleanup for 6 processes
------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[20], line 1
----> 1 run_game(maps.get("2000AtmospheresAIE"), [
2 Bot(Race.Protoss, WorkerRushBot()),
3 Computer(Race.Zerg, Difficulty.Hard)
4 ], realtime=False)
必须参考官方examples:
参考来源:
https://github.com/BurnySc2/python-sc2/blob/develop/examples/protoss/threebase_voidray.py
77行
完整代码:
import random
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId
class WorkerRushBot(BotAI):
async def on_step(self, iteration: int):
# 1. 检查是否有主基地(开局就有,if 基本恒成立)
if self.townhalls:
nexus = self.townhalls.random # 选一个主基地(开局只有1个)
# 📊 调试打印:每帧输出资源、人口、单位/建筑数量
print(f"{iteration},minerals:{self.minerals},vespene:{self.vespene},supply:{self.supply_used}/{self.supply_cap},workers:{self.workers.amount},idle_workers:{self.idle_worker_count},nexus:{self.structures(UnitTypeId.NEXUS).amount},probes:{self.units(UnitTypeId.PROBE).amount},pylon:{self.structures(UnitTypeId.PYLON).amount},forge:{self.structures(UnitTypeId.FORGE).amount},cannon:{self.structures(UnitTypeId.PHOTONCANNON).amount}")
# ⚙️ 自动分配农民采矿(内置函数,异步执行,必须加await)
await self.distribute_workers()
# 🔴 最高优先级:持续造探机(Probe)
if self.can_afford(UnitTypeId.PROBE) and nexus.is_idle:
nexus.train(UnitTypeId.PROBE) # 主基地直接训练,不用农民
# 🟡 优先级2:造第一个水晶塔(Pylon)
elif not self.structures(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) == 0:
if self.can_afford(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=nexus) # 造在主基地旁边
# 🟢 优先级3:向敌方方向铺水晶塔(最多5个)
elif self.structures(UnitTypeId.PYLON).amount < 5:
if self.can_afford(UnitTypeId.PYLON):
# 找离敌方最近的已造水晶塔
target_pylon = self.structures(UnitTypeId.PYLON).closest_to(self.enemy_start_locations[0])
# 向敌方方向走8-15格,作为新水晶塔位置
pos = target_pylon.position.towards(self.enemy_start_locations[0], random.randrange(8, 15))
await self.build(UnitTypeId.PYLON, near=pos) # 铺塔推进
elif self.structures(UnitTypeId.ASSIMILATOR).amount < 2:
for vespene in self.vespene_geyser.closer_than(15, nexus):
if self.can_afford(UnitTypeId.ASSIMILATOR):
worker = self.select_build_worker(vespene.position)
if worker is not None:
# 神族造气矿 官方唯一正确方法
worker.build_gas(vespene)
worker.stop(queue=True)
# 🟣 兜底逻辑:主基地被拆光,自动开分矿重建
else:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() # 补了await,规范异步写法
运行一下
run_game(maps.get("2000AtmospheresAIE"), [
Bot(Race.Protoss, WorkerRushBot()),
Computer(Race.Zerg, Difficulty.Hard)
], realtime=False)
可以看到源源不断的Probe生产和塔被建造


防守建筑构建
一般的核心逻辑是:极限爆Probe → 铺水晶塔推进 → 造熔炉 → 堆光子炮 → 冲脸,专门用来打前期快攻。
在这个网址可以看到建筑树
https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)

如果我们要构建光子炮,就需要修熔炉。
依然是判断是否有建造过/被摧毁,如果没有就看看能否造的起,然后选择合适的位置
熔炉我们选择靠大本营的第一座水晶塔建
near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🔵 优先级4:造熔炉(Forge)
elif not self.structures(UnitTypeId.FORGE):
if self.can_afford(UnitTypeId.FORGE):
# 造在离主基地最近的水晶塔旁边(保证供电)
await self.build(UnitTypeId.FORGE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
然后建造光子炮,要额外判断一下是否存在熔炉,然后也建在主基地附近
near=nexus
# 🟠 优先级5:造光子炮(Photon Cannon,最多3个)
elif self.structures(UnitTypeId.FORGE).ready and self.structures(UnitTypeId.PHOTONCANNON).amount < 3:
if self.can_afford(UnitTypeId.PHOTONCANNON):
await self.build(UnitTypeId.PHOTONCANNON, near=nexus) # 造在主基地附近防守/推进
完整代码:
import random
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId
class WorkerRushBot(BotAI):
async def on_step(self, iteration: int):
# 1. 检查是否有主基地(开局就有,if 基本恒成立)
if self.townhalls:
nexus = self.townhalls.random # 选一个主基地(开局只有1个)
# 📊 调试打印:每帧输出资源、人口、单位/建筑数量
print(f"{iteration},minerals:{self.minerals},vespene:{self.vespene},supply:{self.supply_used}/{self.supply_cap},workers:{self.workers.amount},idle_workers:{self.idle_worker_count},nexus:{self.structures(UnitTypeId.NEXUS).amount},probes:{self.units(UnitTypeId.PROBE).amount},pylon:{self.structures(UnitTypeId.PYLON).amount},forge:{self.structures(UnitTypeId.FORGE).amount},cannon:{self.structures(UnitTypeId.PHOTONCANNON).amount}")
# ⚙️ 自动分配农民采矿(内置函数,异步执行,必须加await)
await self.distribute_workers()
# 🔴 最高优先级:持续造探机(Probe)
if self.can_afford(UnitTypeId.PROBE) and nexus.is_idle:
nexus.train(UnitTypeId.PROBE) # 主基地直接训练,不用农民
# 🟡 优先级2:造第一个水晶塔(Pylon)
elif not self.structures(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) == 0:
if self.can_afford(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=nexus) # 造在主基地旁边
# 🟢 优先级3:向敌方方向铺水晶塔(最多5个)
elif self.structures(UnitTypeId.PYLON).amount < 5:
if self.can_afford(UnitTypeId.PYLON):
# 找离敌方最近的已造水晶塔
target_pylon = self.structures(UnitTypeId.PYLON).closest_to(self.enemy_start_locations[0])
# 向敌方方向走8-15格,作为新水晶塔位置
pos = target_pylon.position.towards(self.enemy_start_locations[0], random.randrange(8, 15))
await self.build(UnitTypeId.PYLON, near=pos) # 铺塔推进
elif self.structures(UnitTypeId.ASSIMILATOR).amount < 2:
for vespene in self.vespene_geyser.closer_than(15, nexus):
if self.can_afford(UnitTypeId.ASSIMILATOR):
worker = self.select_build_worker(vespene.position)
if worker is not None:
# 神族造气矿 官方唯一正确方法
worker.build_gas(vespene)
worker.stop(queue=True)
# 🔵 优先级4:造熔炉(Forge)
elif not self.structures(UnitTypeId.FORGE):
if self.can_afford(UnitTypeId.FORGE):
# 造在离主基地最近的水晶塔旁边(保证供电)
await self.build(UnitTypeId.FORGE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🟠 优先级5:造光子炮(Photon Cannon,最多3个)
elif self.structures(UnitTypeId.FORGE).ready and self.structures(UnitTypeId.PHOTONCANNON).amount < 3:
if self.can_afford(UnitTypeId.PHOTONCANNON):
await self.build(UnitTypeId.PHOTONCANNON, near=nexus) # 造在主基地附近防守/推进
# 🟣 兜底逻辑:主基地被拆光,自动开分矿重建
else:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() # 补了await,规范异步写法
可以看到成功建造了三个光子炮和熔炉。

一转攻势
接下来开始训练进攻单位。
我们要训练虚空战舰VOIDRAY,就需要建造星门Stargate,建造星门需要建造控制核心CyberneticsCore,要建造控制核心,就需要建造传送门Gateway。

建造传送门
照常判断是否有建造过,是否负担得起,然后选址建造。
传送门要建造在靠近主基地的首个水晶塔附近。
# ------建设进攻设施------
# Gateway(传送门/兵营)
elif not self.structures(UnitTypeId.GATEWAY):
if self.can_afford(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
建造控制核心
照常判断是否有建造过,是否负担得起,然后选址建造。
控制核心要建造在靠近主基地的首个水晶塔附近。
# Cybernetic Core(控制核心)
elif not self.structures(UnitTypeId.CYBERNETICSCORE):
if self.can_afford(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
建造星门
照常判断是否有建造过,是否负担得起,然后选址建造。
星门要建造在靠近主基地的首个水晶塔附近。
# Stargate(星门)
elif not self.structures(UnitTypeId.STARGATE):
if self.can_afford(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
建造虚空舰队
这里注意一点,虚空舰队的优先级要高于Probe,否则一旦有矿就会生产Probe而不是舰队了。
我们需要把代码放在
# 🔴 最高优先级:持续造探机(Probe)
if self.can_afford(UnitTypeId.PROBE) and nexus.is_idle:
nexus.train(UnitTypeId.PROBE) # 主基地直接训练,不用农民
之前
首先确定建造数量小于10,然后检查检查星门是否就绪,如果建好了且空闲就开始建造,同时要看看是否负担得起。
if self.units(UnitTypeId.VOIDRAY).amount < 10:
for sg in self.structures(UnitTypeId.STARGATE).ready.idle:
if self.can_afford(UnitTypeId.VOIDRAY):
sg.train(UnitTypeId.VOIDRAY)
进攻代码
进攻的优先级是消灭敌方单位-消灭敌方建筑-消灭敌方主基地
# ------发动进攻------
# 触发条件:虚空辉光舰数量 > 3(凑够3艘就开始进攻)
if self.units(UnitTypeId.VOIDRAY).amount > 3:
# 第一层:如果视野内有敌方单位(兵/农民)
if self.enemy_units:
# 遍历所有空闲的虚空舰
for vg in self.units(UnitTypeId.VOIDRAY).idle:
# 让虚空舰随机攻击一个敌方单位
vg.attack(random.choice(self.enemy_units))
# 第二层:如果没有敌方单位,但有敌方建筑
elif self.enemy_structures:
# 遍历所有空闲的虚空舰
for vg in self.units(UnitTypeId.VOIDRAY).idle:
# 让虚空舰随机攻击一个敌方建筑
vg.attack(random.choice(self.enemy_structures))
# 第三层:如果既没单位也没建筑(视野内没敌人)
else:
# 遍历所有空闲的虚空舰
for vg in self.units(UnitTypeId.VOIDRAY).idle:
# 让虚空舰直接冲向敌方出生点(主基地)
vg.attack(self.enemy_start_locations[0])
完全胜利

完整代码:
下载依赖库
!pip install --upgrade burnysc2
!pip install tensorflow==2.12.0 numpy==1.23.5 matplotlib pandas
导入依赖库
from sc2 import maps
from sc2.player import Bot, Computer
from sc2.main import run_game
from sc2.data import Race, Difficulty
from sc2.bot_ai import BotAI
import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
import nest_asyncio
nest_asyncio.apply()
创建Agent
import random
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId
class WorkerRushBot(BotAI):
async def on_step(self, iteration: int):
if self.townhalls:
nexus = self.townhalls.random
# 状态打印(按老师要求补充完整)
print(f"{iteration},minerals:{self.minerals},vespene:{self.vespene},supply:{self.supply_used}/{self.supply_cap},workers:{self.workers.amount},idle_workers:{self.idle_worker_count},nexus:{self.structures(UnitTypeId.NEXUS).amount},probes:{self.units(UnitTypeId.PROBE).amount},pylon:{self.structures(UnitTypeId.PYLON).amount},forge:{self.structures(UnitTypeId.FORGE).amount},cannon:{self.structures(UnitTypeId.PHOTONCANNON).amount},gateway:{self.structures(UnitTypeId.GATEWAY).amount},cybercore:{self.structures(UnitTypeId.CYBERNETICSCORE).amount},stargate:{self.structures(UnitTypeId.STARGATE).amount},voidray:{self.units(UnitTypeId.VOIDRAY).amount}")
await self.distribute_workers()
# ==============================================
# 🔴 严格按老师顺序:虚空辉光舰(放在探机之前,最高优先级)
# ==============================================
if self.units(UnitTypeId.VOIDRAY).amount < 10:
for sg in self.structures(UnitTypeId.STARGATE).ready.idle:
if self.can_afford(UnitTypeId.VOIDRAY):
sg.train(UnitTypeId.VOIDRAY)
# 🟡 探机(次高优先级)
if self.can_afford(UnitTypeId.PROBE) and nexus.is_idle:
nexus.train(UnitTypeId.PROBE)
# 🟢 水晶塔(第一个)
elif not self.structures(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) == 0:
if self.can_afford(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=nexus)
# 🟢 按一定方向构建水晶塔(最多5个)
elif self.structures(UnitTypeId.PYLON).amount < 5:
if self.can_afford(UnitTypeId.PYLON):
target_pylon = self.structures(UnitTypeId.PYLON).closest_to(self.enemy_start_locations[0])
pos = target_pylon.position.towards(self.enemy_start_locations[0], random.randrange(8, 15))
await self.build(UnitTypeId.PYLON, near=pos)
# # 修复吸收间报错:去掉 already_pending,改用 pending_builds 判断
# elif self.structures(UnitTypeId.ASSIMILATOR) and not self.already_pending(UnitTypeId.ASSIMILATOR):
# for vespene in self.vespene_geyser.closer_than(15, nexus):
# if self.can_afford(UnitTypeId.ASSIMILATOR):
# await self.build(UnitTypeId.ASSIMILATOR, vespene)
elif self.structures(UnitTypeId.ASSIMILATOR).amount < 2:
for vespene in self.vespene_geyser.closer_than(15, nexus):
if self.can_afford(UnitTypeId.ASSIMILATOR):
worker = self.select_build_worker(vespene.position)
if worker is not None:
# 神族造气矿 官方唯一正确方法
worker.build_gas(vespene)
worker.stop(queue=True)
# 🟠 熔炉
elif not self.structures(UnitTypeId.FORGE):
if self.can_afford(UnitTypeId.FORGE):
await self.build(UnitTypeId.FORGE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🟠 光子炮(最多3个)
elif self.structures(UnitTypeId.FORGE).ready and self.structures(UnitTypeId.PHOTONCANNON).amount < 3:
if self.can_afford(UnitTypeId.PHOTONCANNON):
await self.build(UnitTypeId.PHOTONCANNON, near=nexus)
# 🟡 Gateway(传送门)
elif not self.structures(UnitTypeId.GATEWAY):
if self.can_afford(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🟡 Cybernetic Core(控制核心)
elif not self.structures(UnitTypeId.CYBERNETICSCORE):
if self.can_afford(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🟡 Stargate(星门)
elif not self.structures(UnitTypeId.STARGATE):
if self.can_afford(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=self.structures(UnitTypeId.PYLON).closest_to(nexus))
# 🟣 兜底:主基地被拆,自动开矿
else:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
# ==============================================
# 🎯 严格按老师代码:发动进攻(放在所有建造逻辑最后)
# ==============================================
if self.units(UnitTypeId.VOIDRAY).amount > 3:
if self.enemy_units:
for vg in self.units(UnitTypeId.VOIDRAY).idle:
vg.attack(random.choice(self.enemy_units))
elif self.enemy_structures:
for vg in self.units(UnitTypeId.VOIDRAY).idle:
vg.attack(random.choice(self.enemy_structures))
else:
for vg in self.units(UnitTypeId.VOIDRAY).idle:
vg.attack(self.enemy_start_locations[0])
# 兜底:无主基地时的扩张
else:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
运行Agent
run_game(maps.get("2000AtmospheresAIE"), [
Bot(Race.Protoss, WorkerRushBot()),
Computer(Race.Zerg, Difficulty.Hard)
], realtime=False)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)