移動世界を開発する

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

エクスペリエンス内のどの環境でも、移動を作成すると、環境音の木の動き、プレイヤーの相互作用からの反応ドア、または彼らにぶつかると動く箱など、世界にすぐにより没入感と現実感を感じるようになります。スタジオには、物理システム、TweenService、アニメーションなど、世界がより生き生きと感じるために動作を作成するためのユニークな方法がたくさんあります。エクスペリエンスの特定のニーズを分析することで、どれを使用するかを決定するのを助けることができます。このセクションでは、Studio で作成したい移動タイプを決定し、それらの異なる目標を達成するために使用したツールを示します。

嵐を作成する

嵐は、Duvall Drive の謎でライブになるものに決まるまで、多くの反復を経験しました。最初は、嵐を巨大なオブシディアンの柱として考え、後の反復では、それが破損した空間への巨大なポータルであると考えました。彼らにユニークな外見と感触を持つ多くの異なる嵐を試した後、我々は小さな中央の「目」を持つ嵐に落ち着いたのは、以下の理由からです:

  • 嵐は、このイベントが世界に及ぼす影響の 感覚をプレイヤーに与えるべき 、木が吹き飛ばされて飛散するゴミを含む。
  • クラウド自体の回転する渦巻きは、すべてを明らかにしないで、プレイヤーに中央ポータルを見せるべきです 。これはプレイヤーに、何が起こっているかをより近くで調査するように促すでしょう。
  • より緊密な光のポイントは、 家の構成に焦点を合わせる ことができ、これは主人公であり、ゲームプレイの大部分が位置する場所です。

嵐が環境内で動的で攻撃的で変化し続けるようにするには、次のシステムと機能を使用しました:

  1. TweenService - クラウド移動用。
  2. 照明の変更 - クラウド間のライトニングを作成するため。
  3. ビーム - 「ボリューム照明」とライトニングボルトのため。
  4. パーティクルエミッター - ポータルに向かって飛んで回転する廃棄物のために、風が吹くために飛行します。
  5. アニメーション - 風に吹かれていた木のため。

テクスチャで雲を追加

動的クラウド は、普通の、高高度のリアルな雲に最適ですが、ドラマチックで、より強く指向し、カスタマイズできるものが必要でした。これを行うために、表面の外観 オブジェクトを半透明で半重な雲メッシュのシリーズに適用し、雲のカバーを偽装しました。どうしてそれらを積み上げ、それらをこれほど強くレイヤー化したのですか?それは、各クラウドメッシュが異なる速度で移動すると、互いに交差し、お互いの内側と外側に雲の形を作り出すからです。このプロセスにより、雲は回転ディスクであるにもかかわらず、少しより動的で自然に感じられました。クラウドが 半透明 であることも重要でした、 porque queríamos que los jugadores pudieran ver algo brillante en el centro antes de llegar a la casa!

単一のクラウドメッシュ。
>

テクスチャなしのレイヤークラウドメッシュ!
>

各クラウドメッシュが家全体を完全に囲い、嵐の大きさを伝えるために巨大である必要があったので、個々のクラウドメッシュに使用したいテクスチャをタイル化して、メッシュの表面全体で大量に繰り返されるようにする必要があることを知っていました。私たちは、クラウド用に作った材料をこれらの単純なパーツにテストし、それらを渦に適用しました!

パーティクルエミッターやビームとは異なり、メッシュでは、それぞれのメッシュから光を反射させることができ、クラウド間のライトニングを実装したいときに重要でした。また、回転中に照明がそれから反跳して見えるようにモデル化しました。これは、特に経験のパフォーマンス要求が、表面の外観オブジェクトの品質レベルを低下させた状況で重要でした!

照明を追加し始めたら、メッシュに詳細を追加して照明により良く反応させる必要がありました!

クラウドメッシュを回転

クラウドの全体的な視覚的な外観に満足した後、それを動かす必要がありました!私たちは各クラウドレイヤーの一般的な形状を置いていましたが、回転効果が実際に良く見えるようにするために試行錯誤が必要でした。最初は 制約 を使用して、雲を物理的に動かす速度を紹介するように試みました。これは後で反復することを望んでいたよりも難しく、プレイヤーは決してそれと対話しませんので、移動で正確である必要はありませんでした。

クラウドや小さなランプのような、インタラクトできるのが難しかったり、ゲームプレイ/物理学にとって重要ではなかったりするインスタンスを回転させる簡単に使用できる方法を望んでいました。クライアント-サーバーバンド幅を減少し、スムーズな動作を可能にし、各クラウドメッシュが異なる回転速度と遅延を持つことを許可するため、LocalScript を使用することを決めました。より一般的にするために、回転軸を指定できるようにもしました。3つの属性を使用することは可能ですが、私たちの場合は3つの値を使用しました:AxisDelay、そしてSpeedです。

デモの多くの場合と同様、インスタンスタグ付きプラグインを使用して、Studio で影響を受けたインスタンスを管理できるよう、LocalSpaceRotation タグを使用しました。開発プロセス全体でタグ付きのインスタンスをすべて処理するために LocalScript を使用する唯一のシングルを使用し、開発プロセス中に多くのスクリプトを維持する必要がなかったため、開発プロセス全体で CollectionService を使用しました。

デモでは、世界の一部が必要に応じて ServerStorage にワークスペースにクローンされ、タグ付きのオブジェクトが作成および破棄された場合を処理する必要がありました。With LocalScripts では、メッシュとその子供の値がストリーミングされる可能性があるストリーミングにも注意する必要があります。最初に、Init() 関数に配置されたオブジェクトを処理し、CollectionService.GetInstanceAddedSignalCollectionService.GetInstanceRemovedSignal にタグ付きのオブジェクトを接続して、新たに作成/破棄されたオブジェクトを処理しました。同じ SetupObj 機能が使用されて、新しいオブジェクトを Init() および CollectionService.GetInstanceAddedSignal で初期化しました。


local function Init()
for _, obj in CollectionService:GetTagged("LocalSpaceRotation") do
if obj:IsDescendantOf(workspace) then
SetupObj(obj)
end
end
end
CollectionService:GetInstanceAddedSignal("LocalSpaceRotation"):Connect(function(obj)
objInfoQueue[obj] = true
end)
CollectionService:GetInstanceRemovedSignal("LocalSpaceRotation"):Connect(function(obj)
if objInfo[obj] then
objInfo[obj] = nil
if objInfoQueue[obj] then
objInfoQueue[obj] = nil
end
end
end)
Init()

objInfo は、回転速度や軸などすべての関連オブジェクトの情報を持つマップです。注: 我々はすぐに を呼び出さないが、 にオブジェクトを追加した。サーバー上のストリーミングとクローンオブジェクトを使用すると、 が呼び出されると、我々はまだ 、 、および の値を持っていないかもしれません、それで、オブジェクトをキューに追加し、 機能から次のフレームに呼び出され、値がそこにあるまで、それらを個々のオブジェクトの"情報"構造に読み込むことができました。

心拍に接続された Update 機能のインスタンスを回転させました。親変換 (parentTransform) を取得し、このオブジェクトの回転速度に基づいて新しい回転角 ( curObjInfo.curAngle ) を累積し、ローカル変換 ( rotatedLocalCFrame) ) を計算し、最後に CFrame に設定した。両親とオブジェクトの両方が Model または MeshPart であることに注意してください。そのため、IsA("モデル") をチェックし、PrimaryPart.CFrame または CFrame を使用する必要がありました。


local parentTransform
if parentObj:IsA("Model") then
if not parentObj.PrimaryPart then
-- メインパーツはまだストリーミングされていない可能性があります
continue -- メインパーツのレプリケーションを待つ
end
parentTransform = parentObj.PrimaryPart.CFrame
else
parentTransform = parentObj.CFrame
end
curObjInfo.curAngle += dT * curObjInfo.timeToAngle
local rotatedLocalCFrame = curObjInfo.origLocalCFrame * CFrame.Angles( curObjInfo.axisMask.X * curObjInfo.curAngle, curObjInfo.axisMask.Y * curObjInfo.curAngle, curObjInfo.axisMask.Z * curObjInfo.curAngle )
if obj:IsA("Model") then
obj.PrimaryPart.CFrame = parentTransform * rotatedLocalCFrame
else
obj.CFrame = parentTransform * rotatedLocalCFrame
end

ストリーミングを処理するために有効な Model.PrimaryPart を設定することをチェックしました。アップデートがオブジェクトで呼び出されている間、Model.PrimaryPart (子メッシュにポイントできる)がまだストリーミングされていない場合、アップデートをスキップします。現在のシステムはオブジェクト回転の第二回巡りであり、前のシステムは異なる方法で機能しました:値は 12 倍異なっていました!同じデータを維持するために、我々はスクリプトで「12 * obj.Speed.Value」のように変換しました。

デザインライトニングストライク

Studio は箱から出しのライトニングジェネレーターを提供しておらず、パーティクルシステムにはヒーローライトニングストライクには機能しない制限があったため、ヒーローライトニングストライクの解決策を考え出す必要がありました。稲妻を作るために、2つのメインシステムを決めました:嵐の目から来るヒーロー稲妻には、オーディオとポストプロセス効果を明らかにし、同期させる簡単なパーティクル効果があり、遠い雲から雲への稲妻のためのテクスチャ化ビームがスクリプトされています。

テクスチャビーム

通常、シーケンサーまたはタイムラインツールを使用して、照明ボルトストライク効果のタイミングを駆動しますが、Studio はまだこの機能を提供していないので、照明ボルトのタイミングを制御するスクリプトを書くことにしました。この効果のスクリプト化は比較的簡単ですが、次の重要な目標を達成します:

  1. テクスチャ、明るさ、遅延などのライトニングボルトストライクの要素は、すべてのストライクでランダム化されます。
  2. オーディオとポストFXの変更は、ストライクFXとシンクロしています。
  3. 室内にいるか汚れた領域にいるプレイヤーは、見たり聞いたりすることができません。

サーバー側の Script が、さまざまなパラメータとタイミングを計算し、すべてのクライアントに送り、ランダムな期間を待ちます:


local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end

内部の CreateFXData で、すべてのクライアントが同じパラメータを得るように情報構造を記入します。

クライアント側 ( LightningVFXClient ) では、このクライアントがFX を実行すべきかどうかをチェックします:


local function LightningFunc(info)
-- 室内ではFXなし
if inVolumesCheckerFunc:Invoke() then
return
end
-- 「普通」の世界にいないときの FX なし
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end

さらに、シーケンスを実行してテクスチャ、位置、明るさを設定し、ティーンを実行し、task.wait(number) を使用します。ランダムパラメータは、サーバーから受け取った情報構造から来ており、いくつかの数字は固定されています。


beam.Texture = textures[info.textIdx]
beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)
-- ワイプ
beam.Brightness = 10
ppCC.Brightness = maxPPBrightness
ppBloom.Intensity = 1.1
bottom.Position = top.Position
tweenBrightness:Play()
tweenPPBrightness:Play()
tweenPPBrightness:Play()
tweenBottomPos:Play()
tweenBrightness.Completed:Wait()
-- オーディオ
if audioFolder and audioPart then
if audioFolder.Value and audioPart.Value then
audioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)
end
end
task.wait(info.waitTillFlashes)
-- and so on

プレイヤーが室内にいるかどうかをチェックするには、事前配置された室内エリアに近いボリュームを越えるヘルパー機能 inVolumesCheckerFunc を使用し、プレイヤーの位置がそれらのいずれかの内部にあるかどうかをチェックします (PointInABox)。タッチベースの検出を使用できたかもしれませんが、プレイヤーがボリューム内に座ると、ボリュームにもう「触れて」いないことが分かりました。数個の箱に点をテストするのはより簡単で、プレイヤーが以前にテストされた位置から十分に移動したときにのみ行います。

プレイヤーが破損領域にいるかどうかをチェックするには、現在のゲーム状態をチェックするヘルパーgameStateInfoFunc機能を呼び出します。フォルダからランダムなサウンドを再生するには、ヘルパーPlayOneShot機能も使用しました。ライトニングボルト自体に関しては、Photoshop で作成するのが超簡単でした; 揺れる線を描き、「外部輝き」レイヤー効果を追加しました。

粒子エミッターシステムを使用する

ヒーローライトニングストライクは、背景に雲の層をキャッチして光を遠隔攻撃、またはクラウド間照明から受け取る印象を作成して、遠隔ライトニングを示唆するパーティクルシステムによってサポートされています。我々は、主な嵐雲の周辺に雲の看板をフラッシュする非常に単純な粒子システムを通じて、この効果を達成しました。システムは、ランダム化された透明曲線で定期的に雲の粒子を放出します:

木を風に吹かせる

クラウドとライトニングが私たちが望んでいた方法で機能し始めた後、私たちは風と雨という他の主要な要素を追加する必要がありました:私たちが物理と特殊効果システムの現在の制限内で作業する必要がありました! これらの要素はいくつかの課題を提示し、Studioの物理システムと特殊効果システムの現在の制限内で作業する必要がありました。たとえば、実際の風で木を動かすことは現在のエンジンでは不可能ですので、木のために粒子エミッター効果とカスタムキャラクターアニメーションを使用しました。

風と雨の効果を本当に売る必要があると知っていましたが、木自体が移動する必要がありました。エンジン内でこれを行う方法はいくつかあります。公開されている プラグイン を使用してパーツを移動したり、 を使用してモデルを直接アニメーションしたり、公開されている プラグイン を使用してこれを行うこともできます。目的に合わせて、アニメーションは私たちが木から望む動作を制御する機能を提供し、経験内のすべての木に共有できる単一のアニメーションを使用できるようにしました。

私たちは エンドースモデルパック - 森のアセット から複数の木をスキン化して開始しました。これらのツリーが既に存在し、私たちの経験が太平洋北西で起こったので、各ツリーモデルを作成する必要がある時間を早く節約できました。

フォレストパックには、自分の経験で時間を節約できる複数のツリータイプが含まれています。

私たちが木を選んだ後、私たちはそれらをスキンにする必要があると知っていました。メッシュのスキン化 は、BlenderMaya などの別の3Dモデリングアプリケーションにメッシュにジョイント(または骨)を追加し、それらのジョイント/骨に影響を適用してメッシュを移動する行為です。これは最も一般的に ヒューマノイドキャラクター で使用されますが、カスタムキャラクター で、ほぼすべてのものをスキンできます。

時間を節約して同じアニメーションを再使用したいと知っていたので、最初のツリーリグを作成し、ジョイント名が一般的であることを確認しました。これらの同じ名前を他のツリーのためのリグで使用したいからです。また、幹が風に吹かれて枝が揺れ、葉が揺れているように見えるために、主要、次要、および三次元の関節/骨を含める必要があることも知っていました。このプロセスのためには、 セカンダリモーション を作成する必要がありました、これは、どのアクションによってオブジェクトの他の部分がそのアクションに反応し、最初の動作に追いつくように見えるアニメーションコンセプトです。

木には、プライマリ、セカンダリ、およびテーシャリの接合があるため、風に吹かれて移動できるように信頼できるものがあります。

関節/骨を作成したら、Studio のすべての関節と骨を動かしてテストアニメーションを作成し、希望通りに動いているかどうかを確認する時が来ました。これを行うには、我々は ツリーを Studio にインポートする必要がありました 通じて カスタムリグ 設定で、 3D インポーター を使用してメッシュを移動/アニメーションし、アニメーションエディター を使用してアニメーションを動かしました。テストの後、材料とテクスチャを設定しましたが、以下の結果を見ることができます。

Studio 内の同じ階層。

そのツリーでの結果に満足した後、異なるツリーで同じアニメーションをテストする時が来ました! すべてのツリータイプの間で同じアニメーションが行われると知っていたので、すでに高いレッドウッドと頑丈なビーチウッドの間で動作するのに十分なアニメーションのように見えるようにしました!

レッドウッドの木にインポートしたアニメーション。

これを行うには、その森パックからビーチウッドの木を取り、同じ名前のジョイントを使用して同様の装備を構築しました。これは以前にインポートしたアニメーションがこのツリーにも適用できるようにするためでした。アニメーションはすべて回転する関節に基づいていたので、木がどれほど大きく、小さく、高く、広いかは関係なかった!

ビーチウッドの木は、同じジョイントの名前が同じであり、同じ量ではありません。これはアニメーションシステムが名前に一致する特定の接合部にのみアニメーションを適用するため、問題ありません!この理由により、接合部の名前に一致するものに同じアニメーションを適用できました!

ビーチウッドの木を装備とスキンした後、それをインポートして同じアニメーションを適用できました。これは、1つのファイルでのループと編集が必要であり、エクスペリエンスを実行するときに少ないアニメーションでパフォーマンスが保存されることを意味しました。

アニメーションエディタを使用して、同じレッドウッドの木のアニメーションをビーチウッドの木に適用できました!

私たちが望んでいたすべてのツリータイプをアニメーションにしたら、それぞれを パッケージ にして、メインのエリアの経験の周りの複数のアニメーションを再編集して更新できるようにしました。パフォーマンスコストがかかることを知っていたので、効果が最も価値があった家の周りでは、彼らを惜しみなく使用しました!将来、これがよりパフォーマントになるにつれて、スキンされたメッシュインスタンスをどんどん追加できるようになります!

渦が最も強かった家の周りのすべてのアニメーションツリーをすぐに使用し、プレイヤーにとってビジュアル効果が最も影響力があるようにしました。

嵐の破片を作る

雨を重いものにし、霧と廃棄物が木を通り抜けるようにしたかった。これを行うには、大きな嵐雲のすぐ下に子供の パーティクルエミッター として動作するいくつかの見えないパーツを設定しました。Studio のパーティクルカウント制限のため、全体の空間に一つのパーティクルエミッターを使用できませんでした。代わりに、プレイ可能な領域スペースのグリッドパターンで同じサイズのものを複数追加しました、因為樹木の存在は、プレイヤーが非常に遠くを見ることができないことを意味します。

複数のボリュームを使って、雨の量と、我々が望む雨の特定のカバーを両方手に入れました。

雨粒子は、新しい粒子エミッタープロパティ ParticleEmitter.Squash を利用して、粒子を長くするか、スクワットさせることができます。雨に特に役立つのは、大きな雨のテクスチャは必要ないということで、そこにあるものを単に伸ばすだけでよかったからです。ただし、ParticleEmitter.Squash の値を増やすと、全体の ParticleEmitter.Size プロパティも増やさなければならないため、あまりにも痩せすぎることがないようになる!全体的に、私たちが十分な雨を得るまで遊んでいた値を変更するだけだったが、経験の視覚をブロックしなかった!

スクワッシュ値 3 が長くなってテクスチャを伸ばし始めます。
>

スクワッシュ値 20は、パーティクルをはるかに長く伸ばしますが、サイズ値も増やさなければなりませんでした。
>

霧、霧、葉が通り抜けるためには、一度に多くの粒子を走らせる必要がなかったので、少数の領域をカバーする単一の大きなパーツボリュームを追加するのがはるかに簡単でした。最初にボリュームを設定し、必要な場所の粒子の頻度を取得しました。

パーティクルパートボリュームがいくつか出てしまったので、家にパーティクルが入っていなかったし、霧のように木を通り抜ける必要がないと感じなかった。
>

霧粒子パーツのボリュームは、粒子が大きかったので、場所にこだわる必要がなかった。
>

その後、葉の吹き出しと風のテクスチャを作り、粒子をすべて異なる速度で回転/移動させ、異なる速度で開始しました。これは、より大きな霧粒子がより自然に相互作用し、特にサイズが大きいため、繰り返しテクスチャのように見えないことを意味しました。

霧粒子
>

葉のパーティクル
>

結果は、木が移動し、窓が吹き、雷がストームの中央の目を囲む効果を作成する間の素晴らしいアクションでした。

嵐の目を設定する

輝くコアを備えた破片の石の眼は、プレイヤーに家で起こっている何か悪質で秘儀的なことについての最初のヒントを与えることを目的としています。シーンが暗く、目が空に高いので、信頼できる割れた石のシルエットを作成することが重要でしたが、プレイヤーがそれを見ることができないので、信頼できる石の表面詳細を作成することは重要ではありませんでした。不必要な詳細に多くの時間を費やす前に、シーンの照明内でプレイヤーが見るのが現実的なものを知ることで、開発プロセスで多くのリソースを節約できます。

シーンの最終照明を早期に設定すると、不必要な作業を大幅に削減できます。シーンの最終照明でリングの表面詳細を見ることができないので、それらを配置する時間を費やす必要はありません!

プレイヤーからの距離は、目の表面の詳細については通常のマップに完全に頼ることができるため、メッシュが単な球面に過ぎないことを意味しました!詳細を高ポリメッシュに彫り込み、通常のマップを低ポリ球に焼き付けて、巨大なパフォーマンスコストなしですべての美しい詳細を得ることができました。

高ポリスクルプチャー
>

低ポリメッシュ
>

低ポリメッシュは、高ポリの彫刻で焼き上げられた普通の情報を持っています

目に超自然的な感覚を加え、その存在を強調するために、その割れ目を通り抜ける輝く、ネオンマグマを作成することにしました。表面の外外見に放射チャネルはないものの、我々は 2 つの球を使って眼を作り、岩だらけの外表面と輝くマグマの 2 番目の、少し小さいものを克服しました。In サブスタンスペイナー で、透明な領域で内部コアが通過するようにしたい領域のためのベースカラーテクスチャを作成しました。In ブレンダー で、我々は「ベクスパイントペイント」で内部球を安価で簡単に色変化させる方法で「vertex painted」しました。

内部球に描かれた頂点画像。目の周りで最も軽いグラデーションを作成して、より大きな深度と視覚的興味を提供しました。

目を作成するときに直面したもう一つの課題は、ストリーミング と眼のプレイヤーからの距離を組み合わせた使用によって課されました。この構造の中心性を考えると、距離にもかかわらず常に表示されることを望みましたが、メッシュにハックを加えることなく、プレイヤーはソラリウムにいない限り、眼を見ることができませんでした。目とそのリングに少しの幾何学を追加することで、目の場面での定期的な存在を強制できました。この幾何学は、地形の表面のすぐ下に座っており、エンジンを誘惑して、球がプレイヤーにより近いと思わせ、常にそれをストリーミングするのに十分です。これは、多くの大きなオブジェクトをストリーミングすることでメリットが無効になり、ゲームのパフォーマンスに悪影響を及ぼす可能性があるため、かなり控えめに行うべきです。

私たちは、クラウドメッシュを回転させるために使用していた同じスクリプトを使用して、目とそのリングに動きを追加できました。最後のタッチでは、雲の外の別の世界の存在にヒントを追加することにしましたが、場面により多くの幾何学を追加し、ストリーミングによって引き起こされた以前に述べた障害にも対処するために、創造的なアプローチを取らなければならなかったのです。我々は、オブジェクトの相対的なサイズと距離により多くの深さを持つシーンを作成し、このシーンの画像をレンダリングし、その画像を嵐の目のすぐ後ろに配置された部品にデカールとして使用しました。この部分を回転させるための同じ方法を、目とそのリングに使用したものと同じように使用しました。

雲の向こうの世界の幻を作成するために使用した画像。プレイヤーが何かから遠く離れているとき、単純な画像で十分であり、シーンでより深く複雑な印象を作成できるかもしれません!

拡張パントリを作成する

生産するのに最も楽しいものの 1つは、文字通り周りを変更することで、プレイヤーの現実への期待を覆すことができる破損したスペースでした。たとえば、父親のパズルでは、どれほど速く走っても関係なく、部屋がどんどん長くなっているような瞬間を再現したかった。我々は、プレイヤーが部屋を通常の状態に戻すための材料を探している間に逃げる拡張パントリーを作ることにしました。

これは、壁の簡単な動きと、パントリーの両側に表示される我々の部屋の賢明なレイアウトで設定しました。ルームの通常状態では、パントリーは単な廊下でしたが、破損したスペースでは、実際には複数の翼と偽の壁でより長くなっていました!

キッチンパントリーの破損状態。
>

プレイヤーから離れて移動する偽の壁。
>

偽の壁は、プレイヤーがトリガーボリュームに入った瞬間に戻るモデルグループで、以前パントリーで透明な部分だったものでした。そのトリガーは、すべてのドアで使用されるものと同じようなスクリプトでも使用され、TweenService を呼び出して、1つの目標から別の目標に移動しました。パーツボリュームを使って、壁の開始と終了位置がどこにあるかを伝える昇降操作を行いました。

パーツボリュームは、その後ろに偽の壁をトリガーして、その終了ポイントに移動します。この画像で黄色い色合いで表示されます。
Target_Closed は、回転するべき場所のすべてのドアに使用した一般的な目標パーツでした。ここでは、廊下の壁に行く場所を伝えるために再利用されました。

TweenService はそのような一般的なシステムなので、すべての壁データモデルに含まれなければならないのは同じコンポーネントだけでした。たとえば、一般的な「Door_Script」スクリプトは、「Grow_Wall」モデルの下で定義されたサウンドを再生します。同じスクリプト、次のコードサンプルのいくつかの修正で、食堂が移動するときにオーディオもトリガーされました。これは動きに多くのものを追加しました!


local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local model = script.Parent
local sound = model.Sound.Value
local trigger = model.Trigger
local left = model.TargetL_Closed
local right = model.TargetR_Closed
local tweenInfo = TweenInfo.new(
model.Speed.Value, --ドアの挟み時間/速度
Enum.EasingStyle.Quart, --スタイルを緩和する
Enum.EasingDirection.InOut, --緩和方向
0, --繰り返しカウント
false, --真逆にする
0 --遅延
)
local DoorState = {
["Closed"] = 1,
["Opening"] = 2,
["Open"] = 3,
["Closing"] = 4,
}
local doorState = DoorState.Closed
local playersNear = {}
local tweenL = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Open.CFrame})
local tweenR = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Open.CFrame})
local tweenLClose = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Closed.CFrame})
local tweenRClose = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Closed.CFrame})
local function StartOpening()
doorState = DoorState.Opening
sound:Play()
tweenL:Play()
tweenR:Play()
end
local function StartClosing()
doorState = DoorState.Closing
--モデル["ドア"]:Play()
tweenLClose:Play()
tweenRClose:Play()
end
local function tweenOpenCompleted(playbackState)
if next(playersNear) == nil then
StartClosing()
else
doorState = DoorState.Open
end
end
local function tweenCloseCompleted(playbackState)
if next(playersNear) ~= nil then
StartOpening()
else
doorState = DoorState.Closed
end
end
tweenL.Completed:Connect(tweenOpenCompleted)
tweenLClose.Completed:Connect(tweenCloseCompleted)
local function touched(otherPart)
if otherPart.Name == "HumanoidRootPart" then
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player then
--print("タッチ")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end

偽の壁が部屋の後ろに移動し始めたとき、残りのコンテンツもそれと一緒に移動する必要がありました。そのためには、食堂のすべてのゆるいアイテムが移動するときに壁に接着する必要がありました。接着制限 を使用すると、すぐにすべてのオブジェクトをパントリーの壁に接着して、単一のオブジェクトとして移動できました。これを行うと、これらのアイテムを焼き溶かして、プレイヤーがぶつかってノックすることができるようになりました!

壊れたツリーハウスを作る

スタジオは、振動ゲートから回転プラットフォームまで、あらゆるものを作成できる素晴らしい物理ベースのエンジンです。デモでは、物理を使用して、実際には非現実的なセットの環境にリアリズムの感覚を作成したいと考えました。幾つかの 制約 だけを使用すると、自分の経験内で楽しく挑戦的な障害コースを作成できます!

制約 は物理ベースのモーターのグループで、オブジェクトを整列し、動作を制限します。たとえば、ロッド制限 を使用して、オブジェクトを固定距離に保ち、またはロープ制限 でランプを線の端に吊るすことができます。プレイヤーが研究の破損状態に移送される息子のパズルでは、世界を文字通り反転させたかったのです。そうすると、プレイヤーの現実への期待とそこにあるルールが破壊され、物理システムは意図したまま使用され続けます!

息子のパズルは、同じ部屋にいるプレイヤーから始まりましたが、すべて横向きでした。

プレイヤーがパズルのメインエリアまで下がったとき、Roblox で見覚えのある光景に出会いました:障害物コースです。この特定の障害物コースは、複数の回転プラットフォームと回転壁、およびストーリーを進めた「安全エリア」で構成されていました。回転/スピン要素に焦点を合わせます。

マインドを捉える外観は、ここのゲームプレイが非常に簡単であることを隠しました。

なぜここで制約を使用したのですか? TweenService または他の方法では、プレイヤーがそれらの上に立っている間移動しないからです。プレイヤーを移動させるオブジェクトがなければ、誰かがプラットフォームに飛び乗り、その下から回転してしまう可能性があります。代わりに、プレイヤーが次のものにジャンプするようにしながら、回転プラットフォームを通過することを望んでいました。このアプローチのため、プレイヤーはコースを進む方法に関する決定を下しながら、どこに根を下ろしたかを感じ、回転する表面で移動するために特別なことをする必要はありませんでした!

障害物コースをナビゲートしようとしている間、友達が回転しているのを見ることもできます。

これを行うには、まず現在のキットからアセットを使用し、ビジュアル効果のための新しいコンテンツを追加する必要がありました。祖母がツリーハウスを建てた物語を伝えるため、不完全な壁やプラットフォームをいくつか作りましたが、その中に穴がありました。ユニークなプラットフォームの群を作りたくなかったので、4つの異なるベースピースとレーリングピースを別々に作りました。これにより、個々のベースとレールの部品をミックスしてマッチさせることで、多様性を持つことができました。

制約を使用していたので、これらのメッシュをアンカーできないと知っていました、なぜなら制約/モーターが運転していても移動しないからです。プラットフォームが世界から消えないようにするために、何かに固定される必要のある制約。これを解決するために、 モーター_アンカー という部分を作り、プラットフォームの全体の動作を駆動するためのヒンジ制限を持っていました。その後、2つのメッシュが1つとして移動する必要があったので、 モーター_ターン という名前のパーツを作成し、2つのメッシュを接着しました。この方法では、制約は複数のパーツで作動する複数のヒンジとは異なり、単一のパーツで作動できます。

今はヒンジ制約自体の実際の動作を設定し、部品と制約のオリエンテーションと制約を共に行う添付ファイルを追加する時間でした。歩道ピースが接着されたモーター_ターンにターニングアタッチメントを配置し、モーター_アンカー自体のアンカー動作用の別のアタッチメントをヒンジ制約の隣に配置しました。これはプレイヤーに影響を受けるのではなく、独所有に回転する必要があったので、ドアヒンジのように制限を自動車のように扱うため、HingeConstraint.ActuatorTypeモーター に設定し、制限を自動車のように扱いました。

プラットフォームを一定の速度で回転させるために、HingeConstraint.AngularVelocityHingeConstraint.MotorMaxAcceleration、およびHingeConstraint.MotorMaxTorqueプロパティを、プレイヤーがそれに飛び乗った場合に移動を許可し、中断を防ぐ値に設定します。

添付ファイル 0 は基本的にヒンジのアンカーであり、添付ファイル 1 はヒンジ自体を表しました。ヒンジが常に回転していましたが、ドアのヒンジ制約も使用できます。

今、回転する壁を作る必要がありました。壁は彼らの明らかな中心で回転する必要があり、レベルの残りの部分に関連するどんな方向性も処理できるようにしたいと知っていました。プラットフォームと同様、これらを構築して、すべての壁が未固定でモーター_ターンに接着しました。

パフォーマンスを節約するために、実際のツリーハウスメッシュの多くを再使用したいと考えたので、プラットフォームと同じような道を歩みました。いくつかの壁タイプが作られ、異なる組み合わせで一緒にくっつけることができました。

ベースの材料に変化を加えるために、Texture オブジェクトを SurfaceAppearance オブジェクトの上に使用しました。テクスチャ、デカルと同様、メッシュの平面に画像を配置できます。これは、ブリックの壁に汚れを加えたり、同じベースの木材を使用して木材を古く見せたい場合に役立つかもしれません。 オブジェクトは、タイルして画像をオフセットできるので、オーバーレイテクスチャをスケールできるようにしたいけれども、繰り返しても気にしない場合とは少し異なる動作をします!

似た動作とヒンジ制約の設定、および Texture オブジェクトを使用した方法を両方見ることができます。

いくつかのプラットフォームと回転壁をテストし、そして障害物コースが挑戦的で、思考を刺激し、またプレイヤーが行く必要がある場所を明確にすることを確認したら、いくつかの変化を加えてそれらをプレイしました!それらをうまく実行するために、両方の値と位置を調整する必要がありました。プラットフォームや壁が互いに衝突したり、周囲に衝突したりしている複数の場所があったが、移動したり頻繁にテストしたりして、デモに持っている設定に着地できた!

物理オブジェクトが何を攻撃しているかわからない場合は、3Dビューポートの右上隅の ビジュアライゼーションオプション ウィジェットから コリジョン精度 を切り替えることができます。

A close up view of the 3D viewport with the Visualization Options button indicated in the upper-right corner.

衝突ビジュアライゼーションが無効になっていると、ゲーム内で表示される通常の幾何学表現を見ることができます。
>

衝突ビジュアライゼーションが有効になっているとき、ツリーの葉が衝突していないので、回転プラットフォームや壁に干渉しません。
>

ドア/窓の下には見えますが、サブパネルのような小さな詳細は見えません。これは、壁の CollisionFidelity プロパティが ボックス に設定されたためです。これらのパネルに精度は必要ないので、パフォーマンスコストを節約するために、プレイヤーが飛び込むのに十分な詳細でした。プラットフォームと回転壁が完了したので、箱やランプなどの詳細アセットを追加するだけで、プレイできるようになりました!