Unity3D开发实践:动作游戏《君临都市》案例剖析
文/周尚宣
本文选自机械工业出版社出版的《Unity3D动作游戏开发》一书的9.1节,略有改动。经出版社授权刊登于此。
君临都市是一款PS2末期推出的动作游戏,它沿袭了格斗游戏严谨的判定并以拳脚格斗作为其主要战斗模式。战斗中存在着大量的投技、拆投、组合技等,游戏中还设有部位破坏的独特概念,角色被分为上中下三段伤害区域,玩家不可一味地对其某一段进行攻击,从而增加战斗的策略性。本节将针对多人组合技能以及人形通用动作的设计来进行剖析。
通用动作方案设计
本作中设有60名敌人,包括不同的流派、体型、身高等,如第16关的空手道角色或女主角的功夫等。如此之多的角色动画是这一类游戏的典型问题之一,通常可以采用一套通用动画的多个不同形体的方式并借助通用骨骼去解决,即一个动画同时做瘦、中、胖三个版本,以匹配不同体型的敌人。在Unity引擎中,使用人形动画的功能可以解决这类需求。
继续观察本作会发现,一些流派使用的角色相对较少,且角色大都为中等体型,并且女性角色较少。所以进一步优化,在制作通用骨骼动画时对于使用固定流派的敌人可以做一套通用;而对于通用流派的敌人,建议依据身高、体型制作两套或以上通用动画即可。
组合攻击的再实现
本作中的组合攻击通常是指多个己方角色同时对敌人发动的特殊动画攻击,或者是依赖站位在特殊条件下主角一人对多人发动的特殊动画攻击,如图1所示。
一对三组合攻击示意图
这里以主角一对多组合攻击的情形进行脚本实现,这种情形的触发逻辑一般是当主角周边站有敌人时,以敌人的某种朝向、站姿的指定规则进行触发。考虑到其与技能系统还是有一些区别,并且较为依赖敌人朝向等信息,故这里单独作为一个模块制作。
先来看一下实现这个模块所需要的脚本结构关系,如图2所示。
组合攻击功能脚本的逻辑关系
在图2中,ComposeAttackController脚本中存放着不同的组合攻击类型,通过Update事件函数每帧更新当前可触发的组合攻击,并将信息存于索引字段中。上下文Compose- AttackContext结构的信息存放了组合攻击所需要角色的自身组件,如Animator、Transform等,可根据需求自行增加字段。TriggeredComposeSkill函数是在外部模块调用时触发并通过协程执行的。
(1)首先定义一些基础脚本。先来定义上下文结构,它包含了角色自身的一些信息。
- public struct ComposeAttackContext
- {
- public Transform CasterTransform { get; set; } //自身变换
- public Animator Animator { get; set; } //自身Animator组件
- }
-
- 随后编写ComposeAttackBase脚本,它定义了组合攻击的基本抽象行为。
-
- public abstract class ComposeAttackBase : ScriptableObject
- {
- public abstract bool CanTrigger(ComposeAttackContext context, bool
- prepareTrigger);
- public abstract IEnumerator Trigger(ComposeAttackContext context);
CanTrigger函数判断当前是否可以触发组合攻击;第二个参数prepareTrigger决定是否记录参数以准备触发组合攻击,例如在检测的同时记录下RaycastHit信息。第二个函数Trigger将进入触发逻辑。
(2)接下来编写ComposeAttackController脚本,用于处理组合攻击逻辑,是该模块的核心脚本。
- public class ComposeAttackController : MonoBehaviour
- {
- [SerializeField] Animator animator = null; //上下文所需接口,面板暴露参数
- //组件列表面板暴露参数
- [SerializeField] ComposeAttackBase[] composeAttackArray = null;
- //当前已触发的组合技能索引
- public int TriggerableComposeAttackIndex { get; private set; }
- //对外提供组合技能数组列表
- public ComposeAttackBase[] GetComposeAttackArray()
- {
- return composeAttackArray;
- }
- //每一帧更新组合技能是否触发逻辑,但可修改enabled关闭脚本更新
- public void Update()
- {
- var context = new ComposeAttackContext() { Animator = animator,
- CasterTransform = transform };
- for (int i = 0; i < composeAttackArray.Length; i++)
- {
- var item = composeAttackArray[i];
- if (item.CanTrigger(context, true)) //触发条件检测
- {
- TriggerableComposeAttackIndex = i;
- break;
- }
- }
- }
- public IEnumerator TriggeredComposeSkill(int index)//组合技能的触发接口
- {
- if (index > composeAttackArray.Length - 1) //索引越界报错
- throw new ArgumentOutOfRangeException();
- var context = new ComposeAttackContext() { Animator = animator,
- CasterTransform = transform };
- yield return composeAttackArray[index].Trigger(context);//执行触发
- }
- }
通常将该脚本挂载至角色自身。
(3)接着编写一个具体组合攻击脚本ComposeAttack1。若角色前后或左右都有敌人,就会触发该组合攻击。
- public class ComposeAttackController : MonoBehaviour
- {
- [SerializeField] Animator animator = null; //上下文所需接口,面板暴露参数
- //组件列表面板暴露参数
- [SerializeField] ComposeAttackBase[] composeAttackArray = null;
- //当前已触发的组合技能索引
- public int TriggerableComposeAttackIndex { get; private set; }
- //对外提供组合技能数组列表
- public ComposeAttackBase[] GetComposeAttackArray()
- {
- return composeAttackArray;
- }
- //每一帧更新组合技能是否触发逻辑,但可修改enabled关闭脚本更新
- public void Update()
- {
- var context = new ComposeAttackContext() { Animator = animator,
- CasterTransform = transform };
- for (int i = 0; i < composeAttackArray.Length; i++)
- {
- var item = composeAttackArray[i];
- if (item.CanTrigger(context, true)) //触发条件检测
- {
- TriggerableComposeAttackIndex = i;
- break;
- }
- }
- }
- public IEnumerator TriggeredComposeSkill(int index)//组合技能的触发接口
- {
- if (index > composeAttackArray.Length - 1) //索引越界报错
- throw new ArgumentOutOfRangeException();
- var context = new ComposeAttackContext() { Animator = animator,
- CasterTransform = transform };
- yield return composeAttackArray[index].Trigger(context);//执行触发
- }
- }
这里通过CheckBox接口检测四周是否有敌人,mIsForwardAndBackword变量存储是左右受敌状态还是前后受敌状态。Trigger函数中的处理这里较为简单,在实际项目中建议将具体技能逻辑置于其中。
(4)最后将其在Project面板中创建,并结合ComposeAttackController脚本将其挂载。当外部模块触发输入后调用触发接口以触发组合攻击,如图3所示。
组合攻击完成效果图
关注作者,阅读全文
c
还有50%的精彩内容,作者设置为仅对粉丝可见