Unity通过协程实现回合制战斗(一、1v1部分)

相信大家都玩过放置类或挂机类游戏,这些游戏普遍都有回合制战斗,本篇文章将会使用协程来复刻出这种玩法

对于一个回合制游戏,我们需要有2种角色,一种是我方角色,一种是敌方角色,本篇文章将实现敌我双方1V1的回合制战斗。

角色数据类

战斗管理类

注,这里默认我方角色先手

首先,我们来设计角色类,这里我们将使用ScriptableObject来存数据,在Unity项目中新建CharacterData.cs,并用以下代码覆盖即可:

using UnityEngine; [CreateAssetMenu(fileName = "CharacterName", menuName = "创建战斗角色", order = 1)] public class CharacterData : ScriptableObject { public string characterName; public int hp; public int atk; [Range(0, 100)] public int defRate; [Range(0, 100)] public int duckRate; public Character ToCharacter() { return new Character() { Name = characterName, Hp = hp, Atk = atk, DefRate = defRate, DuckRate = duckRate }; } } public class Character { public string Name; public int Hp; public int Atk; public int DefRate; public int DuckRate; public bool Dead => Hp <= 0; }

在这里我们用字段代表了玩家的一些属性,通过Range(0,100)这个标签限制,同时因为继承了ScriptableObject,我们可以很方便的创建数据并保存下来。

注,我们还做了个ToCharacter方法,可以通过配置文件返回战斗时需要用到的角色类型(这个涉及到深拷贝和浅拷贝,可以自行了解)

我们只需在Unity内,在Project窗口下右键,即可看到创造战斗角色的选项,点击后就会生成一个新的文件:

创建后,我们将其命名为主角,然后我们便可以在Inspector上看到主角的数据:

Character Name 我们将其命名为主角(或你喜欢的名字)

Hp 我们将其设置为100

Atk 我们将其设置为15

Def Rate 我们将其设置为20,既无视20%的伤害

Duck Rate 我们将其设置为10,既10%概率可以闪避

还是同样的操作,Project下右键创建新的战斗角色数据,命名为小怪1,然后选择,进入Inspector面板,配置数据,

Character Name 我们将其命名为小怪1(或你喜欢的名字)

Hp 我们将其设置为70

Atk 我们将其设置为10

Def Rate 我们将其设置为10,既无视10%的伤害

Duck Rate 我们将其设置为3,既3%概率可以闪避

恭喜,角色类及其配置已经完成,接下来开始写战斗管理类的代码吧~

在这里,我们会运用到协程,来帮助我们完成回合制战斗,

结尾会提供战斗管理类完整代码

public CharacterData playerData; public CharacterData enemyData; private readonly WaitForSeconds _delaySeconds = new WaitForSeconds(0.3f);

第一个字段是我方角色配置,第二个字段是敌方角色配置,第三个字段是每个回合的间隔

因为我们将使用协程来实现回合制战斗,而WaitForSeconds是协程里等待固定时长的一个方法,既yield return new WaitForSeconds(1)就是等待一秒,但是这边每次等待都会new一个WaitForSeconds对象,会造成GC,所以我们直接定义一个等待0.3秒的WaitForSeconds对象,这样我们只需要yield return _delaySeconds即可,不会造成GC

如果不了解GC,可以了解一下,或者忽略上面提到的内容

接下来我们需要实现协程,只需要根据之前提到的思路设计即可,回合战斗的大致是这样的:

private IEnumerator Fight() { int round = 1; bool playerTurn = true; while (!player.Dead && !enemy.Dead) { Debug.Log($"第{round}回合开始"); round++; if (!player.Dead && !enemy.Dead) { yield return _delaySeconds; } } }

当玩家和敌方都没死亡时,持续循环战斗,如果双方都没阵亡的话每次回合间隔0.3秒(上方提到了如何等待的问题),这里round是回合数,playerTurn代表是否是玩家的回合(这里默认玩家先手),需要注意的是我们在这还没定义player和enemy,我们需要将配置数据转为角色数据

现在我们来实现循环内部的部分战斗逻辑,同时定义一下player和enemy,具体如下:

private IEnumerator Fight() { int round = 1; bool playerTurn = true; Character player = playerData.ToCharacter(); Character enemy = enemyData.ToCharacter(); while (!player.Dead && !enemy.Dead) { Debug.Log($"第{round}回合开始"); Character attacker = playerTurn ? player : enemy; Character opponent = playerTurn ? enemy : player; playerTurn = !playerTurn; Debug.Log($"{attacker.Name}对{opponent.Name}发起了进攻"); round++; if (!player.Dead && !enemy.Dead) { yield return _delaySeconds; } } }

这里我们定义了player是playerData数据的深拷贝,enemy是enemyData数据的深拷贝。

同时我们定义了attacker和opponent,既攻击方和被攻击方,且我们实现了切换攻击方。

这里对攻击方和被攻击方赋值是因为不这么做需要针对2种if情况写2次相同的代码,完全没必要这么做,所以直接对引用类型赋值即可

到这里,我们可以开始实现完整的战斗逻辑了,我们先实现判断闪避:

bool canDuck = Random.Range(0, 101) <= opponent.DuckRate; if (canDuck) { Debug.Log($"{opponent.Name}闪避了!"); } else { }

UnityEngine.Random.Range(0,101)的范围是0~100,既如果随机出来的0~100的数小于等于闪避几率,判定为闪避成功

int dmg = attacker.Atk * (100 - opponent.DefRate) / 100; opponent.Hp -= dmg; Debug.Log($"{attacker.Name}对{opponent.Name}造成了{dmg}点伤害,{opponent.Name}剩余{opponent.Hp}点血量!");

到这里,战斗逻辑就算完成了,我们改一下Log的样式,在Unity项目内创建FightMgr.cs,将以下代码覆盖,同时在Hierarchy中创建FightMgr的游戏物体,挂上FightMgr这个脚本,然后对PlayerData和EnemyData赋值(用之前创建的数据配置)

using UnityEngine; using System.Collections; public class FightMgr : MonoBehaviour { public CharacterData playerData; public CharacterData enemyData; private readonly WaitForSeconds _delaySeconds = new WaitForSeconds(0.3f); private void Start() { StartCoroutine(Fight()); } private IEnumerator Fight() { int round = 1; bool playerTurn = true; Character player = playerData.ToCharacter(); Character enemy = enemyData.ToCharacter(); while (!player.Dead && !enemy.Dead) { Debug.LogWarning($"第{round}回合开始"); Character attacker = playerTurn ? player : enemy; Character opponent = playerTurn ? enemy : player; playerTurn = !playerTurn; Debug.Log($"{attacker.Name}对{opponent.Name}发起了进攻"); bool canDuck = Random.Range(0, 101) <= opponent.DuckRate; if (canDuck) { Debug.LogError($"{opponent.Name}闪避了!"); } else { int dmg = attacker.Atk * (100 - opponent.DefRate) / 100; opponent.Hp -= dmg; Debug.LogError($"{attacker.Name}对{opponent.Name}造成了{dmg}点伤害,{opponent.Name}剩余{opponent.Hp}点血量!"); } round++; if (!player.Dead && !enemy.Dead) { yield return _delaySeconds; } } } }

可以看到,我们的回合制战斗实现的很成功,会进行回合之间的等待,会进行防御力计算,也有概率闪避,同时当有一方阵亡时,循环就会退出。

但是我们少写了一个部分,那就是战斗结果结算,现在我们补一下,将以下代码覆盖到FightMgr.cs即可:

using System; using UnityEngine; using System.Collections; using Random = UnityEngine.Random; public class FightMgr : MonoBehaviour { public CharacterData playerData; public CharacterData enemyData; private readonly WaitForSeconds _delaySeconds = new WaitForSeconds(0.3f); private void Start() { StartCoroutine(Fight(() => { Debug.Log("玩家胜利"); }, () => { Debug.Log("玩家失败"); })); } private IEnumerator Fight(Action sucCallback,Action lostCallback) { int round = 1; bool playerTurn = true; Character player = playerData.ToCharacter(); Character enemy = enemyData.ToCharacter(); while (!player.Dead && !enemy.Dead) { Debug.LogWarning($"第{round}回合开始"); Character attacker = playerTurn ? player : enemy; Character opponent = playerTurn ? enemy : player; playerTurn = !playerTurn; Debug.Log($"{attacker.Name}对{opponent.Name}发起了进攻"); bool canDuck = Random.Range(0, 101) <= opponent.DuckRate; if (canDuck) { Debug.LogError($"{opponent.Name}闪避了!"); } else { int dmg = attacker.Atk * (100 - opponent.DefRate) / 100; opponent.Hp -= dmg; Debug.LogError($"{attacker.Name}对{opponent.Name}造成了{dmg}点伤害,{opponent.Name}剩余{opponent.Hp}点血量!"); } round++; if (!player.Dead && !enemy.Dead) { yield return _delaySeconds; } else { if (player.Dead) { lostCallback?.Invoke(); } else { sucCallback?.Invoke(); } } } } }

在这里我们用2个Action完成了不同结局的回调,在调用的时候对回调事件传参即可

至此,1V1回合制战斗结束了!大家也可以尝试修改配置数据~

这里计算防御力就很简易,直接用百分比去控制,还可以用别的算法计算出一个基于防御力影响伤害的曲线(取曲线上某个点的值做伤害),这样计算出来的伤害会更有趣一点(因为每次都不一样),有兴趣的小伙伴可以自己搜搜伤害计算公式,然后套入目前计算伤害的地方

感谢阅读本篇文章,后续我还会写出1V多,和多V多的文章,以及更复杂的回合制战斗(武器、技能等),感谢大家的支持,如果觉得本篇文章有帮助,请给我点个赞,或给我个关注~

相关知识

Unity 3D 引擎构建虚拟现实游戏世界:设计理念与技术实现步骤
卡牌游戏战斗系统的设计和实现一
攻城掠地程武怎么通过 攻城掠地程武通过攻略
Unity游戏引擎
使用 Unity 开发游戏入门
Unity
Unity快速入门教程:手机游戏开发前的准备
【Unity】Unity游戏开发入门:新手必读的完整游戏开发指南,手把手教你从零开始到制作第一个游戏
unity 3d手机游戏开发(金玺曾)
Unity入门:如何使用Unity

网址: Unity通过协程实现回合制战斗(一、1v1部分) http://www.hyxgl.com/newsview331984.html

推荐资讯