[29회차 수업내용] 250626
1. 다시 조이스틱으로 (복습)
2. Camera Follow
3. 상호작용(InteractionEvent) / 표지판 실습
4. 스크롤뷰 (텍스트 길이가 길 때 아래로 스크롤)
5. NPC와 대화, 대화 타이핑 효과 구현
6. 맵전환_ 건물에 들어가고 나가기 / Door Interaction / Fade 효과
1. 조이스틱 코드 (2D 탑뷰 Knight_Controller_Joystick)
using System;
using UnityEngine;
using UnityEngine.UI;
public class KnightController_Joystick : MonoBehaviour
{
// 애니메이터와 물리 컴포넌트를 위한 참조 변수
private Animator animator;
private Rigidbody2D knightRb;
// 입력 방향을 저장하는 벡터
private Vector3 inputDir;
// 이동 속도 조절용 변수 (인스펙터에서 조절 가능)
[SerializeField] private float moveSpeed = 3f;
void Start()
{
// 시작 시 컴포넌트 연결
animator = GetComponent<Animator>();
knightRb = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
// 물리 관련 이동은 FixedUpdate에서 처리
Move();
}
// 조이스틱 입력을 받아 방향 벡터 설정 및 애니메이터 파라미터 전달
public void InputJoystick(float x, float y)
{
// 입력된 x, y 값을 정규화하여 방향 벡터로 저장
inputDir = new Vector3(x, y, 0).normalized;
// 애니메이터에 방향값 전달 (Blend Tree 제어 등)
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.linearVelocity = inputDir * moveSpeed;
}
}
}
2. Camera Follow
[코드]
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
// 따라갈 대상(예: 플레이어)
[SerializeField] private Transform target;
// 타겟 위치에서 카메라가 떨어져 있을 거리 (위로 3, 뒤로 10)
[SerializeField] private Vector3 offset = new Vector3(0, 3, -10);
// 카메라 이동의 부드러움 정도 (값이 클수록 더 빠르고 즉각적으로 따라감)
[SerializeField] private float smoothSpeed = 5f;
// 카메라가 이동할 수 있는 최소/최대 x, y 좌표 (월드 제한 영역)
[SerializeField] private Vector2 minBounds, maxBounds;
// LateUpdate: 플레이어가 이동을 끝낸 후에 카메라가 따라감
void LateUpdate()
{
// 타겟이 없다면 실행하지 않음 (null 예외 방지)
if (target == null) return;
// 목표 위치 = 타겟 위치 + 오프셋
Vector3 destination = target.position + offset;
// 현재 위치에서 목표 위치까지 부드럽게 이동 (Lerp 사용)
Vector3 smoothPos = Vector3.Lerp(transform.position, destination, smoothSpeed * Time.deltaTime);
// x, y 값을 min/max 범위 내로 제한하여 맵 바깥으로 나가지 않게 함
smoothPos.x = Mathf.Clamp(smoothPos.x, minBounds.x, maxBounds.x);
smoothPos.y = Mathf.Clamp(smoothPos.y, minBounds.y, maxBounds.y);
// 실제 카메라 위치를 계산된 위치로 이동시킴
transform.position = smoothPos;
}
}
2.1 Mathf.Clamp
: 값을 특정 최소(min)와 최대(max) 범위 안으로 제한하는 함수
float result = Mathf.Clamp(value, min, max);
*리마인드
Vector3.Lerp(a, b, t)란?
- a: 시작 위치
- b: 도착(목표) 위치
- t: 0 ~ 1 사이의 값 (비율)
Vector3 smoothPos = Vector3.Lerp(transform.position, destination, smoothSpeed * Time.deltaTime);
*smoothSpeed(속도계수) * Time.deltaTime(한 프레임에 걸린 시간)
* LateUpdate()는 모든 Update()가 끝난 후에 실행
:카메라처럼 다른 오브젝트의 위치를 따라가야 할 때 주로 사용
| 항목 | Update() | FixedUpdate() | LateUpdate() |
| 호출 주기 | 매 프레임마다 (FPS에 따라 변동) | 고정된 시간 간격 (예: 0.02초) | 모든 Update() 후에 한 번 |
| 주 용도 | 입력 처리, UI 업데이트, 비물리적 로직 | 물리 연산 처리 (Rigidbody 사용 등) | 카메라 이동, 따라가는 오브젝트 등 |
| 특징 | 화면 FPS에 영향을 받음 | FPS와 관계없이 일정 | 프레임 끝에서 동기화된 작업에 적합 |
| 예시 | Input.GetKey(), 애니메이션 트리거 | Rigidbody.AddForce(), 충돌 판정 | 카메라가 플레이어를 따라가게 하기 |
2.2 카메라 최대 최소 좌표 지정
Min,Max 좌표 지정 방법 = 카메라 오브젝트를 움직여보며 최대 최소 좌표를 찾는다

3.1 상호작용(InteractionEvent) _ 표지판
[코드]
using System;
using UnityEngine;
public class InteractionEvent : MonoBehaviour
{
// 상호작용의 종류를 정의한 열거형(enum)
public enum InteractionType { SIGN, DOOR, NPC }
public InteractionType type; // 이 오브젝트의 상호작용 타입
// SIGN 타입일 때 띄울 팝업 오브젝트
public GameObject signPopup;
// 트리거 충돌 시 실행됨
void OnTriggerEnter2D(Collider2D other)
{
// 충돌한 객체가 "Player" 태그를 가진 경우
if (other.CompareTag("Player"))
{
// 플레이어와의 상호작용 실행
Interaction(other.transform);
}
}
// 상호작용 처리 메서드
void Interaction(Transform player) // player라는 매개변수에 other.transform이 들어감
{
switch (type)
{
case InteractionType.SIGN: // SIGN일 경우 팝업 활성화
signPopup.SetActive(true);
break;
case InteractionType.DOOR: // 문일 경우의 행동은 추후 구현
break;
case InteractionType.NPC: // NPC 대화 등의 행동은 추후 구현
break;
}
}
}
3.1.1 Unity에서 Sprite Editor의 Border 설정
: 9-Slice(Sprite의 가장자리를 늘림) 기능 = 가장자리는 유지하면서 가운데만 늘어나도록 할 수 있게 해준다.

- Sprite Texture의 Mesh Type이 Full Rect가 아니라 Tight면 Border가 잘 안 먹힐 수 있음 → Full Rect로 설정
- Image 컴포넌트의 Image Type을 반드시 Sliced로 설정해야 함
4. UI ScrollView(스크롤 뷰)
Scroll View (ScrollRect)
├── Viewport (Mask + Image)
│ └── Content (콘텐츠들이 여기에)
└── Scrollbar (Optional)
- [Hierarchy] 우클릭 > UI > Scroll View 생성
- 자동으로 기본 구조가 만들어짐 (Content, Viewport 포함)
- ScrollRect 설정 확인
- Content 필드에 Content 객체 지정
- Movement Type: Elastic, Clamped 등 선택 가능
| Clamped | 더 이상 안 움직임 | 고정 | 딱딱하고 명확 |
| Elastic | 약간 더 밀림 | 되돌아옴 | 부드럽고 자연스러움 |
- Vertical / Horizontal: 원하는 방향만 켜기
- Content 오브젝트의 RectTransform 조절
- 앵커는 top-stretch 또는 left-stretch 등으로 설정하면 유동적인 레이아웃에 적합
- Vertical Layout Group, Content Size Fitter를 함께 사용하면 자동 정렬 가능
[실습]
- Scroll View의 Content 객체의 RectTransform을 “탑 스트레치(Top-Stretch)
| Horizontal Fit | Unconstrained | 가로 너비는 자동으로 조절하지 않음. 부모(Viewport)의 크기나 수동으로 지정한 크기를 그대로 사용함. |
| Vertical Fit | Preferred Size | 세로 높이는 자식 오브젝트들의 크기와 개수에 따라 자동으로 확장됨. 세로 스크롤 시 필수. |


5. NPC
5.1. 에셋으로 이쁘게 꾸미기
(Order in layer 유의) / NPC는 애니메이션으로 구현

5.2 NPC와 대화하는 팝업창 / 팝업 트리거 설정 / 인터렉션 스크립트
= 표지판 팝업과 동일하게 진행

| Dim | 팝업창이 나타날 때 배경을 어둡게 하여 사용자 시선을 집중시킴 | 반투명 이미지 사용 (Image 컴포넌트의 Alpha 조절) |
5.3 대화 타이핑 효과 구현
using System.Collections;
using TMPro; // TextMeshPro 관련 기능 사용
using UnityEngine;
public class TypingText : MonoBehaviour
{
// Inspector에서 연결할 TextMeshProUGUI 컴포넌트
[SerializeField] private TextMeshProUGUI textUI;
// 전체 출력할 문자열 저장용 변수
private string currText;
// 글자가 하나씩 나오는 간격 (초 단위)
[SerializeField] private float typingSpeed = 0.1f;
// 오브젝트가 생성될 때 실행 (게임 시작 시 1회)
void Awake()
{
// Text 컴포넌트에 미리 적어놓은 텍스트를 currText에 저장
currText = textUI.text;
}
// 오브젝트가 활성화될 때 실행됨
void OnEnable()
{
// 텍스트를 비우고 타이핑 시작
textUI.text = string.Empty;
// 타이핑 효과 실행 (코루틴으로 한 글자씩 출력)
StartCoroutine(TypingRoutine());
}
// 실제 타이핑 애니메이션을 수행하는 코루틴
IEnumerator TypingRoutine()
{
int textCount = currText.Length; // 출력할 글자의 총 개수
// 한 글자씩 추가하면서 텍스트 출력
for (int i = 0; i < textCount; i++)
{
textUI.text += currText[i]; // 현재 글자를 추가
yield return new WaitForSeconds(typingSpeed); // 지정한 시간만큼 대기
}
}
}

6. 맵전환_ 건물에 들어가고 나가기 / Door Interaction / Fade 효과
6.1 집 내부 구현

벽에 Collider를 추가 = 3가지 추가 필요
1. rigidbody2D-static
2. 타일 collider2D -Operation = Merge / 인접한 타일들의 Collider를 연결된 하나의 Collider로 처리함
3. composite collider2D / Edge radius 조절
6.2 페이드 생성
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class FadeRoutine : MonoBehaviour
{
// 화면을 덮는 UI 패널 (검정 이미지 등) → 페이드 효과 대상
public Image fadePanel;
// 외부에서 호출하는 메서드
public void OnFade(float fadeTime, Color color, bool isFadeStart)
{
StartCoroutine(Fade(fadeTime, color, isFadeStart));
}
// 실제로 페이드 인/아웃을 실행하는 코루틴
public IEnumerator Fade(float fadeTime, Color color, bool isFadeStart)
{
float timer = 0f; // 경과 시간
float percent = 0f; // 0~1 사이의 진행률
while (percent < 1f) // 진행률이 1(100%)이 될 때까지 반복
{
timer += Time.deltaTime; // 프레임당 시간 누적
percent = timer / fadeTime; // 지금까지의 시간 / 전체 시간 = 현재 진행률
// isFadeStart가 true면 화면을 점점 어둡게 (알파값 ↑), false면 점점 밝게 (알파값 ↓)
float value = isFadeStart ? percent : 1 - percent;
// 지정된 색상으로 알파값만 점점 바꾸면서 페이드 효과 적용
fadePanel.color = new Color(color.r, color.g, color.b, value);
yield return null; // 다음 프레임까지 대기
}
}
}
6.3 문에 트리거 콜라이더 추가시켜놓기
6.4 씬 전환 없는 맵이동 효과
[문 통과 코드]
- 맵 전환: 집 내부/외부 오브젝트를 활성화·비활성화
- 플레이어 위치 이동: 집 안팎의 지정된 위치로 즉시 이동
- 이동 제어: 전환 중 플레이어 조작 잠시 비활성화 후 복구
- UI 연출: 페이드 인/아웃으로 부드러운 화면 전환 구현
using System;
using System.Collections;
using UnityEngine;
public class InteractionEvent : MonoBehaviour
{
public enum InteractionType { SIGN, DOOR, NPC }
public InteractionType type;
public SoundController soundController; // 사운드 재생용 컨트롤러
public GameObject popUp; // 간판이나 NPC 대화창 팝업 UI
public FadeRoutine fade; // 화면 페이드 효과 스크립트
public GameObject map; // 외부 맵 오브젝트
public GameObject house; // 집 내부 오브젝트
public Vector3 inDoorPos; // 집 내부로 들어갈 때 플레이어 위치
public Vector3 outDoorPos; // 집 밖으로 나올 때 플레이어 위치
public bool isHouse; // 플레이어가 집 안에 있는지 여부
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
Interaction(other.transform);
}
}
private void OnTriggerExit2D(Collider2D other)
{
// "Player" 태그라면 팝업 UI 비활성화
if (other.CompareTag("Player"))
{
popUp.SetActive(false);
}
}
// 상호작용 처리 함수
void Interaction(Transform player)
{
switch (type)
{
case InteractionType.SIGN:
popUp.SetActive(true);
break;
case InteractionType.DOOR:
// 문이면 코루틴 실행 (페이드, 위치 이동 등)
StartCoroutine(DoorRoutine(player));
break;
case InteractionType.NPC:
popUp.SetActive(true);
break;
}
}
// 문 통과 연출 코루틴
IEnumerator DoorRoutine(Transform player)
{
// 문 열릴 때 사운드 재생
soundController.EventSoundPlay("Door Open");
// 3초간 화면 페이드 아웃 (점점 어두워짐)
yield return StartCoroutine(fade.Fade(3f, Color.black, true));
// 현재 위치 상태에 따라 맵과 집 오브젝트 활성화/비활성화 전환
map.SetActive(isHouse);
house.SetActive(!isHouse);
// 플레이어 위치 이동: 집 안이면 집 밖 위치로, 아니면 집 안 위치로 이동
var pos = isHouse ? outDoorPos : inDoorPos;
player.transform.position = pos;
// 집 안/밖 상태 반전 (다음 번 이동 시 위치 바꾸기 위함)
isHouse = !isHouse;
// 문 닫힐 때 사운드 재생
soundController.EventSoundPlay("Door Close");
// 1초 대기 (연출 템포 조절용)
yield return new WaitForSeconds(1f);
// 3초간 화면 페이드 인 (점점 밝아짐)
yield return StartCoroutine(fade.Fade(3f, Color.black, false));
}
}
<소리 연결 내용은 뒤에 내용>

'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(플랫포머 맵 만들기 ) _ 멋쟁이사자처럼 유니티 부트캠프 후기 31회차 (5) | 2025.07.03 |
|---|---|
| 유니티(사운드매니저 / 설정창 / 씬 전환) _ 멋쟁이사자처럼 유니티 부트캠프 후기 30회차 (0) | 2025.07.03 |
| 유니티(콤보 공격 구현, 타일맵 Props 실습) _ 멋쟁이사자처럼 유니티 부트캠프 후기 28회차 (1) | 2025.06.26 |
| 유니티(조이스틱구현 2, Animator Blend Tree) _ 멋쟁이사자처럼 유니티 부트캠프 후기 27회차 (4) | 2025.06.24 |
| 유니티(게임수학_외적내적/ 타일 생성 포탑 설치 실습 / 조이스틱구현1) _ 멋쟁이사자처럼 유니티 부트캠프 후기 26회차 (0) | 2025.06.23 |