(2D Raycast와 3D Raycast는 파라미터는 조금 다르지만 그 원리가 같다.)
Physics.Raycast
또는 Physics2D.Raycast
를 사용하여 Raycast를 수행할 수 있습니다. 이들 메서드는 시작점, 방향, 최대 거리, 그리고 선택적으로 레이어 마스크
를 매개변수로 받습니다.충돌한 객체, 충돌 지점, 충돌 지점의 정규화된 벡터
등이 포함됩니다.Debug.DrawRay
와 함께 사용할 수 있습니다. 이를 통해 Scene 뷰에서 Raycast의 경로를 시각적으로 확인할 수 있습니다.using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public Transform Player { get; private set; }
public ObjectPool ObjectPool { get; private set; }
[SerializeField] private string playerTag = "Player";
private void Awake()
{
Instance = this;
Player = GameObject.FindGameObjectWithTag(playerTag).transform;
ObjectPool = GetComponent<ObjectPool>();
}
}
using UnityEngine;
public class TopDownShooting : MonoBehaviour
{
private TopDownController contoller;
private Vector2 aimDirection = Vector2.right;
// ObjectPool 삭제
[SerializeField] private Transform projectileSpawnPosition;
private void Awake()
{
contoller = GetComponent<TopDownController>();
// ObjectPool 삭제
}
---- 생략 ----
private void CreateProjectile(RangedAttackSO RangedAttackSO, float angle)
{
// 오브젝트 풀을 활용한 생성으로 변경
GameObject obj = GameManager.Instance.ObjectPool.SpawnFromPool(RangedAttackSO.bulletNameTag);
// 발사체 기본 세팅
obj.transform.position = projectileSpawnPosition.position;
ProjectileController attackController = obj.GetComponent<ProjectileController>();
attackController.InitializeAttack(RotateVector2(aimDirection, angle), RangedAttackSO);
obj.SetActive(true);
}
private static Vector2 RotateVector2(Vector2 v, float degree)
{
return Quaternion.Euler(0, 0, degree) * v;
}
}
적에도 대응되게 하기 위해서 클래스명을 변경해요.
TopDownAnimationController로 변경합니다.
using UnityEngine;
public class TopDownAnimationController : AnimationController
{
---- 생략 ----
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopDownEnemyController : TopDownCharacterController
{
GameManager gameManager;
protected Transform ClosestTarget { get; private set; }
protected override void Awake()
{
base.Awake();
}
protected virtual void Start()
{
gameManager = GameManager.instance;
ClosestTarget = gameManager.Player;
}
protected virtual void FixedUpdate()
{
}
protected float DistanceToTarget()
{
return Vector3.Distance(transform.position, ClosestTarget.position);
}
protected Vector2 DirectionToTarget()
{
return (ClosestTarget.position - transform.position).normalized;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopDownContactEnemyController : TopDownEnemyController
{
[SerializeField][Range(0f, 100f)] private float followRange;
[SerializeField] private string targetTag = "Player";
private bool isCollidingWithTarget;
[SerializeField] private SpriteRenderer characterRenderer;
protected override void Start()
{
base.Start();
}
protected override void FixedUpdate()
{
base.FixedUpdate();
Vector2 direction = Vector2.zero;
if(DistanceToTarget() < followRange)
{
direction = DirectionToTarget();
}
CallMoveEvent(direction);
Rotate(direction);
}
private void Rotate(Vector2 direction)
{
// TopDownAimRotation에서 했었죠?
// Atan2는 가로와 세로의 비율을 바탕으로 -파이~파이(-180도~180도에 대응, * Rad2Deg가 그 기능)하는 값을 나타내주는 함수였다는 것 기억하시죠?
float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
}
}
- 🆘 캐릭터가 적과 부딪혔을 때 회전하고 있나요?
Rigidbody2D의 Constraint를 확인해보세요.
using UnityEngine;
public class TopDownRangeEnemyController : TopDownEnemyController
{
[SerializeField] private float followRange = 15f;
[SerializeField] private float shootRange = 10f;
private int layerMaskLevel;
private int layerMaskTarget;
protected override void Start()
{
base.Start();
layerMaskLevel = LayerMask.NameToLayer("Level");
layerMaskTarget = stats.CurrentStat.attackSO.target;
}
protected override void FixedUpdate()
{
base.FixedUpdate();
float distanceToTarget = DistanceToTarget();
Vector2 directionToTarget = DirectionToTarget();
UpdateEnemyState(distanceToTarget, directionToTarget);
}
private void UpdateEnemyState(float distance, Vector2 direction)
{
isAttacking = false; // 기본적으로 공격 상태를 false로 설정합니다.
if (distance <= followRange)
{
CheckIfNear(distance, direction);
}
}
private void CheckIfNear(float distance, Vector2 direction)
{
if (distance <= shootRange)
{
TryShootAtTarget(direction);
}
else
{
CallMoveEvent(direction); // 사정거리 밖이지만 추적 범위 내에 있을 경우, 타겟 쪽으로 이동합니다.
}
}
private void TryShootAtTarget(Vector2 direction)
{
// 몬스터 위치에서 direction 방향으로 레이를 발사합니다.
RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, shootRange, GetLayerMaskForRaycast());
// 벽에 맞은게 아니라 실제 플레이어에 맞았는지 확인합니다.
if (IsTargetHit(hit))
{
PerformAttackAction(direction);
}
else
{
CallMoveEvent(direction); }
}
private int GetLayerMaskForRaycast()
{
// "Level" 레이어와 타겟 레이어 모두를 포함하는 LayerMask를 반환합니다.
return (1 << layerMaskLevel) | layerMaskTarget;
}
private bool IsTargetHit(RaycastHit2D hit)
{
// RaycastHit2D 결과를 바탕으로 실제 타겟을 명중했는지 확인합니다.
return hit.collider != null && layerMaskTarget == (layerMaskTarget | (1 << hit.collider.gameObject.layer));
}
private void PerformAttackAction(Vector2 direction)
{
// 타겟을 정확히 명중했을 경우의 행동을 정의합니다.
CallLookEvent(direction);
CallMoveEvent(Vector2.zero); // 공격 중에는 이동을 멈춥니다.
isAttacking = true;
}
}
샤먼의 팔이 너무 많이 회전하면서 거꾸로 들고 있는데, 이를 Y축으로 플립하여 도와줍시다.
캐릭터의 XFlip과 같은 방향이기 때문에 flip여부를 그대로 넣어주겠습니다.
private void RotateArm(Vector2 direction)
{
// Atan2는 직각삼각형이 있다고 할 때 세로가 y, 가로가 x일 때 그 각도를 라디안 [-Pi,Pi]로 나타내는 함수임
// 라디안의 -Pi는 -180도, Pi는 180도 이므로 Mathf.Rad2Deg는 약 57.29임 (180 / 3.14)
float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
// [1. 캐릭터 뒤집기]
// 이때 각도는 오른쪽(1,0 방향)이 0도이므로,
// -90~90도에서는 오른쪽을 바라보는 게 맞지만, -90도 미만 90도 초과라면 왼쪽을 바라보는 것임.
characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
// 플레이어 무기는 상하대칭이라 괜찮았지만 샤먼 무기는 상하대칭이 아니라 뒤집어줘야함.
armRenderer.flipY = characterRenderer.flipX;
// [2. 팔 돌리기]
// 팔을 돌릴 때는 나온 각도를 그대로 적용하는데, 이때 유니티 내부에서 사용하는 쿼터니언으로 변환한다.
// 쿼터니언으로 변형하는 방법 두 가지
// 1) Vector3를 Quaternion으로 변환해서 넣는 방법
// Quaternion.Euler(x 회전, y 회전, z 회전) : 오일러 각 기준으로 값을 넣으면 쿼터니언으로 변환됨
// 2) 프로퍼티를 통해 자동으로 변환되게 하는 방법
// Transform.eulerAngles을 변경
armPivot.rotation = Quaternion.Euler(0, 0, rotZ);
// (2번 방법으로 하면) armPivot.eulerAngles = new Vector3(0, 0, rotZ);
}
같은 애니메이션 구성을 가진 다른 오브젝트가 있는 경우,
애니메이션 파일만 갈아끼워서 같은 애니메이터를 활용하게 하는 컴포넌트입니다.
잡몹들의 행동들은 거의 똑같은데 그림만 바뀌는 게임에서 활용할 수 있겠죠?