본문 바로가기

유니티(Camera Follow / 표지판 팝업 UI 인터랙션/ 씬 내부 맵 이동/타이핑 코루틴) _ 멋쟁이사자처럼 유니티 부트캠프 후기 29회차

@salmu2025. 6. 27. 17:24
반응형

[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 TypeFull 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 세로 높이는 자식 오브젝트들의 크기와 개수에 따라 자동으로 확장됨. 세로 스크롤 시 필수.

접근하면 sign 팝업

 

 

 

 

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));
    }
}

<소리 연결 내용은 뒤에 내용>

canvas, 타일맵 등 시각적인걸 구현하는게 아직 익숙하지 않다. 오늘은 특히 진도가 빨랐던 느낌이라 복습이 밀리지 않게 열심히 해야겠다. 게임에 실용적이게 쓸 수 있는 여러 기능들을 많이 배운 것 같다. 익숙해지게 복습하고 얼른 내 게임에 구현해보고 싶다.

 

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

목차