Unity3D 从入门到放弃(六)-----巡逻兵
2017-04-17 13:13
561 查看
Unity3D 从入门到放弃(六)
—–巡逻兵
2017.5.9修改:修改了UML图。作业需求
游戏规则:创建一个地图和若干巡逻兵;
每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次
确定下一个目标位置,用自己当前位置为原点计算;
巡逻兵碰撞到障碍物如树,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
先上UML图:
做完后的游戏动画:
(这游戏真难玩)
PS:源码在GitHub上,链接如下:
https://github.com/bilibiliChangKai/Unity3D.git
下面说一下我遇到的一些问题。
游戏制作过程:
实例化部分:
本次我运用到了两个外部实例Ami和King,这两个实例可以在我源码中下载。两个实例的动作控制如下:
其中Speed属性判断动作,toDie触发器触发死亡。
将两个预设加上刚体(虽然没用上物理学),和胶囊触发器, 设置触发器的大小即可。
地板和墙用plane和cube实现即可。
动作部分:
动作部分分为巡逻兵UI和主角控制两个动作,分别在两个脚本实现,最后挂在到预设中。巡逻兵:
首先,需要实现巡逻兵的三个动作:静止,走路,追击(主角)我用三个继承SSSAction的基类分别实现它们,通过改Animator中的Speed实现动画切换。
public class IdleAction : SSAction { private float time; private Animator ani; // 站立持续时间 public static IdleAction GetIdleAction(float time, Animator ani) { IdleAction currentAction = ScriptableObject.CreateInstance<IdleAction>(); currentAction.time = time; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 0); // 进入站立状态 } public override void Update() { if (time == -1) return; // 永久站立 time -= Time.deltaTime; // 减去时间 if (time < 0) { this.destory = true; this.callback.SSEventAction(this); } } } public class WalkAction : SSAction { private float speed; private Vector3 target; private Animator ani; // 移动速度和目标的地点 public static WalkAction GetWalkAction(Vector3 target, float speed, Animator ani) { WalkAction currentAction = ScriptableObject.CreateInstance<WalkAction>(); currentAction.speed = speed; currentAction.target = target; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 0.5f); // 进入走路状态 } public override void Update() { Quaternion rotation = Quaternion.LookRotation(target - transform.position); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5); // 进行转向,转向目标方向 this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime); if (this.transform.position == target) { this.destory = true; this.callback.SSEventAction(this); } } } public class RunAction : SSAction { private float speed; private Transform target; private Animator ani; // 移动速度和人物的transform public static RunAction GetRunAction(Transform target, float speed, Animator ani) { RunAction currentAction = ScriptableObject.CreateInstance<RunAction>(); currentAction.speed = speed; currentAction.target = target; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 1); // 进入跑步状态 } public override void Update() { Quaternion rotation = Quaternion.LookRotation(target.position - transform.position); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5); // 转向 this.transform.position = Vector3.MoveTowards(this.transform.position, target.position, speed * Time.deltaTime); if (Vector3. e164 Distance(this.transform.position, target.position) < 0.5) { this.destory = true; this.callback.SSEventAction(this); } } }
巡逻兵主要实现动作的循环运行,这里我的思路就是运用之前学过的ActionManager的callback实现,即在callback中调用另一个动作。
public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null) { currentState = currentState > ActionState.WALKBACK ? ActionState.IDLE : (ActionState)((int)currentState + 1); // 改变当前状态 switch (currentState) { case ActionState.WALKLEFT: walkLeft(); break; case ActionState.WALKRIGHT: walkRight(); break; case ActionState.WALKFORWARD: walkForward(); break; case ActionState.WALKBACK: walkBack(); break; default: idle(); break; } // 执行下个动作 }
主角:
主角部分主要实现按键控制移动,在这里我用运动学实现(实在不会用物理学啊= =):void FixedUpdate () { if (!ani.GetBool("isLive")) return; // 如果死亡,不执行所有动作 float x = Input.GetAxis("Horizontal"); float z = Input.GetAxis("Vertical"); ani.SetFloat("Speed", Mathf.Max(Mathf.Abs(x), Mathf.Abs(z))); // 设置速度 ani.speed = 1 + ani.GetFloat("Speed") / 3; // 调整跑步的时候的动画速度 velocity = new Vector3(x, 0, z); // 如果处于运动,则转向 if (x != 0 || z != 0) { Quaternion rotation = Quaternion.LookRotation(velocity); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.fixedDeltaTime * rotateSpeed); } this.transform.position += velocity * Time.fixedDeltaTime * runSpeed; // 主角移动 }
逻辑交互部分:
逻辑交互部分主要运用观察者模式,就像UML图写的,SceneController和Patrol接受消息,Actor发布消息:基类实现:
/* * 描 述:发布者和订阅者接口,和发布者的实际实现 * 作 者:hza * 创建时间:2017/04/16 09:55:51 * 版 本:v 1.0 */ using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public interface Publish { void notify(ActorState state, int pos, GameObject actor); // 发布函数 void add(Observer observer); // 委托添加事件 void delete(Observer observer); // 委托取消事件 } public interface Observer { void notified(ActorState state, int pos, GameObject actor); // 实现接收函数 } public enum ActorState { ENTER_AREA, DEATH } public class Publisher : Publish { private delegate void ActionUpdate(ActorState state, int pos, GameObject actor); private ActionUpdate updatelist; // 委托定义 /// <summary> /// 单实例模式 /// </summary> private static Publish _instance; public static Publish getInstance() { if (_instance == null) _instance = new Publisher(); return _instance; } public void notify(ActorState state, int pos, GameObject actor) { if (updatelist != null) updatelist(state, pos, actor); // 发布信息 } public void add(Observer observer) { updatelist += observer.notified; } public void delete(Observer observer) { updatelist -= observer.notified; } }
在场景中,我添加了触发器,当角色进入某个区域后,发送消息,相应的Patrol接受消息后就会追击角色,而SceneController会通知ScoreRecord加分:
/// <summary> /// 用于检测Actor进入某个区域 /// </summary> /// <param name="other"></param> private void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("Area")) { Publish publish = Publisher.getInstance(); int patrolType = other.gameObject.name[other.gameObject.name.Length - 1] - '0'; publish.notify(ActorState.ENTER_AREA, patrolType, this.gameObject); // 进入区域后,发布消息 } }
由于Actor和Patrol都添加了碰撞器,当两者碰撞时,角色会发布死亡信息,Patrol接受信息后会停止移动,SceneController接收信息后会通知UI显示游戏结束:
private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Patrol") && ani.GetBool("isLive")) { ani.SetBool("isLive", false); ani.SetTrigger("toDie"); // 执行死亡动作 Publish publish = Publisher.getInstance(); publish.notify(ActorState.DEATH, 0, null); // 碰撞后,发布死亡信息 } }
接收消息部分:
Patrol:
/// <summary> /// 接受信息后执行的动作,判断角色在哪个区域内,并判断角色死亡 /// </summary> /// <param name="state">角色状态</param> /// <param name="pos">角色所在区域</param> /// <param name="actor">角色</param> public void notified(ActorState state, int pos, GameObject actor) { if (state == ActorState.ENTER_AREA) { if (pos == this.gameObject.name[this.gameObject.name.Length - 1] - '0') getGoal(actor); // 如果进入自己的区域,进行追击 else loseGoal(); // 如果离开自己的区域,放弃追击 } else stop(); // 角色死亡,结束动作 }
SceneController:
public void notified(ActorState state, int pos, GameObject actor) { if (state == ActorState.ENTER_AREA) record.addScore(1); else UI.loseGame(); }
其他部分都是可以实现的,或者是以前已经实现的,在此就不再阐述了。
总结:
又是一项做完后感觉非常简单的作业,就是不懂为什么这么多Bug。。。UML图和观察者模式还是很好用的,本来还想看看动画混合,但是没搞出来,各种bug,于是只能作罢,要走的路还有很远啊。。。。
相关文章推荐
- Unity3D 从入门到放弃 ——巡逻兵 观察者模式
- Unity3D 从入门到放弃(五)----射箭游戏
- Unity3D 从入门到放弃(四)----打飞碟
- Unity3D从入门到放弃(七) ----DoTween的实现
- 微服务、容器与容器云-从入门到放弃
- JavaScript从入门到放弃(一)
- 《Java从入门到放弃》框架入门篇:springMVC基本用法
- Mycat从入门到放弃
- shiro 从入门到放弃
- JavaScript从入门到放弃(三)
- Unix网络编程:从入门到放弃——一个简单的时间获取程序
- c++ 从入门到放弃
- 翻身的废鱼——论PHP从入门到放弃需要多久?1
- Mycat从入门到放弃
- Swift3.0从入门到放弃(三)
- <Unity3D>Unity3D入门篇——第四讲 GUI控件(二)
- Unity3D教程系列 新手入门,中度进阶神器
- Git命令“从入门到放弃”
- Cacti从入门到放弃(1)使用安装详解
- DOCKER 从入门到放弃(三)