本周作业
游戏设计要求:
创建一个地图和若干巡逻兵(使用动画);
每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束,玩家失败;玩家得分大于等于5,游戏结束,玩家胜利。
创建一个地图和若干巡逻兵(使用动画);
每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束,玩家失败;玩家得分大于等于5,游戏结束,玩家胜利。
作业过程:
1.素材获取:
http://www.aigei.com/3d/人物模型来自于该网站,而场景则直接搬运大佬的,最终如上。
2.准备阶段:首先我们先把下载好的模型,拖入项目中,然后将动画模式改为Generic,这些都是上课时讲到过的,然后调整模型大小,加刚体和胶囊球碰撞盒,并设置最重要的
AnimatorController,如下:
这两个对象的动作都很简单,actor由玩家自己控制移动,而巡逻兵有3个动作,idle(一段时间内站着不动),walk和run,巡逻兵在没有发现主角到自己区域的时候不会移动的很快,只会缓慢移动,而主角到了巡逻兵所在的区域,巡逻兵会加快速度。
当巡逻兵是idle状态的时候,(刚开始游戏或者由于主角离开了一个区域,到了新的区域),我们就会调用idle动作,当站着不动的时间消耗完毕的时候,我们就会调用回调函数,从而让巡逻兵做预先设置好的下一个动作,空闲->往左走->往上走->往右走->往下走->如此循环,就实现了巡逻的功能。
再来就是这次游戏中所用到的一些模式,都是我们前几次作业中涉及到的。
单例模式:
这个没什么区别,直接套用之前模板即可,不做过多解释。
门面模式:
门面模式中很多东西同样可以直接复用,只更改了userGUI和Scenecontrol。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneController : MonoBehaviour, Observer
{
public Text scoreText;
public Text centerText;
private ScoreRecorder record;
private UIController UI;
private ObjectFactory fac;
private float[] posx = { -5, 7, -5, 5 };
private float[] posz = { -5, -7, 5, 5 };
// 开始位置
void Start()
{
record = new ScoreRecorder();
record.scoreText = scoreText;
UI = new UIController();
UI.centerText = centerText;
fac = Singleton<ObjectFactory>.Instance;
Publish publisher = Publisher.getInstance();
publisher.add(this);
// 添加事件
LoadResources();
}
private void LoadResources()
{
Instantiate(Resources.Load("prefabs/actor"), new Vector3(2, 0, -2), Quaternion.Euler(new Vector3(0, 0, 0)));
// 初始化主角
ObjectFactory fac = Singleton<ObjectFactory>.Instance;
for (int i = 0; i < posx.Length; i++)
{
GameObject patrol = fac.setObjectOnPos(new Vector3(posx[i], 0, posz[i]), Quaternion.Euler(new Vector3(-90, 0, 0)));
patrol.name = "Patrol" + (i + 1);
// 初始化巡逻兵
}
}
/// <summary>
/// 如果角色死亡,显示LOSE
/// </summary>
/// <param name="state">订阅状态</param>
/// <param name="pos"></param>
public void notified(ActorState state, int pos, GameObject actor)
{
if (state == ActorState.ENTER_AREA) record.addScore(1);
else UI.loseGame();
}
}
工厂模式:
类似于飞碟中的工厂模式,在游戏开始时生成巡逻兵和actor在指定的位置,并在之后free掉它们
public class ObjectFactory : MonoBehaviour {
//工厂模式就负责生产,其他的不负责,所以可以看到这个类只和角色有关系和其他所有代码都没有关系
private GameObject actor = null; //玩家
private GameObject patrol = null; //巡逻兵
private List<GameObject> patrolList = new List<GameObject>(); //正在被使用的巡逻兵
private Vector3[] vec = new Vector3[4]; //保存每个巡逻兵的初始位置
public GameObject LoadActor()
{
player = Instantiate(Resources.Load("Prefabs/actor"), new Vector3(0, 4, 0), Quaternion.identity) as GameObject;
return actor;
}
public List<GameObject> LoadPatrol()
{
int[] pos_x = { -6, 4, 13 };
int[] pos_z = { -4, 6, -13 };
int index = 0;
for(int i=0;i < 2;i++)
{
for(int j=0;j < 2;j++)
{
vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
index++;
}
}
for(int i=0; i < 4; i++)
{
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
patrol.transform.position = vec[i];
patrol.GetComponent<PatrolData>().sign = i + 1;
patrol.GetComponent<PatrolData>().start_position = vec[i];
patrolList.Add(patrol);
}
return patrolList;
}
//游戏结束的时候会暂停所有动作
public void StopPatrol()
{
for (int i = 0; i < patrolList.Count; i++)
{
patrolList[i].gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
观察者模式:
然后就是本次作业要求用到的观察者模式,当Actor触发了某些规则,就可以发布相应的信息,然后通过信息的参数,订阅者就会做出相应的动作。比如,当Actor进入到某个巡逻兵的区域,他就会发布信息publish.notify(ActorState.ENTER_AREA, patrol, this.gameObject);通知对应区域的巡逻兵,巡逻兵收到消息,就可以开始追踪主角。
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死亡时,也会发布死亡的消息,由于设计较为简单,只有这两种情况需要使用到观察者模式。
视频链接:点击打开链接