【python-sc2】详细解析!!!手把手教你学会实现星际争霸2游戏AI智能体的基础知识!!!

  • 参考资料

    星际争霸2 AI机器人网站

    AI天梯

    sc2ai_wiki文档

    • 该网站包含基于各种语言编写的sc2库,包括C++、Python、C#、JAVA等。其中,Python有Python-sc2sharpy-sc2PySC2三种框架。此外,针对每个框架提供了教程。

    python-sc2官方文档

    各种族单位名称/科技树

    【神族】单位名称/科技树

    【人族】单位名称/科技树

    【虫族】单位名称/科技树

    星际争霸2单位、建筑及其外号整理

    python-sc2

    环境配置

    1. pip install --upgrade burnysc2
      !!!注意不要在同一个环境中安装pysc2或sc2包,否则会出现冲突!!!
    2. 下载游戏地图Maps
    3. 安装环境中,找到sc2文件夹中paths.py,比如安装在conda环境中,其目录为“D:\anaconda3\envs\pytorch2.1\Lib\site-packages\sc2”。修改paths.py 中windows运行目录“C:/Program Files (x86)/StarCraft II”(默认路径)为自己的游戏路径“D:/Game/StarCraft II”。

    简单测试

    运行代码。(第一次运行之前可能需要自己从战网启动一次游戏?)

    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
    
    class WorkerRushBot(BotAI):
        async def on_step(self, iteration: int):
            print(f"amy:{self.supply_army}")
            print(f"workers:{self.supply_workers}")
            print(f"structures:{self.structures}")
    
    # Protoss ==> 神族
    # Zerg ==> 虫族
    # Terran ==> 人族
    run_game(
        maps.get("Altitude LE"),    # 加载地图
        [Bot(Race.Zerg, WorkerRushBot()), Computer(Race.Protoss, Difficulty.Easy)],
        realtime=True
        )
    

    出现下图所示的游戏窗口。

    img

    可直接获取的游戏数据

    数据种类

    自己的信息

    # Resources and supply
    self.minerals: int    # 晶体矿
    self.vespene: int    # 高能瓦斯
    self.supply_army: int # 战斗单位人口(补给)数量(可在右上角查看)
    self.supply_workers: int # 工人人口(补给)数量(可在右上角查看)
    self.supply_cap: int # 补给数量(最大人口数量)
    self.supply_used: int # 已使用人口(补给)数量
    self.supply_left: int # 未使用(剩余)人口(补给)数量
    
    # Units
    self.warp_gate_count: Units # 折跃门数量(神族专属)
    self.idle_worker_count: int # 空闲工人数量
    self.army_count: int # 战斗单位(部队)数量
    self.workers: Units # 工人人口(指在场上的工人。如果工人进入到瓦斯建筑内,则这个数值会减少1)
    self.larva: Units # 幼虫 (虫族专属)
    self.townhalls: Units # 你的基地 (星灵枢纽nexus, 孵化场hatchery, 虫穴lair, 主巢hive, 指挥中心command center, 轨道控制基地orbital command, 行星要塞planetary fortress)
    self.gas_buildings: Units # 你的瓦斯建筑
    self.units: Units # 你的单位(包括幼虫和工程兵,不包括建筑单位)
    self.structures: Units # 你的建筑(包括基地和瓦斯建筑)
    
    # Other information about your bot
    self.race: Race # 种族
    self.player_id: int # 你的机器人ID (can be 1 or 2 in a 2 player game)
    # 你的基地位置坐标(你第一座基地的位置坐标)
    self.start_location: Point2
    # 主基地坡道的位置,并有一些关于如何将主基地作为人族机器人墙的信息(请参阅GameInfo)
    self.main_base_ramp: Ramp
    

    对手的信息

    # 以下包含你单位视野范围内的敌方单位和结构(包括隐形单位,但不包括洞穴单位)
    # SC2中,某个敌方单位(非建筑单位)出现在自己单位视野范围内,则会在地图上看到它;如果该单位突然离开自己单位视野范围,则对于它的信息也消失,即在地图上看不到了(战争迷雾影响)。
    # 而对于建筑来说是不同的。当某个敌方建筑出现在自己单位视野内时,地图上就会一直显示它。就算该建筑离开了自己的视野范围,它也会一直出现在自己的地图上。
    self.enemy_units: Units    # 敌人单位(只有单位视野看到才会有数值,丢失视野后就没有数值了)
    self.enemy_structures: Units    # 敌人建筑(单位视野看到后就一直有数值,直到该建筑被摧毁)
    self.all_enemy_units: Units # 敌人单位+建筑
    
    # Enemy spawn locations as a list of Point2 points
    self.enemy_start_locations: List[Point2]    # 敌人基地位置
    
    # 在你的感应塔(人族专属)范围内的敌方单位
    self.blips: Set[Blip]
    
    # 敌人种族
    self.enemy_race: Race
    

    其他信息

    # Neutral units and structures
    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]
    
    # Game data about units, abilities and upgrades (see game_data.py)
    self.game_data: GameData
    
    # Information about the map: pathing grid, building placement, terrain height, vision and creep are found here (see game_info.py)
    self.game_info: GameInfo
    
    # Other information that gets updated every step (see game_state.py)
    self.state: GameState
    
    # Extra information
    self.realtime: bool # Displays if the game was started in realtime or not. In realtime, your bot only has limited time to execute on_step()
    self.time: float # 现在的游戏时间,单位为秒.(13.928571428571429)
    self.time_formatted: str # 现在的游戏实践,格式为“分:秒”。(00:14)
    

    读取方式

    1. 对于Units类型的数据,可以使用UnitTypeId来查询指定单位的数据。
    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
    from sc2.ids.unit_typeid import UnitTypeId
    
    class WorkerRushBot(BotAI):
        async def on_step(self, iteration: int):
            # 打印“分裂池”建筑的信息
            # self.structures(UnitTypeId.SPAWNINGPOOL)类型为Units,包含所有的分裂池信息
            print(f"structures:{self.structures(UnitTypeId.SPAWNINGPOOL)}")
    
    # Protoss ==> 神族
    # Zerg ==> 虫族
    # Terran ==> 人族
    run_game(
        maps.get("Altitude LE"),    # 加载地图
        [Bot(Race.Zerg, WorkerRushBot()), Computer(Race.Protoss, Difficulty.Easy)],
        realtime=True
        )
    

    最初的游戏场景中是没有“分裂池”建筑的,因此打印出来的structures为空。当建造出来“分裂池”后,structures有数据。

    img

    1. 对于int型数据,可以直接打印输出。
    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
    
    class WorkerRushBot(BotAI):
        async def on_step(self, iteration: int):
            # 打印工程兵数量    初始为12
            print(f"workers:{self.supply_workers}")
    
    # Protoss ==> 神族
    # Zerg ==> 虫族
    # Terran ==> 人族
    run_game(
        maps.get("Altitude LE"),    # 加载地图
        [Bot(Race.Protoss, WorkerRushBot()), Computer(Race.Protoss, Difficulty.Easy)],
        realtime=True
        )
    

    函数解析

    bot_ai.py

    already_pending(BotAI类)

    输入:unit_type: Union[UpgradeId, UnitTypeId]

    输出:float

    输入为UnitTypeId的情况

    返回一些已在进行中的建筑或单元,或者如果工人正在建造它。这还包括工人的排队订单和建筑的建造队列。

    例如:

    print(f"base_pending: {self.already_pending(UnitTypeId.COMMANDCENTER)\n")
    print(f"amount_of_scv_in_production: {self.already_pending(UnitTypeId.SCV)}")
    
    • 如果没有正在建造“指挥中心”,那么base_pending数值为0。
    • 如果正在建造1个“指挥中心”,那么base_pending数值为1。
    • 如果没有正在生产SVC,那么amount_of_scv_in_production数值为0。
    • 如果正在建造2个SVC,那么amount_of_scv_in_production数值为2。

    输入为Upgradeld的情况

    与already_pending_upgrade函数相同。

    already_pending_upgrade(BotAI类)

    输入:upgrade_type: UpgradeId

    输出:float

    检查某项技术研发升级的状态。

    • 如果该技术没有被研发,则数值为0。
    • 如果正在研发,则数值在0-1之间;随着研发进行,其数值越来越大,表示研发过程;
    • 如果已被研发,则数值为1.

    例如:

    # 检查“传送门”的研发状态。
    print(f"warpgate_research_status: {self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH)}")
    
    is_visible(BotAI类)

    输入:pos: Union[Point2, Unit]

    输出:bool

    输入为Point2

    如果你拥有该网格点的视野,那么函数返回True。

    输入为Unit

    如果你拥有该单位的视野,那么函数返回True。

    # 坐标点
    print(f"enemy_start_locations: {self.is_visible(self.enemy_start_locations[0])}")
    # 单位
    print(f"townhalls: {self.is_visible(self.watchtowers[0])}")
    

    unit.py

    attack(Unit类)

    输入:Union[Unit, Point2];queue: bool = False

    输出:Union[UnitCommand, bool]

    命令部队攻击。攻击目标可以是单位或者位置。

    输入为Point2

    如果是攻击一个位置,则会使部队移动到那里,并攻击途中的一切。

    输入为Unit

    如果是攻击一个单位,则会使部队攻击那个单位。但游戏单位貌似有自动攻击周围敌人的默认指令,因此在攻击完单位后还会进行自动攻击。

    move(Unit类)

    输入:position: Union[Unit, Point2];queue: bool = False

    输出:Union[UnitCommand, bool]

    命令单位移动。

    输入为Unit

    如果输入是单位,则会跟随该单位或前往该单位所在的位置。

    输入为Point2

    如果输入是目标点,则会移动到目标点。

    tag(Unit类)

    输出:int

    返回该单位特殊的标签值。

    这个标签值可以看作是游戏中每个单位(包括建筑)的特殊标签或标号,是独一无二的。

    is_moving(Unit类)

    输出:bool

    检查自己的某个单位是否正在移动。

    is_attacking(Unit类)

    输出:bool

    检查自己的某个单位是否正在攻击。

    is_idel(Unit类)

    输出:bool

    检查自己的某个单位是否正在空闲。

    health(Unit类)

    输出:float

    返回单位的当前生命值。不包括护盾。

    health_max(Unit类)

    输出:float

    返回单位的最大生命值。不包括护盾。

    health_percentage (Unit类)

    输出:float 范围[0-1.0]

    返回单位的当前生命值百分比。不包括护盾。

    shield(Unit类)

    输出:float

    返回单位的当前护盾值。对于非神族单位,返回0。

    shield_max(Unit类)

    输出:float

    返回单位的最大护盾值。对于非神族单位,返回0。

    shield_percentage(Unit类)

    输出:float 范围[0-1.0]

    返回单位的当前护盾值百分比。对于非神族单位,返回0。

    shield_health_percentage(Unit类)

    输出:float

    返回单位拥有的护盾+生命值的百分比。

    units.py

    idle(Units类)

    输入:position: Union[Unit, Point2], queue: bool = False

    输出:Units

    返回所有空闲的单位或者建筑。(单位处于站着,建筑没有做任何事)

    例如:

    返回所有空闲的狂热者。

    如果狂热者在移动或攻击,就不算空闲。

    if self.units(UnitTypeId.ZEALOT).amount > 0:
        print(f"self.units(UnitTypeId.ZEALOT).idle: {self.units(UnitTypeId.ZEALOT).idle}")
    
    closest_to(Units类)

    输入:Union[Unit, Point2]

    输出:Unit

    返回距离输出位置或单位最近的单位。

    of_type(Units类)

    输入:Union[UnitTypeId, Iterable[UnitTypeId]]

    输出:Units

    过滤特定类型的所有单位。

    score.py(得分)

    self.state.score.score

    在建和当前单位与建筑价值+矿物+vespene的总和。

    例如,初始为1050分,如果又采集了100晶体矿,则分数变为1150。如果花100去建造水晶塔,在建过程中仍为1150,建造完毕后也是1150。如果又生产了几个狂热者,则分数也是不变的。

    self.state.score.killed_value_units

    玩家销毁的敌方单位的矿物和瓦斯总和。例如,跳虫是25个晶体矿生产的,击杀一只跳虫后,该数值增加25.

    self.state.score.killed_value_structures

    玩家销毁的敌方建筑的矿物和瓦斯总和。

    self.state.score.killed_minerals_army

    玩家销毁敌方战斗单位(不包括工人单位)的总晶体矿价值。

    self.state.score.killed_vespene_army

    玩家销毁敌方战斗单位(不包括工人单位)的总高能瓦斯矿价值。

    获取游戏数据

    get_resource

    def get_resource(self):
        return {
            "游戏时间": self.time_formatted,
            "工人数量": self.workers.amount,
            "晶体矿数量": self.minerals,
            "高能瓦斯数量": self.vespene,
            "最大补给量": self.supply_cap,
            "剩余补给量": self.supply_left,
            "已使用补给量": self.supply_used,
            "战斗单位已消耗补给量": self.supply_army,
            "战斗单位数量": self.army_count,
            "基地数量": self.townhalls.amount,
        }
    

    get_structures

    def get_structures(self):
        return {
            "已有基地数量": self.townhalls.amount,
            "已有吸纳仓数量": self.structures(UnitTypeId.ASSIMILATOR).amount,
            "已有水晶塔数量": self.structures(UnitTypeId.PYLON).amount,
            "已有传送门数量": self.structures(UnitTypeId.GATEWAY).amount,
            "正在建造的水晶塔数量": self.already_pending(UnitTypeId.PYLON),
            "正在建造的传送门数量": self.already_pending(UnitTypeId.GATEWAY),
            #"护盾充能器数量": self.structures(UnitTypeId.SHIELDBATTERY).amount,
            #"折跃门数量": self.structures(UnitTypeId.WARPGATE).amount,
            #"控制芯核数量": self.structures(UnitTypeId.CYBERNETICSCORE).amount,
            #"光影议会数量": self.structures(UnitTypeId.TWILIGHTCOUNCIL).amount,
            #"机械台数量": self.structures(UnitTypeId.ROBOTICSFACILITY).amount,
            #"星门数量": self.structures(UnitTypeId.STARGATE).amount,
        }
    

    训练单位

    train_Probe

    async def train_Probe(self):
        print(f'Action ==> 训练探机')
    
        if not self.structures(UnitTypeId.NEXUS).exists:
            print('没有可用的基地')
            return '没有可用的基地'
    
        for nexus in self.townhalls:
            if self.workers.amount + self.already_pending(UnitTypeId.PROBE) > 75:
                print('已经有太多探机了(超过75个)')
                return '已经有太多探机了(超过75个)'
            if self.supply_left <= 0:
                print('剩余补给不足')
                return '剩余补给不足'
            if not self.can_afford(UnitTypeId.PROBE):
                print('资源不足')
                return '资源不足'
            nexus.train(UnitTypeId.PROBE, True)
            print('训练探机')
            return True
    
        print('所有基地都在忙碌')
        return '所有基地都在忙碌'
    

    train_Zealot

    async def train_Zealot(self):
        print(f"Action ==> 训练狂热者")
    
        if not self.structures(UnitTypeId.GATEWAY).exists:
            print("没有可用的传送门")
            return "没有可用的传送门"
    
        # 检查补给
        required_supply = self.calculate_supply_cost(UnitTypeId.ZEALOT)
        if self.supply_left < required_supply:
            print("剩余补给不足")
            return "剩余补给不足"
        
        # 检查传送门空闲状态
        ready_gates = self.structures(UnitTypeId.GATEWAY).ready
        if not ready_gates:
            print("传送门正在建造")
            return "传送门正在建造"
        else:
            # 存在BUG,即无法判断传送门是否真的训练了2个狂热者。
            for i in range(2):
                for gate in ready_gates:
                    if len(gate.orders) < 5:
                        if not self.can_afford(UnitTypeId.ZEALOT):
                            print("资源不足")
                            return "资源不足"
                        gate.train(UnitTypeId.ZEALOT, True)
                        break
            return True
    

    建造单位

    build_Pylon

    async def build_Pylon(self):
        print(f"Action ==> 建造水晶塔")
        
        if not (self.structures(UnitTypeId.NEXUS).exists and self.units(UnitTypeId.PROBE).exists):
            print("没有基地或探机")
            return "没有基地或探机"
        
        if not self.supply_cap == 200:
            if not self.can_afford(UnitTypeId.PYLON):
                print("资源不足")
                return "资源不足"
    
            # 建造位置为,随机的一个基地向地图中心的方向前进5个单位的位置
            res = await self.build(UnitTypeId.PYLON, near=self.townhalls.random.position.towards(self.game_info.map_center, 5))
            if not res:
                print("建造位置被占用")
                return "建造位置被占用"
            return True
        else:
            print("剩余补给不足")
            return "剩余补给不足"
    

    build_Assimilator

    async def build_Assimilator(self):
        print(f"Action ==> 建造吸纳仓")
        
        if not (self.structures(UnitTypeId.NEXUS).exists and self.units(UnitTypeId.PROBE).exists):
            print("没有基地或探机")
            return "没有基地或探机"
        if not self.can_afford(UnitTypeId.ASSIMILATOR):
            print("资源不足")
            return "资源不足"
        if not self.vespene_geyser.exists:
            print("没有可用的气矿")
            return "没有可用的气矿"
    
        # if not self.structures(UnitTypeId.PYLON).exists:
        #     print("没有水晶塔")
        #     return "没有水晶塔"
    
        building_assimilators_num = self.already_pending(UnitTypeId.ASSIMILATOR)
        if building_assimilators_num < 2:
            for nexus in self.townhalls:
                for vespene in self.vespene_geyser.closer_than(10, nexus):
                    moving_workers = [worker for worker in self.workers.closer_than(5, vespene) if worker.is_moving]
                    if moving_workers:
                        continue
                    if self.can_afford(UnitTypeId.ASSIMILATOR) and not self.structures(UnitTypeId.ASSIMILATOR).closer_than(2, vespene):
                        await self.build(UnitTypeId.ASSIMILATOR, vespene)
                        print('建造吸纳仓')
                        return True
        else:
            print("已经有2个气矿正在建造")
            return "已经有2个气矿正在建造"
    

    build_Gateway

    async def build_Gateway(self):
        print(f"Action ==> 建造传送门")
        if not self.structures(UnitTypeId.PYLON).exists:
            print("没有水晶塔")
            return "没有水晶塔"
        if not self.structures(UnitTypeId.NEXUS).exists:
            print("没有基地")
            return "没有基地"
        if not self.units(UnitTypeId.PROBE).exists:
            print("没有探机")
            return "没有探机"
        if not self.can_afford(UnitTypeId.GATEWAY):
            print("建造资源不足")
            return "资源不足"
        
        res = await self.build(UnitTypeId.GATEWAY, near=self.townhalls.random.position.towards(self.game_info.map_center, 10))
        if not res:
            print("建造位置被占用")
            return "建造位置被占用"
        return True
    

    其他动作

    attack_enemy_base_all_Zeolot

    async def attack_enemy_base_all_Zeolot(self):
        print(f"Action ==> 全体狂热者攻击敌方基地")
        
        if not self.units(UnitTypeId.ZEALOT).exists:
            print("没有狂热者")
            return "没有狂热者"
        if self.units(UnitTypeId.ZEALOT).idle.amount >= 10:
            for zealot in self.units(UnitTypeId.ZEALOT).idle:
                zealot.attack(self.enemy_start_locations[0])
            print("狂热者攻击敌方基地")
            return True
        else:
            print("狂热者数量不足")
            return "狂热者数量不足"
    

    attack_enemy_base_all_Probe

    async def attack_enemy_base_all_Probe(self):
        print(f"Action ==> 全体狂热者攻击敌方基地")
        
        if not self.units(UnitTypeId.PROBE).exists:
            print("没有探机")
            return "没有探机"
    
        for probe in self.units(UnitTypeId.PROBE).idle:
            probe.attack(self.enemy_start_locations[0])
    

    empty_action

    async def empty_action(self):
        print(f"Action ==> 空动作")
        await asyncio.sleep(0.5)
        return True
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值