Me encontré con esta pregunta cuando estaba diseñando un videojuego en C #.
Si consideramos juegos como Battlefield o Call of Duty , cientos o incluso miles de balas vuelan al mismo tiempo. Los eventos se desencadenan constantemente, y por lo que sé, esto absorbe mucha potencia de procesamiento ... ¿o no? Quiero saber cómo varios desarrolladores de juegos manejan las viñetas (2D y 3D) y cuál es el método más eficiente para cada uno.
Leí la pregunta ¿Cómo se simulan las balas en los videojuegos? pero no toca cómo funcionan las viñetas desde la perspectiva del diseño del programa.
Tenía un par de ideas, pero cada una tiene sus inconvenientes:
El método más eficiente que se me ocurrió (para juegos 2D):
Digamos que debía crear una clase llamada Bullet, y durante el tiempo que el usuario mantenga presionado un botón, cada 0.01 segundos se crearía un objeto Bullet. Esta bala tiene:
1 velocidad
2 Posición inicial de donde se está disparando
Textura de 3 sprites
4 Un efecto de golpe
Dado que la viñeta sería su propia clase, podría administrar los oyentes de dibujo, movimiento y acción.
¿No sería difícil para el procesador procesar miles de estos objetos que se instancian y luego destruyen (cuando se activa el efecto de impacto)? Espacio RAM?
Método eficiente para juegos 3D: otro pensamiento que tuve fue:
Digamos que creo una clase de arma. Esta arma tiene varias características, algunas de las cuales:
1 Detecta a dónde apunta el arma y determina si está mirando a un objetivo
2 Activa una animación del tiroteo
3 Tiene un método doDamage () que indica algo para restar salud de lo que sea que apunte el arma
4 Notifica una clase de animación de viñeta cuando se presiona el botón
Entonces podría crear una clase estática, digamos BulletAnimation, que podría recibir una notificación de dónde está el arma que la activó, dónde apunta el arma (para el destino de la bala) e información sobre un sprite y velocidad apropiados para usar para la bala . Esta clase luego dibuja sprites (tal vez en un nuevo hilo, idk) basado en ambas posiciones y el sprite deseado, para simular una bala disparada desde una pistola.
Esto último parece mucho más difícil de codificar, y ¿no se necesitaría mucha potencia de procesamiento para llamar constantemente a la estática para hacer esto por miles de balas a la vez? También sería difícil obtener actualizaciones constantes tanto en las posiciones iniciales como finales.
Mi pregunta es, ¿cuál es la forma más eficiente en que los creadores de juegos lo hacen? ¿Este método cambia de juegos 2D a 3D?
pew-pew-pew
tecnología :)Respuestas:
Ciertamente, puedo ver por qué pensarías que sería difícil simularlos, pero hay suficientes restricciones en las balas (todos los proyectiles, realmente) para que sean más fáciles.
Generalmente se simulan como un punto único, en lugar de como algo con volumen. Esto hace que la detección de colisiones sea significativamente más fácil, ya que ahora solo necesito hacer colisiones contra superficies muy simples, como una línea contra un círculo.
Sabemos cómo se moverán, por lo que no hay mucha información que necesitemos almacenar o calcular para ellos. Su lista fue razonablemente precisa, generalmente tendremos algunas cosas más asociadas, como quién disparó la bala y cuál es su tipo.
Como todos los proyectiles serán muy similares, podemos preasignarlos para evitar toda la sobrecarga de crearlos dinámicamente. Puedo asignar una matriz de 1000 proyectiles, y ahora se puede acceder a ellos con solo un índice, y todos son secuenciales en la memoria, por lo que el procesamiento será rápido.
Tienen una vida útil / rango fijo, por lo que puedo expirar viñetas viejas y reciclar la memoria en viñetas nuevas muy rápidamente.
Una vez que golpean algo, también puedo expirarlos, por lo que tienen una vida limitada.
Como sabemos cuándo se crearon, si necesitamos otros nuevos y no tenemos ninguno gratuito en nuestra lista preasignada, puedo tomar los más antiguos y reciclarlos, y la gente no se dará cuenta si las balas caducan un poco antes .
Se representan como sprites (generalmente) o como modelos de baja poli, y ocupan muy poco espacio en la pantalla, por lo que son rápidos de renderizar.
Teniendo en cuenta todas esas cosas, las balas tienden a ser relativamente baratas. Si nuestro presupuesto alguna vez fue consumido por las balas y renderizándolas, generalmente lo rediseñamos para limitar la cantidad de disparos que puede disparar a la vez (lo verá en muchos juegos de arcade antiguos), use armas de haz que se muevan instantáneamente , o reduzca la velocidad de disparo para asegurarse de que nos mantengamos dentro del presupuesto.
fuente
num_characters * max_bullets_per_character
Probablemente una de las formas más eficientes de implementar viñetas es usar lo que se conoce como hitscan . Su implementación es bastante simple: cuando disparas, verificas a qué apunta el arma (posiblemente usando un rayo para encontrar la entidad / objeto / malla más cercana), y luego lo 'golpeas', haciendo daño. Si desea que parezca que se disparó una bala invisible real y de rápido movimiento, puede simularla agregando un ligero retraso dependiendo de la distancia antes de hacer daño.
Este enfoque esencialmente asume que el proyectil disparado tiene una velocidad infinita, y generalmente se usa para tipos de armas como láseres y haces de partículas / cañones y tal vez algunas formas de rifles de francotirador .
El siguiente enfoque sería modelar la bala disparada como un proyectil, que se modela como su propia entidad / objeto que está sujeto a colisión, y posiblemente la resistencia a la gravedad y / o al aire que altera su dirección y velocidad. Es más complejo que el enfoque hitscan debido a las ecuaciones físicas adicionales, y requiere más recursos debido a que existe un objeto de bala real, pero puede proporcionar viñetas más realistas.
En cuanto a la gestión de las colisiones entre las balas con base de proyectiles y otros objetos en el juego, detección de colisiones se puede simplificar en gran medida por ordenar los objetos en quad o octrees . Los octrees se usan principalmente en juegos 3d, mientras que los quadtrees se pueden usar en juegos 2d o 3d. Las ventajas de usar uno de estos árboles es que puede reducir en gran medida el número de posibles controles de colisión. Por ejemplo, si tiene 20 objetos activos en el nivel, sin usar uno de estos árboles, deberá verificar que los 20 tengan una colisión con la bala. Al dividir los 20 objetos entre las hojas (nodos finales) del árbol, puede reducir el número de controles a la cantidad de entidades presentes en la misma hoja que la viñeta.
En cuanto a estos enfoques: hitscan y proyectiles, ambos se pueden usar libremente en juegos 2d o 3d. Depende más de qué es el arma y de cómo el creador ha decidido que el arma funcionará.
fuente
De ninguna manera soy un experto, pero para responder a su pregunta, sí, necesitaría muchas de esas cosas que menciona.
Para su ejemplo 2D, podría tener una posición y velocidad para una bala. (Es posible que también necesite una distancia máxima o de por vida, dependiendo de cómo implemente sus viñetas). Eso generalmente implicaría 2 valores (x, y). Si fueran flotadores, eso es 16 bytes. Si tiene 100 balas, eso es solo 1600bytes o aproximadamente 1.5k. Eso no es nada en una máquina hoy.
A continuación, mencionas los sprites. Solo necesitarías un solo sprite para representar cada viñeta. Su tamaño dependerá de la profundidad de bits en la que esté dibujando y de cuán grande debería aparecer en la pantalla. Incluso sin comprimir, digamos, 256x256 en flotación total de 32 bits por canal, eso es 1 MB para el sprite. (¡Y eso sería muy grande!) Dibujaría el mismo sprite en cada ubicación de viñeta, pero no requiere memoria adicional para cada copia del sprite. Sería similar para un efecto de impacto.
Usted menciona disparar cada 0.01 segundos. Eso sería 100 balas por segundo de tu arma. ¡Incluso para un arma futurista que es bastante! De acuerdo con este artículo de Wikipedia :
¡Entonces esa sería la velocidad de un helicóptero de ataque!
Para un mundo grande como el que mencionas en Battlefield / Call of Duty / etc., pueden calcular todas esas posiciones de bala, pero no sacarlas todas si la acción está muy lejos. O pueden no simularlos hasta que te acerques. (Tengo que admitir que estoy adivinando un poco sobre esta parte, ya que no he trabajado en nada tan grande).
fuente
Creo que estás subestimando lo rápido que son las computadoras. Esto fue a veces un problema en los sistemas de los años 80 y 90. Es en parte por qué los Space Invaders originales no te permitirán disparar otra bala hasta que la actual haya alcanzado. Algunos juegos sufrían de "desaceleración" si había demasiados sprites en la pantalla.
¿Pero hoy en día? Tiene suficiente potencia de procesamiento para miles de operaciones por píxel que se requieren para texturizar e iluminar. No hay problema con miles de objetos en movimiento; Esto le permite hacer un terreno destructible (por ejemplo, Red Faction) donde cada fragmento procesa la colisión con otros fragmentos y sigue una curva balística.
Debe tener un poco de cuidado algorítmicamente: no puede hacer el enfoque ingenuo de verificar cada objeto contra cualquier otro objeto cuando tiene miles de objetos. Las balas generalmente no verifican las colisiones con otras balas.
Una pequeña anécdota: la primera versión de Doom en red (el original de los 90) envió un paquete a través de la red por cada bala disparada. Cuando uno o más jugadores obtuvieron la ametralladora, esto podría abrumar fácilmente la red. Los años 90 estaban llenos de personas que jugaban ilícitamente Doom en la universidad o en redes de trabajo que se metían en problemas con sus administradores de red cuando la red se volvía inutilizable.
fuente
Estoy lejos de ser un experto, pero he estado trabajando en un juego de disparos 2D multijugador en mi tiempo libre.
Mi metodo
Hay diferentes clases de viñetas entre el cliente y el servidor (incluso cuando se juega sin conexión, una instancia del servidor se inicia en un proceso separado y se conecta mediante el juego 'principal').
En cada tic (60 por segundo), el cliente calcula el rumbo entre el puntero del mouse del jugador y el centro de la pantalla (donde está su personaje) y es parte de la información enviada al servidor. Si el jugador también está disparando en ese momento (suponiendo que el arma esté cargada y lista), se crea una instancia de bala del lado del servidor, simplemente con algunas coordenadas y un daño base (que se deriva de las estadísticas del arma que disparó eso). La instancia de viñeta luego usa algunas funciones matemáticas para calcular una velocidad X e Y a partir del rumbo que reunimos del cliente.
Por cada tic posterior, la bala se mueve por esas coordenadas y reduce su daño base en una cantidad predefinida. Si este valor cae por debajo de 1, o si golpea un objeto sólido en el mundo, la instancia de bala se elimina, y como las colisiones de puntos de prueba son increíblemente baratas en 2D, incluso las armas de disparo rápido tienen un impacto insignificante en el rendimiento.
En cuanto al cliente, la información de la viñeta en realidad no se recibe a través de la red (demostró ser un desperdicio en las pruebas), en cambio, como parte de la actualización por tick, cada personaje tiene un booleano 'disparado', que si es verdadero, el cliente crea un local objeto de bala que funciona casi exactamente como los servidores, la única diferencia es que tiene un sprite.
Esto significa que, aunque la viñeta que ve no es una representación totalmente precisa de la misma en el servidor, cualquier diferencia sería apenas perceptible en absoluto para un jugador, y los beneficios de la red superan cualquier inconsistencia.
Nota sobre diferentes métodos
Algunos juegos, incluido el mío, mueven las viñetas cada tic como si fueran objetos físicos, mientras que otros simplemente crean un vector en la dirección del disparo, o calculan el camino completo de la viñeta en el tic que se crea, por ejemplo en Counter- Juegos de huelga. Hay algunos pequeños trucos del lado del cliente para disfrazarlo, como una animación del disparo de bala, pero para todos los efectos, cada bala es solo un láser .
Con los modelos 3D que pueden tener hitboxes complejos, es estándar probar las colisiones contra un cuadro delimitador simple PRIMERO, y si eso tiene éxito, pasar a una detección de colisión más 'detallada'.
fuente
Se llama detección de colisión. Las computadoras de 8 bits hicieron esto usando gráficos de misiles de jugador en hardware. Los motores de juegos modernos usan motores de física y álgebra lineal. La dirección actual de un arma se representa como un vector 3D. Eso proporciona una línea infinita en la dirección del fuego. Cada objeto en movimiento tiene una o más esferas de delimitación, ya que es el objeto más simple para detectar una colisión con una línea. Si los dos se cruzan, es un golpe, si no, no hay golpe. Pero el escenario puede estar en el camino, por lo que también debe verificarse (utilizando volúmenes de límite jerárquico). El objeto más cercano que tiene una intersección es el que ha sido golpeado.
fuente