我们对Unity3D-魔鬼与牧师游戏开发-优快云博客 进行进一步改进。
目录
一、动作管理器
1、为什么要引入动作管理器
在一个场景中肯定有很多“会动”的物体,它们的运动是有很多共性的,如果我们为游戏角色实现一个运动方法,为船实现一个运动方法,为将来出现的所有会动的物体都实现一个运动方法,势必是一种资源的浪费。我们可以将运动的共性提取出来,用一个管理器统一管理,这样,代码的复用性和可读性都会提高。
2、什么是动作管理器
- 动作管理器就是一个对象,管理整个场景中所有的动作。
- 一个SceneController(场景管理器)只配备一个动作管理器对象。
- 不管是游戏角色的移动还是船的移动,都归这个对象管;
- 动作管理器可以添加动作(添加的时候要指定动作所作用的GameObject),监测已经完成的动作并清除。
二、实现思路
- 设计一个抽象类作为游戏对象动作的基类
- 设计一个动作管理器类管理一组游戏动作的实现类
- 通过回调,实现动作完成时的通知
- 设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束
将移动动作的执行从每一个游戏对象中提取出来,建立一个动作管理器来管理移动方法,游戏对象通过将目标位置传给动作管理器,让动作管理器来移动游戏对象
将游戏的判定机制抽象成一个类,通过调用该类的方法来判定游戏是否结束
这使得:
- 程序的解耦合程度提高
- 更多对象可以复用
- 程序更容易维护
三、动作分离
在上次实验中,游戏对象的移动由MoveController和Move共同管理,这样做的坏处是在FirstController中仍有管理对象运动的代码。在动作分离版的代码中,管理动作的代码被分解成以下三部分,CCActionManager用于管理所有动作,CCMoveAction用于管理“移动”这种动作,Move则是移动这个动作的实体。
1、 CCActionManager
主要实现了MoveRole()和MoveBoat()两个函数。通过调用CCMoveAction中的MoveTo和MoveSeqcenceTo来实现两种不同形式的移动效果。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager
{
public CCMoveAction moveBoatAction;
public CCMoveAction moveRoleAction;
public FirstController controller;
public CCActionManager(){
controller = SSDirector.GetInstance().CurrentSceneController as FirstController;
controller.actionManager = this;
moveBoatAction = new CCMoveAction();
moveRoleAction = new CCMoveAction();
}
public bool IsMoving(){
return moveRoleAction.IsMoving() || moveBoatAction.IsMoving();
}
public void MoveRole(BoatController BoatCtrl, RoleController RoleCtrl, int destination, int seat){
Vector3 finalPos;
if(destination == FirstController.RIGHTLAND){
finalPos = Position.roleRightPos[seat];
}
else if(destination == FirstController.LEFTLAND){
finalPos = Position.roleLeftPos[seat];
}
else{
if(BoatCtrl.onLeftside){
finalPos = Position.seatLeftPos[seat];
}
else{
finalPos = Position.seatRightPos[seat];
}
}
moveRoleAction.MoveSequenceTo(RoleCtrl.GetModelGameObject(), finalPos);
}
public void MoveBoat(BoatController BoatCtrl, int destination){
if(destination == FirstController.RIGHTLAND){
moveBoatAction.MoveTo(BoatCtrl.GetModelGameObject(), Position.boatRightPos);
for(int i = 0; i < 3; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = controller.RoleCtrl[controller.IDToNumber(BoatCtrl.seat[i])];
moveRoleAction.MoveTo(r.GetModelGameObject(), Position.seatRightPos[i]);
}
}
}
else{
moveBoatAction.MoveTo(BoatCtrl.GetModelGameObject(), Position.boatLeftPos);
for(int i = 0; i < 3; i++){
if(BoatCtrl.seat[i] != -1){
RoleController r = controller.RoleCtrl[controller.IDToNumber(BoatCtrl.seat[i])];
moveRoleAction.MoveTo(r.GetModelGameObject(), Position.seatLeftPos[i]);
}
}
}
}
}
2、 CCMoveAction
给特定的gameobject添加Move脚本并且设置脚本的属性。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveAction
{
GameObject moveObject;
public bool IsMoving(){
return(this.moveObject != null && this.moveObject.GetComponent<Move>().isMoving == true);
}
public void MoveTo(GameObject moveObject, Vector3 destination){
Move test;
this.moveObject = moveObject;
if (!moveObject.TryGetComponent<Move>(out test)) {
moveObject.AddComponent<Move>();
}
this.moveObject.GetComponent<Move>().moveAction = this;
this.moveObject.GetComponent<Move>().destination = destination;
this.moveObject.GetComponent<Move>().moveMode = Move.single;
}
public void MoveSequenceTo(GameObject moveObject, Vector3 destination){
Move test;
this.moveObject = moveObject;
if (!moveObject.TryGetComponent<Move>(out test)) {
moveObject.AddComponent<Move>();
}
this.moveObject.GetComponent<Move>().moveAction = this;
this.moveObject.GetComponent<Move>().destination = destination;
this.moveObject.GetComponent<Move>().moveMode = Move.sequence;
}
}
3、 Move
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
public static int single = 0;
public static int sequence = 1;
public bool isMoving;
bool initialized;
public int moveMode;
public bool doneMoving;
public float speed = 5;
int n_seq;
public Vector3[] desseq;
public Vector3 destination;
public CCMoveAction moveAction;
public Move(){
n_seq = 0;
isMoving = false;
initialized = false;
moveMode = -1;
}
void Update()
{
if(moveMode == -1) return;
if(!initialized){
if(moveMode == single){
desseq = new Vector3[1];
desseq[0] = destination;
}
else if(moveMode == sequence){
desseq = new Vector3[3];
desseq[0] = transform.localPosition + new Vector3(0, 1, 0);
desseq[1] = destination + new Vector3(0, 1, 0);
desseq[2] = destination;
}
else{
Debug.Log("ERROR!");
}
initialized = true;
}
isMoving = true;
if(n_seq >= desseq.Length){
n_seq = 0;
moveMode = -1;
initialized = false;
isMoving = false;
return;
}
if(transform.localPosition == desseq[n_seq]){
n_seq += 1;
return;
}
transform.localPosition = Vector3.MoveTowards(transform.localPosition, desseq[n_seq], speed * Time.deltaTime);
}
}
四、裁判类
实现一个裁判类,控制器初始化时将游戏对象注入到裁判类中,裁判类通过游戏规则判断游戏是否结束。而通过调用裁判类来通知场景控制器,而场景控制器又通知UI,在UI中查看游戏状态判断是否结束游戏即可
创建一个裁判类,把原来写在FirstController中的GetAndSetGameState函数迁移到裁判类中,形成了裁判类的UpdateGameState方法。原来的场景控制器FirstController需要在Awake和初始化函数中初始化一个裁判类,现在将原来调用GetAndSetGameState函数的地方修改成请求裁判类裁决。裁判类的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JudgeController{
FirstController firstCtrl;
public JudgeController(){
firstCtrl = SSDirector.GetInstance().CurrentSceneController as FirstController;
}
//判断游戏状态
public int UpdadeGameState(){
if(firstCtrl.gameState != FirstController.PLAYING) return firstCtrl.gameState;
//判断是否失败
int[,] rolePos = new int[2, 3]{{0, 0, 0}, {0, 0, 0}};
foreach(RoleController r in firstCtrl.RoleCtrl){
rolePos[r.roleType, r.roleState]++;
}
if((rolePos[0,0]>0 && rolePos[0,0]<rolePos[1,0]) ||
(rolePos[0,1]>0 && rolePos[0,1]<rolePos[1,1]) ||
(rolePos[0,2]>0 && rolePos[0,2] < rolePos[1,2])){
return FirstController.FAILED;
}
//判断是否成功
foreach(RoleController r in firstCtrl.RoleCtrl){
if(r.roleType == 0 && r.roleState != FirstController.RIGHTLAND){
return FirstController.PLAYING;
}
}
return FirstController.WIN;
}
}
五、加载资源、点击事件以及用户GUI
这几部分与上次实验基本一致。
六、面向对象设计UML图
七、游戏运行效果展示
将Assert/Scripts/UserGUI拖动到Main Camera上,创建一个新的空GameObject,将FirstController拖动到GameObject上,点击运行即可。