要求
订阅与发布模式
- 我的理解是相当于一些社交软件,明星发布信息,通过中间平台,订阅者可以收到其信息。
- 订阅者与发布模式的优点:
- 松耦合:可以将众多需要通信的子系统(Subsystem)解耦,每个子系统可以分别管理,子系统的状态不影响整体系统的运行。
- 高伸缩性:增加了系统的可伸缩性,并提高了发送者的响应能力。发送方只需要向“平台”发送信息,就可以继续处理自己的核心业务,而不必关心子系统的运行情况。“平台”负责将信息传到订阅者。
- 高可靠性:提高了可靠性。异步的消息传递有助于应用程序在增加的负载下继续平稳运行,并且可以更有效地处理间歇性故障
- 灵活性:不必关心子系统是如何组合到一起的,只要整体系统平稳运行即可
- 可测试性:提高了可测试性。通道可以被监视,消息可以作为整体集成测试策略的一部分而被检查或记录。
- 我们采用了GameEventManager作为订阅者和发布者模式的“公众号”
资源一览
- Asset
- Perfabs
- Scripts
- Animations
- tt:使用了巡逻兵动画的一些组件,在资源包中,这里没显示出来
- New Animator 1:这个是从主角小姐姐自带的动画改的
- tt:使用了巡逻兵动画的一些组件,在资源包中,这里没显示出来
游戏内截图
这里主角小姐姐跟巡逻兵碰撞,游戏失败
主要代码
- FirstSceneController: 统筹全局,这里控制了玩家的攻击方式和计分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
public PatorlFactory _PatorlFactory; //巡逻者工厂
public ScoreRecorder _ScoreRecorder; //记录员
public PatrolActionManager _PatrolActionManager; //运动管理器
public int wall_sign = -1; //当前玩家所处哪个格子
public GameObject player; //玩家
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;
}
//20分结束游戏
if(_ScoreRecorder.score == 20)
{
Gameover();
}
}
void Start()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
_PatorlFactory = Singleton<PatorlFactory>.Instance;//巡逻者工厂
_PatrolActionManager = Singleton<PatrolActionManager>.Instance;//巡逻者的动作管理器
//ISceneController接口中函数的实现
LoadResources();
_ScoreRecorder = Singleton<ScoreRecorder>.Instance;//拿到计分的单实例对象
}
//加载资源
public void LoadResources()
{
player = _PatorlFactory.LoadPlayer();//player
patrols = _PatorlFactory.LoadPatrol();
for (int i = 0; i < patrols.Count; i++)
{
_PatrolActionManager.GoPatrol(patrols[i]);//让巡逻兵有动作
}
}
public void Attack()
{
if (!game_over)
{
//Fire1对应鼠标左键
if (Input.GetButtonDown("Fire1"))
{
Debug.Log("Fire1");
player.GetComponent<Animator>().SetTrigger("yep");
}
}
}
//获得分数
public int GetScore()
{
return _ScoreRecorder.score;
}
//判断是不是game_over的函数
public bool GetGameover()
{
return game_over;
}
//重新开始,这个用的是重新加载场景
public void Restart()
{
Debug.Log("游戏重新开始!");
SceneManager.LoadScene("Scenes/SampleScene");
}
//发布与订阅模式
void OnEnable()
{
GameEventManager.ScoreChange += AddScore;
GameEventManager.GameoverChange += Gameover;
}
void OnDisable()
{
GameEventManager.ScoreChange -= AddScore;
GameEventManager.GameoverChange -= Gameover;
}
void AddScore()
{
_ScoreRecorder.AddScore();
}
//游戏失败
void Gameover()
{
game_over = true;
_PatorlFactory.StopPatrol();
_PatrolActionManager.DestroyAllAction();
}
}
- AreaDetection:在场景中创建若干个Collider来检测Player进入场景,这个脚本挂载在这些Collider上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//检测范围的函数
public class AreaDetection : MonoBehaviour
{
//墙位置的标签,说明要用empty创建东西
public int sign = 0;
//场景Controller
FirstSceneController sceneController;
private void Start()
{
//利用单实例得到唯一的场景Controller
sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
}
//碰撞器
void OnTriggerEnter(Collider collider)
{
//如果是Player碰撞的话,这个场景就被激活了
if (collider.gameObject.tag == "Player")
{
sceneController.wall_sign = sign;
Debug.Log("你现在进入的教室是:" + sign);
}
}
}
- GameEventManager:订阅者发布者模式中的“公众号”,这里主要发布玩家躲开追踪和玩家碰撞到巡逻兵的信息。(这个我不是很会,参考了师兄师姐们的代码)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEventManager : MonoBehaviour
{
//玩家躲开追踪时候触发
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();
}
}
}
- PatorlFactory:产生巡逻兵的工厂,值得注意的是,我只有七个房间却有八个巡逻兵,在命名的的后要注意
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatorlFactory : MonoBehaviour {
private GameObject player = null; //玩家
private GameObject patrol = null; //巡逻兵
private List<GameObject> patrolList = new List<GameObject>(); //正在被使用的巡逻兵
private Vector3[] vec = new Vector3[8]; //保存每个巡逻兵的初始位置
public GameObject LoadPlayer()
{
player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(8, 0, 8), Quaternion.Euler(new Vector3(0,180,0))) as GameObject;
Debug.Log("初始化玩家");
return player;
}
public List<GameObject> LoadPatrol()
{
//这里自定义八个位置,只有七个房间
float[] pos_x = { 18, 18, 18, 18, -3.5f, -3.5f, -3.5f,18};
float[] pos_z = { -0.5f,-11.5f, -22.5f, -32, -.5f,-11.5f, -22.3f,-34};
int index = 0;
for(int i=0;i < 8;i++)
{
vec[index] = new Vector3(pos_x[i], 0, pos_z[i]);
index++;
}
for(int i=0; i < 8; i++)
{
int name = i + 1;
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol "+name));
patrol.transform.position = vec[i];
patrol.GetComponent<PatrolData>().sign = i + 1;
if(i==7) patrol.GetComponent<PatrolData>().sign = i + 1;
patrol.GetComponent<PatrolData>().start_position = vec[i];
patrolList.Add(patrol);
Debug.Log("初始化老师!" + patrol.name);
}
return patrolList;
}
//游戏结束的时候会暂停所有动作
public void StopPatrol()
{
for (int i = 0; i < patrolList.Count; i++)
{
patrolList[i].gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
- GoPatrolAction:控制巡逻兵移动的代码,学习了师兄师姐的巡逻函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
//移动的距离随机,在4-6之间
action.move_length = Random.Range(4, 6);
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();
//Debug.Log("摧毁巡逻动作之前:"+ "data.follow_player"+ data.follow_player +" "+ data.wall_sign+" "+data.sign);
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("walk", 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;
}
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.SOUTH)
{
dirction = Dirction.EAST;
}
move_sign = true;
}
}
}
- PatrolFollowAction:控制巡逻兵跟随玩家的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolFollowAction : SSAction
{
private float speed = 0.1f; //跟随玩家的速度
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()
{
//Debug.Log("follow:" + this.transform.position + " " + player.transform.position);
transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
this.transform.LookAt(player.transform.position);
}
}
- PlayerCollideDetection:检测巡逻兵与玩家碰撞的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//玩家与巡逻兵碰撞
public class PlayerCollideDetection : MonoBehaviour {
//当玩家与巡逻兵碰撞
void OnCollisionEnter(Collision other)
{
if (other.gameObject.tag == "Player" && other.gameObject.GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name=="yep")
{
Debug.Log(other.gameObject.GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name);
this.gameObject.SetActive(false);
return;
}
else if (other.gameObject.tag == "Player")
{
other.gameObject.GetComponent<Animator>().SetBool("death",true);
this.GetComponent<Animator>().SetTrigger("attack");
Singleton<GameEventManager>.Instance.PlayerGameover();
}
}
}
- PlayerInDetection:检测玩家与进入巡逻兵的追踪范围
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//玩家进入巡逻兵的追踪范围
public class PlayerInDetection : MonoBehaviour
{
//玩家进入巡逻兵的追踪范围
void OnTriggerEnter(Collider collider)
{
if (collider.gameObject.tag == "Player")
{
Debug.Log("this.gameObject" + this.gameObject.name);
this.gameObject.transform.GetComponent<PatrolData>().follow_player = true;
this.gameObject.transform.GetComponent<PatrolData>().player = collider.gameObject;
//触发巡逻兵向前扑的动作
this.gameObject.transform.GetComponent<Animator>().SetTrigger("follow");
}
}
//玩家离开巡逻兵的追踪范围
void OnTriggerExit(Collider collider)
{
Debug.Log("玩家离开巡逻兵的追踪范围"+ collider.gameObject.tag);
if (collider.gameObject.tag == "Player")
{
this.gameObject.transform.GetComponent<PatrolData>().follow_player = false;
this.gameObject.transform.GetComponent<PatrolData>().player = null;
}
}
}
Unity中配置
-
脚本挂载:挂在场景中的灯光下也行
-
巡逻兵
-
主角小姐姐,可以使摄像头围着小姐姐360无死角旋转,主角小姐姐的移动考WASD实现
-
school:这个预制件内要放入Area用来检测Player的进入,并且Area要挂载检测脚本