3D游戏编程与设计 HW 4.5 牧师与恶魔(动作分离版)
文章目录
3D游戏编程与设计 HW 4.5 牧师与恶魔(动作分离版)1.作业要求2.游戏制作① 设计思路② 设计代码一、ActionController.cs1)动作基类 SSAction2)基础动作 移动子类 SSMoveToAction (牧师与恶魔游戏只需设计直线运动)3)组合动作 SequenceAction4)动作管理 ActionManager
二、FirstSceneController.cs三、Check.cs(裁判类)
③ 修改已有代码一、Moveable.cs二、Interface.cs1)增加 动作事件回调接口 ISSActionCallback
三、Boat.cs 和 Character.cs四、FirstController.cs五、GameController
3.游戏截图
完整游戏过程可见以下视频: https://www.bilibili.com/video/BV1gv411y7xz/
完整代码可见以下仓库: https://gitee.com/beilineili/game3-d
1.作业要求
2.游戏制作
① 设计思路
在上一版的牧师与恶魔中,场记管的事情太多,不仅要处理用户交互事件,还要进行游戏对象加载、游戏规则实现、运动实现等工作,显得非常臃肿。对这个问题,一个最直观的想法就是让更多的人(角色)来管理不同方面的工作。显然,这就是面向对象基于职责的思考。例如:就和踢足球一样,自己踢5人场,一个裁判就够了,但如果是国际比赛,就需要主裁判、边裁判、电子裁判等角色通过消息协同来完成更为复杂的工作。 在之前的牧师与恶魔的游戏制作中,我们使用FirstController来控制游戏中人物的动作。本次作业,我们对上一版的牧师与恶魔更新,将动作从FirstController中分离出来。为了用一组简单的动作组合成复杂的动作,我们采用 cocos2d 的方案,建立与 CCAtion 类似的类。先上设计图:
设计思路如下:
通过门面模式(控制器模式)输出组合好的几个动作,供原来程序调用。通过组合模式实现将基本动作组合接口回调(函数回调)实现管理者与被管理者解耦通过模板方法,让使用者减少对动作管理过程细节的要求
② 设计代码
一、ActionController.cs
1)动作基类 SSAction
需要用户实现方法 Start 和 Update ,分别用于动作初始化和实现动作逻辑
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; }
public virtual void Start() {
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
2)基础动作 移动子类 SSMoveToAction (牧师与恶魔游戏只需设计直线运动)
public class SSMoveToAction : SSAction {
public Vector3 target
;
public float speed
;
private SSMoveToAction() { }
public static SSMoveToAction GetSSMoveToAction(Vector3 goal
, float speed
) {
SSMoveToAction action
= CreateInstance<SSMoveToAction>();
action
.target
= goal
;
action
.speed
= speed
;
return action
;
}
public override void Start() { }
public override void Update() {
Transform
.position
= Vector3
.MoveTowards(Transform
.position
, target
, speed
* Time
.deltaTime
);
if (Transform
.position
== target
) {
destroy
= true;
Callback
.ActionDone(this);
}
}
}
3)组合动作 SequenceAction
牧师与恶魔里的动作,即上下船/岸动作,可以抽象为一个直角折线运动。这时,就需要两步直线运动,所以要实现组合动作。组合动作类实现一个动作组合序列,顺序播放动作
public class SequenceAction: SSAction, ISSActionCallback
{
public List
<SSAction
> sequence
;
public int repeat
= -1;
public int currentActionIndex
= 0;
public static SequenceAction GetSequenceAction(int repeat
, int currentActionIndex
, List
<SSAction
> sequence
) {
SequenceAction action
= CreateInstance<SequenceAction>();
action
.sequence
= sequence
;
action
.repeat
= repeat
;
action
.currentActionIndex
= currentActionIndex
;
return action
;
}
public override void Update() {
if (sequence
.Count
== 0) return;
if (currentActionIndex
< sequence
.Count
) {
sequence
[currentActionIndex
].Update();
}
}
public void ActionDone(SSAction source
) {
source
.destroy
= false;
currentActionIndex
++;
if (currentActionIndex
>= sequence
.Count
) {
currentActionIndex
= 0;
if (repeat
> 0) repeat
--;
if (repeat
== 0) {
destroy
= true;
Callback
.ActionDone(this);
}
}
}
public override void Start() {
foreach(SSAction action
in sequence
) {
action
.GameObject
= GameObject
;
action
.Transform
= Transform
;
action
.Callback
= this;
action
.Start();
}
}
void OnDestroy() {
foreach(SSAction action
in sequence
) {
DestroyObject(action
);
}
}
}
4)动作管理 ActionManager
管理动作的执行,它来调度调配所有的动作的执行,决定游戏对象做某个动作或是一连串动作
public class ActionManager : MonoBehaviour, ISSActionCallback
{
private Dictionary
<int, SSAction
> actions
= new Dictionary<int, SSAction>();
private List
<SSAction
> waitingAdd
= new List<SSAction>();
private List
<int> waitingDelete
= new List<int>();
protected void Update() {
foreach(SSAction action
in waitingAdd
) {
actions
[action
.GetInstanceID()] = action
;
}
waitingAdd
.Clear();
foreach(KeyValuePair
<int, SSAction
> kv
in actions
) {
SSAction action
= kv
.Value
;
if (action
.destroy
) {
waitingDelete
.Add(action
.GetInstanceID());
} else if (action
.enable
) {
action
.Update();
}
}
foreach(int key
in waitingDelete
) {
SSAction action
= actions
[key
];
actions
.Remove(key
);
DestroyObject(action
);
}
waitingDelete
.Clear();
}
public void AddAction(GameObject gameObject
, SSAction action
, ISSActionCallback callback
) {
action
.GameObject
= gameObject
;
action
.Transform
= gameObject
.transform
;
action
.Callback
= callback
;
waitingAdd
.Add(action
);
action
.Start();
}
public void ActionDone(SSAction source
) { }
}
二、FirstSceneController.cs
增加一个场记动作管理者类,避免场记代码冗余,它含有控制船只和人物运动的方法移动船:一个动作,从当前位置以speed的速率移动到destination移动角色:两个动作,从当前位置以speed的速率移动到middlePos,再以speed的速率移动到destination
public class FirstSceneActionManager : ActionManager {
public void MoveBoat(BoatController boatController
) {
SSMoveToAction action
= SSMoveToAction
.GetSSMoveToAction(boatController
.GetDestination(), boatController
.boat
.movingSpeed
);
AddAction(boatController
.boat
._Boat
, action
, this);
}
public void MoveCharacter(MyNamespace.CharacterController characterCtrl
, Vector3 destination
) {
Vector3 currentPos
= characterCtrl
.character
.Role
.transform
.position
;
Vector3 middlePos
= currentPos
;
if (destination
.y
> currentPos
.y
) middlePos
.y
= destination
.y
;
else {
middlePos
.x
= destination
.x
;
}
SSAction action1
= SSMoveToAction
.GetSSMoveToAction(middlePos
, characterCtrl
.character
.movingSpeed
);
SSAction action2
= SSMoveToAction
.GetSSMoveToAction(destination
, characterCtrl
.character
.movingSpeed
);
SSAction seqAction
= SequenceAction
.GetSequenceAction(1, 0, new List<SSAction
> { action1
, action2
});
AddAction(characterCtrl
.character
.Role
, seqAction
, this);
}
}
三、Check.cs(裁判类)
增加了一个裁判类,用来判定游戏是否结束。一旦游戏结束,UserGUI即会得到信息来打印出胜或败的游戏结果将之前在 FirstController.cs 里的 Check 函数删去,在 FirstController 的 Awake 函数对裁判类实例进行初始化。
public class Check : MonoBehaviour {
public FirstController sceneController
;
protected void Start() {
sceneController
= (FirstController
)Director
.GetInstance().CurrentSecnController
;
sceneController
.gameStatusManager
= this;
}
public int CheckGame() {
int rightPriests
= (sceneController
.rightCoastCtrl
.GetCharacterNum())[0];
int rightDevils
= (sceneController
.rightCoastCtrl
.GetCharacterNum())[1];
int leftPriests
= (sceneController
.leftCoastCtrl
.GetCharacterNum())[0];
int leftDevils
= (sceneController
.leftCoastCtrl
.GetCharacterNum())[1];
if (leftPriests
+ leftDevils
== 6) return 2;
if (sceneController
.boatCtrl
.boat
.Location
== Location
.right
) {
rightPriests
+= sceneController
.boatCtrl
.GetCharacterNum()[0];
rightDevils
+= sceneController
.boatCtrl
.GetCharacterNum()[1];
} else {
leftPriests
+= sceneController
.boatCtrl
.GetCharacterNum()[0];
leftDevils
+= sceneController
.boatCtrl
.GetCharacterNum()[1];
}
if ((rightPriests
< rightDevils
&& rightPriests
> 0) ||
(leftPriests
< leftDevils
&& leftPriests
> 0)) {
return 1;
}
return 0;
}
}
③ 修改已有代码
一、Moveable.cs
将Moveable.cs删除,有动作管理类就不再需要移动控制器了。
二、Interface.cs
1)增加 动作事件回调接口 ISSActionCallback
ActionDone 用于通知更高级的对象动作已执行完毕
public interface ISSActionCallback {
void ActionDone(SSAction source
);
}
三、Boat.cs 和 Character.cs
将 Model 里的 Boat 和 Character 中与运动相关的方法删除,并添加speed
public class Boat {
public readonly Vector3 departure
;
public readonly Vector3 destination
;
public readonly Vector3
[] departures
;
public readonly Vector3
[] destinations
;
public readonly float movingSpeed
= 20;
public CharacterController
[] passenger
= new CharacterController[2];
public GameObject boat_
{ get; set; }
public Location Location
{ get; set; }
public Boat() {
departure
= new Vector3(5, 1, 0);
destination
= new Vector3(-5, 1, 0);
Location
= Location
.right
;
departures
= new Vector3[] {new Vector3(4.5f, 1.5f, 0), new Vector3(5.5f, 1.5f, 0) };
destinations
= new Vector3[] {new Vector3(-5.5f, 1.5f, 0),new Vector3(-4.5f, 1.5f, 0) };
boat_
= Object
.Instantiate(Resources
.Load("Prefab/Boat", typeof(GameObject
)),departure
, Quaternion
.identity
, null) as GameObject
;
boat_
.name
= "boat";
boat_
.AddComponent(typeof(UserGUI
));
}
}
四、FirstController.cs
加入动作管理类 ActionManager
private FirstSceneActionManager actionManager
;
void Start() {
actionManager
= GetComponent<FirstSceneActionManager>();
}
修改MoveBoat 和 CharacterClicked,使用动作管理器 ActionManager 来实现
public void MoveBoat() {
if (boatCtrl
.IsEmpty()) return;
actionManager
.MoveBoat(boatCtrl
);
boatCtrl
.SetPos();
UserGUI
.status
= CheckGameOver();
}
五、GameController
删除和移动动作相关的代码,改成直接设置 pos
3.游戏截图
从 Asset Store 中下载并添加了天空盒,以及流动的水元素 资源:流动的水游戏开始界面
游戏结束界面