作业7-智能巡逻兵

智能巡逻兵

提交要求:

游戏设计要求:

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

程序设计要求:

  • 必须使用订阅与发布模式传消息

    • subject:OnLostGoal
    • Publisher: ?
    • Subscriber: ?
  • 工厂模式生产巡逻兵

  • 友善提示1:生成 3~5个边的凸多边型

    • 随机生成矩形
    • 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
    • 5 ?
  • 友善提示2:参考以前博客,给出自己新玩法

游戏规则:

  • 玩家每甩掉一个巡逻兵得1分,得8分获得胜利,撞到巡逻兵游戏失败。
  • 左上角显示游戏分数,左下角是小地图方便观察全景。
  • 每个方格有一个巡逻兵,当玩家进入方格后,方格内的巡逻兵会感知到玩家,自动追踪。
  • 游戏胜利或失败后,出现“Restart”按键,点击重新开始游戏。

游戏效果展示:

展示视频链接

项目链接:

hw7-Patrols

项目设置:

  1. 新建Plane预制,包括四面的外墙wall,中间分隔方块的innerwall,和每个方格的trigger。为trigger挂上AreaCollider.cs。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 从“Adventure - Sample Game”中导入Player。新建一个Animator Controller,名称为PlayerController。加上Rigidbody和Capsule Colloder部件。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 从“Toon Soldiers WW2”中导入士兵作为Patrol。新建一个Animator Controller,名称为PatrolController。加上Rigidbody和Capsule Colloder部件。挂上PatrolData.cs和PlayerCollider.cs。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 新建Second Camera,设置如下。
    在这里插入图片描述

  2. 新建空对象,挂载如下scripts。

在这里插入图片描述

代码说明

在这里插入图片描述

  1. Actions.cs: SSAction类和SSActionManager类与之前项目相同。PatrolActionManger类负责巡逻兵的运动管理,包括Patrol的运动和销毁。GoPatrolAction类负责巡逻兵巡逻的动作,视线拉巡逻兵走任意四边形。PatrolFollowAction类负责巡逻兵追逐玩家的动作,当玩家进入巡逻兵所在方格时,巡逻兵追逐玩家,当玩家离开方格时,巡逻兵不再追逐,调用回调函数,继续巡逻。
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();
	}
}

public class GoPatrolAction : SSAction
{
	private enum Dirction { E, N, W, S };
	private float pos_x, pos_z; 
	private float move_length; 
	private float move_speed = 1.2f; 
	private bool move_sign = true;
	private Dirction dirction = Dirction.E; 
	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(5, 8);
		return action;
	}
	public override void Update()
	{
		if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
			transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);          
		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()
	{
		data  = this.gameobject.GetComponent<PatrolData>();
	}

	void Gopatrol()
	{
		if (move_sign){
			switch (dirction){
			case Dirction.E:
				pos_x -= move_length;
				break;
			case Dirction.N:
				pos_z += move_length;
				break;
			case Dirction.W:
				pos_x += move_length;
				break;
			case Dirction.S:
				pos_z -= move_length;
				break;
			}
			move_sign = false;
		}
		this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
		float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
		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.S)dirction = Dirction.E;
			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()
	{
		if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
			transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);          
		if (transform.position.y != 0)
			transform.position = new Vector3(transform.position.x, 0, transform.position.z);
		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);
		this.transform.LookAt(player.transform.position);
	}
}
  1. PatrolData.cs: 定义了Patrol具有的一些属性。其中follow_player标志这个巡逻兵是否在追逐玩家,wall_sign指的是Patrol在第几个方格。
public class PatrolData : MonoBehaviour
{
    public int sign;
    public bool follow_player = false;
    public int wall_sign = -1;
    public GameObject player;
    public Vector3 start_position;
}
  1. PlayerCollider.cs: 挂载在Patrol上,当Patrol和Player碰撞时,游戏结束。
public class PlayerCollider : MonoBehaviour
{
    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.name == "Player(Clone)")
            Singleton<GameEventManager>.Instance.PlayerGameover();
    }
}
  1. PropFactory.cs: Patrol工厂类,首先得到九个巡逻兵的起始位置,然后根据预设新建巡逻兵,设置他们的位置,PatrolData中的sign和start_position,最后将它们添加到list里。
public class PropFactory : MonoBehaviour
{
    private GameObject patrol = null;  
    private List<GameObject> used = new List<GameObject>();               
    private Vector3[] pos = new Vector3[9]; 
    public Controller sceneController;

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -6, 4, 13 };
        int[] pos_z = { -4, 6, -13 };
        int[] num = {8, 1, 9, 7, 2, 6, 4, 3, 5};
        int index = 0;
        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 3; j++){
                pos[index] = new Vector3(pos_x[i], 5, pos_z[j]);
                index++;
            }
        }
        for(int i = 0; i < 9; i++){
            patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
            patrol.transform.position = pos[i];
            patrol.GetComponent<PatrolData>().sign = num[i];
            patrol.GetComponent<PatrolData>().start_position = pos[i];
            used.Add(patrol);
        }   
        return used;
    }
}

  1. AreaCollider.cs:挂载在Plane的trigger上。负责当和Player发生碰撞时,将sceneController中的wall_sign变量设置为trigger的sign,以此使sceneController知道Player在哪一个方格,需要设置所在方格内的Patrol追逐Player。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AreaCollider : MonoBehaviour
{
    public int sign = 0;
    public Controller sceneController;

    private void Start()
    {
        sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
    }
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.name == "Player(Clone)"){
            sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
            sceneController.wall_sign = sign;
        }     
    }
}

  1. Controller.cs:Update函数完成了追逐Player的Patrol的更新。LoadResource函数完成了资源的加载,主要是Plane、Player、Patrols。MovePlayer完成在游戏未结束时Player的移动和转向。
public class Controller : MonoBehaviour, IUserAction, ISceneController
{
    public PropFactory patrol_factory;
    public ScoreRecorder recorder;
    public PatrolActionManager action_manager;
    public int wall_sign = 1;
    public GameObject player;
    public float player_speed = 30; 
	public float rotate_speed = 10; 
    private List<GameObject> patrols;
    private bool game_over = false;

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

	public bool GetGameover()
    {
        return game_over;
    }

    public bool GetWin()
    {
        return recorder.score == 8;
    }

	public void Restart()
    {
        SceneManager.LoadScene(0);
    }

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

    void Update()
    {
        for (int i = 0; i < patrols.Count; i++) {
            patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
        }
		for (int i = 0; i < patrols.Count; i++){
			if (patrols [i].gameObject.GetComponent<PatrolData> ().sign == patrols [i].gameObject.GetComponent<PatrolData> ().wall_sign) {
				patrols [i].gameObject.GetComponent<PatrolData> ().follow_player = true;
				patrols [i].gameObject.GetComponent<PatrolData> ().player = player;
			} else {
				patrols [i].gameObject.GetComponent<PatrolData>().follow_player = false;
				patrols [i].gameObject.GetComponent<PatrolData>().player = null;
			}
		}
    }
    void Start()
    {
		wall_sign = 1;
        SSDirector director = SSDirector.GetInstance();
        director.CurrentSceneController = this;
        patrol_factory = Singleton<PropFactory>.Instance;
        action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;
        LoadResources();
        recorder = Singleton<ScoreRecorder>.Instance;
    }

    public void LoadResources()
    {
        Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(-10, 0, 10), Quaternion.Euler(0, 90, 0)) as GameObject;
        patrols = patrol_factory.GetPatrols();
        for (int i = 0; i < patrols.Count; i++){
            action_manager.GoPatrol(patrols[i]);
        }
    }
	public Vector3 movement;
    public void MovePlayer(float translationX, float translationZ)
    {
        if(!game_over)
        {
			Vector3 direction = new Vector3(translationX, 0, translationZ).normalized;
			player.transform.position = Vector3.MoveTowards(player.transform.position, player.transform.position+direction, player_speed * Time.deltaTime);
			player.transform.LookAt(player.transform.position+direction);
        }
    }
}
  1. ScoreRecorder.cs:负责分数的计算和返回,比较简单。
public class ScoreRecorder : MonoBehaviour
{
    public Controller sceneController;
    public int score = 0; 

    void Start() 
    {
        sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
        sceneController.recorder = this;
    }

    public int GetScore()
    {
        return score;
    }

    public void AddScore()
    {
        score++;
    }
}
  1. Interface.cs:接口。
public interface ISceneController{void LoadResources();}
public interface IUserAction                          
{
    void MovePlayer(float translationX, float translationZ);
    int GetScore();
    bool GetGameover();
    bool GetWin();
    void Restart();
}
public interface ISSActionCallback{void SSActionEvent(SSAction source,int intParam = 0,GameObject objectParam = null);}
public interface IGameStatusOp
{
    void PlayerEscape();
    void PlayerGameover();
}
  1. GameEventManager.cs:订阅与发布模式中发布事件的类。订阅者订阅GameEventManager类,当其他类使用GameEventManager的方法发布消息时,订阅者(这里是Controller)会调用相应的方法(这里是得分、游戏结束和获胜)。
public class GameEventManager : MonoBehaviour
{
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;
    public delegate void WinEvent();
    public static event WinEvent WinChange;

    public void PlayerEscape()
    {
        if (ScoreChange != null){
            ScoreChange();
        }
    }

    public void PlayerGameover()
    {
        if (GameoverChange != null){
            GameoverChange();
        }
    }

    public void PlayerWin()
    {
        if (WinChange != null){
            WinChange();
        }
    }
}
  1. UserGUI.cs:左上角显示分数。游戏失败提示GameOver,显示Restar按键。游戏获胜提示YouWin,显示Restart按键。
public class UserGUI : MonoBehaviour {

    private IUserAction action;
    private GUIStyle style = new GUIStyle();

    void Start ()
    {
        style.fontSize = 25;
        action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
    }

    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), "Score:");
		GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString());
        if(action.GetGameover()){
            GUI.Label(new Rect(Screen.width / 2 - 70, 80, 200, 50), "Game Over", style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, 150, 100, 50), "Restart"))
            {
                action.Restart();
                return;
            }
        }
        if(action.GetWin()){
            GUI.Label(new Rect(Screen.width / 2 - 70, 80, 200, 50), "You Win", style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, 150, 100, 50), "Restart"))
            {
                action.Restart();
                return;
            }
        }
    }
}
  1. FollowCam.cs:挂载在MainCamera上,使MainCamera始终跟随Player。
public class FollowCam : MonoBehaviour
{
    public Transform playerTransform; 
    public Vector3 deviation; 
 
    void Start()
    {
        playerTransform = GameObject.Find("Player(Clone)").GetComponent<Transform>();
        deviation = transform.position - playerTransform.position; 
    }
    void Update()
    {
        transform.position = playerTransform.position + deviation; 
        transform.LookAt (playerTransform);
    }
}

ollowCam.cs:挂载在MainCamera上,使MainCamera始终跟随Player。

public class FollowCam : MonoBehaviour
{
    public Transform playerTransform; 
    public Vector3 deviation; 
 
    void Start()
    {
        playerTransform = GameObject.Find("Player(Clone)").GetComponent<Transform>();
        deviation = transform.position - playerTransform.position; 
    }
    void Update()
    {
        transform.position = playerTransform.position + deviation; 
        transform.LookAt (playerTransform);
    }
}
  1. Singleton.cs、SSDirector.cs:和之前的项目是一致的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值