技能系统

技能系统

技能的流程:从 能否释放对谁放 再到 生效结果。任何技能必将经历此流程

一,释放类型

  • 主动释放:满足释放前置条件,玩家主动按键释放。
  • 自动释放:满足释放前置条件,自动释放。
  • 被动释放:玩家在战斗中因达成某些事件而触发。

可配置性模板

角色模板(玩家和怪物)

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
using Sirenix.OdinInspector;
using System.Collections;
using System.Collections.Generic;

namespace Runtime.Logic
{
/// <summary>
/// 角色的逻辑配置类
/// </summary>
public class LogicActorData
{
[LabelText("角色ID")]
public int actorID;
[LabelText("逻辑动画组")]
public Dictionary<string, LogicClipData> stateClipDatas = new Dictionary<string, LogicClipData>();// 所有逻辑动画
}

/// <summary>
/// 角色逻辑动画配置
/// </summary>
public class LogicClipData
{
[LabelText("动画名称")]
public string name;
[LabelText("动画长度")]
public int renderFrames;
[LabelText("总长度")]
public int totalFrames;// 总长度
[LabelText("循环")]
public bool isLoop;// 是否循环
[LabelText("关键帧")]
public Dictionary<int, LogicFrameData> keyFrames = new Dictionary<int, LogicFrameData>();// 关键帧

public List<SpilitData> renderSpilitDatas = null;// 缩放控制
}

/// <summary>
/// 存缩放数据
/// </summary>
public class SpilitData
{
[LabelText("起始帧")]
public int startFrame;
[LabelText("结束帧")]
public int endFrame;
[LabelText("长度")]
public int length;
}

/// <summary>
/// 逻辑关键帧数据
/// </summary>
public class LogicFrameData
{
[LabelText("帧号")]
public int frameIndex;// 帧号
[LabelText("脚本列表")]
public List<LogicScriptData> scriptDatas = new List<LogicScriptData>();// 脚本
}
}

locomotion: idle - 走 - 跑 这三个被称为locomotion 是一个角色的基础动作组。BlendTree

动力学和动画模块分离

LogicActor中负责处理输入的组件,处理后内容交给动力学组件,最后交给ViewActor的Animato组件

  • 按键模块:接收按键输入(SynCmd),交给LogicActor去执行指令,同时缓存起来
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
using System.Collections;
using System.Collections.Generic;

namespace Runtime.Logic
{
/// <summary>
/// 记录按键缓存的信息类
/// </summary>
public class KeyCacheInfo
{
public int keyValue;// 键值
public bool isDown;// 当前是否按下
public int lastDownFrame;// 最近一次按下的帧号
}

public class LKeyComponent : LComponentBase
{
private Dictionary<int, KeyCacheInfo> m_cacheInfos = new Dictionary<int, KeyCacheInfo>();

public void InputKey(int key, int[] args)
{
// 缓存记录
CacheKey(key, args);
// 交给角色逻辑去执行
m_actor.ExecVkey(key, args);
}

/// <summary>
/// 缓存按键按下或弹起相关的信息
/// </summary>
/// <param name="key"></param>
/// <param name="args"></param>
private void CacheKey(int key, int[] args)
{
bool isDown = key < VkeyDefine.UP_DELTA;
int keyValue = isDown ? key : key - VkeyDefine.UP_DELTA;

if (!m_cacheInfos.TryGetValue(keyValue, out var cacheInfo))
{
cacheInfo = new KeyCacheInfo();
cacheInfo.keyValue = keyValue;
m_cacheInfos.Add(key, cacheInfo);
}
cacheInfo.isDown = isDown;
if (isDown)
cacheInfo.lastDownFrame = LTime.frameCount;
}

/// <summary>
/// 判断按键是否按下
/// </summary>
/// <param name="key">键值</param>
/// <param name="recordFrames">接收之前按下的帧数</param>
/// <returns></returns>
public bool IsVkeyDown(int key, int recordFrames = 0)
{
if (m_cacheInfos.TryGetValue(key, out var cacheInfo))
{
if (recordFrames <= 0)
return cacheInfo.isDown;
else
return cacheInfo.isDown || (LTime.frameCount - cacheInfo.lastDownFrame <= recordFrames);
}
return false;
}

/// <summary>
/// 获取按键按下的帧号
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public int GetKeyDownFrame(int key)
{
if (m_cacheInfos.TryGetValue(key, out var cacheInfo))
return cacheInfo.lastDownFrame;
return -1;
}

}

}

  • 接收输入后,LogicAction执行状态切换,LogicAnimator播放逻辑动画,
1
2
3
4
5
6
internal virtual void SetCurrentState(int state)
{
m_state = state;
m_actor.aniCom.Play(MotionDefine.GetMotionNameByID(m_actionID * 100 + m_state));
}

GamePlay常见的几个业务

  • 穿墙问题
    • 逻辑的位置到了墙,就应该停下,同时有输入动画还是会播,那显示层墙怎么办呢?
    • 为了看起来正常,配置定义逻辑层墙的时候的位置,要宽于显示层墙的位置
  • 边缘检测,逻辑层算好位置就可以了,位置都是由逻辑层处理,表现层只是负责表现,所以不会出现抖得问题
  • 跳跃1:原地跳
  • 跳跃2:冲刺跳
  • 爬梯子
  • 爬墙壁

状态管理

输入的处理

  • 玩家键鼠
  • AI行为树
  • 网络

输入分为几个类型

  • 普通事件:封装成帧命令,逻辑处理,再表现,而不是输入直接操作动画,操作一定要做一层抽象;
  • 状态转换型输入:不同状态下,针对相同的输入指令,在逻辑状态转换中做好处理,
  • 输入的时效:前摇,执行中,后摇
  • 按键指令的重排序:???

动作细节

  • 攻击中的顿帧: 对应的LogicActor的 Tick里 跳过要停顿的帧数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
internal void Tick()
{
if (IsPaused)
return;

// 顿帧逻辑
if (m_freezeTime != 0)
{
if (m_freezeTime > LTime.time)
return;
else
m_freezeTime = 0;
}

m_curAction.Tick();
aniCom.Tick();
scriptCom.Tick();
moveCom.Tick();
}