基本格斗系统设计
人物状态
Stand:包括 Walk Run Idle
Jump
Crouch
Block:Stand_Block Crouch_Block
判定逻辑:Stand: IsRight?Walk.x>0:Walk.x<0
0无法防御的投技
1必须下防
2站立防御和下防
Hurt:等级一: Stand_Hurt Jump_Hurt Crouch_Hurt
等级二:HeavyCombat
等级三:Blow_Hurt
受伤等级根据受到的攻击优先级决定
场景的空间感
- 由3D空间内不同位置摆放不同层次的2D面片构成
- 后处理景深
- 2dSprite分层,进行视差滚动
- 摄像机设为透视投影。
招式种类与数据分析
个人归纳为以下几种:
基本招式:如升龙拳:一套动作分为前摇,判定,后摇三部分组成
分为单指令和多指令
序列招式:例如拳皇里的相扑人(J,J,J,J,J) 由前摇,判定,前摇,判定,前摇,判定,后摇组成。
这里面也可分为循环和非循环两种,总之就是一套动作涵多个判定的招数
更麻烦的就是一个序列种不同受击帧的有利帧还不同。
其他如必杀技和投技:这种就强制动画执行了,IK什么的,我对此表示摸棱两可。也许就是代码严格控制位移等或者用TimeLine这种预先处理好的序列的把
其分别用int枚举 0(单指令基本招式),1(多指令招式),2(派生招式),3(循环序列)4(projectile),5(必杀)表示
招式之间的衔接通常为“Cancel”,派生招式可以取消被派生招式的后摇(前摇,判定–前摇,判定,后摇),其与序列不同的是,序列是一次输入产生的,而派生招式是在被派生招式的基础上搓出指定指令才可释放的招数。某个招式可以被哪些招数取消是要考虑的问题
更多的可以的细节看街霸6官网的帧数表
招式格挡属性:

对此我分别用3,2,1,0(P也用3表示)
招式数据
以Json方式配置, 通过LitJson 进行序列化反序列化
人物
组件模式
实现有限状态机(利用反射)
严格的帧判定
基于unitask的异步开发
碰撞检测
碰撞盒分为
- 位置碰撞盒:1个,范围人物全身,防止双方位置穿模。
Collider Layer:Player - 攻击碰撞盒:1个,范围因攻击动画而变。
Trigger Layer:PlayerXHit(**‘X’代表1或2,指的是1玩家和2玩家,后文一同**) - 受击碰撞盒:6个,分别是头1,左右手各1,身1,左右大腿各1。
Trigger,Layer:PlayerXHurt
Layer碰撞关系设置
玩家自身的碰撞盒不会相互影响,1,2玩家的受击碰撞盒也不会相互影响,位置碰撞盒不与攻/受击碰撞盒互相影响
因此下列层级对不会检测碰撞:
- Player1Hurt :Player2Hurt
- Player:Player1Hit:Player1Hurt
- Player:Player2Hit:Player2Hurt
在很长一段开发时间里,我一直在用设置
hitbox.enabled的方式来控制开启碰撞检测,由于过于频繁的开启和关闭,致使检测的效果很糟糕,问题在哪而不自知,结果后知后觉瞥了一眼,立马发现自己是个猪鼻🐷。
搓招
搓招功能设计:
方法一:输入缓存记录100帧内的输入,从当前输入开始,由后向前遍历过去的限定数量的输入
方法二:从前向后
方法一更好
方法二缺点:
现有两搓招指令分别为
- S,D,J
- D,A,J
如果此时我正输入s键想释放招数一,但突然想改为二连招,那么我的输入顺序为 S,D,A,J,
如果为方法二,检测时的逻辑为:
- S:进入连招一的指令输入状态
- D:持续连招一的指令输入状态
- A:退出连招一的指令输入状态,重新寻找招式
- J:无反应
方法一逻辑:
- J:进入连招一/连招二的指令输入状态
- A:退出连招一的指令输入状态
- D:与招式二配对
- S:检测有无更高优先级的招式配对
显然方法一更好
实现了个动作状态机,用来管理搓招和连击,及其动画和碰撞检测
- 有利帧不利帧
搓招出招表用json文件配置,序列化对象 用数字表示输入
连招可以再搓招的ComboData类中写明他的连招是什么,
输入
按键输入参考街霸6(此处讨论的是搓招输入处理,而非移动输入处理)
方向为八个方向有:上,下,左,右,右上,右下,左上,左下 对应按键都明白
键盘需要优化按键
只在Keyup KeyDown两种情况下有指令输入,指令输入判断先后关系为为:复合型攻击输入>复合型方向键输入>攻击键输入>方向键输入
复合型输入指 WA SD DJ这种前键Hold 后键Down/Up类的输入。
人物的移动输入和动作输入分开,
输入按键之间的时间间隔控制着搓招输入的难度
可以适当的放松指令判定的严格度
无输入是指令0,而0的指令是不会引起搓招检测的,但对于指令0的判定想法很多
移动键和攻击键的Hold输入是不是一种0的情况,有待商榷了。
若有蓄力输入的情况:两者都不能为0;
若要降低搓招难度:两者可以为0,此时搓招难度就只取决于在输入间隔以内玩家有没有输入指定的招式指令,而不是严格的每帧输入检测(尽管代码还是每帧检测,但逻辑上可以归纳),也就是说前者输入难度秒级,后者输入难度是帧级。
按键指令表:
| 按键 | 指令 |
|---|---|
| W(上) | 8 |
| D(前) | 6 |
| S(下) | 2 |
| A(后) | 4 |
| J(轻拳) | 11 |
| L(中拳) | 12 |
| D+J(重拳) | 17 |
| A/D+L(里投) | 16/18 |
| K(轻脚) | 21 |
| U(重脚) | 22 |
| A/D+U(里投) | 26/28 |
| WA(左上) | 7 |
| WD(右上) | 9 |
| SD(右下) | 3 |
| AS(左下) | 1 |
| 无输入/按住攻击输入键 | 0 |
| … | … |
输入缓存取消机制:
- 输入间隔超过限制时间
- 释放招式后
- 被攻击
受击反馈
- 受击动画
- 特效/音效
- 顿帧
- 屏幕震动
- 位移
顺序:受击动画 特效 屏幕震动 同步
异步: 顿帧 结束后 位移
同时不同招数在不同受击类型下的反馈也应是不同的;
姑且分为
- 挥空 无反馈
- 有效攻击/是否板边/是否空中
- 无效攻击/是否板边(被格挡)/是否空中
参考隆
| 招式 | 有效攻击非板边 | 有效攻击板边 | 无效攻击非板边 | 无效攻击板边 |
|---|---|---|---|---|
| 站轻拳 | 无 | 后退 | 无反应 | 后退 |
| 站中拳 | 无 | 后退 | 无反应 | 后退 |
| 站重拳 | 前进 | 后退 | 无反应 | 后退 |
人物受击时的位移也该由受击动作和当前状态共同决定
预输入
从攻击开始到攻击结束是预输入时间段,在这段时间内,仍会检测玩家指令,并保存优先级最高的动作,在这段时间结束后,若攻击挥空或者受伤,则无事发生,若未挥空且预输入时间段内有动作保存,则在攻击后摇阶段释放该动作(如果两动作之间存在取消/派生关系,则按取消关系来处理)。
挥空判定逻辑:每次出招时,设定挥空=true;
HitBox触发:设定挥空=false
若AB两角色互相攻击 A先接收消息
A HitEvent A.挥空false B HurtEvent B.挥空=true B.State=Hurt
B HitEvent Hurtstate B.挥空 true A hurtEvent 挥空=false;
残留指令
残留指令是指玩家每次释放招式后将不会清空输入缓存,留到下次搓招以简化出招。但可能会出现串招的情况。
派生攻击处理
派生攻击:招式A有派生攻击B,招式A释放后,若在A结束之前搓出派生动作的指令,即可取消A的未执行帧,在A攻击检测帧之后马上释放派生攻击B。
释放派生攻击过程中,只检测派生动作的指令,由于其取消后摇的高有利,所以挥空或中断不利也会很高。
派生攻击一般是无法直接释放的。
攻击取消
取消含义就是取消攻击后摇,其逻辑和派生攻击相像,若招式A可被B取消,其含义为:释放A后,未挥空,若能在A后摇某一帧之前搓出招式B即可取消A的后摇立即释放招式B
还有一种更为严格的取消关系是:在A招式受击确认时检测是否搓出B,即在A成功攻击前搓出B才能达成取消关系,
街霸六采取的是后者方案,这里我采取的是前者。
逻辑层和动画表现层的尽量统一
根动画
Playerables API控制动画
动画速率与游戏逻辑帧并不一致,他取决于动画采样率 Sample Frame Rate 可以设置,因此游戏锁帧60,动画采样率也设置为60帧。
这里也可以完全依托于动画来进行开发,位移上应用根动画,格斗事件在动画帧事件上触发,前提是动画资源极度丰富
顿帧
就是设定一段帧时间,攻击方和受击方在这段帧内动画速度降低。
我对此也有异议:
此次顿帧可以设置与动作帧同时进行,反之也可以
举个例子:动作帧1-10,顿帧 J-L 在5帧时触发顿帧
方法前者即1,2,3,4,(5|j),(6|k),(7|l),8,9,10 缺点是 导致后续动画在后摇帧内无法播放完全,需要计算后续动画播放速度,当然也可以不管,只要动画表现不突兀即可。
后者 1,2,3,4,5,j,k,l,6,7,8,9,10 缺点是 影响了招式的帧数,所以弃用
动作事件
- 发招时
- 攻击触发时
- 攻击结束时
- 攻击取消时。
- 前摇帧的一段时间里
- 后摇帧的一段时间里
摄像机的跟随与变化
实现了一个摄像机脚本
AI行为树
命令模式的命令流+NodeCanvas行为树 效果很差劲,得再思考。