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

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

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

这里写图片描述
由于自己不专业导致渲染效果不好,暂且这样吧。

总结游戏对象的使用

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

牧师与魔鬼 动作分离版

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

规划与设计

这里写图片描述

动作基类

  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;
    }
}

猜你喜欢

转载自blog.csdn.net/z_j_q_/article/details/79828936