规则介绍
玩家使用ws或者↑↓控制角色前进/后退,使用ad或者←→控制角色向左向右转。控制角色避开僵尸的追击,每逃避一次就加一分,最高记录将一直显示。
视频演示
[Unity-3d]小女孩与鬼畜巡逻僵尸
本次作业要求使用订阅发布模式实现。简单来说,订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。该模式中发布者和订阅者没有直接的耦合,是实现模型与视图分离的重要手段。具体到这次的作业,将涉及到逃离僵尸追踪和被僵尸追上等事件需要监听,定义一个发布事件类,以及在场记类中实现订阅者,代码如下:
发布者
// 发布事件类 EventManager.cs 中的发布者
// 时间响应:分数变化
public delegate void scoreEvent();
public static event scoreEvent changeScore;
// 事件响应:游戏结束
public delegate void gameOverEvent();
public static event gameOverEvent gameOver;
// 事件1:逃离僵尸
public void PlayerEscape()
{
if (changeScore)
changeScore();
}
// 事件2:被僵尸追上
public void PlayerGameover()
{
if (gameOver)
gameOver();
}
订阅者
// 场记类 SceneController.cs 中的订阅者
public PatrolFactory patrolFactory; // 僵尸工厂
public ScoreRecorder recorder; // 记分牌
public PatrolActionManager actionManager; // 僵尸动作管理者
private bool isOver = false; // 游戏结束标识
void OnEnable() // 注册订阅
{
EventManager.changeScore += AddScore;
EventManager.gameOver += GameOver;
}
void OnDisable() //取消订阅
{
EventManager.changeScore -= AddScore;
EventManager.gameOver -= GameOver;
}
void AddScore() // 记分牌变化
{
recorder.addScore(); // 加分
}
void GameOver() // 游戏结束变化
{
isOver = true; // 游戏结束
patrolFactory.reset(); // 重置巡逻僵尸位置
actionManager.freezeAllAction(); // 停止所有僵尸动作
}
其实要实现的部分还算是比较多的,主要有巡逻僵尸部分、玩家角色部分、UI、记分牌等等,接下来将说一下核心的代码部分。
首先当然是搭建地图啦,大家可以在AssetsStore上找到很多很好看的又免费的资源,但是为了节约时间,这一次我就弄了一个比较简单的九宫地图。
一个值得注意的问题就是如何防止僵尸们越界追人,于是我给每一个房间设了一个BoxCollider(如上图所示),这样当小女孩跟某一个BoxCollider有接触时,我们就可以知道她在哪个房间,就可以只允许对应房间僵尸运动了。
// ColliderBlock.cs
public int block = 0;
void OnTriggerEnter(Collider collider)
{
// 若小女孩进入该房间,更新p_block
if (collider.gameObject.tag == "Player")
{
sceneController.p_block = sign;
}
}
首先,是把最基础的预制设置好,进入AssetsStore,本来是要搜索soilders的,结果在搜索结果里面发现了这么一套可爱的小人,还是免费的,果断下载。
OK,然后我们要做的是给巡逻僵尸加上Animator、CapsuleCollider、Rigidbody组件,分别是为了实现动画效果、添加碰撞体、添加刚体。除此之外,我们还要在他的子结点添加一个BoxCollider,并调整它的大小,这个碰撞体其实是实现一个探测器的效果,当小女孩与其碰撞时,就相当于小女孩进入僵尸的探测范围啦。
接下来就是巡逻僵尸的相关类实现啦。
巡逻僵尸基本属性类
// PatrolData.cs
public int block; // 巡逻兵当前区域
public int p_block = -1; // 小女孩当前区域
public bool isChasing = false; // 是否追踪小女孩
public GameObject player; // 玩家游戏对象
public Vector3 start_position; // 当前巡逻兵初始位置
巡逻僵尸工厂类
// PatrolFactory.cs
private GameObject temp = null; // 临时对象
private List patrols = new List(); // 巡逻僵尸队列
private Vector3[] pos = new Vector3[9]; // 巡逻僵尸们的初始位置
public List GetPatrols()
{
int[] pos_x = { -5, 5, 15 };
int[] pos_z = { -5, 5, 15 };
int index = 0;
// 设置初始位置
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
pos[index++] = new Vector3(pos_x[i], 0, pos_z[j]);
// 放置僵尸
for(int i = 0; i < 9; i++)
{
temp = Instantiate(Resources.Load("Prefabs/Patrol2"));
temp.transform.position = pos[i];
temp.GetComponent().sign = i + 1;
temp.GetComponent().start_position = pos[i];
patrols.Add(temp);
}
return patrols;
}
public void reset() // 重置巡逻僵尸
{
for (int i = 0; i < used.Count; i++)
used[i].gameObject.GetComponent().SetBool("run", false);
}
}
巡逻僵尸动作类
僵尸巡逻
// PatrolGo.cs 僵尸巡逻
private enum Direction { EAST, NORTH, WEST, SOUTH };
private float pos_x, pos_z; //移动前的初始x和z方向坐标
private float length; //移动的长度
private float p_speed = 1.2f; //巡逻移动速度
private bool isEnd = true; //是否到达路径终点
private Direction dir = Direction.EAST; //移动的方向
private PatrolData data; //巡逻僵尸的属性
public static PatrolGon getInstance(Vector3 loc, GameObject p)
{
PatrolGo instance = CreateInstance();
instance.pos_x = loc.x;
instance.pos_z = loc.z;
instance.length = Random.Range(4, 7);
return instance;
}
public override void Start()
{
this.gameobject.GetComponent().SetBool("run", true);
data = this.gameobject.GetComponent();
}
public override void Update()
{
if (isEnd)
{
switch (dir)
{
case Direction.EAST:
pos_x += length;
break;
case Direction.WEST:
pos_x -= length;
break;
case Direction.NORTH:
pos_z += length;
break;
case Direction.SOUTH:
pos_z -= length;
break;
}
isEnd = false;
}
Vector3 des = new Vector3(pos_x, 0, pos_z);
this.transform.LookAt(des);
if (Vector3.Distance(this.transform.position, des) > 0)
this.transform.position =
Vector3.MoveTowards(this.transform.position, des, p_speed * Time.deltaTime);
else {
dir++;
if (dir > Direction.SOUTH)
dir = Direction.EAST;
isEnd = true;
}
//若僵尸探测到玩家,巡逻动作结束,开始追踪
if (data.follow_player && data.wall_sign == data.sign)
{
this.destroy = true;
this.callback.SSActionEvent(this,0,this.gameobject);
}
}
僵尸追踪
// PatrolChase.cs 僵尸追踪
private float speed = 2f; // 追踪玩家的速度
private GameObject player; // 玩家
private PatrolData data; // 巡逻僵尸属性
public static PatrolFollowAction GetSSAction(GameObject player)
{
PatrolFollowAction action = CreateInstance();
action.player = player;
return action;
}
public override void Start()
{
data = this.gameobject.GetComponent();
}
public override void Update()
{
this.transform.LookAt(player.transform.position);
this.transform.position =
Vector3.MoveTowards(this.transform.position, player.transform.position, c_speed * Time.deltaTime);
//若僵尸探测不到玩家,追踪动作结束,开始巡逻
if (!data.follow_player || data.wall_sign != data.sign)
{
this.destroy = true;
this.callback.SSActionEvent(this,1,this.gameobject);
}
}
以上动作类实现完之后,即可在动作管理类中调用。如在结束巡逻并使用回调函数后追踪玩家,又或者是丢失玩家并使用回调函数重新巡逻。
相对僵尸巡逻和追踪两种行动模式来说,小女孩的操作就简单多了,只需要通过GUI让玩家控制小女孩的行动就好。
UI类
// UserInterface.cs
private IUserAction action;
void Update()
{
// 键入方向键,移动小女孩
float change_x = Input.GetAxis("Horizontal");
float change_z = Input.GetAxis("Vertical");
action.MovePlayer(change_x, change_z);
}
场景控制类
// SceneController.cs
//玩家移动
public void MovePlayer(float change_x, float change_z)
{
if(!game_over)
{
// 若在走动则设置run动画,否则静止
if (change_x != 0 || change_z != 0)
player.GetComponent().SetBool("run", true);
else
player.GetComponent().SetBool("run", false);
// 移动小女孩
player.transform.Translate(0, 0, change_z * player_speed * Time.deltaTime);
player.transform.Rotate(0, change_x * rotate_speed * Time.deltaTime, 0);
// 稳定人物模型
if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0)
player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);
if (player.transform.position.y != 0)
player.transform.position =
new Vector3(player.transform.position.x, 0, player.transform.position.z);
}
}
僵尸的探测器 与 小女孩 交互
// ColliderDetector.cs
void ChasingFlag(Collider girl)
{
if (girl.gameObject.tag == "Player")
{
// 小女孩被探测到
this.gameObject.transform.parent.GetComponent().isChasing = true;
this.gameObject.transform.parent.GetComponent().player = collider.gameObject;
}
}
void EscapeFlag(Collider collider)
{
if (collider.gameObject.tag == "Player")
{
// 小女孩脱离探测范围
this.gameObject.transform.parent.GetComponent().isChasing = false;
this.gameObject.transform.parent.GetComponent().player = null;
}
}
僵尸 与 小女孩 交互
// ColliderPatrol.cs
void collide(Collision collider)
{
if (collider.gameObject.tag == "Player")
{
// 小女孩与僵尸相碰撞
colliderr.gameObject.GetComponent().SetTrigger("death");
Singleton.Instance.PlayerGameover();
}
}
以上便是基本的核心代码了,还有一些记分牌、摄像头跟踪什么的,这里不做赘述,都是一些可以随意发挥的东西。本次作业就写到这里了,特别鸣谢C486C大神的博客,在下实在是佩服的五体投地hhh。