项目链接
本次项目在第一版牧师与魔鬼的基础上,将动作从场记中分离出来,并设计一个裁判类实时监测游戏进行的情况。这样改进的优点有很多:
本次改进参考了上课时介绍的cocos2d方案,它的UML图如下:
接下来将逐个介绍其中的类
SSAction
动作基类。
定义了两个虚函数 Start
和 Update
,后续的动作类都继承动作基类并实现这两个虚函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set;}
protected SSAction() {}
// Start is called before the first frame update
public virtual void Start()
{
throw new System.NotImplementedException();
}
// Update is called once per frame
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
ISSActionCallback
回调函数。
SSActionEvent
是回调函数,动作完成后需要通知主控制器完成结果,设计这样的类更加方便事件的调度。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
// Start is called before the first frame update
// void Start()
// {
// }
// // Update is called once per frame
// void Update()
// {
// }
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
SSActionManager
动作管理基类
在Update
中实现了所有动作的基本管理。具体操作是遍历动作字典中的所有动作,查看它们的信息,进行相应的销毁或更新操作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour
{
private Dictionary actions = new Dictionary();
private List waitingAdd = new List ();
private List waitingDelete = new List();
// Start is called before the first frame update
protected void Start()
{
}
// Update is called once per frame
protected void Update()
{
foreach(SSAction ac in waitingAdd){
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach(KeyValuePair kv in actions){
SSAction ac = kv.Value;
if(ac.destroy){
waitingDelete.Add(ac.GetInstanceID());
}
else if(ac.enable){
ac.Update();
}
}
foreach(int key in waitingDelete){
SSAction ac = actions[key];
actions.Remove(key);
Destroy(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager){
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
}
CCMoveToAction
简单移动实现。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction
{
public Vector3 target; // 移动后的目标位置
public float speed;
private CCMoveToAction(){
}
// Start is called before the first frame update
public override void Start()
{
}
// Update is called once per frame
public override void Update()
{
this.transform.localPosition = Vector3.MoveTowards(this.transform.localPosition, target, speed * Time.deltaTime);
// 如果游戏对象不存在或者当前位置已在目标位置上,则不移动
if(this.transform.localPosition == target || this.gameobject == null){
this.destroy = true; // 标记为销毁
this.callback.SSActionEvent(this); // 回调函数
return;
}
}
public static CCMoveToAction GetSSAction(Vector3 target, float speed){
CCMoveToAction action = ScriptableObject.CreateInstance();
action.target = target;
action.speed = speed;
return action;
}
}
CCSequenceAction
组合动作实现
通过一个列表存储组合动作中的各个子动作,并按存放的顺序依次执行。根据参数 repeat
判断是否要重复执行组合动作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCSequenceAction : SSAction, ISSActionCallback
{
public List sequence;
public int repeat = -1;
public int start = 0;
// Start is called before the first frame update
public override void Start()
{
// 初始化列表中的动作
foreach(SSAction action in sequence){
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}
// Update is called once per frame
public override void Update()
{
if(sequence.Count <= 0){
return;
}
if(sequence.Count > 0 && start < sequence.Count){
sequence[start].Update();
}
else{
return;
}
}
public static CCSequenceAction GetSSAction(int repeat, int start, List sequence){
CCSequenceAction action = ScriptableObject.CreateInstance();
action.repeat = repeat;
action.start = start;
action.sequence = sequence;
return action;
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null){
source.destroy = false;
this.start++;
if(this.start >= sequence.Count){
this.start = 0;
if(this.repeat > 0){
this.repeat--;
}
else{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
}
void OnDestroy(){
}
}
CCActionManager
动作组合
MoveBoat
移动船,这是单独的一个动作,对应为 CCMoveToAction
。
MoveRole
移动人物,这是一个组合动作(折线运动),对应为 CCSequenceAction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback
{
public CCMoveToAction boatMovement;
public CCSequenceAction roleMovement;
public FirstController controller;
private bool isMoving = false;
protected new void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
controller.actionManager = this;
}
public bool CheckMoving()
{
return isMoving;
}
public void MoveBoat(GameObject boat, Vector3 target, float speed)
{
if (isMoving)
return;
isMoving = true;
boatMovement = CCMoveToAction.GetSSAction(target, speed);
this.RunAction(boat, boatMovement, this);
}
public void MoveRole(GameObject role, Vector3 middle_pos, Vector3 target, float speed)
{
if (isMoving)
return;
isMoving = true;
SSAction ac1 = CCMoveToAction.GetSSAction(middle_pos, speed);
SSAction ac2 = CCMoveToAction.GetSSAction(target, speed);
roleMovement = CCSequenceAction.GetSSAction(0, 0, new List {CCMoveToAction.GetSSAction(middle_pos, speed), CCMoveToAction.GetSSAction(target, speed)});
this.RunAction(role, roleMovement, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
Object objectParam = null)
{
isMoving = false;
}
}
JudgeController
裁判类(新增)
裁判类的作用是判断游戏进行的状态并通知主控制器,简单来说就是将原来 FirstController
中的 Check
抽离出来用一个类单独实现。需要在每一帧判断当前游戏的输赢,因此需要输入相关的变量进行判断。
在 Update
中实现判断的逻辑,以达到实施监测的效果。
这里有一个问题,我们需要利用到判断的结果在主控制器中进行相应的处理(打印信息等等),但 Update
函数不返回任何值(void类型)。解决的方法是,在FirstCotroller
中设计一个回调函数JudgeResultCallBack
,在裁判类中调用 JudgeResultCallBack
以返回判断结果。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JudgeController : MonoBehaviour
{
public FirstController sceneController;
public Land rightLand;
public Land leftLand;
public Boat boat;
// Start is called before the first frame update
void Start()
{
this.sceneController = (FirstController)SSDirector.GetInstance().CurrentSceneController;
this.rightLand = sceneController.rightLandController.GetLand();
this.leftLand = sceneController.leftLandController.GetLand();
this.boat = sceneController.boatController.GetBoatModel();
}
// Update is called once per frame
void Update()
{
if(sceneController.isRunning == false){
return;
}
this.gameObject.GetComponent().gameMessage = "";
if(rightLand.priestCount == 3){
// win, callback
sceneController.JudgeResultCallBack("You win!!");
return;
}
else{
int leftPriestCount, rightPriestCount, leftDevilCount, rightDevilCount;
leftPriestCount = leftLand.priestCount + (boat.isRight ? 0 : boat.priestCount);
rightPriestCount = 3 - leftPriestCount;
leftDevilCount = leftLand.devilCount + (boat.isRight ? 0: boat.devilCount);
rightDevilCount = 3 - leftDevilCount;
if((leftPriestCount != 0 && leftPriestCount < leftDevilCount) || (rightPriestCount != 0 && rightPriestCount < rightDevilCount)){
// lose
sceneController.JudgeResultCallBack("Game over!!");
return;
}
}
}
}
FirstController
场景控制器
与上一次项目的主要不同是,这次的场景控制器并不具体实现移动和检查的逻辑,而是交由相应的功能类实现。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
public LandControl leftLandController, rightLandController;
public River river;
public BoatControl boatController;
public RoleControl[] roleControllers;
public MoveCtrl moveController;
public bool isRunning;
public float time;
public CCActionManager actionManager;
public float speed = 5;
public void LoadResources() {
roleControllers = new RoleControl[6];
for (int i = 0; i < 6; ++i) {
roleControllers[i] = new RoleControl();
roleControllers[i].CreateRole(Position.role_land[i], i < 3 ? true : false, i);
}
leftLandController = new LandControl();
leftLandController.CreateLand(Position.left_land);
leftLandController.GetLand().land.name = "left_land";
rightLandController = new LandControl();
rightLandController.CreateLand(Position.right_land);
rightLandController.GetLand().land.name = "right_land";
foreach (RoleControl roleController in roleControllers)
{
roleController.GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleController.GetRoleModel());
}
boatController = new BoatControl();
boatController.CreateBoat(Position.left_boat);
river = new River(Position.river);
moveController = new MoveCtrl();
isRunning = true;
time = 60;
}
public void MoveBoat() {
if (isRunning == false || actionManager.CheckMoving() == true) return;
Vector3 target;
if(boatController.GetBoatModel().isRight){
target = Position.left_boat;
}
else{
target = Position.right_boat;
}
actionManager.MoveBoat(boatController.GetBoatModel().boat, target, speed);
boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;
}
public void MoveRole(Role roleModel) {
if (isRunning == false || actionManager.CheckMoving() == true) return;
Vector3 middle_pos;
Vector3 target;
if(roleModel.inBoat){
if(boatController.GetBoatModel().isRight){
target = rightLandController.AddRole(roleModel);
}
else{
target = leftLandController.AddRole(roleModel);
}
// if(roleModel.role.transform.localPosition.y > target.y){
// middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z);
// }
// else{
// middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z);
// }
middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z);
actionManager.MoveRole(roleModel.role, middle_pos, target, speed);
roleModel.onRight = boatController.GetBoatModel().isRight;
boatController.RemoveRole(roleModel);
}
else{
if (boatController.GetBoatModel().isRight == roleModel.onRight){
if (roleModel.onRight) {
rightLandController.RemoveRole(roleModel);
}
else {
leftLandController.RemoveRole(roleModel);
}
target = boatController.AddRole(roleModel);
// if(roleModel.role.transform.localPosition.y > target.y){
// middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z);
// }
// else{
// middle_pos = new Vector3(roleModel.role.transform.localPosition.x, target.y, target.z);
// }
middle_pos = new Vector3(target.x, roleModel.role.transform.localPosition.y, target.z);
actionManager.MoveRole(roleModel.role, middle_pos, target, speed);
}
}
}
public void Check() {
}
public void JudgeResultCallBack(string result){
this.gameObject.GetComponent().gameMessage = result;
this.isRunning = false;
}
void Awake() {
SSDirector.GetInstance().CurrentSceneController = this;
LoadResources();
this.gameObject.AddComponent();
this.gameObject.AddComponent();
this.gameObject.AddComponent();
}
void Update() {
}
}
以上完整介绍了牧师与魔鬼动作分离版本的主要变动和新增的函数,这一个版本和初始版本相比,增加了动作管理器,提升了代码的可复用性。