Unity3D学习笔记(3) 牧师与魔鬼(P&D) 第二版(添加动作管理)
程序员文章站
2022-07-12 23:37:12
...
参考 Fantasy Skybox FREE 构建自己的游戏场景
由于自己不专业导致渲染效果不好,暂且这样吧。
总结游戏对象的使用
游戏中的每个对象都是一个游戏对象,通过添加组件、赋予属性实现不同的功能,可成为一个角色、环境或特殊效果。
牧师与魔鬼 动作分离版
https://gitee.com/Ernie1/unity3d-learning/tree/hw3/hw3
规划与设计
动作基类
- ScriptableObject是不需要绑定 GameObject 对象的可编程基类。这些对象受 Unity 引擎场景管理
- 防止用户自己new对象
- 使用virtual申明虚方法,通过重写实现多态。这样继承者就明确使用 Start 和 Update 编程游戏对象行为
- 利用接口实现消息通知,避免与动作管理者直接依赖。
//SSAction.cs
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 () {}
//Use this for initialization
public virtual void Start () {
throw new System.NotImplementedException ();
}
// Update is called once per frame
public virtual void Update () {
throw new System.NotImplementedException ();
}
}
简单动作实现
- 让 Unity 创建动作类,确保内存正确回收。别指望手机开发者是 c 语言高手。
- 多态。C++ 语言必申明重写,Java则默认重写。
- 似曾相识的运动代码。动作完成,则期望管理程序自动回收运行对象,并发出事件通知管理者。
//CCMoveToAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction {
public Vector3 target;
public float speed;
public static CCMoveToAction GetSSAction (Vector3 target, float speed) {
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction> ();
action.target = target;
action.speed = speed;
return action;
}
public override void Update () {
this.transform.position = Vector3.MoveTowards (this.transform.position, target, speed);
if (this.transform.position == target) {
//waiting for destroy
this.destroy = true;
this.callback.SSActionEvent (this);
}
}
public override void Start () {
//TODO: something
}
}
组合动作实现
- 创建一个动作顺序执行序列,-1 表示无限循环,start 开始动作。
- 执行当前动作。
- 收到当前动作执行完成,推下一个动作,如果完成一次循环,减次数。如完成,通知该动作的管理者。
//CCSequenceAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCSequenceAction : SSAction, ISSActionCallback {
public List<SSAction> sequence;
public int repeat = -1; //repeat forever
public int start = 0;
public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence) {
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
// Update is called once per frame
public override void Update () {
if (sequence.Count == 0) return;
if (start < sequence.Count) {
sequence [start].Update ();
}
}
public void SSActionEvent (SSAction source,SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null) {
source.destroy = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
}
// Use this for initialization
public override void Start() {
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start ();
}
}
void OnDestroy () {
//TODO: something
}
}
动作事件接口定义
- 事件类型定义,使用了枚举变量
- 定义了事件处理接口,所有事件管理者都必须实现这个接口,来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
- 这里展示了语言函数默认参数的写法。
//ISSActionCallback.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType : int { Started, Completed }
public interface ISSActionCallback {
void SSActionEvent (SSAction source,SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null);
}
动作管理基类
- 创建 MonoBehaiviour 管理一个动作集合,动作做完自动回收动作。
- 该类演示了复杂集合对象的使用。
- 提供了运行一个新动作的方法。该方法把游戏对象与动作绑定,并绑定该动作事件的消息接收者。
- 执行改动作的 Start 方法
//SSActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour {
private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();
private List <SSAction> waitingAdd = new List <SSAction> ();
private List <int> waitingDelete = new List <int> ();
// Update is called once per frame
protected void Update () {
foreach (SSAction ac in waitingAdd)
actions [ac.GetInstanceID ()] = ac;
waitingAdd.Clear ();
foreach (KeyValuePair <int,SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destroy) {
waitingDelete.Add (ac.GetInstanceID ()); // release action
} else if (ac.enable) {
ac.Update (); // update aciton
}
}
foreach (int key in waitingDelete) {
SSAction ac = actions [key];
actions.Remove (key);
DestroyObject (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 ();
}
// Use this for initialization
protected void Start () {
}
}
实战动作管理
- 该类的职责
- 接收场景控制的命令
- 管理动作的自动执行
1.这是实战的管理器。所以它需要与场景控制器配合。
2.Update 是方法覆盖,提醒编程人员重视该方法不会多态,且要用 base 调用原方法。
//CCActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback {
public FirstController sceneController;
protected new void Start() {
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.actionManager=this;
}
// Update is called once per frame
protected new void Update () {
base.Update ();
}
public void singleRunAction (GameObject gameObject, Vector3 destination) {
this.RunAction (gameObject, CCMoveToAction.GetSSAction (destination, 1), this);
}
public void doubleRunAction (GameObject gameObject, Vector3 via, Vector3 destination) {
CCSequenceAction ccs = CCSequenceAction.GetSSAction (1, 0, new List<SSAction> {
CCMoveToAction.GetSSAction (via, 1),
CCMoveToAction.GetSSAction (destination, 1)
});
this.RunAction (gameObject, ccs, this);
}
#region ISSActionCallback implementation
public void SSActionEvent (SSAction source,SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null) {
sceneController.someObjectHandling = false;
}
#endregion
}
导演
- 获取当前游戏的场景
- 控制场景运行、切换、入栈与出栈
- 暂停、恢复、退出
- 管理游戏全局状态
- 设定游戏的配置
- 设定游戏全局视图
//SSDirector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController
{
void LoadResources();
void Pause ();
void Resume ();
void Restart ();
void GameOver ();
}
public class SSDirector : System.Object
{
private static SSDirector _instance;
public ISceneController currentSceneController { get; set; }
public bool running { get; set; }
public static SSDirector getInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
public int getFPS()
{
return Application.targetFrameRate;
}
public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}
}
场景管理器
- 管理本次场景所有的游戏对象
- 协调游戏对象(预制件级别)之间的通讯
- 响应外部输入事件
- 管理本场次的规则(裁判)
- 杂务
//FirstController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
public CCActionManager actionManager;
private GameObject objectClicked;
bool[] boatSeat;
public bool someObjectHandling;
private bool pausing;
private SSDirector director;
private UserGUI userGUI;
private Model model;
void Awake () {
model = Model.getInstance ();
userGUI = gameObject.AddComponent<UserGUI> ();
userGUI.action = this;
director = SSDirector.getInstance ();
director.currentSceneController = this;
director.setFPS (60);
director.currentSceneController.LoadResources ();
}
void Start () {
actionManager = gameObject.AddComponent<CCActionManager> ();
}
// 对ISceneController的实现
public void LoadResources () {
boatSeat = new bool[]{ true, true };
someObjectHandling = false;
model.GenGameObjects();
pausing = false;
userGUI.status = 2;
}
public void Pause () {
pausing = true;
userGUI.status = 3;
}
public void Resume () {
pausing = false;
userGUI.status = 2;
}
public void Restart () {
model.destroy ();
LoadResources ();
}
public void GameOver () {
pausing = true;
}
void handleClickedObject () {
if (model.isBoat (ref objectClicked)) {
if (someoneOnBoat ()) {
actionManager.singleRunAction (objectClicked, model.boatPos [1 - model.whereBoat ()]);
} else
someObjectHandling = false;
return;
}
//priest
int index = model.whichPriest(ref objectClicked);
if (index != -1) {
if (model.wherePriest (index) == 2) {
actionManager.doubleRunAction (objectClicked, model.bankSharp [model.whereBoat ()], model.priestPos [index, model.whereBoat ()]);
boatSeat [model.whichSeatByPosition (ref objectClicked)] = true;
model.ashore (ref objectClicked);
} else if (model.wherePriest (index) == model.whereBoat () && whereCanSit () != 2) {
actionManager.doubleRunAction (objectClicked, model.bankSharp [model.whereBoat ()], model.seatGlobalPos (whereCanSit ()));
boatSeat [whereCanSit ()] = false;
model.aboard (ref objectClicked);
} else
someObjectHandling = false;
return;
}
//devil
index = model.whichDevil(ref objectClicked);
if (index != -1) {
if (model.whereDevil (index) == 2) {
actionManager.doubleRunAction (objectClicked, model.bankSharp [model.whereBoat ()], model.devilPos [index, model.whereBoat ()]);
boatSeat [model.whichSeatByPosition (ref objectClicked)] = true;
model.ashore (ref objectClicked);
}
else if (model.whereDevil (index) == model.whereBoat () && whereCanSit () != 2) {
actionManager.doubleRunAction (objectClicked, model.bankSharp [model.whereBoat ()], model.seatGlobalPos (whereCanSit ()));
boatSeat [whereCanSit ()] = false;
model.aboard (ref objectClicked);
} else
someObjectHandling = false;
return;
}
//其它不管
someObjectHandling = false;
}
int whereCanSit() {
if (boatSeat [0])
return 0;
if (boatSeat [1])
return 1;
return 2;
}
bool someoneOnBoat () {
if(boatSeat[0]&&boatSeat[1])
return false;
return true;
}
//0 lose 1 win 2 -
int checkGame () {
int priestHere = 0, priestThere = 0, devilHere = 0, devilThere = 0;
for(int i=0;i<3;++i){
if (model.wherePriest (i) == 0)
++priestHere;
else if (model.wherePriest (i) == 1)
++priestThere;
else if (model.whereBoat () != 2) {
if (model.whereBoat () == 0)
++priestHere;
else
++priestThere;
}
if (model.whereDevil (i) == 0)
++devilHere;
else if (model.whereDevil (i) == 1)
++devilThere;
else if (model.whereBoat () != 2) {
if (model.whereBoat () == 0)
++devilHere;
else
++devilThere;
}
}
if ((priestHere > 0 && priestHere < devilHere) || (priestThere > 0 && priestThere < devilThere))
return 0;
if (priestThere == 3 && devilThere == 3)
return 1;
return 2;
}
// Update is called once per frame
void Update () {
if (!pausing) {
if (!someObjectHandling && userGUI.action.checkObjectClicked ())
handleClickedObject ();
userGUI.status = checkGame ();
}
}
void IUserAction.GameOver () {
director.currentSceneController.GameOver ();
}
void IUserAction.Pause () {
director.currentSceneController.Pause ();
}
void IUserAction.Resume () {
director.currentSceneController.Resume ();
}
void IUserAction.Restart () {
director.currentSceneController.Restart ();
}
bool IUserAction.checkObjectClicked(){
if(Input.GetMouseButton(0)) {
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit hit ;
if(Physics.Raycast (ray,out hit)) {
objectClicked = hit.collider.gameObject;
someObjectHandling = true;
return true;
}
}
return false;
}
}
用户界面
- 显示交互界面
- 获取交互信息
- 响应用户操作
//UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction
{
void Restart ();
void GameOver ();
void Pause ();
void Resume ();
bool checkObjectClicked ();
}
public class UserGUI : MonoBehaviour {
public IUserAction action;
public int status;
void Awake () {
}
void Start () {
}
void Update () {
}
void OnGUI () {
float width = Screen.width / 6;
float height = Screen.height / 12;
if (status == 0) {
action.GameOver ();
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height), "Game Over!\nRestart")) {
action.Restart ();
}
}
if (status == 1) {
action.GameOver ();
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height), "Win!\nRestart")) {
action.Restart ();
}
}
if (status == 2) {
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height), "Pause")) {
action.Pause ();
}
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2 + height, width, height), "Restart")) {
action.Restart ();
}
}
if (status == 3) {
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height), "Resume")) {
action.Resume ();
}
if (GUI.Button (new Rect (Screen.width / 2 - width / 2, Screen.height / 2 - height / 2 + height, width, height), "Restart")) {
action.Restart ();
}
}
}
}
游戏模型
- 创建、配置、管理游戏对象
- 响应场景管理器的请求
//Model.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Model {
private static Model _instance;
private GameObject bankHere, bankThere, boat, water;
private GameObject []priest, devil;
public Vector3[] boatPos, onBoat;
public Vector3[,] priestPos, devilPos;
public Vector3[] bankSharp;
public static Model getInstance()
{
if (_instance == null)
{
_instance = new Model();
}
return _instance;
}
public void GenGameObjects () {
boatPos = new Vector3[]{ new Vector3 (2, 0.1F, 0), new Vector3 (-2, 0.1F, 0) };
onBoat = new Vector3[]{ new Vector3 (-1.5F, 2, 0), new Vector3 (1.5F, 2, 0) };
priestPos = new Vector3[3, 2];
devilPos = new Vector3[3, 2];
bankSharp = new Vector3[]{ new Vector3 (3, 1, 0), new Vector3 (-3, 1, 0) };
for (int i = 0; i < 3; ++i) {
priestPos [i, 0] = new Vector3 (3.4F + 0.7F * i, 1, 0);
priestPos [i, 1] = new Vector3 (-6.9F + 0.7F * i, 1, 0);
devilPos [i, 0] = new Vector3 (5.5F + 0.7F * i, 1, 0);
devilPos [i, 1] = new Vector3 (-4.8F + 0.7F * i, 1, 0);
}
bankHere = GameObject.Instantiate (Resources.Load ("Prefabs/bank"), new Vector3 (5.25F, 0, 0), Quaternion.identity, null) as GameObject;
bankHere.name = "bankHere";
bankThere = GameObject.Instantiate (Resources.Load ("Prefabs/bank"), new Vector3 (-5.25F, 0, 0), Quaternion.identity, null) as GameObject;
bankThere.name = "bankThere";
boat = GameObject.Instantiate (Resources.Load ("Prefabs/boat"), boatPos [0], Quaternion.identity, null) as GameObject;
boat.name = "boat";
water = GameObject.Instantiate (Resources.Load ("Prefabs/water"), new Vector3 (0, -0.25F, 0), Quaternion.identity, null) as GameObject;
water.name = "water";
priest = new GameObject[3];
devil = new GameObject[3];
for (int i = 0; i < 3; ++i) {
priest [i] = GameObject.Instantiate (Resources.Load ("Prefabs/priest"), priestPos [i, 0], Quaternion.identity, null) as GameObject;
priest [i].name = "priest" + i.ToString ();
devil [i] = GameObject.Instantiate (Resources.Load ("Prefabs/devil"), devilPos [i, 0], Quaternion.identity, null) as GameObject;
devil [i].name = "devil" + i.ToString ();
}
}
public void destroy () {
GameObject.Destroy (bankHere);
GameObject.Destroy (bankThere);
GameObject.Destroy (boat);
GameObject.Destroy (water);
foreach (GameObject e in priest)
GameObject.Destroy (e);
foreach (GameObject e in devil)
GameObject.Destroy (e);
}
// 此岸 0,彼岸 1,否则 2
public int whereBoat () {
for (int i = 0; i < 2; ++i)
if (boat.transform.position == boatPos [i])
return i;
return 2;
}
public int wherePriest (int e) {
for (int i = 0; i < 2; ++i)
if (priest [e].transform.position == priestPos [e, i])
return i;
return 2;
}
public int whereDevil (int e) {
for (int i = 0; i < 2; ++i)
if (devil [e].transform.position == devilPos [e, i])
return i;
return 2;
}
public int whichPriest (ref GameObject g) {
return Array.IndexOf (priest, g);
}
public int whichDevil (ref GameObject g) {
return Array.IndexOf (devil, g);
}
public int whichSeatByPosition (ref GameObject g) {
if (g.transform.localPosition == onBoat [0])
return 0;
if (g.transform.localPosition == onBoat [1])
return 1;
return -1;
}
public Vector3 seatGlobalPos (int seatIndex) {
return boat.transform.TransformPoint (onBoat [seatIndex]);
}
public void ashore (ref GameObject Object) {
Object.transform.parent = null;
}
public void aboard (ref GameObject Object) {
Object.transform.parent = boat.transform;
}
public bool isBoat (ref GameObject Object) {
return Object == boat;
}
}
上一篇: 适配器模式