Unity实战之智能巡逻兵
游戏说明
一个地图,其中有多个巡逻兵,靠近巡逻兵会被追击,远离一定距离停止追击。每甩掉一个巡逻兵记一分。
项目预制
项目一共需要三种预制资源:
-
地图
为了让巡逻兵在特定的地方追踪,给每个区域都增加了一个空气墙(绿色线)
-
玩家
增加碰撞器和刚体属性
添加动画控制器
-
巡逻兵
由两部分组成,其中Patrol和root都分别挂载了碰撞器(如下图绿线)
动画:
其中大的盒碰撞器勾选isTrigger
,由OnTriggerEnter
和OnTriggerExit
函数触发
项目结构
本次项目依旧采用mvc架构,并引入订阅/发布模式。整体UML图如下
相关代码
PatrolData
:保存了巡逻兵的相关属性
public class PatrolCollisionAction : MonoBehaviour
{
void OnCollisionEnter(Collision collider){
if(collider.gameObject.tag == "player"){
Debug.Log("1111");
collider.gameObject.GetComponent<Animator>().SetTrigger("death");
this.GetComponent<Animator>().SetTrigger("attack");
Singleton<GameEventManager>.Instance.PlayerBeCaught();
}
if(collider.gameObject.tag == "wall"){
Debug.Log("撞墙");
int curr = this.GetComponent<PatrolData>().curr;
curr = (curr+2)%4;
this.GetComponent<PatrolData>().curr = curr;
}
}
}
PatrolTrigger
:巡逻兵的触发脚本,用于处理和玩家靠近,远离
public class PatrolTrigger : MonoBehaviour
{
void OnTriggerEnter(Collider collider) {
if (collider.gameObject.tag == "player") {
Debug.Log("enter");
this.gameObject.transform.parent.GetComponent<PatrolData>().isTracing = true;
this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = collider.gameObject;
}
}
void OnTriggerExit(Collider collider) {
if (collider.gameObject.tag == "player") {
Debug.Log("exit");
this.gameObject.transform.parent.GetComponent<PatrolData>().isTracing = false;
this.gameObject.transform.parent.GetComponent<PatrolData>().trackPlayer = null;
}
}
}
PatrolFactory
:巡逻兵工厂,用于巡逻兵的生产和释放
public class PatrolFactory : MonoBehaviour
{
private List<GameObject> used = new List<GameObject>();
private List<GameObject> free = new List<GameObject>();
GameObject patrol;
FirstController firstController;
// Start is called before the first frame update
void Start()
{
firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
}
public List<GameObject> GetPatrols() {
float[] x = {-6f, -2f, 3f};
float[] z = {-6f, -1.6f, 3f};
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
Vector3 start_pos = new Vector3(x[i], 0, z[j]);
patrol = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/Patrol"),
start_pos,
Quaternion.identity
);
patrol.SetActive(true);
patrol.GetComponent<PatrolData>().patrolAreaId = i*3+j+1;
patrol.GetComponent<PatrolData>().startPos = start_pos;
used.Add(patrol);
}
}
return used;
}
public void FreePatrols() {
foreach(GameObject gameObject in used){
gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
PatrolMoveAction
:巡逻兵正常巡逻动作,规定好四个点,走到某一个点就把目的地置成下一个点。当巡逻动作结束时,就调用回调函数切换成追击动作。
public class PatrolFactory : MonoBehaviour
{
private List<GameObject> used = new List<GameObject>();
private List<GameObject> free = new List<GameObject>();
GameObject patrol;
FirstController firstController;
// Start is called before the first frame update
void Start()
{
firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
}
public List<GameObject> GetPatrols() {
float[] x = {-6f, -2f, 3f};
float[] z = {-6f, -1.6f, 3f};
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
Vector3 start_pos = new Vector3(x[i], 0, z[j]);
patrol = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/Patrol"),
start_pos,
Quaternion.identity
);
patrol.SetActive(true);
patrol.GetComponent<PatrolData>().patrolAreaId = i*3+j+1;
patrol.GetComponent<PatrolData>().startPos = start_pos;
used.Add(patrol);
}
}
return used;
}
public void FreePatrols() {
foreach(GameObject gameObject in used){
gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
PatrolTrackAction
:巡逻兵的追击事件
public class PatrolTrackAction : SSAction
{
public GameObject trackPlayer;
public PatrolData patrol;
public FirstController firstController;
public bool isLookAt = false;
// Start is called before the first frame update
public static PatrolTrackAction GetSSAction(GameObject player){
PatrolTrackAction patrolTrackAction = CreateInstance<PatrolTrackAction>();
patrolTrackAction.trackPlayer = player;
return patrolTrackAction;
}
public override void Start()
{
patrol = this.gameObject.GetComponent<PatrolData>();
firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
}
// Update is called once per frame
public override void Update()
{
transform.position = Vector3.MoveTowards(this.transform.position, trackPlayer.transform.position, Time.deltaTime);
this.transform.LookAt(trackPlayer.transform.position);
if(patrol.patrolAreaId != firstController.GetPlayerMapId() || !patrol.isTracing){
this.destroy = true;
this.callback.SSActionEvent(this, SSActionEventType.Completed, 1, null, this.gameObject);
}
PatrolCollisionAction
:巡逻兵的撞击事件。若和玩家撞击,则调整状态为攻击;若和墙撞击,则换巡逻方向
public class PatrolCollisionAction : MonoBehaviour
{
void OnCollisionEnter(Collision collider){
if(collider.gameObject.tag == "player"){
Debug.Log("1111");
collider.gameObject.GetComponent<Animator>().SetTrigger("death");
this.GetComponent<Animator>().SetTrigger("attack");
Singleton<GameEventManager>.Instance.PlayerBeCaught();
}
if(collider.gameObject.tag == "wall"){
Debug.Log("撞墙");
int curr = this.GetComponent<PatrolData>().curr;
curr = (curr+2)%4;
this.GetComponent<PatrolData>().curr = curr;
}
}
}
MapTrigger
:地图的触发器
public class MapTrigger : MonoBehaviour
{
public int areaID;
FirstController firstController;
// Start is called before the first frame update
private void Start()
{
firstController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
}
void OnTriggerEnter(Collider collider){
if (collider.gameObject.tag == "player") {
firstController.SetPlayerAreaId(areaID);
}
}
}
UserGUI
:整体视图
public class UserGUI : MonoBehaviour
{
private FirstController firstController;
GUIStyle textStyle;
GUIStyle topicStyle;
// Start is called before the first frame update
void Start()
{
SetStyle();
firstController = SSDirector.GetInstance().CurrentScenceController as FirstController;
}
// Update is called once per frame
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
firstController.MovePlayer(x, z);
}
void SetStyle(){
textStyle = new GUIStyle();
textStyle.normal.textColor = Color.black;
textStyle.fontSize = 30;
topicStyle = new GUIStyle();
topicStyle.normal.textColor = Color.black;
topicStyle.fontSize = 60;
}
public void Move(){
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
firstController.MovePlayer(x, z);
}
void OnGUI(){
if(firstController.isGameOver) {
GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 240, 100, 100), "score: "+firstController.score, topicStyle);
GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 300, 100, 100), "Game Over!", topicStyle);
if (GUI.Button(new Rect(Screen.width/2 - 45, Screen.width/2 - 170, 100, 50), "Start Game")) {
firstController.Restart();
}
}
}
}
FirstController
:主控制器,用于资源加载、事件调度
public class UserGUI : MonoBehaviour
{
private FirstController firstController;
GUIStyle textStyle;
GUIStyle topicStyle;
// Start is called before the first frame update
void Start()
{
SetStyle();
firstController = SSDirector.GetInstance().CurrentScenceController as FirstController;
}
// Update is called once per frame
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
firstController.MovePlayer(x, z);
}
void SetStyle(){
textStyle = new GUIStyle();
textStyle.normal.textColor = Color.black;
textStyle.fontSize = 30;
topicStyle = new GUIStyle();
topicStyle.normal.textColor = Color.black;
topicStyle.fontSize = 60;
}
public void Move(){
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
firstController.MovePlayer(x, z);
}
void OnGUI(){
if(firstController.isGameOver) {
GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 240, 100, 100), "score: "+firstController.score, topicStyle);
GUI.Label(new Rect(Screen.width/2 - 150, Screen.width/2 - 300, 100, 100), "Game Over!", topicStyle);
if (GUI.Button(new Rect(Screen.width/2 - 45, Screen.width/2 - 170, 100, 50), "Start Game")) {
firstController.Restart();
}
}
}
}
订阅发布模式
定义一个游戏的管理者,用于充当消息发布媒体
GameEventManager
public class GameEventManager : MonoBehaviour
{
public delegate void AddScoreEvent();
public static event AddScoreEvent AddScoreAction;
public delegate void GameOverEvent();
public static event GameOverEvent GameOverAction;
public void PlayerRunAway() {
if(AddScoreAction != null){
AddScoreAction();
}
}
public void PlayerBeCaught() {
if(GameOverAction != null){
GameOverAction();
}
}
}
并在FirstController
中添加客户代码
void OnEnable() {
GameEventManager.AddScoreAction += AddScore;
GameEventManager.GameOverAction += EndGame;
}
void OnDisable() {
GameEventManager.AddScoreAction -= AddScore;
GameEventManager.GameOverAction -= EndGame;
}
最后在玩家身上加一个镜头跟随的脚本
CameraFollow
public class CameraFollow : MonoBehaviour {
private Vector3 offset;
public GameObject role;
void Start () {
role = (SSDirector.GetInstance().CurrentScenceController as FirstController).player;
offset = role.transform.position - this.transform.position;
}
void Update () {
transform.position = role.transform.position - offset;
}
}