1. 程式人生 > >Unity遊戲AI記錄(2d橫板為例)

Unity遊戲AI記錄(2d橫板為例)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GeneralPeopleController : MonoBehaviour {

protected ContactFilter2D contactFilter;
protected RaycastHit2D[] hitBuffer = new RaycastHit2D[16];
protected List<RaycastHit2D> hitBufferList = new List<RaycastHit2D>(16);
protected Rigidbody2D rigid2D;
ConstantForce2D constantForce2D;
private SpriteRenderer spriteRenderer;
private Animator animator;

public LayerMask layerMask;//接觸的物體層
public LayerMask tarLayerMask;//攻擊的物體層
public float minGroundNormalY = 0.6f;//與物體碰撞的法向量的用於定義是否站在地面上的y值
public bool grounded = false;
public bool walled = false;
public bool pited = false;
public bool isGetPlayer=false;
public float jumpForce = 20f;//跳躍力度
public float horizonForce = 30f;//水平運動的驅動力
public Vector2 maxSpeed;
public int horizonDir;

public float climbSpeed = 2f;

bool inLadder = false;
public bool lockIgnore = false;

public Collider2D targetCollider2D;

public float fightTime = 0.2f;//攻擊狀態持續時間
float fightTimer = 0f;
public float fightSpaceTime = 2f;
float fightSpaceTimer=0f;
//攻擊的範圍框體
public Vector2 boxFightZoneSize;
public Vector2 boxFightZonePos;

//被攻擊狀態的持續時間
public float beAttackedContinueTime = 0.1f;
float beAttackedContinueTimer = 0;

float minGroundNormalX=0.7f;

//檢測坑的框體範圍
public Vector2 boxCheckPitSize;
public Vector2 boxCheckPitPos;
//偵測目標的框體範圍
public Vector2 boxCheckTargetSize;
public Vector2 boxCheckTargetPos;

//目標物件
public Transform targetTransform;

//角色行為模式定義:平民,敵人
public int role;
//或者稱為狀態模式,建立在多個情緒運動的基礎上,表示現在進入哪種行為動作,逃跑,攻擊,巡邏;比如市民被攻擊的時候第一時間是逃跑,
//而敵人就應該是發起反擊
public int state;
//角色的情緒狀態,表示角色現在想做什麼,這是角色行為最高層的狀態:巡邏(隨機運動),警戒(一直水平運動,遇到牆體或者坑就返回),
//追蹤(向目標位置運動,到達位置後在左右徘徊),攻擊(面向目標位置,直接攻擊)
public int emotionState;
//運動狀態(向左運動,向右運動,靜止)
public int moveState;

//時間引數
//狀態行為持續時間引數
float stateTime=0f;
//情緒行為持續時間
float emotionTime = 0f;
//運動狀態持續時間
float moveTime=0f;

// Use this for initialization
void Start()
{
constantForce2D = GetComponent<ConstantForce2D>();
rigid2D = GetComponent<Rigidbody2D>();
contactFilter.SetLayerMask(layerMask);
}

private void Update()
{
CoolTimer();
}

private void FixedUpdate()
{
grounded = false;
CheckGround();
walled = false;
CheckWalled(horizonDir);
pited = false;
CheckPit(horizonDir);
isGetPlayer = false;
CheckTarget(horizonDir);

Role();
}

//---------------------------------------------------------------角色行為------------------------------------------------------------------
//表示角色的行為模式,
void Role() {
switch (role) {
case 0:Enemy(); break;
case 1:CommonRole(); break;
}
}

//---------------------------------------------------------------狀態行為------------------------------------------------------------------
//或者稱為狀態模式,建立在多個情緒運動的基礎上,表示現在進入哪種行為動作,逃跑,攻擊,巡邏;比如市民被攻擊的時候第一時間是逃跑,
//而敵人就應該是發起反擊


//市民
void CommonRole() {
switch (emotionState) {
case 0:RandomMove();

break;
case 1:WarningMove(); break;
}
}

//敵人
//預設是巡邏狀態;如果發生警報那麼進入警戒狀態;如果發現目標,並且目標在追蹤範圍內,那麼進入追蹤狀態;如果目標在攻擊範圍內那麼進入攻擊狀態
void Enemy() {
switch (state) {
case 0:
Patrol();
break;
case 1:
Warning();
if (targetTransform) {
state = 3;
emotionState = 0;
}
if (stateTime <= 0) {
state = 0;
emotionState = 1;
moveState = 0;
}
break;
case 2:
Track();
break;
case 3:
Fight();
if (!targetTransform || Vector3.Distance(transform.position, targetTransform.position) > 20f) {
state = 1;
emotionState = 0;
moveState = Random.Range(1, 3);
rigid2D.drag = 0f;
stateTime = Random.Range(20, 40);
}
break;
}
}

//---------------------------------------------------------------情緒運動------------------------------------------------------------------
//情緒行為是建立在基本行為的基礎上,由多個基本行為組成,表示為了表達當前的情緒而進行的行為,比如攻擊情緒,先進行基本追蹤尋路,
//然後到達攻擊位置後進入攻擊行為

//巡邏,是隨機運動和警戒運動結合的更上一層的隨機行為運動
void Patrol() {
switch (emotionState) {
case 0:WarningMove();
if (emotionTime <= 0) {
emotionTime = Random.Range(5, 10);
emotionState = 1;
}
break;
case 1:RandomMove();
if (emotionTime <= 0)
{
emotionTime = Random.Range(5, 10);
moveState = 1;
emotionState = 0;
rigid2D.drag = 0f;
}
break;
}
}

//警戒
void Warning()
{
switch (emotionState) {
case 0:WarningMove();break;
}
}

//追蹤
void Track() {
switch (emotionState)
{
case 0: TrackingMove(); break;
}
}

//攻擊
void Fight() {
switch (emotionState)
{
case 0: TrackingMove();
if (grounded &&targetTransform&& Vector3.Distance(transform.position,targetTransform.position) < 1f) {
horizonDir = (int)Mathf.Sign(targetTransform.position.x - transform.position.x);
emotionState = 1;
moveState = 0;
rigid2D.drag = 20f;
}
break;
case 1: FightMove();
if (grounded&&targetTransform && (Vector3.Distance(transform.position, targetTransform.position) > 1f||horizonDir!= (int)Mathf.Sign(targetTransform.position.x - transform.position.x))&&moveState==1)
{
horizonDir = (int)Mathf.Sign(targetTransform.position.x - transform.position.x);
emotionState = 0;
rigid2D.drag = 0f;
}
break;
}
}

//---------------------------------------------------------------基本行為------------------------------------------------------------------
//攻擊行為
void FightMove() {
switch (moveState) {
case 0://攻擊狀態,對應攻擊的動作時間
if (fightTimer <= 0) {
moveState = 1;
fightSpaceTimer = fightSpaceTime;
}
break;
case 1://預備攻擊狀態
if (fightSpaceTimer <= 0) {
moveState = 0;
fightTimer = fightTime;
}
break;
}
}

//追蹤運動
void TrackingMove() {
int tarDir=0;
if(targetTransform)
tarDir = (int)(Mathf.Sign(targetTransform.position.x - transform.position.x));
switch (moveState) {
case 1:
HorizonMove(1);
horizonDir = 1;
if ((pited || walled) && grounded)
{
moveState = 2;
horizonDir = -1;
if (tarDir == 1) moveTime = 0.3f;//如果在追蹤目標的方向碰到坑道,那麼朝相反地方方向移動一段時間,表示在邊緣徘徊
}
else if (tarDir != 1&&moveTime<=0 && grounded) {
moveState = 2;
horizonDir = -1;
}
break;
case 2:
HorizonMove(-1);
horizonDir = -1;
if ((pited || walled) && grounded)
{
moveState = 1;
horizonDir = 1;
if (tarDir == -1) moveTime = 0.3f;
}
else if (tarDir != -1 && moveTime <= 0 && grounded)
{
moveState = 1;
horizonDir = 1;
}
break;
}
}

//警戒運動
void WarningMove() {
switch (moveState) {
case 1:
HorizonMove(1);
horizonDir = 1;
if ((pited || walled) && grounded)
{
moveState = 2;
horizonDir = -1;
}
break;
case 2:
HorizonMove(-1);
horizonDir = -1;
if ((pited || walled) && grounded)
{
moveState = 1;
horizonDir = 1;
}
break;
}
}

//隨機運動
void RandomMove() {
switch (moveState) {
case 0://靜止狀態
if (moveTime <= 0) {
moveTime = Random.Range(1, 3);
float randFloat = Random.Range(0, 100);

if (randFloat >= 0 && randFloat < 50)
{
moveState = 1; rigid2D.drag = 0f; horizonDir = 1;
}
else if (randFloat >= 50 && randFloat < 100)
{
moveState = 2; rigid2D.drag = 0f; horizonDir = -1;
}
}
break;
case 1://向右運動

//運動,如果時間到或者遇到牆體,坑地,轉換運動方向
HorizonMove(1);

//狀態轉換
if (moveTime <= 0)
{
moveTime = Random.Range(1, 5);
float randFloat = Random.Range(0, 100);

if (randFloat >= 0 && randFloat < 40)
{
moveState = 0;
//設定角色減速
if (grounded) rigid2D.drag = 20f;
constantForce2D.force = new Vector2(0, 0);
}
else if (randFloat >= 40 && randFloat < 100) {
moveState = 2;
horizonDir = -1;
}
}
else if ((pited || walled)&&grounded) {
moveState = 2;
horizonDir = -1;
}
break;
case 2://向左運動
HorizonMove(-1);

//狀態轉換
if (moveTime <= 0)
{
moveTime = Random.Range(1, 5);
float randFloat = Random.Range(0, 100);

if (randFloat >= 0 && randFloat < 40)
{
moveState = 0;
//設定角色減速
if (grounded) rigid2D.drag = 20f;
constantForce2D.force = new Vector2(0, 0);
}
else if (randFloat >= 40 && randFloat < 100) {
moveState = 1;
horizonDir = 1;
}
}
else if ((pited || walled)&&grounded)
{
moveState = 1;
horizonDir = 1;
}
break;
}
}

//---------------------------------------------------------------底層運動------------------------------------------------------------------
//水平運動
void HorizonMove(int dir) {
if (Mathf.Sign(rigid2D.velocity.x)!=Mathf.Sign(dir*maxSpeed.x) || Mathf.Sign(rigid2D.velocity.x) == Mathf.Sign(dir * maxSpeed.x) && Mathf.Abs( rigid2D.velocity.x) < maxSpeed.x)
constantForce2D.force = new Vector2(dir*horizonForce, 0);
else constantForce2D.force = new Vector2(0, 0);
}

private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag.Equals("Ladder"))
{
inLadder = true;
targetCollider2D = collision.gameObject.GetComponent<LaderClimb>().targetCollider2D;
}
}

private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag.Equals("Ladder"))
{
inLadder = false;
}

if (collision.gameObject.tag.Equals("LockIgnore")) lockIgnore = false;
}

private void OnTriggerStay2D(Collider2D collision)
{
if (collision.gameObject.tag.Equals("LockIgnore")) lockIgnore = true;
}

private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
Vector2 pos = new Vector2(transform.position.x + horizonDir * boxFightZonePos.x, transform.position.y + boxFightZonePos.y);
Gizmos.DrawWireCube(pos, boxFightZoneSize);

Gizmos.color = Color.red;
pos = new Vector2(transform.position.x + horizonDir*boxCheckPitPos.x, transform.position.y + boxCheckPitPos.y);
Gizmos.DrawWireCube(pos, boxCheckPitSize);

Gizmos.color = Color.yellow;
pos = new Vector2(transform.position.x + horizonDir * boxCheckTargetPos.x, transform.position.y + boxCheckTargetPos.y);
Gizmos.DrawWireCube(pos, boxCheckTargetSize);
}

//檢查是否有坑
void CheckPit(int dir) {
Vector2 pos = new Vector2(transform.position.x +dir* boxCheckPitPos.x, transform.position.y + boxCheckPitPos.y);
RaycastHit2D[] hit2Ds = Physics2D.BoxCastAll(pos, boxCheckPitSize, 0, Vector2.right, 0.1f,layerMask);
if (hit2Ds.Length == 0) pited = true;
}

//判斷是否碰到牆體
void CheckWalled(int dir) {
int count = rigid2D.Cast(Vector2.right*dir, contactFilter, hitBuffer, 0.02f);//對碰撞體向下的方向檢測是否站在某個物體上,精度由檢測的射線數和射線長度決定
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
//如果有一個 射線的碰撞點的法線的y大於minGroundNormalY那麼設定grounded為true表示站在了物體上
//這裡minGroundNormalY一般設定為1到0.6之間即可決定站的平面的傾斜度
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
if (Mathf.Abs(currentNormal.x) > minGroundNormalX)
{
walled = true;
}
}
}

//判斷是正站在某個物體上
void CheckGround()
{
int count = rigid2D.Cast(Vector2.down, contactFilter, hitBuffer, 0.02f);//對碰撞體向下的方向檢測是否站在某個物體上,精度由檢測的射線數和射線長度決定
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
//如果有一個 射線的碰撞點的法線的y大於minGroundNormalY那麼設定grounded為true表示站在了物體上
//這裡minGroundNormalY一般設定為1到0.6之間即可決定站的平面的傾斜度
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
if (currentNormal.y > minGroundNormalY)
{
grounded = true;
}
}
}

//檢查攻擊目標是否存在
void CheckTarget(int dir) {
Vector2 pos = new Vector2(transform.position.x + dir * boxCheckTargetPos.x, transform.position.y + boxCheckTargetPos.y);
RaycastHit2D[] hit2Ds = Physics2D.BoxCastAll(pos, boxCheckTargetSize, 0, Vector2.right, 0.1f, tarLayerMask);
foreach (RaycastHit2D ray in hit2Ds)
{
if (ray.transform.tag.Equals("Player")&&(!targetTransform||Vector3.Distance(transform.position,targetTransform.position)>20))
{
targetTransform = ray.transform;
Debug.Log(targetTransform.tag);
}
}
}

//正常運動狀態:跳躍,走動
void NormalMove()
{
float j = Input.GetAxis("Jump");
if (grounded && (Input.GetButtonUp("Jump") || j > 0.99f))
{
rigid2D.AddForce(new Vector2(0, j * jumpForce));
}

if (Input.GetKey(KeyCode.D))
{
if (rigid2D.velocity.x < maxSpeed.x)
constantForce2D.force = new Vector2(horizonForce, 0);
else constantForce2D.force = new Vector2(0, 0);
}
else if (Input.GetKey(KeyCode.A))
{
if (rigid2D.velocity.x > -maxSpeed.x)
constantForce2D.force = new Vector2(-horizonForce, 0);
else constantForce2D.force = new Vector2(0, 0);
}
else constantForce2D.force = new Vector2(0, 0);

//設定角色減速
if (grounded && !(Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D) || Input.GetKeyDown(KeyCode.Space)) && !lockIgnore) rigid2D.drag = 20f;
else rigid2D.drag = 0f;
}

//攀爬樓梯
void ClimbLadder()
{
if (Input.GetKey(KeyCode.W))
{
rigid2D.velocity = new Vector2(0, climbSpeed);
Physics2D.IgnoreCollision(GetComponent<Collider2D>(), targetCollider2D);
}
else if (Input.GetKey(KeyCode.S))
{
rigid2D.velocity = new Vector2(0, -climbSpeed);
Physics2D.IgnoreCollision(GetComponent<Collider2D>(), targetCollider2D);
}
else
{
rigid2D.velocity = new Vector2(0, 0);
}

if ((Mathf.Abs(Input.GetAxis("Horizontal")) > 0) && (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))) rigid2D.velocity = new Vector2(climbSpeed * Input.GetAxis("Horizontal"), rigid2D.velocity.y);

}

void fight()
{
Vector2 pos = new Vector2(transform.position.x + boxFightZonePos.x, transform.position.y + boxFightZonePos.y);
RaycastHit2D[] hit2Ds = Physics2D.BoxCastAll(pos, boxFightZoneSize, 0, Vector2.right, 0.1f);
for (int i = 0; i < hit2Ds.Length; i++)
{
if (hit2Ds[i].collider.tag.Equals("Enemy"))
Debug.Log(hit2Ds[i].collider.tag);
}
}

void CoolTimer()
{
if (fightTimer > 0) fightTimer -= Time.deltaTime;
if (beAttackedContinueTimer > 0) beAttackedContinueTimer -= Time.deltaTime;
if (moveTime > 0) moveTime -= Time.deltaTime;
if (fightSpaceTimer > 0) fightSpaceTimer -= Time.deltaTime;
if (emotionTime > 0) emotionTime -= Time.deltaTime;
if (stateTime > 0) stateTime -= Time.deltaTime;
}
}

 

最後:上面的構思大多來自《Windows遊戲程式設計:大師技巧》,老外寫的,迄今為止我買過的最好的書,他還有一個版本是關於寫3d遊戲,但是偏底層,能讀懂搞定的話你就可以自己寫個遊戲引擎了,讀到一半搞了個demo我就放棄了,這玩意不是一個人能搞定的。好吧,吐槽一下,外國人寫書真的是簡單明瞭,國人就喜歡搞玄學化。