Siempre me he preguntado hasta qué punto los videojuegos utilizan la orientación a objetos, especialmente los grandes proyectos 3D. Creo que estaría bien que tanto troll
y werewolf
heredaron sus atributos desde enemy
y sobreescribí algunos de ellos. Pero tal vez las clases son demasiado pesadas para ser prácticas, no sé ...
18
Respuestas:
Para llegar a tu ejemplo, explicaré mis pensamientos sobre Entites en videojuegos. No discutiré las ventajas y desventajas generales de los objetos y las clases, ni su papel en los videojuegos.
Las entidades en los videojuegos pequeños se definen principalmente por clases separadas, en los grandes juegos a menudo se definen en archivos. Hay dos enfoques generales a seguir:
Extensión : en su ejemplo
Troll
yWerewolf
se extenderáEnemy
, que se extiendeEntity
.Entity
se ocupa de las cosas básicas como el movimiento, la salud, etc., mientrasEnemy
que declararía a las subclases como enemigas y haría otras cosas (Minecraft sigue este enfoque).Basado en componentes : el segundo enfoque (y mi favorito) es una
Entity
clase, que tiene componentes. Un componente podría serMoveable
oEnemy
. Estos componentes se encargan de una cosa como el movimiento o la física. Este método rompe largas cadenas de extensión y minimiza el riesgo de encontrarse con un conflicto. Por ejemplo: ¿Cómo puedes hacer enemigos no movibles, si heredan de un personaje movible? .En grandes videojuegos como World of Warcraft o Starcraft 2, las Entidades no son clases, sino conjuntos de datos que especifican toda la Entidad. La secuencia de comandos también es importante, porque define lo que la entidad no hace en una clase, sino en una secuencia de comandos (si es única, no cosas como moverse).
Actualmente estoy programando un RTS y adopto el enfoque basado en componentes con archivos Descriptor y Script para Entites, Skills, Items y todo lo demás dinámico en el juego.
fuente
component
es a en este contexto. Un enlace sería muy apreciado.Lamentablemente, este enfoque del polimorfismo, popular en los años 90, ha demostrado ser una mala idea en la práctica. Imagina que agregas 'lobo' a la lista de enemigos; bueno, comparte algunos atributos con el hombre lobo, por lo que querrás consolidarlos en una clase base compartida, por ejemplo. 'WolfLike'. Ahora agregas 'Humano' a la lista de enemigos, pero los hombres lobo a veces son humanos, por lo que también comparten atributos, como caminar sobre dos patas, poder hablar, etc. ¿Creas una base común 'Humanoide' para humanos y hombres lobo? , ¿y luego tienes que detener a los hombres lobo derivados de WolfLike? ¿O multiplica la herencia de ambos, en cuyo caso, qué atributo tiene prioridad cuando algo en Humanoid choca con algo en WolfLike?
El problema es que probar y modelar el mundo real como un árbol de clases es ineficaz. Tome 3 cosas y probablemente pueda encontrar 4 formas diferentes de factorizar su comportamiento en compartido y no compartido. Esta es la razón por la que muchos han optado por un modelo modular o basado en componentes, en el que un objeto se compone de varios objetos más pequeños que forman el todo, y los objetos más pequeños se pueden intercambiar o cambiar para crear el comportamiento que desea. Aquí puede tener solo 1 clase de Enemigo, y contiene subobjetos o componentes de cómo camina (por ejemplo, Bípedo contra Cuadrúpedo), sus métodos de ataque (por ejemplo, mordedura, garra, arma, puño), su piel (verde para trolls, peludos para hombres lobo y lobos), etc.
Esto todavía está completamente orientado a objetos, pero la noción de lo que es un objeto útil es diferente de lo que solía enseñarse en los libros de texto. En lugar de tener un gran árbol de herencia de varias clases abstractas y varias clases concretas en las puntas del árbol, generalmente tiene solo 1 clase concreta que representa un concepto abstracto (por ejemplo, 'enemigo') pero que contiene más clases concretas que representan conceptos más abstractos (por ejemplo, ataques, tipo de piel). A veces, estas segundas clases se pueden implementar mejor a través de 1 nivel de herencia (por ejemplo, una clase de ataque base y varias clases derivadas para ataques específicos), pero el árbol de herencia profunda se ha ido.
En la mayoría de los idiomas modernos, las clases no son "pesadas". Son solo otra forma de escribir código y, por lo general, simplemente envuelven el enfoque de procedimiento que habría escrito de todos modos, excepto con una sintaxis más fácil. Pero, por supuesto, no escriba una clase donde una función servirá. Las clases no son intrínsecamente mejores, solo cuando hacen que el código sea más fácil de administrar o comprender.
fuente
No estoy seguro de qué quieres decir con "demasiado pesado", pero C ++ / OOP es la lengua franca del desarrollo de juegos por las mismas buenas razones por las que se usa en otros lugares.
Hablar de volar cachés es un problema de diseño, no una característica del lenguaje. C ++ no puede ser tan malo ya que se ha utilizado en juegos durante los últimos 15-20 años. Java no puede ser tan malo, ya que se utiliza en entornos de tiempo de ejecución muy ajustados de teléfonos inteligentes.
Ahora a la pregunta real: durante muchos años las jerarquías de clase se hicieron profundas y comenzaron a sufrir problemas relacionados con eso. En tiempos más recientes, las jerarquías de clases se han aplanado y la composición y otros diseños basados en datos están siendo más que una herencia basada en la lógica.
Todo es POO y todavía se usa como línea de base al diseñar un nuevo software, solo se enfoca de manera diferente en lo que encarnan las clases.
fuente
Las clases no implican ninguna sobrecarga en tiempo de ejecución. El polimorfismo en tiempo de ejecución solo llama a través de un puntero de función. Estos gastos generales son extremadamente mínimos en comparación con otros costos como llamadas de Draw, sincronización de concurrencia, E / S de disco, conmutadores de modo de kernel, mala ubicación de memoria, uso excesivo de la asignación de memoria dinámica, mala elección de algoritmos, uso de lenguajes de script y la lista sucede. Hay una razón por la que la orientación a objetos esencialmente ha suplantado lo que vino antes, y es porque es mucho mejor.
fuente
Una cosa a tener en cuenta es que los conceptos de código orientado a objetos a menudo son más valiosos que su implementación en lenguajes. En particular, tener que realizar miles de llamadas de actualización virtual en entidades en un bucle principal puede causar un desastre en la memoria caché en C ++ en plataformas como 360 o PS3, por lo que la implementación podría evitar palabras clave "virtuales" o incluso "de clase" por el bien de velocidad
A menudo, aunque seguirá los conceptos OO de herencia y encapsulación, solo los implementará de manera diferente a cómo lo hace el lenguaje (por ejemplo, plantillas curiosamente recursivas, composición en lugar de herencia (el método basado en componentes descrito por Marco)). Si la herencia siempre se puede resolver en tiempo de compilación, los problemas de rendimiento desaparecen, pero el código puede volverse un poco complicado con plantillas, implementaciones RTTI personalizadas (una vez más, las incorporadas suelen ser muy lentas) o la lógica de tiempo de compilación en macros etc.
En Objective-C para iOS / Mac hay problemas similares, pero peores, con el esquema de mensajería (incluso con las cosas sofisticadas de almacenamiento en caché) ...
fuente
El método que intentaré usar combina la herencia de clases y los componentes. (En primer lugar, no convierto al Enemigo en una clase, lo convierto en un componente). No me gusta la idea de usar una clase de entidad genérica para todo y luego agregar los componentes necesarios para desarrollar la entidad. En cambio, prefiero que una clase agregue automáticamente componentes (y valores predeterminados) en el constructor. Luego, las subclases agregan sus componentes adicionales en su constructor, por lo que la subclase tendría sus componentes y sus componentes de clase primaria. Por ejemplo, una clase de Arma agregaría componentes básicos comunes a todas las armas, y luego la subclase de Espada agregaría cualquier componente adicional específico para espadas. Todavía estoy debatiendo si usar activos text / xml para definir valores para entidades (o espadas específicas, por ejemplo), o hacer todo eso en código, o cuál es la combinación correcta. Esto aún permite que el juego use la información de tipo y el polimorfismo del lenguaje de código, y hace que ObjectFactories sea mucho más simple.
fuente