上一节回顾
在上一章中,我提出了两个问题,一个就是为何要单独提出布局,一个是为何要不停的刷新布局。第一个,单独写出一个布局类是因为你会在很多地方去访问这个布局类里面的布局信息,其次统一在一个地方进行修改的话会方便很多,同时也能让人第一眼就看到你的技能编辑器就知道长啥样。第二个不停的刷新布局,是因为你的窗体可以被你去拖拽大小,拉伸,而在我的设计中那些窗体中各个功能的区域都是相对而言的,比如时间轴占了1/2f,那么当你拉伸的时候就会导致这个1/2f是上一次的1/2f,这样会导致表现奇怪,所以需要每帧去刷新。
窗体主类
我的技能编辑器是采用了在一个窗口内显示完全整个内容,所以只有上一节创建的SkillEditorWindow一个主类,但是我在代码使用了一个partial部分类,其实就是为了自己看的清晰点,如下图
其实这些类都是同一个类,只不过为了看的清晰点所以这样做了。有一个主要部分是用来启动其他的部分的逻辑,代码如下:
private void OnGUI()
{
styles.RefreshStyles(position);
DrawSkillActionsWindow();
if(cfgAsset != null)
{
DrawInspectorWidnow();
DrawHeaderWidnow();
DrawTrackWidnow();
ProcessSkillEditorEvent(Event.current);
ProcessActionsEevent(Event.current);
ProcessInspectorEevent(Event.current);
ProcessHeaderEevent(Event.current);
ProcessTrackEvent(Event.current);
}
}
选定一个主要部分,在里面写上OnGUI方法,此方法是编辑器下每一帧调用绘制的,从代码上可以看到我做了两件事情,1.绘制窗体,2,处理事件。
其实这些绘制窗体和处理事件就分发到各个部分类中,这样代码结构一下就清晰了,下面看一段某一个部分类中的代码片段。
private void DrawTrackWidnow()
{
GUILayout.BeginArea(styles.trackRect);
DrawTrackBackColor();
DrawTrack();
DrawTrackEvents();
DrawTrackMask();
DrawTimeLine();
DrawTimePointer();
GUILayout.EndArea();
}
private void ProcessTrackEvent(Event evt)
{
//处理轨道上的拖拽这些
foreach (var temp in allTracks)
{
foreach (var track in temp)
{
track.ProcessBodyEvent(evt);
}
}
ProcessTimeLineEvent(evt);
}
可以看到我在绘制代码中首先就是划定区域,使用了上节提到的GUILayout.BeginArea(Rect)和GUILayout.EndArea(),这样就绘制出了所占用的矩阵区域,然后在其中就是绘制轨道,绘制时间轴,绘制时间轴指针等等。然后处理事件函数就是去处理每个轨道上面的一些事件操作,这些具体细化接下来会慢慢讲,这部分大概的效果就是如下图:
其他的区域模块的大概写法也是按照我这样。
技能编辑器框架介绍
上面我已经讲了如何打开整个编辑器窗体的布局,相信能够认真思考并且实际实验写代码的小伙伴,现在也可以自由自在的划分区域了。现在就开始介绍整个技能编辑器的思路便于后续开始开发各个模块功能。
我整个技能编辑器是以状态机为处理,每一个角色就是一个自己的状态机,而每一个技能或者动作就是一个State,一个角色有无数的状态,我们去配置每个状态的表现就是完成了一个技能的配置。如下图:
每个状态(技能)应该包含什么
上面我们已经把逻辑其实拆分到State层了,那么现在是需要思考的是技能包含了哪些东西,而我的设计就是如下图:
动画轨道、声音轨道、特效轨道、相机轨道、打击轨道、消息轨道、子弹轨道。大家可以脑补一下这些轨道是否足以满足大部分的技能需求,技能伴随着动画,同时带有声音和特效,还可以有一些相机效果,比如放大招后处理,屏幕震动,碎屏,同时技能命中后带有效果,然后在技能期间可以被打断,可以处理其他的事情,最后技能可以扔出一些弹道效果,比如剑气,火球。
在这里我提出几个问题,
- 动画结束了,技能就结束了吗?
- 1个技能只有一个动画吗?
- 上述轨道是否真正已经满足了很多技能?
在下一节我会回答这些问题,并且会开始一个轨道一个轨道讲解绘制,以及运行时候的逻辑。