Mostrar información de un plano
#1
Escrito 06 septiembre 2011 - 07:31
Hace tiempo que no me pasaba por aquí porque he andado muy liado en el trabajo, encima no hacía trabajo de programador, así que entre el poco tiempo libre y las pocas ganas de abrir el Delphi.... pues eso jejeje
Ahora estoy haciendo una pequeña aplicación para mi mismo, para intentar no oxidarme en esto de la programación y se me presenta una duda y, como no, recurro a vosotros que siempre estáis ahí jejeje
Os explico. Tengo un plano dividido en cuadrículas de unos 650x550 y quiero mostrar este plano por pantalla en cualquier componente que tenga celdas (StringGrid, DBGrid,.......). Está claro que todas las celdas en pantalla no se verán, por lo que se tendrá que hacer scroll vertical y horizontal. Las celdas se pintarán de color según una serie de condiciones definidas por el usuario. También habrá un TScrollBar para poder hacer "zoom" en el plano (mostrar más o menos celdas tanto en horizontal como en vertical).
De momento tengo una tabla en Firebird con el plano estilo
x: integer
y: integer
editable: smallint
El campo "editable" indica si esa casilla puede o no ser editada (cambiada de color) por el usuario.
Luego hay otras tablas que contendrán las celdas y color que el usuario quiere resaltar.
Actualmente tengo un TStringGrid y en el OnDrawCell realizo una consulta a la base de datos para saber con qué color pintar esa celda. Claro, cuando hay muchas celdas visualizadas, el proceso se ralentiza mucho y queda feo.
Se os ocurre alguna cosa para poder hacer ésto algo más "ágil" o mejorarlo
Gracias
Nos leemos
cadetill
#2
Escrito 06 septiembre 2011 - 07:49
Saludos
#3
Escrito 06 septiembre 2011 - 08:08
Salud OS
Archivos adjuntos
#4
Escrito 06 septiembre 2011 - 09:27
Ante todo gracias por las rápidas respuestas.
Intento explicarme mejor
Esto es para un juego en el que paso mis ratos libres y va sobre la época de los romanos en la Península Ibérica (Hispania por si a alguno le suena jejeje). Bien, el mapa de este juego es una cuadrícula de unos 679x539. Cada casilla puede ser o no colonizable (serían las celdas fijas y representan ríos, mar y montañas). Las casillas colonizables pueden estar o no ocupadas. Si están ocupadas, se necesita saber nombre de jugador, rango, puntuación y demás información. Si no está ocupada, el tipo de terreno.
En la base de datos guardo el mapa en sí (coordenas y tipo de terreno), los jugadores (nombre, puntuación, rango,....) y sus ciudades (nombre y posición). Esto es más o menos a grandes rasgos.
Luego el usuario pone una serie de condiciones (ej: resaltar de un color a los jugadores de un determinado rango o una determinada alianza).
Lo que tiene que hacer el programa es mirar en una determinada celda qué hay y si lo que hay concuerda con alguna de las condiciones del usuario para pintar o no esa celda.
Como decía, ahora tengo un TStringGrid (podría valerme el GridPanel si está en D2010 pero tendría el mismo problema). He pensado dos formas de hacer lo que necesito.
1.- Al iniciar el programa rellenar cada celda del mapa con un objeto con la información y en el OnDrawCell comparar la información del objeto con las condiciones del usuario. Problema: tarda en arrancar el programa y consume mucho recurso (son 365.981 celdas a rellenar con sus respectivos objetos).
2.- Dejar el TStringGrid sin información y en el OnDrawCell hacer una consulta a la base de datos para sacar la información de esa celda, compararla con las condiciones del usuario y actuar en consecuencia (pintar o no la celda). Con esto solucionamos el problema del arranque lento pero si el usuario quiere ver muchas celdas a la vez, se ralentiza el dibujado del TStringGrid.
No se si ahora se entiende más jejejejeje
Alguna idea para mejorar el rendimiento de ésto?
Gracias de nuevo
Nos leemos
cadetill
#5
Escrito 06 septiembre 2011 - 10:42
¿Y si pintas en un TImage por cuadrículas directamente en su canvas (CopyRect) o te construyes un array de imágenes?
Saludos.
#6
Escrito 06 septiembre 2011 - 01:26
No, no es necesario el uso de un Grid, simplemente me decidí por este componente por la facilidad de uso para el usuario final (scroll horizontal y vertical, selección de una celda, ....).
La idea del TImage no me desagrada, es más, dado que sería yo el que se encargara de pintar, podría mostrar un TProgressBar durante el proceso de pintado o algo similar para "entretener" al usuario mientras se pinta el mapa.
Dado que la consulta de cada "celda" no me la quita nadie y ese tiempo lo tengo que perder sí o sí, la carga inicial del TImage está clara, si al iniciar la aplicación se muestras 25x25 posiciones, son 625 consultas. La duda me llega con el scroll. Si en la carga inicial se visualizan las columnas de 1 a 25 y me desplazo una posición para ver las columnas 2 a 26 (mediante un TButton, un TScrollBar o lo que sea), ¿tendría que volver a realizar las consultas de las columnas 2 a 25? ¿O existe alguna manera de "desplazar" la imagen para no tener que volver a lanzar las consultas?
Gracias
Nos leemos
cadetill
#7
Escrito 06 septiembre 2011 - 01:29
http://www.delphiacc...lphi-24/puzzle/
Salud OS
#8
Escrito 06 septiembre 2011 - 01:47
Si conoces de antemano el tamaño máximo del TImage puedes introducirlo en un TScrollBox, por ejemplo. De todas formas siempre puedes agrandarlo y rellenarlo.
Saludos.
#9
Escrito 06 septiembre 2011 - 03:37
Me miraré con detenimiento el ejemplo de chackall, pero me parece que se va de mis conocimientos jejejejeje Así que me toca "estudiarlo" a fondo.
Gracias por los comentarios
Nos leemos
cadetill
#10
Escrito 06 septiembre 2011 - 04:03
Buenas de nuevo
Me miraré con detenimiento el ejemplo de chackall, pero me parece que se va de mis conocimientos jejejejeje Así que me toca "estudiarlo" a fondo.
Gracias por los comentarios
Nos leemos
cadetill
No te preocupes, que se va del conociento muchos de por aquí.
Salud OS
#11
Escrito 07 septiembre 2011 - 10:31
Cada vez que pidas un color de una casilla, almacenala en un stringlist o donde mejor veas, un array dinamico tambien valdria, y luego, en sucesivas consultas, usa el color almacenado en la matriz si encuentras esas coordenadas en tu lista, y solo si no lo encuentra, lo lees de la base de datos, lo almacena en el array para evitar volver a pedirlo, y entonces lo dibujas.
Esto hace que cada cuadricula se lea de la base de datos una vez, y justo en el momento que se necesita, no antes.
Es solo una idea, igual necesitas refrescos continuos a la base de datos porque el mapa cambia en tiempo real... pero bueno, la idea podria ser modificada segun el caso.
Otro problema es que el redibujado en si del mapa aun sea lento:
Puedes no tratar de dibujar el mapa puntito a puntito: En temas de ray tracing pasa igual, se tardan horas en obtener la imagen completa, pero puedes dibujar cuadriculas de 4x4 inicialmente, con el color correspondiente a la primera casilla -esquina sup. izda.- y en una segunda pasada "refinar" el dibujo consultando los colores de las otras 3 esquinas de 2x2, luego de 1x1, y terminarias.
Yo lo hacia con pixeles, asi que empezaba digamos con cuadros de 64x64, bajaba a 32x32... hasta llegar a 1x1. La imagen era visible pero borrosa pasados solo unos minutos, y solo si querias ver mas detalle lo dejabas continuar refinando. Tu podrias probar a inciar con 8x8, solo tiene que ser una potencia de 2.
Visto asi, un usuario puede hacer un zoom, y cuando la imagen aun se ve a 4x4, ya puede hacer un segundo zoom, lo que agiliza mucho el juego.
Un ultimo problema es que el redibujado, aun siendo ahora mas agil, te bloquee el uso del juego mientras ocurre.
Una idea al respecto: Pon un "spooler" de cuadriculas a ponerles color, podria ser un ObjectList relleno cobn objetos a medida tipo (x, y, tamaño), donde inicialmente pones todos los recuadros de 4x4 (o 8x8, segun decidas) y un proceso aparte, en otro hilo, puedes ir leyendo la primera cuadricula a definir su color, pedir el color -de la esquina sup. izda.- a la base de datos o el array de puntos ya leidos, dibujar el recuadro, eliminar la cuadricula ya dibjada y crear 3 nuevas de 2x2 (de la mitad de tamaño de la que borras) en las esquinas que dejamos sin "refinar".
Como esos nuevos recuadros mas chicos van al final de la lista, el dibujado se hara en pasadas cada vez mas finas, pero al ser otro hilo, este proceso no interfiere en el juego, el jugador puede en cualquier momento cambiar el zoom: Las cuadriculas pendientes se borran y se añaden de nuevo los recuadros de 4x4 con el nuevo zoom. El otro hilo se percatara, borrara todo -o no- y comenzara a dibujar cuadros nuevos grandes tapando lo anterior (por eso no es necesario borrar el mapa anterior realmente)... el redibujado del mapa se haria totalmente en "paralelo" al juego en si, y en pasadas cada vez mas "finas".
#12
Escrito 07 septiembre 2011 - 02:42
Saludos.
#13
Escrito 08 septiembre 2011 - 05:32
Quizá tengas que batallar más en lograr darle forma a tus mapas pero te darán la seguridad y velocidad de que estará hecho perfectamente y luego por debajo de esa gráfica lo demás es pura lógica.
Saludos,
#14
Escrito 09 septiembre 2011 - 11:10
Desarrollando la idea de Sergio, de usar un array y consultar los datos sólo bajo demanda, creo que no es descabellado definir una matriz de punteros con esas dimensiones, 650 x 550. Suponiendo que la información de cada celda ocupe unos 100 bytes (echando por arriba), el conjunto una vez relleno ocuparía unos 35 MBytes, algo que cualquier máquina moderna tiene de sobra, y si es dedicada a juegos como el tuyo más aún . Y en el caso de no estar rellena de inicio se quedaría en unos 3 MBytes que es lo que ocuparían sólo los punteros (pensando ya en punteros de 8 bytes en sistemas de 64 bits).
Ejemplo de estructuras:
PCellInfo = ^TCellInfo; TCellInfo = record Color : TColor; Editable : Boolean; etc ... end; TPlano : array [650, 550] of PCellInfo;
Al arrancar, inicializas todos los elementos de la matriz a nil, y cuando necesitas dibujar o acceder a una celda compruebas si ya ha sido asignada, en caso contrario creas un puntero PCellInfo, lo asignas a esa celda y haces la consulta a la BBDD de esa/s celda/s, de esa forma sólo se ralentiza lo justo y cuando las celdas no han sido todavía visualizadas o accedidas.
Si sigues este sistema creo que es mejor usar un TDrawGrid, dibujando como pensabas mediante el evento OnDrawCell accediendo a esa matriz, ya que el TStringGrid consume más memoria ya que está pensado para almacenar las cadenas de cada celda, aunque también puedes usar el TStringGrid accediendo a su propiedad Objects[ACol, ARow] para almacenar esa información y comprobando igualmente si es nil o no cada vez.
Saludos
#15
Escrito 19 septiembre 2011 - 10:49
Pues muchas gracias por todas las sugerencias.
El tema de la matriz que habéis expuesto es algo que "hacía" al principio y creo que volveré a ello. Lo que pasa es que no usaba una matriz, sino un objeto que era asignado a cada celda del TStringGrid. Tardaba un poco en arrancar el programa, pero luego el redibujado era más o menos ligero (dependiendo de la cantidad de celdas que se visualizaban). Eso sí, como dice Andrés hay un consumo de memoria.
Iré mirándome las distintas opciones y cuando tenga algo que más o menos funcione, os lo cuelgo para que me hagáis críticas jejejeje
Nos leemos
cadetill
#16
Escrito 20 septiembre 2011 - 02:49
Eso quiere decir que partes del mapa que hace rato que no visitas podrias ser borradas si hace falta almacenar nuevas zonas, y te aseguras de que la vcantidad de memoria usada por este invento estara por debajo de la cantidad que tengas disponible, que incluso podrias hacer configurable o definirla dinamicamente en funcion de la memoria libre que haya en el sistema.
Y ademas es muy simple de programar!
#17
Escrito 20 septiembre 2011 - 09:38
Saludos