Unity3D-- 简单巡逻兵 (订阅与发布模式)

本文介绍了如何使用Unity3D设计一款巡逻兵小游戏,游戏规则包括玩家移动、避开巡逻兵和收集水晶。文章详细阐述了设计要求、游戏预览,并重点讲解了运用订阅与发布模式进行程序设计,同时提到了工厂模式用于生产巡逻兵。设计过程中涉及地图、预制、碰撞器和刚体组件的配置,以及订阅与发布模式在游戏事件响应中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

巡逻兵小游戏

1、设计要求

  • 游戏规则 
    使用WSAD或方向键上下左右移动player,进入巡逻兵的追捕后逃脱可积累一分,若与巡逻兵碰撞则游戏结束,收集完地图上的所有水晶即可获胜。

  • 游戏设计要求:

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

    • 必须使用订阅与发布模式传消息
    • 工厂模式生产巡逻兵

2、游戏预览

附上录制小视频的地址:  https://www.bilibili.com/video/av23317658/

游戏截图:


3、设计过程

1、首先上unity3d的官网上的asset store上找一些能用的免费的资源

2、设计预制

利用找到的免费资源和一些unity自带的模型设计各种你需要的预制,这些都是游戏中可能会使用到的。我的预制有这几种:地图、玩家以及巡逻兵。

地图的话,可以按照自己的喜好来设计,设置墙体、障碍以及金币什么的都是可以的,按自己喜欢的来

然后我们需要在地图、玩家以及巡逻兵身上设计各种碰撞器,另外我们还需要在玩家和巡逻兵身上设置刚体组件

3、编写程序

这次作业老师要求我们使用订阅与发布模式来传消息,刚接触到这个概念的时候还是十分陌生的,那么究竟什么是订阅与发布模式呢?首先我们来思考这样一个问题,假设一个程序的后台有这样一种功能:

当产生警告的时候,程序需要执行若干种操作来进行响应。我们想象一下,如果产生警告的时候我们需要做好几十件事情的话,那么这个类是有多复杂。所以我们需要把报警信息和各种方法解耦合,这个时候就需要用到我们的订阅与发布模式了。

                        

事件触发者称为消息发布者,即图中的P。事件接受称为消息订阅者,即图中的S。P与S之间通过S.P(即订阅器)连接。这样就实现了P与S的解耦。首先,P就把消息发送到指定的订阅器上,S如果想接收消息,就要向订阅器进行订阅,订阅成功后,S就可以接收来自S.P的消息了同理,S还可以向S.P进行退订操作,成功退订后,S就无法接收到来自指定S.P的消息了。这样就完美的解决了P与S之间的解耦。

4、UML图


5、项目代码

AreaCollide.cs

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

public class AreaCollide : MonoBehaviour
{
    public int sign = 0;
    FirstSceneController sceneController;
    private void Start()
    {
        sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        //标记玩家进入自己的区域
        if (collider.gameObject.tag == "Player")
        {
            sceneController.wall_sign = sign;
        }
    }
}
FirstSceneController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;






public interface ISceneController
{
	//加载场景资源
	void LoadResources();
}

public interface IUserAction                          
{
	//移动玩家
	void MovePlayer(float translationX, float translationZ);
	//得到分数
	int GetScore();
	//得到水晶数量
	bool GetGameover();
	//重新开始
	void Restart();
}

public interface ISSActionCallback
{
	void SSActionEvent(SSAction source,int intParam = 0,GameObject objectParam = null);
}

public interface IGameStatusOp
{
	void PlayerEscape();
	void PlayerGameover();
}


public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
    public PropFactory patrol_factory;                               //巡逻者工厂
    public ScoreRecorder recorder;                                   //记录员
    public PatrolActionManager action_manager;                       //运动管理器
    public int wall_sign = -1;                                       //当前玩家所处哪个格子
    public GameObject player;                                        //玩家
    public Camera main_camera;                                       //主相机
    public float player_speed = 5;                                   //玩家移动速度
    public float rotate_speed = 135f;                                //玩家旋转速度
    private List<GameObject> patrols;                                //场景中巡逻者列表
    private bool game_over = false;                                  //游戏结束

    void Update()
    {
        for (int i = 0; i < patrols.Count; i++)
        {
            patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
        }
    }
    void Start()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentScenceController = this;
        patrol_factory = Singleton<PropFactory>.Instance;
        action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;
        LoadResources();
        //main_camera.GetComponent<CameraFlow>().follow = player;
        recorder = Singleton<ScoreRecorder>.Instance;
    }

    public void LoadResources()
    {
        Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
		player = Instantiate(Resources.Load("Prefabs/hope"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        patrols = patrol_factory.GetPatrols();
        //所有侦察兵移动
        for (int i = 0; i < patrols.Count; i++)
        {
            action_manager.GoPatrol(patrols[i]);
        }
    }
    //玩家移动
    public void MovePlayer(float translationX, float translationZ)
    {
        if(!game_over)
        {
            if (translationX != 0 || translationZ != 0)
            {
                player.GetComponent<Animator>().SetBool("run", true);
            }
            else
            {
                player.GetComponent<Animator>().SetBool("run", false);
            }
            //移动和旋转
            player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime);
            player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);
            //防止碰撞带来的移动
			player.GetComponent<Rigidbody>().freezeRotation = true;
			//防止上天入地
            if (player.transform.position.y != 0)
            {
                player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
            }     
        }
    }

    public int GetScore()
    {
        return recorder.GetScore();
    }

    public bool GetGameover()
    {
        return game_over;
    }
    public void Restart()
    {
        SceneManager.LoadScene("Scenes/mySence");
    }

    void OnEnable()
    {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
        
    }
    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
       
    }
    void AddScore()
    {
        recorder.AddScore();
    }
    void Gameover()
    {
        game_over = true;
        patrol_factory.StopPatrol();
        action_manager.DestroyAllAction();
    }
}
GameEventManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameEventManager : MonoBehaviour
{
    //分数变化
	//delegate是委托类型的声明, 能够持有对某个方法的引用的类,需要匹配签名
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //游戏结束变化
    public delegate void GameoverEvent();
	public static event GameoverEvent GameoverChange;
    //玩家逃脱
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //玩家被捕
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
}
PatrolCollide.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolCollide : MonoBehaviour
{
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            //玩家进入侦察兵追捕范围
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;
        }
    }
    void OnTriggerExit(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = false;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = null;
        }
    }
}
PatrolData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolData : MonoBehaviour
{
	public int sign;                      //标志巡逻兵在哪一块区域
	public bool follow_player = false;    //是否跟随玩家
	public int wall_sign = -1;            //当前玩家所在区域标志
	public GameObject player;             //玩家游戏对象
	public Vector3 start_position;        //当前巡逻兵初始位置     
}

PlayerCollide.cs

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

public class PlayerCollide : MonoBehaviour
{

    void OnCollisionEnter(Collision other)
    {
        //当玩家与侦察兵相撞
        if (other.gameObject.tag == "Player")
        {
            other.gameObject.GetComponent<Animator>().SetTrigger("death");
            this.GetComponent<Animator>().SetTrigger("shoot");
            Singleton<GameEventManager>.Instance.PlayerGameover();
        }
    }
}

PropFactory.cs

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

public class PropFactory : MonoBehaviour
{
    private GameObject patrol = null;                              //巡逻兵
    private List<GameObject> used = new List<GameObject>();        //正在被使用的巡逻兵
    private Vector3[] vec = new Vector3[9];                        //保存每个巡逻兵的初始位置

    public FirstSceneController sceneControler;                    //场景控制器

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -6, 4, 13 };
        int[] pos_z = { -4, 6, -13 };
        int index = 0;
        //生成不同的巡逻兵初始位置
        for(int i=0;i < 3;i++)
        {
            for(int j=0;j < 3;j++)
            {
                vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
                index++;
            }
        }
        for(int i=0; i < 9; i++)
        {
			patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().start_position = vec[i];
            used.Add(patrol);
        }   
        return used;
    }
		
    public void StopPatrol()
    {
        //切换所有侦查兵的动画
        for (int i = 0; i < used.Count; i++)
        {
            used[i].gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }
}
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;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //动作完成后的消息通知者

    protected SSAction() { }
    //子类可以使用下面这两个函数
    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }
    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

public class GoPatrolAction : SSAction
{
	private enum Dirction { EAST, NORTH, WEST, SOUTH };
	private float pos_x, pos_z;                 //移动前的初始x和z方向坐标
	private float move_length;                  //移动的长度
	private float move_speed = 1.2f;            //移动速度
	private bool move_sign = true;              //是否到达目的地
	private Dirction dirction = Dirction.EAST;  //移动的方向
	private PatrolData data;                    //侦察兵的数据


	private GoPatrolAction() { }
	public static GoPatrolAction GetSSAction(Vector3 location)
	{
		GoPatrolAction action = CreateInstance<GoPatrolAction>();
		action.pos_x = location.x;
		action.pos_z = location.z;
		//设定移动矩形的边长
		action.move_length = Random.Range(4, 7);
		return action;
	}
	public override void Update()
	{
		//防止碰撞发生后的旋转,这里的参数设置为true即为禁止旋转
		gameobject.GetComponent<Rigidbody>().freezeRotation = true;

		// 这里是防止因碰撞使得巡逻兵掉到地下或者飞上天了
		if (transform.position.y != 0)
		{
			transform.position = new Vector3(transform.position.x, 0, transform.position.z);
		}
		//每帧都刷新移动策略,侦察移动
		Gopatrol();
		//如果侦察兵需要跟随玩家并且玩家就在侦察兵所在的区域,侦查动作结束
		if (data.follow_player && data.wall_sign == data.sign)
		{
			this.destroy = true;
			this.callback.SSActionEvent(this,0,this.gameobject);
		}
	}
	public override void Start()
	{
		this.gameobject.GetComponent<Animator>().SetBool("run", true);
		data  = this.gameobject.GetComponent<PatrolData>();
	}

	void Gopatrol()
	{
		// 到达了一条边的尽头就要换个方向继续进行移动
		if (move_sign)
		{
			//不需要转向则设定一个目的地,按照矩形移动
			switch (dirction)
			{
			case Dirction.EAST:
				pos_x -= move_length;
				break;
			case Dirction.NORTH:
				pos_z += move_length;
				break;
			case Dirction.WEST:
				pos_x += move_length;
				break;
			case Dirction.SOUTH:
				pos_z -= move_length;
				break;
			}
			move_sign = false;
		}
		// 巡逻兵旋转自身,老是朝着Vector3所在的位置
		this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
		float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
		//当前位置与目的地距离浮点数的比较
		//如果当前位置与目标位置相差大于0.9,则换一个方向继续巡逻
		if (distance > 0.9)
		{
			transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
		}
		else
		{
			dirction = dirction + 1;
			if(dirction > Dirction.SOUTH)
			{
				dirction = Dirction.EAST;
			}
			move_sign = true;
		}
	}
}

public class PatrolFollowAction : SSAction
{
	private float speed = 2f;            //跟随玩家的速度
	private GameObject player;           //玩家
	private PatrolData data;             //侦查兵数据

	private PatrolFollowAction() { }
	public static PatrolFollowAction GetSSAction(GameObject player)
	{
		PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
		action.player = player;
		return action;
	}

	public override void Update()
	{
		//防止碰撞后发生旋转
		gameobject.GetComponent<Rigidbody> ().freezeRotation = true;
		//防止碰撞后上天入地,老把对象固定在地上
		if (transform.position.y != 0)
		{
			transform.position = new Vector3(transform.position.x, 0, transform.position.z);
		}
		//每帧都刷新朝向目标的角度,永远对准这target
		Follow();
		//如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内
		//离开追捕范围需要重新开始巡逻
		if (!data.follow_player || data.wall_sign != data.sign)
		{
			this.destroy = true;
			// 回调函数提高并行度和处理效率
			this.callback.SSActionEvent(this,1,this.gameobject);
		}
	}
	public override void Start()
	{
		data = this.gameobject.GetComponent<PatrolData>();
	}
	void Follow()
	{

		transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
		// 旋转自身使得自身老是朝着目标对象target所在的位置
		this.transform.LookAt(player.transform.position);
	}
}

SSActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSActionManager : MonoBehaviour, ISSActionCallback
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合
    private List<SSAction> waitingAdd = new List<SSAction>();                       //等待去执行的动作列表
    private List<int> waitingDelete = new List<int>();                              //等待删除的动作的key                

    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());
            }
            else if (ac.enable)
            {
                //运动学运动更新
                ac.Update();
            }
        }

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

    public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
    {
        if(intParam == 0)
        {
            //侦查兵跟随玩家
			//获取靠近玩家的方法放在follow里
            PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            //侦察兵按照初始位置开始继续巡逻
            GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
            this.RunAction(objectParam, move, this);
            //玩家逃脱
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }

	// 游戏结束后,摧毁所有动作,巡逻兵不再移动
    public void DestroyAll()
    {
        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            ac.destroy = true;
        }
    }
}

// 这个类可以合并进入SSActionManager
public class PatrolActionManager : SSActionManager
{
	private GoPatrolAction go_patrol;                            //巡逻兵巡逻
	//场景控制器调用此类中的方法,让巡逻兵开始巡逻
	public void GoPatrol(GameObject patrol)
	{
		go_patrol = GoPatrolAction.GetSSAction(patrol.transform.position);
		this.RunAction(patrol, go_patrol, this);
	}
	//游戏结束的时候,停止所有动作
	public void DestroyAllAction()
	{
		DestroyAll();
	}
}
SSDirector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSDirector : System.Object
{
    private static SSDirector _instance;             //导演类的实例
    public ISceneController CurrentScenceController { get; set; }
    public static SSDirector GetInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

ScoreRecorder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour
{
    public FirstSceneController sceneController;
    public int score = 0;                            //分数
   

    // Use this for initialization
    void Start()
    {
        sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;
        sceneController.recorder = this;
    }
    public int GetScore()
    {
        return score;
    }
    public void AddScore()
    {
        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 there is none.");
                }
            }
            return instance;
        }
    }
}

UserGUI.cs

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

public class UserGUI : MonoBehaviour {

    private IUserAction action;
    private GUIStyle score_style = new GUIStyle();
    private GUIStyle text_style = new GUIStyle();
    private GUIStyle over_style = new GUIStyle();
    public  int show_time = 8;                         //展示提示的时间长度
    void Start ()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
        text_style.normal.textColor = new Color(0, 0, 0, 1);
        text_style.fontSize = 16;
        score_style.normal.textColor = new Color(1,0.92f,0.016f,1);
        score_style.fontSize = 16;
        over_style.fontSize = 25;
        //展示提示
        StartCoroutine(ShowTip());
    }

    void Update()
    {
        //获取方向键的偏移量
        float translationX = Input.GetAxis("Horizontal");
        float translationZ = Input.GetAxis("Vertical");
        //移动玩家
        action.MovePlayer(translationX, translationZ);
    }
    private void OnGUI()
    {
		GUI.Label(new Rect(10, 5, 200, 50), "分数:", text_style);
		GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), score_style);
        if(action.GetGameover())
        {
			
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏结束", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始"))
            {
                action.Restart();
                return;
            }
        }
        if(show_time > 0)
        {
            GUI.Label(new Rect(Screen.width / 2-80 ,10, 100, 100), "按WSAD或方向键移动", text_style);
        }
    }

    public IEnumerator ShowTip()
    {
        while (show_time >= 0)
        {
            yield return new WaitForSeconds(1);
            show_time--;
        }
    }
}


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值