식물 참조 프로젝트

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

식물은 플레이어가 씨앗을 심고 물을 주어 후에 수확하고 판매할 수 있는 참조 경험입니다.

식물 프로젝트 배너

이 프로젝트는 Roblox에서 경험을 개발할 때 마주칠 수 있는 일반적인 사용 사례에 중점을 두고 있습니다. 해당 내용이 적용 가능한 경우에는 다양한 구현 선택의 트레이드오프, 타협 및 논리에 대한 메모를 발견할 수 있으며, 이를 통해 자신의 경험에 최선의 결정을 내릴 수 있습니다.

파일 가져오기

  1. 식물 경험 페이지로 이동합니다.
  2. 버튼을 클릭하고 스튜디오에서 편집합니다.

사용 사례

식물은 다음 사용 사례를 다루고 있습니다:

  • 세션 데이터 및 플레이어 데이터 지속성
  • UI 뷰 관리
  • 클라이언트-서버 네트워킹
  • 최초 사용자 경험(FTUE)
  • 하드 및 소프트 화폐 구매

또한 이 프로젝트는 많은 경험에 적용되는 더 좁은 문제 집합을 해결하며, 여기에는 다음이 포함됩니다:

  • 플레이어와 연결된 장소의 영역 커스터마이징
  • 플레이어 캐릭터의 이동 속도 관리
  • 캐릭터를 따라다니는 객체 생성
  • 캐릭터가 있는 세계의 일부 감지

이 경험에는 너무 작거나 틈새가 있거나 흥미로운 설계 문제에 대한 해결 솔루션을 보여주지 않는 여러 사용 사례가 있으므로 이러한 사례는 다루어지지 않습니다.

프로젝트 구조

경험을 생성할 때 가장 첫 번째 결정은 프로젝트를 어떻게 구조화할 것인가 하는 것입니다. 이는 주로 특정 인스턴스를 데이터 모델에 배치하는 위치와 클라이언트 및 서버 코드의 진입점을 어떻게 구성하고 구조화하는지를 포함합니다.

데이터 모델

다음 표는 데이터 모델의 인스턴스가 배치되는 컨테이너 서비스를 설명합니다.

서비스인스턴스 유형
Workspace

플레이어에게 속하지 않는 세계의 일부를 구체적으로 나타내는 정적 모델을 포함합니다. 런타임 중에 이러한 인스턴스를 동적으로 생성, 수정 또는 파괴할 필요가 없으므로 여기 두는 것이 허용됩니다.

런타임 중 플레이어 농장 모델이 추가될 빈 Folder도 있습니다.

Lighting

대기 효과 및 조명 효과.

ReplicatedFirst

로딩 화면을 표시하고 경험을 초기화하는 데 필요한 최소한의 인스턴스 집합을 포함합니다. ReplicatedFirst에 배치된 인스턴스가 많을수록, ReplicatedFirst의 코드가 실행되기 전에 복제되기까지 걸리는 대기 시간이 길어집니다.

  • Instances 폴더에 로딩 화면 GUI가 있습니다.
  • Source 폴더에는 로딩 화면 코드와 나머지 경험이 로드될 때까지 기다리는 데 필요한 코드가 있습니다. start LocalScript는 프로젝트의 모든 클라이언트 측 코드의 진입점입니다.
ReplicatedStorage

클라이언트와 서버 모두에서 접근이 필요한 모든 인스턴스의 저장 컨테이너 역할을 합니다.

  • Dependencies 폴더에는 프로젝트에서 사용되는 일부 서드파티 라이브러리가 있습니다.
  • Instances 폴더에는 다양한 사전 제작 인스턴스가 있습니다.
  • Source 폴더에는 클라이언트와 서버 모두에서 접근 가능해야 하는 로딩 과정에 필요하지 않은 모든 코드가 있습니다.
ServerScriptService

프로젝트의 모든 서버 측 코드에 대한 진입 점 역할을 하는 Script를 포함합니다.

ServerStorage

클라이언트에 복제할 필요가 없는 모든 인스턴스의 저장 컨테이너 역할을 합니다.

  • Instances 폴더에는 템플릿 농장 모델이 있습니다. 플레이어가 경험에 참여할 때 이를 Workspace에 복사하고 모든 플레이어에게 복제됩니다.
  • Source 폴더에는 서버에 특화된 모든 코드가 있습니다.
SoundService

경험에서 사용되는 사운드 효과를 위한 Sound 객체를 포함합니다. SoundService 아래의 이 Sound 객체는 위치가 없으며 3D 공간에서 시뮬레이션되지 않습니다.

진입점

대부분의 프로젝트는 코드 내부에 재사용 가능한 ModuleScripts를 조직하여 전체 코드베이스에서 가져올 수 있게 합니다. ModuleScripts는 재사용 가능하지만 독립적으로 실행되지 않고 Script 또는 LocalScript에 의해 가져와야 합니다. 많은 Roblox 프로젝트에는 각 경험의 동작 또는 특정 시스템에 관련된 수많은 ScriptLocalScript 객체가 있어 여러 진입점을 생성합니다.

식물 마이크로게임의 경우 모든 클라이언트 코드의 진입점 역할을 하는 단일 LocalScript와 모든 서버 코드의 진입점 역할을 하는 단일 Script를 통해 다른 접근 방식이 구현되었습니다. 프로젝트에 맞는 올바른 접근 방식은 요구 사항에 따라 다르지만, 단일 진입점은 시스템이 실행되는 순서를 보다 효과적으로 제어할 수 있게 해줍니다.

다음 목록은 두 접근 방식의 트레이드오프를 설명합니다:

  • 단일 Script와 단일 LocalScript가 각각 서버와 클라이언트 코드를 다룹니다.
  • 모든 코드가 단일 스크립트에서 초기화되기 때문에 시스템을 시작하는 순서를 제어할 수 있는 더 큰 권한.
  • 시스템 간에 객체를 참조로 전달할 수 있습니다.

고수준 시스템 아키텍처

프로젝트의 주요 시스템은 아래에 자세히 설명되어 있습니다. 이 시스템들은 서로 다르게 복잡하며, 많은 경우 이들의 기능은 다른 클래스의 계층 구조에 의해 추상화됩니다.

식물 프로젝트 시스템 아키텍처 다이어그램

이 시스템 각각은 "싱글톤"으로, 인스턴스화할 수 없는 클래스로서 해당 클라이언트 또는 서버 start 스크립트에 의해 초기화됩니다. 이 싱글톤 패턴에 대해 더 읽어보실 수 있습니다.

서버

다음 시스템은 서버와 관련이 있습니다.

시스템설명
네트워크
  • 모든 RemoteEventRemoteFunction 인스턴스를 생성합니다.
  • 클라이언트의 메시지를 전송하고 듣는 메서드를 노출합니다.
  • 런타임에서 클라이언트로부터 수신된 인수에 대한 유형 검증.
PlayerDataServer
  • DataStoreService를 사용하여 지속적인 플레이어 데이터를 저장하고 로드합니다.
  • 플레이어 데이터를 메모리에 저장하고 변이를 클라이언트에 복제합니다.
  • 플레이어 데이터 구독, 쿼리 및 업데이트를 위한 신호와 메서드를 노출합니다.
마켓
  • 클라이언트로부터 소프트 화폐 거래를 처리합니다.
  • 수확된 식물을 판매하는 메서드를 노출합니다.
CollisionGroupManager
  • 플레이어 캐릭터 모델을 충돌 그룹에 할당합니다.
  • 플레이어 캐릭터가 식물 마차와 충돌하지 않도록 충돌 그룹을 구성합니다.
FarmManagerServer
  • 플레이어가 경험에 참가하면 플레이어 데이터에서 농장 모델을 재생성합니다.
  • 플레이어가 퇴장할 때 농장 모델을 제거합니다.
  • 플레이어의 농장이 변경될 때 플레이어 데이터를 업데이트합니다.
  • 특정 플레이어와 연관된 Farm 클래스에 접근하는 메서드를 노출합니다.
PlayerObjectsContainer
  • 플레이어의 생애와 관련된 다양한 객체를 생성하고 이를 검색하는 메서드를 제공합니다.
TagPlayers
  • 모든 플레이어와 캐릭터 객체에 CollectionService 태그를 추가합니다.
FtueManagerServer
  • FTUE 동안 각 단계를 실행하고 완료될 때까지 기다립니다.
CharacterSpawner
  • 캐릭터가 죽으면 재생성됩니다. Players.CharacterAutoLoads가 비활성화되었으므로 플레이어의 데이터가 로드될 때까지 스폰이 일시 중지됩니다.

클라이언트

다음 시스템은 클라이언트와 관련이 있습니다.

시스템설명
네트워크
  • 서버가 모든 RemoteEventRemoteFunction 인스턴스를 생성할 때까지 기다립니다.
  • 서버와 메시지를 주고받기 위한 메서드를 노출합니다.
  • 런타임 매개변수 유형 검증을 시행합니다.
  • 원격 함수에서 pcall()을 실행합니다.
PlayerDataClient
  • 로컬 플레이어의 데이터를 메모리에 저장합니다.
  • 플레이어 데이터의 변경을 쿼리하고 구독하기 위한 메서드와 신호를 노출합니다.
MarketClient
  • 서버에 소프트 화폐로 아이템을 구매 요청하는 메서드를 노출합니다.
LocalWalkJumpManager
  • 여러 위치에서 이 값을 수정할 때 충돌을 피하기 위해 WalkSpeed 또는 JumpHeight를 수정하는 메서드를 노출합니다.
FarmManagerClient
  • 특정 CollectionService 태그가 인스턴스에 적용되는 것을 듣고 "구성 요소"를 생성하여 이 인스턴스에 동작을 추가합니다. "구성 요소"는 CollectionService 태그가 인스턴스에 추가되면 생성되고 제거될 때 파괴되는 클래스를 의미하며, 이는 농장에서의 CTA 프롬프트 및 플레이어에게 농장 상태를 전송하는 다양한 클래스에 사용됩니다.
UISetup
  • 모든 UI 레이어를 초기화합니다.
  • 특정 레이어를 세계의 물리적 부분에서만 볼 수 있도록 구성합니다.
  • 메뉴가 활성화될 때 특별한 카메라 효과를 연결합니다.
FtueManagerClient
  • 클라이언트에서 FTUE 단계를 구성합니다.
CharacterSprint
  • 플레이어 캐릭터가 농장에서 벗어날 때 LocalWalkJumpManager를 사용하여 WalkSpeed를 증가시킵니다.

클라이언트-서버 통신

대부분의 Roblox 경험은 클라이언트와 서버 간의 통신 요소를 포함합니다. 여기에는 클라이언트가 서버에 특정 작업을 수행해 달라고 요청하고 서버가 클라이언트에 업데이트를 복제하는 것이 포함될 수 있습니다.

이 프로젝트에서는 RemoteEventRemoteFunction 객체의 사용을 제한하여 통신을 가능한 한 일반적으로 유지하여 특별한 규칙을 추적해야 할 양을 줄입니다. 이 프로젝트는 다음 메서드를 선호하는 순서로 사용합니다:

플레이어 데이터 시스템을 통한 복제

플레이어 데이터 시스템을 사용하면 데이터가 플레이어와 연관되어 저장 세션 간에 지속됩니다. 이 시스템은 클라이언트에서 서버로의 복제를 제공하며 데이터 쿼리 및 변경 구독에 사용될 수 있는 API 세트를 제공합니다. 이는 서버에서 클라이언트로의 플레이어 상태 변경을 복제하는 데 이상적입니다.

예를 들어, 클라이언트에게 몇 개의 동전을 가지고 있는지를 알리기 위해 맞춤형 UpdateCoins RemoteEvent를 호출하는 대신 다음을 호출하고 클라이언트가 PlayerDataClient.updated 이벤트를 통해 구독하도록 할 수 있습니다.


PlayerDataServer.setValue(player, "coins", 5)

물론, 이는 서버에서 클라이언트로의 복제 및 세션 간에 지속되기를 원하는 값에만 유용하지만, 이는 프로젝트의 놀라울 정도로 많은 경우에 적용됩니다. 여기에는 포함됩니다:

  • 현재 FTUE 단계
  • 플레이어의 인벤토리
  • 플레이어가 가진 동전의 양
  • 플레이어 농장의 상태

속성을 통한 복제

서버가 특정 Instance에 대해 클라이언트에 복제해야 하는 사용자 지정 값을 복제해야 하는 상황에서는 속성을 사용할 수 있습니다. Roblox는 속성 값을 자동으로 복제하므로 객체에 연결된 상태를 복제하기 위해 코드 경로를 유지할 필요가 없습니다. 또 다른 장점은 이 복제가 인스턴스와 함께 이루어진다는 것입니다.

이는 런타임에 생성된 인스턴스에 특히 유용합니다. 데이터 모델에 부모가 되기 전에 새 인스턴스에 설정된 속성은 인스턴스와 원자적으로 복제됩니다. 이는 RemoteEvent 또는 StringValue를 통해 추가 데이터를 복제하는 "기다리는" 코드를 작성할 필요를 피합니다.

클라이언트 또는 서버에서 GetAttribute() 메서드를 사용하여 데이터 모델에서 속성을 직접 읽을 수 있으며, GetAttributeChangedSignal() 메서드를 사용하여 변경을 구독할 수 있습니다. 식물 프로젝트에서는 이 방법이 식물의 현재 상태를 클라이언트에 복제하는 데 사용됩니다.

태그를 통한 복제

CollectionService를 사용하면 Instance에 문자열 태그를 적용할 수 있습니다. 이는 인스턴스를 분류하고 해당 분류를 클라이언트로 복제하는 데 유용합니다.

예를 들어, 서버에서는 특정 화분이 식물을 받을 수 있음을 나타내기 위해 CanPlant 태그를 클라이언트에게 적용합니다.

네트워크 모듈을 통한 직접 메시징

이전의 옵션이 모두 적용되지 않는 상황에서는 네트워크 모듈을 통해 사용자 지정 네트워크 호출을 사용할 수 있습니다. 이는 클라이언트-서버 통신을 허용하는 프로젝트의 유일한 옵션이며, 따라서 클라이언트 요청을 전송하고 서버 응답을 수신하는 데 가장 유용합니다.

식물은 다음과 같은 다양한 클라이언트 요청에 대한 직접 네트워크 호출을 사용합니다:

  • 식물에 물 주기
  • 씨앗 심기
  • 아이템 구매

이 접근 방식의 단점은 각 개별 메시지가 별도의 구성을 필요로 하여 프로젝트의 복잡성을 증가시킬 수 있다는 것이지만, 이는 서버에서 클라이언트로의 통신을 위해 가능한 한 많이 피해왔습니다.

클래스와 싱글톤

식물 프로젝트의 클래스는 Roblox의 인스턴스와 마찬가지로 생성 및 삭제할 수 있습니다. 그 클래스 문법은 객체 지향 프로그래밍을 위한 관용적 Lua 접근 방식에서 영감을 받아 엄격한 유형 검증 지원을 가능하게 하는 여러 변경 사항이 포함되어 있습니다.

인스턴스화

프로젝트의 많은 클래스는 하나 이상의 Instances와 관련이 있습니다. 주어진 클래스의 객체는 new() 메서드를 사용하여 생성되며, 이는 Roblox에서 Instance.new()를 사용하여 인스턴스가 생성되는 방식과 일관됩니다.

이 패턴은 클래스가 데이터 모델 내에 물리적 표현을 가지고 있고 클래스의 기능을 확장할 때 일반적으로 사용됩니다. 좋은 예로는 주어진 두 Attachment 객체 사이에 Beam 객체를 생성하고 그 부착물이 항상 위를 향하도록 유지하는 BeamBetween이 있습니다. 이러한 인스턴스는 ReplicatedStorage의 사전 제작된 버전에서 복제할 수 있거나 new()의 인수로 전달되어 self에서 저장될 수 있습니다.

해당 인스턴스

위에서 언급한 바와 같이 이 프로젝트의 많은 클래스는 데이터 모델 표현이 있으며, 클래스와 함께 조작되는 인스턴스가 있습니다.

클래스 객체가 인스턴스화될 때 이러한 인스턴스를 생성하는 대신 코드에서는 일반적으로 Clone()을 사용하여 ReplicatedStorage 또는 ServerStorage에 저장된 사전 제작된 버전을 클론합니다. 이러한 인스턴스의 속성을 직렬화하고 클래스의 new() 함수에서 처음부터 생성할 수 있지만, 그렇게 하면 객체 편집이 매우 번거로워지고 독자가 이해하기 더 어려워집니다. 또한 인스턴스를 복제하는 것은 일반적으로 새로운 인스턴스를 생성하고 런타임에서 그 속성을 사용자화하는 것보다 더 빠른 작업입니다.

조합

Luau에서는 메타 테이블을 사용하여 상속이 가능하지만, 프로젝트에서는 대신 조합을 통해 클래스를 확장하도록 선택했습니다. 조합을 통해 클래스를 결합할 때 "자식" 객체는 클래스의 new() 메서드에서 인스턴스화되며, self 아래의 멤버로 포함됩니다.

이것을 실제로 보기 위해 CloseButton 클래스를 참조하세요. 이 클래스는 Button 클래스를 래핑합니다.

정리

InstanceDestroy() 메서드를 사용하여 파괴될 수 있는 것처럼, 인스턴스화할 수 있는 클래스도 파괴될 수 있습니다. 프로젝트 클래스의 소멸자 메서드는 destroy()이며, 자바스크립트 메서드에서의 camelCase 일관성을 위해 소문자로 "d"가 포함되어 있으며, 프로젝트의 클래스와 Roblox 인스턴스를 구분하기 위해 사용됩니다.

destroy() 메서드의 역할은 객체가 생성한 모든 인스턴스를 파괴하고, 모든 연결을 분리하며, 모든 자식 객체에서 destroy()를 호출하는 것입니다. 이는 인스턴스에 연결이 활성화되어 있어도 연결이 존재하지 않더라도 Luau 가비지 수집기가 이를 정리하지 않기 때문에 특히 중요합니다.

싱글톤

싱글톤은 이름에서 알 수 있듯이 오직 하나의 객체만 존재할 수 있는 클래스입니다. 이는 프로젝트가 Roblox의 서비스와 동등합니다. 싱글톤 객체에 대한 참조를 저장하고 Luau 코드에서 이를 전달하기보다는, 식물ModuleScript를 요구하는 것이 반환 값을 캐시한다는 사실을 이용합니다. 이는 서로 다른 장소에서 동일한 싱글톤 ModuleScript를 요구할 경우 일관되게 동일한 반환 객체를 제공합니다. 이 규칙의 유일한 예외는 서로 다른 환경(클라이언트 또는 서버)에서 액세스하는 경우입니다.

싱글톤은 new() 메서드가 없다는 점에서 인스턴스화 가능한 클래스와 구별됩니다. 대신, 객체와 그 메서드 및 상태는 ModuleScript를 통해 직접 반환됩니다. 싱글톤은 인스턴스화되지 않으므로 self 구문은 사용되지 않으며 메서드는 대신 점(.)으로 호출됩니다.

엄격한 유형 추론

Luau는 점진적 타이핑을 지원하므로 코드의 일부 또는 전부에 선택적 유형 정의를 추가할 수 있습니다. 이 프로젝트에서는 모든 스크립트에 대해 strict 유형 검사가 사용됩니다. 이는 Roblox의 스크립트 분석 도구를 위한 가장 적은 허용 옵션이며, 따라서 런타임 이전에 유형 오류를 포착할 가능성이 가장 높습니다.

타이핑된 클래스 문법

Lua에서 클래스를 생성하는 확립된 접근 방식은 잘 문서화되어 있으나, 이는 강력한 Luau 타이핑에는 잘 맞지 않습니다. Luau에서 클래스의 유형을 얻는 가장 간단한 방법은 typeof() 메서드입니다:


type ClassType = typeof(Class.new())

이것은 작동하지만 클래스가 런타임에만 존재하는 값, 예를 들어 Player 객체로 초기화될 때는 그다지 유용하지 않습니다. 또한 관용적 Lua 클래스 문법에서 만들었으면 하는 가정은 self에 선언된 메서드가 항상 해당 클래스의 인스턴스라는 것입니다; 이는 유형 추론 엔진이 가정할 수 없는 사항입니다.

엄격한 유형 추론을 지원하기 위해 식물 프로젝트는 관용적 Lua 클래스 문법과 여러 면에서 다른 솔루션을 사용합니다. 이 중 일부는 직관적이지 않게 느껴질 수 있습니다:

  • self의 정의가 유형 선언과 생성자 모두에 중복되어 있습니다. 이는 유지 관리 부담을 주지만, 두 정의가 서로 동기화되지 않으면 경고가 표시됩니다.
  • 클래스 메서드는 점으로 선언되므로 selfClassType 유형임을 명시적으로 선언할 수 있습니다. 메서드는 여전히 예상대로 콜론을 사용하여 호출할 수 있습니다.

--!strict
local MyClass = {}
MyClass.__index = MyClass
export type ClassType = typeof(setmetatable(
{} :: {
property: number,
},
MyClass
))
function MyClass.new(property: number): ClassType
local self = {
property = property,
}
setmetatable(self, MyClass)
return self
end
function MyClass.addOne(self: ClassType)
self.property += 1
end
return MyClass

논리적 가드 이후 유형 변환

작성 시점에는, 가드 조건문 이후에 값의 유형이 좁혀지지 않습니다. 예를 들어, 아래 가드를 따르는 경우 optionalParameter의 유형은 number로 좁혀지지 않습니다.


--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
print(optionalParameter + 1)
end

이를 완화하기 위해 이러한 가드 뒤에 새로운 변수를 생성하고 그 유형을 명시적으로 변환합니다.


--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
local parameter = optionalParameter :: number
print(parameter + 1)
end

데이터 모델 계층 탐색

일부 경우, 코드베이스는 런타임에 생성되는 객체의 트리로 이루어진 데이터 모델 계층 구조를 탐색해야 합니다. 이는 유형 검사의 흥미로운 도전을 제시합니다. 작성 시점에는 데이터 모델 구조를 유형으로 정의할 수 없습니다. 결과적으로 데이터 모델 구조에 대한 유일한 유형 정보는 최상위 인스턴스의 유형입니다.

이 문제에 대한 접근 방식은 any로 캐스팅한 다음 세분화하는 것입니다. 예를 들어:


local function enableVendor(vendor: Model)
local zonePart: BasePart = (vendor :: any).ZonePart
end

이 접근 방식의 문제는 가독성에 영향을 미칩니다. 대신 이 프로젝트에서는 데이터 모델 계층 구조를 탐색하는 데 사용되는 getInstance라는 일반 모듈을 사용하여 내부적으로 any로 캐스팅합니다.


local function enableVendor(vendor: Model)
local zonePart: BasePart = getInstance(vendor, "ZonePart")
end

타입 엔진의 데이터 모델 이해가 발전함에 따라 이러한 패턴이 더 이상 필요하지 않을 수도 있습니다.

사용자 인터페이스

식물에는 복잡하고 간단한 2D 사용자 인터페이스가 다양하게 포함되어 있습니다. 여기에는 동전 카운터와 같은 비대화형 헤드업 디스플레이(HUD) 항목과 상점과 같은 복잡한 대화형 메뉴가 포함됩니다.

UI 접근 방식

Roblox UI는 HTML DOM과 loosely 비교할 수 있습니다. 객체의 계층 구조가 사용자에게 보여줄 내용을 설명하기 때문입니다. Roblox UI를 구축하고 업데이트하는 방법은 넓게 나누어 명령적선언적 관행으로 나뉩니다.

접근 방식장점 및 단점
명령적

명령적 접근 방식에서는 UI를 Roblox의 다른 인스턴스 계층 구조처럼 취급합니다. UI 구조는 Studio에서 런타임 이전에 생성되어 데이터 모델에 추가되며, 일반적으로 StarterGui 내에 직접 추가됩니다. 그런 다음 런타임 동안 코드는 생성자가 요구하는 상태를 반영하기 위해 UI의 특정 부분을 조작합니다.

이 접근 방식에는 몇 가지 장점이 있습니다. Studio에서 UI를 처음부터 끝까지 생성하고 데이터 모델에 저장할 수 있습니다. 이는 UI 제작을 가속화할 수 있는 간단하고 시각적인 편집 경험입니다. 명령적 UI 코드는 무엇을 변경해야 할지를 스스로 결정하므로 간단한 UI 변경을 쉽게 구현할 수 있습니다.

주목할 단점은 명령적 UI 접근 방식이 변환 형태로 수동 구현이 필요하므로 상태를 복잡하게 나타내기 어렵고 상태와 UI가 여러 업데이트 상호작용으로 인해 비동기화되는 오류가 발생하는 경우가 있다는 것입니다.

또다른 문제는 명령적 접근 방식으로 UI를 의미 있는 구성 요소로 분해하기 더 어렵습니다. 전체 UI 트리가 편집 시 선언되므로 데이터 모델의 여러 부분에서 공통 패턴이 반복될 수 있습니다.

선언적

선언적 접근 방식에서는 UI 인스턴스의 원하는 상태가 명시적으로 선언되고, 이 상태의 효율적인 구현이 Roact 또는 Fusion와 같은 라이브러리를 통해 추상화됩니다.

이 접근 방식의 장점은 상태 구현이 사소해지고 UI가 어떻게 보이기를 원하는지 설명하기만 하면 됩니다. 이는 버그 식별 및 수정이 훨씬 더 수월합니다.

주요 단점은 UI 트리를 코드에서 선언해야 한다는 것입니다. Roact 및 Fusion과 같은 라이브러리는 이를 쉽게 하는 문법을 제공하지만, 여전히 시간이 많이 소요되고 UI를 조합할 때 직관적인 편집 경험이 부족하게 됩니다.

식물은 변환을 직접 보여주는 것이 UI가 Roblox에서 어떻게 생성되고 조작되는지를 보다 효과적으로 개요할 수 있다는 점에서 명령적 접근 방식을 사용합니다. 이는 선언적 접근 방식으로는 불가능할 것입니다. 반복되는 일부 UI 구조와 로직은 일반적인 명령적 UI 설계의 공통적인 함정을 피하기 위해 재사용 가능한 구성 요소로 추상화됩니다.

고수준 아키텍처

식물 프로젝트 UI 아키텍처 다이어그램

레이어와 구성 요소

식물에서 모든 UI 구조는 Layer 또는 Component입니다.

  • LayerReplicatedStorage에 있는 상대적으로 사전 제작된 UI 구조를 래핑하는 최상위 그룹화 싱글톤으로 정의됩니다. 레이어는 여러 구성 요소를 포함할 수 있으며, 자체 논리를 완전히 캡슐화할 수 있습니다. 레이어의 예로는 인벤토리 메뉴 또는 헤드업 디스플레이의 동전 수 제공자가 있습니다.
  • Component는 재사용 가능한 UI 요소입니다. 새 구성 요소 객체가 인스턴스화될 때, 이를 ReplicatedStorage에서 사전 제작된 템플릿에서 클론합니다. 구성 요소는 자신 안에 다른 구성 요소를 포함할 수 있습니다. 구성 요소의 예로는 일반 버튼 클래스 또는 항목 목록 개념이 있습니다.

뷰 처리

일반적인 UI 관리 문제는 뷰 처리입니다. 이 프로젝트에는 높은 수준의 메뉴와 HUD 항목이 있으며, 그 중 몇몇은 사용자 입력을 청취하며, 그들의 표시 여부 또는 활성화의 세심한 관리가 필요합니다.

식물UIHandler 시스템을 통해 이 문제에 접근합니다. 이 시스템은 UI 레이어가 표시되어야 할 때와 표시되어서는 안 되는 시간을 관리합니다. 경험의 모든 UI 레이어는 HUD 또는 Menu로 분류되며, 다음과 같은 규칙에 따라 가시성이 관리됩니다:

  • MenuHUD 레이어는 활성화 상태를 토글할 수 있습니다.
  • 활성화된 HUD 레이어는 활성화된 Menu 레이어가 없을 경우에만 표시됩니다.
  • 활성화된 Menu 레이어는 스택에 저장되며, 한 번에 하나의 Menu 레이어만 표시됩니다. Menu 레이어가 활성화될 때 스택의 맨 앞에 삽입되어 표시됩니다. Menu 레이어가 비활성화될 때, 스택에서 제거되고 큐의 다음 활성화된 Menu 레이어가 표시됩니다.

이 접근 방식은 메뉴를 기록으로 탐색할 수 있게 해주므로 직관적입니다. 한 메뉴에서 다른 메뉴를 열면, 새 메뉴를 닫으면 이전 메뉴가 다시 표시됩니다.

추가 자료

식물 프로젝트에 대한 이 철저한 개요를 바탕으로, 관련 개념 및 주제에 대해 더 깊이 탐구할 수 있는 다음 가이드를 살펴볼 수 있습니다.

  • 클라이언트-서버 모델 — Roblox의 클라이언트-서버 모델에 대한 개요.
  • LuauLua 5.1에서 파생된 Roblox가 만든 스크립트 언어 Luau에 대한 세부 사항.
  • 원격 이벤트 및 콜백 — 클라이언트-서버 경계를 가로지르는 통신을 위한 원격 네트워크 이벤트 및 콜백에 관한 모든 것.
  • UI — Roblox의 사용자 인터페이스 객체 및 디자인에 대한 세부 사항.
©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.