Ya he buscado respuestas, pero no pude encontrar el mejor enfoque para manejar costosas funciones / cálculos.
En mi juego actual (un edificio de ciudad basado en fichas 2d) el usuario puede colocar edificios, construir carreteras, etc. Todos los edificios necesitan una conexión a un cruce que el usuario debe colocar en el borde del mapa. Si un edificio no está conectado a este cruce, aparecerá un letrero de "No conectado a la carretera" sobre el edificio afectado (de lo contrario, debe eliminarse). La mayoría de los edificios tienen un radio y pueden estar relacionados entre sí (por ejemplo, un departamento de bomberos puede ayudar a todas las casas dentro de un radio de 30 baldosas). Eso es lo que también necesito actualizar / verificar cuando cambia la conexión de la carretera.
Ayer me encontré con un gran problema de rendimiento. Echemos un vistazo al siguiente escenario: un usuario también puede borrar edificios y carreteras. Entonces, si un usuario ahora rompe la conexión justo después del cruce , necesito actualizar muchos edificios al mismo tiempo . Creo que uno de los primeros consejos sería evitar los bucles anidados (que definitivamente es una gran razón en este escenario), pero tengo que verificar ...
- si un edificio todavía está conectado al cruce en caso de que se haya eliminado una baldosa de la carretera (lo hago solo para los edificios afectados por esa carretera). (Podría ser un problema menor en este escenario)
la lista de mosaicos de radio y obtener edificios dentro del radio (bucles anidados: ¡gran problema!) .
// Go through all buildings affected by erasing this road tile. foreach(var affectedBuilding in affectedBuildings) { // Get buildings within radius. foreach(var radiusTile in affectedBuilding.RadiusTiles) { // Get all buildings on Map within this radius (which is technially another foreach). var buildingsInRadius = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex); // Do stuff. } }
Todo esto descompone mi FPS de 60 a casi 10 por un segundo.
Entonces, ¿podría hacerlo? Mis ideas serían:
- No utiliza el hilo principal (Función de actualización) para este, sino para otro hilo. Podría tener problemas de bloqueo cuando empiezo a usar subprocesos múltiples.
- Usar una cola para manejar muchos cálculos (¿cuál sería el mejor enfoque en este caso?)
- Mantenga más información en mis objetos (edificios) para evitar más cálculos (por ejemplo, edificios en radio).
Usando el último enfoque, podría eliminar una anidación en forma de foreach en su lugar:
// Go through all buildings affected by erasing this road tile.
foreach(var affectedBuilding in affectedBuildings) {
// Go through buildings within radius.
foreach(var buildingInRadius in affectedBuilding.BuildingsInRadius) {
// Do stuff.
}
}
Pero no sé si esto es suficiente. Los juegos como Cities Skylines tienen que manejar muchos más edificios si el jugador tiene un mapa grande. ¿Cómo manejan esas cosas? Puede haber una cola de actualización ya que no todos los edificios se actualizan al mismo tiempo.
¡Espero sus ideas y comentarios!
¡Muchas gracias!
fuente
Respuestas:
Caché de cobertura del edificio
La idea de almacenar en caché la información de qué edificios están dentro del alcance de un edificio efector (que puede almacenar en caché desde el efector o en el afectado) es definitivamente una buena idea. Los edificios (generalmente) no se mueven, por lo que hay pocas razones para rehacer estos costosos cálculos. "A qué afecta este edificio" y "qué afecta a este edificio" es algo que solo necesita verificar cuando se crea o se elimina un edificio.
Este es un intercambio clásico de ciclos de CPU por memoria.
Manejo de información de cobertura por región
Si resulta que está utilizando demasiada memoria para realizar un seguimiento de esta información, vea si puede manejar dicha información por regiones del mapa. Divide tu mapa en regiones cuadradas de
n
*n
losas. Si una región está completamente cubierta por un departamento de bomberos, todos los edificios en esa región también están cubiertos. Por lo tanto, solo necesita almacenar información de cobertura por región, no por edificio individual. Si una región solo está parcialmente cubierta, debe recurrir al manejo de conexiones de construcción en esa región. Entonces, la función de actualización para sus edificios primero verificaría "¿La región en la que se encuentra este edificio está cubierta por un departamento de bomberos?" y si no "¿Este edificio está cubierto individualmente por un departamento de bomberos?". Esto también acelera las actualizaciones, porque cuando se elimina un departamento de bomberos, ya no necesita actualizar los estados de cobertura de 2000 edificios, solo necesita actualizar 100 edificios y 25 regiones.Actualización demorada
Otra optimización que puede hacer es no actualizar todo de inmediato y no actualizar todo al mismo tiempo.
Si un edificio todavía está conectado o no a la red de carreteras no es algo que necesite verificar en cada cuadro (por cierto, también puede encontrar algunas formas de optimizar esto específicamente al analizar un poco la teoría de gráficos). Sería completamente suficiente si los edificios solo lo revisan periódicamente cada pocos segundos después de la construcción del edificio (Y si hubo un cambio en la red de carreteras). Lo mismo se aplica a los efectos de rango de construcción. Es perfectamente aceptable si un edificio solo verifica cada pocos cientos de fotogramas "¿Al menos uno de los departamentos de bomberos que me afectan todavía está activo?"
Por lo tanto, puede hacer que su ciclo de actualización solo haga estos costosos cálculos para unos cientos de edificios a la vez para cada actualización. Es posible que desee dar preferencias a los edificios que se encuentran actualmente en la pantalla, para que los jugadores reciban comentarios inmediatos sobre sus acciones.
En cuanto a Multithreading
Los constructores de ciudades tienden a estar en el lado computacionalmente más costoso, especialmente si quieres permitir que los jugadores construyan realmente grandes y si quieres tener una alta complejidad de simulación. Entonces, a la larga, podría no estar equivocado pensar en qué cálculos en su juego se pueden manejar de forma asíncrona.
fuente
1. Trabajo duplicado .
Su
affectedBuildings
son presumiblemente cerca uno del otro, por lo que los diferentes radios se solapará. Marque los edificios que deben actualizarse y luego actualícelos.2. Estructuras de datos inadecuadas.
claramente debería ser
donde Edificios es un
IEnumerable
con tiempo de iteración constante (por ejemplo, aList<Building>
)fuente