对于不同类型角色类游戏的战斗系统思考

类型

传统指令式回合制

代表:《梦幻西游》《宝可梦》《最终幻想》早期作品、《仙剑》战斗

核心需求:

  • 双方轮流下达指令(攻击/技能/道具/逃跑)

  • 速度属性决定同一回合内谁先出手

  • 一个完整回合 = 所有单位各行动一次

  • 战斗结果是确定性的(同样操作得到同样结果),便于做战斗录像和平衡

python 复制代码
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class CommandType(Enum):
    ATTACK = 1
    SKILL = 2
    DEFEND = 3
    ITEM = 4
    FLEE = 5

@dataclass
class Unit:
    name: str
    hp: int
    max_hp: int
    mp: int
    max_mp: int
    atk: int
    defense: int
    speed: int           # 决定回合内出手顺序
    defending: bool = False
    alive: bool = True

    def take_damage(self, dmg: int) -> int:
        if self.defending:
            dmg = max(1, dmg // 2)
        else:
            dmg = max(1, dmg)
        self.hp = max(0, self.hp - dmg)
        if self.hp == 0:
            self.alive = False
        return dmg

class ClassicTurnBattle:
    def __init__(self, allies: list[Unit], enemies: list[Unit]):
        self.allies = allies
        self.enemies = enemies
        self.round = 0
        self.log: list[str] = []

    def _alive_units(self) -> list[Unit]:
        return [u for u in self.allies + self.enemies if u.alive]

    def _enemies_of(self, unit: Unit) -> list[Unit]:
        pool = self.enemies if unit in self.allies else self.allies
        return [u for u in pool if u.alive]

    def _allies_of(self, unit: Unit) -> list[Unit]:
        pool = self.allies if unit in self.allies else self.enemies
        return [u for u in pool if u.alive]

    def _deal_damage(self, attacker: Unit, target: Unit) -> int:
        raw = attacker.atk * 2 - target.defense
        crit = random.random() < 0.05  # 5% 暴击
        dmg = max(1, int(raw * (1.5 if crit else 1)))
        dealt = target.take_damage(dmg)
        self.log.append(
            f"{attacker.name} 攻击 {target.name} 造成 {dealt} 伤害"
            f"{'(暴击!)' if crit else ''}"
        )
        return dealt

    def execute_round(self, commands: dict[Unit, tuple[CommandType, Optional[Unit]]]):
        """commands: {unit: (command_type, target)}"""
        self.round += 1
        self.log.append(f"===== 第 {self.round} 回合 =====")

        # 1. 按速度降序决定出手顺序
        turn_order = sorted(
            [u for u in commands.keys() if u.alive],
            key=lambda u: -u.speed
        )

        for actor in turn_order:
            if not actor.alive:
                continue
            cmd, target = commands[actor]

            # 重置防御姿态
            actor.defending = False

            if cmd == CommandType.ATTACK:
                if target is None or not target.alive:
                    target = random.choice(self._enemies_of(actor))
                self._deal_damage(actor, target)

            elif cmd == CommandType.DEFEND:
                actor.defending = True
                self.log.append(f"{actor.name} 进入防御姿态")

            elif cmd == CommandType.FLEE:
                if random.random() < 0.5:
                    self.log.append(f"{actor.name} 逃跑成功!战斗结束")
                    return True  # 逃跑成功
                else:
                    self.log.append(f"{actor.name} 逃跑失败")

            # 死亡检查
            if all(not u.alive for u in self._enemies_of(actor)):
                self.log.append("敌方全灭,战斗胜利!")
                return True
            if all(not u.alive for u in self._allies_of(actor)):
                self.log.append("我方全灭,战斗失败...")
                return True

        # 回合结束:清除本回合的防御标记(为下回合准备)
        for u in self._alive_units():
            u.defending = False
        return False

行动条驱动半即时

代表:《阴阳师》《崩坏:星穹铁道》《最终幻想》ATB 版本、你前面问的三国幻想志类

核心需求:

  • 没有"回合"概念,每个单位有独立的行动条

  • 行动条按单位速度累积,满了就行动一次,然后清零

  • 行动顺序不是固定轮换,而是动态竞争

  • 技能可以"推条/拉条"------改变行动条进度,这是策略深度的核心来源

  • 表现层通常是"走条"动画

python 复制代码
import random
from dataclasses import dataclass
from typing import Optional

@dataclass
class ATBUnit:
    name: str
    hp: int
    max_hp: int
    atk: int
    speed: int          # 行动条累积速度
    gauge: float = 0.0  # 当前行动条 [0, 100]
    alive: bool = True

    def fill_gauge(self, dt: float):
        if self.alive:
            self.gauge = min(100.0, self.gauge + self.speed * dt)

    def reset_gauge(self):
        self.gauge = 0.0

class ATBBattle:
    """行动条驱动的半即时战斗"""
    def __init__(self, allies: list[ATBUnit], enemies: list[ATBUnit]):
        self.units = allies + enemies
        self.allies = allies
        self.enemies = enemies
        self.log: list[str] = []
        self.time = 0.0
        self.dt = 0.1  # 时间步长

    def _enemies_of(self, unit: ATBUnit) -> list[ATBUnit]:
        pool = self.enemies if unit in self.allies else self.allies
        return [u for u in pool if u.alive]

    def _pick_target(self, attacker: ATBUnit) -> Optional[ATBUnit]:
        """简单 AI:选敌方血量最低的"""
        enemies = self._enemies_of(attacker)
        if not enemies:
            return None
        return min(enemies, key=lambda e: e.hp / e.max_hp)

    def _cast_skill_with_push(self, caster: ATBUnit, target: ATBUnit):
        """
        释放技能,并演示推条/拉条机制
        - 自身行动条清零(已行动)
        - 目标被'推条':行动条减少,延迟其下次行动
        """
        # 伤害结算
        dmg = int(caster.atk * 1.8)
        target.hp = max(0, target.hp - dmg)
        if target.hp == 0:
            target.alive = False
        self.log.append(
            f"[{self.time:.1f}s] {caster.name} 释放技能 → {target.name} 造成 {dmg} 伤害"
        )

        # 推条:让目标行动条后退 30%
        if target.alive:
            target.gauge = max(0, target.gauge - 30)
            self.log.append(f"  └ 推条:{target.name} 行动条 -30")

        # 自身行动条清零
        caster.reset_gauge()

    def run(self, max_time: float = 60.0):
        """主循环:时间驱动"""
        self.log.append("===== 战斗开始(行动条驱动)=====")
        while self.time < max_time:
            self.time += self.dt

            # 1. 所有存活单位累积行动条
            for u in self.units:
                u.fill_gauge(self.dt)

            # 2. 找出行动条满的单位
            ready = [u for u in self.units if u.alive and u.gauge >= 100]
            if not ready:
                continue

            # 3. 如果有多个就绪,速度最高的先动
            actor = max(ready, key=lambda u: u.speed)

            # 4. 选目标并行动
            target = self._pick_target(actor)
            if target is None:
                self.log.append("战斗结束")
                break

            # 50% 普通攻击,50% 技能(演示推条)
            if random.random() < 0.5:
                dmg = max(1, actor.atk - target.atk // 2)
                target.hp = max(0, target.hp - dmg)
                if target.hp == 0:
                    target.alive = False
                actor.reset_gauge()
                self.log.append(
                    f"[{self.time:.1f}s] {actor.name} 普通攻击 → {target.name} 造成 {dmg} 伤害"
                )
            else:
                self._cast_skill_with_push(actor, target)

            # 5. 胜负判定
            if not any(u.alive for u in self.enemies):
                self.log.append(f"[{self.time:.1f}s] 战斗胜利!")
                break
            if not any(u.alive for u in self.allies):
                self.log.append(f"[{self.time:.1f}s] 战斗失败...")
                break

        return self.log

九宫格 / 自走棋式自动战斗

代表:《三国幻想志2》《自走棋》《领主冲突》《止戈之战》《真理之拳》

核心需求:

  • 战前布阵(3×3 或类似网格),战时不可操作

  • 单位按行动条/速度决定出手顺序

  • 有空间关系:单位在网格上有坐标,攻击有范围

  • 目标选择按规则:最前排优先 → 同列优先 → 距离最近

  • 不在攻击范围内 → 向目标移动一格

  • 表现层是"武将走过去打一下"的动画

python 复制代码
import math
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class Faction(Enum):
    ALLY = 1
    ENEMY = 2

@dataclass
class GridUnit:
    name: str
    faction: Faction
    hp: int
    max_hp: int
    atk: int
    speed: int
    pos: tuple[int, int]      # (row, col) 九宫格坐标
    attack_range: int = 1
    gauge: float = 0.0
    alive: bool = True

    def fill_gauge(self, dt: float):
        if self.alive:
            self.gauge = min(100.0, self.gauge + self.speed * dt)

class GridAutoBattle:
    """
    九宫格自动战斗:战前布阵,战时全自动
    棋盘:3x3,ally 在左三列 (col 0,1,2),enemy 在右三列 (col 6,7,8)
    或者更紧凑:ally 占 col 0,1,enemy 占 col 1,2(共用中线)
    """
    def __init__(self, allies: list[GridUnit], enemies: list[GridUnit]):
        # 简化:ally 在 col 0,enemy 在 col 2
        self.allies = allies
        self.enemies = enemies
        self.units = allies + enemies
        self.log: list[str] = []
        self.time = 0.0
        self.dt = 0.1

    def _enemies_of(self, unit: GridUnit) -> list[GridUnit]:
        pool = self.enemies if unit.faction == Faction.ALLY else self.allies
        return [u for u in pool if u.alive]

    def _select_target(self, attacker: GridUnit) -> Optional[GridUnit]:
        """
        目标选择规则(九宫格优先级):
        1. 优先攻击最前排(离自己最近的列)
        2. 同排优先同列
        3. 都没有则曼哈顿距离最近
        """
        enemies = self._enemies_of(attacker)
        if not enemies:
            return None

        ar, ac = attacker.pos

        def priority_key(e: GridUnit):
            er, ec = e.pos
            # 1. 列距离(越小越靠前)
            col_dist = abs(ec - ac)
            # 2. 是否同列(同列优先)
            same_col = 0 if ec == ac else 1
            # 3. 曼哈顿距离
            manhattan = abs(er - ar) + col_dist
            return (col_dist, same_col, manhattan)

        return min(enemies, key=priority_key)

    def _manhattan(self, a: tuple, b: tuple) -> int:
        return abs(a[0] - b[0]) + abs(a[1] - b[1])

    def _can_attack(self, attacker: GridUnit, target: GridUnit) -> bool:
        return self._manhattan(attacker.pos, target.pos) <= attacker.attack_range

    def _step_toward(self, attacker: GridUnit, target: GridUnit):
        """向目标移动一格(贪心:先对齐行,再对齐列)"""
        ar, ac = attacker.pos
        tr, tc = target.pos

        if ar < tr:
            ar += 1
        elif ar > tr:
            ar -= 1
        elif ac < tc:
            ac += 1
        elif ac > tc:
            ac -= 1

        old_pos = attacker.pos
        attacker.pos = (ar, ac)
        self.log.append(
            f"[{self.time:.1f}s] {attacker.name} 移动:{old_pos} → {attacker.pos}"
        )

    def run(self, max_time: float = 120.0):
        self.log.append("===== 九宫格自动战斗开始 =====")
        while self.time < max_time:
            self.time += self.dt

            # 1. 累积行动条
            for u in self.units:
                u.fill_gauge(self.dt)

            # 2. 找到行动条满的单位
            ready = [u for u in self.units if u.alive and u.gauge >= 100]
            if not ready:
                continue

            # 3. 行动顺序:速度 → 战力(这里用 atk 近似)→ 站位
            actor = max(ready, key=lambda u: (u.speed, u.atk))

            # 4. 选目标
            target = self._select_target(actor)
            if target is None:
                self.log.append("战斗结束")
                break

            # 5. 判断攻击 or 移动
            if self._can_attack(actor, target):
                # 在范围内,发动攻击
                dmg = max(1, actor.atk - target.atk // 3)
                target.hp = max(0, target.hp - dmg)
                if target.hp == 0:
                    target.alive = False
                actor.gauge = 0.0
                self.log.append(
                    f"[{self.time:.1f}s] {actor.name} 攻击 {target.name} "
                    f"造成 {dmg} 伤害 (HP: {target.hp}/{target.max_hp})"
                )
            else:
                # 不在范围内,向目标移动一格
                actor.gauge = 0.0  # 移动也消耗本次行动机会
                self._step_toward(actor, target)

            # 6. 胜负判定
            if not any(u.alive for u in self.enemies):
                self.log.append(f"[{self.time:.1f}s] 战斗胜利!")
                break
            if not any(u.alive for u in self.allies):
                self.log.append(f"[{self.time:.1f}s] 战斗失败...")
                break

        return self.log

挂机 / 放置类自动战斗

代表:各种 Idle Game、放置 RPG、《剑与远征》AFK 系统

核心需求:

  • 完全没有玩家战时操作

  • 战前配置队伍和站位,然后离线也能战斗

  • 战斗逻辑必须纯函数化、可序列化、可加速模拟

  • 通常批量模拟成千上万场战斗(推图、挂机收益计算)

python 复制代码
import random
from dataclasses import dataclass, replace
from typing import Optional

@dataclass
class IdleUnit:
    name: str
    faction: Faction
    hp: int
    max_hp: int
    atk: int
    speed: int
    pos: tuple[int, int]
    attack_range: int = 1
    gauge: float = 0.0
    alive: bool = True

    def clone(self) -> 'IdleUnit':
        """深拷贝,用于批量模拟"""
        return replace(self)

class IdleAutoBattle:
    """
    挂机战斗:无渲染、可批量、可加速
    关键设计:战斗过程是纯函数,给定初始状态和随机种子,结果完全确定
    """
    def __init__(self, allies: list[IdleUnit], enemies: list[IdleUnit], seed: int = 42):
        self.initial_allies = [u.clone() for u in allies]
        self.initial_enemies = [u.clone() for u in enemies]
        self.seed = seed

    def simulate_one_battle(self) -> dict:
        """模拟一场战斗,返回结果统计"""
        random.seed(self.seed)  # 固定种子保证可复现

        # 重置战斗状态
        allies = [u.clone() for u in self.initial_allies]
        enemies = [u.clone() for u in self.initial_enemies]
        all_units = allies + enemies

        time = 0.0
        dt = 0.1
        max_time = 300.0

        while time < max_time:
            time += dt

            # 累积行动条
            for u in all_units:
                if u.alive:
                    u.gauge = min(100.0, u.gauge + u.speed * dt)

            # 找行动单位
            ready = [u for u in all_units if u.alive and u.gauge >= 100]
            if not ready:
                continue

            actor = max(ready, key=lambda u: (u.speed, u.atk))

            # 选目标(敌方存活单位)
            enemy_pool = enemies if actor.faction == Faction.ALLY else allies
            alive_enemies = [e for e in enemy_pool if e.alive]
            if not alive_enemies:
                break

            target = min(alive_enemies, key=lambda e: e.hp / e.max_hp)

            # 攻击 or 移动(逻辑同上,这里简化只做攻击)
            if actor.attack_range >= abs(target.pos[1] - actor.pos[1]):
                dmg = max(1, actor.atk - target.atk // 3 + random.randint(-2, 2))
                target.hp = max(0, target.hp - dmg)
                if target.hp == 0:
                    target.alive = False
                actor.gauge = 0.0
            else:
                # 移动:简化版,向目标列靠近
                ar, ac = actor.pos
                _, tc = target.pos
                if ac < tc:
                    ac += 1
                elif ac > tc:
                    ac -= 1
                actor.pos = (ar, ac)
                actor.gauge = 0.0

            # 胜负
            if not any(e.alive for e in enemies):
                return {
                    "result": "win",
                    "time": time,
                    "remaining_hp_percent": sum(u.hp for u in allies if u.alive) /
                                            sum(u.max_hp for u in self.initial_allies)
                }
            if not any(a.alive for a in allies):
                return {"result": "lose", "time": time, "remaining_hp_percent": 0.0}

        return {"result": "timeout", "time": time, "remaining_hp_percent": 0.0}

    def batch_simulate(self, n: int = 1000) -> dict:
        """批量模拟 n 场,统计胜率"""
        wins = 0
        total_hp_percent = 0.0
        for i in range(n):
            self.seed = 42 + i  # 每场不同种子
            result = self.simulate_one_battle()
            if result["result"] == "win":
                wins += 1
                total_hp_percent += result["remaining_hp_percent"]

        return {
            "total": n,
            "wins": wins,
            "win_rate": wins / n,
            "avg_remaining_hp_on_win": total_hp_percent / max(1, wins)
        }

# 使用示例
if __name__ == "__main__":
    # 配置两支队伍
    allies = [
        IdleUnit("赵云", Faction.ALLY, 1000, 1000, 150, 120, (0, 0)),
        IdleUnit("关羽", Faction.ALLY, 1200, 1200, 130, 100, (1, 0)),
        IdleUnit("张飞", Faction.ALLY, 1100, 1100, 140, 90, (2, 0)),
    ]
    enemies = [
        IdleUnit("吕布", Faction.ENEMY, 1500, 1500, 160, 130, (0, 2)),
        IdleUnit("董卓", Faction.ENEMY, 1800, 1800, 120, 80, (1, 2)),
    ]

    battle = IdleAutoBattle(allies, enemies)
    stats = battle.batch_simulate(1000)
    print(f"胜率: {stats['win_rate']:.1%}")
    print(f"胜利时平均剩余血量: {stats['avg_remaining_hp_on_win']:.1%}")
相关推荐
爱写代码的阿木12 小时前
基于鸿蒙OS开发附近社交游戏平台(二十二)-ChatPage 即时通讯 UI
游戏·华为·harmonyos
爱写代码的阿木14 小时前
基于鸿蒙OS开发附近社交游戏平台(二十三)-拉黑系统、屏蔽与全局过滤
游戏·华为·harmonyos
木木子2220 小时前
# 猜数字游戏 — HarmonyOS交互逻辑与随机算法实现
算法·游戏·华为·交互·harmonyos
爱写代码的阿木20 小时前
基于鸿蒙OS开发附近社交游戏平台(十二)-狼人杀角色技能与投票系统
游戏·华为·harmonyos
qizayaoshuap1 天前
# 随机密码生成器 — HarmonyOS数据绑定与字符串处理实践
算法·游戏·华为·交互·harmonyos
爱写代码的阿木1 天前
基于鸿蒙OS开发附近社交游戏平台(五)-构建配置与签名部署
游戏·华为·harmonyos
潜龙95271 天前
H5类游戏在Web和App中智能UI方案
游戏·h5测试
xvhao20131 天前
T750414 【游戏】计算24点 题解
数据结构·c++·算法·游戏
逻辑君1 天前
The Null Epoch 游戏研究报告(2)
人工智能·深度学习·游戏·机器学习