作业需求
游戏规则:
创建一个地图和若干巡逻兵;
每个巡逻兵走一个3〜5个边的凸多边型,位置数据是相对地址即每次确定下一个目标位置,用自己当前位置为原点计算。
巡逻障碰撞到障碍物如树,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
PS:源码和视频在GitHub的的上,链接如下:
https://github.com/wym199807/unity3d-hw6
扫描二维码关注公众号,回复:
857578 查看本文章
游戏制作过程
实例化部分:
两个预设:
两个实例的动作控制如下:
其中速度属性判断动作,toDie触发器触发死亡。
将两个预设加上刚体(虽然没用上物理学),和胶囊触发器,设置触发器的大小即可。
地板和墙用平面和立方体实现即可。
动作部分:
动作部分分为巡逻兵UI和主角控制两个动作,分别在两个脚本实现,最后挂在到预设中。
巡逻兵:
首先,需要实现巡逻兵的三个动作:静止,走路,追击(主角)
我用三个继承SSSAction的基类分别实现它们,通过改动画中的速度实现动画切换。
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.Distance(this.transform.position, target.position) < 0.5) { this.destory = true; this.callback.SSEventAction(this); } } }
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; // 主角移动 }
逻辑交互部分:
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; } }
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); // 进入区域后,发布消息 } }
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); // 碰撞后,发布死亡信息 } }