Lua en Defold

Enviado por foroDEFOLD el Mié, 15/11/2017 - 23:30
Foros

El motor Defold tiene el lenguaje Lua incrustado para scripting. Lua es un lenguaje dinámico ligero que es potente, rápido y fácil de integrar. Es ampliamente utilizado como un lenguaje de scripting de videojuegos. Los programas Lua están escritos en una sintaxis de procedimiento simple. El lenguaje se escribe de forma dinámica y lo ejecuta un intérprete de códigos de bytes. Cuenta con administración de memoria automática con recolección de basura incremental.

Este manual brindará una introducción rápida a los aspectos básicos de la programación de Lua en general y lo que debe tener en cuenta al trabajar con Lua en Defold. Si tiene alguna experiencia con Python, Perl, Ruby, Javascript o un lenguaje dinámico similar, se pondrá en marcha rápidamente. Si eres totalmente nuevo en programación, quizás quieras comenzar con un libro de Lua dirigido a principiantes. Hay mucho de donde escoger.

Versiones de Lua

Nuestro objetivo es mantener Defold igual en todas las plataformas, pero actualmente tenemos una pequeña discrepancia entre las versiones de Lua. Para las plataformas HTML5 y iOS de 64 bits, usamos Lua 5.1, pero para otras plataformas usamos LuaJIT. LuaJIT se basa en 5.1 pero contiene algunas características adicionales.

Para mantener su juego funcionando en plataforma cruzada, le sugerimos que se adhiera a las funciones de Lua 5.1.

Libros y recursos de Lua

Sintaxis

Los programas tienen una sintaxis simple y fácil de leer. Las declaraciones se escriben una en cada línea y no es necesario marcar el final de una declaración. Opcionalmente puede usar punto ; coma ; para separar las declaraciones. Los bloques de código están delimitados por palabras clave y terminan con la palabra clave end . Los comentarios pueden ser de bloque o hasta el final de la línea:

--[[
Here is a block of comments that can run over several lines in the source file.
--]]

a = 10
b = 20 ; c = 30 -- two statements on one line

if my_variable == 3 then
    call_some_function(true) -- Here is a line comment
 else
    call_another_function(false)
 end

 

Variables y tipos de datos

Lua se tipea dinámicamente, lo que significa que las variables no tienen tipos, pero sí los valores. A diferencia de los idiomas tipados, puede asignar cualquier valor a cualquier variable que desee. Hay ocho tipos básicos en Lua:

nil

Este tipo solo tiene el valor nil . Por lo general, representa la ausencia de un valor útil, por ejemplo, variables no asignadas.

print (my_var) -- will print 'nil' since 'my_var' is not yet assigned a value

booleano

Tiene el valor true o false . Las condiciones que son false o nil se hacen falsas. Cualquier otro valor lo hace verdadero.

flag = true
if flag then
    print("flag is true")
else
    print("flag is false")
end

if my_var then
    print("my_var is not nil nor false!")
end

if not my_var then
    print("my_var is either nil or false!")
end
número

Los números se representan internamente como números enteros de 64 bits o coma flotante de 64 bits. Lua convierte automáticamente entre estas representaciones según sea necesario, por lo que generalmente no tiene que preocuparse por ello.

print(10) --> prints '10'
print(10.0) --> '10'
print(10.000000000001) --> '10.000000000001'

a = 5 -- integer
b = 7/3 -- float
print(a - b) --> '2.6666666666667'
cuerda

Las cadenas son secuencias inmutables de bytes que pueden contener cualquier valor de 8 bits, incluidos los ceros incrustados ( \0 ). Lua no hace suposiciones sobre el contenido de una cadena para que pueda almacenar los datos que desee en ellos. Los literales de cadena están escritos en comillas simples o dobles. Lua convierte entre números y cadenas en tiempo de ejecución. Las cadenas se pueden concatenar con el operador ..

Las cadenas pueden contener las siguientes secuencias de escape estilo C:

Secuencia Personaje
\a campana
\b espacio trasero
\f formulario de alimentación
\n nueva línea
\r retorno de carro
\t pestaña horizontal
\v pestaña vertical
\\ barra invertida
\" doble cita
\' una frase
\[ corchete izquierdo
\] corchete derecho
\ddd carácter denotado por su valor numérico donde ddd es una secuencia de hasta tres dígitos decimales
my_string = "hello"
another_string = 'world'
print(my_string .. another_string) --> "helloworld"

print("10.2" + 1) --> 11.2
print(my_string + 1) -- error, can't convert "hello"
print(my_string .. 1) --> "hello1"

print("one\nstring") --> one
                     --> string

print("\097bc") --> "abc"

multi_line_string = [[
Here is a chunk of text that runs over several lines. This is all
put into the string and is sometimes very handy.
]]
función

Las funciones son valores de primera clase en Lua, lo que significa que puede pasarlos como parámetros a funciones y devolverlos como valores. Las variables asignadas a una función contienen una referencia a la función. Puede asignar variables a funciones anónimas, pero Lua proporciona azúcar sintáctica ( function name(param1, param2) ... end ) por conveniencia.

-- Assign 'my_plus' to function
my_plus = function(p, q)
    return p + q
end

print(my_plus(4, 5)) --> 9

-- Convenient syntax to assign function to variable 'my_mult'
function my_mult(p, q)
    return p * q
end

print(my_mult(4, 5)) --> 20

-- Takes a function as parameter 'func'
function operate(func, p, q)
    return func(p, q) -- Calls the provided function with parameters 'p' and 'q'
end

print(operate(my_plus, 4, 5)) --> 9
print(operate(my_mult, 4, 5)) --> 20

-- Create an adder function and return it
function create_adder(n)
    return function(a)
        return a + n
    end
end

adder = create_adder(2)
print(adder(3)) --> 5
print(adder(10)) --> 12
tabla

Las tablas son el único tipo de estructuración de datos en Lua. Son objetos de matriz asociativa que se utilizan para representar listas, matrices, secuencias, tablas de símbolos, conjuntos, registros, gráficos, árboles, etc. Las tablas son siempre anónimas y las variables que asigna una tabla no contienen la tabla en sí, sino una referencia a eso. Al inicializar una tabla como una secuencia, el primer índice es 1 , no 0 .

-- Initialize a table as a sequence
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"}
print(weekdays[1]) --> "Sunday"
print(weekdays[5]) --> "Thursday"

-- Initialize a table as a record with sequence values
moons = { Earth = { "Moon" }, 
          Uranus = { "Puck", "Miranda", "Ariel", "Umbriel", "Titania", "Oberon" } }
print(moons.Uranus[3]) --> "Ariel"

-- Build a table from an empty constructor {}
a = 1
t = {}
t[1] = "first"
t[a + 1] = "second"
t.x = 1 -- same as t["x"] = 1

-- Iterate over the table key, value pairs
for key, value in pairs(t) do
    print(key, value)
end
--> 1   first
--> 2   second
--> x   1

u = t -- u now refers to the same table as t
u[1] = "changed"

for key, value in pairs(t) do -- still iterating over t!
    print(key, value)
end
--> 1   changed
--> 2   second
--> x   1
datos del usuario

Se proporcionan datos de usuario para permitir que datos de C arbitrarios se almacenen en variables de Lua. Defold utiliza los objetos Lua userdata para almacenar valores Hash (hash), objetos URL (url), objetos matemáticos (vector3, vector4, matrix4, quaternion), objetos Game, nodos GUI (nodo), predicados Render (predicado), renderizar objetivos (render_target ) y Renderizar búferes constantes (constante_buffer)

hilo

Los subprocesos representan hilos de ejecución independientes y se utilizan para implementar corutinas. Ver abajo para más detalles.

 

Operadores

Operadores aritméticos

Operadores matemáticos + , - , * , / , el unario - (negación) y exponencial ^ .

 a = -1 print (a * 2 + 3 / 4 ^ 5 ) --> -1.9970703125 

Lua proporciona conversiones automáticas entre números y cadenas en tiempo de ejecución. Cualquier operación numérica aplicada a una cadena intenta convertir la cadena a un número:

 print ( "10" + 1 ) --> 11 
Operadores relacionales / de comparación

< (menor que), > (mayor que), <= (menor o igual), >= (mayor o igual), == (igual), ~= (no igual). Los operadores siempre devuelven true o false . Los valores de diferentes tipos se consideran diferentes. Si los tipos son iguales, se comparan según su valor. Lua compara tablas, datos de usuario y funciones por referencia. Dos de estos valores se consideran iguales solo si se refieren al mismo objeto.

 a = 5 b = 6 if a <= b then print ( "a is less than or equal to b" ) end print ( "A" < "a" ) --> true print ( "aa" < "ab" ) --> true print ( 10 == "10" ) --> false print ( tostring ( 10 ) == "10" ) --> true 
Operadores logicos

and , or , y not . and devuelve su primer argumento si es false , de lo contrario, devuelve su segundo argumento. or devuelve su primer argumento si no es false , de lo contrario, devuelve su segundo argumento.

 print ( true or false ) --> true print ( true and false ) --> false print ( not false ) --> true if a == 5 and b == 6 then print ( "a is 5 and b is 6" ) end 
Concatenación

Las cadenas se pueden concatenar con el operador .. Los números se convierten en cadenas cuando se concatenan.

 print ( "donkey" .. "kong" ) --> "donkeykong" print ( 1 .. 2 ) --> "12" 
Longitud

El operador de longitud única # . La longitud de una cadena es su número de bytes. La longitud de una tabla es su longitud de secuencia, la cantidad de índices numerados de 1 adelante donde el valor no es nil . Nota: Si la secuencia tiene un valor nil "agujeros", la longitud puede ser cualquier índice que preceda a un valor nil .

 s = "donkey" print (#s) --> 6 t = { "a" , "b" , "c" , "d" } print (#t) --> 4 u = { a = 1 , b = 2 , c = 3 } print (#u) --> 0 v = { "a" , "b" , nil } print (#v) --> 2 
 

Control de flujo

Lua proporciona el conjunto habitual de construcciones de control de flujo.

if-then-else

Pruebe una condición, ejecute la parte then si la condición es verdadera, de lo contrario ejecute la parte else (opcional). En lugar de anidar declaraciones if , puede usar elseif . Esto reemplaza una declaración de cambio que Lua no tiene.

 a = 5 b = 4 if a < b then print ( "a is smaller than b" ) end if a == '1' then print ( "a is 1" ) elseif a == '2' then print ( "a is 2" ) elseif a == '3' then print ( "a is 3" ) else print ( "I have no idea what a is..." ) end 
mientras

Pruebe una condición y ejecute el bloqueo mientras sea verdadero.

 weekdays = { "Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" } -- Print each weekday i = 1 while weekdays[i] do print (weekdays[i]) i = i + 1 end 
repetir hasta

Repite el bloqueo hasta que una condición sea verdadera. La condición se prueba después del cuerpo, por lo que se ejecutará al menos una vez.

 weekdays = { "Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" } -- Print each weekday i = 0 repeat i = i + 1 print (weekdays[i]) until weekdays[i] == "Saturday" 
para

Lua tiene dos tipos de bucle for : numérico y genérico. El valor numérico toma 2 o 3 valores numéricos mientras que el genérico itera sobre todos los valores devueltos por una función de iterador .

 -- Print the numbers 1 to 10 for i = 1 , 10 do print (i) end -- Print the numbers 1 to 10 and increment with 2 each time for i = 1 , 10 , 2 do print (i) end -- Print the numbers 10 to 1 for i= 10 , 1 , -1 do print (i) end t = { "a" , "b" , "c" , "d" } -- Iterate over the sequence and print the values for i, v in ipairs (t) do print (v) end 
romper y regresar

Use la instrucción break para salir de un bloque interno de un ciclo for , while o repeat . Use return para devolver un valor de una función o para finalizar la ejecución de una función y regresar a la persona que llama. break o return puede aparecer solo como la última declaración de un bloque.

 a = 1 while true do a = a + 1 if a >= 100 then break end end function my_add (a, b) return a + b end print (my_add( 10 , 12 )) --> 22 
 

Locales, globales y alcance léxico

Todas las variables que declaras son por defecto globales, lo que significa que están disponibles a través de todas las partes del contexto del tiempo de ejecución de Lua. Puede declarar explícitamente variables local , lo que significa que la variable solo existirá dentro del alcance actual.

Cada archivo fuente de Lua define un ámbito separado. Las declaraciones locales en el nivel superior de un archivo significa que la variable es local al archivo de script de Lua. Cada función crea otro ámbito anidado y cada bloque de estructura de control crea ámbitos adicionales. Puede crear explícitamente ámbito con las palabras clave do y end . Lua tiene un alcance léxico, lo que significa que un ámbito tiene acceso completo a las variables locales del ámbito adjunto. Tenga en cuenta que las variables locales deben declararse antes de su uso.

 function my_func (a, b) -- 'a' and 'b' are local to this function and available through its scope do local x = 1 end print (x) --> nil. 'x' is not available outside the do-end scope print (foo) --> nil. 'foo' is declared after 'my_func' print (foo_global) --> "value 2" end local foo = "value 1" foo_global = "value 2" print (foo) --> "value 1". 'foo' is avalable in the topmost scope after declaration. 

Tenga en cuenta que si declara las funciones local en un archivo de script (que generalmente es una buena idea), debe vigilar cómo ordena el código. Puede usar declaraciones directas si tiene funciones que se llaman entre sí.

 local func2 -- Forward declare 'func2' local function func1 (a) print ( "func1" ) func2(a) end function func2 (a) -- or func2 = function(a) print ( "func2" ) if a < 10 then func1(a + 1 ) end end function init (self) func1( 1 ) end 

Si escribe una función encerrada en otra función, también tiene acceso completo a las variables locales de la función adjunta. Esta es una construcción muy poderosa.

 function create_counter (x) -- 'x' is a local variable in 'create_counter' return function () x = x + 1 return x end end count1 = create_counter( 10 ) count2 = create_counter( 20 ) print (count1()) --> 11 print (count2()) --> 21 print (count1()) --> 12 
 

Sombreado variable

Las variables locales declaradas en un bloque sombrearán las variables de un bloque circundante con el mismo nombre.

 my_global = "global" print (my_global) -->"global" local v = "local" print (v) --> "local" local function test (v) print (v) end function init (self) v = "apple" print (v) --> "apple" test( "banana" ) --> "banana" end 
 

Corutinas

Las funciones se ejecutan de principio a fin y no hay forma de detenerlas a mitad de camino. Las corutinas le permiten hacer eso, lo cual puede ser muy conveniente en algunos casos. Supongamos que queremos crear una animación muy específica cuadro por cuadro donde movemos un objeto del juego desde la posición y 0 a algunas posiciones y específicas del cuadro 1 al cuadro 5. Podríamos resolver eso con un contador en la función update() (ver abajo) y una lista de los puestos. Sin embargo, con una coroutine obtenemos una implementación muy limpia que es fácil de extender y trabajar. Todo estado está contenido dentro de la corutina misma.

Cuando una corutina cede devuelve el control a la persona que llama pero recuerda su punto de ejecución para que pueda continuar a partir de allí más adelante.

 -- This is our coroutine local function sequence (self) coroutine .yield( 120 ) coroutine .yield( 320 ) coroutine .yield( 510 ) coroutine .yield( 240 ) return 440 -- return the final value end function init (self) self .co = coroutine .create(sequence) -- Create the coroutine. 'self.co' is a thread object go .set_position( vmath .vector3( 100 , 0 , 0 )) -- Set initial position end function update (self, dt) local status, y_pos = coroutine .resume( self .co, self ) -- Continue execution of coroutine. if status then -- If the coroutine is still not terminated/dead, use its yielded return value as a new position go .set_position( vmath .vector3( 100 , y_pos, 0 )) end end 
 

Defold scripts

El editor Defold admite la edición de scripts Lua con coloreado de sintaxis y autocompletado. Para completar los nombres de las funciones de Defold, presione Ctrl + Space para que aparezca una lista de las funciones que coincidan con lo que está escribiendo.

Autocompletado

Hay tres tipos de script Lua en Defold, cada uno tiene diferentes bibliotecas Defold disponibles.

Guiones lógicos
Extensión .script . Ejecutar por componentes de script en objetos de juego. Los scripts lógicos se usan generalmente para controlar los objetos del juego y la lógica que une el juego con la carga de niveles, las reglas del juego, etc. Los scripts lógicos tienen acceso a todas las funciones de la biblioteca Defold, excepto las funciones GUI y Render .
Guiones de GUI
Extensión .gui_script . Ejecutado por los componentes de la GUI y que generalmente contiene la lógica necesaria para mostrar los elementos de la GUI, como las pantallas de inicio, los menús, etc. Las secuencias de comandos de la GUI tienen acceso a las funciones de la biblioteca de la GUI .
Renderizar guiones
Extensión .renderscript . Ejecutar por el canal de renderizado y contener la lógica necesaria para representar todos los gráficos de aplicaciones / juegos en cada cuadro. Las secuencias de comandos de renderizado tienen acceso a las funciones de la biblioteca Render .
 

Ejecución de scripts y devoluciones de llamada

Defold ejecuta los guiones de Lua como parte del ciclo de vida del motor y expone el ciclo de vida a través de un conjunto de funciones de devolución de llamada predefinidas. Cuando agrega un componente de script a un objeto de juego, el script se convierte en parte del ciclo de vida del objeto del juego y sus componentes. El script se evalúa en el contexto de Lua cuando se carga, luego el motor ejecuta las siguientes funciones y pasa una referencia a la instancia del componente de script actual como parámetro. Puede usar esta referencia self para almacenar estado en la instancia del componente. self es un objeto userdata que actúa como una tabla Lua pero no puede iterar sobre ella con pairs() o ipairs() .

init(self)

Se invoca cuando el componente se inicializa.

 function init (self) -- These variables are available through the lifetime of the component instance self .my_var = "something" self .age = 0 end 
final(self)

Se invoca cuando el componente se elimina. Esto es útil para fines de limpieza, por ejemplo, si ha generado objetos de juego que deben eliminarse cuando se elimina el componente.

 function final (self) if self .my_var == "something" then -- do some cleanup end end 
update(self, dt)

Llamado una vez cada fotograma. dt contiene el tiempo delta desde el último cuadro.

 function update (self, dt) self .age = self .age + dt -- increase age with the timestep end 
on_message(self, message_id, message, sender)

Cuando los mensajes se envían al componente de script a través de msg.post() el motor llama a esta función del componente receptor.

on_input(self, action_id, action)

Si este componente ha adquirido el enfoque de entrada (ver acquire_input_focus ) el motor llama a esta función cuando se registra la entrada.

on_reload(self)

Se llama a esta función cuando la secuencia de comandos se vuelve a cargar a través de la función del editor de recarga en caliente ( Editar ▸ Recargar recurso ). Es muy útil para depurar, probar y modificar.

 function on_reload (self) print ( self .age) -- print the age of this game object end 
 

Lógica reactiva

Un objeto de juego con un componente de script implementa cierta lógica. A menudo, esa lógica depende de algún factor externo. Una IA enemiga podría reaccionar si el jugador se encuentra dentro de un cierto radio de la IA; una puerta podría desbloquearse y abrirse como resultado de la interacción del jugador, etc., etc.

La función update() permite implementar comportamientos complejos definidos como una máquina de estado que ejecuta cada cuadro; a veces ese es el enfoque adecuado. Pero hay un costo asociado con cada llamada para update() . A menos que realmente necesite la función, debe eliminarla y, en su lugar, tratar de construir su lógica de forma reactiva . Es más barato esperar pasivamente que un mensaje genere una respuesta que sondear activamente el mundo del juego para que los datos respondan. Además, resolver un problema de diseño de manera reactiva también suele conducir a un diseño e implementación más limpios y estables.

Veamos un ejemplo concreto. Supongamos que desea que un componente de script envíe un mensaje 2 segundos después de que se haya iniciado. Luego debe esperar un determinado mensaje de respuesta y luego de recibir la respuesta, debe enviar otro mensaje 5 segundos después. El código no reactivo para eso se vería así:

 function init (self) -- Counter to keep track of time. self .counter = 0 -- We need this to keep track of our state. self .state = "first" end function update (self, dt) self .counter = self .counter + dt if self .counter >= 2.0 and self .state == "first" then -- send message after 2 seconds msg .post( "some_object" , "some_message" ) self .state = "waiting" end if self .counter >= 5.0 and self .state == "second" then -- send message 5 seconds after we received "response" msg .post( "another_object" , "another_message" ) -- Nil the state so we don't reach this state block again. self .state = nil end end function on_message (self, message_id, message, sender) if message_id == hash( "response" ) then -- “first” state done. enter next self .state = "second" -- zero the counter self .counter = 0 end end 

Incluso en este caso bastante simple, tenemos una lógica bastante enredada. Es posible hacer que esto se vea mejor con la ayuda de corutinas en un módulo (ver más abajo), pero intentemos hacer que esto sea reactivo y usemos un mecanismo de sincronización incorporado: animación de la propiedad.

 -- Dummy property only for timing go .property( "dummy" , 0 ) function init (self) -- Wait 2s then call send_first() go .animate( "#" , "dummy" , go .PLAYBACK_ONCE_FORWARD, 0 , go .EASING_LINEAR, 2.0 , 0 , send_first) end function send_first () msg .post( "some_object" , "some_message" ) end function send_second () msg .post( "another_object" , "another_message" ) end function on_message (self, message_id, message, sender) if message_id == hash( "response" ) then -- Wait 5s then call send_second() go .animate( "#" , "dummy" , go .PLAYBACK_ONCE_FORWARD, 0 , go .EASING_LINEAR, 5.0 , 0 , send_second) end end 

Esto es más limpio y más fácil de seguir. Nos deshacemos de variables de estado internas que a menudo son difíciles de seguir a través de la lógica, y que pueden conducir a errores sutiles. También desechamos la función update() completo. Eso alivia el motor de llamar a nuestra secuencia de comandos 60 veces por segundo, incluso si solo está al ralentí.

 

Contextos Lua en Defold

Todas las variables que declaras son por defecto globales, lo que significa que están disponibles a través de todas las partes del contexto del tiempo de ejecución de Lua. Defold tiene una configuración setting_state establecida en game.project que controla este contexto. Si se establece la opción, todos los scripts, scripts de GUI y el script de renderización se evalúan en el mismo contexto de Lua y las variables globales son visibles en todas partes. Si la opción no está configurada, el motor ejecuta scripts, scripts GUI y el script de render en contextos separados.

Contextos

Defold le permite usar el mismo archivo de script en varios componentes de objetos de juego separados. Cualquier variable declarada localmente se comparte entre los componentes que ejecutan el mismo archivo de script.

 -- 'my_global_value' will be available from all scripts, gui_scripts, render script and modules (Lua files) my_global_value = "global scope" -- this value will be shared through all component instances that use this particular script file local script_value = "script scope" function init (self, dt) -- This value will be available on this script component instance self .foo = "self scope" -- this value will be available inside init() and after it's declaration local local_foo = "local scope" print (local_foo) end function update (self, dt) print ( self .foo) print (my_global_value) print (script_value) print (local_foo) -- will print nil, since local_foo is only visible in init() end 
 

Consideraciones de rendimiento

En un juego de alto rendimiento que tiene la intención de correr a una velocidad de 60 FPS, los pequeños errores de rendimiento pueden tener un gran impacto en la experiencia. Hay algunas cosas generales simples a considerar y algunas cosas que pueden no parecer problemáticas.

Comenzando con las cosas simples. En general, es una buena idea escribir código que sea sencillo y que no contenga bucles innecesarios. A veces es necesario repetir las listas de cosas, pero tenga cuidado si la lista de cosas es lo suficientemente grande. Este ejemplo se ejecuta en poco más de 1 milisegundo en una computadora portátil bastante decente, lo que puede marcar la diferencia si cada fotograma tiene solo 16 milisegundos de duración (a 60 FPS) y con el motor, render script, simulación física y mucho más. de eso.

 local t = socket.gettime() local table = {} for i= 1 , 2000 do table [i] = vmath .vector3(i, i, i) end print ((socket.gettime() - t) * 1000 ) -- DE BUG: SCRIPT: 0.40388 

Utilice el valor devuelto por socket.gettime() (segundos desde la época del sistema) para comparar el código sospechoso.

 

Memoria y recolección de basura

La recolección de elementos no utilizados de Lua se ejecuta automáticamente en segundo plano de forma predeterminada y recupera la memoria que ha asignado el tiempo de ejecución Lua. Recolectar mucha basura puede ser una tarea que consume mucho tiempo, por lo que es bueno mantener baja la cantidad de objetos que se deben recolectar.

  • Las variables locales son en sí mismas libres y no generarán basura. (es decir, local v = 42 )
  • Cada nueva cadena única crea un nuevo objeto. Escribir local s = "some_string" creará un nuevo objeto y le asignará s . El propio local no generará basura, pero el objeto de cadena sí lo hará. Usar la misma cadena varias veces no agrega ningún costo de memoria adicional.
  • Cada vez que se ejecuta un constructor de tabla ( { ... } ) se crea una nueva tabla.
  • La ejecución de una declaración de función crea un objeto de cierre. (es decir, ejecutando la function () ... end declaración function () ... end , no llamando a una función definida)
  • Las funciones Vararg ( function(v, ...) end ) crean una tabla para los puntos suspensivos cada vez que se llama a la función (en Lua antes de la versión 5.2, o si no se usa LuaJIT).
  • dofile() y dostring()
  • Objetos de datos de usuario

Hay muchos casos en los que puede evitar la creación de objetos nuevos y, en su lugar, reutilizar los que ya tiene. Por ejemplo. Lo siguiente es común al final de cada update() :

 -- Reset velocity self .velocity = vmath .vector3() 

Es fácil olvidar que cada llamada a vmath.vector3() crea un nuevo objeto. Veamos cuánta memoria usa un vector3 :

 print ( collectgarbage ( "count" ) * 1024 ) -- 88634 local v = vmath .vector3() print ( collectgarbage ( "count" ) * 1024 ) -- 88704. 70 bytes in total has been allocated 

Se han agregado 70 bytes entre las llamadas a collectgarbage() , pero esto incluye asignaciones para más que el objeto vector3 . Cada impresión del resultado de collectgarbage() construye una cadena que en sí misma agrega 22 bytes de basura:

 print ( collectgarbage ( "count" ) * 1024 ) -- 88611 print ( collectgarbage ( "count" ) * 1024 ) -- 88633. 22 bytes allocated 

Entonces un vector3 pesa 70-22 = 48 bytes. Eso no es mucho, pero si creas uno en cada fotograma en un juego de 60 FPS, de repente hay 2.8 kB de basura por segundo. Con 360 componentes de script que cada uno crea un vector3 cada cuadro, estamos viendo 1 MB de basura generada por segundo. Los números pueden agregar upp muy rápidamente. Cuando el tiempo de ejecución de Lua recoge basura, puede consumir muchos preciosos milisegundos, especialmente en plataformas móviles.

Una forma de evitar asignaciones es crear un vector3 y luego seguir trabajando con el mismo objeto. Por ejemplo, para restablecer un vector3 podemos usar la siguiente construcción:

 -- Instead of doing self.velocity = vmath.vector3() which creates a new object -- we zero an existing velocity vector object's components self .velocity.x = 0 self .velocity.y = 0 self .velocity.z = 0 

El esquema de recolección de basura predeterminado puede no ser óptimo para algunas aplicaciones de tiempo crítico. Si ve tartamudeo en su juego o aplicación, es posible que desee sintonizar cómo Lua recoge la basura a través de la función Lua collectgarbage() . Puede, por ejemplo, ejecutar el recopilador durante un corto período de tiempo en cada cuadro con un valor de step bajo. Para tener una idea de cuánta memoria está consumiendo su juego o aplicación, puede imprimir la cantidad actual de bytes de basura con:

 print ( collectgarbage ( "count" ) * 1024 ) 
 

Mejores prácticas

Una consideración de diseño de implementación común es cómo estructurar código para comportamientos compartidos. Varios enfoques son posibles.

Comportamientos en un módulo

Encapsular un comportamiento en un módulo le permite compartir código fácilmente entre los diferentes componentes del script de los objetos del juego (y las secuencias de comandos GUI). Al escribir funciones de módulo, generalmente es mejor escribir un código estrictamente funcional. Hay casos en que el estado almacenado o los efectos secundarios son una necesidad (o conducen a un diseño más limpio). Si tiene que almacenar el estado interno en el módulo, tenga en cuenta que los componentes comparten contextos Lua. Consulte la documentación de los Módulos para más detalles.

Módulo

Además, incluso si es posible tener un código de módulo que modifique directamente las partes internas de un objeto de juego (pasándose a una función de módulo), lo desaconsejamos encarecidamente ya que creará un acoplamiento muy ajustado.

Un objeto de juego auxiliar con comportamiento encapsulado

Al igual que puede contener código de secuencia de comandos en un módulo Lua, puede contenerlo en un objeto de juego con un componente de secuencia de comandos. La diferencia es que si la contiene en un objeto del juego, puede comunicarse con ella estrictamente mediante el envío de mensajes.

Ayudante

Agrupando objeto de juego con objeto de comportamiento auxiliar dentro de una colección

En este diseño, puede crear un objeto de juego de comportamiento que actúe automáticamente sobre otro objeto objetivo del juego, ya sea por un nombre predefinido (el usuario debe cambiar el nombre del objeto objetivo del juego para que coincida) oa través de una URL go.property() que apunte a el objeto objetivo del juego.

Colección

El beneficio de esta configuración es que puede colocar un objeto de juego de comportamiento en una colección que contiene el objeto de destino. Cero código adicional es necesario.

En situaciones en las que necesita administrar grandes cantidades de objetos de juego, este diseño no es preferible ya que el objeto de comportamiento se duplica para cada instancia y cada objeto costará la memoria.

 

 

Fuente: https://www.defold.com/manuals/lua/