バルーン検索
森に迷った:バルーン探検は、不思議な森を冒険する魅力的な3Dパズルゲームです。 直感的な操作性、ダイナミックな要素、そして魅力的なビジュアルを備えたこのゲームは、Niantic Studioがクリエイターに独自の魅力あふれる3Dウェブゲームを作成する力を与えることを示す完璧な例です。 私たちは、彼らの創造的な道のりについて学び、どのように『Lost in the Woods』を形にしたのかを知る機会を得ました。
サンプルプロジェクトを参考に、自分好みにカスタマイズしてください。

Behind the Build: Balloon Search

Written by eenieweenie interactive
December 26, 2024
Introduction
この体験は、2024年10月にNiantic Studio(ベータ版)を使用して作成された3Dブラウザゲームです。
Project Structure
3Dシーン
- ベースエンティティ: ゲーム環境用の正射影カメラとアンビエント/方向性ライトを含みます。
スクリプト
/colliders/
:finale-collider.js
: ファイナルエリアとの衝突を検出します。
Implementation
ゲームのコアロジックは、相互接続された一連のコンポーネントによって管理されています。 主要な機能は、以下のコードスニペットで示されています。
ゲームマネージャー
game-controller.js
はゲームマネージャーとして機能し、さまざまなイベントを接続し、状態の移行を管理します。
// Define other states
ecs.defineState('welcome')
.initial()
.onEvent('ShowInstructionsScreen', 'instructions')
.onEnter(() => {
console.log('Entering welcome state.')
dataAttribute.set(eid, {currentScreen: 'welcome', score: 0}) // Reset score
resetUI(world, schema, 'welcomeContainer') // Show welcome screen
})
ecs.defineState('instructions')
.onEvent('ShowMovementScreen', 'movement')
.onEnter(() => {
console.log('Entering instructions state.')
dataAttribute.set(eid, {currentScreen: 'instructions'})
resetUI(world, schema, 'instructionsContainer') // Show instructions screen
})
ecs.defineState('movement')
.onEvent('StartGame', 'inGame')
.onEnter(() => {
console.log('Entering movement state.')
dataAttribute.set(eid, {currentScreen: 'movement'})
resetUI(world, schema, 'moveInstructionsContainer') // Show movement screen
})
ecs.defineState('inGame')
.onEvent('gameOver', 'fall')
.onEvent('finale', 'final')
.onEnter(() => {
console.log('Entering inGame state.')
dataAttribute.set(eid, {currentScreen: 'inGame'})
resetUI(world, schema, null) // Show score UI
if (schema.character) {
world.events.dispatch(schema.character, 'start_moving') // Dispatch start moving event
}
world.events.addListener(world.events.globalId, 'balloonCollected', balloonCollect)
world.events.addListener(world.events.globalId, 'FinaleCollision', handleFinaleCollision)
})
.onExit(() => {
world.events.dispatch(eid, 'exitPoints') // Hide points UI
world.events.removeListener(world.events.globalId, 'balloonCollected', balloonCollect)
world.events.removeListener(world.events.globalId, 'FinaleCollision', handleFinaleCollision)
})
主要な機能
- トランジション:
ウェルカム
、インストラクション
、インゲーム
、フォールなど
、ゲーム状態間の移行を管理します。
// Balloon collection handler
const balloonCollect = () => {
const data = dataAttribute.acquire(eid)
data.score += schema.pointsPerBalloon
if (schema.pointValue) {
ecs.Ui.set(world, schema.pointValue, {
text: data.score.toString(),
})
}
ecs.Audio.set(world, eid, {
url: balloonhit,
loop: false,
})
console.log(`Balloon collected! Score: ${data.score}`)
dataAttribute.commit(eid)
}
ダイナミックなゲーム要素
- フィナーレコライダー: プレイヤーがフィナーレエリアに到達した際に検出し、最終スコア画面へ移行します。
const handleCollision = (event) => {
if (schemaAttribute.get(eid).character === event.data.other) {
console.log('Finale collision detected!')
world.events.dispatch(schemaAttribute.get(eid).gameController, 'FinaleCollision')
}
}
ecs.defineState('default')
.initial()
.onEnter(() => {
world.events.addListener(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
})
.onExit(() => {
world.events.removeListener(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
})
例:スコアの更新
const handleCollision = (event) => {
if (schemaAttribute.get(eid).character === event.data.other) {
console.log(`Collision detected with character entity: ${event.data.other}`)
// Notify the GameController
world.events.dispatch(schemaAttribute.get(eid).gameController, 'balloonCollected')
console.log('balloonCollected event dispatched to GameController')
}
}
例:カメラ追跡
const {Position} = ecs
const {vec3} = ecs.math
const offset = vec3.zero()
const playerPosition = vec3.zero()
const cameraFollower = ecs.registerComponent({
name: 'cameraFollower',
schema: {
player: ecs.eid, // Reference to the player entity
camera: ecs.eid, // Reference to the camera entity
offsetX: ecs.f32, // X offset for the camera position
offsetY: ecs.f32, // Y offset for the camera position
offsetZ: ecs.f32, // Z offset for the camera position
},
schemaDefaults: {
offsetX: 0, // Default X offset
offsetY: 5, // Default Y offset
offsetZ: 8, // Default Z offset
},
tick: (world, component) => {
const {player, camera, offsetX, offsetY, offsetZ} = component.schema
offset.setXyz(offsetX, offsetY, offsetZ)
// Set the camera position to the current player position plus an offset.
Position.set(world, camera, playerPosition.setFrom(Position.get(world, player)).setPlus(offset))
},
})
UI
動的画面
各UI画面は、ゲームの状態に応じて動的に表示または非表示されます。
例: UIのリセット
const resetUI = (world, schema, activeContainer = null) => {
[
'welcomeContainer',
'instructionsContainer',
'moveInstructionsContainer',
'gameOverContainer',
'finalScoreContainer',
'perfectScoreContainer',
...Array.from({length: 9}, (_, i) => `scoreContainer${i}`),
].forEach((container) => {
if (schema[container]) {
ecs.Ui.set(world, schema[container], {
opacity: container === activeContainer ? 1 : 0, // Show active container, hide others
})
console.log(
container === activeContainer
? `Showing ${container}`
: `Hiding ${container}`
)
} else {
console.warn(`Container ${container} is not defined in the schema.`)
}
})
例: 動的スコア更新
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })
例: スコア コンテナのハイライト
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })