Ballonsuche
Lost in the Woods: Balloon Search ist ein fesselndes 3D-Puzzlespiel, in dem die Spieler gemeinsam mit dem neugierigen Purple Guy durch einen märchenhaften Wald navigieren müssen. Mit intuitiven Steuerelementen, dynamischen Elementen und einer bezaubernden Grafik ist das Spiel ein perfektes Beispiel dafür, wie Niantic Studio Entwickler dabei unterstützt, einzigartige und fesselnde 3D-Webspiele zu entwickeln. Wir hatten die Gelegenheit, mehr über ihren kreativen Werdegang zu erfahren und darüber, wie sie Lost in the Woods zum Leben erweckt haben.
Gestalten Sie es mit dem Beispielprojekt ganz nach Ihren Wünschen.

Player Movement with Music
A sample project that features character's movement, animations, and sounds, with a camera that follows the character smoothly.
View sample projectBehind the Build: Balloon Search

Written by eenieweenie interactive
December 26, 2024
Introduction
Diese Erfahrung ist ein 3D-Browsergame, das im Oktober 2024 mit Niantic Studio (Beta) erstellt wurde.
Der Hauptcharakter, Purple Guy, beginnt am Eingang des Waldes und muss sich durch das Gelände navigieren, um Ballons zu sammeln und das Finale zu erreichen.
Unterwegs:
- Auf der Karte sind 8 Ballons verstreut, die durch einen Ticker in der oberen rechten Ecke des Bildschirms verfolgt werden.
- Vermeiden Sie Gewässer, denn wenn Sie ins Wasser treten, wird das Spiel zurückgesetzt und Sie verlieren alle gesammelten Ballons.
- Verwenden Sie die Pfeiltasten, um sich nach oben, unten, links und rechts zu bewegen, und drücken Sie die Leertaste, um den Begrüßungsbildschirm und die Anweisungen anzuzeigen.
Project Structure
3D-Szene
- Basisentitäten: Enthält die orthogonale Kamera und Umgebungs-/Richtungslichter für die Spielumgebung.
- UI-Entitäten: Enthält alle Elemente der Benutzeroberfläche, einschließlich eines Tutorials zu Beginn, in dem das Hauptziel des Spiels erklärt wird.
Assets
- Audio: Hintergrundmusik, Soundeffekte für das Sammeln von Ballons, Game Over und Finale.
- Modelle: Ballons, Purple Guy und Umgebungsobjekte.
Skripte
/colliders/
:finale-collider.js
: Erkennt Kollisionen mit dem Finalbereich.game-over-collider.js
: Erkennt Kollisionen, die zu einem Spielende führen.score-collider.js
: Erkennt Kollisionen mit Ballons, um den Punktestand zu aktualisieren.
balloon.js
: Registriert die Balloon-Komponente als wiederverwendbare Entität "
" für das gesamte Spiel.character-controller.js
: Verwaltet die Bewegungen und Animationen der Spieler sowie die Soundeffekte von "
" basierend auf den Eingaben und dem Spielstatus.camera-follower.js
: Hält die Kamera relativ zum Charakter positioniert.game-controller.js
: Dient als zentrale Schaltstelle für die Verwaltung von Spielzuständen, Übergängen und UI-Aktualisierungen.
Implementation
Die Kernlogik des Spiels wird durch eine Reihe miteinander verbundener Komponenten verwaltet. Die wichtigsten Funktionen werden anhand der folgenden Codeausschnitte demonstriert.
Spielmanager
Die Datei "game-controller.js"
fungiert als Spielmanager, verbindet verschiedene Ereignisse und verwaltet Zustandsübergänge.
Beispiel: Zustandsverwaltung
// 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)
})
Wichtigste Funktionen
- Übergänge: Wechselt zwischen Spielzuständen wie "
Willkommen"
,"Anweisungen"
,"Im Spiel
" und"Sturz"
. - Ereignisbehandlung: Verwaltet Ereignisse wie "
balloonCollected"
und "FinaleCollision
", um Punktestände zu aktualisieren oder die Spielende-Logik auszulösen.
Beispiel: Ereignisbehandlung
// 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)
}
Dynamische Spielelemente
- Finale Collider: Erkennt, wenn der Spieler den Finalbereich erreicht, und wechselt zum Endbildschirm mit der Punktzahl.
- Score Collider: Verfolgt das Sammeln von Ballons und aktualisiert die Punktzahl.
- Follow Camera: Aktualisiert dynamisch die Position der Kamera, um dem Spieler zu folgen.
Beispiel: Kollisionsbehandlung
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)
})
Beispiel: Punktestand-Aktualisierung
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')
}
}
Beispiel: Kameraführung
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
Dynamische Bildschirme
Jeder UI-Bildschirm wird je nach Spielstatus dynamisch ein- oder ausgeblendet.
- Begrüßungsbildschirm (
welcomeContainer
):- Zweck: Zeigt die Schaltfläche "Start" an und setzt den Punktestand zurück.
- Anweisungsbildschirm (
instructionsContainer
):- Zweck: Enthält Anweisungen zum Spielablauf.
- Bildschirm mit Bewegungsanweisungen (
moveInstructionsContainer
):- Zweck: Erklärt die Bewegungssteuerung.
- Bildschirm "Spiel vorbei " (
gameOverContainer
):- Zweck: Zeigt die Game-Over-Meldung an.
- Bildschirme für Endpunkt und perfekte Punktzahl (
finalScoreContainer
,perfectScoreContainer
):- Zweck: Hebt die Endpunktzahl des Spielers hervor.
Interaktive Elemente
- Punktetitel und Wert:
- Wird während des Spiels dynamisch aktualisiert, um die Punktzahl des Spielers anzuzeigen.
Beispiel: Benutzeroberfläche zurücksetzen
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.`)
}
})
Beispiel: Dynamische Punktestandsaktualisierung
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })
Beispiel: Score-Container hervorheben
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })