バルーン検索

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

サンプルプロジェクトを参考に、自分好みにカスタマイズしてください。

balloon-cover

音楽による選手の動き checkmark bullet

キャラクタの動き、アニメーション、サウンド、そしてキャラクタを滑らかに追うカメラをフィーチャーしたサンプルプロジェクト。

サンプルプロジェクトを見る

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() })

      
Your cool escaped html goes here.