Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





creación de objetos ¿array dinámicos o estáticos?

Iniciado por Hechelion, 17 de Marzo de 2009, 01:30:04 AM

« anterior - próximo »

Hechelion

Buenas.

me encuentro trabajando en la prueba del juego de naves horizontal, estaba programando los disparos y las naves enemigas, creándolos de forma dinámica según aparecen en pantalla y destruyendo los objetos cuando ya no son necesarios, pero me ha entrado la duda si no sería más eficiente tener precargados una cantidad de objetos con una variable que indique si están en uso o no.

por ejemplo, para los disparos de las naves.
si el juego dura 180 segundos y puedes disparar hasta 3 veces por segundo, significa que en el transcurso del programa se habrán creado y destruido un total de 540 objetos, sin embargo en el mejor de los casos en pantalla sólo existirán 10 disparos a la vez, en este caso no sería más eficiente tener un array con 10 objetos_disparo ya creados con una marca que indique si están en uso o no.

[EX3]

Hombre, no se seguro si es lo mas optimo o no por que no se como trabaja internamente la maquina virtual de Visual Basic 6.0 ni si en verdad seria lo correcto en cualquier otro lenguaje. Yo lo que solia hacer las ultimas veces era usar colecciones para el uso de listas dinamicas de objetos en vez de arrays (ojala lo hubiera usado en su dia para dx_lib32, que coñazo me hubiera ahorrado xDDD). No recuerdo si en el cutre-tutorial del matamarcianos que hice hace uno o dos años lo hice asi.

En Visual Basic 6.0 las colecciones son un poco simples pero van de lujo (aunque me quedo con las colecciones tipadas de .NET). La base es el objeto Collection que permite añadir y eliminar objetos a través de una clave que le asignas al elemento en la colección, lo cual según casos viene de lujo para tener localizados ciertos objetos en la colección. Dicha clave si la dejas en blanco suele ser su indice en la colección(de 1 a n) y lo mejor de todo, es que a diferencia de los arcaicos arrays, a las colecciones las puedes recorrer con un For Each cómodamente.

Dentro de lo simples que son las colecciones, con su método añadir,  eliminar y poco mas, definidas como contenedores de tipo Variant, Visual Basic 6 incorpora una pequeña herramienta de creación de clases en la lista de complementos, el "Generador de clases", que te permite definir las clases de un proyecto con todos sus atributos, métodos y propiedades, y permite crear colecciones personalizadas, a medida. No son mas que módulos clase comunes con métodos y propiedades armados contra un objeto collection. De esta forma puedes crear tus propias colecciones tipadas (una colección del tipo de dato u objeto que quieras en vez de tipo Variant) e incluso añadir o modificar la conducta de la colección añadiendo mas funcionalidades como por ejemplo una función de búsqueda.

Échale un vistazo al objeto Collection y al "Generador de clases" en la lista de complementos (si lo tienes instalado) por que seguro te vendrán de perlas para lo que buscas hacer ;)

Al caso practico que preguntabas. Lo que yo haria asi de primeras seria definir a los enemigos una propiedad que defina si estan muertos o vivos (o un simple Enabled As Boolean por ejemplo) y un metodo Update en la coleccion personalizada o un metodo externo contra un simple objeto Collection, segun lo quieras hacer, que en cada ciclo del bucle recorra con un For Each la coleccion buscando que enemigos estan desactivados y eliminandolos mediante el metodo Remove de la coleccion. Un breve ejemplo:

Código (vbnet) [Seleccionar]
' Modulo clase "Class1"
Option Explicit

Public Enabled As Boolean
Public Key As String

' Inicializamos la clase con estado activado:
Private Sub Class_Initialize()
    Me.Enabled = True
End Sub


Código (vbnet) [Seleccionar]
' Formulario "Form1"
Option Explicit

Dim col As New Collection

' Desactivamos el elemento con clave "Gamma":
Private Sub Command1_Click()
    Dim tmp As Class1
    Set tmp = col.Item("Gamma")
    tmp.Enabled = False
End Sub

' Recorremos la coleccion buscando los elementos desactivados y los eliminamos de la colecccion:
Private Sub Command2_Click()
    Dim tmp As Class1
   
    For Each tmp In col
        If Not tmp.Enabled Then Call col.Remove(tmp.Key)
    Next
   
    Debug.Print col.Count ' Muestra el numero de elementos de la coleccion.
End Sub

Private Sub Form_Load()
    Dim tmp As Class1
   
    ' Agregamos 4 instancias de Class1 con diferentes claves personalizadas:
    Set tmp = New Class1
    tmp.Key = "Alfa"
    Call col.Add(tmp, tmp.Key)
   
    Set tmp = New Class1
    tmp.Key = "Beta"
    Call col.Add(tmp, tmp.Key)
   
    Set tmp = New Class1
    tmp.Key = "Gamma"
    Call col.Add(tmp, tmp.Key)
   
    Set tmp = New Class1
    tmp.Key = "Delta"
    Call col.Add(tmp, tmp.Key)
   
    Set tmp = New Class1
    tmp.Key = "Epsilon"
    Call col.Add(tmp, tmp.Key)
   
    Debug.Print col.Count ' Muestra el numero de elementos de la coleccion.
End Sub

Como ves es mucho mas cómodo de lejos que usar arrays dinámicos y tener que ir dejando posiciones vacías, reescalando el array, etc...

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

Hechelion

si conozco las colecciones y las he utilizado en varios programas, pero jamas las he usado sobre clases propias, en este caso lo descarte porque hasta donde tengo entendido (puedo estar equivocado pues nunca lo he probado) cuando creo un nuevo objeto este no copia los datos privados así que vuelvo al mismo problema inicial, necesito varias rutinas cada vez que creo algo v/s tener una cantidad fija precargada y sólo recorrer una de sus propiedades.

[EX3]

Cuando creas un objeto nuevo este se crea con sus propiedades por defecto tal y como estén definidas en el código salvo que modifiques algún valor en el constructor de la misma (método privado Class_Initialize()). El caso es que no entiendo exactamente cual es el problema para no usar colecciones. Si usas un array tendrás que crear igualmente los objetos de 0 inicializando manualmente o mediante un método sus propiedades.

Cita de: Hechelion en 17 de Marzo de 2009, 06:48:39 PM
hasta donde tengo entendido (puedo estar equivocado pues nunca lo he probado) cuando creo un nuevo objeto este no copia los datos privados así que vuelvo al mismo problema inicial
Exactamente te refieres a crear una nueva instancia de una clase?
Código (vbnet) [Seleccionar]
Dim c As Class1
Set c = New Class1

o a copiar una clase? (hacer referencia mas bien)

Código (vbnet) [Seleccionar]
Dim Alpha As Class1, Beta As Class1
' (...) inicializamos Alpha, etc...
Set Beta = Alpha ' Creamos una instancia de Class1 en Beta que hace referencia a la instancia de Alpha. No es una copia de Alpha.


En el primer caso se crea la clase desde 0. En el segundo caso Beta obtiene los mismos valores que Alpha, miembros privados inclusive, pero sus cambios afectan directamente a Alpha (parecido a un puente o acceso directo entre las dos instancias)

No se si es a esto a lo que te refieres (para variar ando algo espeso, sorry  >.<)

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

Hechelion

#4
Pues nuevamente no nos estamos entendiendo Ex3  :'(

No me refiero a crear nuevas instancias al mismo objeto, imagínate tener 10 naves enemigas creadas de esa forma, llamas a la animación que destruye una y vas a destruir las 10  :(. A ver, creo que mejor voy a explicarme lentamente y desde el principio.  :P

1.- Yo creo una nueva instancia a mi clase de animación (en realidad uso una clase especial para las naves que hace referencia a la clase de animación, pero como ejemplo espero se entienda)
Código (vb) [Seleccionar]

Dim oNaveMala as cAnim
Set oNaveMala = New cAnim


2.- Yo inicializo la clase y le entrego varios parámetros donde el más importante es el string que contiene la información de la animación (esos archivos AAD que nombraba en el otro post)
éste paso es muy importante, ya que la clase se encarga de cargar por si sola los PNG que necesite para crear las animaciones del archivo AAD
Código (vb) [Seleccionar]
call oNaveMala.Initialize(parametro_1, Parametros_2, etc. , Ruta_a_archivo_animación)

3.- Necesito una segunda nave mala en pantalla, así que creo una segunda variable
Código (vb) [Seleccionar]
Dim oNaveMala2 as cAnim

4.- como la segunda nave tiene que ser completamente independiente de la primera, lo lógico sería iniciarlizarla desde 0 igual que la primera, pero si hago eso, se volverá a cargar el PNG, tendré 2 instancias independientes, pero tendré 2 veces el mismo PNG en memoria, imagina si en un momento quiero mostrar 100 naves, tendría en memoria 100 imagine iguales. Entonces uno piensa, mejor que volver a inicializarla sería poder copiar toda la información que tiene la variable oNaveMala sobre oNaveMala2 y aquí empiezan los problemas, pues VB6 no tiene nada que haga eso, lo más cercano es el preserve que sólo funciona con los elementos públicos pero no con los privados.
No te preocupes, a la clase cAnim le programé una función muy útil llamada clone que permite clonar toda la información.
Código (vb) [Seleccionar]
Set oNaveMala2 = oNaveMala.Clone

ahora tengo 2 instancias completamente independientes y un solo PNG cargado en memoria. (de esta forma puedo tener 1000 naves en pantalla usando el mismo PNG en memoria)

¿Cual es la duda?
si durante el juego aparecen 1000 naves en total, significa que se ejecutaron 1000 clonaciones  (o suponiendo que no existiera el clone y tuviera que iniciar, habría iniciado 1000 instancias y habría cargado y descargado 1000 veces el mismo PNG, mejor ni hablar del rendimiento en este caso)

ante esa duda me pregunte a mi mismo... yo mismo. Y si en vez de crear, clonar y destruir 1000 instancias, crearas sólo 50 al inicio del nivel y  reutilizarlas ¿será más eficiente? ¿es correcto hacer esto en un juego?.

PD: En el post anterior, a lo de nunca haber probado me refiero a LOAD y UNLOAD de las colecciones.

PD2: Releyendo todo el hilo creo que encontré el problema de comunicación, yo di por asumido que al momento de crear una instancia era necesario entregarle parámetros únicos (como el PNG, la cantidad de animaciones, etc), mientras tú, sólo contemplabas el SET y no un Initialize. Y claro, sin inicializar, clonar o nada de eso crear y destruir 1000 instancias no debe consumir mucho recurso.

[EX3]

Citarcomo la segunda nave tiene que ser completamente independiente de la primera, lo lógico sería iniciarlizarla desde 0 igual que la primera, pero si hago eso, se volverá a cargar el PNG, tendré 2 instancias independientes, pero tendré 2 veces el mismo PNG en memoria, imagina si en un momento quiero mostrar 100 naves, tendría en memoria 100 imagine iguales.
Esto tiene una solucion sencilla y que a parte de evitar duplicaciones de recursos en memoria ayuda a organizarlos mejor. Yo lo primero que suelo hacer al desarrollar la base del motor del juego es crear colecciones de recursos únicos: texturas, sonidos, músicas, etc... En este caso, en vez de cargar y asociar directamente el recurso PNG a la clase de animación lo que habría hecho hubiera sido cargar dicho PNG en la colección de texturas del motor tomando su ruta de carga como clave de acceso en la colección (yo suelo tener empaquetado los recursos por lo que mis rutas suelen ser tipo "textures/enemy/standby.png"). El mecanismo exacto seria tener una función de carga de recursos genérica a la que le pases la clave del recursos a cargar y su tipo (textura, sonido, etc... para que el motor sepa donde almacenarlo) y que al usar una colección como lista de recursos en memoria no te permita cargar dos veces el mismo recurso dado que no puedes duplicar una clave en la colección. De esta forma rápida y sencilla, tu crearías tu clase animación pasandole la clave del PNG de la lista, solo cargaría una vez y todas las instancias apuntarían no al recurso si no al elemento de la colección por su clave.

Citar¿Cual es la duda?
si durante el juego aparecen 1000 naves en total, significa que se ejecutaron 1000 clonaciones  (o suponiendo que no existiera el clone y tuviera que iniciar, habría iniciado 1000 instancias y habría cargado y descargado 1000 veces el mismo PNG, mejor ni hablar del rendimiento en este caso)

ante esa duda me pregunte a mi mismo... yo mismo. Y si en vez de crear, clonar y destruir 1000 instancias, crearas sólo 50 al inicio del nivel y  reutilizarlas ¿será más eficiente? ¿es correcto hacer esto en un juego?.
Hombre, hablando nuevamente desde la teoría y la poca experiencia que tengo en materia, diría algo parecido a lo que dije en otro post, salvo que uses un 8086 de hace 20 años no debería sobrecargar demasiado la creación de objetos de forma dinámica en las maquinas actuales, pero claro, teniendo en cuenta si no vas a estar creando objetos cada segundo y si el constructor del objeto se limita solo a asignar valores a unas pocas variables (nada de cálculos o cargas de datos, etc...). También es cierto que según casos te puede ser mas optimo usar una lista fija de elementos e ir habilitando y deshabilitando en la marcha. Creo de por si que en juegos de este estilo, matamarcianos y shooters de naves, se hacia algo similar, se marca un maximo de enemigos en pantalla y se juega con ese numero de instancias, pero claro, también hablamos de hace 15 o 20 años incluso donde las maquinas no disponían de muchos recursos para meterles caña, de ahí que defienda por mi parte el uso de colecciones y creación dinámica de objetos en pro de la comodidad para el programador, hoy dia que podemos, en vez de usar una lista fija en pro de la optimizacion. Aquí la verdad que ya casi lo dejaría a tu elección, si quieres optimizar usa la lista fija de elementos, que solo incializarias una vez, o usa el sistema de colecciones creando instancias durante la marcha, solo ten en cuenta si esa pequeña optimizacion te merece la pena hoy día.

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

Hechelion

#6
gracias por la respuesta.

Respeto a que la clase cargue el PNG es por un asunto de política, la gracia de la clase animación es que la programé pensando en ser lo más versátil posible, está pensada para trabajar con un solo PNG y hacer una misera animación de una bala hasta aguantar 100 PNG o más si se te ocurre y meter todas las animaciones de una ciudad y todo lo puedes hacer de forma medianamente amigable con una interfaz gráfica.

Si separo la carga gráfica de la clase, ocurre el problema que el encargado de  ordenar los gráficos es el programador y lo que es peor, tendría que entregar los PNG en un orden especifico en caso de haber utilizado más de un PNG por animación, la clase está pensada para precisamente evitar estas situaciones.
La solución que propones es sencilla, pero atenta contra el concepto de versatilidad que trate de programar, por eso me di el trabajo de crear una función clone, que mantiene la versatilidad y evita que los recursos gráficos se repitan en memoria.

saludos







Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.