Unity Thêm kẻ thù vào nền tảng 2D
Tạo Nền tảng trong Unity tương đối dễ dàng, nhưng việc thêm kẻ thù chức năng có thể không đơn giản như vậy.
Trong bài đăng này, tôi sẽ hướng dẫn cách tạo một trò chơi platformer 2D với AI của kẻ thù.
Nói chung trong 2D platformer, người chơi chỉ có thể đi tới/sau, nhảy và trong một số trường hợp leo lên/xuống thang nếu bản đồ có nhiều cấp độ. Biết rằng chúng tôi có thể sử dụng phương pháp mô-đun trong đó người chơi và AI chia sẻ cùng một bộ điều khiển.
Bước 1: Tạo tập lệnh
Hãy bắt đầu bằng cách tạo tất cả các tập lệnh cần thiết. Kiểm tra mã nguồn bên dưới:
Thang2D.cs
//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed
using UnityEngine;
public class Ladder2D : MonoBehaviour
{
Collider2D ladderCollider;
[HideInInspector]
public Vector3 boundsCenter;
void Start()
{
//t = transform;
ladderCollider = GetComponent<Collider2D>();
if (ladderCollider)
{
ladderCollider.isTrigger = true;
ladderCollider.gameObject.layer = 2; //Set ladder collider layer to IgnoreRaycast
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
if (ladderCollider)
{
boundsCenter = ladderCollider.bounds.center;
}
other.SendMessage("AssignLadder", this, SendMessageOptions.DontRequireReceiver);
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
other.SendMessage("RemoveLadder", this, SendMessageOptions.DontRequireReceiver);
}
}
}
PlayerController2D.cs
//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed
using System.Collections.Generic;
using UnityEngine;
public class PlayerController2D : MonoBehaviour
{
//Move player in 2D space
public float maxSpeed = 2.57f;
public float jumpHeight = 6.47f;
public float playerHP = 100;
[HideInInspector]
public bool facingRight = true;
[HideInInspector]
public float moveDirection = 0;
[HideInInspector]
public Rigidbody2D r2d;
[HideInInspector]
public Collider2D mainCollider;
[HideInInspector]
public Vector2 playerDimensions;
[HideInInspector]
public bool isGrounded = false;
//Check every collider except Player and Ignore Raycast
LayerMask layerMask = ~(1 << 2 | 1 << 8); //Make sure our player has Layer 8
[HideInInspector]
public Ladder2D currentLadder;
List<Ladder2D> allLadders = new List<Ladder2D>();
float moveDirectionY = 0;
float distanceFromLadder;
[HideInInspector]
public bool isAttachedToLadder = false;
bool ladderGoingDown = false;
//bool isMovingOnLadder = false;
[HideInInspector]
public bool canGoDownOnLadder = false;
[HideInInspector]
public bool canClimbLadder = false;
//Bot movement directions
[HideInInspector]
public bool isBot = false;
[HideInInspector]
public float botMovement = 0;
[HideInInspector]
public float botVerticalMovement = 0;
[HideInInspector]
public bool botJump = false;
[HideInInspector]
public Transform t;
[HideInInspector]
public int selectedWeaponTmp = 0;
float gravityScale;
// Use this for initialization
void Start()
{
r2d = GetComponent<Rigidbody2D>();
r2d.freezeRotation = true;
mainCollider = GetComponent<Collider2D>();
t = transform;
gravityScale = r2d.gravityScale;
selectedWeaponTmp = -100;
facingRight = t.localScale.x > 0;
//sr = GetComponent<SpriteRenderer>();
playerDimensions = BotController2D.ColliderDimensions(GetComponent<Collider2D>());
}
void OnDisable()
{
r2d.bodyType = RigidbodyType2D.Static;
r2d.velocity = Vector3.zero;
}
// Update is called once per frame
void Update()
{
if (!isBot)
{
if ((Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D)) &&
(isGrounded || r2d.velocity.x > 0.01f || isAttachedToLadder))
{
moveDirection = Input.GetKey(KeyCode.A) ? -1 : 1;
}
else
{
if (isGrounded || r2d.velocity.magnitude < 0.01f)
moveDirection = 0;
}
}
else
{
if (botMovement != 0 && (isGrounded || r2d.velocity.x > 0.01f))
{
moveDirection = botMovement < 0 ? -1 : 1;
}
else
{
if (isGrounded || r2d.velocity.magnitude < 0.01f)
moveDirection = 0;
}
}
//Change facing position
if (moveDirection != 0)
{
if (moveDirection > 0 && !facingRight)
{
facingRight = true;
}
if (moveDirection < 0 && facingRight)
{
facingRight = false;
}
}
if (facingRight)
{
if (t.localScale.x < 0)
{
t.localScale = new Vector3(Mathf.Abs(t.localScale.x), t.localScale.y, transform.localScale.z);
}
}
else
{
if (t.localScale.x > 0)
{
t.localScale = new Vector3(-Mathf.Abs(t.localScale.x), t.localScale.y, t.localScale.z);
}
}
//Vector2 velocityTmp = r2d.velocity;
bool canGoDownTmp = false;
// LADDER CONTROL START
if (currentLadder)
{
distanceFromLadder = Mathf.Abs(currentLadder.boundsCenter.x - t.position.x);
canClimbLadder = distanceFromLadder < 0.34f;
if (!isAttachedToLadder)
{
if (canClimbLadder)
{
if (currentLadder.boundsCenter.y > t.position.y)
{
if (!isBot)
{
if (Input.GetKey(KeyCode.W))
{
isAttachedToLadder = true;
}
}
else
{
if (botVerticalMovement > 0)
{
isAttachedToLadder = true;
}
}
}
if (currentLadder.boundsCenter.y < t.position.y)
{
if (!isBot)
{
if (Input.GetKey(KeyCode.S))
{
isAttachedToLadder = true;
}
}
else
{
if (botVerticalMovement < 0)
{
isAttachedToLadder = true;
}
}
canGoDownTmp = true;
}
}
if (isAttachedToLadder)
{
r2d.gravityScale = 0;
moveDirection = 0;
moveDirectionY = 0;
}
}
else
{
//Make our collider trigger if we stand on top of the ladder (To prevent collision with the ground while going down)
mainCollider.isTrigger = currentLadder.boundsCenter.y < t.position.y;
//Ladder movement
if ((!isBot && Input.GetKey(KeyCode.W)) || (isBot && botVerticalMovement > 0))
{
moveDirectionY = 3.97f;
ladderGoingDown = false;
//For sound controller
//isMovingOnLadder = true;
}
else if ((!isBot && Input.GetKey(KeyCode.S)) || (isBot && botVerticalMovement < 0))
{
moveDirectionY = -3.97f;
ladderGoingDown = true;
if (!mainCollider.isTrigger && isGrounded)
{
//RemoveLadder(currentLadder);
isAttachedToLadder = false;
mainCollider.isTrigger = false;
r2d.gravityScale = gravityScale;
moveDirectionY = 0;
}
//For sound controller
//isMovingOnLadder = true;
}
else
{
//isMovingOnLadder = false;
moveDirectionY = 0;
}
}
if (distanceFromLadder > playerDimensions.x * 2)
{
RemoveLadder(currentLadder);
}
}
canGoDownOnLadder = canGoDownTmp;
// LADDER CONTROL END
if (!isBot)
{
//Jumping
if (Input.GetKeyDown(KeyCode.W))
{
Jump();
}
}
else
{
if (botJump)
{
botJump = false;
Jump();
}
}
if (!isBot)
{
//Weapon firing
if (Input.GetKeyDown(KeyCode.LeftControl))
{
Attack();
}
}
}
void FixedUpdate()
{
Bounds colliderBounds = mainCollider.bounds;
Vector3 groundCheckPos = colliderBounds.min + new Vector3(colliderBounds.size.x * 0.5f, 0.1f, 0);
//Check if player is grounded
isGrounded = Physics2D.OverlapCircle(groundCheckPos, 0.25f, layerMask);
Debug.DrawLine(groundCheckPos, groundCheckPos - new Vector3(0, 0.25f, 0), isGrounded ? Color.green : Color.red);
//Apply player velocity
r2d.velocity = new Vector2((moveDirection) * maxSpeed, isAttachedToLadder ? moveDirectionY : r2d.velocity.y);
}
void AssignLadder(Ladder2D ladderTmp)
{
currentLadder = ladderTmp;
allLadders.Add(ladderTmp);
}
void RemoveLadder(Ladder2D ladderTmp)
{
//print("On trigger out");
allLadders.Remove(ladderTmp);
if (currentLadder == ladderTmp)
{
currentLadder = null;
if (allLadders.Count > 0)
{
currentLadder = allLadders[allLadders.Count - 1];
}
}
if (isAttachedToLadder && !currentLadder)
{
isAttachedToLadder = false;
//r2d.bodyType = RigidbodyType2D.Dynamic;
mainCollider.isTrigger = false;
r2d.gravityScale = gravityScale;
r2d.velocity = Vector3.zero;
if (!ladderGoingDown)
{
r2d.velocity = new Vector2(r2d.velocity.x, 1.47f);
}
ladderGoingDown = false;
}
}
public void Jump()
{
if (isGrounded && !isAttachedToLadder)
{
r2d.velocity = new Vector2(r2d.velocity.x, jumpHeight);
//Tip: Play jump sound here
}
}
public void Attack()
{
print(gameObject.name + " is Attacking");
//Tip: Write your attack function here (ex. Raycast toward the enemy to inflict the damage)
}
}
CameraFollow2D.cs
//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed
using UnityEngine;
public class CameraFollow2D : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0, 2.8f, 0);
public bool smoothFollow = true;
Vector2 moveToPos;
bool beginMove = false;
float distanceTmp = 0f;
// Update is called once per frame
void LateUpdate()
{
if (!target)
return;
if (smoothFollow)
{
distanceTmp = ((target.position + offset) - transform.position).sqrMagnitude;
if (beginMove)
{
moveToPos = Vector3.Lerp(moveToPos, target.position + offset, Time.fixedDeltaTime * 7.75f);
transform.position = new Vector3(moveToPos.x, moveToPos.y, -10);
if (distanceTmp < 0.05f * 0.05f)
{
beginMove = false;
}
}
else
{
if (distanceTmp > 0.5f * 0.5f)
{
beginMove = true;
}
}
}
else
{
transform.position = new Vector3(target.position.x, target.position.y, -10);
}
}
void StopFollowing()
{
beginMove = false;
}
}
BotController2D.cs
//Copyright @2018 sharpcoderblog.com
//You are free to use this script in free or commercial projects
//Selling the source code of this script is not allowed
using System.Collections;
using UnityEngine;
public class BotController2D : MonoBehaviour
{
//This script will handle bot control
public enum BotType { Enemy, Friendly }
public BotType botType = BotType.Enemy;
public enum BotDifficulty { Easy, Medium, Hard }
public BotDifficulty botDifficulty = BotDifficulty.Medium;
public enum InitialState { Idle, Explore }
public InitialState initialState = InitialState.Idle; //Should the Bot stand in place until approached or begin exploring the level right away
public bool canJump = true; //Can this bot jump?
public enum CurrentState { Idle, MovingLeft, MovingRight, GoinUPLadder, GoingDownLadder, Attack }
CurrentState currentState;
InitialState appliedState;
PlayerController2D pc2d;
RaycastHit2D hitLeft;
RaycastHit2D hitRight;
RaycastHit2D groundHit;
Vector3 leftOrigin;
Vector3 rightOrigin;
float distanceLeft = -1;
float distanceRight = -1;
int encounteredLadders = 0;
int encounteredLaddersCacche = 0;
Ladder2D previousLadder;
Ladder2D lastAttachedLadder;
bool previousCanGoDownOnLadder = false;
bool previousCanClimbLadder = false;
//Everything except "Player" and "IgnoreRaycast" layers
LayerMask layerMask = ~(1 << 2 | 1 << 8);
//Only "Player" layer
LayerMask playerLayerMask = 1 << 8;
float timeMotionless = 0.0f;
bool statePause = false;
float trVelocity;
Vector3 previousPos;
Collider2D[] detectedPlayers = new Collider2D[0];
Collider2D[] previousDetectedPlayers = new Collider2D[0];
PlayerController2D enemyToFollow;
int followPriority = 0; //0 = Easy, 1 = Medium (This player inflicted the damage)
[HideInInspector]
public Transform t;
bool checkingTotalEnemies = false;
bool runAway = false;
int attackingFromLeft = 0;
int attackingFromRight = 0;
//Limit attack rate for easy bots
float attackTimer = 0;
float nextAttackTime = 0;
Camera mainCamera;
float cameraWidth; //Horizontal size of camera view
// Use this for initialization
void Start()
{
pc2d = GetComponent<PlayerController2D>();
pc2d.isBot = true;
t = transform;
appliedState = initialState;
if (Random.Range(-10, 10) > 0)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
// Update is called once per frame
void FixedUpdate()
{
//Draw rays back and forth
if (!mainCamera)
{
mainCamera = Camera.main;
cameraWidth = mainCamera.aspect * mainCamera.orthographicSize;
}
rightOrigin = t.position + t.right * (pc2d.playerDimensions.x / 2f);
hitRight = Physics2D.Raycast(rightOrigin, t.right, cameraWidth, layerMask);
if (hitRight)
{
Debug.DrawLine(rightOrigin, hitRight.point, Color.red);
distanceRight = hitRight.distance;
}
else
{
Debug.DrawLine(rightOrigin, rightOrigin + t.right * cameraWidth, Color.cyan);
distanceRight = -1;
}
leftOrigin = t.position - t.right * (pc2d.playerDimensions.x / 2f);
hitLeft = Physics2D.Raycast(leftOrigin, -t.right, cameraWidth, layerMask);
if (hitLeft)
{
Debug.DrawLine(leftOrigin, hitLeft.point, Color.red);
distanceLeft = hitLeft.distance;
}
else
{
Debug.DrawLine(leftOrigin, leftOrigin - t.right * cameraWidth, Color.cyan);
distanceLeft = -1;
}
if (appliedState == InitialState.Explore)
{
if (currentState == CurrentState.Idle)
{
if (!statePause)
{
//Decide which direction to move
if (distanceRight == -1 && distanceLeft == -1)
{
//Decide random direaction
currentState = Random.Range(-10, 10) > 0 ? CurrentState.MovingRight : CurrentState.MovingLeft;
}
else if (distanceRight == -1 && distanceLeft >= 0)
{
currentState = CurrentState.MovingRight;
}
else if (distanceRight >= 0 && distanceLeft == -1)
{
currentState = CurrentState.MovingLeft;
}
else if (distanceRight > distanceLeft)
{
currentState = CurrentState.MovingRight;
}
else if (distanceRight < distanceLeft)
{
currentState = CurrentState.MovingLeft;
}
}
}
else if (currentState == CurrentState.MovingLeft)
{
if (!statePause && pc2d.isGrounded)
{
pc2d.botMovement = -1;
float jumpHeightTmp = pc2d.jumpHeight * 0.25f;
if (distanceLeft > 0 && distanceLeft < pc2d.playerDimensions.x)
{
if (hitLeft && canJump &&
!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, layerMask) &&
Random.Range(-2, 10) > 0
)
{
StartCoroutine(DoJump());
}
else
{
if (!enemyToFollow)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
}
/*if(!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2)){
Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, Color.yellow);
}
else
{
Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) - t.right * pc2d.playerDimensions.x * 2, Color.red);
}*/
//Jump if there is no groun in front
groundHit = Physics2D.Raycast(leftOrigin, -t.up, pc2d.playerDimensions.y * 2.1f, layerMask);
if (groundHit)
{
Debug.DrawLine(leftOrigin, groundHit.point, Color.red);
}
else
{
Debug.DrawLine(leftOrigin, leftOrigin - t.up * (pc2d.playerDimensions.y * 2.1f), Color.blue);
if (canJump)
{
StartCoroutine(DoJump());
}
else
{
//StartCoroutine(StatePause(CurrentState.MovingRight, true));
StartCoroutine(CheckEnemiesEnumerator(1, 0, false));
}
}
}
}
else if (currentState == CurrentState.MovingRight)
{
if (!statePause && pc2d.isGrounded)
{
pc2d.botMovement = 1;
float jumpHeightTmp = pc2d.jumpHeight * 0.25f;
if (distanceRight > 0 && distanceRight < pc2d.playerDimensions.x)
{
if (hitRight && canJump &&
!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, layerMask) &&
Random.Range(-2, 10) > 0
)
{
StartCoroutine(DoJump());
}
else
{
if (!enemyToFollow)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
}
/*if (!Physics2D.Linecast(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2))
{
Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, Color.yellow);
}
else
{
Debug.DrawLine(t.position + t.up * jumpHeightTmp, (t.position + t.up * jumpHeightTmp) + t.right * pc2d.playerDimensions.x * 2, Color.red);
}*/
//Jump if there is no groun in front
groundHit = Physics2D.Raycast(rightOrigin, -t.up, pc2d.playerDimensions.y * 2.1f, layerMask);
if (groundHit)
{
Debug.DrawLine(rightOrigin, groundHit.point, Color.red);
}
else
{
Debug.DrawLine(rightOrigin, rightOrigin - t.up * (pc2d.playerDimensions.y * 2.1f), Color.blue);
if (canJump)
{
StartCoroutine(DoJump());
}
else
{
//StartCoroutine(StatePause(CurrentState.MovingLeft, true));
StartCoroutine(CheckEnemiesEnumerator(0, 1, false));
}
}
}
}
else if (currentState == CurrentState.GoinUPLadder)
{
if (!statePause)
{
pc2d.botVerticalMovement = 1;
if (!pc2d.currentLadder)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
}
else if (currentState == CurrentState.GoingDownLadder)
{
if (!statePause)
{
pc2d.botVerticalMovement = -1;
if (!pc2d.currentLadder)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
}
else if (currentState == CurrentState.Attack)
{
if (!statePause)
{
if (!enemyToFollow)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
else
{
//Firing weapon
if (attackTimer >= nextAttackTime)
{
//Check if player is above us and jump
if (enemyToFollow.t.position.y > t.position.y && enemyToFollow.t.position.y - t.position.y > pc2d.playerDimensions.y * 0.95f)
{
if (Random.Range(-5, 10) > 0)
{
StartCoroutine(DoJump());
}
}
//
pc2d.Attack();
if (botDifficulty == BotDifficulty.Easy)
{
attackTimer = 0;
nextAttackTime = Random.Range(0.25f, 0.95f);
}
if (botDifficulty == BotDifficulty.Medium)
{
attackTimer = 0;
nextAttackTime = Random.Range(0.01f, 0.37f);
}
if (botDifficulty == BotDifficulty.Hard)
{
attackTimer = 0;
nextAttackTime = Random.Range(0.01f, 0.24f);
}
}
else
{
attackTimer += Time.deltaTime;
}
if (enemyToFollow && !checkingTotalEnemies)
{
if (enemyToFollow.t.position.x > t.position.x && !pc2d.facingRight)
{
pc2d.facingRight = true;
}
if (enemyToFollow.t.position.x < t.position.x && pc2d.facingRight)
{
pc2d.facingRight = false;
}
attackingFromLeft = 0;
attackingFromRight = 0;
//Check if there too many player attacking us and run away
for (int i = 0; i < detectedPlayers.Length; i++)
{
if (detectedPlayers[i])
{
BotController2D bcTmp = detectedPlayers[i].GetComponent<BotController2D>();
if (bcTmp && bcTmp.botType != botType && bcTmp.enemyToFollow == pc2d && bcTmp.currentState == CurrentState.Attack)
{
if (bcTmp.t.position.x > t.position.x)
{
attackingFromRight++;
}
else
{
attackingFromLeft++;
}
}
}
}
//If the value playerHP from PlayerController2D get too low, and the bot is being attacked, increase the probability to run away
if (attackingFromRight >= 2 || attackingFromLeft >= 2 || (pc2d.playerHP < 70 && botDifficulty == BotDifficulty.Hard && (attackingFromRight > 0 || attackingFromLeft > 0)) || (pc2d.playerHP < 40 && botDifficulty == BotDifficulty.Medium && (attackingFromRight > 0 || attackingFromLeft > 0)))
{
StartCoroutine(CheckEnemiesEnumerator(attackingFromLeft, attackingFromRight, false));
}
}
}
}
}
}
if (pc2d.currentLadder && (previousLadder != pc2d.currentLadder || previousCanGoDownOnLadder != pc2d.canGoDownOnLadder || previousCanClimbLadder != pc2d.canClimbLadder))
{
previousLadder = pc2d.currentLadder;
previousCanGoDownOnLadder = pc2d.canGoDownOnLadder;
previousCanClimbLadder = pc2d.canClimbLadder;
if (!pc2d.isAttachedToLadder)
{
if (pc2d.canClimbLadder)
{
encounteredLadders++;
if ((lastAttachedLadder != pc2d.currentLadder || encounteredLadders > 1) && !statePause)
{
if (Random.Range(-10, 10) > 0)
{
if (pc2d.canGoDownOnLadder)
{
StartCoroutine(StatePause(CurrentState.GoingDownLadder, true));
}
else
{
StartCoroutine(StatePause(CurrentState.GoinUPLadder, true));
}
}
}
}
}
else
{
encounteredLadders = 0;
lastAttachedLadder = pc2d.currentLadder;
}
}
trVelocity = ((t.position - previousPos).magnitude) / Time.deltaTime;
previousPos = t.position;
if (trVelocity < 0.01f && !statePause)
{
timeMotionless += Time.deltaTime;
if (timeMotionless > 0.5f)
{
StartCoroutine(StatePause(CurrentState.Idle, true));
}
}
else
{
timeMotionless = 0;
}
//Detect and attack enemy players
detectedPlayers = Physics2D.OverlapCircleAll(t.position, cameraWidth, playerLayerMask);
if (!enemyToFollow)
{
if (!runAway)
{
if (previousDetectedPlayers.Length != detectedPlayers.Length || (previousDetectedPlayers.Length > 0 && detectedPlayers.Length > 0 && previousDetectedPlayers[0] != detectedPlayers[0]))
{
previousDetectedPlayers = detectedPlayers;
for (int i = 0; i < detectedPlayers.Length; i++)
{
BotController2D bcTmp = detectedPlayers[i].GetComponent<BotController2D>();
PlayerController2D pc2dTmp = null;
if (!bcTmp)
{
pc2dTmp = detectedPlayers[i].GetComponent<PlayerController2D>();
}
if ((pc2dTmp && botType == BotType.Enemy) || (bcTmp && bcTmp.botType != botType))
{
Vector3 enemyPos = bcTmp ? bcTmp.t.position : pc2dTmp.t.position;
float yDistance = Mathf.Abs(enemyPos.y - t.position.y);
if (yDistance < pc2d.playerDimensions.y * 2)
{
if (!enemyToFollow || Mathf.Abs(enemyPos.x - t.position.x) < Mathf.Abs(enemyToFollow.t.position.x - t.position.x))
{
enemyToFollow = bcTmp ? bcTmp.pc2d : pc2dTmp;
appliedState = InitialState.Explore;
}
}
}
}
}
}
}
else
{
float yDistance = enemyToFollow.t.position.y - t.position.y;
float xDistance = enemyToFollow.t.position.x - t.position.x;
if (Mathf.Abs(yDistance) >= pc2d.playerDimensions.y * 2 || Mathf.Abs(xDistance) > cameraWidth || !enemyToFollow.enabled)
{
enemyToFollow = null;
}
else
{
if (Mathf.Abs(xDistance) > pc2d.playerDimensions.x * 1.45f)
{
if (!statePause && pc2d.botVerticalMovement == 0)
{
if (xDistance > 0)
{
if (currentState != CurrentState.MovingRight)
{
statePauseCoroutine = StartCoroutine(StatePause(CurrentState.MovingRight, false));
}
}
else if (xDistance < 0)
{
if (currentState != CurrentState.MovingLeft)
{
statePauseCoroutine = StartCoroutine(StatePause(CurrentState.MovingLeft, false));
}
}
}
else
{
StopPauseCoroutine();
}
}
else
{
if (pc2d.botVerticalMovement == 0)
{
if (currentState != CurrentState.Attack)
{
if (!statePause)
{
statePauseCoroutine = StartCoroutine(StatePause(CurrentState.Attack, true));
}
}
else
{
if (Mathf.Abs(xDistance) < pc2d.playerDimensions.x / 3 && !checkingTotalEnemies)
{
//print("Enemies are too close!!!");
StartCoroutine(CheckEnemiesEnumerator(attackingFromLeft, attackingFromRight, true));
}
}
}
}
}
}
}
Coroutine statePauseCoroutine = null;
void StopPauseCoroutine()
{
if (statePauseCoroutine != null)
{
StopCoroutine(statePauseCoroutine);
statePauseCoroutine = null;
statePause = false;
}
}
IEnumerator StatePause(CurrentState newState, bool stopMovement)
{
//print("State pause");
statePause = true;
if (stopMovement)
{
pc2d.botMovement = 0;
pc2d.botVerticalMovement = 0;
}
currentState = newState;
if (newState == CurrentState.Attack && botDifficulty == BotDifficulty.Hard)
{
yield return new WaitForSeconds(Random.Range(0.15f, 0.45f));
}
else
{
yield return new WaitForSeconds(Random.Range(0.45f, 0.75f));
}
statePause = false;
}
IEnumerator DoJump()
{
//print("Do jump");
statePause = true;
pc2d.botJump = true;
yield return new WaitForSeconds(0.65f);
statePause = false;
}
IEnumerator CheckEnemiesEnumerator(int attackingFromLeft, int attackingFromRight, bool doNotRunAway)
{
checkingTotalEnemies = true;
//print("CHECKING FOR TOTAL ENEMIES");
yield return new WaitForSeconds(Random.Range(0.27f, 0.75f));
if (Random.Range(-10, 10) > 0)
{
runAway = true;
enemyToFollow = null;
if (attackingFromLeft > attackingFromRight)
{
currentState = CurrentState.MovingRight;
}
else
{
currentState = CurrentState.MovingLeft;
}
}
checkingTotalEnemies = false;
if (runAway)
{
if (doNotRunAway)
{
//Simply walk away a bit
yield return new WaitForSeconds(Random.Range(0.37f, 0.75f));
}
else
{
//Run away
yield return new WaitForSeconds(Random.Range(1.57f, 2.45f));
}
runAway = false;
}
}
public static Vector2 ColliderDimensions(Collider2D sp)
{
return new Vector2(sp.bounds.max.x - sp.bounds.min.x, sp.bounds.max.y - sp.bounds.min.y);
}
}
Bước 2: Thiết lập Người chơi và Kẻ thù
Bây giờ là lúc thiết lập trình phát của chúng ta và AI của kẻ thù bằng cách sử dụng các tập lệnh ở trên.
Thiết lập phiên bản Player của chúng tôi
- Tạo một GameObject mới và đặt tên cho nó "Player"
- Thay đổi Tag của đối tượng đó thành "Player"
- Thay đổi lớp của đối tượng thành 8 (nếu không có Lớp 8 trong vùng chọn, hãy thêm một lớp bằng cách nhấp vào Thêm lớp..., đặt tên là "Player")
- Tạo một GameObject khác, gọi nó là "Body" và thêm thành phần SpriteRenderer
- Gán Sprite cho trình phát của bạn cho một "Body" và di chuyển nó vào trong đối tượng "Player"
- Chọn đối tượng "Player" và thêm các thành phần CapsuleCollider2D, Rigidbody2D và PlayerController2D
- Chia tỷ lệ CapsuleCollider2D cho đến khi nó phù hợp với người chơi Sprite
Như bạn có thể thấy PlayerController2D có một vài biến, hầu hết chúng đều tự giải thích, tuy nhiên, một trong số chúng cần được giải thích một chút:
PlayerHP - giá trị này được sử dụng trong BotController2D để quyết định xem AI có nên bỏ chạy khi HP của nó quá thấp hay không.
Sử dụng biến PlayerHP khi triển khai chức năng tấn công (Kiểm tra void Attack() ở cuối tập lệnh PlayerController2D.cs).
Thiết lập máy ảnh của người chơi
- Chọn Camera chính và thêm thành phần CameraFollow2D
- Gán Trình phát cho biến Mục tiêu
- Tùy chọn bạn có thể điều chỉnh biến Offset (Nếu bạn không muốn camera được căn giữa chính xác ở giữa)
Thiết lập một cái thang
PlayerController2D cũng hỗ trợ Thang có thể leo được.
Thiết lập một bậc thang mới thực sự dễ dàng:
- Tạo một GameObject mới và gọi nó "Ladder"
- Thay đổi lớp của nó thành "IgnoreRaycast"
- Tạo một đối tượng trò chơi khác bằng SpriteRenderer và gán một sprite cho thang của bạn và di chuyển nó vào trong đối tượng Ladder
- Thêm các thành phần BoxCollider2D và Ladder2D vào đối tượng "Ladder"
- Chia tỷ lệ kích thước của máy va chạm để phù hợp với bậc thang Sprite và đánh dấu nó là Trình kích hoạt
Thiết lập AI của kẻ thù
- Đầu tiên, hãy vào bảng Physics2D và vô hiệu hóa xung đột giữa lớp Người chơi để bot và người chơi không bị mắc kẹt giữa nhau.
- Nhân đôi phiên bản Player
- Thêm thành phần BotController2D
- Bot hiện đã sẵn sàng
Hãy xem video bên dưới để biết kẻ thù AI đang hoạt động (Phiên bản màu trắng là trình phát của chúng tôi, Phiên bản màu đỏ được điều khiển bởi AI):