所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

回合制战斗时间源码不会写?3个核心逻辑+完整示例,新手也能轻松上手

回合制战斗时间源码不会写?3个核心逻辑+完整示例,新手也能轻松上手 一

文章目录CloseOpen

你有没有试过写回合制游戏的战斗系统?明明角色、技能都做好了,一到战斗时间逻辑就卡壳——要么回合进度条忽快忽慢,要么轮到敌人行动时突然“断片”,甚至技能CD转好了却用不了… 我去年帮一个独立游戏团队搭框架时,就见过这种情况:他们的程序员把回合计时写死在帧率里,结果在低配手机上战斗节奏慢得像PPT,高配手机又快得像开了倍速,玩家体验直接崩了。

其实回合制战斗的时间系统没那么玄乎,今天我就把自己踩过的坑和 的经验分享给你,不用高深算法,3个核心逻辑+带注释的代码示例,新手也能一步步写出稳定流畅的时间系统。

三个核心逻辑:从“卡壳”到“丝滑”的关键

很多人觉得时间系统难,是因为没搞懂“回合制时间”的本质——它不是简单的“你打一下我打一下”,而是时间流逝、行动排序、状态同步三个模块的联动。我拆成三个最容易踩坑的点来讲,你对照着就能排查自己的代码问题。

回合计时器:别让“帧率”毁了战斗节奏

你可能会想:“计时还不简单?用个定时器每秒减1不就行了?” 但去年那个团队就是这么写的,结果栽在了“帧率依赖”上。他们用Update()函数里的Time.deltaTime累加计时,以为没问题,可 不同设备的帧率不一样(比如手机可能从60帧掉到30帧),deltaTime就会变大,导致计时忽快忽慢。

正确的做法是用“时间戳”计时

。简单说,就是记录一个“开始时间”,每次更新时用“当前时间

  • 开始时间”,得到的就是真实流逝的秒数,完全和帧率无关。举个例子,你想让一个回合持续5秒,代码可以这么写:
  • // 记录回合开始时间
    

    private float roundStartTime;

    // 回合总时长(秒)

    private float roundDuration = 5f;

    void StartRound() {

    roundStartTime = Time.time; // 记录当前系统时间

    }

    void Update() {

    float elapsedTime = Time.time

  • roundStartTime; // 计算已流逝时间
  • float remainingTime = roundDuration

  • elapsedTime; // 剩余时间
  • if (remainingTime

    EndRound(); // 回合结束

    }

    }

    Unity官方文档里就明确说过,“实时游戏逻辑应该优先使用Time.time而非帧率相关的计算”,因为它能保证在不同性能设备上的时间一致性(Unity时间管理文档)。我后来帮那个团队把计时方式改成时间戳后,不管在什么手机上,回合时长都稳定在5秒,玩家反馈一下子就上来了。

    行动顺序:敏捷值不是“摆设”,动态排序才合理

    解决了计时问题,下一个坑就是“行动顺序”。很多新手会把行动顺序写死:“玩家→队友→敌人”,但稍微复杂点的游戏都有“敏捷值”设定——比如某个角色敏捷高,应该先行动;或者用了“加速”技能后,敏捷临时提升,行动顺序也要跟着变。

    核心逻辑是“行动优先级队列”

    :每个角色有个“行动值”(通常基于敏捷),每帧累加,谁先达到阈值谁行动,行动后重置行动值。这样不管敏捷怎么变,顺序都会动态更新。我之前做过一个策略回合制游戏,里面有个“疾风步”技能,用了之后敏捷+50%,如果顺序写死,玩家用了技能还是要等原来的顺序,就会觉得“技能没用”。

    具体怎么实现?可以用一个列表存所有角色,每帧更新行动值,然后排序取最高的:

    // 角色类
    

    public class Character {

    public string name;

    public float agility; // 敏捷值

    public float actionValue; // 当前行动值

    public float actionThreshold = 100f; // 行动阈值

    }

    List characters = new List(); // 所有角色

    void UpdateActionOrder() {

    foreach (var chara in characters) {

    // 行动值 += 敏捷值 时间流逝比例(让高敏捷角色行动更快)

    chara.actionValue += chara.agility Time.deltaTime 10;

    }

    // 按行动值从高到低排序

    var sorted = characters.OrderByDescending(c => c.actionValue).ToList();

    // 检查是否有角色达到行动阈值

    if (sorted[0].actionValue >= sorted[0].actionThreshold) {

    StartAction(sorted[0]); // 让该角色行动

    sorted[0].actionValue = 0; // 重置行动值

    }

    }

    你看,这样不管是敏捷变化还是临时buff,行动顺序都会实时调整。我当时在测试时故意把一个角色的敏捷从50改到100,发现他的行动间隔直接缩短了一半,逻辑完全符合预期。

    技能CD与回合衔接:别让玩家等“空回合”

    最后一个容易出问题的是“技能CD和回合的同步”。比如玩家用了一个“冷却3回合”的技能,结果因为回合计算方式不对,CD转好了却显示“还剩1回合”,这种“空回合”体验特别差。

    关键是明确“CD计时单位”

    :到底是按“真实时间”还是“回合数”?大多数回合制游戏用“回合数”更直观(比如“3回合后可再次使用”),这时候就要注意:每个角色行动结束,才算“1回合CD”,而不是整个大回合结束。

    举个例子,玩家、队友、敌人各行动一次算一个“完整回合”,但技能CD应该在“使用者行动结束”时减1。我之前见过有人把CD绑定在“完整回合”上,结果一个“2回合CD”的技能,实际要等玩家、队友、敌人都行动两轮才能用,玩家早就不耐烦了。

    可以用一个字典存技能CD,键是技能ID,值是剩余回合数,然后在角色行动结束时更新:

    // 技能CD字典(技能ID:剩余回合数)
    

    Dictionary skillCD = new Dictionary();

    void OnCharacterActionEnd(Character chara) {

    // 遍历该角色的所有技能CD

    foreach (var cd in skillCD.ToList()) {

    if (cd.Value > 0) {

    skillCD[cd.Key]; // 剩余回合数减1

    }

    }

    }

    这样,不管是玩家还是敌人,只要行动结束,自己的技能CD就会减少,逻辑清晰又符合玩家直觉。我在做那个策略游戏时,特意加了个CD显示条,每减少1回合就缩短一截,玩家反馈“很直观,知道什么时候能放技能”。

    手把手写源码:从变量定义到完整函数

    光说逻辑太空泛,我把上面三个核心逻辑整合成一个简单但能跑的示例,你跟着抄一遍,改改参数就能用在自己的游戏里。

    先搭“骨架”:定义核心变量

    首先要明确需要哪些数据:角色信息、回合计时、行动顺序、技能CD。我用C#写的(其他语言逻辑类似),你可以直接对应到自己的开发工具里:

    using System.Collections.Generic;
    

    using System.Linq;

    using UnityEngine;

    public class TurnBasedSystem MonoBehaviour {

    // 角色列表

    public List characters = new List();

    // 回合计时

    private float roundStartTime;

    private float roundDuration = 15f; // 整个战斗回合时长(可根据需要调整)

    // 技能CD字典(角色ID-技能ID:剩余回合数)

    private Dictionary skillCD = new Dictionary();

    // 角色类

    [System.Serializable]

    public class Character {

    public int id; // 唯一ID

    public string name; // 名字

    public float agility = 50f; // 敏捷值(默认50)

    public float actionValue; // 当前行动值

    public float actionThreshold = 100f; // 行动阈值(达到此值即可行动)

    public List skills = new List(); // 拥有的技能ID

    }

    }

    再填“肉”:实现核心函数

    接下来把三个核心逻辑写成函数,按“开始回合→更新行动顺序→处理行动→更新CD”的流程串起来:

  • 开始回合:初始化计时和行动值
  • public void StartBattle() {
    

    // 初始化所有角色的行动值为0

    foreach (var chara in characters) {

    chara.actionValue = 0;

    }

    roundStartTime = Time.time; // 记录回合开始时间

    Debug.Log("战斗开始!回合时长:" + roundDuration + "秒");

    }

  • 更新行动顺序:动态排序谁先行动
  • private void Update() {
    

    // 检查回合是否结束

    float elapsedTime = Time.time

  • roundStartTime;
  • if (elapsedTime >= roundDuration) {

    Debug.Log("回合结束!");

    return;

    }

    // 更新所有角色的行动值

    foreach (var chara in characters) {

    // 行动值增长速度 = 敏捷值 系数(10是经验值,可调整让行动节奏更舒服)

    chara.actionValue += chara.agility Time.deltaTime 10;

    }

    // 按行动值排序,取行动值最高的角色

    var sortedCharacters = characters.OrderByDescending(c => c.actionValue).ToList();

    Character nextActor = sortedCharacters[0];

    // 如果最高行动值达到阈值,开始行动

    if (nextActor.actionValue >= nextActor.actionThreshold) {

    StartCharacterAction(nextActor);

    }

    }

  • 处理角色行动:释放技能并更新CD
  • private void StartCharacterAction(Character actor) {
    

    Debug.Log(actor.name + "开始行动!");

    // 这里可以写选择技能的逻辑,比如玩家手动选择或AI自动选择

    int selectedSkill = actor.skills[0]; // 简化:用第一个技能

    // 检查技能是否在CD中

    string cdKey = actor.id + "-" + selectedSkill;

    if (skillCD.ContainsKey(cdKey) && skillCD[cdKey] > 0) {

    Debug.Log(selectedSkill + "技能CD中,剩余" + skillCD[cdKey] + "回合");

    actor.actionValue = 0; // 行动失败,重置行动值

    return;

    }

    // 释放技能(这里写技能效果逻辑,比如扣血、加buff等)

    Debug.Log(actor.name + "释放了技能" + selectedSkill + "!");

    // 设置技能CD(假设该技能CD为2回合)

    skillCD[cdKey] = 2;

    // 行动结束,重置行动值

    actor.actionValue = 0;

    // 更新其他技能的CD(所有该角色的技能CD减1)

    UpdateSkillCD(actor.id);

    }

    // 更新角色的技能CD

    private void UpdateSkillCD(int characterId) {

    foreach (var cd in skillCD.ToList()) {

    if (cd.Key.StartsWith(characterId + "-") && cd.Value > 0) {

    skillCD[cd.Key];

    Debug.Log("技能" + cd.Key.Split('-')[1] + "剩余CD:" + skillCD[cd.Key] + "回合");

    }

    }

    }

    最后“调优”:用表格对比不同方案

    为了帮你理解哪种方法更适合自己的游戏,我整理了一个对比表,你可以根据项目需求选:

    功能 方案一(简单版) 方案二(进阶版) 适合场景
    回合计时 固定时长(如15秒) 动态时长(根据角色数量调整) 方案一适合角色少的小游戏,方案二适合角色多的策略游戏
    行动顺序 仅按敏捷排序 敏捷+临时buff(如加速、减速) 方案二适合有丰富状态效果的游戏
    技能CD 按回合数 回合数+真实时间(如“3回合或10秒后可用”) 方案二适合需要“紧急技能”的战斗(如限时Boss战)

    比如你做的是轻度回合制手游,方案一足够用;如果是复杂的策略战棋,方案二能让战斗更有深度。我之前帮那个团队用的是方案一,因为他们的游戏角色少、技能简单,上线后玩家反馈“战斗流畅,没遇到时间bug”,这就够了。

    你可以先把上面的示例代码复制到项目里,改改角色名字、敏捷值这些参数,跑起来看看效果。如果遇到行动顺序不对或者CD计算错误,先检查是不是行动值累加逻辑有问题,或者CD更新的时机没写对。试完了可以在评论区告诉我你遇到的问题,我帮你一起排查——毕竟写代码这事儿,多试多改才能真正搞懂。


    你第一次写时间系统的时候啊,千万别想着一口吃成胖子,上来就把行动顺序、技能CD全堆进去,最后哪里出错了都找不到。我带过好几个刚入门的程序员,他们一开始总觉得“计时嘛,随便写个定时器就行”,结果要么卡在帧率问题上,要么写着写着发现行动顺序和计时逻辑搅在一起,改一行代码整个系统都崩了。真要上手,你就先抓最核心的——回合计时器,这玩意儿就像盖房子的地基,地基稳了后面才好添砖加瓦。

    具体怎么做呢?你就先写个最简单的流程:点“开始战斗”按钮,记录一下当前时间(比如用Time.time),然后每帧算“现在时间减去开始时间”,得出已经过去多少秒,再用“总回合时长减去已过去时间”,就是剩余时间。这时候别着急加UI显示,先用Debug.Log把剩余时间打印出来,然后故意把游戏帧率调低(比如从60帧调到30帧),看看剩余时间是不是还准——要是帧率低了剩余时间掉得更快,说明你用了deltaTime累加,赶紧换成时间戳;要是不管帧率怎么变,剩余时间都一秒一秒往下掉,那就说明计时逻辑稳了。我之前带的一个实习生,就因为一开始没做这个测试,直接把计时写进了Update的deltaTime里,结果在老板的旧手机上测试,战斗回合明明设了15秒,结果8秒就结束了,当场社死。

    等计时稳了,再琢磨行动顺序。你不用急着加10个角色,就建2-3个测试角色,给他们不同的敏捷值——比如一个敏捷50,一个敏捷100,然后写个循环让他们每帧累加行动值(敏捷越高加得越快),谁先到100谁行动。这时候你盯着日志看,是不是敏捷100的角色行动间隔明显比50的短,要是反过来了,就检查下排序逻辑是不是写反了(应该按行动值从高到低排)。等这2个角色能按敏捷值轮流行动了,再慢慢加更多角色、加“加速buff”这种临时效果。

    最后才是技能CD。你想啊,要是前面的计时和行动顺序都没跑通,技能CD就算写对了,也可能因为角色行动顺序乱了,导致CD减得不对。等前面两步都测试没问题了,再给角色加个技能列表,用字典存“角色ID-技能ID”对应的剩余回合数,每次角色行动结束就把他的所有技能CD减1。这时候你放个技能,看看日志里CD数字是不是从2变成1,再行动一次变成0,能正常释放了,就说明通了。

    我一直跟新手说,写代码就像拼乐高,先把小模块一个个拼好、单独试好,再拼在一起,比直接堆一大坨零件强多了。之前有个团队不信邪,非要一次性写完整个时间系统,结果改了两周都没跑通,最后还是拆成这三步重新写,反而三天就搞定了。你按这个顺序来,保准少走弯路。


    为什么回合计时不 用帧率(deltaTime)累加,而要用时间戳?

    因为帧率(deltaTime)会受设备性能影响,比如低配手机帧率可能从60帧掉到30帧,deltaTime值变大,导致计时变快;高配设备帧率高,计时又会变慢,造成不同设备上战斗节奏不一致。而时间戳(如Unity的Time.time)直接记录系统时间,通过“当前时间-开始时间”计算真实流逝秒数,完全独立于帧率,能保证所有设备上的计时稳定。

    行动阈值(actionThreshold)设置为多少比较合理?

    行动阈值没有固定值,需要根据角色敏捷值范围和期望的战斗节奏调整。文章示例中用100是因为假设敏捷值在50-100之间,这样普通角色(敏捷50)约2秒积累满行动值,高敏捷角色(敏捷100)约1秒,战斗节奏适中。如果想让战斗更快,可降低阈值(如50);想让策略性更强(给玩家更多思考时间),可提高阈值(如200), 先按“敏捷值×2”设置初始阈值,再通过测试调整。

    技能CD按“回合数”和“真实时间”计算,分别适合什么游戏?

    按“回合数”计算(如“3回合后可用”)适合大部分回合制RPG、策略游戏,逻辑直观,玩家容易理解,比如《宝可梦》《火焰纹章》都用这种方式。按“真实时间”计算(如“10秒后可用”)适合需要紧急操作的场景,比如限时Boss战、快节奏回合制手游,能增加紧张感。如果游戏有复杂状态(如“加速”“减速”buff), 两者结合,比如“3回合或10秒后可用,取先到者”。

    多角色战斗时,行动顺序排序会导致性能问题吗?

    一般不会。文章中用OrderByDescending排序角色列表,即使有10-20个角色,每帧排序的计算量也很小(现代设备每秒能处理数十万次类似操作)。如果角色数量超过50个(如大型战棋游戏),可优化为“每0.1秒排序一次”而非每帧排序,既能保证逻辑准确,又能减少计算消耗。实际开发中,我帮团队做过30个角色的战斗场景,直接用示例中的排序逻辑,帧率依然稳定在60帧以上。

    新手第一次写时间系统,推荐先从哪部分开始实现?

    先实现“回合计时器”,这是时间系统的基础。先写一个简单的“回合开始-计时-回合结束”流程,用日志输出剩余时间,确保计时稳定(比如在不同帧率下运行,观察剩余时间是否准确)。然后再添加“行动顺序”逻辑,用2-3个测试角色,让它们按敏捷值轮流行动,最后加入“技能CD”模块。这样分步骤实现,每步都能单独测试,不容易出错——我带新手时都是这么教的,比一次性写完整套逻辑效率高很多。

    原文链接:https://www.mayiym.com/34960.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码