Unity小游戏: 智能巡逻兵

Unity3d游戏(七): 智能巡逻兵

智能巡逻兵

  • 游戏设计要求:
    • 创建一个地图和若干巡逻兵(使用动画);
    • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
    • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
    • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
    • 失去玩家目标后,继续巡逻;
    • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;

设计过程:

 采用MVC架构,用工厂模式产生巡逻兵。

        

巡逻兵的设计:
  巡逻兵的路径为一个3~5个边的凸多边形,需因此要一些数组来存放巡逻兵路径的相对位置:

if (sideNum == 3) {
    posSet = new Vector3[] {new Vector3 (0, 0, 0), new Vector3 (8, 0, 0),
        new Vector3 (4, 0, 6), new Vector3 (0, 0, 0)};
} else if (sideNum == 4) {
    posSet = new Vector3[] {new Vector3 (0, 0, 0), new Vector3 (8, 0, 0),
        new Vector3 (8, 0, 8), new Vector3 (0, 0, 8), new Vector3 (0, 0, 0)};
} else {
    posSet = new Vector3[] {new Vector3 (0, 0, 0), new Vector3 (5, 0, 0),
        new Vector3 (7, 0, 5), new Vector3 (3, 0, 8), new Vector3 (-2, 0, 5), new Vector3 (0, 0, 0)};
}

PatrolUpdate ()方法来实现巡逻兵的动作

void Update () {
    if (playerIsLive && inField (playerPos)) {  //玩家存活并且在此巡逻兵视野中
        catchPlayer ();     //追击玩家
    } else {
        if (patrolInMap (currentSide)) {    //否则沿着自己的路径巡逻
            if (++currentSide >= sideNum) {
                bothPos = transform.position;
                currentSide = 0;
            }
        }
    }
}

其中追击玩家以及巡逻的具体实现参考后面的代码Patrol.cs

  设计的重点是使用发布订阅模式完成PlayerPatrolSceneController以及ScoreManager之间的通信。首先定义了一个抽象类Subject.cs(发布者)和一个接口Handle.cs(订阅者)。


Subject中有一个List<Handle>存放所有的Handle,并且有增加和删除Handle的方法Attach ()Detach ()。还有一个Notify ()方法向每一个Handle发送消息。Handle中则有处理Subject发送过来的消息的方法Reaction ()Player继承Subject作为发布者将自己的状态以及位置发送给每一个Handle

public override void Notify (bool live, Vector3 pos) {
    foreach (Handle h in handles) {
        h.Reaction(live, pos);
    }
}
PatrolSceneControllerScoreManager都作为 Handle接受到 Player发送过来的消息,但是不同的 Handle则会对这些信息做出不同地处理。巡逻兵 Patrol需要知道玩家的位置,以及是否存活;而 SceneControllerScoreManager并不在乎玩家的位置,它们只关心玩家是不是还活着。

  至于ScoreManager中的计分操作,是用事件(Event)与委托(delegate)的观察者模式实现。Patrol中声明了委托getScore与事件escape,以及注册、注销观察者的方法。

    public delegate void getScore (int n);
    public event getScore escape;

    public void register (getScore s) {
        escape += s;
    }

    public void unRegister (getScore s) {
        escape -= s;
    }

  在每个巡逻兵被创建时,注册ScoreManager为观察者,当玩家离开巡逻兵的视野,巡逻兵发出escape的信号,然后ScoreManager就执行计分操作。

  游戏的难度也不是一成不变的,每当玩家分数增加到一定数量,又会产生新的,更强的巡逻兵!具体实现见SceneController.cs

编译脚本


以下为完整代码:

SSDirector.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSDirector : System.Object {
    //singleton instance
    private static SSDirector instance;

    public ISceneController currentScene;
    public bool running {
        get;
        set;
    }

    public static SSDirector getInstance () {
        if (instance == null) {
            instance = new SSDirector ();
        }
        return instance;
    }
}

ISceneController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISceneController {
    void LoadResources ();
    void CreatePatrols ();
    void CreateMore ();
}

SceneController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using com.myspace;

public class SceneController : MonoBehaviour, ISceneController, IUserAction, IScore, Handle {

    public GameObject player;

    private SSDirector director;
    private bool canOperation;
    private bool create;
    private int bearNum;
    private int ellephantNum;
    private Subject sub;
    private Animator ani;

    private Vector3 movement;   // The vector to store the direction of the player's movement.

    void Awake () {
        director = SSDirector.getInstance ();
        sub = player.GetComponent<Player> ();
        ani = player.GetComponent<Animator> ();
        director.currentScene = this;
        director.currentScene.LoadResources ();
        director.currentScene.CreatePatrols ();
        Handle sc = director.currentScene as Handle;
        sub.Attach (sc);
        GetComponent<ScoreManager> ().resetScore ();
        bearNum = 0;
        ellephantNum = 0;
        create = false;
    }

    void Update () {
        int score = GetComponent<ScoreManager> ().getScore ();
        if (score % 10 == 0) {
            director.currentScene.CreateMore ();
        } else {
            create = true;
        }
    }

    #region ISceneController
    public void LoadResources () {
        GameObject Environment = Instantiate<GameObject> (
            Resources.Load<GameObject> ("Prefabs/Environment"));
        Environment.name = "Environment";
    }

    public void CreatePatrols () {      //创建游戏开始时的巡逻兵
        PatrolFactory pf = PatrolFactory.getInstance ();
        for (int i = 1; i <= 12; i++) {
            GameObject patrol = pf.getPatrol ();
            patrol.name = "Patrol" + ++bearNum;
            Handle p = patrol.GetComponent<Patrol> ();
            sub.Attach (p);
            patrol.GetComponent<Patrol> ().register (GetComponent<ScoreManager> ().addScore);
        }
    }

    public void CreateMore () {     //每增加十分,创建新的巡逻兵
        if (create) {
            PatrolFactory pf = PatrolFactory.getInstance ();
            for (int i = 1; i <= 3; i++) {
                GameObject patrol = pf.getPatrol ();
                patrol.name = "Patrol" + ++ellephantNum;
                Handle p = patrol.GetComponent<Patrol> ();
                sub.Attach (p);
                patrol.GetComponent<Patrol> ().register (GetComponent<ScoreManager> ().addScore);
            }
            for (int i = 1; i <= 3; i++) {
                GameObject patrolplus = pf.getPatrolPlus ();
                patrolplus.name = "Patrolplus" + ++bearNum;
                Handle p = patrolplus.GetComponent<Patrol> ();
                sub.Attach (p);
                patrolplus.GetComponent<Patrol> ().register (GetComponent<ScoreManager> ().addScore);
            }
            create = false;
        }
    }
    #endregion

    #region IUserAction
    public void movePlayer (float h, float v) {
        if (canOperation) {
            player.GetComponent<Player> ().move (h, v);
            if (h == 0 && v == 0) {
                ani.SetTrigger ("stop");
            } else {
                ani.SetTrigger ("move");
            }
        }
    }

    public void setDirection (float h, float v) {
        if (canOperation) {
            player.GetComponent<Player> ().turn (h, v);
        }
    }

    public bool GameOver () {
        return (!canOperation);
    }
    #endregion

    #region ISceneController
    public int currentScore () {
        return GetComponent<ScoreManager> ().getScore ();
    }
    #endregion

    #region Handele
    public void Reaction (bool isLive, Vector3 pos) {
        ani.SetBool ("live", isLive);
        canOperation = isLive;
    }
    #endregion
}

IUserAction.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IUserAction {
    void movePlayer (float h, float v);
    void setDirection (float h, float v);
    bool GameOver ();
}

UI.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class UI : MonoBehaviour {

    private IUserAction action;
    private IScore score;

    public Transform player;            // The position that that camera will be following.
    public float smoothing = 5f;        // The speed with which the camera will be following.

    public Text s;
    public Text gg;
    public Button re;

    Vector3 offset;                     // The initial offset from the player.

    // Use this for initialization
    void Start () {
        action = SSDirector.getInstance ().currentScene as IUserAction;
        score = SSDirector.getInstance ().currentScene as IScore;
        // Calculate the initial offset.
        offset = transform.position - player.position;

        re.gameObject.SetActive (false);
        Button btn = re.GetComponent<Button> ();
        btn.onClick.AddListener(restart);
    }

    void Update () {
        // Create a postion the camera is aiming for based on the offset from the player.
        Vector3 playerCamPos = player.position + offset;
        // Smoothly interpolate between the camera's current position and it's player position.
        transform.position = Vector3.Lerp (transform.position, playerCamPos, smoothing * Time.deltaTime);

        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        move (h, v);
        turn (h, v);
        showScore ();
        gameOver ();
    }

    //移动玩家
    public void move (float h, float v) {
        action.movePlayer (h, v);
    }

    //使玩家面向移动方向
    public void turn (float h, float v) {
        if (h != 0 || v != 0) {
            action.setDirection (h, v);
        }
    }

    //显示分数
    public void showScore () {
        s.text = "Score : " + score.currentScore ();
    }

    //游戏结束
    public void gameOver () {
        if (action.GameOver ()) {
            if (!re.isActiveAndEnabled) {
                re.gameObject.SetActive (true);
            }
            gg.text = "Game Over!";
        }
    }

    //重新开始
    public void restart () {
        SceneManager.LoadScene ("main");
    }
}

Subject.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Subject : MonoBehaviour {

    protected List<Handle> listeners = new List<Handle> ();

    public abstract void Attach (Handle listener);

    public abstract void Detach (Handle listener);

    public abstract void Notify (bool live, Vector3 pos);
}

Handle.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface Handle {
    void Reaction (bool isLive, Vector3 pos);
}

Player.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : Subject {

    private bool isLive;
    private Vector3 position;
    private float speed;

    Vector3 movement;   // The vector to store the direction of the player's movement.

    protected List<Handle> handles = new List<Handle> ();   //所有观察者

    // Use this for initialization
    void Start () {
        isLive = true;
        speed = 8.0f;
    }

    public override void Attach (Handle h) {
        handles.Add (h);
    }

    public override void Detach (Handle h) {
        handles.Remove (h);
    }

    public override void Notify (bool live, Vector3 pos) {
        foreach (Handle h in handles) {
            h.Reaction(live, pos);
        }
    }

    //玩家碰到巡逻兵,就死亡
    void OnCollisionEnter (Collision other) {
        if (other.gameObject.tag == "patrol") {
            isLive = false;
        }
    }

    // Update is called once per frame
    void Update () {
        position = transform.position;
        Notify (isLive, position);
    }

    public void move (float h, float v) {
        if (isLive) {
            // Set the movement vector based on the axis input.
            movement.Set (h, 0f, v);
            // Normalise the movement vector and make it proportional to the speed per second.
            movement = movement.normalized * speed * Time.deltaTime;
            // Move the player to it's current position plus the movement.
            GetComponent<Rigidbody> ().MovePosition (transform.position + movement);
        }
    }

    public void turn (float h, float v) {
        if (isLive) {
            // Set the movement vector based on the axis input.
            movement.Set (h, 0f, v);
            Quaternion rot = Quaternion.LookRotation (movement);
            // Set the player's rotation to this new rotation.
            GetComponent<Rigidbody> ().rotation = rot;
        }
    }
}

Patrol.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Patrol : MonoBehaviour, Handle {

    protected Vector3 bothPos;

    private bool playerIsLive;
    private Vector3 playerPos;
    private Vector3[] posSet;
    private int currentSide;
    private int sideNum;

    private bool turn;
    private bool isCatching = false;

    public int score = 1;
    public float field = 7f;
    public float speed = 1f;

    public delegate void getScore (int n);
    public event getScore escape;

    public void register (getScore s) {
        escape += s;
    }

    public void unRegister (getScore s) {
        escape -= s;
    }

    // Use this for initialization
    void Start () {
        transform.position = getBothPos ();
        bothPos = transform.position;
    }

    void Awake () {
        turn = false;
        sideNum = Random.Range (3, 6);
        currentSide = 0;
        if (sideNum == 3) {
            posSet = new Vector3[] { new Vector3 (0, 0, 0), new Vector3 (8, 0, 0),
                new Vector3 (4, 0, 6), new Vector3 (0, 0, 0) };
        } else if (sideNum == 4) {
            posSet = new Vector3[] { new Vector3 (0, 0, 0), new Vector3 (8, 0, 0),
                new Vector3 (8, 0, 8), new Vector3 (0, 0, 8), new Vector3 (0, 0, 0) };
        } else {
            posSet = new Vector3[] { new Vector3 (0, 0, 0), new Vector3 (5, 0, 0),
                new Vector3 (7, 0, 5), new Vector3 (3, 0, 8), new Vector3 (-2, 0, 5), new Vector3 (0, 0, 0) };
        }
    }

    void OnCollisionEnter (Collision other) {
        turn = true;
    }

    public bool inField (Vector3 targetPos) {
        float distance = (transform.position - targetPos).sqrMagnitude;
        if (distance <= field * field) {
            return true;
        }
        return false;
    }

    public void Reaction (bool isLive, Vector3 pos) {
        playerIsLive = isLive;
        playerPos = pos;
    }

    public void catchPlayer () {
        bothPos = transform.position;
        isCatching = true;
        transform.LookAt (playerPos);
        transform.position = Vector3.Lerp (transform.position, playerPos, speed * Time.deltaTime);
    }

    public bool patrolInMap (int side) {
        if (isCatching && playerIsLive) {
            isCatching = false;
            if (escape != null) {
                escape (score);
            }
        }
        if (turn) {
            turn = false;
            Vector3 v = transform.forward;
            Quaternion dir = Quaternion.LookRotation (v);
            Quaternion toDir = Quaternion.LookRotation (-v);
            transform.rotation = Quaternion.RotateTowards (dir, toDir, 1f);
            return true;
        }
        if (transform.position != bothPos + posSet [side + 1]) {
            transform.LookAt (bothPos + posSet [side + 1]);
            transform.position = Vector3.Lerp (transform.position ,
                bothPos + posSet [side + 1], speed * Time.deltaTime);
        }
        if ((transform.position - (bothPos + posSet [side + 1])).sqrMagnitude <= 0.1f) {
            return true;
        }
        return false;
    }

    public Vector3 getBothPos () {
        while (true) {
            Vector3 pos = new Vector3 (Random.Range (-30f, 30f), 0, Random.Range (-30f, 30f));
            if ((pos - Vector3.zero).sqrMagnitude >= 100f) {
                return pos;
            }
        }
    }

    // Update is called once per frame
    void Update () {
        if (playerIsLive && inField (playerPos)) {
            catchPlayer ();
        } else {
            if (patrolInMap (currentSide)) {
                if (++currentSide >= sideNum) {
                    bothPos = transform.position;
                    currentSide = 0;
                }
            }
        }
    }
}

PatrolFactory.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace com.myspace{

    public class PatrolFactory : System.Object {

        private static PatrolFactory instance;

        public static PatrolFactory getInstance () {
            if (instance == null) {
                instance = new PatrolFactory ();
            }
            return instance;
        }

        public GameObject getPatrol () {
            GameObject patrol = GameObject.Instantiate<GameObject> (
                    Resources.Load<GameObject> ("Prefabs/Patrol"));;
            return patrol;
        }

        public GameObject getPatrolPlus () {
            GameObject patrolplus = GameObject.Instantiate<GameObject> (
                Resources.Load<GameObject> ("Prefabs/Patrolplus"));;
            return patrolplus;
        }

        public void freePatrol (GameObject p) {
            p.SetActive (false);
        }
    }
}

IScore.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IScore {
    int currentScore ();
}

ScoreManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreManager : MonoBehaviour, Handle {

    private int score;
    private bool playerIsLive;

    public void Reaction (bool isLive, Vector3 pos) {
        playerIsLive = isLive;
    }

    public int getScore () {
        return score;
    }

    public void addScore (int s) {
        if (playerIsLive) {
            score += s;
        }
    }

    public void resetScore () {
        score = 0;
    }

    void Awake () {
        playerIsLive = true;
        score = 0;
    }

    void Update () {

    }
}

猜你喜欢

转载自blog.csdn.net/qq_36494170/article/details/80286394