自动巡逻兵

对于这次的作业,想要弄得好,好看的人物模型自然是必不可少的。直接从unitystore里下载相关的资源。





巡逻兵设计

    设计回调函数,用来决定下一个动作做什么

public void SSEventAction(SSAction source,SSActionEventType events=SSActionEventType.COMPLETED,int intParam=0,string strParam=null,Object objParam = null) {
        // 改变当前状态
        currentState = currentState > ActionState.WALKBACK ? ActionState.IDLE : (ActionState)((int)currentState + 1);

        // 执行下个动作
        switch (currentState) {
            case ActionState.WALKLEFT:
                walkLeft();
                break;
            case ActionState.WALKRIGHT:
                walkRight();
                break;
            case ActionState.WALKFORWARD:
                walkForward();
                break;
            case ActionState.WALKBACK:
                walkBack();
                break;
            default:
                idle();
                break;
        }
    }

turnNextDirection() 函数用使物体当碰到墙壁或者出了巡逻区域后,往反方向走。

public void turnNextDirection() {
        // 销毁当前动作
        currentAction.destory = true;

        // 往相反方向走
        switch (currentState) {
            case ActionState.WALKLEFT:
                currentState = ActionState.WALKRIGHT;
                walkRight();
                break;
            case ActionState.WALKRIGHT:
                currentState = ActionState.WALKLEFT;
                walkLeft();
                break;
            case ActionState.WALKFORWARD:
                currentState = ActionState.WALKBACK;
                walkBack();
                break;
            case ActionState.WALKBACK:
                currentState = ActionState.WALKFORWARD;
                walkForward();
                break;
        }
    }

本次设计的主要是使用了发布订阅模式。



这个模式的用法就是当在玩家身上遇到了一些事件时,比如在进入怪物攻击范围是可以通过trigger的实例发布相应的消息,而根据相应回复的消息内容,就如微信公众号一般,有对该公众号感兴趣的订阅者,就会做出相应的回应动作,比如怪物回去追击玩家,加分。


下面是相应的代码

ActorController.cs

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

[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]

public class ActorController : MonoBehaviour {

    private Animator ani;
    private AnimatorStateInfo currentBaseState;
    private Rigidbody rig;

    private Vector3 velocity;
    // 旋转速度,奔跑速度
    private float rotateSpeed = 15f;
    private float runSpeed = 5f;
    
	void Start () {
        ani = GetComponent<Animator>();
        rig = GetComponent<Rigidbody>();
	}

    void FixedUpdate() {
        //如果小姐姐死亡,不执行所有动作
        if (!ani.GetBool("isAlive")) return;

        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        //设置速度,用来判断做哪个动作
        ani.SetFloat("Speed", Mathf.Max(Mathf.Abs(x), Mathf.Abs(z)));
        //跑步时候的动画速度
        ani.speed = 1 + ani.GetFloat("Speed") / 3;

        velocity = new Vector3(x, 0, z);

        if (x != 0 || z != 0) {
            Quaternion rotation = Quaternion.LookRotation(velocity);
            //ani正面的方向就是z轴正方向
            //如果需要转向,球形插值转向
            if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.fixedDeltaTime * rotateSpeed);
        }
        //小姐姐位置移动   
        transform.position += velocity * Time.fixedDeltaTime * runSpeed;
    }

    // 用于检测Actor进入某个区域
    private void OnTriggerEnter(Collider other) {
        if (other.gameObject.CompareTag("Area")) {
            //进入区域后,发布消息
            Publish publish = Publisher.getInstance();
            int patrolType = other.gameObject.name[other.gameObject.name.Length - 1] - '0';
            publish.notify(ActorState.ENTER_AREA, patrolType, this.gameObject);
        }
    }

    /// 用于检测Actor与Patrol碰撞后死亡
    private void OnCollisionEnter(Collision collision) {
        if (collision.gameObject.CompareTag("guawu") && ani.GetBool("isAlive")) {
            Debug.Log("death");
            //调整相应条件以满足状态转移,执行死亡动作
            ani.SetBool("isAlive", false);
            ani.SetTrigger("toDie");
            //碰撞后,发布死亡信息
            Publisher publisher = Publisher.getInstance();
            publisher.notify(ActorState.DEATH, 0, null);
        }
    }
}

BaseAction.cs

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

namespace Tem.Action {
    public enum SSActionEventType : int { STARTED, COMPLETED }

    public interface ISSActionCallback {
        void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED,
            int intParam = 0, string strParam = null, Object objParam = null);
    }

    
    public class SSAction : ScriptableObject {
        public bool enable = true;
        public bool destory = false;

        public GameObject gameObject { get; set; }
        public Transform transform { get; set; }
        public ISSActionCallback callback { get; set; }
        
        public virtual void Start() {
            throw new System.NotImplementedException("Action Start Error!");
        }

        public virtual void FixedUpdate() {
            throw new System.NotImplementedException("Physics Action Start Error!");
        }

        public virtual void Update() {
            throw new System.NotImplementedException("Action Update Error!");
        }
    }

    public class CCSequenceAction : SSAction, ISSActionCallback {
        public List<SSAction> sequence;
        public int repeat = -1;
        public int start = 0;

        public static CCSequenceAction GetSSAction(List<SSAction> _sequence,int _start = 0,int _repeat = 1) {
            CCSequenceAction actions = ScriptableObject.CreateInstance<CCSequenceAction>();
            actions.sequence = _sequence;
            actions.start = _start;
            actions.repeat = _repeat;
            return actions;
        }

        public override void Start() {
            foreach (SSAction ac in sequence) {
                ac.gameObject = this.gameObject;
                ac.transform = this.transform;
                ac.callback = this;
                ac.Start();
            }
        }

        public override void Update() {
            if (sequence.Count == 0) return;
            if (start < sequence.Count) sequence[start].Update();
        }

        public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED,
            int intParam = 0, string strParam = null, Object objParam = null) //通过对callback函数的调用执行下个动作
        {
            source.destory = false;
            this.start++;
            if (this.start >= this.sequence.Count) {
                this.start = 0;
                if (this.repeat > 0) this.repeat--;
                if (this.repeat == 0) {
                    this.destory = true;
                    this.callback.SSEventAction(this);
                }
            }
        }

        private void OnDestroy() {
            this.destory = true;
        }
    }

    public class IdleAction : SSAction {
        // 站立持续时间
        private float time;
        private Animator ani;

        public static IdleAction GetIdleAction(float time,Animator ani) {
            IdleAction currentAction = ScriptableObject.CreateInstance<IdleAction>();
            currentAction.time = time;
            currentAction.ani = ani;
            return currentAction;
        }

        public override void Start() {
            // 进入站立状态
            ani.SetFloat("Speed", 0);
        }

        public override void Update() {
            // 永久站立
            if (time == -1) return;
            // 减去站立时间
            time -= Time.deltaTime;
            if (time < 0) {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }
    }

    public class WalkAction : SSAction {

        // 移动速度和目标地点
        private float speed;
        private Vector3 target;
        private Animator ani;

        public static WalkAction GetWalkAction(Vector3 target,float speed,Animator ani) {
            WalkAction currentAction = ScriptableObject.CreateInstance<WalkAction>();
            currentAction.speed = speed;
            currentAction.target = target;
            currentAction.ani = ani;
            return currentAction;
        }

        public override void Start() {
            // 进入走路状态
            //Debug.Log("walk");
            ani.SetFloat("Speed", 0.5f);
        }

        public override void Update() {
            Quaternion rotation = Quaternion.LookRotation(target - transform.position);
            // 进行转向,转向目标方向
            if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5);
            //沿着直线方向走到目标位置
            this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
            if (this.transform.position == target) {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }
    }

    public class RunAction : SSAction {
        // 移动速度和人物的transform
        private float speed;
        private Transform target;
        private Animator ani;

        public static RunAction GetRunAction(Transform target,float speed,Animator ani) {
            RunAction currentAction = ScriptableObject.CreateInstance<RunAction>();
            currentAction.speed = speed;
            currentAction.target = target;
            currentAction.ani = ani;
            return currentAction;
        }

        public override void Start() {
            // 进入跑步状态
            ani.SetFloat("Speed", 1);
        }

        public override void Update() {
            Quaternion rotation = Quaternion.LookRotation(target.position - transform.position);
            // 进行转向,转向目标方向
            if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5);

            this.transform.position = Vector3.MoveTowards(this.transform.position, target.position, speed * Time.deltaTime);
            if (Vector3.Distance(this.transform.position, target.position) < 0.5) {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }
    }

    public class SSActionManager : MonoBehaviour {
        private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>();
        private List<SSAction> waitingAddAction = new List<SSAction>();
        private List<int> waitingDelete = new List<int>();

        protected void Start() {

        }

        protected void Update() {
            // 将待加入动作加入dictionary执行
            foreach (SSAction ac in waitingAddAction) dictionary[ac.GetInstanceID()] = ac;
            waitingAddAction.Clear();

            // 判断waitingList中的动作对是否执行,如果要删除,加入要删除的list,否则更新
            foreach (KeyValuePair<int,SSAction> dic in dictionary) {
                SSAction ac = dic.Value;
                if (ac.destory) waitingDelete.Add(ac.GetInstanceID());
                else if (ac.enable) ac.Update();
            }

            foreach (int id in waitingDelete) {
                SSAction ac = dictionary[id];
                dictionary.Remove(id);
                DestroyObject(ac);
            }

            // 将deletelist中的动作删除
            waitingDelete.Clear();
        }

        public void runAction(GameObject gameObject,SSAction action,ISSActionCallback callback) {
            action.gameObject = gameObject;
            action.transform = gameObject.transform;
            action.callback = callback;
            waitingAddAction.Add(action);
            action.Start();
        }
    }

    public class PYActionManager : MonoBehaviour {
        private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>();
        private List<SSAction> waitingAddAction = new List<SSAction>();
        private List<int> waitingDelete = new List<int>();

        protected void Start() {
            
        }

        protected void FixedUpdate() {
            // 将待加入动作加入dictionary执行
            foreach (SSAction ac in waitingAddAction) dictionary[ac.GetInstanceID()] = ac;
            waitingAddAction.Clear();

            // 如果要删除,加入要删除的list,否则更新
            foreach (KeyValuePair<int, SSAction> dic in dictionary) {
                SSAction ac = dic.Value;
                if (ac.destory) waitingDelete.Add(ac.GetInstanceID());
                else if (ac.enable) ac.FixedUpdate();
            }

            // 将deletelist中的动作删除
            foreach (int id in waitingDelete) {
                SSAction ac = dictionary[id];
                dictionary.Remove(id);
                DestroyObject(ac);
            }
            waitingDelete.Clear();
        }

        public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback) {
            action.gameObject = gameObject;
            action.transform = gameObject.transform;
            action.callback = callback;
            waitingAddAction.Add(action);
            action.Start();
        }
    }
}

ObjectFactory.cs

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

public class ObjectFactory : MonoBehaviour {

    // 正在使用的对象链表
    private static List<GameObject> used = new List<GameObject>();
    // 正在空闲的对象链表
    private static List<GameObject> free = new List<GameObject>();

	public GameObject setObjectOnPos(Vector3 targetposition, Quaternion faceposition) {
        if (free.Count==0) {
            // 新建实例,将位置设置成为targetposition,将面向方向设置成faceposition
            GameObject aGameObject = Instantiate(Resources.Load("prefabs/guawu"), targetposition, faceposition) as GameObject;
            used.Add(aGameObject);
        }
        else {
            used.Add(free[0]);
            free.RemoveAt(0);
            used[used.Count - 1].SetActive(true);
            used[used.Count - 1].transform.position = targetposition;
            used[used.Count - 1].transform.localRotation = faceposition;
        }
        return used[used.Count - 1];
    }

    public void freeObject(GameObject obj) {
        obj.SetActive(false);
        used.Remove(obj);
        free.Add(obj);
    }

}

PublisherAndObservser.cs

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

public enum ActorState { ENTER_AREA, DEATH }

public interface Publish {
    // 发布函数
    void notify(ActorState state, int pos, GameObject actor);

    // 委托添加事件
    void add(Observer observer);

    // 委托取消事件
    void delete(Observer observer);
}

public interface Observer {
    //实现接收函数
    void notified(ActorState state, int pos, GameObject actor);
}

public class Publisher : Publish {
    private delegate void ActionUpdate(ActorState state, int pos, GameObject Actor);
    private ActionUpdate updatelist;

    private static Publisher _instance;
    public static Publisher getInstance() {
        if (_instance == null) _instance = new Publisher();
        return _instance;
    }

    public void notify(ActorState state, int pos, GameObject actor) {
        if (updatelist != null) updatelist(state, pos, actor);
    }

    public void add(Observer observer) {
        updatelist += observer.notified;
    }

    public void delete(Observer observer) {
        updatelist -= observer.notified;
    }
}

PatrolUI.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Tem.Action;

[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(CapsuleCollider))]
[RequireComponent(typeof(Rigidbody))]

public class PatrolUI : SSActionManager, ISSActionCallback, Observer {

    // 各种动作
    public enum ActionState : int { IDLE, WALKLEFT, WALKFORWARD, WALKRIGHT, WALKBACK }

    // 动作
    private Animator ani;

    // 保证当前只有一个动作
    private SSAction currentAction;
    private ActionState currentState;

    // 跑步和走路的速度
    private const float walkSpeed = 1f;
    private const float runSpeed = 3f;

    // Use this for initialization
    new void Start () {
        ani = this.gameObject.GetComponent<Animator>();

        // 添加巡逻兵的事件
        Publish publisher = Publisher.getInstance();
        publisher.add(this);

        // 开始时,静止状态
        currentState = ActionState.IDLE;
        idle();
	}
	
	// Update is called once per frame
	new void Update () {
        base.Update();
	}

    //回调函数,用来决定下一个动作做什么
    public void SSEventAction(SSAction source,SSActionEventType events=SSActionEventType.COMPLETED,int intParam=0,string strParam=null,Object objParam = null) {
        // 改变当前状态
        currentState = currentState > ActionState.WALKBACK ? ActionState.IDLE : (ActionState)((int)currentState + 1);

        // 执行下个动作
        switch (currentState) {
            case ActionState.WALKLEFT:
                walkLeft();
                break;
            case ActionState.WALKRIGHT:
                walkRight();
                break;
            case ActionState.WALKFORWARD:
                walkForward();
                break;
            case ActionState.WALKBACK:
                walkBack();
                break;
            default:
                idle();
                break;
        }
    }

    public void idle() {
        currentAction = IdleAction.GetIdleAction(Random.Range(1, 1.5f), ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    public void walkLeft() {
        Vector3 target = Vector3.left * Random.Range(3, 5) + this.transform.position;
        currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    public void walkRight() {
        Vector3 target = Vector3.right * Random.Range(3, 5) + this.transform.position;
        currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani);
        this.runAction(this.gameObject, currentAction, this);
    }
    
    public void walkForward() {
        Vector3 target = Vector3.forward * Random.Range(3, 5) + this.transform.position;
        currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    public void walkBack() {
        Vector3 target = Vector3.back * Random.Range(3, 5) + this.transform.position;
        currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    //当碰到墙壁或者出了巡逻区域后,向反方向走
    public void turnNextDirection() {
        // 销毁当前动作
        currentAction.destory = true;

        // 往相反方向走
        switch (currentState) {
            case ActionState.WALKLEFT:
                currentState = ActionState.WALKRIGHT;
                walkRight();
                break;
            case ActionState.WALKRIGHT:
                currentState = ActionState.WALKLEFT;
                walkLeft();
                break;
            case ActionState.WALKFORWARD:
                currentState = ActionState.WALKBACK;
                walkBack();
                break;
            case ActionState.WALKBACK:
                currentState = ActionState.WALKFORWARD;
                walkForward();
                break;
        }
    }

    public void getGoal(GameObject gameobject) {
        
        currentAction.destory = true;
       
        currentAction = RunAction.GetRunAction(gameobject.transform, runSpeed, ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    public void loseGoal() {
        
        currentAction.destory = true;
        
        idle();
    }

    public void stop() {
       
        currentAction.destory = true;
        currentAction = IdleAction.GetIdleAction(-1f, ani);
        this.runAction(this.gameObject, currentAction, this);
    }

    private void OnCollisionEnter(Collision collision) {
        
        Transform parent = collision.gameObject.transform.parent;
        if (parent != null && parent.CompareTag("Wall")) turnNextDirection();
    }

    private void OnTriggerEnter(Collider other) {
        // 走出巡逻区域
        if (other.gameObject.CompareTag("Door")) turnNextDirection();
    }

    
    //  name="state"   角色状态
    //  name="pos"   角色所在区域
    // name="actor"  角色
    public void notified(ActorState state, int pos, GameObject actor) {
        if (state == ActorState.ENTER_AREA) {
            // 如果进入自己的区域,进行追击
            // 如果离开自己的区域,放弃追击
            if (pos == this.gameObject.name[this.gameObject.name.Length - 1] - '0')
                getGoal(actor);
            else loseGoal();
        }
        else stop();
        // 角色死亡,结束动作
    }
}

SceneController.cs

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

public class SceneController : MonoBehaviour, Observer {
    public Text scoreText;
    public Text centerText;
    
    private ScoreRecorder record;
    private UIController UI;
    private ObjectFactory fac;

    private float[] posx = { -5, 7, -5, 5 };
    private float[] posz = { -5, -7, 5, 5 };

    
    void Start () {
        record = new ScoreRecorder();
        record.scoreText = scoreText;
        UI = new UIController();
        UI.centerText = centerText;
        fac = Singleton<ObjectFactory>.Instance;

        //订阅者添加事件
        Publish publisher = Publisher.getInstance();
        publisher.add(this);

        LoadResources();
	}
	
    private void LoadResources() {
        
        Instantiate(Resources.Load("prefabs/player1"), new Vector3(2, 0, -2), Quaternion.Euler(new Vector3(0, 180, 0)));
        for (int i = 0; i < 4; i++) {
            
            GameObject patrol = fac.setObjectOnPos(new Vector3(posx[i], 0, posz[i]), Quaternion.Euler(new Vector3(0, 180, 0)));
            patrol.name = "Patrol" + (i + 1);
        }
    }
    
    public void notified(ActorState state, int pos, GameObject actor) {
        if (state == ActorState.ENTER_AREA) record.addScore(1);
        else {
            UI.loseGame();
            Debug.Log("lose game");
        }
    }
}

ScoreRecorder.cs

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

public class ScoreRecorder {

    // 计分板
    public Text scoreText;

    // 纪录分数
    private int score = -1;

    public void resetScore() {
        score = -1;
    }

    //走入新场景加分
    public void addScore(int addscore) {
        score += addscore;
        scoreText.text = "Score:" + score;
    }

    public void setDisActive() {
        scoreText.text = "";
    }

    public void setActive() {
        scoreText.text = "Score:" + score;
    }
}

Singleton.cs

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

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
    protected static T instance;

    public static T Instance {
        get {
            if (instance == null) {
                instance = (T)FindObjectOfType(typeof(T));
                if (instance == null) {
                    Debug.LogError("An instance of " + typeof(T) +
                        " is needed in the scene, but it not!");
                }
            }
            return instance;
        }
    }
}

UIController.cs




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

public class UIController {

    public Text centerText;

    public void loseGame() {
        centerText.text = "LOSE!";
    }

    public void resetGame() {
        centerText.text = "";
    }
}

要实现Unity敌人自动巡逻,可以按照以下步骤操作: 1.创建一个敌人游戏对象,并添加一个NavMeshAgent组件。NavMeshAgent组件可以让敌人在导航网格上移动。 2.在场景中创建一个空对象作为巡逻点,将其命名为“Waypoints”。将巡逻点放置在地图上,以便敌人可以沿着它们移动。可以在Waypoints对象上创建子对象,以便更好地组织巡逻点。 3.编写一个脚本来控制敌人的巡逻行为。该脚本应该附加到敌人游戏对象上。在脚本中,可以使用NavMeshAgent组件来控制敌人的移动。 4.在脚本中定义一个数组来存储巡逻点。可以使用FindGameObjectsWithTag()函数来查找Waypoints对象,并获取其子对象作为巡逻点。 5.在脚本中定义一个变量来存储当前巡逻点的索引。在敌人接近一个巡逻点时,可以将索引增加1,以便敌人移动到下一个巡逻点。如果敌人已到达最后一个巡逻点,则应将索引重置为0,以便敌人开始从头再次巡逻。 6.在Update()函数中,可以检查敌人是否接近当前巡逻点。如果敌人已接近当前巡逻点,则应将索引增加1,并将敌人移动到下一个巡逻点。 7.在脚本中添加一些额外的逻辑,以便敌人可以在巡逻过程中检测并攻击玩家或其他敌人。 这些步骤可以帮助您实现Unity敌人的自动巡逻。需要注意的是,在实现过程中可能会遇到一些问题,需要根据具体情况进行调整和修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值