Desarrollo / ROBLOX
Tu primer guardado de progreso en Roblox: cargar, modificar y volver a entrar
Prepara un ejercicio pequeño con un único contador persistente. Aprende a distinguir los datos del jugador del archivo del proyecto, a utilizar una experiencia de prueba separada y a evitar que un fallo de carga se convierta en un perfil nuevo y vacío. Esta introducción todavía no constituye un sistema completo para la economía de un juego publicado.
Decide qué debe sobrevivir al salir #
Imagina un taller pequeño. Un jugador completa tres pedidos, cierra el juego y regresa por la tarde. El edificio forma parte del proyecto; el número de pedidos completados pertenece a ese jugador. Guardar el archivo del nivel no conserva automáticamente el progreso personal de cada visitante.
Empieza con un solo número entero. No añadas a la vez monedas, objetos, misiones y comercio: sería difícil localizar la causa del primer resultado inesperado. Puedes llamar al contador TestCompletedOrders. La interfaz debe mostrar el valor que el servidor realmente haya cargado, en lugar de una cifra colocada de antemano. Antes de introducir interacciones, escribe qué valor esperas recuperar al volver a entrar.
Prepara una experiencia de prueba separada #
Crea una experiencia independiente para el ejercicio, publícala y limita el acceso de manera adecuada. Añadir otro place dentro de una experiencia existente no ofrece la misma separación: los places de una misma experiencia pueden acceder a sus almacenes de datos. Comprueba en Creator Hub qué proyecto tienes abierto antes de modificar ajustes.
Roblox explica el acceso de Studio a las API en Security, dentro de Experience Settings. Actívalo solamente en tu experiencia de prueba independiente. Los menús de Studio pueden cambiar; consulta la documentación actual si no coinciden. No habilites el acceso a los datos reales de un juego solo para acelerar un experimento. Un registro desconocido de producción no debe convertirse por accidente en material de práctica.
La persistencia pertenece al servidor #
Las operaciones con DataStore se ejecutan mediante un Script del servidor. ServerScriptService es un lugar adecuado para este ejercicio. Un LocalScript puede mostrar el estado de carga y el resultado, pero no debe decidir el contador guardado ni anunciar por su cuenta que una escritura ha tenido éxito.
Separa tres responsabilidades: leer un valor al entrar, aplicar un cambio autorizado por el servidor y guardar ese cambio. Si mezclas todo en el evento de un botón, resulta fácil considerar cualquier clic como un éxito. En nuestro taller, «pedido completado» significa que el servidor comprobó la tarea. Una pulsación visible, por sí sola, no demuestra que el jugador haya conseguido progreso.
Elige el nombre del almacén y la clave #
Utiliza un nombre estable para el almacén de prueba y una clave basada en Player.UserId. Nuestra propuesta es GuidebookOrdersTest_v1 para el almacén y User_ seguido del identificador del usuario para la clave. Son nombres elegidos para el ejercicio, no un formato obligatorio de Roblox.
No uses Display Name como identidad: distintas personas pueden compartir el mismo nombre visible. Mantén el nombre del almacén entre la primera y la segunda sesión. De lo contrario podrías consultar otro almacén y concluir, equivocadamente, que el guardado desapareció. Anota el nombre, la regla de construcción de la clave y la experiencia en el registro de pruebas. Así podrás investigar después con una referencia clara.
Un dato ausente no es un fallo de carga #
Una primera lectura correcta puede devolver nil si todavía no existe un valor bajo esa clave. En este ejercicio, inicializa el contador únicamente cuando una lectura exitosa haya establecido que falta el registro. Si la solicitud falla, la ausencia de un resultado útil no demuestra que no exista un perfil guardado.
Mantén estados explícitos como Loading, Ready y LoadFailed. Mientras esté Loading, las acciones que modifican progreso permanecerán deshabilitadas. En LoadFailed, muestra un mensaje comprensible y no sobrescribas datos desconocidos con cero. Para la primera práctica basta con detener los cambios y terminar la prueba. Diseñar reintentos es una decisión separada; no justifica sustituir silenciosamente el perfil por uno nuevo.
| Estado | Comportamiento esperado |
|---|---|
| Loading | Cambios de progreso deshabilitados |
| Ready | Acciones autorizadas por el servidor disponibles |
| LoadFailed | No escribir el perfil inicial |
Gestiona el fallo sin anunciar un éxito falso #
Las llamadas al almacén pueden fallar. La documentación de Roblox muestra cómo envolverlas con pcall, pero pcall no vuelve fiable el guardado por sí solo. Debes comprobar el resultado y decidir cómo responderá la aplicación si la operación falla.
«Intento enviado» y «guardado confirmado» son estados diferentes. Usa mensajes distintos en Output para carga, modificación y escritura. La interfaz no debe mostrar «guardado» hasta recibir un resultado satisfactorio del servidor. Evita incluir perfiles completos o información personal en los errores visibles para el jugador. Para la práctica bastan una explicación breve y la etapa afectada. El diagnóstico detallado pertenece al entorno de prueba controlado.
Comprende las formas de actualizar #
Compara SetAsync y UpdateAsync antes de elegir una estrategia. SetAsync escribe el valor que le proporcionas. UpdateAsync aplica una transformación al valor almacenado actual. Cuando varios servidores escriben, asignar un valor local antiguo puede sobrescribir otra modificación.
La función callback de UpdateAsync no puede ceder la ejecución, por ejemplo mediante task.wait. No entregues una recompensa externa dentro de esa transformación suponiendo que siempre se ejecutará exactamente una vez. Define primero cómo cambian los datos y comunica después el resultado confirmado. Una solución completa para perfiles utilizados por varios servidores necesita más reglas. La propiedad de la sesión y la gestión de compras quedan fuera de esta introducción.
Coloca el código educativo siguiente en un ModuleScript llamado StoreExperiment dentro de ServerScriptService. Un Script del servidor obtiene el almacén de prueba mediante DataStoreService:GetDataStore("GuidebookOrdersTest_v1"), carga el módulo con require y crea el adaptador con StoreExperiment.new(store). Lee con adapter:Load(player.UserId). Tras validar una acción real del ejercicio en el servidor, llama a adapter:CompleteTestOrder(player.UserId). No es un controlador de botón ni una recompensa automática por entrar. El éxito devuelve true y un número; el fallo devuelve false y LoadFailed o SaveFailed. Compara la tabla. Solo admite enteros de 0 a 1.000.000 y se detiene en ese límite elegido para la práctica. Repetir una llamada separada vuelve a incrementar: no evita recompensas duplicadas. Las comprobaciones locales de Luau utilizaron un almacén simulado, sin Studio ni llamadas reales a la API.
-- ModuleScript: StoreExperiment (ServerScriptService).
-- Learning adapter only; no sessions, receipts, retries or production economy.
local StoreExperiment = {}
local function keyFor(userId)
assert(type(userId) == "number" and userId > 0
and userId < math.huge and userId == math.floor(userId), "Invalid UserId")
return "User_" .. tostring(userId)
end
local function counter(value)
if value == nil then return 0 end
assert(type(value) == "number" and value >= 0 and value <= 1000000
and value == math.floor(value), "Unexpected counter format")
return value
end
function StoreExperiment.new(store)
local adapter = {}
function adapter:Load(userId)
local key = keyFor(userId)
local ok, result = pcall(function()
return counter(store:GetAsync(key))
end)
if not ok then return false, "LoadFailed" end
return true, result
end
function adapter:CompleteTestOrder(userId)
-- Call only after the server verifies the exercise action.
-- A failed read never authorizes a write of initial data.
local loaded = self:Load(userId)
if not loaded then return false, "LoadFailed" end
local key = keyFor(userId)
local ok, result = pcall(function()
return store:UpdateAsync(key, function(current)
local value = counter(current)
assert(value < 1000000, "Exercise counter limit reached")
return value + 1
end)
end)
if not ok then return false, "SaveFailed" end
return true, result
end
return adapter
end
return StoreExperiment
Deja claros los límites del ejercicio #
Un contador de aprendizaje no es una cartera lista para producción. Antes de implantar una economía real debes decidir qué servidor controla el perfil, cómo identificar operaciones repetidas, cómo manejar fallos de red y qué hacer con las escrituras pendientes cuando se cierra un servidor.
No guardes en cada fotograma ni cada vez que cambie un texto de la interfaz. La frecuencia depende de los límites del servicio y de la pérdida de progreso que puedas aceptar. Aquí comprobamos una escritura deliberada y la lectura en una sesión nueva. Esta prueba explica el mecanismo; no demuestra que una escritura sea suficiente para todos los juegos ni que un resultado exitoso elimine los fallos posteriores.
Comprueba dos sesiones consecutivas #
Prepara el registro antes de iniciar. En la primera sesión, espera a que termine correctamente la carga, completa una acción de prueba, recibe la confirmación de escritura y apunta el contador mostrado. Termina la sesión. Después entra en la misma experiencia de prueba con la misma cuenta, almacén y clave.
Compara el valor recién cargado con el esperado. Reiniciar una interfaz dentro de la sesión anterior no sustituye la prueba de persistencia. Para investigaciones adicionales, recuerda que GetAsync puede utilizar caché: dos lecturas inmediatas no demuestran necesariamente dos consultas independientes al estado actual del servicio. Describe con precisión lo observado, sin llamar «comprobación nueva» a cualquier lectura repetida.
Comprueba los errores y la separación entre jugadores #
Dos entradas exitosas solo cubren parte de la práctica. Verifica que otro usuario reciba una clave propia y que modificar el primer contador no altere el segundo. Un fallo al leer no debe iniciar una escritura de datos iniciales. Un fallo al escribir no debe producir un mensaje de guardado exitoso.
Para probar estas situaciones de forma controlada, separa la interfaz de persistencia de la lógica restante y utiliza temporalmente un adaptador de aprendizaje que devuelva un fallo. No dañes registros reales para provocar el caso. La simulación comprueba las ramas de la aplicación, pero sigue siendo necesaria una prueba adicional con la API real. Identifica ambos tipos de prueba por separado; una respuesta simulada no acredita una operación ejecutada en Roblox.
| Prueba | Resultado para aprobar |
|---|---|
| Volver a entrar | Se carga el contador confirmado |
| Otro jugador | Clave y valor separados |
| Fallo de lectura | No escribir datos vacíos |
| Fallo de escritura | No mostrar éxito del guardado |
Si al volver aparece cero #
Recorre la cadena: ¿es la experiencia correcta?, ¿coincide el nombre del almacén?, ¿entró el UserId previsto?, ¿terminó bien la escritura anterior?, ¿creó la aplicación datos iniciales después de un fallo de carga? Lee los mensajes de Output en orden, no solamente la última línea roja.
No borres registros en masa para resolver una duda. Conserva el registro de pruebas y reproduce primero la causa con una clave separada. Si un experimento alcanzó por accidente un juego activo, detén las escrituras experimentales e investiga las herramientas disponibles para administrar datos. Ejecutar repetidamente la misma lógica defectuosa puede ampliar el daño en lugar de aclarar el origen.
Cuándo termina la primera etapa #
El ejercicio supera la revisión cuando el contador persiste entre sesiones confirmadas, los jugadores tienen datos separados, un fallo de carga nunca sobrescribe un perfil con datos vacíos y un fallo de escritura nunca se presenta como éxito. Anota resultados reales: cuentas, entorno, acciones y valores observados.
La siguiente etapa trata actualizaciones entre servidores, reintentos limitados y propiedad del perfil. Hasta comprobarlos, no traslades esta práctica a un juego existente con compras y colecciones. Studio no se ha ejecutado para este borrador. El material contiene una explicación y un plan de pruebas, no un informe de una comprobación completada dentro del juego.
Fuentes originales
Roblox Creator Hub — Data storesRoblox Creator Hub — Best practices for data stores