Búsqueda de globos
Perdidos en el bosque: La Búsqueda del Globo es un atractivo juego de puzles en 3D que invita a los jugadores a navegar por un caprichoso bosque junto al curioso Chico Púrpura. Con controles intuitivos, elementos dinámicos y efectos visuales encantadores, el juego es un ejemplo perfecto de cómo Niantic Studio permite a los creadores crear juegos web 3D únicos y atractivos. Tuvimos la oportunidad de conocer su trayectoria creativa y cómo dieron vida a Lost in the Woods.
Hazlo tuyo con el proyecto de ejemplo

Movimiento del jugador con la música
Un proyecto de ejemplo que incluye el movimiento, las animaciones y los sonidos del personaje, con una cámara que lo sigue suavemente.
Ver ejemplo de proyectoBehind the Build: Balloon Search

Written by eenieweenie interactive
December 26, 2024
Introduction
Esta experiencia es un juego de navegador en 3D creado con Niantic Studio (Beta) en octubre de 2024.
El personaje principal, Purple Guy, comienza en la entrada del bosque y debe navegar por el terreno para recoger globos y llegar al final.
A lo largo del camino:
- Hay 8 globos repartidos por el mapa, seguidos por un ticker en la esquina superior derecha de la pantalla.
- Evita las masas de agua; al pisar el agua, el juego se reinicia y pierdes todos los globos recogidos.
- Utiliza las teclas de flecha para moverte arriba, abajo, izquierda y derecha, y pulsa la barra espaciadora para alternar entre la pantalla de bienvenida y las instrucciones.
Project Structure
Escena 3D
- Entidades base: Incluye la cámara ortográfica y las luces ambientales/direccionales para el entorno del juego.
- Entidades de interfaz de usuario: Contiene todos los elementos de la interfaz de usuario, incluyendo un tutorial al principio para explicar el objetivo principal del juego.
Activos
- Audio: Música de fondo, efectos de sonido para la recogida de globos, game-over y final.
- Modelos: Globos, tipo morado y objetos del entorno.
Guiones
/colisionadores/
:finale-collider.js
: Detecta colisiones con el área final.game-over-collider.js
: Detecta las colisiones que conducen a un estado de game-over.marcador-colisionador.js
: Detecta colisiones con globos para actualizar la puntuación.
globo.js
: Registra el componente Globo como una entidad reutilizable
a lo largo del juego.character-controller.js
: Gestiona el movimiento del jugador, las animaciones y los efectos de sonido de
en función de la entrada y el estado del juego.cámara-seguidor.js
: Mantiene la cámara en posición relativa al personaje.game-controller.js
: Sirve como eje central para gestionar los estados del juego, las transiciones y las actualizaciones de la interfaz de usuario.
Implementation
La lógica central del juego está gestionada por una serie de componentes interconectados. A continuación se muestran fragmentos de código de las principales funciones.
Gestor del juego
El game-controller.js
actúa como gestor del juego, conectando diferentes eventos y gestionando las transiciones de estado.
Ejemplo: Gestión de estados
// 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)
})
Características principales
- Transiciones: Cambios entre estados del juego como
bienvenida
,instrucciones
,inGame
ycaída
. - Manejo de Eventos: Maneja eventos como
balloonCollected
yFinaleCollision
para actualizar puntuaciones o activar la lógica de game-over.
Ejemplo: Manejo de Eventos
// 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)
}
Elementos dinámicos del juego
- Colisionador Final: Detecta cuando el jugador llega a la zona final y pasa a la pantalla de puntuación final.
- Colisionador depuntuación: Realiza un seguimiento de la recogida de globos y actualiza la puntuación.
- Cámara de seguimiento: Actualiza dinámicamente la posición de la cámara para seguir al jugador.
Ejemplo: Manejo de Colisiones
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)
})
Ejemplo: Actualización de la puntuación
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')
}
}
Ejemplo: Seguimiento de cámara
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))
},
})
INTERFAZ DE USUARIO
Pantallas dinámicas
Cada pantalla de la IU se muestra u oculta dinámicamente en función del estado del juego.
- Pantalla de bienvenida
(welcomeContainer
):- Propósito: Muestra el botón "Empezar" y reinicia la puntuación.
- Pantalla
de instrucciones (instructionsContainer
):- Propósito: Proporciona instrucciones de juego.
- Pantalla de instrucciones de movimiento
(moveInstructionsContainer
):- Propósito: Enseña los controles de movimiento.
- Pantalla de fin de partida
(gameOverContainer
):- Finalidad: Muestra el mensaje de fin de partida.
- Pantallas de puntuación final y perfecta
(finalScoreContainer
,perfectScoreContainer
):- Propósito: Destaca la puntuación final del jugador.
Elementos interactivos
- Título y valor del punto:
- Se actualiza dinámicamente durante el juego para mostrar la puntuación del jugador.
Ejemplo: Restablecer IU
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.`)
}
})
Ejemplo: Actualización dinámica de resultados
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })
Ejemplo: Resaltar contenedor de puntuación
ecs.Ui.set(world, schema.pointValue, { text: data.score.toString() })