植物 は、プレイヤーが種を植えて水やりをし、後で結果の植物を収穫して販売できる参考経験です。

プロジェクトは、Roblox でエクスペリエンスを開発するときに出会う可能性のある一般的な使用ケースに焦点を当てています。適用可能な場合、トレードオフ、コンプロミス、およびさまざまな実装オプションの理由に関するノートを見つけることができますので、自分の経験に最適な決定を下すことができます。
ファイルを取得する
- ナビゲート to the 植物 エクスペリエンスページ。
- クリックする ⋯ ボタンと Studio で編集 。
ケースの使用
植物 は次の使用ケースをカバーします:
- セッションデータとプレイヤーデータの持続性
- UIビュー管理
- クライアント-サーバーネットワーク
- 最初のユーザーエクスペリエンス (FTUE)
- ハードとソフトの通貨購入
また、このプロジェクトは、多くの経験に適用できるより狭い問題のセットを解決します:
- プレイヤーと関連する場所のエリアのカスタマイズ
- プレイヤーキャラクターの移動速度を管理する
- 文字を追跡するオブジェクトを作成する
- キャラクターが世界のどの部分にいるかを検出する
このエクスペリエンスには、小さすぎる、狭すぎる、または興味深いデザインチャレンジに対する解決策を示さない複数の使用ケースがあります;これらはカバーされていません。
プロジェクトの構造
エクスペリエンスを作成するときの最初の決定は、主に プロジェクト をどのように構造化するかを決定することで、これには特定のインスタンスを データモデル に配置する場所と、クライアントとサーバーコードの両方の入力ポイントを整理および構造化する方法が含まれます。
データモデル
次の表は、データモデルインスタンス内のコンテナサービスが配置される順序を説明します。
サービス | インスタンスの種類 |
---|---|
Workspace | 3D 世界を表す静的モデルを含み、特にプレイヤーに属していない世界の部分を表します。実行時にこれらのインスタンスを動的に作成、変更、または破棄する必要はありませんので、ここに置いておくことが許容されます。: 空の Folder もあり、プレイヤーの農場モデルが実行時に追加されます。 |
Lighting | 大気と照明の効果。 |
ReplicatedFirst | 必要な最小のインスタンスのサブセットを含み、ロード画面を表示し、ゲームを初期化する必要があります。ReplicatedFirst に配置されるインスタンスの数が多いほど、ReplicatedFirst でコードが実行される前に再複製するのに時間がかかります。: インスタンス フォルダには、ロード画面 GUI が存在します。: ソース フォルダには、ロードスクリーンコードとゲームの残りをロードするのに必要なコードが存在します。The 3> 6> は、プロジェクトのすべてのクライアント側コードの読み込むり口です。8> 7> 0> |
ReplicatedStorage | クライアントとサーバーの両方でアクセスが必要なすべてのインスタンスのストレージコンテナとして機能します。: プロジェクトに使用されているサードパーティライブラリが、 依存性 フォルダに存在します。: インスタンス フォルダには、ゲーム内のさまざまなクラスによって使用されるプレファブリケイテッドインスタンスの広範囲が存在します。: ソース フォルダには、クライアントとサーバーの両方からアクセスする必要があるロードプロセスに必要なすべてのコードが存在します。 |
ServerScriptService | プロジェクト内のすべてのサーバー側コードの入り口として機能する Script を含みます。 |
ServerStorage | クライアントにレプリケートする必要がないすべてのインスタンスのストレージコンテナとして機能します。: インスタンス フォルダには、ファーム モデルのテンプレートが存在します。これのコピーは、プレイヤーがゲームに参加すると 4> に配置され、すべてのプレイヤーにレプリケートされます。: ソース フォルダには、サーバーに限定されたすべてのコードが存在します。8> 7> |
SoundService | ゲーム内でサウンド効果に使用される Class.Sound``Class.SoundService 以下、これらの Sound オブジェクトには位置がなく、3D 空間でシミュレートされません。 |
入り口点
ほとんどのプロジェクトは、コードを全体のコードベースにわたってインポートできる再利用可能な ModuleScripts 内で整理します。ModuleScripts は再利用可能ですが、自所有で実行しません; Script または LocalScript によってインポートする必要があります。多くの Roblox プロジェクトには、ゲーム内の動作または特定のシステムに関連する大量の Script および LocalScript オブジェクトがあり、複数の入り口が生成されます。
For the 植物 microgamコード, a different approach is implemented through a sinコードle LocalScript that is the entry point for all client code, and a single Script that is the entry point for all server code.プロジェクトの正しいアプローチは要件によって異なりますが、単一の入り口は、システムが実行される順序に対するより多くの制御を提供します。
次のリストは、両方のアプローチのトレードオフを説明します:
- 単一の Script と単一の LocalScript が、それぞれサーバーとクライアントコードをカバーします。
- すべてのコードが単一のスクリプトから初期化されるため、異なるシステムの順序をより制御できます。
- システム間で参照でオブジェクトをパスできます。
高レベルシステムアーキテクチャ
プロジェクトのトップレベルシステムは以下で詳細に説明されています。これらのシステムの一部は、他のシステムよりも大幅に複雑であり、多くの場合、機能は他のクラスの階層を介して抽象化されます。

これらのシステムのそれぞれは、関連するクライアントまたはサーバーの start スクリプトによって代わりに初期化される非インスタント化可能なクラスです。このガイドの後半で シングルトンパターン についてもっと読むことができます。
サーバー
次のシステムがサーバーに関連しています。
システム | 説明 |
---|---|
ネットワーク |
|
プレイヤーデータサーバー |
|
市場 |
|
衝突グループマネージャー |
|
FarmManagerServer |
|
プレイヤーオブジェクトコンテナ |
|
タグプレイヤー |
|
FtueManagerServer |
|
キャラクタースポーナー |
|
クライバー
次のシステムがクライアントと関連しています。
システム | 説明 |
---|---|
ネットワーク |
|
プレイヤーデータクライアント |
|
マーケットクライアント |
|
ローカルウォークジャンプマネージャ |
|
ファームマネージャクライアント |
|
UI設定 |
|
FtueManagerClient |
|
キャラクタースプリント |
|
クライアント-サーバー通信
ほとんどの Roblox の経験には、クライアントとサーバーの間のコミュニケーションの要素が含まれています。これには、サーバーが特定のアクションを実行し、クライアントに更新をレプリケートするクライアントのリクエストが含まれます。
このプロジェクトでは、特別なルールを追跡するための量を減らすために、RemoteEvent および RemoteFunction オブジェクトの使用を制限して、クライアント-サーバー通信を可能な限り一般的なものとします。このプロジェクトは、次の順序で以下のメソッドを使用します:
- プレイヤーデータシステム を介したレプリケーション。
- 属性 を介したレプリケーション。
- タグ を介したレプリケーション。
- メッセージング 直接 ネットワークモジュールを介して。
プレイヤーデータシステムを介したレプリケーション
プレイヤーデータシステム は、保存セッション間に持続するプレイヤーにデータを関連付けることを許可します。このシステムは、クライアントからサーバーへのレプリケーションと、データをクエリーして変更をサブスクライブするために使用できる API セットを提供し、サーバーからプレイヤー状態に変更をレプリケーションするのに最適です。
たとえば、クライアントにコインの数を伝えるために特注の UpdateCoins RemoteEvent を発射するのではなく、クライアントに次のものを呼び出して、PlayerDataClient.updated イベントを通じてサブスクライブさせることができます。
PlayerDataServer:setValue(player, "coins", 5)
もちろん、これはサーバーからクライアントへのレプリケーションと、セッション間で持続したい値にのみ有用ですが、プロジェクト内の驚くほど多くのケースに適用されます:
- 現在の FTUE ステージ
- プレイヤーのインベント持ち物リスト
- プレイヤーが持っているコインの量
- プレイヤーの農場の状態
属性を介したレプリケーション
サーバーが特定の Instance に関連するカスタム値をクライアントに複製する必要がある状況では、属性 を使用できます。Roblox は属性値を自動的に複製するので、オブジェクトに関連付けられた状態を複製するためのコードパスを維持する必要はありません。もう一つの利点は、このレプリケーションがインスタンス自体と並行して行われることです。
これは、データモデルに親になる前に新しいインスタンスに設定された属性が、インスタンス自体とともにアトミックに複製されるため、実行時に作成されたインスタンスに特に役立ちます。これにより、追加のデータが RemoteEvent または StringValue を介してレプリケートされるのを「待つ」ためにコードを書く必要がなくなります。
クライアントまたはサーバーから、GetAttribute() メソッドでデータモデルの属性を直接読み込み、GetAttributeChangedSignal() メソッドで変更を購読できます。植物 プロジェクトでは、このアプローチは、他のことのうち、植物の現在の状態をクライアントにレプリケートするために使用されます。
タグを介したレプリケーション
CollectionService は、Instance にストリングタグを適用できます。これは、インスタンスをカテゴリ化し、そのカテゴリ化をクライアントにレプリケートするのに便利です。
たとえば、CanPlant タグは、サーバーに適用されて、クライアントに指定の鍋が植物を受け取ることができることを示します。
ネットワークモジュールを介して直接メッセージ Message directly via network module
前のオプションのどれも適用しない状況では、 ネットワーク モジュールを通じてカスタムネットワーク呼び出しを使用できます。これはプロジェクト内でクライアントからサーバーへの通信を許可する唯一のオプションであり、それゆえクライアントのリクエストを送信し、サーバーの返答を受けるのに最も有用です。
植物 は、包括: 多様なクライアントリクエストのための直接ネットワーク呼び出しを使用します:
- 植物に水をやる
- 種を植える
- アイテムの購入
このアプローチの欠点は、それぞれの個々のメッセージにはプロジェクトの複雑さを増やす可能性のあるカスタム設定が必要であることですが、可能な限り回避されており、特にサーバーからクライアントへの通信ではそうです。
クラスとシングルトン
プラント プロジェクトのクラス、Roblox(ロブロックス)blox のインスタンスのように、作成および破壊できます。クラスの構文は、オブジェクト指向プログラミング に数多くの変更を加えることで、厳格な型チェック をサポートするようにイディオム的な Lua アプローチに触発されています。
インスタント化
プロジェクトの多くのクラスは、1つまたは複数の Instances に関連しています。指定されたクラスのオブジェクトは、 メソッドを使用して作成され、Roblox でインスタンスを作成する方法と一致します。
このパターンは、クラスがデータモデルに物理的な表現を持ち、クラスが機能を拡張するオブジェクトで一般的に使用されます。良い例は BeamBetween で、2つの指定された Beam オブジェクトの間に Attachment オブジェクトを作成し、ビームが常に上を向いているようにそれらの付属物を向きを保持します。これらのインスタンスは、ReplicatedStorage または new() のプレファブリケーションバージョンからクローンされ、self のオブジェクト内に保存されました。
対応するインスタンス
上記で述べたように、このプロジェクトの多くのクラスには、クラスに対応し、それによって操作されるインスタンスのデータモデル表現があります。
クラスオブジェクトがインスタンス化されるときにこれらのインスタンスを作成するのではなく、コードは一般的に Clone() プレファブリケ版の Instance を ReplicatedStorage または ServerStorage に保存されたものを選択します。これらのインスタンスのプロパティをシリアライズし、クラスの new() 関数で新規作成しても、そうするとオブジェクトの編集が非常に面倒になり、読者が解析するのが困難になります。さらに、インスタンスをクローンすることは、新しいインスタンスを作成して実行時にそのプロパティをカスタマイズするより一般的に速い操作です。
構成
ルアウでは、メタテーブルを使用して相続が可能ですが、プロジェクトは代わりにクラスが コンポーネント を通じて互いに拡張できるようにすることを選択します。組み合わせを通じてクラスを結合すると、「子」オブジェクトはクラスの new() メソッドでインスタンス化され、 self メンバーとして含まれます。
アクションの例は、CloseButton クラスで Button クラスを包むクラスを参照してください。
片づける
メソッドで破壊できるように、インスタンス化できるクラスも破壊できます。プロジェクトクラスの破壊者メソッドは、 で、コードベースのメソッドの一貫性と、プロジェクトのクラスと Roblox インスタンスの区別を目的として、小文字の を使用します。
destroy() メソッドの役割は、オブジェクトによって作成されたすべてのインスタンスを破壊し、接続を切断し、子オブジェクトすべてに destroy() を呼び出すことです。これは特に接続に重要であり、インスタンスにアクティブな接続が残っていない場合でも、インスタンスまたは接続への参照が残っていない場合でも、インスタンスのゴミ収集機がクリーンアップされないためです。
シングルトン
名前の通り、シングルトンは、1つのオブジェクトのみが存在できるクラスです。プロジェクトの Roblox の サービス に相当するものです。シングルトンオブジェクトへの参照を保存し、ルアウコードで回転させるのではなく、植物は、返された値をキャッシュする必要があることを利用しています。これは、同じシングルトン ModuleScript を異なる場所から一貫して要求すると、同じ返されたオブジェクトが提供されることを意味します。このルールの例外は、異なる環境(クライアントまたはサーバー)が ModuleScript にアクセスした場合だけです。
シングルトンは、new() メソッドを持っていないことにより、不安定なクラスから区別されます。代わりに、オブジェクトとそのメソッドと状態は、ModuleScript を介して直接返されます。シングルトンがインスタンス化されないため、self 構文は使用されず、メソッドはコロン(.)ではなくドット(:)で呼ばれます。
厳格なタイプ推定
Luau は、段階的なタイプ入力をサポートしており、オプションのタイプ定義をいくつかまたはすべてのコードに追加できます。このプロジェクトでは、strict 型チェックがすべてのスクリプトに使用されます。これは Roblox のスクリプト分析ツールで最も寛容なオプションであり、実行時にタイプエラーをキャッチする可能性が最も高いです。
タイプされたクラスの構文
Lua でクラスを作成する既存のアプローチは よく文書化されている が、強力な Luau タイプには向いていません。Luau では、クラスのタイプを取得する最も簡単な方法は typeof() メソッドです:
type ClassType = typeof(Class.new())
これは機能しますが、クラスが実行時にのみ存在する値で初期化されるとき、たとえば Player オブジェクトではあまり役に立ちません。さらに、idiomatic Lua クラスの構文で仮定されているのは、クラス self にメソッドを宣言することは、常にそのクラスのインスタンスになるという仮定です;これは、タイプ推論エンジンが行うことができる仮定ではありません。
厳密な型推論をサポートするために、プラント プロジェクトは、いくつかの方法で idiomatic Lua クラスのシンタックスと異なるソリューションを使用し、そのうちのいくつかは非直感的に感じるかもしれません:
- self の定義は、タイプ宣言とコンストラクタでそれぞれ複製されます。これは維持可能性の負担を紹介しますが、2つの定義が相互に同期しなくなると警告がマークされます。
- クラスメソッドはドットで宣言されるため、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
これを軽減するために、これらのガードの後、そのタイプを明示的にキャストする新しい変数が作成されます。
--!厳格な
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
local parameter = optionalParameter :: number
print(parameter + 1)
end
データモデルの階層を横断する
いくつかの場合、コードベースは、実行時に作成されるオブジェクトのツリーのデータモデル階層を通過する必要があります。これはタイプチェックにおける興味深い課題を提示します。執筆時には、一般的なデータモデル階層をタイプとして定義することはできません。結果として、データモデル構造で利用可能な唯一のタイプ情報がトップレベルインスタンスのタイプである場合があります。
このチャレンジに対する 1つのアプローチは、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 を作成および更新する方法は、 命令型 と 宣言型 の緩やかな分離によって広く分類されます。
手法 | 利点と欠点 |
---|---|
命令 | 絶対的アプローチでは、UI は RoblRoblox(ロブロックス)x の他のインスタンス階層と同じように扱われます。UI 構造は、Studio で実行時前に作成され、データモデルに追加され、通常直接 StarterGui に追加されます。それから、実行時にコードは、クリエイターが必要とする状態を反映するために、UI の特定の部分を操作します。: このアプローチにはいくつかのメリットがあります。Studio で UI をゼロから作成し、データモデルに保存できます。これは、UI の作作品を加速できるシンプルでビジュアルな編集エクスペリエンスです。必須の UI コードは、変更する必要があるものだけを関心しているため、簡単な UI 変更を実装するのも簡単です。: 注目すべき欠点は、必須の UI アプローチでは、状態が変換の形で手動で実装する必要があるため、状態の複雑な表現を見つけるのが非常に困難になり、デバッグするのが困難になることです。強制的な UI コードを開発する際にエラーが発生するのは一般的で、特に状態と UI が予期せぬ順序で同期を解除するための複数のアップデートによって同期が解除されるときです。: 強制的なアプローチでもう一つの課題は、UIを一度宣言して再利用できる意味のある構成要素に分解するのが難しいということです。UI ツリー全体が編集時に宣言されるため、一般的なパターンはデータモデルの複数の部分で繰り返される可能性があります。 |
宣言的 | 宣言的アプローチでは、UIインスタンスの望ましい状態が明示的に宣言され、この状態の効率的な実装は、Roact または Fusion などのライブラリによって抽象化されます。: このアプローチのメリットは、状態の実装が単純化され、UIがどのように見えるかを説明するだけで十分であることです。これにより、バグの発見と解決が大幅に簡素化されます。: キーの欠点は、コードで全体の UI ツリーを宣言する必要があることです。Roact や Fusion のようなライブラリには、これを簡素化するための構文がありますが、UI を構成するときの編集プロセスはまだ時間がかかり、編集エクスペリエンスは少し少なくなっています。 |
植物 は、UI が Roblox で作成され、操作される方法を直接示すことで、より効果的な概要を提供するという概念の下で 絶対命令 アプローチを使用します。宣言的アプローチではこれは可能ではありません。一部の繰り返し UI 構造とロジックは、命令的な UI デザインで一般的な落とし穴を避けるために、再利用可能な コンポーネント に抽象化されます。
高レベルアーキテクチャ

レイヤーとコンポーネント
In 植物 , すべての UI 構造は Layer または Component です。
- Layer は、プレファブリケの UI 構造を ReplicatedStorage で包むトップレベルグループ化シングルトンとして定義されています。レイヤーには複数のコンポーネントが含まれるか、または独自のロジックを完全にカプセル化することもできます。レイヤーの例は、インベントリメニューまたはヘッドアップディスプレイのコイン数インジケータです。
- Component は再利用可能な UI 要素です。新しいコンポーネントオブジェクトがインスタンス化されると、ReplicatedStorage からプレファブリケーションされたテンプレートをクローンします。コンポーネントは、自体に他のコンポーネントを含むことができます。コンポーネントの例は、一般的なボタンクラスまたはアイテムのリストのコンセプトです。
ハンドリングを表示
一般的な UI 管理問題はビュー処理です。このプロジェクトには、メニューや HUD アイテムの範囲があり、そのうちの一部はユーザーの入力を聞き、表示または有効化時間を注意深く管理する必要があります。
植物 は、UIレイヤーが表示されるべきかどうかを管理する UIハンドラー システムでこの問題に対処します。ゲームのすべてのUIレイヤーは、HUD または Menu と分類され、視認性は次のルールによって管理されます:
- Menu および HUD レイヤーの有効状態を切り替えることができます。
- 有効になっている HUD レイヤーは、Menu レイヤーが有効になっていない場合にのみ表示されます。
- 有効になったレイヤーはスタックに保存され、一度に表示されるレイヤーは1つのみです。Menu レイヤーが有効になると、スタックの前に挿入されて表示されます。Menu レイヤーが無効になると、スタックから削除され、次に有効な Menu レイヤーがキューに表示されます。
このアプローチは直感的であるため、メニューを履歴でナビゲートできます。1つのメニューが別のメニューから開かれた場合、新しいメニューを閉じると、古いメニューが再び表示されます。
UIレイヤーのシングルトンは、 UI処理者 と登録し、ビジビリティが変更されるときに発射するシグナルを提供されます。
さらに読む
植物 プロジェクトのこの徹底した概観から、関連する概念やトピックについてさらに深く探検する次のガイドを探索したいかもしれません。
- クライアント-サーバーモデル — RoblRoblox(ロブロックス) のクライアント-サーバーモデルの概要
- Luau — Roblox が作成したスクリプト言語 Luau の詳細、Lua 5.1 から派生したスクリプト言語。
- リモートイベントとコールバック — クライアントサーバー境界を超えて通信するリモートネットワークイベントとコールバックに関するすべて
- UI — Roblox のユーザーインターフェイスオブジェクトとデザインの詳細