你说得对,但是EmiteInnaACTSystemFramework是一款由EmiteInna自主研发,适用于PC平台的Unity第三人称上帝视角3D动作游戏代码框架。您将在游戏里扮演一个名叫游戏程序的角色,通过跳跃,冲刺和各种各样的丝滑小连招击败你的对手,修复程序中的bug,找回失散多年的代码能力,发掘未知的设计模式和模拟算法,揭开游戏行业无法入行、毕业即失业的真相。

本篇是ACT系列之一,github仓库位于:https://github.com/EmiteInna/EmiteInnaActSystem

简述

为了增加一下这个自称动作游戏框架的动作游戏手感,增加一下角色的操作量和流畅度,一个复杂度更高一点的3C是必不可少的,所以拿了一个以前设计的动作游戏做内容,目前而言它的基础操作是这样的。

图片

这涉及到一个更复杂的状态机,但由于原本的分层状态机机制,实际上要添加的状态只有地面和空中的防御状态。然后这里面涉及到一个双击的操作。

对于走路和奔跑的双击,我们可以理出一个逻辑:当按下某个方向键之后进行计时,下次按下这个键如果和上次在一定间隔之内,就进入奔跑状态,而进入奔跑状态之后就不需要判这个东西了。但是如果中间按了其他的方向键就会重置别的三个方向键。

而对于防御状态切冲刺的情况,因为防御是一个瞬时的过程,所以只需要在状态机回复到Idle状态之前有相关的操作就可以直接执行函数过程。

值得一提的坑是有些判断是在fixedUpdate里进行的,根据unity的机制有可能会在一个fixedupdate帧里转移状态,又update帧里判到了目标状态的输入指令,导致按一下就可以读入两次输入的后果。解决方法是尽量把所有按键操作都在update里更新,并且尽量不要用GetKey来写连招逻辑。

当然还有另一个方法,在状态设置一个frameCount,记录进入状态之后的帧数,在几帧之后开始判连招,因为人类应该还是很难把两次按键加速到几帧之内吧。

新的WalkState代码如下

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Searcher.SearcherWindow.Alignment;

namespace EmiteInnaACT
{
/// <summary>
/// 玩家走路状态,只要停止移动就会脱离这个状态,可以进入冲刺状态。
/// </summary>
public class PlayerWalkState : PlayerGroundNormalState
{
float lastW;
float lastA;
float lastS;
float lastD;
public override void OnEnterState()
{

base.OnEnterState();
//lastW = -100;
//lastA = -100;
//lastS = -100;
//lastD = -100;
controller.PlayAnimation(controller.configure.WalkAnimation,0.2f);
if (ConfigureInstance.GetValue<EmiteInnaBool>("uniform", "StateMachingDebug").Value)
Debug.Log(controller.name + " 进入Walk状态");
//TODO:播放动画

//if (Input.GetKeyDown(controller.configure.HorizontalLeft))
// UpdateKeyCheck(controller.configure.HorizontalLeft);
//if (Input.GetKeyDown(controller.configure.HorizontalRight))
// UpdateKeyCheck(controller.configure.HorizontalRight);
//if (Input.GetKeyDown(controller.configure.VerticalDown))
// UpdateKeyCheck(controller.configure.VerticalDown);
//if (Input.GetKeyDown(controller.configure.VerticalUp))
// UpdateKeyCheck(controller.configure.VerticalUp);

}
/// <summary>
/// 检验是否进入冲刺状态的东西,逻辑很简单,按下两次键的时间间隔够近,按下别的方向键的话就重置其它的时间。
/// </summary>
/// <param name="k"></param>
public bool UpdateKeyCheck(KeyCode k)
{
float rc = 0.2f;
CharacterConfigureBase c = controller.configure;
if (k == c.VerticalDown)
{
if (Time.realtimeSinceStartup - lastS <= rc)
{
controller.EnterState("Sprint");
return true;
}
lastW = -100;
lastS = Time.realtimeSinceStartup;
lastA = -100;
lastD = -100;
}else if (k == c.VerticalUp)
{
if (Time.realtimeSinceStartup - lastW <= rc)
{
controller.EnterState("Sprint");
return true;
}
lastS = -100;
lastW = Time.realtimeSinceStartup;
lastA = -100;
lastD = -100;
}
else if (k == c.HorizontalLeft)
{
if (Time.realtimeSinceStartup - lastA <= rc)
{
controller.EnterState("Sprint");
return true;
}
lastS = -100;
lastA = Time.realtimeSinceStartup;
lastW = -100;
lastD = -100;
}
else if (k == c.HorizontalRight)
{
if (Time.realtimeSinceStartup - lastD <= rc)
{
controller.EnterState("Sprint");
return true;
}
lastS = -100;
lastD = Time.realtimeSinceStartup;
lastA = -100;
lastW = -100;
}
return false;
}
/// <summary>
/// 判断是否按着移动,如果没有的话回到Idle,如果按了双击进入冲刺状态。
/// 跳跃同理。
/// 在状态转换的之后一定要直接return掉,避免不必要的错误。
/// </summary>
public override void OnFixedUpdateState()
{
base.OnFixedUpdateState();
CharacterConfigureBase c = controller.configure;

float vertical = 0;
float horizontal = 0;
if (Input.GetKey(controller.configure.VerticalUp)) vertical++;
if (Input.GetKey(controller.configure.VerticalDown)) vertical--;
if (Input.GetKey(controller.configure.HorizontalRight)) horizontal++;
if (Input.GetKey(controller.configure.HorizontalLeft)) horizontal--;
if (Mathf.Abs(vertical) > 0.8f || Mathf.Abs(horizontal) > 0.8f) {

Vector3 dir = new Vector3(horizontal, 0, vertical);
controller.CharacterMove(dir, c.MovementSpeed, c.RotationSpeed);
}
else
{
controller.EnterState("Idle");
}

}
public override void OnUpdateState()
{
base.OnUpdateState();
if (controller.currentState != this) return;
if (Input.GetKeyDown(controller.configure.HorizontalLeft))
{
if (UpdateKeyCheck(controller.configure.HorizontalLeft))
{
return;
}
}
if (Input.GetKeyDown(controller.configure.HorizontalRight))
{
if (UpdateKeyCheck(controller.configure.HorizontalRight))
{
return;
}
}
if (Input.GetKeyDown(controller.configure.VerticalUp))
{
if (UpdateKeyCheck(controller.configure.VerticalUp))
{
return;
}
}
if (Input.GetKeyDown(controller.configure.VerticalDown))
{
if (UpdateKeyCheck(controller.configure.VerticalDown))
{
return;
}
}
}
}
}

冲刺和长跳的残影

关于残影,很多人其实已经做过了,这边就只是记录一下自己的实现方法吧。

在角色的View层里可以找到模型,模型里会有一个物体包含SkinnedMeshRenderer组件,这个组件的Bake函数可以输出当前动画帧下的mesh。

然后写一个像模像样的幻影Shader,我一般写shader喜欢先在shaderGraph里连一遍,然后再写代码优化,这里就直接连了一下,一个菲涅尔半透明的外壳,配上略微的自发光,中间混一点屏幕空间采样杂质进去,有种神君的感觉(bushi

图片

生成的残影只是一个简单的物体,挂着这个材质,在触发的时候换上bake出来的mesh,在代码里控制材质的透明度来实现幻影的显现和消失,这里用对象池优化是比较必要的,因为幻影是一个非常频繁触发的特效。

大概效果如下(无视瞎搞的角色shader

图片

图片

当然,冲刺的手感除了残影以外,和音效、特效、动作也有很大关系,光有一个残影是远远不够的。

这个过程有一个残念的地方,那就是很显然残影的颜色如果色相和角色相差太多的话这个残影是不好看的,非常突兀。但是在C#中给Color好像没法直接给HDR,除非挂上特性放在Inspector里给,所以要想在输入的时候加上幻影颜色可能比较困难,只能在shader里挂一个算法简单搞搞了。

动作游戏移动操作设计思路

不是策划,只是浅谈。

玩过的动作游戏还是不少的,其中平台跳跃类的巨多(3D平台跳跃也算平台跳跃),ARPG较少,主打喜欢一个操作的感觉。

平台跳跃里主角至少要能通过基本的操作去把一些能够做到的东西都做到,比如基本的移动,为了移动不那么无聊,显得可以操作,我们需要奔跑功能,然后是跳跃,很多人玩游戏都喜欢一直跳(?),所以这些毋庸置疑是最基本的。

然后是无敌帧或者防御技能,因为在动作游戏里让所有玩家都能剁掉弹幕和敌人的攻击是不太可能的,所以至少还是要有一个或者几个负责提高容错率的技能来让玩家可以操作一下。

再次基础上,玩家需要一些有冲击力的技能,比如冲刺、重击、旋风等等,这些是动作游戏手感的重要来源,需要操作按出来的东西也能提升游戏操作的深度,而这些里面又有一些是进攻型的,有一些是防守型的,有些是可攻可守的。最好的例子就是空中冲刺,我一直认为它是ACT里最优秀的设计之一,玩家可以靠空中冲刺来通过原本不能通过的地形,在空中冲刺的过程中角色不受重力影响,这会增加玩家滞空的时间从而完成一些操作,同时,空中冲刺可以用于接近一些陆地上原本难以接近的敌人,这些都是很重要的游戏性提升,而在这个基础上,空中冲刺甚至不会增加玩家需要按的按键。

按键同样是一个重要的东西,玩家总是喜欢通过10个键左右就能完成所有操作,并且用这些键的组合完成更炫酷的操作,如果键太多的话就显得有点烧脑了,手感也不一定好(对动作玩家来说手感一般,对RTS玩家来说恰到好处!),更重要的一点是动作游戏的重要受众是手柄玩家,按键一多的话手柄就有点顶不住。

当然,ACT游戏3C的手感在另外一大重要的部分是战斗方面,这方面的设计在之后才会制作。

后续

接下来会做一做UI系统和继续更新技能编辑器,完成整个角色控制面板。

本来是不想把UI做进框架的,但后面想了想随机应变写的UI大概率出锅,所以还是写写吧,不过优化什么的后面再说。

角色的移动逻辑虽然是做进来了,但是和ALS等等这种动画TA大哥才能掌握的各种状态设计对比下来是远远不够的,到最后总还是要更新。

那为什么不现在就学呢?

还是那句话,喜欢的事情不一定就是想做的事情。

有时候技术和爱好、表现效果带来的反馈对游戏开发者来说都是非常重要的事情。

但有时候,产能才更加重要。