一、动作分离
1.改进目的:
- 将 CCAction 代码补充修改,按课件(第四章,作业2 “牧师与魔鬼 动作分离版”),编写“牧师与魔鬼游戏” 要求。
- 集成 CCAction,使得动作管理从场景控制器中分离。
- 利用接口消息传递机制,设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束。实现游戏结束判定从场景控制器中分离。
我们在上次作业使用MVC代码架构制作了牧师与魔鬼小游戏,但是不难发现,场景控制器FirstController需要管理的事务过于繁杂,不利于代码管理。而我们现在要做的事情就是,建立一个动作管理器,使得动作管理从场景控制器中分离出来,优化结构,通过场景控制器把需要移动的游戏对象传递给动作管理器,让动作管理器去移动游戏对象。为此,我们需要把每个需要移动的游戏对象的移动方法提取出来,添加到动作管理器中由它来管理不同的移动方法。当动作很多或是需要做同样动作的游戏对象很多的时候,使用动作管理器可以让动作很容易管理,也提高了代码复用性。
除此之外,我们还需要利用接口消息传递机制,设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束。实现“游戏结束判定”从场景控制器中分离。
2.UML图
本次动作分离版牧师与魔鬼的UML图如下:
其中,ISceneController和IUserAction等接口设计、SSDirector场记和UserGUI用户交互的设计、ModelController和对应Model的设计基本与上次作业无异,主要是新增了SSAction动作基类和SSActionManager动过管理器基类并派生出两个子类CCMoveAction和CCActionManager,他们将实现和管理所有的动作方法(虽然我们在该游戏中只有平移这一动作),在该游戏例子中,在FirstController中声明一个CCActionManager动作管理器对象,需要移动对象时只需要声明一个CCMoveAction对象并添加到动作管理器对象即可实现动作从场景控制器中分离。而Judge类则将场景管理器的“判断游戏结束”功能方法分离出来,判断游戏结束,并通知场景管理器。
二、代码实现
Judge:
裁判类实际上是把原来FirstController中的UpdadeGameState()方法单独分离出来成为一个类
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;
}
}
在上一篇文章中,游戏对象的移动由MoveController和Move共同管理,这样做的坏处是在FirstController中仍然保留有一小部分的用于管理对象运动的代码。在动作分离版的代码中,管理动作的代码被分解成以下三部分,CCActionManager用于管理所有动作,CCMoveAction用于管理“移动”这种动作,Move则是移动这个动作的实体。
在CCActionManager中主要实现了MoveRole()和MoveBoat()两个函数。通过调用CCMoveAction中的MoveTo和MoveSeqcenceTo来实现两种不同形式的移动效果。
CCMoveAction:
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;
}
}
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);
}
}
CCActionManager:
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]);
}
}
}
}
}
FirstController:
在FirstController中就创建Judge类对象和CCActionManager类对象,由judge判断游戏输赢,由CCActionManager执行游戏对象的移动动作,其他与之前无异
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
public static int LEFTLAND = 0;
public static int RIGHTLAND = 1;
public static int BOAT = 2;
public static int PRIEST = 0;
public static int DEVIL = 1;
public static int PLAYING = 0;
public static int WIN = 1;
public static int FAILED = 2;
public CCActionManager actionManager;
public BoatController BoatCtrl;
public RoleController[] RoleCtrl = new RoleController[6];
public LandController[] LandCtrl = new LandController[2];
public JudgeController JudgeCtrl;
public int[] rolesID = new int[6]{0,1,2,3,4,5};
public int gameState;
void Awake(){
SSDirector director = SSDirector.GetInstance();
director.CurrentSceneController = this;
director.CurrentSceneController.Initialize();
}
public void Initialize(){
//如果有,则释放原有的GameObject
for(int i = 0; i < 6; i++){
if(RoleCtrl[i] != null){
Destroy(RoleCtrl[i].GetModelGameObject());
}
}
for(int i = 0; i < 2; i++){
if(LandCtrl[i] != null){
Destroy(LandCtrl[i].GetModelGameObject());
}
}
if(BoatCtrl != null){
Destroy(BoatCtrl.GetModelGameObject());
}
// 加载控制器和模型
BoatCtrl = new BoatController();
BoatCtrl.CreateModel();
for(int i = 0; i < 6; i++){
int roleType = (i < 3) ? PRIEST : DEVIL;
RoleCtrl[i] = new RoleController(roleType, rolesID[i]);
RoleCtrl[i].CreateModel();
}
LandCtrl[0] = new LandController(LEFTLAND, rolesID);
LandCtrl[1] = new LandController(RIGHTLAND, rolesID);
LandCtrl[0].CreateModel();
LandCtrl[1].CreateModel();
JudgeCtrl = new JudgeController();
actionManager = new CCActionManager();
//开始游戏
gameState = PLAYING;
}
//将角色的ID转换成数组的下标
public int IDToNumber(int ID){
for(int i = 0; i < 6; i++){
if(rolesID[i] == ID){
return i;
}
}
return -1;
}
//点击船时执行
public void MoveBoat(){
if(gameState != PLAYING || actionManager.IsMoving()) return;
gameState = JudgeCtrl.UpdadeGameState();
if(BoatCtrl.onLeftside){
actionManager.MoveBoat(BoatCtrl, RIGHTLAND);
}
else{
actionManager.MoveBoat(BoatCtrl, LEFTLAND);
}
BoatCtrl.onLeftside = !BoatCtrl.onLeftside;
}
//点击角色时执行
public void MoveRole(int id){
int num = IDToNumber(id);
if(gameState != PLAYING || actionManager.IsMoving()) return;
int seat;
switch(RoleCtrl[num].roleState){
case 0: // LEFTLAND
if(!BoatCtrl.onLeftside) return;
seat = BoatCtrl.embark(id);
if(seat == -1) return;
LandCtrl[0].LeaveLand(id);
RoleCtrl[num].GoTo(BOAT);
actionManager.MoveRole(BoatCtrl, RoleCtrl[num], BOAT, seat);
break;
case 1: // RIGHTLAND
if(BoatCtrl.onLeftside) return;
seat = BoatCtrl.embark(id);
if(seat == -1) return;
LandCtrl[1].LeaveLand(id);
RoleCtrl[num].GoTo(BOAT);
actionManager.MoveRole(BoatCtrl, RoleCtrl[num], BOAT, seat);
break;
case 2: //BOAT
if(BoatCtrl.onLeftside){
seat = LandCtrl[0].getEmptySeat();
BoatCtrl.disembark(id);
LandCtrl[0].GoOnLand(id);
RoleCtrl[num].GoTo(LEFTLAND);
actionManager.MoveRole(BoatCtrl, RoleCtrl[num], LEFTLAND, seat);
}
else{
seat = LandCtrl[1].getEmptySeat();
BoatCtrl.disembark(id);
LandCtrl[1].GoOnLand(id);
RoleCtrl[num].GoTo(RIGHTLAND);
actionManager.MoveRole(BoatCtrl, RoleCtrl[num], RIGHTLAND, seat);
}
break;
default: break;
}
}
//Reset按钮执行的功能
public void Restart(){
Initialize();
gameState = PLAYING;
}
//获取游戏当前状态
public int GetGameState(){
return gameState;
}
}
三、游戏展示
游戏展示视频:unity 3d游戏编程作业 牧师与魔鬼动作分离版 (bilibili.com)
代码地址:冯威彬/unity 3d - 码云 - 开源中国 (gitee.com)
参考师兄文章,感谢师兄!Unity3D小游戏——牧师与魔鬼(动作分离版) - LoongChan - 博客园 (cnblogs.com)