public class Player_movement : MonoBehaviour
{
Rigidbody2D rigid;
SpriteRenderer render;
public float maxSpeed;
Animator anime;
// Start is called before the first frame update
void Awake()
{
rigid = GetComponent<Rigidbody2D>();
render = GetComponent<SpriteRenderer>();
anime = GetComponent<Animator>();
}
void Update()
{
if (Input.GetButtonUp("Horizontal")) {
rigid.velocity = new Vector2(rigid.velocity.normalized.x * 3f, rigid.velocity.y);
}
if(Input.GetButton("Horizontal")) {
render.flipX = Input.GetAxisRaw("Horizontal") == -1;
}
if (rigid.velocity.normalized.x == 0) {
anime.SetBool("isWalking", false);
}
else {
anime.SetBool("isWalking", true);
}
}
// Update is called once per frame
void FixedUpdate()
{
float movement_speed = Input.GetAxisRaw("Horizontal");
rigid.AddForce(movement_speed * Vector2.right, ForceMode2D.Impulse);
if (rigid.velocity.x > maxSpeed) {
rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);
}
else if (rigid.velocity.x < maxSpeed * (-1)) {
rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);
}
}
}
대충 이렇게 코드가 있다고 치면, 저렇게 Flip을(참고로 Flip은 True(1) or False(-1)이 있는데, 1 == -1이면 False기 때문에 Flip 설정이 꺼지면서 원래 상태로 돌아오며, -1 == -1일 경우 True기 때문에 옵션이 켜지면서 반대로 돈다. 이것을 Update에서 계속 설정해주면, 실시간으로 1, -1으로 보기 때문에 캐릭터가 보는 시점을 변경할 수 있다) 바꿀 수 있다.
그리고 normalized는 간단히 말해 벡터의 크기를 1로 맞춰주는 연산이다.
Vector2 v = new Vector2(3, 4);
Vector2 norm = v.normalized; // (0.6, 0.8)
원래 v의 길이는 5 → (3/5, 4/5) = (0.6, 0.8) 방향은 유지하면서 길이를 1로 조정
참고로
여기서 Freeze Rotation을 키면 캐릭터(이미지)가 고꾸라지거나 돌아가는 것을 방지한다.
그리고 anime란 변수가 있는데
이렇게 isWalking이라는 파라미터를 만들고, Make Transition을 각각 만들어서 서로간의 에니메이션을 연결시키는 작업이다. 그리고 잘 보면 옆에
이것들이 있는데, 이것을 꺼줘야 애니메이션이 끝나고 다음 동작이 아니라, 바로 다음 동작을 실행할 수 있게 해준다.
그리고 이렇게 SetBool을 통해 Bool 타입을 false로 바꿔준다. 아니면 true로 바꿈
Mathf- 유니티에서 제공하는 수학 관련 함수를 제공하는 클래스
if (Mathf.Abs(rigid.velocity.x) == 0.3)
이렇게 코드를 바꾼다고 했을 때, Abs는 절대 값 즉, 마이너스 플러스 상관 x, 아무튼 저렇게 하면 -, + 에 상관없이 값을 변경 시킬 수 있다.
레이어, 비트마스크, 레이어마스크
레이어(Layer)란? 오브젝트들을 그룹으로 나누는 시스템이며, 유니티에서 게임 오브젝트(GameObject)들은 "레이어" 라는 그룹에 속할 수 있다. 레이어를 사용하면 특정 오브젝트들만 감지하거나 무시할 수 있다.
게임을 만들 때, 다음과 같은 오브젝트들이 있다고 하자: 이제 레이어를 사용하면: 플레이어(🏃♂️)는 벽(🏗️)과 충돌하지만, 총알(🚀)은 벽을 통과하게 만들 수 있음. 레이캐스트(Raycast) 를 쏠 때 적(🔥)만 감지하고 싶다면 "Enemy" 레이어만 체크하면 됨. 하지만 이렇게 보면 테그와 매우 비슷해보이지만, 다르다. 태그(Tag)란? 각 오브젝트를 특정한 이름으로 구분하는 기능 게임 오브젝트를 논리적으로 그룹화하는 용도 여러 개의 오브젝트에 같은 태그 부여 가능 한 개의 오브젝트에는 단 하나의 태그만 가능 비교할 때는 CompareTag("태그이름") 사용
레이어(Layer) 오브젝트를 그룹화하여, 충돌 감지 및 렌더링 최적화할 때 사용 하나의 오브젝트는 오직 하나의 레이어만 가질 수 있음 충돌 필터링(물리 엔진)과 렌더링 제어에 사용됨
예제 2: 특정 레이어 오브젝트끼리 충돌 여부 설정하기 유니티 "Edit → Project Settings → Physics 2D" 에서 충돌 설정 가능! "Ground" 레이어와 "Player" 레이어 간의 충돌을 설정하거나 해제 가능 불필요한 충돌을 막아 게임 최적화 가능! 레이어의 특징? 태그와 달리, 충돌(Physics)이나 카메라 렌더링 필터링이 가능 LayerMask를 이용해서 특정 레이어만 감지 가능
비트마스크(Bitmask)란? 숫자로 여러 개의 레이어를 한 번에 저장하는 방식 비트(bit): 0 또는 1의 값만 가짐 (컴퓨터가 데이터를 표현하는 기본 단위) 마스크(mask): 특정 데이터를 선택적으로 활성화하거나 비활성화하는 방법
예제: 레이어를 숫자로 표현하기 (비트마스크 원리) 유니티에서는 각 레이어가 2의 제곱수(비트값) 로 저장됨.
비트마스크 값의 특징 각 비트는 특정 레이어를 의미함 여러 개의 레이어를 합칠 수 있음 예: Ground(8) + Player(64) = 00001000 | 01000000 = 01001000 (72)
Raycast
유니티에서 Raycast는 가상의 "레이저(광선)"를 특정 방향으로 쏴서 충돌한 오브젝트를 감지하는 기능입니다. 특정 방향으로 투사(발사)한 가상의 광선이 Collider(충돌체) 를 가진 오브젝트에 닿으면 충돌 정보를 반환해줌
언제 사용할까? 시야 검사 (적이 플레이어를 볼 수 있는지 판단) 마우스 클릭 감지 (UI 버튼이 아닌 3D 오브젝트 클릭) 문 열기 / 버튼 상호작용 (캐릭터가 문 근처에 있을 때만 열림) 총알 / 무기 공격 판정 (물리 기반 총알 대신 레이캐스트로 히트 스캔 구현) 바닥 감지(점프 체크)
구조
RaycastHit2D hit = Physics2D.Raycast(시작점, 방향, 거리, 충돌할 레이어);
Raycasthit2D -> 2D 레이캐스트가 감지한 오브젝트의 충돌 정보를 담고 있는 데이터 구조(Ray에 닿은 오브젝트) Physics2D.Raycast(시작위치, 방향, 거리); Vector2.down → 아래 방향으로 광선 발사 1f → 1유닛 거리까지만 감지 hit.collider가 null이 아니면 뭔가 맞았다는 뜻
특정 레이어만 감지하는 Raycast
void Update()
{
int groundLayer = LayerMask.GetMask("Ground");
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1f, groundLayer);
if (hit.collider != null)
{
Debug.Log("바닥 감지됨: " + hit.collider.name);
}
}
LayerMask.GetMask("Ground") → "Ground" 레이어에 속한 오브젝트만 감지 충돌한 오브젝트의 hit.collider.name을 출력
일단 기초적인 Debug.DrawRay (Debug.DrawRay(시작 위치, 방향 * 거리, 색상, 지속 시간);) Debug.DrawRay는 씬(Scene)에서 Raycast가 어떻게 동작하는지 시각적으로 확인할 수 있도록 디버깅용으로 선을 그려주는 기능이다. 참고로 Debug를 이렇게 쓰니까 헷갈릴 수 있는 사람을 위해
둘 다 사용 예시:
void Update()
{
int groundLayer = LayerMask.GetMask("Ground");
Vector2 start = transform.position; // 플레이어 현재 위치
Vector2 direction = Vector2.down; // 아래 방향
float distance = 1f; // 감지 거리
// Raycast 발사
RaycastHit2D hit = Physics2D.Raycast(start, direction, distance, groundLayer);
// 디버그용 시각적 Ray 그리기 (빨간색)
Debug.DrawRay(start, direction * distance, Color.red);
if (hit.collider != null)
{
Debug.Log("바닥 감지됨: " + hit.collider.name);
}
}
Debug.DrawRay가 하는 역할 씬(Scene) 창에서 플레이어 아래로 빨간색 선이 그려짐 이 선이 Raycast가 발사되는 경로를 나타냄 선의 끝이 바닥과 맞닿으면 바닥이 감지된 것
그렇다면 위에 int groundLayer = LayerMask.GetMask("Ground"); 이건 뭘까요. 바로 레이어 마스크라는 것입니다. 레이어와 비트마스크에 대해 배웠으니, 쉽게 알 수 있습니다.
LayerMask.GetMask("레이어이름")의 역할 특정 레이어의 오브젝트만 감지하도록 필터링하는 기능 유니티에서 충돌 검사, 레이캐스트(Raycast), 트리거 감지 등을 할 때 특정 레이어만 포함하거나 제외할 수 있도록 도와주는 게 바로 LayerMask이다. 먼저,
int groundLayer = LayerMask.GetMask("Ground");
라고 하면, "Ground" 레이어의 비트마스크 값을 가져옴 "Ground"의 값이 8(윗 예제-비트마스크 값)이므로, groundLayer에는 8이 저장됨.
LayerMask가 왜 필요할까? Physics2D.Raycast() 함수의 매개변수 구조를 보면
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1f);
먼저, 이렇게 하면 모든 오브젝트에 부딪힐 수 있어서, 바닥뿐만 아니라 적, 아이템 같은 것에도 반응할 수 있다. 즉, 틀렸다.
이 문제를 해결하려면? "Ground" 레이어에 속한 오브젝트 만 감지하도록 설정해야한다. 바로 이때 LayerMask 를 사용해 특정 레이어만 감지할 수 있도록 필터링하는 것이다.
LayerMask 기본 사용법 특정 레이어만 감지하기
int groundLayer = LayerMask.GetMask("Ground");
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1f, groundLayer);
"Ground" 레이어의 비트마스크 값을 가져와 groundLayer에 저장 레이캐스트를 쏠 때 오직 "Ground" 레이어에 속한 오브젝트만 감지 즉, 땅(Ground)에만 반응하고, 다른 오브젝트(적, 아이템)는 무시
이렇듯, 마지막 매개변수의 유무에 따라서, hit에는 맞은 오브젝트의 데이터가 들어가게 된다. 하지만 문제가 있다, 처음처럼 만약에 어떠한 오브젝트를 지정하지 않는다면, 문제가 생긴다. 1. 만약에 플레이어 중심에 레이를 시작한다고 치자, 그렇게 될 경우, 빔은 플레이어 중심에서 나오기 때문에, 즉 시작하자 마자 플레이어에게 빔이 맞아, 플레이어의 값이 들어가게 된다는 뜻이다. 2. 이게 무슨 뜻이냐면, 플레이어 중심에서 시작되기 때문에, 애초에 플레이어도 빔을 맞게 된다는 소리이다. 게다가 한번 맞으면 다른 값은 들어오지 않기 때문에, 플레이어의 값이 계속해서 들어오게 되는 것이다.
여러 개의 레이어를 감지하고 싶다면? LayerMask.GetMask("레이어1", "레이어2")
int layerMask = LayerMask.GetMask("Ground", "Player");
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1f, layerMask);
"Ground"(8) + "Player"(64) → 비트마스크 값 72 이제 "Ground"와 "Player" 레이어에 속한 오브젝트만 감지 가능!
특정 레이어를 제외하고 싶다면? ~LayerMask.GetMask("제외할 레이어")
int excludeEnemies = ~LayerMask.GetMask("Enemy");
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 1f, excludeEnemies);
LayerMask.GetMask("Enemy") → "Enemy" 레이어의 비트마스크 값을 가져옴 ~(비트 NOT 연산자) → 모든 레이어를 포함하되, "Enemy"만 제외
LayerMask와 int의 차이
LayerMask mask1 = LayerMask.GetMask("Ground"); // LayerMask 타입
int mask2 = LayerMask.GetMask("Ground"); // int 타입
사실 LayerMask는 int와 같은 값을 가짐, 하지만 LayerMask 타입을 사용하면 인스펙터(Inspector)에서 직접 설정할 수 있음
만약에 빔을 쐈는데 부딛힌 오브젝트가 groundLayer가 아니라면 hit에는 어떤 값이 들어갈까? 1. 아무것도 감지하지 못한 경우, hit.collider == null이 된다. 2. 맞은 오브젝트가 groundLayer가 아닐 때, 레이는 그 오브젝트를 무시하고 지나간다. 즉, 해당 레이어가 아닌 오브젝트는 존재하지 않는 것처럼 동작한다.(똑같이 hit.collider == null이 된다.)
velocity vs AddForce
이렇게 보니 rigidbody의 velocity로 움직이는 것과, AddForce로 주는 것이 매우 비슷하게 느껴집니다. 차이점이 뭘까요? rigid.velocity란? 속도를 직접 설정하는 변수 (강제 변경) 즉각적으로 속도가 바뀜 (현재 속도를 무시하고 바로 적용됨) Rigidbody 컴포넌트의 Inspector 창에는 표시되지 않음 (코드로만 조작 가능)
rigid.velocity = new Vector2(5f, rigid.velocity.y); // 즉시 X 방향 속도 5로 설정
설명 현재 속도를 무시하고 X 방향 속도를 5f로 즉시 변경 기존의 관성을 무시하고 바로 새 속도를 적용
문제점 물리적 힘(Force)이 적용되지 않음 → 부드러운 움직임이 사라짐
rigid.AddForce()란? 물리 엔진을 사용하여 힘을 가하는 방식 현재 속도에 추가적인 힘을 가하는 형태 (관성이 유지됨) 힘을 가하는 방식에 따라 속도가 점진적으로 변함
설명 ForceMode2D.Impulse → 즉시 강한 힘을 줘서 순간적으로 밀어냄 물리 엔진이 관성을 적용 → 부드러운 움직임 가능
ForceMode2D의 종류
그러면 언제 사용하는게 좋을까
.distance
위에서 보았듯이, hit.collider는 빔이 충돌한 오브젝트의 정보를 가지고 있다. 그러면 hit.distance는 뭘까? 바로 레이가 발사된 원점에서 충돌한 지점까지의 거리를 말한다. 예를 들어, hit.distance < 0.5f라고 하고, 빔의 길이를 1f라고 할 때, 땅에 닿으면 hit.distance의 값은 1f가 된다. 왜냐하면 빔의 길이가 1f이고, 땅에 닿을 때에 길이 1f인 빔이 끝에 닿았기 때문에 1f가 되는데, update를 통해 계속해서 땅에 닿으면서 값을 바뀌게 된다.
1f -> 0.98f -> 0.75f 이렇게 말이다. 즉, 원점에서 현대 닿아진 오브젝트의 길이라고 볼 수 있다. hit.distance = 레이 발사 원점 → 충돌 지점까지의 거리 주로 점프 높이 제한, 장애물 감지 등에 활용 가능하다.
GetBool
자, 저번에는 SetBool을 통해 애니메이션의 동작들을 설정해줬다면, GetBool을 통해 그 값을 가져올 수 있다. 예를 들어 isJump의 false를 가져온다던지. 이렇게 말이다.
먼저 앞에 !가 있는 이유는 간단하다. isJump = false -> 점프를 안 뛰는 도중. 즉 false이기 때문에, 실행이 안되기 때문에 !을 붙이고 만약에 점프를 했으면 false가 되기 때문에 실행이 안되서 점프를 하면 한번 더 점프를 할 수 없게 되는 것이다.