1、智能巡逻兵
提交要求:
游戏设计要求:
创建一个地图和若干巡逻兵(使用动画);
每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
巡逻兵在设定范围内感知到玩家,会自动追击玩家;
失去玩家目标后,继续巡逻;
计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
程序设计要求:
必须使用订阅与发布模式传消息
subject:OnLostGoal
Publisher: ?
Subscriber: ?
工厂模式生产巡逻兵
友善提示1:生成 3~5个边的凸多边型
随机生成矩形
在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
5 ?
友善提示2:参考以前博客,给出自己新玩法
游戏设计:
动画部分
我用两个动画控制器来操控角色和怪物
怪物动画控制器:
怪物中的动画转换用两个布尔类型来操控,当没有碰到角色时,怪物一直处于巡逻动画,当碰到角色时,怪物会触发攻击动画。
角色动画控制器:
角色动画有三个布尔值来操控,当没走路时,处于静止动画,当走路时处于跑步动画,如果死亡了,则直接进入伤心动画(资源包里没有死亡动画)。
巡逻兵巡逻部分
其实我不是很读的懂题目那个凸多边形的要求。。
我的实现是这样的:
让巡逻兵从上下左右找一个方向来运动,并且运动的距离随机,运动完之后找下一个方向,这样子就能大概实现一个凸多边形的运动轨迹,且这个轨迹是不断在变化的,更具有随机性,下一个目标的位置都是通过方向和距离来相对当前位置得到的。
我的巡逻兵碰到墙会反方向返回(就是找下一个点来运动过去),保证不会走出自己的巡逻范围(各自的房间)。
ps:其实我应该选取房间四边的某点来每次巡逻的,但是我觉得那样子不够随机化,所以我就是以角色自己来进行随机化的选择
巡逻兵脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class XLBMind : MonoBehaviour
{
public enum State : int { IDLE, LEFT, UP, RIGHT, DOWN }
// Start is called before the first frame update
public Vector3 target;
public Animator ani;
public State mystate;
public bool locked;
public int cd;
void OnEnable(){
if(this.transform.name == "xlb1"){
Me.InArea1 += pursue;
}
if(this.transform.name == "xlb2"){
Me.InArea2 += pursue;
}
if(this.transform.name == "xlb3"){
Me.InArea3 += pursue;
}
if(this.transform.name == "xlb4"){
Me.InArea4 += pursue;
}
}
void Disable(){
}
void pursue(GameObject self,string info){
target=self.transform.position;
}
void Start()
{
locked=false;
cd=0;
ani = GetComponent<Animator>();
ani.SetBool("Walking", true);
ani.SetBool("Attack", false);
Vector3 del = new Vector3(-1,0,0) * Random.Range(1, 5f);
mystate=State.RIGHT;
target = this.transform.position - del;
OnEnable();
// Quaternion rotation = Quaternion.LookRotation(target - transform.position);
// transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * 5f * 5);
// this.transform.position = Vector3.MoveTowards(this.transform.position, target, 2f * Time.deltaTime);
}
void FixedUpdate(){
cd=cd+1;
//Debug.Log(cd);
}
// Update is called once per frame
void Update()
{
if(Controller.getInstance().running == false){
return;
}
if(locked) return;
if(this.transform.name == "xlb1" && (target.x<0 || target.z>8)){
changeDirection2();
}
if(this.transform.name == "xlb2" && (target.x>-1 || target.z>8)){
changeDirection2();
}
if(this.transform.name == "xlb3" && (target.x>-1 || target.z<9)){
changeDirection2();
}
if(this.transform.name == "xlb4" && (target.x<0 || target.z<9)){
changeDirection2();
}
Quaternion rotation = Quaternion.LookRotation(target - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * 5f * 5);
this.transform.position = Vector3.MoveTowards(this.transform.position, target, 1f * Time.deltaTime);
if(this.transform.position == target){
changeDirection2();
//ani.SetBool("Walking", false);
}
}
private void changeDirection1(){
locked=true;
if(mystate == State.LEFT){
Vector3 del = new Vector3(-1,0,0) * Random.Range(1, 5f);
mystate=State.RIGHT;
target = this.transform.position - del;
} else if(mystate == State.RIGHT){
Vector3 del = new Vector3(1,0,0) * Random.Range(1, 5f);
mystate=State.LEFT;
target = this.transform.position - del;
} else if(mystate == State.UP){
Vector3 del = new Vector3(0,0,1) * Random.Range(1, 5f);
mystate=State.DOWN;
target = this.transform.position - del;
} else if(mystate == State.DOWN){
Vector3 del = new Vector3(0,0,-1) * Random.Range(1, 5f);
mystate=State.UP;
target = this.transform.position - del;
}
locked=false;
}
private void changeDirection2(){
locked=true;
if(mystate == State.LEFT){
Vector3 del = new Vector3(0,0,1) * Random.Range(1, 5f);
mystate=State.DOWN;
target = this.transform.position - del;
} else if(mystate == State.RIGHT){
Vector3 del = new Vector3(0,0,-1) * Random.Range(1, 5f);
mystate=State.UP;
target = this.transform.position - del;
} else if(mystate == State.UP){
Vector3 del = new Vector3(-1,0,0) * Random.Range(1, 5f);
mystate=State.RIGHT;
target = this.transform.position - del;
} else if(mystate == State.DOWN){
Vector3 del = new Vector3(1,0,0) * Random.Range(1, 5f);
mystate=State.LEFT;
target = this.transform.position - del;;
}
locked=false;
}
private void OnTriggerEnter(Collider other)
{
string name = other.gameObject.name;
if(other.gameObject.name == "Cube") {
if(cd < 60) return;
cd=0;
Debug.Log(this.transform.gameObject.name+"change");
changeDirection1();
}
else
{
//Debug.Log(other.gameObject.name);
ani.SetBool("Attack", true);
}
}
}
工厂模式
工厂单例模式来生产巡逻兵
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sing : MonoBehaviour
{
protected static Factory instance;
public static Factory Instance{
get{
if(instance == null){
instance = (Factory)FindObjectOfType(typeof(Factory));
}
return instance;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
public class Factory : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Vector3 pos1=new Vector3(7,0,5);
Vector3 pos2=new Vector3(-4,0,5);
Vector3 pos3=new Vector3(-4,0,14);
Vector3 pos4=new Vector3(7,0,14);
GameObject xlb1;
GameObject xlb2;
GameObject xlb3;
GameObject xlb4;
xlb1 = Instantiate(Resources.Load("Prefabs/XLB"), pos1, Quaternion.identity) as GameObject;
xlb2 = Instantiate(Resources.Load("Prefabs/XLB"), pos2, Quaternion.identity) as GameObject;
xlb3 = Instantiate(Resources.Load("Prefabs/XLB"), pos3, Quaternion.identity) as GameObject;
xlb4 = Instantiate(Resources.Load("Prefabs/XLB"), pos4, Quaternion.identity) as GameObject;
xlb1.transform.gameObject.name = "xlb1";
xlb2.transform.gameObject.name = "xlb2";
xlb3.transform.gameObject.name = "xlb3";
xlb4.transform.gameObject.name = "xlb4";
}
// Update is called once per frame
void Update()
{
}
}
消息订阅模式
我用消息订阅模式来实现通知巡逻兵追角色,巡逻兵丢失角色和角色死亡这几个事件。
挂在角色上的脚本有
public delegate void Myposition(GameObject sender, string info);
public static event Myposition InArea1;
public static event Myposition InArea2;
public static event Myposition InArea3;
public static event Myposition InArea4;
public static event Myposition addscore;
public static event Myposition die;
巡逻兵会各自订阅这些推送,死亡推送的订阅是model来做的,控制整个系统的运行。
所以我们这个游戏还是MVC模式的。
int t=test;
if(transform.position.x>0 && transform.position.z<9){
test=1;
InArea1(this.gameObject,"area1");
}
if(transform.position.x<-1 && transform.position.z<9){
test=2;
InArea2(this.gameObject,"area2");
}
if(transform.position.x<-1 && transform.position.z>10){
test=3;
InArea3(this.gameObject,"area3");
}
if(transform.position.x>0 && transform.position.z>10){
test=4;
InArea4(this.gameObject,"area4");
}
if(t!=test) addscore(this.gameObject,"addscore");
我通过判断角色位置来发送推送,如果角色位置切换其实就意味着它摆脱了一个巡逻兵,所以分数加一,死亡推送发生在和巡逻兵碰撞
private void OnTriggerEnter(Collider other) {
Debug.Log(other.gameObject.tag);
if(other.gameObject.tag=="Finish"){
die(this.gameObject,"die");
ani.SetBool("die",true);
Debug.Log("die");
}
}
代码见github