[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 타입 하나를 받을 수 있음

- 애니메이션 클립을 Animation 창에서 연다.
- 타임라인에서 이벤트를 넣을 시점 클릭.
- 이벤트 마커 추가 후 호출할 함수명 입력.
- 호출할 함수는 스크립트에 public void 함수명() 형태로 작성.
- 플레이 시 자동으로 애니메이션 타이밍에 함수 호출됨.
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 캐릭터 움직임 미끄러짐 해결
Rigidbody2D의 Linear 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 Mode를 Multiple로 설정 후 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 Type → Polygons 또는 Outlines
- Polygons: 바닥/벽처럼 덩어리로 충돌 영역을 만들 때
- Outlines: 바깥 테두리만 콜라이더로 만들고 싶을 때
- Edge Radius = 콜라이더 두께
- 너무 크면 경계가 부드럽게 굴곡지지만, 너무 작으면 캐릭터가 끼일 수 있음
- 적정값 예시: 0.01 ~ 0.1
- 원하는 감각에 맞춰 실험적으로 조절
- 참고: 이 컴포넌트는 Tilemap Collider 2D의 Used by Composite가 체크돼 있어야 작동
3.3.3 Rigidbody2D
: 충돌 처리를 위해 Rigidbody2D가 필요함. (필수 조건)설정값:
- Body Type → Static (이 타일맵은 움직이지 않는 고정 지형이므로 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를 적용해 충돌 판정을 구현하는 과정을 쭉 해보면서, 이제 다양한 애셋들을 내 게임에 구현해넣을 수 있겠구나하는 기대감이 들었다. 시간이 더 많으면 더 섬세히 할 수 있을 것 같은데 아쉽다