智能巡逻兵

UML图

用的依然是第一次作业就在用的祖传架构,不过这次可以整体复用的比较少,只有 IsceneController,Director和singleton。其他类都需要做出较大的改动。
UML图

动画编辑

玩家,也就是rooster的动画逻辑比较简单,一直奔跑就可以

在这里插入图片描述

巡逻兵,也就是Crow的动画逻辑如下:

在这里插入图片描述
其中从Fly到Attack需要一个trigger类型的变量,在Crow捕捉到rooster的时候设置即可,相关代码如下:

if (name.Substring(0,6) == "rooste") {
    m_Animator.SetTrigger("fly_attack");
}

代码实现

本次作业我采用了自底向上的方式,首先实现了较为底层的Moveable和Patrolable。Moveable和Patrolable相当于两种功能,若gameObject挂载上Moveable就可以控制WASD控制行动;Patrolable则是由巡逻兵挂载上,以实现自动巡逻以及追击功能。

Moveable 核心代码如下:

if (Input.GetKey(KeyCode.W))
{
    m_Animator.SetTrigger("standMove");
    m_transform.rotation = Quaternion.Euler(0, 180, 0);
    zm += m_movSpeed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.S))//按键盘S向下移动
{
    m_Animator.SetTrigger("standMove");
    m_transform.rotation = Quaternion.Euler(0, 0, 0);
    zm -= m_movSpeed * Time.deltaTime;
}

if (Input.GetKey(KeyCode.A))//按键盘A向左移动
{
    m_Animator.SetTrigger("standMove");
    m_transform.rotation = Quaternion.Euler(0, 90, 0);
    xm -= m_movSpeed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.D))//按键盘D向右移动
{
    m_Animator.SetTrigger("standMove");
    m_transform.rotation = Quaternion.Euler(0, 270, 0);
    xm += m_movSpeed * Time.deltaTime;
}
m_transform.position = m_transform.position + new Vector3 (xm, ym, zm);

Patrolable的实现相对复杂。为了实现巡逻的目的,首先要构造一个矩形,并在矩形上随机选取3~5个点。为了实现这个目的,我采取的策略是先选取三个点,若随机选取的点数大于三,再选取剩余的点:

	void SetPatrolPoints(){
		patrolPoints [0] = transform.position;
		patrolPoints[1] = patrolPoints [0] + new Vector3(Random.Range(0.0f,domainWidth),0.5f,-domainWidth/2);
		patrolPoints[2] = patrolPoints [0] + new Vector3(domainWidth,0.5f,Random.Range(-domainWidth/2,domainWidth/2));
		if (pointNum >= 4) {
			patrolPoints[3] = patrolPoints [0] + new Vector3(Random.Range(0.0f,domainWidth),0.5f,domainWidth/2);
		} 
		if (pointNum == 5){
			patrolPoints[4] = patrolPoints [0] + new Vector3(0,0.5f,domainWidth/2);
		}
	}

选取好点之后,根据反三角函数算出从当前点到下一个点运动时Crow需要旋转的角度,计算方式如下:

	float Angle(Vector3 cen, Vector3 first, Vector3 second) 
	{ 
		float dx1, dx2, dy1, dy2, dx3, dy3; 
		float angle; 
		dx1 = first.x - cen.x; 
		dy1 = first.z - cen.z; 
		dx2 = second.x - cen.x; 
		dy2 = second.z - cen.z; 
		dx3 = second.x - first.x;
		dy3 = second.z - first.z;
		float a = (float)Mathf.Sqrt(dx1 * dx1 + dy1 * dy1);
		float b = (float)Mathf.Sqrt(dx2 * dx2 + dy2 * dy2);
		float c =(float)Mathf.Sqrt(dx3 * dx3 + dy3 * dy3);
		
		if (dx1 == 0 && dy1 < 0) return 180; 
		//angle = (float)Mathf.Acos((dx1 * dx2 + dy1 * dy2) / c); 
		angle = (float)Mathf.Acos((a*a + b*b -c*c) / (2*a*b)); 
		if (angle == null)
			return 180;
		return (angle *180) / 3.14159f ; 
	} 

随后就可以通过MoveTowards前往下一个点了。

注意还需要检查是否撞墙,撞墙后巡逻顺序要反过来:

	void CheckHitWall(){
		reverse = (reverse + 1) % 2;
		int temp = curPoint;
		curPoint = nextPoint;
		nextPoint = temp;
	}

整体的巡逻函数如下:

	void Patrol(){
		GetComponent<Rigidbody>().velocity = Vector3.zero;
		GetComponent<Rigidbody>().angularVelocity = Vector3.zero;

		if (this.transform.position == patrolPoints [nextPoint]) {

			if(reverse == 0){
				if(nextPoint == pointNum-1){
					curPoint = nextPoint;
					nextPoint = 0;
				}else{
					curPoint = nextPoint;
					nextPoint ++;
				}
			}
			else{
				if(nextPoint == 0){
					curPoint = nextPoint;
					nextPoint = pointNum -1;
				}
				curPoint = nextPoint;
				nextPoint --;
			}
			float yRotation = Angle(transform.position,patrolPoints [nextPoint],new Vector3(transform.position.x,0.5f,transform.position.z+1));
			if(patrolPoints [nextPoint].x < transform.position.x) yRotation = -yRotation;
			transform.rotation = Quaternion.Euler(0, yRotation, 0);
		}
		float step =  speed * Time.deltaTime;

		// check if find enemy
		if (findEnemy) {
			float yRotation = Angle (transform.position, enemyPos, new Vector3 (transform.position.x, 0.5f, transform.position.z + 1));
			if (enemyPos.x < transform.position.x)
				yRotation = -yRotation;
			transform.rotation = Quaternion.Euler (0, yRotation, 0);
			transform.position = Vector3.MoveTowards (transform.position, enemyPos, step);
		} else {
			float yRotation = Angle(transform.position,patrolPoints [nextPoint],new Vector3(transform.position.x,0.5f,transform.position.z+1));
			if(patrolPoints [nextPoint].x < transform.position.x) yRotation = -yRotation;
			transform.rotation = Quaternion.Euler(0, yRotation, 0);
			transform.position = Vector3.MoveTowards (transform.position, patrolPoints[nextPoint], step);
		}
		// check if hit wall
		//CheckHitWall ();
		// move to next point

	}

有了Patrolable函数后,可以挂载到一个Crow实例上查看一下效果,发现可以正常巡逻后再来实现Crow类。

Crow类主要管理crow实例的初始位置,Patrolable组件也是在这里添加上的。另外在这个类初始化的时候根据乌鸦所处的位置设置了行动速度,越深入乌鸦运行速度越快,游戏也就越困难。

	public class Crow{
		public GameObject crow;
		public Vector3 startPoint;
		public Patrolable pt;

		public Crow(float x,float y){
			haveFoundEnemy = false;
			startPoint = new Vector3 (10 * x, 0.5f, 10 * y);
			crow = Object.Instantiate((GameObject)UnityEditor.AssetDatabase.LoadAssetAtPath ("Assets/Prefabs/crow.prefab", typeof(GameObject)),startPoint , Quaternion.identity) as GameObject;
			//crow = Object.Instantiate(Resources.Load("Assets/Prefabs/crow"),new Vector3(0,0,0), Quaternion.identity);
			pt = crow.AddComponent(typeof(Patrolable)) as Patrolable;
			if (x >= 3.0f) {
				pt.SetSpeed(6);
			}
		}
	}

CrowFactory初始时维护一个空链表,GetCrow()函数会填充这个空链表并返回。

	public class Crow{
		public GameObject crow;
		public Vector3 startPoint;
		public Patrolable pt;

		public Crow(float x,float y){
			haveFoundEnemy = false;
			startPoint = new Vector3 (10 * x, 0.5f, 10 * y);
			crow = Object.Instantiate((GameObject)UnityEditor.AssetDatabase.LoadAssetAtPath ("Assets/Prefabs/crow.prefab", typeof(GameObject)),startPoint , Quaternion.identity) as GameObject;
			pt = crow.AddComponent(typeof(Patrolable)) as Patrolable;
			if (x >= 3.0f) {
				pt.SetSpeed(6);
			}
		}
	}

MsgManager管理信息的传递,由于这次老师要求用订阅与发布模式,所以需要这个类居中管理信息的传递。其他类通过单例模式引用这个类传递信息。MsgManager维护需要被传递信息的实例,本次作业中体现为FirstController,因此FirstController必须有接收信息的功能。

public class MsgManager : MonoBehaviour {
	private UserAction action;
	// Use this for initialization
	void Start () {
		action = Director.getInstance().currentSceneController as UserAction;
	}

	void Update(){
		if(action == null)
			action = Director.getInstance().currentSceneController as UserAction;
	}
	
	public void SendMsg(string msg, string from, string to){
		if (to == "FC") {
			action.ReceiveMessage(msg);
		}
	}
}

另外本次作业是由挂载Moveable的rooster和挂载Patrolable的Crow来向FirstController传递加分、游戏结束、游戏胜利的信息。举例如下:

public MsgManager msgm;
void Awake(){
	···
	msgm = Singleton<MsgManager>.Instance;
}
void OnLostEnemy(){
	···
	msgm.SendMsg ("add score","PT","FC");
}

FirstController实现起来比较简单,大部分代码都用于加载资源。其他例如Restart(),BeginGame()等函数的实现也相对简单。和以往不同的是,FirstController类实现了ReceiveMessage()函数以接收MsgController传来的信息。

public void ReceiveMessage(string msg){
		if (msg == "game over") {
			Debug.Log ("game over right here");
			GameOver ();
		} else if (msg == "add score") {
			Debug.Log ("add score right here");
			Debug.Log (score);
			score ++;
		} else if (msg == "win") {
			GameOver();
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值