巡逻兵
游戏设计要求
- 创建一个地图和若干巡逻兵
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算
- 巡逻兵碰撞到障碍物如树,则会自动选下一个点为目标
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家
- 失去玩家目标后,继续巡逻
- 计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束
程序设计要求
- 必须使用订阅与发布模式传消息、工厂模式生产巡逻兵
游戏演示
演示了玩家左键劈砍,右键范围攻击,space跳跃,
巡逻兵触发追踪时候扑到动作,以及碰到玩家挥砍动作
还有玩家攻击时候碰到巡逻兵,巡逻兵会直接消失
准备工作
首先需要下载模型,推荐一个网站还算比较好的免费网站,其中我的作业模型就是从里面下载的,场景模型本来也下载了,但是建立一个场景实在太费时间,所以就直接借用了之前大佬的场景。
人物的动作
首先打开下载好的人物模型,点开人物旁边的按钮可以看到很多部件以及动作,可以点开玩一下。如果决定用这个人物模型,就放置到Resources中的Prefabs文件夹里。放到
在Prefabs中的人物就没有那些动作了,这时候就需要自己设计AnimatorController。
新建一个文件夹AnimatorController,里面用来控制动作,在里面Create,Animator Controller,就比如新建了一个skeleton。
双击skeleton打开,我已经建立好了,稍微有点复杂,首先在左边的Parameters新建变量,我建立了五个变量,bool型的run,bool型的death,以及Trigger型的attack1,attack2,jump。Trigger类型给的解释是,先设置true,然后自动设置为false,但似乎运行的时候有点奇怪,这个是作为skeleton的动作设计,鼠标左键,从右向左劈砍,鼠标右键,旋转劈砍,空格键跳跃。所以从AnyState开始有三个动作,都是Trigger触发,执行完毕后进入到idle状态,在idle状态下,如果run为true就run,death为true就death,其他同理,这时候不能连到Exit,原因是连到Exit就会从入口再次进入,就会发现死了一遍又又一遍。
状态绑定动作,就是Motion那里选择动作,一定要记住将动作的Animation Type改成Generic,才可以从Motion里面选择,并且会生成结尾为Avatar的东西,这个在与物体绑定的时候有用。
下面是转换时候的设置,至于下面为什么有两个run,这个原因是有时候动作切换很不自然,或者延迟太高,多个组合的话会比较好调整,具体设计方法,把loop打勾,然后apply一下就好。这里还有个小技巧,就是两个状态之间的连线怎么删掉的问题,在Transition下面的点减号就行。
然后在人物增加新组件,Animator,在Controller和Avatar,选择刚刚做好和生成的文件。这样的话一个具有动作的人物就设置好了。
碰撞检测
这个需要设置碰撞盒,这个是我player的碰撞盒,差不多是药丸形状的,注意IsTrigger不打勾,说明碰到了就是碰到了,而不是碰到了触发一下。
巡逻兵也需要设置碰撞盒,但是比玩家复杂一点,因为巡逻兵需要检测玩家的范围,所以还要给巡逻兵建立一个box Collider,用户触发追踪事件。
人物效果看起来是这样的
当然场景也是需要设置9个碰撞盒的,并且勾上IsTrigger,用来判断人物在哪一块区域。
到此为止准备工作算是做完了。
代码实现
这次我整理和参考了以前的PPT和大佬的代码,严格按照所将的设计模式组织代码。
动作代码的实现
首先动作代码可以复用的,一点都不用改的是,SSActionManager.cs
,SSAction.cs
,ISSActionInterface.cs
其中ISSActionInterface.cs
是ISSActionCallback
接口。需要改动的就是CCSequenceAction.cs
和CCMoveToAction.cs
以及CCActionManager.cs
,似乎CC开头的都是自己定义的。这个图还是牧师与魔鬼那章PPT的图,其中CCSequenceAction.cs
是定义动作序列,因为坐船是,人物先移动船的上方,在移到船上面,相当于两个动作,所以用动作序列,而船的移动只有左右,所以用CCMoveToAction.cs
实现单个动作。本次作业设计的动作也就两个,第一个是PatrolAction.cs
实现巡逻动作,一个是PatrolFollowAction.cs
实现跟踪动作,然后这个两个动作的管理在PatrolActionManager.cs
中,因为PatrolActionManager.cs
实现了ISSActionCallback
的接口,所有动作的都是靠这个接口来完成的,所以具体的动作管理,也是靠这个接口。
PatrolAction.cs 巡逻动作实现
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-7之间
action.move_length = Random.Range(4, 7);
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();
//如果有跟随的玩家,并且玩家在自己所在的区域,就会调用ISSActionCallback接口中的函数
//所以说动作的管理是靠ISSActionCallback实现的,同样在跟踪的时候的动作也会有切换到巡逻状态的方法。
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: