欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity3D学习笔记(3) 牧师与魔鬼(P&D) 第二版(添加动作管理)

程序员文章站 2022-07-12 23:37:12
...

牧师与魔鬼(P&D) 第一版(基础 MVC 实现)

参考 Fantasy Skybox FREE 构建自己的游戏场景

Unity3D学习笔记(3) 牧师与魔鬼(P&D) 第二版(添加动作管理)
由于自己不专业导致渲染效果不好,暂且这样吧。

总结游戏对象的使用

游戏中的每个对象都是一个游戏对象,通过添加组件、赋予属性实现不同的功能,可成为一个角色、环境或特殊效果。

牧师与魔鬼 动作分离版

https://gitee.com/Ernie1/unity3d-learning/tree/hw3/hw3

规划与设计

Unity3D学习笔记(3) 牧师与魔鬼(P&D) 第二版(添加动作管理)

动作基类

  1. ScriptableObject是不需要绑定 GameObject 对象的可编程基类。这些对象受 Unity 引擎场景管理
  2. 防止用户自己new对象
  3. 使用virtual申明虚方法,通过重写实现多态。这样继承者就明确使用 Start 和 Update 编程游戏对象行为
  4. 利用接口实现消息通知,避免与动作管理者直接依赖。
//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 ();
    }
}

简单动作实现

  1. 让 Unity 创建动作类,确保内存正确回收。别指望手机开发者是 c 语言高手。
  2. 多态。C++ 语言必申明重写,Java则默认重写。
  3. 似曾相识的运动代码。动作完成,则期望管理程序自动回收运行对象,并发出事件通知管理者。
//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. 创建一个动作顺序执行序列,-1 表示无限循环,start 开始动作。
  2. 执行当前动作。
  3. 收到当前动作执行完成,推下一个动作,如果完成一次循环,减次数。如完成,通知该动作的管理者。
//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
    }
}

动作事件接口定义

  1. 事件类型定义,使用了枚举变量
  2. 定义了事件处理接口,所有事件管理者都必须实现这个接口,来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
  3. 这里展示了语言函数默认参数的写法。
//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);
}

动作管理基类

  1. 创建 MonoBehaiviour 管理一个动作集合,动作做完自动回收动作。
  2. 该类演示了复杂集合对象的使用。
  3. 提供了运行一个新动作的方法。该方法把游戏对象与动作绑定,并绑定该动作事件的消息接收者。
  4. 执行改动作的 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
}

导演

  1. 获取当前游戏的场景
  2. 控制场景运行、切换、入栈与出栈
  3. 暂停、恢复、退出
  4. 管理游戏全局状态
  5. 设定游戏的配置
  6. 设定游戏全局视图
//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;  
    }

}  

场景管理器

  1. 管理本次场景所有的游戏对象
  2. 协调游戏对象(预制件级别)之间的通讯
  3. 响应外部输入事件
  4. 管理本场次的规则(裁判)
  5. 杂务
//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;
    }
}

用户界面

  1. 显示交互界面
  2. 获取交互信息
  3. 响应用户操作
//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 ();
            }
        }
    }

}

游戏模型

  1. 创建、配置、管理游戏对象
  2. 响应场景管理器的请求
//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;
    }
}
相关标签: Unity 3D