unity - juegos en c#
Juego de arquitectura (4)
Tengo una pregunta sobre un juego XNA que estoy creando, pero también es una pregunta genérica para juegos futuros. Estoy haciendo un juego de Pong y no sé exactamente qué actualizar dónde, así que explicaré mejor a qué me refiero. Tengo un juego de clase, una paleta y una pelota y, por ejemplo, quiero verificar las colisiones entre la pelota con los límites de la pantalla o las paletas, pero me encuentro con dos enfoques para hacer esto:
Enfoque de nivel superior : haga que las propiedades de las paletas y las bolas sean públicas y estén en el juego.
Enfoque de nivel inferior : proporciono cada información que necesito (los límites de la pantalla y la información de las paletas) a la clase de pelota (por parámetro o en una clase estática pública común) y en la actualización de Ball.Update ¿verifico si hay colisiones?
Supongo que mi pregunta de una manera más genérica es:
¿Un objeto necesita saber cómo actualizarse y dibujarse a sí mismo, incluso teniendo dependencias de niveles más altos que de alguna manera se les proporcionan?
o
¿Es mejor procesarlo en niveles más altos en Game.Update o Game.Draw o usar Managers para simplificar el código?
Creo que esta es una pregunta de modelo de lógica de juego que se aplica a cada juego. No sé si dejé clara mi pregunta, si no, no dude en preguntar.
Estoy de acuerdo con lo que dijo Andrew. Solo estoy aprendiendo XNA también y en mis clases, por ejemplo, tu clase de pelota. Tendría al menos un método Update (gametime) y un método Draw (). Por lo general, un Initialize (), Load () también. Luego, desde la clase de juego principal, llamaré a esos métodos de sus primos respectivos. Esto fue antes de que aprendiera sobre GameComponent. Aquí hay un buen artículo sobre si debes usar eso. http://www.nuclex.org/blog/gamedev/100-to-gamecomponent-or-not-to-gamecomponent
La parte difícil de responder a tu pregunta es que estás preguntando a ambos: "qué debo hacer ahora, por Pong" y "qué debo hacer más tarde, en algún juego genérico".
Para hacer Pong, ni siquiera necesitas clases de pelota y paddle, porque son básicamente solo posiciones. Solo pega algo como esto en tu clase de Juego:
Vector2 ballPosition, ballVelocity;
float leftPaddlePosition, rightPaddlePosition;
Luego, actualícelos y dibújelos en el orden que más le convenga en las funciones Update
y Draw
su juego. ¡Fácil!
Pero, supongamos que desea crear múltiples bolas, y las bolas tienen muchas propiedades (posición, velocidad, rotación, color, etc.): es posible que desee crear una clase o estructura de Ball
que pueda instanciar (lo mismo ocurre con las paletas). Incluso podría mover algunas funciones a esa clase en las que son independientes (una función de Draw
es un buen ejemplo).
Pero mantenga el concepto de diseño de la misma forma: todo el manejo de la interacción objeto-objeto (es decir, el juego) ocurre en su clase de Game
.
Todo esto está bien si tienes dos o tres elementos de juego diferentes (o clases).
Sin embargo, vamos a postular un juego más complicado. Tomemos el juego básico de pong, agregue algunos elementos de pinball como el balón múltiple y las aletas controladas por el jugador. Agreguemos algunos elementos de Snake, digamos que tenemos una "serpiente" controlada por la IA, así como algunos objetos de recolección que pueden golpear las bolas o la serpiente. Y, en buena medida, digamos que las palas también pueden disparar láseres como en Space Invaders y los rayos láser pueden hacer diferentes cosas dependiendo de lo que golpeen.
Golly eso es un gran lío de interacción! ¿Cómo vamos a sobrellevarlo? ¡No podemos ponerlo todo en el juego!
¡Sencillo! Creamos una interfaz (o una clase abstracta o una clase virtual) de la que derivará cada "cosa" (o "actor") de nuestro mundo de juego. Aquí hay un ejemplo:
interface IActor
{
void LoadContent(ContentManager content);
void UnloadContent();
void Think(float seconds);
void UpdatePhysics(float seconds);
void Draw(SpriteBatch spriteBatch);
void Touched(IActor by);
Vector2 Position { get; }
Rectangle BoundingBox { get; }
}
(Esto es solo un ejemplo. No hay una " interfaz de verdadero actor " que funcione para cada juego, tendrás que diseñar el tuyo. Por eso no me gusta DrawableGameComponent
).
Tener una interfaz común permite que el juego solo hable sobre los actores, en lugar de tener que conocer cada tipo de juego. Solo queda hacer cosas comunes a cada tipo: detección de colisiones, dibujo, actualización, carga, descarga, etc.
Una vez que estés en el actor, puedes comenzar a preocuparte por tipos específicos de actores. Por ejemplo, este podría ser un método en Paddle
:
void Touched(IActor by)
{
if(by is Ball)
((Ball)by).BounceOff(this.BoundingBox);
if(by is Snake)
((Snake)by).Kill();
}
Ahora, me gusta hacer que la bola rebote por la paleta, pero es realmente una cuestión de gusto. Podrías hacerlo al revés.
Al final, deberías poder incluir a todos tus actores en una gran lista que simplemente puedes recorrer en el juego.
En la práctica, podría terminar teniendo varias listas de actores de diferentes tipos por razones de rendimiento o simplicidad de código. Esto está bien, pero en general, trate de atenerse al principio de que el Juego solo sepa sobre actores genéricos.
Los actores también pueden querer consultar qué otros actores existen por varias razones. Así que dale a cada actor una referencia al juego y haz pública la lista de actores en el juego (no es necesario ser muy estricto con el público / privado cuando escribas el código de juego y es tu propio código interno).
Ahora, incluso podría ir un paso más allá y tener múltiples interfaces. Por ejemplo: uno para renderizar, uno para scripting y AI, uno para física, etc. Luego, tenga múltiples implementaciones que puedan componerse en objetos.
Esto se describe en detalle en este artículo . Y tengo un ejemplo simple en esta respuesta . Este es un próximo paso apropiado si comienza a descubrir que su interfaz de actor único está comenzando a convertirse en más de un "árbol" de clases abstractas.
Podrías ver tu bola y tu pala como un componente de tu juego, y XNA te da la clase base GameComponent
que tiene un método Update(GameTime gameTime)
que puedes anular para hacer la lógica. Además, también está la clase DrawableGameComponent
, que viene con su propio método Draw
para anular. Cada clase GameComponent
tiene también una propiedad del Game
que contiene el objeto del juego que los creó. Allí puede agregar algunos Servicios que su componente puede usar para obtener información por sí mismo.
El enfoque que desee realizar, ya sea tener un objeto "maestro" que maneje cada interacción, o proporcionar la información a los componentes y hacer que reaccionen ellos mismos, depende totalmente de usted. El último método es preferido en proyectos más grandes. Además, esa sería la forma orientada a los objetos para manejar las cosas, para dar a cada entidad sus propios métodos de Actualizar y Dibujar.
También puede optar por comenzar a pensar en cómo los diferentes componentes del juego necesitan comunicarse entre sí.
Ball y Paddle, ambos son objetos en el juego y en este caso, Objetos rendibles, móviles. La paleta tiene los siguientes criterios.
Solo se puede mover hacia arriba y hacia abajo.
- La pala está fijada a un lado de la pantalla, o al fondo.
- La paleta puede ser controlada por el usuario (1 vs computadora o 1 vs 1)
- La paleta puede ser renderizada.
- La paleta solo se puede mover a la parte inferior o superior de la pantalla, no puede pasar sus límites
La pelota tiene los siguientes criterios.
No puede salir de los límites de la pantalla.
- Se puede hacer
- Dependiendo de dónde sea golpeado en la paleta, puedes controlarlo indirectamente (algo de física simple)
- Si va por detrás de la paleta se termina la ronda.
- Cuando se inicia el juego, la pelota generalmente está unida a la paleta de la persona que perdió.
Identificando los criterios comunes puedes extraer una interfaz.
public interface IRenderableGameObject
{
Vector3 Position { get; set; }
Color Color { get; set; }
float Speed { get; set; }
float Angle { get; set; }
}
También tienes algunos GamePhysics
public interface IPhysics
{
bool HasHitBoundaries(Window window, Ball ball);
bool HasHit(Paddle paddle, Ball ball);
float CalculateNewAngle(Paddle paddleThatWasHit, Ball ball);
}
Luego hay algo de lógica de juego.
public interface IGameLogic
{
bool HasLostRound(...);
bool HasLostGame(...);
}
Esta no es toda la lógica, pero debería darle una idea de lo que debe buscar, porque está creando un conjunto de bibliotecas y funciones que puede usar para determinar qué sucederá y qué puede suceder y cómo debe hacerlo. actuar cuando esas cosas pasan.
Además, mirando esto, puede refinar y refactorizar esto para que sea un mejor diseño.
Conoce tu dominio y anota tus ideas. No planificar es planificar el fracaso