본문 바로가기

유니티(콤보 공격 구현, 타일맵 Props 실습) _ 멋쟁이사자처럼 유니티 부트캠프 후기 28회차

@salmu2025. 6. 26. 15:29
반응형

[28일차 수업내용] 250625

1. Attack & Jump 버튼 / 콤보 공격 구현

2. 키보드 이동 전환

3. 타일맵 기능 실습

 

 

1.1 조이스틱 + 버튼 구현 (공격제외)

(지난코드에 추가 됨)

 

using System;
using UnityEngine;
using UnityEngine.UI;

public class KnightController_Joystick : MonoBehaviour
{
    private Animator animator; // 애니메이터 컴포넌트
    private Rigidbody2D knightRb; // 리지드바디 컴포넌트 (2D 물리)

    [SerializeField] private Button jumpButton; // UI 점프 버튼
    [SerializeField] private Button atkButton;  // UI 공격 버튼

    private Vector3 inputDir; // 조이스틱 방향 벡터
    [SerializeField] private float moveSpeed = 3f; // 이동 속도
    [SerializeField] private float jumpPower = 13f; // 점프 힘

    private float atkDamage = 3f; // 공격 데미지 (기본값)

    private bool isGround = false; // 땅에 닿았는지 여부
    private bool isAttack = false; // 현재 공격 중인지 여부
    private bool isCombo = false;  // 콤보 공격 여부

    void Start()
    {
        // 컴포넌트 초기화
        animator = GetComponent<Animator>();
        knightRb = GetComponent<Rigidbody2D>();

        // 버튼에 함수 연결
        jumpButton.onClick.AddListener(Jump);
        atkButton.onClick.AddListener(Attack);
    }

    void FixedUpdate()
    {
        Move(); // 이동 처리
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        // 땅과 충돌했을 때
        if (other.gameObject.CompareTag("Ground"))
        {
            animator.SetBool("isGround", true);
            isGround = true;
        }
    }

    void OnCollisionExit2D(Collision2D other)
    {
        // 땅에서 벗어났을 때
        if (other.gameObject.CompareTag("Ground"))
        {
            animator.SetBool("isGround", false);
            isGround = false;
        }
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        // 몬스터 공격 판정
        if (other.CompareTag("Monster"))
        {
            Debug.Log($"{atkDamage}로 공격"); // 데미지 출력
        }
    }

    // 조이스틱 입력을 받아 방향 벡터 설정
    public void InputJoystick(float x, float y)
    {
        inputDir = new Vector3(x, y, 0).normalized;

        // 애니메이션 파라미터에 방향 전달
        animator.SetFloat("JoystickX", inputDir.x);
        animator.SetFloat("JoystickY", inputDir.y);
    }

    // 이동 처리
    void Move()
    {
        if (inputDir.x != 0)
        {
            // 방향에 따라 캐릭터 좌우 반전
            var scaleX = inputDir.x > 0 ? 1 : -1;
            transform.localScale = new Vector3(scaleX, 1, 1);

            // 좌우 이동 속도 적용
            knightRb.linearVelocityX = inputDir.x * moveSpeed;
        }
    }

    // 점프 처리
    void Jump()
    {
        if (isGround)
        {
            animator.SetTrigger("Jump");
            knightRb.AddForceY(jumpPower, ForceMode2D.Impulse);
        }
    }

 

 

1.2 조이스틱 콤보공격 구현 (해당 부분만)

    // 공격 시작 _ 이 함수는 공격 버튼을 누를 때마다 실행됨.

    void Attack() // 공격 버튼이 눌렸는데
    {
        if (!isAttack)    // 아직 공격 중이 아니면 (첫 번째 공격)
        {
            isAttack = true;
            atkDamage = 3f;
            animator.SetTrigger("Attack");
        }
        else // 공격 중 상태에서 또 공격 입력이 들어오면
        {
            // 콤보 입력 감지
            isCombo = true;
        }
    }

    // 콤보 타이밍에서 호출되는 함수 (애니메이션 이벤트용)
    public void WaitCombo()
    {
        if (isCombo)
        {
            atkDamage = 5f; // 콤보 시 데미지 증가
            animator.SetBool("isCombo", true);
        }
        else
        {
            isAttack = false;  // 콤보가 아니면 공격 종료
            animator.SetBool("isCombo", false);  // 콤보 상태 해제
        }
    }

    // 콤보 종료 처리 (애니메이션 이벤트용)
    public void EndCombo()
    {
        isAttack = false;
        isCombo = false;
        animator.SetBool("isCombo", false);
    }
}

 

- WaitCombo() , EndCombo()는 언제 호출되는지에 대한 부분이 없다.

언제 어떻게 호출되는가 = 애니메이션 Event

 

 

 

1.3 애니메이션 이벤트

:애니메이션 재생 도중 특정 시점에 특정 함수(메서드)를 호출하는 기능

 

[함수 조건] : 호출하는 함수는 다음 조건을 만족해야 함

  • public 접근 제한자
  • 반환형 void
  • 파라미터가 없거나, AnimationEvent 타입 하나를 받을 수 있음
  1. 애니메이션 클립을 Animation 창에서 연다.
  2. 타임라인에서 이벤트를 넣을 시점 클릭.
  3. 이벤트 마커 추가 후 호출할 함수명 입력.
  4. 호출할 함수는 스크립트에 public void 함수명() 형태로 작성.
  5. 플레이 시 자동으로 애니메이션 타이밍에 함수 호출됨.

 

 

1.4 Rigidbody2D Sleeping Mode 

  • NeverSleep : 절대 잠들지 않음. 항상 물리 시뮬레이션에 참여함.
  • StartAwake (기본값) : 처음엔 깨어있지만, 일정 시간 움직임 없으면 잠듦.
  • StartAsleep : 처음부터 잠든 상태에서 시작. 움직임이 감지되면 깨어남.
  • Sleep 조건: 속도가 일정 이하로 떨어지고, 외부 힘이나 충돌이 없으면 잠듦.

 

 

2.1 키보드 기반 구현

using System;
using System.Collections;
using UnityEngine;

public class KnightController_Keyboard : MonoBehaviour
{
    private Animator animator;               // 애니메이터 컴포넌트 저장 변수
    private Rigidbody2D knightRb;            // Rigidbody2D 컴포넌트 저장 변수

    private Vector3 inputDir;                // 입력 방향 벡터
    [SerializeField] private float moveSpeed = 3f;  // 이동 속도
    [SerializeField] private float jumpPower = 13f; // 점프 힘

    private float atkDamage = 3f;            // 공격 데미지

    private bool isGround;                   // 캐릭터가 바닥에 닿았는지 여부
    private bool isAttack;                   // 현재 공격 중인지 여부
    private bool isCombo;                    // 콤보 공격이 가능한 상태인지 여부

    void Start()
    {
        // 컴포넌트 초기화: Animator, Rigidbody2D 가져오기
        animator = GetComponent<Animator>();
        knightRb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        InputKeyboard();  // 키보드 입력 처리
        Jump();           // 점프 처리
        Attack();         // 공격 처리
    }

    void FixedUpdate()
    {
        Move();           // 물리 이동 처리 (FixedUpdate에서)
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        // 바닥에 닿았을 때 처리
        if (other.gameObject.CompareTag("Ground"))
        {
            animator.SetBool("isGround", true); // 애니메이터에 바닥 상태 전달
            isGround = true;                    // 바닥 상태 true로 변경
        }
    }

    void OnCollisionExit2D(Collision2D other)
    {
        // 바닥에서 떨어졌을 때 처리
        if (other.gameObject.CompareTag("Ground"))
        {
            animator.SetBool("isGround", false); // 애니메이터에 바닥 상태 전달
            isGround = false;                     // 바닥 상태 false로 변경
        }
    }
    
    void OnTriggerEnter2D(Collider2D other)
    {
        // 몬스터와 충돌 시 공격 데미지 로그 출력
        if (other.CompareTag("Monster"))
        {
            Debug.Log($"{atkDamage}로 공격");
        }
    }

    void InputKeyboard()
    {
        // 키보드 수평, 수직 입력 받아서 벡터로 저장
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        inputDir = new Vector3(h, v, 0);
        
        // 애니메이터에 방향 값 전달해 애니메이션 변경
        animator.SetFloat("JoystickX", inputDir.x);
        animator.SetFloat("JoystickY", inputDir.y);
    }

    void Move()
    {
        // 좌우 입력이 있을 때만 이동 처리
        if (inputDir.x != 0)
        {
            // 이동 방향에 따라 캐릭터 스케일 좌우 반전 (좌/우 바라보기)
            var scaleX = inputDir.x > 0 ? 1 : -1;
            transform.localScale = new Vector3(scaleX, 1, 1);
            
            // Rigidbody2D의 속도를 변경하여 이동 구현
            knightRb.linearVelocityX = inputDir.x * moveSpeed;
        }
    }

    void Jump()
    {
        // 스페이스바를 누르고 바닥에 있을 때만 점프 실행
        if (Input.GetKeyDown(KeyCode.Space) && isGround)
        {
            animator.SetTrigger("Jump");                   // 점프 애니메이션 트리거
            knightRb.AddForceY(jumpPower, ForceMode2D.Impulse);  // 위로 힘 추가
        }
    }
    
    void Attack()
    {
        // Z 키를 눌렀을 때 공격 처리
        if (Input.GetKeyDown(KeyCode.Z)) 
        {
            if (!isAttack)   // 공격 중이 아니면
            {
                isAttack = true;       // 공격 상태로 변경
                atkDamage = 3f;        // 기본 데미지 설정
                animator.SetTrigger("Attack");  // 공격 애니메이션 트리거
            }
            else
                isCombo = true;        // 이미 공격 중이면 콤보 상태로 변경
        }
    }

    // 콤보 대기 함수: 애니메이터 이벤트 등에서 호출해 콤보 여부에 따라 상태 전환
    public void WaitCombo()
    {
        if (isCombo)    // 콤보 가능 상태면
        {
            atkDamage = 5f;                 // 데미지 증가
            animator.SetBool("isCombo", true);  // 콤보 애니메이터 파라미터 true
        }
        else
        {
            isAttack = false;               // 콤보 아니면 공격 종료
            animator.SetBool("isCombo", false); // 콤보 애니메이터 파라미터 false
        }
    }
     
    // 콤보 종료 함수: 콤보 끝났을 때 호출해 상태 초기화
    public void EndCombo()
    {
        isAttack = false;              // 공격 상태 초기화
        isCombo = false;               // 콤보 상태 초기화
        animator.SetBool("isCombo", false); // 애니메이터 콤보 파라미터 false
    }
}

 

 

  • Attack Collider 도 애니메이션으로 구현 : Box Collider 2D -> is Trigger 적용
void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag("Monster"))
    {
        Debug.Log($"{atkDamage}로 공격");
    }
}

-> 여기서 감지

 

 

3.1 이동 방식 변경

using System;
using UnityEngine;
using UnityEngine.UI;

public class KnightController_Joystick1 : MonoBehaviour
{
    private Animator animator;               // 캐릭터의 애니메이션 제어용 컴포넌트
    private Rigidbody2D knightRb;            // 캐릭터의 물리 이동 제어용 Rigidbody2D

    private Vector3 inputDir;                // 조이스틱 입력 방향 벡터 (x, y)

    [SerializeField] private float moveSpeed = 3f;  // 이동 속도 (Inspector에서 조절 가능)

    void Start()
    {
        // 컴포넌트 참조 초기화
        animator = GetComponent<Animator>();
        knightRb = GetComponent<Rigidbody2D>();
    }

    void FixedUpdate()
    {
        Move(); // 고정된 프레임마다 캐릭터 이동 처리
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        // 충돌 감지용 (현재 미사용)
    }

    void OnCollisionExit2D(Collision2D other)
    {
        // 충돌 종료 감지용 (현재 미사용)
    }

    // 외부에서 조이스틱으로 방향 입력을 받는 메서드 (ex. JoystickController.cs에서 호출)
    public void InputJoystick(float x, float y)
    {
        inputDir = new Vector3(x, y, 0).normalized; // 방향 벡터 정규화 (속도 일정하게 유지)

        // 애니메이터에 방향값 전달 → Blend Tree에서 사용
        animator.SetFloat("JoystickX", inputDir.x);
        animator.SetFloat("JoystickY", inputDir.y);
    }

    void Move()
    {
        // X방향 입력이 있을 경우에만 이동 및 좌우 반전 처리
        if (inputDir.x != 0)
        {
            var scaleX = inputDir.x > 0 ? 1 : -1; // 오른쪽이면 +1, 왼쪽이면 -1
            transform.localScale = new Vector3(scaleX, 1, 1); // 캐릭터 좌우 반전

            // 이동: 입력 방향 * 속도
            knightRb.velocity = inputDir * moveSpeed;
        }
    }
}
  • 2D 탑뷰로 전환 -> Rigidbody Gravity scale 제거
  • Animator 수정

 

 

 

 

2.2 캐릭터 움직임 미끄러짐 해결

Rigidbody2DLinear Damping 수치 높이기


*리마인드 (Rigidbody 2d)

Body Type 물리 시뮬레이션에서 객체의 종류를 결정함.
- Static: 움직이지 않는 물체, 충돌 시 다른 물체에 영향만 줌.
- Dynamic: 물리 엔진에 의해 완전히 제어되는 물체 (중력, 힘, 충돌 모두 적용됨).
- Kinematic: 스크립트로 움직임 제어, 물리엔진에 의한 힘은 받지 않음.
Mass 물체의 질량. 질량이 클수록 가속도가 작아지고 관성이 커짐.
Use Auto Mass 질량을 자동으로 계산할지 여부. 활성화 시, Rigidbody2D가 연결된 콜라이더 면적 등을 기준으로 질량 자동 산출. 수동으로 조정 불가.
Linear Damping 물체의 선형 운동 속도를 감소시키는 저항력. 값이 클수록 속도가 빠르게 줄어들어 미끄러짐 감소.
Angular Damping 물체의 회전 운동 속도를 감소시키는 저항력. 값이 클수록 회전이 빨리 멈춤.

 

 

 

3.1 타일맵이란 

  • 격자(Grid) 위에 작은 타일(Sprite)들을 배열해서 맵을 구성하는 방식
  • 각 타일은 독립된 이미지(스프라이트)로, 벽, 바닥, 장애물 등으로 활용

 

 

3.2 타일맵 활용방식

  • 스프라이트 준비: 벽돌, 바닥, 나무 등 사용할 타일 이미지(스프라이트)를 준비
  • 스프라이트 임포트 설정: Texture Type을 Sprite (2D and UI)로 설정, 필요 시 여러 타일이 한 이미지에 있으면 Sprite ModeMultiple로 설정 후 Sprite Editor에서 개별 타일로 자름 : 보통 제작자가 사이즈 정해놓음 (16*16 등) 

 

[타일 팔레트 만들기]

  • Unity 메뉴에서 Window > 2D > Tile Palette 열기
  • 새 팔레트 생성 (이름, 격자 타입 설정)
  • 프로젝트 창에서 타일 스프라이트를 타일 팔레트 창으로 드래그해서 타일 에셋 생성 (개별 드래그 말고 부모로 해야 안 흐트러짐)

 

  • 씬에 2D> Grid > Tilemap 오브젝트 생성
  • Tile Palette에서 원하는 타일 선택 후 Scene 뷰의 Tilemap 위에 클릭/드래그하여 타일 배치

 

 

 

3.3 길 설정_  충돌지역 (벽 등) Collider 설정하기

3.3.1  Tilemap Collider 2D
:
타일맵에 있는 각 타일에 자동으로 콜라이더를 부착해줌.

  • Tilemap Collider 2D연결된 타일들끼리 합쳐지지 않음 → 연산 많고 비효율적

 

3.3.2 Composite Collider 2D

:Tilemap Collider 2D의 타일별 콜라이더를 하나로 병합하여 최적화된 충돌 판정 제공

  • Geometry TypePolygons 또는 Outlines
    • Polygons: 바닥/벽처럼 덩어리로 충돌 영역을 만들 때
    • Outlines: 바깥 테두리만 콜라이더로 만들고 싶을 때
  •  Edge Radius = 콜라이더 두께
    • 너무 크면 경계가 부드럽게 굴곡지지만, 너무 작으면 캐릭터가 끼일 수 있음
    • 적정값 예시: 0.01 ~ 0.1
    • 원하는 감각에 맞춰 실험적으로 조절
  • 참고: 이 컴포넌트는 Tilemap Collider 2D의 Used by Composite가 체크돼 있어야 작동

 

 

3.3.3 Rigidbody2D

: 충돌 처리를 위해 Rigidbody2D가 필요함. (필수 조건)설정값:

  • Body TypeStatic (이 타일맵은 움직이지 않는 고정 지형이므로 Static 설정)

 

 

 

3.4 Tilemap의 Order in Layer 

:렌더링 순서를 제어 - 숫자가 높을수록 앞쪽에(위에) 렌더링되고, 낮을수록 뒤쪽에(아래에) 렌더링 됨.

 

[예시]

Ground Tilemap 0 배경 (기본 바닥)
Object Tilemap 1 나무, 풀 같은 오브젝트
Effect Tilemap 2 물결, 이펙트 등
Shadow Tilemap -1 캐릭터 아래 그림자용 타일맵

 

 

 

 

 

 

3.4 Props 삽입하기

:Tilemap으로 설정

 

벤치가 너무 큰데?

 

 

오늘은 조이스틱 입력으로 캐릭터의 콤보 공격을 구현하고, 타일맵을 직접 제작해보며 Unity의 2D 게임 개발 흐름을 실습했다. 타일셋 이미지를 자르고 타일 팔레트를 만들며 격자 위에 타일을 배치하고, 그 위에 Tilemap Collider 2D와 Composite Collider를 적용해 충돌 판정을 구현하는 과정을 쭉 해보면서, 이제 다양한 애셋들을 내 게임에 구현해넣을 수 있겠구나하는 기대감이 들었다. 시간이 더 많으면 더 섬세히 할 수 있을 것 같은데 아쉽다

반응형
salmu
@salmu :: SMU 각종 기록

목차