<< 1은 왼쪽으로 1만큼 보내라는 의미에요. 빈칸은 0으로 채워요.
Unity에서 각 게임 오브젝트는 특정 레이어에 속할 수 있으며, 이는 주로 오브젝트 간의 상호작용을 관리하기 위해 사용됩니다. 레이어는 비트 필드로 표현되어, 각 비트가 다른 레이어를 나타내게 됩니다.
비트 연산자는 레이어의 비트 필드를 조작하기 위해 사용됩니다. 여기에는 주로 다음과 같은 연산자가 포함됩니다
&
) : 두 비트 필드 모두에서 해당 비트가 설정되어 있을 때만 결과 비트를 설정합니다. 이를 통해 특정 레이어의 존재 여부를 확인할 수 있습니다.|
) : 두 비트 필드 중 하나라도 해당 비트가 설정되어 있으면 결과 비트를 설정합니다. 이는 새로운 레이어를 추가할 때 유용합니다.^
) : 두 비트 필드에서 해당 비트가 서로 다를 때만 결과 비트를 설정합니다. 이는 두 레이어의 차이를 찾을 때 사용됩니다.~
) : 모든 비트를 반전시킵니다. 이는 특정 레이어를 제외시킬 때 유용합니다.비트 시프트 연산: 1 << n은 1을 n번째 비트 위치로 시프트합니다. 이는 n번째 레이어를 나타내는 비트마스크를 생성하는 데 사용됩니다. 이를 통해 특정 레이어에 대한 연산을 쉽게 수행할 수 있습니다.
이 방법은 물리적 충돌, 레이캐스팅, 카메라 렌더링 등을 제어하는 데 사용됩니다.
using System;
using UnityEngine;
public class TopDownController : MonoBehaviour
{
// Action은 void형 메소드를 등록할 수 있는데 Vector2를 인자로 받는 메소드를 등록하도록 Action<Vector2>를 정의
// event 키워드를 입력하면 public이더라도 나만 Invoke가능.
public event Action<Vector2> OnMoveEvent;
public event Action<Vector2> OnLookEvent;
// OnAttackEvent는 눌렸을 때 공격 기준정보(AttackSO)를 들고 옴
public event Action<AttackSO> OnAttackEvent;
private float timeSinceLastAttack = float.MaxValue;
protected bool isAttacking;
protected CharacterStatHandler stats { get; private set; }
protected virtual void Awake()
{
stats = GetComponent<CharacterStatHandler>();
}
protected virtual void Update()
{
HandleAttackDelay();
}
private void HandleAttackDelay()
{
if (timeSinceLastAttack <= stats.CurrentStat.attackSO.delay)
{
timeSinceLastAttack += Time.deltaTime;
}
if (isAttacking && timeSinceLastAttack > stats.CurrentStat.attackSO.delay)
{
timeSinceLastAttack = 0;
// 현재 장착된 무기의 attackSO전달
CallAttackEvent(stats.CurrentStat.attackSO);
}
}
public void CallMoveEvent(Vector2 direction)
{
// onMoveEvent는 public이어서 TopDownMovement에서 메소드들을 등록해놨음(a.k.a. 구독)
OnMoveEvent?.Invoke(direction);
}
public void CallLookEvent(Vector2 direction)
{
// 조준 시스템에서 등록했음.
OnLookEvent?.Invoke(direction);
}
public void CallAttackEvent(AttackSO attackSO)
{
// TopDownShooting에서 등록한 OnShoot 메소드가 구독되어 있음.
OnAttackEvent?.Invoke(attackSO);
}
}
using UnityEngine;
public class TopDownShooting : MonoBehaviour
{
private TopDownController contoller;
[SerializeField] private Transform projectileSpawnPosition;
private Vector2 aimDirection = Vector2.right;
public GameObject testPrefab;
private void Awake()
{
contoller = GetComponent<TopDownController>();
}
void Start()
{
contoller.OnAttackEvent += OnShoot;
// OnLookEvent에 이제 두개가 등록되는 것(하나는 지난 시간에 등록했었죠? TopDownAimRotation.OnAim(Vec2)
// 한 개의 델리게이트에 여러 개의 함수가 등록되어있는 것을 multicast delegate라고 함.
// Action이나 Func도 델리게이트의 일종인 것 기억하시죠..?
contoller.OnLookEvent += OnAim;
}
private void OnAim(Vector2 newAimDirection)
{
aimDirection = newAimDirection;
}
private void OnShoot(AttackSO attackSO)
{
RangedAttackSO RangedAttackSO = attackSO as RangedAttackSO;
float projectilesAngleSpace = RangedAttackSO.multipleProjectilesAngel;
int numberOfProjectilesPerShot = RangedAttackSO.numberofProjectilesPerShot;
// 중간부터 펼쳐지는게 아니라 minangle부터 커지면서 쏘는 것으로 설계했어요!
float minAngle = -(numberOfProjectilesPerShot / 2f) * projectilesAngleSpace + 0.5f * RangedAttackSO.multipleProjectilesAngel;
for (int i = 0; i < numberOfProjectilesPerShot; i++)
{
float angle = minAngle + projectilesAngleSpace * i;
// 그냥 올라가면 재미없으니 랜덤으로 변하는 randomSpread를 넣었어요!
float randomSpread = Random.Range(-RangedAttackSO.spread, RangedAttackSO.spread);
angle += randomSpread;
CreateProjectile(RangedAttackSO, angle);
}
}
private void CreateProjectile(RangedAttackSO RangedAttackSO, float angle)
{
// 화살 생성 -> 다음강에서 구조개선을 위해 잠시 흉물스러운 이름 참아주세요!
GameObject obj = Instantiate(testPrefab);
// 발사체 기본 세팅
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;
}
}
using UnityEngine;
public class ProjectileController : MonoBehaviour
{
// 벽에 부딪혔을 때 사라지면서 이펙트 나오게 해야돼서 레이어를 알고 있어야 해요!
[SerializeField] private LayerMask levelCollisionLayer;
private RangedAttackSO attackData;
private float currentDuration;
private Vector2 direction;
private bool isReady;
private Rigidbody2D rigidbody;
private SpriteRenderer spriteRenderer;
private TrailRenderer trailRenderer;
public bool fxOnDestory = true;
private void Awake()
{
spriteRenderer = GetComponentInChildren<SpriteRenderer>();
rigidbody = GetComponent<Rigidbody2D>();
trailRenderer = GetComponent<TrailRenderer>();
}
private void Update()
{
if (!isReady)
{
return;
}
currentDuration += Time.deltaTime;
if (currentDuration > attackData.duration)
{
DestroyProjectile(transform.position, false);
}
rigidbody.velocity = direction * attackData.speed;
}
private void OnTriggerEnter2D(Collider2D collision)
{
// levelCollisionLayer에 포함되는 레이어인지 확인합니다.
if (IsLayerMatched(levelCollisionLayer.value, collision.gameObject.layer))
{
// 벽에서는 충돌한 지점으로부터 약간 앞 쪽에서 발사체를 파괴합니다.
Vector2 destroyPosition = collision.ClosestPoint(transform.position) - direction * .2f;
DestroyProjectile(destroyPosition, fxOnDestory);
}
// _attackData.target에 포함되는 레이어인지 확인합니다.
else if (IsLayerMatched(attackData.target.value, collision.gameObject.layer))
{
// 아야! 피격 구현에서 추가 예정
// 충돌한 지점에서 발사체를 파괴합니다.
DestroyProjectile(collision.ClosestPoint(transform.position), fxOnDestory);
}
}
// 레이어가 일치하는지 확인하는 메소드입니다.
private bool IsLayerMatched(int layerMask, int objectLayer)
{
return layerMask == (layerMask | (1 << objectLayer));
}
public void InitializeAttack(Vector2 direction, RangedAttackSO attackData)
{
this.attackData = attackData;
this.direction = direction;
UpdateProjectileSprite();
trailRenderer.Clear();
currentDuration = 0;
spriteRenderer.color = attackData.projectileColor;
transform.right = this.direction;
isReady = true;
}
private void UpdateProjectileSprite()
{
transform.localScale = Vector3.one * attackData.size;
}
private void DestroyProjectile(Vector3 position, bool createFx)
{
if (createFx)
{
// TODO : ParticleSystem에 대해서 배우고, 무기 NameTag로 해당하는 FX가져오기
}
gameObject.SetActive(false);
}
}
BoxCollider 2D 추가
Rigidbody 2D 추가
Trail Renderer 추가
물체가 이동하면서 뒤에 "잔상"이나 "자취"를 남기는 효과를 만드는 데 사용됩니다
- 커브 조정을 잘 못하겠어요!
각 지점의 우클릭을 하면 Edit Key를 통해 값을 바꿀 수 있어요.
이렇게 하면 강의와 더 가까운 값을 얻을 수 있어요!