ドティ・ラン
Doty Runは、ダイナミックなゲームプレイ、プロシージャル生成、そして革新的なメカニクスを特徴とするエンドレスランナーゲームです。 冒険心あふれるドティの導きに従い、プレイヤーはコイン、障害物、急な曲がり角が待ち受ける氷の道を進みながら、高得点を狙います。 Doty Runは、Niantic Studioが複雑でインタラクティブなウェブゲームを開発する能力を際立たせています。
サンプルプロジェクトで自分好みにカスタマイズしましょう。

Player Controller
Learn how to implement a player controller that responds to input actions and touch gestures.
View sample project
Gyroscope Controller
Master gyroscope-based tilt controls for intuitive and immersive gameplay.
View sample projectBehind the Build: Doty Run

Written by Camilo Medina
December 4, 2024
Introduction
Doty Runは、プレイヤーが勇敢な冒険者Dotyを操作し、氷点下の道を駆け抜けながら貴重なDotyコインを集める、興奮のエンドレスランニングゲームです。 目標は? できるだけ長く生き残り、できるだけ遠くまで進み、できるだけ多くのコインを集めてハイスコアを更新しよう。
スマホを左右に傾けてドティを左右に移動させ、上下左右にスワイプしてジャンプ、ロール、または回転させよう。 PCユーザーの場合、移動は矢印キーで操作します。 氷の道を navigation し、障害物を避け、道をたどって冒険を続けよう。 すべてのプレイで最高のスコアが保存されます—それぞれの冒険が、
でそのスコアを更新するチャンスです! どこまで行けるでしょうか?
Project Structure
3D シーン
- GameManager: ゲームの状態とスコアを管理する
ゲームマネージャースクリプト
を含みます。 - GameUI: UIを担当するエンティティを含みます。
- Start:
ui-start
スクリプトを含みます。 - Gameplay:
ui-gameplay
スクリプトを含みます 。
- GameOver:
ui-game-over
スクリプトを含みます。
- Start:
- Environment: ゲームの環境用3Dモデルを含みます 。
- Fog: シーン内の
霧効果を管理するfog-controller
スクリプトを含みます 。
- Sfx:背景音楽用のオーディオコンポーネントと、
サウンド効果を管理するsfx-managerスクリプト
を含みます - PathRoot: 実行時に生成されるパス要素のルート親です。
パス移動スクリプト
を含みます - PathPool: ゲームプレイ
- 中に 利用可能な子要素からパスオブジェクトを動的に作成
- Base: プレイヤーが左右中央の3つの位置に移動できるベースタイルの3Dモデルです。 メッシュと物理コライダーを含む
- 部分的: 部分的なタイル3Dモデルで、プレイヤーは左、中央、または右の1つの位置に移動できます。 メッシュと物理コライダーを含みます。
- コーナー: パスの方向が変わるコーナータイルの3Dモデル。 メッシュと物理コライダーを含む
- Arc: 弧形の障害物3Dモデル。 メッシュと物理コライダーを含む
- SmallObstacle: 小さな障害物の3Dモデル、条件に応じてアクティブになります。 メッシュと物理コライダーを含む
- LargeObstacle: 大型障害物3Dモデル、条件に応じてアクティブになります。 メッシュと物理コライダーを含みます。
- コイン: コインの3Dモデル。 メッシュと物理コライダーを含む、イベント専用に設定されたコライダーセット
- Player: 物理コライダーとすべてのプレイヤー関連スクリプトを含む:
player-controller
、player-collision
、input-manager
、gyro-controller
、player-animator
、およびtouch-input-controller
- Doty: Dotyの3Dモデルで、プレイヤーのすべての動作用のメッシュとアニメーションを含む。
- カメラ: ゲームカメラを含みます
- アンビエントライト: ゲームのアムビエントライトを含みます
- ディレクショナルライト: ゲームの方向性ライトを含みます
・生成するためのパスマネージャースクリプトとオブジェクトスプナー
を含みます
資産
3Dモデル
- 環境
- env-1.glb: メイン環境の3Dモデル
- オブジェクト
- coin_1.glb: 収集アイテムとして使用されるコインの3Dモデル
- doty_coin_gold_v2.glb: コインモデルの代替バージョン。
- ice_large.glb: 大型の氷の障害物の3Dモデル。
- ice_small.glb: 小型の氷の障害物の3Dモデル。
- パス
- arc.glb: アーチ構造の3Dモデル。
- base_v1.glb: ベースタイルの3Dモデル。
- base_v2.glb: ベースタイルの代替3Dモデル。
- corner.glb: コーナータイルの3Dモデル。
- partial.glb: ベース部分タイルの3Dモデル。
プレイヤー
- doty_opti_web_anim.glb: ウェブパフォーマンス向けに最適化されたプレイヤーキャラクターの3Dモデルで、アニメーションが設計されています。
サウンドエフェクト (SFX)
- coin.mp3: コインを収集した際に再生されるサウンドエフェクト。
- Fast_Lane_Fun.mp3: テンポの速いゲームプレイシーケンス用の背景音楽。
UI
- ゲームオーバー
- game_over_background.jpg: ゲームオーバー画面に表示される背景画像。
- restart_button.png: ゲームを再起動するためのボタン画像。
- ゲームプレイ
- coin_icon.png: UI用のコインアイコン。
- スタート
- start_background.jpg: スタート画面に表示される背景画像。
- start_button.png: ゲームを開始するためのボタン画像。
スクリプト
これは、このプロジェクト内のスクリプトの簡単な概要です。
app.js
: この .js ファイルはウェブアプリケーションのメインエントリポイントとして機能し、コア機能の実装に使用できます。 ここでは、グローバルなCSSスタイルとモジュール化されたCSSスタイル(utilities.css
、start.css
、gameplay.css
、およびgame-over.css
)をインポートし、ゲームUIのビジュアルデザインとレイアウトを定義しています。 さらに、UIで一貫したスタイリングを実現するために使用するフォントを注入します。
game-manager.ts
: ゲームのコア状態(開始
、ゲームプレイ
、ゲームオーバー
)を管理し、プレイヤーのスコア計算を処理し、
のハイスコアの保存と取得を管理します。
utilities.js
:THREE
ライブラリへのアクセスが必要なコアメカニクスを実装します。例えば、ジャイロスコープへのアクセスを要求する関数などです。 これらのユーティリティは、Studio スクリプトから分離して管理されており、ECS ライブラリとの衝突を回避するためです。
カメラ
camera-follow-player.ts:
シンプルな三人称カメラシステムを実装します。 スキーマでは、プレイヤーへの参照が必要です。 カメラはプレイヤーの位置と回転を追跡し、Y軸に沿ってプレイヤーの回転と位置を追従します。
このメカニズムは、プレイヤーを追従する空のエンティティを作成し、そのエンティティの子としてカメラを設定することで実現されます。 カメラは現在の位置に配置され、プレイヤーに対してオフセットが生じます。 このアプローチでは、プレイヤーの位置に対してカメラの位置を動的に調整するために変換行列を計算する必要がなくなります。
このスクリプトは、プレイヤーをX軸に沿って追跡するように拡張することも可能です。 ゲームプレイの都合上、この機能は有効化されていませんが、関連するコードのセクションのコメントを外すことで有効化できます。
CSS
game-over.css
: ゲームオーバー画面のUIスタイルを処理します。 これには、ゲームオーバー画面の背景、スコア表、およびリスタートボタンが含まれます。
gameplay.css
: ゲームプレイの状態におけるUIのスタイルを定義します。 静的および動的なインジケーターが左から右へ表示されます:コインの数量、距離、およびポイント。
start.css
: スタート画面のUIスタイルを管理します。 スタート画面の背景とスタートボタンの外観を定義します。
utilities.css
: UI要素用のユーティリティクラスを提供します。例えば、要素を非表示にするクラスやアニメーションを追加するクラスなど。
入力
gyro-controller.ts
: このコンポーネントは、電話のジャイロスコープを使用してY軸(左右の傾き)の回転を検出する入力メカニズムを実装します。これは、設定可能なスキーマプロパティangleLimitに基づいて動作します。 一部のデバイスでは、ジャイロスコープにアクセスするには明示的なユーザー許可が必要であり、この許可はボタンを押すなどの直接的なユーザー操作を通じてリクエストする必要があります。 このコンポーネントは、Z軸(コンパス方向)やX軸(前後傾き)周りの回転など、他の種類の回転を検出するように簡単に変更可能です。
input-manager.ts
: このコンポーネントは、Studio入力システムの実現を管理します。 現在、Studioでは、入力がアクティブかどうかを確認する機能(input.getAction())
のみを提供しており、入力がトリガーされたタイミングや終了したタイミングに関する情報は提供されていません。 このスクリプトは独自のシステムを実装し、入力アクションのトリガー時(on-input-action-triggered)と
入力アクションの終了時(on-input-action-ended)
の両方のケースでイベントをディスパッチします。
touch-input-controller.ts
: このコンポーネントは画面上のタッチ操作を管理し、指が画面上で上、下、左、または右にスワイプされた際にon-input-action-triggeredイベントを
トリガーします。
Path Builder
fog-controller.ts
: このコンポーネントは、world.three.scene
経由で three.js シーンにアクセスし、Studio シーンにフォグ効果を設定して適用します。 このスキーマでは、霧の色(RGB値を使用)およびその近距離と遠距離をカスタマイズできます。
object-spawner.ts
: このコンポーネントは、プロシージャルに生成された設定に基づいて、パスの新しいセクションのタイル上にオブジェクトを配置します。 設定により、各タイルに配置されるオブジェクト(コイン、障害物、パワーアップなど)が決定されます。
path-manager.ts
: このコンポーネントは、ゲームの核心的なメカニズムである「新しいパスセクションを動的に生成する」機能を管理します。 タイルをプロシージャルに生成された配置に基づいて配置し、セクションの長さ、方向、および部分的なタイルの位置を決定します。
path-movement.ts
: このコンポーネントは、パスセクション内の現在の
タイルを前方方向に移動させ、
ゲームシーン内でプレイヤーの移動の錯覚を生み出します。
Player
player-animator.ts
: ジャンプ、ロール、落下、衝突などのゲーム内のイベントに反応して、プレイヤーのアニメーションを制御します。 プレイヤーの操作やゲームイベントに応じて、Idle
(待機
)、Run
(走行)、Jump(ジャンプ
)、Fall(落下)
などのアニメーションを切り替えます。 このコンポーネントは、特定のアニメーションのタイミング処理も担当しています。例えば、ロール後にRun
に戻す処理や、ゲームオーバー時にアニメーションを一時停止する処理などです。 また、弧状の障害物との衝突を、シンプルで直感的な方法でチェックします:現在のアニメーションが「Roll」
であるかどうかを確認するだけです。 このアプローチにより、プレイヤーが下ボタンを適切なタイミングで押したかどうかを確認しつつ、コードを過剰に複雑化することなく実現できます。
player-collision.ts
: プレイヤーのすべての衝突を処理します。 このゲームでは、2種類の主要な衝突処理が実装されています:タイル用の衝突処理とオブジェクト用の衝突処理です。 各衝突に対応するイベントを送信し、他のスクリプトでアクションをトリガーします。 Studioには現在、タグ付けやエンティティ名付けシステムが実装されていないため、衝突フィルタリングはworld.three.entityToObject.get(entity)
を通じて取得または設定可能なobjectThree名
を使用して行われます。 このアプローチは、ゲームを生プレビューでテストする際、予期しない動作を引き起こす可能性があります。
player-controller.ts
:gyro-controller.ts
、input-manager.ts
、およびtouch-input-controller.ts
から送信される入力イベントに基づいて、プレイヤーの移動(ジャンプ、ロール、回転など)を管理します。 プレイヤー・アニメーター(player-animator.ts)
経由で対応するアニメーションをトリガーし、 関数でゲームオーバー条件
(落下や障害物との衝突など)をチェックし、必要に応じてon-player-lost-game イベントを
ディスパッチします。 さらに、入力方法に応じて適応します:デスクトップやジャイロセンサーを搭載していないデバイスでは、キーボードまたはスワイプジェスチャーで横方向の移動を可能にします。
SFX
sfx-manager.ts
: 背景音楽の再生を制御し、スタート
イベントがゲームプレイを開始した際にオーディオを再生します。
UI
ui-game-over.ts
: ゲームオーバーUI画面の各要素(背景、リスタートボタン、ゲームスコアを表示するパネルなど)を格納する個別のdiv要素を作成します。 また、リスタートボタンが押された際に発生するイベントを管理します。
ui-gameplay.ts
: ゲームプレイUI画面の各要素を格納する個別のdiv要素を作成し、3つのゲームステータス(コイン、距離、ポイント)を表します。 ゲームプレイ中にプレイヤーの走行距離とゲームのスコアを追跡します。
ui-start.ts
: ゲーム開始UI画面の各要素(背景や開始ボタンなど)を格納する個別のdiv要素を作成します。 また、スタートボタンが押された際に発生するイベントを管理します。
Implementation
エンドレスランニングゲームを開発する際、特に注意すべき点があります:プレイヤーキャラクターは動きません。プレイヤーに向かって進むのは道であり、プレイヤーが常に前方向に進んでいるような錯覚を生み出す必要があります。

なぜ?
ゲームはプレイヤーがどこまで移動するかを予測できないため、グローバル原点から離れすぎると、浮動小数点数の精度制限により数学的な計算エラーが発生する可能性があります。 さらに、プレイヤーを位置 (0,0,0)
に固定することで、衝突検出やカメラ移動などの特定のゲームプレイメカニクスの開発が簡素化されます。
では、それぞれ何をするのでしょうか?
- プレイヤー: X軸とY軸に沿ってのみ移動し、ジャンプ、
回転、横移動、およびY軸を中心に回転するなどの動作を行う。 - パス: プレイヤーの方向に向かってZ軸に沿って移動し、前方への
移動の錯覚を生み出します。
UI
すべてのゲームにはUIが必要であり、Studioには組み込みのUIシステムが提供されています。 ただし、このゲームはウェブブラウザで実行されるように設計されているため、StudioのUIはCSSを使用して強化でき、よりスタイリッシュなデザインや高度なカスタマイズが可能です。
StudioでTypeScriptとCSSを使用してUIを実装する簡単なワークフローは以下の通りです:
- ECSコンポーネントを作成: 例:
my-component.ts
- コンポーネントをシーンエンティティに追加: シーン内のエンティティにカスタムコンポーネントをアタッチします。
- UI要素をインスタンス化:
新しい
div
要素を作成し、一意のIDを割り当てます。 例:const myUiElement = document.createElement('div') myUiElement.id = 'my-ui-element'
- UI要素をドキュメントに追加する: 要素をドキュメントの本体に追加します。 例:
document.body.append(myUiElement)
- CSS ファイルの作成: 例:
my-css-style.
css
- 新しい UI 要素のスタイルを定義: CSS ファイル内で要素を指定するために一意の ID を使用します。 例:
#my-ui-element {/_ ここにスタイルを追加 _/}
- CSS ファイルをアプリケーションにインポート: 作成したCSS ファイルを app.js にインポートします。 例:
import './my-css-style.css'
ヒント:app.js
ファイルは手動で作成する必要があります。Studio プロジェクトにはデフォルトで含まれていません。
これで完了です! このワークフローを使用することで、任意のUI要素を実装することが可能です。
このゲームでは、上記のステップに従って実装された3つの異なるUI画面が特徴です:
- スタート画面:背景画像、ゲームタイトル、およびゲームを始めるためのスタートボタンを表示します。
- ゲームプレイ画面:スコアやプレイヤーの状態などのリアルタイム更新を表示します。
- ゲームオーバー画面:最終スコアを表示し、ゲームを再プレイするためのリスタートボタンを含みます。

このゲームでは、プレイヤーの操作にジャイロスコープへのアクセスが必要のため、スタートボタンを押すと、utilities.js
スクリプトからrequestGyroscopePermission
関数が呼び出されます。 これにより、ゲームプレイが開始される前に、ブラウザはユーザーから必要な権限を要求します。
プレイヤーコントローラー
pl
ayer-controller.ts
スクリプトは、gyro-controller.ts
、input-manager.ts
、およびtouch-input-controller.ts
の3つの別々のスクリプトからの入力を処理するように設計されています。 各スクリプトは、プレイヤーコントローラーがプレイヤーキャラクターを移動またはアニメーションさせるために使用する特定の入力データを提供します。
gyro-controller.ts
スクリプトは、デバイスの方向が変更された際に on-device-orientation-changed
イベントを送信し、そのデータとしてdeviceOrientationState
を渡します。 この状態は、デバイスの向き(左、中央、または右)を表します。 player-controller.ts
はこのイベントを監視し、movePlayerSideways(side)
関数を呼び出し、プレイヤーの位置を適切に調整します。
input-manager.ts
とtouch-input-controller.ts
は、actionName
をデータとしてon-input-action-triggered
イベントをディスパッチします。 これはトリガーされたアクション(上、左、右、または下)を表します。 player-controller.ts
はこのイベントを監視し、指定されたアクションに基づいて回転と移動を処理するrotateAndMovePlayer(actionName)
を呼び出します。
プレイヤーが実行するすべてのアクションに対して、player-controller.ts
スクリプトは対応するアニメーションイベントをディスパッチし、適切なアニメーションがトリガーされるようにします。
パスビルダー
先ほど述べたように、プレイヤーキャラクターは移動しません。代わりに、パスがプレイヤーの方へ移動し、前進しているような錯覚を生み出します。 これを実現するため、ゲームではセクションから構成されるパスを実装し、各セクションはタイルで構成されています。 タイルにはさまざまな種類があります。

- ベースタイル:標準の完全なタイル。 ベースタイル上では、プレイヤーは左右に3つの位置(左、中央、右)の間を移動できます。
- 部分タイル:ベースタイルの幅の1/3のみを覆う薄いタイル。 ここでは、プレイヤーは横方向のみに移動できます。
- コーナータイル: 経路の方向変更を示し、新しいセクションの開始点をマークします。 各コーナーのタイルは、新しいパスセクションへと移行します。 このタイルでは、プレイヤーは進行方向のまま進むために方向転換が可能です。
ゲーム開始時およびコーナーを配置する際、path-manager.
tsはcomputeNewSectionPathConfig(includeCornerTile, includePartialTile)
関数を使用して、新しいセクションの構成を計算します。

// TODO: convert image to code
この関数は、セクション内のタイルのシーケンスを表す文字列の配列を生成します。
例:
['tile-base', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-corner']
各文字列は、セクション内に生成するタイルの種類を指定します。 includeCorner
またはincludeVariants
パラメーターにfalseを指定することで、ゲームは必要に応じてコーナータイルや部分タイルを除外できます。
path-manager.tsは
、player-collision.ts
スクリプトから送信されるon-tile-changed
イベントを監視します。 このイベントは、プレイヤーが新しいタイルと衝突するたびに発生します。 コールバック関数handleTileChange(event)
は、このイベントを処理し、以前のタイルを削除して新しいタイルを追加することで、パスを継続的に維持します。
セクションに新しいタイルが追加されると、そのタイルはPathRoot
エンティティの子として追加されます。 path-movement.ts
スクリプトは、すべての子供タイルをプレイヤーの方向へ前進させ、プレイヤーが常に前方向へ移動しているような錯覚を生み出します。

// TODO: convert image to code
オブジェクトの配置(コイン、障害物、パワーアップなど)は、タイルの配置と類似したプロセスに従います。 object-spawner.ts
スクリプトは、computeObjectConfigInSectionTiles(addCoins, addObstacles, addPowerUps)
関数を使用して、新しいセクション内のオブジェクトの構成を生成します。
この関数は、path-manager.ts
から送信されるon-new-path-section-computed
イベントによってトリガーされます。
例: { 0: [], 1: [], 2: ['obstacle'], 3: ['coin', 'obstacle'], 4: ['powerUp'], 5: [], 6: ['corner'] }
各キーはタイルインデックスに対応し、値は該当するタイルに配置するオブジェクトの配列です。 このシステムは柔軟性と拡張性を重視して設計されており、新しいオブジェクトタイプの追加が可能です。
ゲームプレイの美観を向上させるため、Studioに霧の効果を実装し、プレイヤーに道が無限に続くような錯覚を与えます。 これにより、プレイヤーはタイルが1つずつ追加されることに気づかなくなります。
ゲームプレイ
ゲームプレイ
中、画面の左側に現在収集したコインの数が表示され、右側には現在のポイントが表示されます。 プレイヤーが250メートルごとに到達すると、現在の距離が表示されます。
プレイヤーが落下したり障害物に衝突したりしてゲームオーバーになった場合、game-manager.js
スクリプトが新しいスコアを計算します。 新しいスコアが以前のスコアよりも高い場合、新しいハイスコアを保存し、独自のメッセージと共に表示します。そうでない場合は、単にスコアを表示します。 データの保存と取得は、これらの2つの関数を使用して行われます。

// TODO: convert image to code