作业需求
游戏规则:
创建一个地图和若干巡逻兵;
每个巡逻兵走一个3〜5个边的凸多边型,位置数据是相对地址即每次确定下一个目标位置,用自己当前位置为原点计算。
巡逻障碰撞到障碍物如树,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
PS:源码和视频在GitHub的的上,链接如下:
https://github.com/wym199807/unity3d-hw6
游戏制作过程
实例化部分:
两个预设:
两个实例的动作控制如下:
其中速度属性判断动作,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);
// 碰撞后,发布死亡信息
}
}