식물은 플레이어가 씨앗을 심고 물을 주면 나중에 수확하고 판매할 수 있는 참조 경험입니다.
프로젝트는 Roblox에서 경험을 개발할 때 발생할 수 있는 일반적인 사용 사례에 집중합니다. 해당되는 경우, 교환, 타협 및 다양한 구현 선택의 논리에 대한 노트를 찾을 수 있습니다. 이렇게 하면 최선의 결정을 내릴 수 있습니다.
파일 가져오기
- 식물 경험 페이지로 이동.
- 클릭하십시오 ⋯ 버튼과 Studio에서 편집 .
사용 사례
식물은 다음과 같은 사용 사례를 덮습니다.
- 세션 데이터 및 플레이어 데이터 지속
- UI 뷰 관리
- 클라이언트-서버 네트워킹
- 처음으로 사용자 경험 (FTUE)
- 하드 및 소프트 통화 구매
또한 이 프로젝트는 다음과 같은 많은 경험에 적용할 수 있는 좁은 집합의 문제를 해결합니다.
- 플레이어와 관련된 장소의 지역 사용자 정의
- 플레이어 캐릭터의 이동 속도 관리
- 문자를 따라 개체 생성
- 캐릭터가 세계의 어느 지역에 있는지 감지
이 경험에는 너무 작고, 너무 특화되거나 흥미로운 디자인 도전에 해결책을 제공하지 않는 몇 가지 사용 사례가 있습니다. 이러한 사용 사례는 커버되지 않습니다.
프로젝트 구조
경험을 생성할 때 첫 번째 결정은 프로젝트를 구조하는 방법을 결정하는 것입니다, 주로 데이터 모델에 특정 인스턴스를 배치하는 위치와 서버 코드 모두에 대한 입장 포인트를 구성하는 방법을 포함합니다.
데이터 모델
다음 표에서는 데이터 모델 인스턴스의 컨테이너 서비스가 배치되는 위치를 설명합니다.
서비스 | 인스턴스 종류 |
---|---|
Workspace | 이 세계의 3D 세계를 나타내는 정적 모델을 포함합니다. 이는 플레이어가 소유하지 않은 세계의 일부를 나타냅니다. 이러한 인스턴스를 런타임에 동적으로 생성하거나 수정하거나 삭제하지 않아도 됩니다. 따라서 이 항목을 여기에 |
Lighting | 대기 및 조명 효과입니다. |
ReplicatedFirst | 로딩 화면을 표시하고 게임을 초기화하는 데 필요한 가장 작은 하위 집합을 포함합니다. 더 많은 인스턴스가 ReplicatedFirst 에 배치될수록 코드를 더 빨리 복제하기까지 기다릴 수 있습니다.
|
ReplicatedStorage | 클라이언트와 서버에서 액세스할 수 있는 모든 인스턴스에 대한 저장소 컨테이너 역할을 합니다.
|
ServerScriptService | 프로젝트의 모든 서버 사이드 코드의 입점으로 사용되는 Script 을 포함합니다. |
ServerStorage | 클라이언트에 복제되지 않아도 되는 모든 인스턴스의 저장 컨테이너 역할을 합니다. 인스턴스 폴더의 농장 모델이 있습니다. 이 모델의 복사본은 게임에 참여한 플레이어가 복제되어 모든 플레이어에게 배포됩니다.를 위해 서버�� |
SoundService | 게임에서 사운드 효과를 위해 사용하는 Sound 개체를 포함합니다. 이 SoundService 아래에 있습니다. 이 Sound 개체는 위치가 없으며 3D 공간에서 시뮬레이션되지 않습니다. |
입장 포인트
대부분의 프로젝트는 전체 코드 기반에 걸쳐 가져올 수 있는 재사용 가능한 ModuleScripts 내에서
For the Plant microgame, a different approach is implemented through a single LocalScript that is the entry point for all client 코드, and a single Script that is the entry point for all server 코드. The correct approach for your project depends on your requirements, but a single point of entry provides greater control over the order in which systems are executed in.
다음 목록에서는 두 접근 방식의 교환 효과를 설명합니다.
- 단일 Script 및 단일 LocalScript 서버 및 클라이언트 코드 서로 다른 서버 및 클라이언트 코드.
- 모든 코드가 단일 스크립트에서 초기화되므로 시작 시 시스템 간의 순서를 더 쉽게 제어할 수 있습니다.
- 시스템 간의 참조로 개체를 전달할 수 있습니다.
고수준 시스템 아키텍처
프로젝트의 상위 수준 시스템은 아래에 자세히 설명되어 있습니다. 일부는 다른 시스템보다 훨씬 복잡하며, 많은 경우 기능은 다른 클래스 계층에서 개체화됩니다.
이 시스템 중 각각은 "싱글"입니다, 즉 해당 클라이언트 또는 서버 start 스크립트에 의해 초기화되는 비즉시 클래스입니다. 이 가이드의 나머지에 대해 싱글 패턴에 대해 자세히 읽을 수 있습니다.
서버
다음 시스템은 서버와 관련이 있습니다.
시스템 | 설명 |
---|---|
네트워크 | 모든 Class.RemoteEvent 및 Class.RemoteFunction 인스턴스를 생성합니다. 0>
|
PlayerDataServer |
|
시장 |
|
충돌 그룹 관리자 |
|
FarmManagerServer |
|
PlayerObjectsContainer |
|
태그 플레이어 |
|
FtueManagerServer |
|
CharacterSpawner |
|
클라이언트
다음 시스템은 클라이언트와 관련이 있습니다.
시스템 | 설명 |
---|---|
네트워크 |
|
PlayerDataClient |
|
마켓 클라이언트 |
|
LocalWalkJumpManager |
|
FarmManagerClient | 특정 Class.CollectionService 태그가 인스턴스에 적용되고 있고 이 인스턴스에 대한 행동을 나타내는 "구성 요소"를 생성합니다. 구성 요소는 클래스를 참조하여 클래스가 생성되고 제거되면 이 클래스를 |
UI 설정 |
|
FtueManagerClient |
|
캐릭터 질주 |
|
클라이언트-서버 통신
대부분의 Roblox 경험은 클라이언트와 서버 간의 통신의 일부를 포함합니다. 여기에는 클라이언트가 서버에서 특정 작업을 요청하고 서버가 클라이언트에 업데이트를 복제하는 것이 포함됩니다.
이 프로젝트에서 클라이언트-서버 통신은 최소화하여 사용 가능한 한 일반적으로 유지되도록 하려면 RemoteEvent 및 RemoteFunction 개체의 사용을 제한하여 특칭 규칙을 추적하기 위해 필요한 특칭 규칙의 수를 줄입니다. 이 프로젝트는 다음과 같은 메서드를 사용
- 복제는 player data system을 통해 수행됩니다.
- 특성을 통해 복제.
- 태그를 사용하여 복제 >.
- 메시징 직접 네트워크 모듈을 통해.
Player Data System을 통한 복제
플레이어 데이터 시스템은 플레이어가 세션 사이에 지속되는 동안 연관될 수 있는 데이터를 허용합니다. 이 시스템은 클라이언트에서 서버로 복제를 제공하고 클라이언트에서 변경 내용을 쿼리하고 서브 스크립트를 수신할 수 있는 세트의 API를 제공합니다. 이를 통해 플레이어 상태를 서버에
예를 들어, 클라이언트에게 코인이 얼마인지 알려주려면 사용자 정의 UpdateCoins``Class.RemoteEvent 를 실행하는 대신 다음을 호출하고 클라이언트가 이벤트 PlayerDataClient.updated 를 통해 구독할 수 있습니다.
PlayerDataServer:setValue(player, "coins", 5)
물론, 이것은 서버-클라이언트 복제와 세션 간에 유지하려는 값에 대해서만 유용하지만, 프로젝트의 놀라운 수의 경우에 적용됩니다. 여기에는 다음이 포함됩니다.
- 현재 FTUE 단계
- 플레이어의 인벤토리
- 플레이어가 가진 코인의 양
- 플레이어의 농장 상태
특성 기반 복제
특정 Instance에 대해 클라이언트에 복제할 사용자 정의 값을 필요로 하는 경우 특성 을 사용하십시오. Roblox는 속성 값을 자동으로 복제하므로 상태와 관련된 코드 경로를 유지할 필요가 없습니다. 또 다른 장점은 Roblox가 인스턴스
이 기능은 특히 런타임에 생성된 인스턴스에 유용하며, 새 인스턴스에 속성이 설정되어 있기 전에 데이터 모델에 아톰이 복제되면 인스턴스 자체에 아톰이 복제되는 경우 코드를 작성할 필요가 없습니다. 이렇게 하면 코드를 "대기"하기 위해 추가 데
클라이언트나 서버에서 데이터 모델에서 직접 특성을 읽을 수도 있습니다, GetAttribute() 메서드, GetAttributeChangedSignal() 메서드를 통해 변경 내용을 구독하고, 식물 Class.Instance:Get
태그를 사용하여 복제
CollectionService를 사용하면 문자열 태그를 Instance에 적용할 수 있습니다. 이를 통해 인스턴스를 범주하고 클라이언트에 그 범주를 복제할 수 있습니다.
예를 들어, CanPlant 태그는 서버에 적용되어 클라이언트에게 지정된 화분이 식물을 받을 수 있음을 나타냅니다.
네트워크 모듈을 통해 직접 메시징
이전 옵션이 적용되지 않는 상황에서는 네트워크 모듈을 통해 사용자 정의 네트워크 호출을 사용할 수 있습니다. 이는 클라이언트-서버 통신을 가능하게 하는 유일한 옵션이며, 따라서 클라이언트 요청을 전송하고 서버 응답을 수신하는 데 가장 유용합니다.
식물은 다양한 클라이언트 요청을 위해 직접 네트워크 호출을 사용합니다, 포함:
- 식물에 물주기
- 씨앗 심기
- 아이템 구매
이 접근 방식의 단점은 각 개별 메시지에 대한 일부 사용자 지정 구성이 프로젝트의 복잡성을 증가시킬 수 있지만, 이 점은 가능한 한 서버-클라이언트 통신에 대해서는 피하는 것이 좋습니다.
클래스 및 싱글턴
클래스는 식물 프로젝트에 있는 인스턴스처럼 생성 및 파괴할 수 있습니다. 클래스 구문은 Roblox의 인스턴스와 같이 개체 지향 프로그래밍에 대한 몇 가지 변경을 통해 엄격한 형식 검사 지원을 활성화합니다.
즉시 시작
프로젝트의 많은 클래스는 하나 이상의 Instances 와 연관되어 있습니다. 지정된 클래스의 개체는 new() 메서드를 사용하여 생성되며, Roblox에서 인스턴스를 생성하는 방식과 일치합니다. Instance.new() 를 사용하여 인스턴스를 생성하는 방식은 Roblox에서 사
이 패턴은 일반적으로 클래스가 데이터 모델에 물리적 표현이 있고 클래스가 기능을 확장할 때 사용되는 개체에 사용됩
해당 인스턴스
위에서 설명한 대로 이 프로젝트의 많은 클래스에는 데이터 모델 표현이 있으며, 클래스와 일치하고 클래스에 의해 조작되는 인스턴스입니다.
클래스 개체가 인스턴스를 만들 때 이러한 인스턴스를 만드는 것이 일반적인 경우, 코드는 일반적으로
구성
Lua에서 메타테이블을 사용하여 상속이 가능하지만, 프로젝트는 클래스가 서로 확장되도록 하기 위해 대신 구성 을 통해 클래스를 확장하도록 선택합니다. 클래스를 구성하는 경우 new() 개체는 1>새로1> 메서 인스턴스화되고
이 기능을 사용하는 예는 CloseButton 클래스로, Button 클래스를 감쌀 수 있습니다.
청소
Class.Instance:Destroy()|Destroy() 메서드로 클래스를 파괴하는 것과 유사하게, 클래스를 인스턴스화할 수 있는 클래스도 파괴될 수 있습니다. 프로젝트 클래스의 파괴자 메서드는
destroy() 메서드의 역할은 개체에 의해 생성된 인스턴스를 파괴하고 연결을 연결 해제하며 모든 자식 개체에서 destroy() 를 호출하는 것입니다. 이는 특히 연결에 중요합니다, 왜냐하면 활성 연결이 있는 인스턴스는 Lua 가비지 수집기에 의해 제거되지 않으며
싱글턴
싱글턴은, 이름에서 알 수 있듯이, Roblox하나의 개체만 존재할 수 있는 클래스입니다. 그들은 서비
싱글턴은 즉각적 클래스와 달리 new() 메서드가 없기 때문에 클래스로부터 직접 반환됩니다. 대신, 메서드와 상태가 함께 클래스 내에 반환되지 않고 클래스 외에 반환됩니다. �
엄격한 형식 유추
Luau는 부분 또는 모든 코드에 옵션 유형 정의를 추가할 수 있는 점진 입력을 지원합니다. 이 프로젝트에서는 모든 스크립트에 대해 strict 형식 체크를 사용합니다. 이는 Roblox의 스크립트 분석 도구에서 가장 권한이 적은 옵션이며 따라서 런타임
쿼리 클래스 구문
Lua에서 클래스를 생성하는 권장 접근 방식은 잘 문서화되어 있습니다. 하지만 강력한 Luau 입력에는 적합하지 않습니다. 강력한 Luau 입력에는 typeof() 메서드가 가장 간단한 접근 방식입니다.
type ClassType = typeof(Class.new())
이 작동은 하지만 클래스가 실행 시에만 존재하는 값으로 초기화되는 경우에는 매우 유용하지 않습니다. 예를 들어 Player 개체와 같은 경우 클래스 self 개체는 항상 해당 클래스의 인스턴스이므로 이 점은 형식 유추 엔진이 만들
엄격한 형식 유추를 지원하기 위해 Plant 프로젝트는 idiomatic Lua 클래스 구문과 여러 면에서 다른 솔루션을 사용하여 형식 유추를 지원합니다. 일부는 직관적이지 않을 수 있습니다.
- self의 정의는 형식 선언과 생성자에서 중복됩니다. 이렇게 하면 유지 관리 부담이 발생하지만 두 정의가 서로 동기화되지 않으면 경고가 표시됩니다.
- 클래스 메서드는 대시로 선언되므로 self 는 명시적으로 유형 ClassType 이 될 수 있습니다. 메서드는 여전히 콜론으로 명시적으로 호출할 수 있습니다.
--!엄격
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 의 값 형식이 좁혀지지 않습니다.
--!엄격
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
print(optionalParameter + 1)
end
이를 해결하기 위해 이 경비원의 유형이 명시적으로 캐스트된 후 새 변수가 생성됩니다.To mitigate this, new variables are created after these guards with their type explicitly cast.
--!엄격
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과 비교할 수 있습니다, 왜냐하면 그것은 사용자가 볼 내용을 설명하는 개체의 계층입니다. Roblox UI 생성 및 업데이트에 대한 접근 방식은 명령 및 선언 방식으로 나뉩니다.
접근 | 장점과 단점 |
---|---|
명령어 | 즉, 사용자 인터페이스는 사용자 코드 |
선언적 | 선언적 접근 방식에서는 사용자 인스턴스의 원하는 상태가 명시적으로 선언되며 이 상태의 효율적인 구현은 라이브 |
식물은 Roblox에서 UI를 직접 표시하는 것이 더 효과적인 개요에 따라 명령 접근 방식을 사용합니다. 이것은 선언적 접근 방식으로는 가능하지 않습니다. 일부 반복된 UI 구조 및 로직은 재사용 가능한 구성 요소로 표시됩니다.
고도 아키텍처
레이어 및 구성 요소
In Plant에서 모든 UI 구조는 Layer 또는 Component입니다.
- Layer는 프리프레임화된 UI 구조를 ReplicatedStorage 에 감싼 최상위 그룹으로 정의됩니다. 레이어는 구성 요소를 여러 개 포함하거나 전체 로직을 캡슐화할 수 있습니다. 레이어의 예는 인벤토리 메뉴 또는 코인 표시기 헤드업 디스플레이
- Component 는 재사용 가능한 UI 요소입니다. 새로운 구성 요소 개체가 인스턴스된 경우, 구성 요소는 ReplicatedStorage 에서 미리 정의된 템플릿을 클론합니다. 구성 요소는 자체에 다른 구성 요소를 포함할 수 있습니다. 구성 요소의 예는 일반적인 버튼 클래
처리 보기
일반적인 UI 관리 문제 중 하나는 뷰 핸들링입니다. 이 프로젝트에는 사용자 입력을 감지하고 관리하는 몇 가지 메뉴 및 HUD 항목이 있습니다. 표시 또는 활성화될 때 신중한 관리가 필요합니다.
Plant 은 이 문제를 UIHandler 시스템으로 접근하여 UI 레이어가 표시되어야 하거나 표시되지 않아야 하는 시점을 관리합니다. 게임의 모든 HUD 레이어는 0>0> HUD0> 또는 3>1> Menu1> 으로 분류되며 이 규칙은 다음과
- 메뉴 및 HUD 레이어의 활성화된 상태를 전환할 수 있습니다.
- 활성화된 HUD 레이어는 Menu 레이어가 활성화되지 않은 경우에만 표시됩니다.
- Menu 레이어는 스택에 저장되며, 한 번에 하나만 Menu 레이어가 표시됩니다. Menu 레이어가 활성화되면 스택의 앞에 삽입되며 표시됩니다. 1>Menu1> 레이어가 비활성
이 접근 방식은 메뉴가 기록과 함께 탐색할 수 있기 때문에 직관적입니다. 다른 메뉴에서 메뉴를 열면 새 메뉴를 닫으면 이전 메뉴가 다시 표시됩니다.
UI 레이어 신규 등록은 UIHandler 로 자동으로 수행되며, 신뢰성이 떨어지는 경우 신호를 제공합니다.
자세한 내용은
이 포괄적인 식물 프로젝트에 대한 전망에서, 관련 개념과 주제에 대해 더 자세히 살펴보기를 원할 수 있습니다.
- 클라이언트-서버 모델 — Roblox의 클라이언트-서버 모델에 대한 개요.
- Luau — Roblox 스크립트 언어 Lua 5.1 에서 파생된 세부 정보.
- 원격 이벤트 및 콜백 — 모든 것을 원격 네트워크 이벤트 및 콜백에 대해 설명합니다.
- UI — Roblox의 사용자 인터페이스 개체 및 디자인에 대한 자세한 내용.