前段时间比较忙,囫囵吞枣学了个大概,作业写的也很烂,趁最近有空整理一下,认真写一次打飞碟游戏(主要是总结一下导演场记动作管理这些面向对象设计)
放个UML图
1.SSDirection,导演对象负责
- 获取当前游戏的场景
- 控制场景运行、切换、入栈与出栈
- 暂停、恢复、退出
- 管理游戏全局状态
- 设定游戏的配置
- 设定游戏全局视图
因为导演只能有一个,所以一般搭配着单例模式使用
现在来分析一下导演负责的内容:
导演需要控制场景,和下面要提到的场记“XXSceneController”交互,所以需要定义一个接口:
public interface ISceneController
{
void StartGame();
void Restart();
void Pause();
void Resume();
}
导演真正需要做的事情很少,只是起到一个调度的作用,所以代码也很短,在其他地方如果需要用到导演,直接利用getInstance()即可
2.SceneController,场景控制器(场记)
场记的职责:
- 管理本次场景所有的游戏对象
- 协调游戏对象(预制件级别)之间的通讯
- 响应外部输入事件
- 管理本场次的规则(裁判)
杂务
如果用这次打飞碟游戏进行说明,那就是需要在SceneController.cs中实现:
运动模式切换
- 加载资源
- 具体实现上面接口定义的暂停、恢复、开始、重启等操作
- 接收用户点击,并作出相应反应(回收飞碟、加分)
- 进入到下一轮游戏
其中,像飞碟的回收、分数的增加这些,还涉及到其他的类,这时候需要在相应的类代码中实例化场景控制器,通过这个唯一的场景控制器来进行相应的操作。
3.UserGUI,人机交互部分
也就是MVC模式中的V,把后台的数据显示给User,接收User的指令传给后台。
这个就比较简单,但是如果需要考虑到用户习惯之类还是值得好好设计的。这一次我仅仅实现了几个简单的按钮,start开始游戏,restart重新开始游戏,nextRound进入下一关。
在UserGUI中,也可以利用和场景控制器交互的接口,然后在类中声明一个SceneController变量,就可以调用StartGame(),reStartGame()这些函数,比如
if (GUI.Button(new Rect(width + 120, height, 60, 30), "Restart"))
{
action.Restart();
}
if (GUI.Button(new Rect(width + 200, height, 80, 30), "Change"))
{
action.ChangeActionManager();
action.Restart();
}
4.SSAction,动作基类
(这里直接照搬课件)
所有的动作都应该继承自SSAction。其中,ISSActionCallback指的是
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Compeleted,
int intParam = 0,
string strParam = null,
Object objectParam = null);
}
5.SSActionManager,动作管理基类
(这里直接照搬课件)
说明注释写得很清楚了。
6.具体的某个Action
//CCMoveToAction.cs
void Start(){
//确定起止位置、方向
}
void Update(){
//每一帧通过position使得飞碟看起来在运动
}
public static CCMoveToAction GetSSAction(float speed)
{
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.Speed = speed;
return action;
}
7.具体的某个ActionManager
void Awake()
{
//加载场景控制器和factory,利用单例模式
}
public CCMoveToAction MoveToAction(GameObject obj, float speed)
{
CCMoveToAction action = CCMoveToAction.GetSSAction(speed);
base.RunAction(obj, action, this);
return action;
}
public void moveDisk()
{
GameObject diskObj = factory.getDiskCountObject(factory.getDiskCount(sceneController.round));
this.MoveToAction(diskObj, sceneController.getSpeed());
}
说明一下MVC框架的应用。
比如用户点击“Start”按钮,UserGUI接收到用户点击事件,调用interface ISceneController中的用户接口函数void StartGame();这个函数具体在FirstSceneController中实现。场记受到导演控制,导演会确定此时应该是哪一个场记负责这个动作,因此在UserGUI中,需要SSDirector.getInstance().currentSceneController as ISceneController;然后再action.StartGame()即可完成游戏的”开始”操作。
再说一下动作管理器部分。
定义CCMoveToAction.cs,在Start()中确定飞碟出现和消失的位置、运动方向;在Update中实现每一帧更新飞碟位置,写一个函数GetSSAction()实例化一个CCMoveToAction,使得Start和Update被调用;然后在CCActionManager.cs中,实现MoveToAction函数,函数生成一个CCMoveToActions实例,然后利用这个实例调用GetSSAction,再把这个action实例作为参数传到父类SSActionManager中,利用base.RunAction真正串起整个调用过程。
至于Adapter模式。
一开始觉得很复杂,但是仔细研究一下也挺简单的。其实就是判断此时Action是CC还是Physics,然后分别调用不同的ActionManager就可以了,两个的Manager代码很相似,只是物理运动需要加上刚体处理的部分。PhysicsMoveToAction.cs中,基本思想和CC是一样的,不同的是物理运动不需要手工计算每一帧的位置,而只需要给他施加一个力就可以了,具体看代码实现即可,没有什么技术难度。
工厂模式部分。
第一次作业写的其实有一点问题,实际应该做到我使用了某一个飞碟,然后它就需要被添加到待使用的队伍中。因此需要给每一个飞碟确定不同的ID,这里我选择给每一个飞碟以不同的名字,其实就是Disk加上序号,然后用这个ID在队列中寻找相应的飞碟,进行释放或者再利用。
这次作业完成后大概算是真的了解这整个结构了吧,就游戏实现来说还是有一些小bug,比如因为刚体所以需要FixedUpdate但是切换之后CCMoveTo却不需要,如果使用会有一种卡顿的感觉,所以在SSActionManager中就要写两次,不知道这样是不是正确的。有时候还会出现诡异的短暂停留现象,不知道是因为卡机还是Update调用顺序问题。
偶尔会出现,如果没有点击飞碟,让它自由坠落到“地面”,飞碟不会消失的诡异情况,但并不是所有时候都会出现,所以不知道怎么解决。
没有用上个星期判断“落地”的代码,因为发现这个“在Camera”范围内的判断其实是有一点问题的,它除了判断角度之外,还有距离的限制,所以有时候明明还在视线范围内却已经无法点击,但是也不太好改,所以直接利用飞碟y坐标来判断是否落地了。但是具体数字的选择就很纠结,试了好几个,只能尽量找一个出错少的数字,没有一个科学的计算,感觉不太对。
除此之外还有一开始做作业时遇到的那些问题,飞碟位置随机参数的设定、飞行方向、施加的作用力这些,都没有什么依据,都是试着觉得可以了就用这个,也不知道什么时候会触发“不可以”的状态。
最后说一点感想,以前写cocos2d虽然也用到了动作管理,但是感觉没有unity3d这么麻烦,同样是CCActionManager类,直接addAction,removAction,pauseTarget……当CCNode执行runAction时,会把动作通过动作管理类的addAction函数将对象传递给CCActionManager的单例,该实例再把这个动作添加到自己的动作序列中。导演只需要->getActionManager()->pauseTarget(Node)就可以。具体的语法记得也不是很清楚了,印象中不需要自己写这么多类来进行动作的执行的操作(大概是因为直接封装好了)
查了一下资料感觉业内写游戏也并不苛求MVC框架,游戏对象之间大量的交互会让C的工作变得很繁重。当然,一定程度的代码分离是有必要的,在没有学会更有效的框架之前还是老老实实MVC吧,虽然我个人觉得这样debug真的很麻烦,特别是脚本还不能next step进行调试,在好几个类之间跳来跳去真的随时失去耐心。
游戏截图就不放了,和上次没什么区别。上次的博客链接。