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

이 프로젝트는 Roblox에서 게임을 개발할 때 마주칠 수 있는 일반적인 사용 사례에 중점을 두고 있습니다. 적용 가능한 경우, 다양한 구현 선택의 트레이드오프, 타협 및 근거에 대한 노트를 찾아볼 수 있어, 자신의 게임에 가장 적합한 결정을 내릴 수 있습니다.
파일 가져오기
- 식물 게임 페이지로 이동합니다.
- ⋯ 버튼을 클릭하고 Studio에서 편집합니다.
사용 사례
식물은 다음과 같은 사용 사례를 다룹니다:
- 세션 데이터 및 플레이어 데이터 지속성
- UI 뷰 관리
- 클라이언트-서버 네트워킹
- 첫 사용자 경험 (FTUE)
- 하드 및 소프트 통화 구매
또한, 이 프로젝트는 많은 게임에 적용 가능한 더 좁은 문제 집합을 해결합니다. 여기에는 다음이 포함됩니다:
- 플레이어와 연관된 장소의 특정 영역 사용자 정의
- 플레이어 캐릭터의 이동 속도 관리
- 캐릭터를 따라다니는 객체 생성
- 캐릭터가 있는 세계의 부분 감지
이 게임에는 너무 작거나 틈새가 있거나 흥미로운 디자인 문제에 대한 해결책을 보여주지 않는 여러 사용 사례가 있다는 점에 유의하십시오. 이러한 사항은 다루지 않습니다.
프로젝트 구조
게임을 만들 때 첫 번째 결정은 프로젝트를 어떻게 구조화할 것인지입니다. 이는 주로 특정 인스턴스를 데이터 모델에 배치하는 위치와 클라이언트 및 서버 코드의 진입점을 어떻게 구성하고 구조화할 것인지에 관한 것입니다.
데이터 모델
다음 표는 데이터 모델 인스턴스가 배치되는 컨테이너 서비스에 대해 설명합니다.
| 서비스 | 인스턴스 유형 |
|---|---|
| Workspace | 플레이어에 속하지 않는 세계의 부분을 포함하는 3D 세계를 나타내는 정적 모델을 포함합니다. 런타임에서 이러한 인스턴스를 동적으로 생성, 수정 또는 파괴할 필요가 없으므로 여기에 두는 것이 허용됩니다. 런타임에 플레이어의 농장 모델이 추가될 빈 Folder도 있습니다. |
| Lighting | 대기 및 조명 효과. |
| ReplicatedFirst | 로딩 화면을 표시하고 게임을 초기화하는 데 필요한 최소한의 인스턴스 하위 집합을 포함합니다. ReplicatedFirst에 배치된 인스턴스가 많을수록 ReplicatedFirst의 코드가 실행되기 전에 복제되는 대기 시간이 길어집니다.
|
| ReplicatedStorage | 클라이언트와 서버 모두에서 액세스가 필요한 모든 인스턴스의 저장 컨테이너 역할을 합니다.
|
| ServerScriptService | 프로젝트의 모든 서버 측 코드에 대한 진입점 역할을 하는 Script를 포함합니다. |
| ServerStorage | 클라이언트에 복제할 필요가 없는 모든 인스턴스의 저장 컨테이너 역할을 합니다.
|
| SoundService | 게임의 사운드 효과에 사용되는 Sound 객체를 포함합니다. SoundService 아래의 이러한 Sound 객체는 위치가 없으며 3D 공간에서 시뮬레이션되지 않습니다. |
진입점
대부분의 프로젝트는 전체 코드베이스에서 가져올 수 있는 재사용 가능한 ModuleScripts 내에 코드를 구성합니다. ModuleScripts는 재사용 가능하지만 스스로 실행되지 않으며 Script 또는 LocalScript에 의해 가져와야 합니다. 많은 Roblox 프로젝트에는 게임의 동작이나 특정 시스템과 관련된 많은 Script 및 LocalScript 객체가 있어 여러 진입점을 생성합니다.
식물 마이크로게임의 경우, 모든 클라이언트 코드에 대한 진입점 역할을 하는 단일 LocalScript와 모든 서버 코드에 대한 진입점 역할을 하는 단일 Script를 통해 다른 접근 방식이 구현됩니다. 프로젝트에 대한 올바른 접근 방식은 요구 사항에 따라 다르지만, 단일 진입점은 시스템이 실행되는 순서에 대한 더 큰 제어를 제공합니다.
다음 목록은 두 접근 방식의 트레이드오프를 설명합니다:
- 단일 Script와 단일 LocalScript가 각각 서버 및 클라이언트 코드를 다룹니다.
- 모든 코드가 단일 스크립트에서 초기화되기 때문에 서로 다른 시스템이 시작되는 순서에 대한 더 큰 제어를 제공합니다.
- 시스템 간에 객체를 참조로 전달할 수 있습니다.
고수준 시스템 아키텍처
프로젝트의 최상위 시스템은 아래에 자세히 설명되어 있습니다. 이러한 시스템 중 일부는 다른 시스템보다 상당히 복잡하며, 많은 경우 그 기능은 다른 클래스의 계층 구조에 걸쳐 추상화됩니다.

이러한 시스템 각각은 "싱글톤"으로, 인스턴스화할 수 없는 클래스이며, 관련 클라이언트 또는 서버 start 스크립트에 의해 초기화됩니다. 싱글톤 패턴에 대한 자세한 내용은 이 가이드의 나중 부분에서 읽을 수 있습니다.
서버
다음 시스템은 서버와 관련이 있습니다.
| 시스템 | 설명 |
|---|---|
| 네트워크 |
|
| PlayerDataServer |
|
| 마켓 |
|
| CollisionGroupManager |
|
| FarmManagerServer |
|
| PlayerObjectsContainer |
|
| TagPlayers |
|
| FtueManagerServer |
|
| CharacterSpawner |
|
클라이언트
다음 시스템은 클라이언트와 관련이 있습니다.
| 시스템 | 설명 |
|---|---|
| 네트워크 |
|
| PlayerDataClient |
|
| MarketClient |
|
| LocalWalkJumpManager |
|
| FarmManagerClient |
|
| UISetup |
|
| FtueManagerClient |
|
| CharacterSprint |
|
클라이언트-서버 통신
대부분의 Roblox 게임은 클라이언트와 서버 간의 통신 요소를 포함합니다. 여기에는 클라이언트가 서버에 특정 작업을 수행하도록 요청하고 서버가 클라이언트에 업데이트를 복제하는 것이 포함될 수 있습니다.
이 프로젝트에서는 RemoteEvent 및 RemoteFunction 객체의 사용을 제한하여 클라이언트-서버 통신을 가능한 한 일반적으로 유지합니다. 이 프로젝트는 다음 방법을 사용하며, 선호하는 순서로 나열됩니다:
- 플레이어 데이터 시스템을 통한 복제.
- 속성을 통한 복제.
- 태그을 통한 복제.
- 네트워크 모듈을 통한 직접 메시징.
플레이어 데이터 시스템을 통한 복제
플레이어 데이터 시스템은 플레이어와 연관된 데이터를 저장하여 저장 세션 간에 지속됩니다. 이 시스템은 클라이언트에서 서버로의 복제를 제공하며, 데이터를 쿼리하고 변경 사항에 구독할 수 있는 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 아래의 멤버로 포함됩니다.
이것이 작동하는 예로는 Button 클래스를 래핑하는 CloseButton 클래스가 있습니다.
정리
Instance를 Destroy() 메서드를 사용하여 파괴할 수 있는 것처럼, 인스턴스화할 수 있는 클래스도 파괴할 수 있습니다. 프로젝트 클래스의 소멸자 메서드는 destroy()이며, 이는 코드베이스의 메서드 간의 camelCase 일관성을 유지하고 프로젝트의 클래스와 Roblox 인스턴스를 구별하기 위해 소문자로 작성됩니다.
destroy() 메서드의 역할은 객체에 의해 생성된 모든 인스턴스를 파괴하고, 모든 연결을 끊고, 자식 객체의 destroy()를 호출하는 것입니다. 이는 활성 연결이 있는 인스턴스는 인스턴스나 인스턴스에 대한 연결에 대한 참조가 없더라도 Luau 가비지 수집기에 의해 정리되지 않기 때문에 특히 중요합니다.
싱글톤
싱글톤은 이름에서 알 수 있듯이 단 하나의 객체만 존재할 수 있는 클래스입니다. 이는 프로젝트의 Roblox 서비스와 동등합니다. 싱글톤 객체에 대한 참조를 저장하고 Luau 코드에서 전달하는 대신, 식물은 ModuleScript를 요구할 때 반환된 값을 캐시하는 사실을 활용합니다. 이는 서로 다른 장소에서 동일한 싱글톤 ModuleScript를 요구할 때 일관되게 동일한 반환 객체를 제공합니다. 이 규칙의 유일한 예외는 서로 다른 환경(클라이언트 또는 서버)이 ModuleScript에 접근하는 경우입니다.
싱글톤은 new() 메서드가 없다는 점에서 인스턴스화 가능한 클래스와 구별됩니다. 대신, 객체와 그 메서드 및 상태는 ModuleScript를 통해 직접 반환됩니다. 싱글톤은 인스턴스화되지 않으므로 self 구문이 사용되지 않으며, 메서드는 대신 점(.)으로 호출됩니다.
엄격한 유형 추론
Luau는 점진적 타이핑을 지원하므로 일부 또는 모든 코드에 선택적 유형 정의를 추가할 수 있습니다. 이 프로젝트에서는 모든 스크립트에 대해 strict 유형 검사를 사용합니다. 이는 Roblox의 스크립트 분석 도구에 대한 가장 제한적인 옵션이며, 따라서 런타임 전에 유형 오류를 포착할 가능성이 가장 높습니다.
유형이 지정된 클래스 구문
Lua에서 클래스를 생성하는 기존 접근 방식은 잘 문서화되어 있습니다. 그러나 이는 강력한 Luau 타이핑에 잘 맞지 않습니다. Luau에서 클래스의 유형을 얻는 가장 간단한 방법은 typeof() 메서드입니다:
type ClassType = typeof(Class.new())이것은 작동하지만, 클래스가 런타임에만 존재하는 값으로 초기화될 때 유용하지 않습니다. 예를 들어 Player 객체와 같은 경우입니다. 또한, 관용적인 Lua 클래스 구문에서 self에 메서드를 선언하면 항상 해당 클래스의 인스턴스가 될 것이라는 가정이 있습니다. 이는 유형 추론 엔진이 가정할 수 없는 것입니다.
엄격한 유형 추론을 지원하기 위해, 식물 프로젝트는 관용적인 Lua 클래스 구문과 여러 면에서 다른 솔루션을 사용합니다. 그 중 일부는 직관적이지 않을 수 있습니다:
- self의 정의가 유형 선언과 생성자 모두에 중복됩니다. 이는 유지 관리 부담을 초래하지만, 두 정의가 서로 동기화되지 않으면 경고가 표시됩니다.
- 클래스 메서드는 점으로 선언되므로 self를 ClassType 유형으로 명시적으로 선언할 수 있습니다. 메서드는 여전히 예상대로 콜론으로 호출할 수 있습니다.
--!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이 접근 방식의 문제는 가독성에 영향을 미친다는 것입니다. 대신, 프로젝트는 데이터 모델 계층을 탐색하기 위해 내부적으로 any로 캐스트하는 일반 모듈인 getInstance를 사용합니다.
local function enableVendor(vendor: Model)
local zonePart: BasePart = getInstance(vendor, "ZonePart")
end타입 엔진의 데이터 모델 이해가 발전함에 따라, 이러한 패턴이 더 이상 필요하지 않을 가능성이 있습니다.
사용자 인터페이스
식물은 복잡하고 간단한 2D 사용자 인터페이스를 다양하게 포함합니다. 여기에는 동전 카운터와 같은 비대화형 헤드업 디스플레이(HUD) 항목과 상점과 같은 복잡한 대화형 메뉴가 포함됩니다.
UI 접근 방식
Roblox UI는 HTML DOM과 느슨하게 비교할 수 있습니다. 이는 사용자가 무엇을 보고 있어야 하는지를 설명하는 객체의 계층 구조이기 때문입니다. Roblox UI를 생성하고 업데이트하는 접근 방식은 대체로 명령형 및 선언형 관행으로 나뉩니다.
| 접근 방식 | 장점 및 단점 |
|---|---|
| 명령형 | 명령형 접근 방식에서는 UI가 Roblox의 다른 인스턴스 계층 구조처럼 취급됩니다. UI 구조는 런타임 전에 Studio에서 생성되어 데이터 모델에 추가되며, 일반적으로 StarterGui에 직접 추가됩니다. 그런 다음 런타임에서 코드는 생성자가 요구하는 상태를 반영하기 위해 UI의 특정 부분을 조작합니다. 이 접근 방식에는 몇 가지 장점이 있습니다. Studio에서 UI를 처음부터 생성하고 데이터 모델에 저장할 수 있습니다. 이는 UI 생성 속도를 높일 수 있는 간단하고 시각적인 편집 경험입니다. 명령형 UI 코드는 변경해야 할 사항에만 관심을 가지므로 간단한 UI 변경을 쉽게 구현할 수 있습니다. 주목할 만한 단점은 명령형 UI 접근 방식이 변환의 형태로 수동으로 구현된 상태를 요구하므로, 상태의 복잡한 표현을 찾고 디버깅하기가 매우 어려워질 수 있다는 점입니다. 명령형 UI 코드를 개발할 때 오류가 발생하는 것은 일반적이며, 특히 상태와 UI가 여러 업데이트로 인해 예상치 못한 순서로 상호 작용하여 비동기화될 때 그렇습니다. 명령형 접근 방식의 또 다른 문제는 UI를 의미 있는 구성 요소로 분해하기가 더 어렵다는 것입니다. 전체 UI 트리가 편집 시간에 선언되기 때문에, 일반적인 패턴이 데이터 모델의 여러 부분에서 반복될 수 있습니다. |
| 선언형 | 선언형 접근 방식에서는 UI 인스턴스의 원하는 상태가 명시적으로 선언되며, 이 상태의 효율적인 구현은 Roact 또는 Fusion과 같은 라이브러리에 의해 추상화됩니다. 이 접근 방식의 장점은 상태 구현이 사소해지고 UI가 어떻게 보이기를 원하는지를 설명하기만 하면 된다는 것입니다. 이는 버그를 식별하고 해결하는 것을 상당히 쉽게 만듭니다. 주요 단점은 전체 UI 트리를 코드로 선언해야 한다는 것입니다. Roact 및 Fusion과 같은 라이브러리는 이를 쉽게 만들기 위한 구문을 제공하지만, 여전히 시간이 많이 소요되는 과정이며 UI를 구성할 때 직관적이지 않은 편집 경험이 될 수 있습니다. |
식물은 변환을 직접 보여주는 것이 Roblox에서 UI가 생성되고 조작되는 방식을 보다 효과적으로 개요할 수 있다는 생각 하에 명령형 접근 방식을 사용합니다. 이는 선언형 접근 방식으로는 불가능합니다. 반복되는 UI 구조와 논리의 일부는 재사용 가능한 구성 요소로 추상화되어 명령형 UI 디자인의 일반적인 함정을 피합니다.
고수준 아키텍처

레이어 및 구성 요소
식물에서 모든 UI 구조는 Layer 또는 Component입니다.
- Layer는 ReplicatedStorage에 있는 프리패브 UI 구조를 래핑하는 최상위 그룹 싱글톤으로 정의됩니다. 레이어는 여러 구성 요소를 포함할 수 있으며, 자체 논리를 완전히 캡슐화할 수도 있습니다. 레이어의 예로는 인벤토리 메뉴 또는 헤드업 디스플레이의 동전 수 표시기가 있습니다.
- Component는 재사용 가능한 UI 요소입니다. 새 구성 요소 객체가 인스턴스화될 때, ReplicatedStorage에서 프리패브 템플릿을 복제합니다. 구성 요소는 그 자체로 다른 구성 요소를 포함할 수 있습니다. 구성 요소의 예로는 일반 버튼 클래스 또는 항목 목록의 개념이 있습니다.
뷰 처리
일반적인 UI 관리 문제는 뷰 처리입니다. 이 프로젝트에는 사용자 입력을 듣는 여러 메뉴와 HUD 항목이 있으며, 이들이 언제 보이거나 활성화되는지를 신중하게 관리해야 합니다.
식물은 UI 레이어가 보이거나 보이지 않아야 할 때를 관리하는 UIHandler 시스템을 통해 이 문제에 접근합니다. 게임의 모든 UI 레이어는 HUD 또는 Menu로 분류되며, 이들의 가시성은 다음 규칙에 의해 관리됩니다:
- Menu 및 HUD 레이어의 활성화 상태는 전환할 수 있습니다.
- 활성화된 HUD 레이어는 활성화된 Menu 레이어가 없을 때만 표시됩니다.
- 활성화된 Menu 레이어는 스택에 저장되며, 한 번에 하나의 Menu 레이어만 표시됩니다. Menu 레이어가 활성화되면 스택의 맨 앞에 삽입되어 표시됩니다. Menu 레이어가 비활성화되면 스택에서 제거되고 대기열에서 다음 활성화된 Menu 레이어가 표시됩니다.
이 접근 방식은 메뉴를 탐색할 수 있는 이력을 허용하므로 직관적입니다. 한 메뉴에서 다른 메뉴를 열면 새 메뉴를 닫을 때 이전 메뉴가 다시 표시됩니다.
UI 레이어 싱글톤은 UIHandler에 자신을 등록하고 가시성이 변경되어야 할 때 신호를 제공합니다.
추가 읽기
식물 프로젝트에 대한 이 철저한 개요를 통해, 관련 개념 및 주제에 대해 더 깊이 탐구할 수 있는 다음 가이드를 살펴보시기 바랍니다.
- 클라이언트-서버 모델 — Roblox의 클라이언트-서버 모델 개요.
- 원격 이벤트 및 콜백 — 클라이언트-서버 경계를 넘어 통신하기 위한 원격 네트워크 이벤트 및 콜백에 대한 모든 것.
- UI — Roblox의 사용자 인터페이스 객체 및 디자인에 대한 세부 정보.