Skip to content

heoweb906/WINDER

Repository files navigation

목차

  1. [📌 게임 소개
  2. [👥 팀원 소개]
  3. [🕹️ 게임 플레이]
  4. [🛠 기능 구현 및 작업]
    • [NPC 상태머신 설계 및 활용]
    • [부드러운 UI 시스템]
    • [퍼즐 기믹 구현]

WINDER

1. 📌 게임 소개

한 줄 소개 : 누구나 각자의 사정으로 도움이 필요한 법이죠. 그들에게 손을 내밀어 주세요.

  • 개발 기간 : 2024.08 ~ 2025.06
image

WINDER은 서로를 외면한 채 살아가는, 태엽으로 움직이는 세상을 배경으로 합니다. 이 무관심 세계 속에서 도움이 필요한 이들을 도와주고타인의 감정과 상처를 ‘내면’이라는 공간을 통해 체험하고 공감하는 게임입니다. 인물의 내면은 그들의 기억과 감정이 담긴 공간으로 상징적 오브젝트와 퍼즐로 표현되며 플레이를 통해 인물의 힘듦을 깊이 이해하고 위로하는 경험을 제공합니다.




2. 👥 팀원 소개

이름 역할
김병섭 Game Designer
안정빈 Game Designer
허재승 Programmer
양재호 Programmer
김화빈 Graphic 2D
김민진 Graphic 2D
오예령 Graphic 2D
강민희 Graphic 3D
김규진 Graphic 3D
김세인 Graphic 3D
이은송 Graphic 3D
백현민 Graphic 3D
조현재 Graphic 3D
손효민 Acting
오하루 Acting





3. 🕹️ 게임 플레이

Left Image 1 Right Image 1
Left Image 1 Right Image 1 Left Image 1

"WINDER"는 복잡한 컨트롤이나 스트레스 없이, 이야기와 연출에 온전히 집중할 수 있는 시네마틱 플랫포머입니다. 마치 한 편의 애니메이션을 보듯 주인공과 함께 여행하며, 맵 곳곳에 숨겨진 이야기들을 발견해 보세요. 여러분의 도움이 필요한 NPC들이 기다리고 있습니다.

4. 🛠 기술 구현

NPC 상태머신 설계 및 활용

계층적 유한 상태 머신 (HFSM) 기반 NPC 시스템

게임 내 등장하는 수많은 NPC들이 각자의 역할(출근, 스마트폰 사용, 태엽 감기 등)에 맞춰 자연스럽게 행동하도록 계층적 FSM 구조를 설계했습니다. 이를 통해 수십 종의 NPC 로직을 하나의 통합된 구조 안에서 유연하게 확장하고 관리할 수 있었습니다.

1. 구조 설계 (Architecture)

모든 NPC는 공통된 BaseState를 상속받아 기본적인 물리 연산과 애니메이션 이벤트를 공유하며, 개별적인 행동 로직은 하위 상태 클래스에서 구체화하는 방식을 채택했습니다.

classDiagram
    class NPC_Simple {
        +StateMachine machine
        +ActionEventList actionEvent
    }
    class StateMachine {
        +ChangeState()
        +CurrentState
    }
    class BaseState {
        +OnEnter()
        +OnUpdate()
        +OnAnimationEvent()
    }
    class NPC_Simple_State {
        <<Base Implementation>>
    }
    
    NPC_Simple --> StateMachine
    StateMachine --> BaseState
    BaseState <|-- NPC_Simple_State
    
    NPC_Simple_State <|-- IDLEState
    NPC_Simple_State <|-- WalkState
    NPC_Simple_State <|-- ActionEventState
    
    ActionEventState ..> TextingState : Switch
    ActionEventState ..> SpinTaeYubState : Switch
    ActionEventState ..> WorkingState : Switch

Loading
  1. 핵심 구현 사항 동적 상태 분기 (Dynamic State Dispatching) NPC_Simple_ActionEvent 상태를 진입점(Entry Point)으로 활용하여, NPC에게 할당된 Enum 데이터(actionEventList)에 따라 즉시 적절한 행동 상태(태엽 감기, 스마트폰 하기 등)로 전환되도록 구현했습니다. 이 구조 덕분에 기획 데이터 수정만으로 NPC의 초기 행동 패턴을 쉽게 변경할 수 있습니다.

애니메이션 이벤트와 로직의 동기화 단순한 Update 루프 의존을 넘어, OnAnimationEnterEvent, OnAnimationExitEvent 등 애니메이션 프레임 단위의 이벤트를 상태 머신이 직접 수신하도록 설계했습니다. 이를 통해 '태엽을 다 감은 직후'나 '인사 동작이 끝난 순간' 등 정밀한 타이밍에 상태 전이가 발생하여 시네마틱한 연출의 끊김을 방지했습니다.

높은 재사용성과 확장성 기본적인 이동(NavMesh)이나 대기 로직은 상위 클래스에서 처리하고, 특수 행동만 오버라이드하여 구현했습니다. 새로운 NPC 패턴이 필요할 때 기존 코드를 건드리지 않고 새로운 State 클래스만 추가하면 되는 구조입니다.

  1. 코드 예시 (Core Logic) C#

// NPC의 행동 타입(Enum)에 따라 적절한 상태로 자동 분기하는 로직

public class NPC_Simple_ActionEvent : NPC_Simple_State
{
    public override void OnEnter()
    {
        base.OnEnter();
        
        // 기획 데이터(actionEventList)에 따라 구체적인 행동 상태로 전이
        switch ((int)npc.actionEventList)
        {
            case (int)ActionEvent.WorkInCompany:
                machine.OnStateChange(machine.SpinTaeYubState); // 태엽 감기
                break;
            case (int)ActionEvent.TextingSmartPhone:
                machine.OnStateChange(machine.TextingSmartPhoneState); // 스마트폰
                break;
            default:
                machine.OnStateChange(machine.IDLEState);
                break;
        }
    }
}

UI 시스템 (Smooth UI System)

사용자 경험(UX)을 최우선으로 고려한 객체 지향적 UI 프레임워크

"UI의 움직임이 딱딱하면 게임 전체의 퀄리티가 낮아 보인다"는 철학 아래, 단순한 기능 구현을 넘어 버튼의 반응, 애니메이션, 사운드 등 미세한 디테일을 제어할 수 있는 시스템을 구축했습니다.

1. 구조 설계 (Architecture)

MainMenuController가 전체적인 입력과 패널 전환을 관리하고, 개별 버튼은 MenuButton 베이스 클래스를 상속받아 각자의 역할에 맞는 애니메이션과 기능을 오버라이딩하여 구현했습니다.

classDiagram
    class MainMenuController {
        +PanelOn()
        +InputKey()
    }
    class MenuButton {
        +SelectButtonOn()
        +SelectButtonOff()
        +ImplementButton()
    }
    class Button_StartGame {
        +SelectButtonOn()
        +ImplementButton()
    }
    class Button_Option {
        +SelectButtonOff()
        +ImplementButton()
    }

    MainMenuController --> MenuButton : Manages
    MenuButton <|-- Button_StartGame : Inherits
    MenuButton <|-- Button_Option : Inherits

Loading
  1. 핵심 구현 사항 DOTween을 활용한 감성적인 피드백 딱딱하게 켜지고 꺼지는 UI 대신, DOTween 라이브러리를 활용해 알파값 페이드(Fade), 폰트 크기 조절, 색상 변경 등을 수학적으로 계산하여 부드럽게 연출했습니다. 텍스트와 이미지가 순차적으로 나타나는 딜레이 효과를 통해 고급스러운 연출을 구현했습니다.

확장성 높은 버튼 시스템 모든 버튼은 MenuButton 클래스를 상속받습니다. 새로운 버튼이 필요할 때 베이스 클래스의 SelectButtonOn/Off 메서드만 오버라이드하면, 기존 입력 시스템의 수정 없이도 고유한 애니메이션과 기능을 가진 버튼을 쉽게 추가할 수 있습니다.

통합 입력 처리 (Cross-Platform Input) 키보드와 게임 패드(Controller) 입력을 통합하여 처리합니다. 특히 스틱 입력 시 대각선 입력을 방지하고 가장 강한 축의 입력을 우선 처리하는 알고리즘을 적용하여 오작동 없는 정확한 메뉴 탐색을 지원합니다.

  1. 코드 예시 (Core Logic) // 1. 패널 등장 시 순차적 페이드인 효과 (DOTween)
public void PanelOn(GameObject ActivePanel, float fAnimSpeed = 1f)
{
    // 이미지와 텍스트들을 순차적으로 부드럽게 페이드인
    foreach (TextMeshProUGUI textMesh in textMeshes)
    {
        DOTween.To(() => textMesh.color.a, x => {
            Color c = textMesh.color; c.a = x; textMesh.color = c;
        }, 1f, fAnimSpeed)
        .SetEase(Ease.Linear)
        .SetDelay(0.1f); // 약간의 딜레이로 순차적 등장 연출
    }
}


// 2. 버튼 상속 및 커스텀 애니메이션 구현
public class Button_StartRealNewGame : MenuButton
{
    public override void SelectButtonOn()
    {
        base.SelectButtonOn();
        // 선택 시 폰트가 커지며 청록색으로 부드럽게 변경
        textButton.DOFontSize(24f, 0.35f).SetEase(Ease.OutCirc);
        textButton.DOColor(new Color(0.58f, 1f, 1f, 1f), 0.35f);
    }

    public override void SelectButtonOff()
    {
        base.SelectButtonOff();
        // 선택 해제 시 원래대로 복귀
        textButton.DOFontSize(20f, 0.35f).SetEase(Ease.OutCirc);
        textButton.DOColor(Color.white, 0.35f);
    }
}

퍼즐 기계 (ClockWork) 구조 설계

다형성과 인터페이스를 활용한 확장 가능한 퍼즐 상호작용 시스템

게임의 핵심인 '태엽 장치(ClockWork)'와 다양한 퍼즐 오브젝트들은 저마다 다른 기능과 로직을 가지고 있습니다. 수많은 장치를 개별적으로 하드코딩하는 대신, 통일된 인터페이스와 상속 구조를 설계하여 플레이어는 일관된 방식으로 상호작용하고, 각 오브젝트는 스스로 로직을 수행하도록 구현했습니다.

1. 구조 설계 (Architecture)

모든 상호작용 가능한 물체는 InteractableObject를 상속받으며, 부품을 끼워야 작동하는 복잡한 기계들은 IPartsOwner 인터페이스를 통해 부품 슬롯(PartsArea)과 느슨하게 연결됩니다.

classDiagram
    class InteractableObject {
        +ActiveEvent()
        +bool canInteract
    }
    class ClockWork {
        +ClockBattery battery
        +ChargingBattery()
        +ClockWorkRotate()
    }
    class PartsArea {
        +InsertParts()
        +RemoveParts()
    }
    class IPartsOwner {
        <<interface>>
        +InsertOwnerFunc()
        +RemoveOwnerFunc()
    }
    class SoundBlockMachine {
        +PlayPitchSounds()
    }

    InteractableObject <|-- ClockWork
    InteractableObject <|-- CarriedObject
    InteractableObject <|-- PartsArea
    
    PartsArea --> IPartsOwner : Notifies
    IPartsOwner <|.. SoundBlockMachine : Implements
    ClockWork <-- SoundBlockMachine : Uses Logic
Loading
  1. 핵심 구현 사항 통일된 상호작용 (Unified Interaction) 플레이어는 대상이 태엽이든, 운반 가능한 물체든 상관없이 동일한 키 입력으로 상호작용합니다. InteractableObject의 ActiveEvent()만 호출하면, 각 객체가 오버라이딩된 로직(태엽 감기, 물건 집기 등)을 수행합니다.

인터페이스를 통한 느슨한 결합 (Loose Coupling) PartsArea(부품 슬롯)는 자신이 어떤 기계에 부착되어 있는지 알 필요가 없습니다. 단지 부품이 들어오면 연결된 IPartsOwner에게 신호를 보낼 뿐입니다. 이를 통해 신호등, 도장 기계, 사운드 머신 등 전혀 다른 기계들도 동일한 부품 시스템을 재사용할 수 있습니다.

배터리 및 태엽 시스템 모듈화 ClockWork 클래스는 태엽의 회전(DoTween 활용)과 배터리 충전 로직을 캡슐화했습니다. 벽에 붙은 태엽, 바닥에 있는 태엽 등 타입에 따라 회전 축과 속도만 다르게 설정하여 코드 중복을 최소화했습니다.

  1. 코드 예시 (Core Logic)
// 1. 최상위 상호작용 클래스
public class InteractableObject : MonoBehaviour
{
    public InteractableType type; // ClockWork, Carried, Grab 등
    public virtual void ActiveEvent() { } // 자식 클래스에서 구체화
}

// 2. 태엽 장치 (퍼즐의 핵심)
public class ClockWork : InteractableObject
{
    public override void ClockWorkRotate(float dir)
    {
        // 타입(벽/바닥)에 따라 다른 축으로 회전하며, 연결된 다른 태엽들도 재귀적으로 회전시킴
        transform.DORotate(new Vector3(0, 0, 180 * dir), 0.3f, RotateMode.LocalAxisAdd)
            .SetEase(Ease.Linear);
            
        foreach (var linkedClock in plusClockWorksList)
            linkedClock.ClockWorkRotate(dir * -1); // 맞물린 태엽은 반대로 회전
    }
}

// 3. 부품 소유자 인터페이스 (유연한 확장성)
public interface IPartsOwner
{
    // 부품이 슬롯에 장착/해제될 때 호출되는 콜백
    void InsertOwnerFunc(GameObject parts, int index);
    void RemoveOwnerFunc(int index);
}

// 4. 구현 예시: 사운드 블록 머신
public class SoundBlockMachine : ClockBattery, IPartsOwner
{
    public void InsertOwnerFunc(GameObject soundPiece, int index)
    {
        // 부품이 들어오면 해당 위치의 소리 조각 데이터 갱신
        soundPieces[index] = soundPiece.GetComponent<SoundPiece>();
    }
    
    public override void TurnOnObj()
    {
        // 작동 시작 시, 장착된 소리 조각들을 순차적으로 연주
        StartCoroutine(PlayPitchSoundsCoroutine());
    }
}

About

Project TaeYub

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 5