您的位置:首页 > 移动开发 > Unity3D

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