Hacer powerups en un sistema basado en componentes

29

Estoy empezando a entender realmente el diseño basado en componentes. No sé cuál es la forma "correcta" de hacer esto.

Aquí está el escenario. El jugador puede equipar un escudo. El escudo se dibuja como una burbuja alrededor del jugador, tiene una forma de colisión separada y reduce el daño que el jugador recibe de los efectos de área.

¿Cómo se crea tal escudo en un juego basado en componentes?

Donde me confundo es que el escudo obviamente tiene tres componentes asociados.

  • Reducción de daños / filtrado
  • Un sprite
  • Un colisionador.

Para empeorar las cosas, diferentes variaciones de escudo podrían tener incluso más comportamientos, todos los cuales podrían ser componentes:

  • impulsar la salud máxima del jugador
  • regeneración de la salud
  • desviación del proyectil
  • etc.

  1. ¿Estoy pensando demasiado en esto? ¿Debería el escudo ser un súper componente?
    Realmente creo que esta es una respuesta incorrecta. Entonces, si cree que este es el camino a seguir, explique.

  2. ¿Debería ser el escudo su propia entidad que rastrea la ubicación del jugador?
    Eso podría dificultar la implementación del filtro de daños. También difumina las líneas entre los componentes y las entidades adjuntas.

  3. ¿Debería ser el escudo un componente que alberga otros componentes?
    Nunca he visto o escuchado algo así, pero tal vez es común y todavía no soy lo suficientemente profundo.

  4. ¿Debería el escudo ser solo un conjunto de componentes que se agregan al reproductor?
    Posiblemente con un componente adicional para administrar los demás, por ejemplo, para que todos puedan eliminarse como grupo. (accidentalmente dejar atrás el componente de reducción de daños, ahora eso sería divertido).

  5. ¿Algo más que sea obvio para alguien con más experiencia en componentes?

código_deft
fuente
Me tomé la libertad de hacer tu título más específico.
Tetrad

Respuestas:

11

¿Debería ser el escudo su propia entidad que rastrea la ubicación del jugador? Eso podría dificultar la implementación del filtro de daños. También difumina las líneas entre los componentes y las entidades adjuntas.

Editar: Creo que no hay suficiente "comportamiento autónomo" para una entidad separada. En este caso específico, un escudo sigue al objetivo, funciona para el objetivo y no lo sobrevive. Si bien tiendo a estar de acuerdo en que no hay nada de malo en el concepto de "objeto de escudo", en este caso estamos lidiando con el comportamiento, que encaja perfectamente en un componente. Pero también soy un defensor de las entidades puramente lógicas (a diferencia de los sistemas de entidades completos en los que puede encontrar componentes de transformación y renderizado).

¿Debería ser el escudo un componente que alberga otros componentes? Nunca he visto o escuchado algo así, pero tal vez es común y todavía no soy lo suficientemente profundo.

Véalo en una perspectiva diferente; agregar un componente también agrega otros componentes, y al eliminarlos, los componentes adicionales también desaparecen.

¿Debería el escudo ser solo un conjunto de componentes que se agregan al reproductor? Posiblemente con un componente adicional para administrar los demás, por ejemplo, para que todos puedan eliminarse como grupo. (accidentalmente dejar atrás el componente de reducción de daños, ahora eso sería divertido).

Esto podría ser una solución, promovería la reutilización, sin embargo, también es más propenso a errores (para el problema que mencionó, por ejemplo). No es necesariamente malo. Es posible que descubras nuevas combinaciones de hechizos con prueba y error :)

¿Algo más que sea obvio para alguien con más experiencia en componentes?

Voy a elaborar un poco.

Creo que notó cómo algunos componentes deben tener prioridad sin importar cuándo se hayan agregado a una entidad (esto también respondería a su otra pregunta).

También voy a suponer que estamos usando comunicación basada en mensajes (por el bien de la discusión, es solo una abstracción sobre una llamada de método por el momento).

Cada vez que se "instala" un componente de protección, los manejadores de mensajes del componente de protección se encadenan con un orden específico (superior).

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

El componente "estadísticas" instala un manejador de mensajes "daños" en el índice In / Invariant / Normal. Cada vez que se recibe un mensaje de "daño", disminuya el HP por su cantidad de "valor".

Comportamiento bastante estándar (poner algo de resistencia al daño natural y / o rasgos raciales, lo que sea).

El componente de escudo instala un manejador de mensajes de "daño" en el índice In / Pre / High.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

Puede ver que esto es bastante flexible, aunque requeriría una planificación cuidadosa al diseñar la interacción de componentes, ya que tendrá que determinar en qué parte de la tubería de manejo de mensajes se instalan los controladores de eventos de mensajes de componentes.

¿Tiene sentido? Avísame si puedo agregar más detalles.

Editar: con respecto a las instancias de múltiples componentes (dos componentes de armadura). Puede realizar un seguimiento del recuento total de instancias en una sola instancia de entidad (sin embargo, esto elimina el estado por componente) y seguir agregando controladores de eventos de mensaje, o asegurarse de que sus contenedores de componentes permitan tipos de componentes duplicados por adelantado.

Raine
fuente
Respondió "No" a la primera pregunta sin dar ninguna razón. Enseñar a otros se trata de ayudarlos a comprender el razonamiento detrás de cualquier decisión. En mi opinión, el hecho de que en RL un campo de fuerza sería una "entidad física" separada de su propio cuerpo es suficiente para permitir que sea una entidad separada en el código. ¿Puedes sugerir buenas razones para sugerir por qué ir por esta ruta es malo?
Ingeniero
@ Nick, de ninguna manera estoy tratando de enseñarle algo a nadie, más bien compartiendo lo que sé sobre el tema. Sin embargo, voy a agregar una justificación detrás de ese "no" que con suerte eliminará ese voto negativo desagradable :(
Raine
Su punto de autonomía tiene sentido. Pero usted nota: "en este caso estamos tratando con el comportamiento". Verdadero: comportamiento que involucra un objeto físico completamente separado (la forma de colisión del escudo). Para mí, una entidad se vincula a un cuerpo de física (o conjunto compuesto de cuerpos conectados, por ejemplo, por articulaciones). ¿Cómo reconcilias esto? Por mi parte, me sentiría incómodo agregando un accesorio físico "ficticio" que se activaría solo si el jugador usa un escudo. OMI inflexible, difícil de mantener en todas las entidades. Considere también, un juego donde los cinturones de escudo mantienen los escudos incluso después de la muerte (Dune).
Ingeniero
@Nick, la mayoría de los sistemas de entidades tienen componentes tanto lógicos como gráficos, por lo que, en este caso, tener una entidad para un escudo es absolutamente razonable. En los sistemas de entidad puramente lógicos, la "autonomía" es el producto de cuán complejo es un objeto, sus dependencias y su vida útil. Al final, el requisito es el rey - y teniendo en cuenta que no hay un consenso real acerca de lo que es un sistema de entidad, hay un montón de espacio para soluciones a medida con el proyecto :)
Raine
@deft_code, avíseme si puedo mejorar mi respuesta.
Raine
4

1) ¿Estoy pensando demasiado en esto? ¿Debería el escudo ser un súper componente?

Tal vez, depende de qué tan reutilizable desee que sea su código y si tiene sentido.

2) ¿Debería el escudo ser su propia entidad que rastrea la ubicación del jugador?

No, a menos que este escudo sea una especie de criatura que pueda caminar independientemente en algún momento.

3) ¿Debería ser el escudo un componente que alberga otros componentes?

Esto se parece mucho a la entidad, por lo que la respuesta es no.

4) ¿Debería el escudo ser solo un conjunto de componentes que se agregan al reproductor?

Es probable.

"Reducción de daños / filtrado"

  • funcionalidad del componente de escudo central.

"Un sprite"

  • ¿Hay alguna razón por la que no puedas agregar otro SpriteComponent a tu entidad de personaje (en otras palabras, más de un componente de cierto tipo por entidad)?

"Un colisionador"

  • ¿Estás seguro de que necesitas otro? Esto depende de tu motor de física. ¿Puedes enviar un mensaje al ColliderComponent de la entidad de personaje y pedirle que cambie de forma?

"Aumenta la salud máxima del jugador, la regeneración de salud, la desviación del proyectil, etc."

  • otros artefactos podrían hacer esto (espadas, botas, anillos, hechizos / pociones / santuarios visitantes, etc.), por lo que estos deberían ser componentes.
Guarida
fuente
3

Un escudo, como entidad física , no es diferente de cualquier otra entidad física , por ejemplo, un avión no tripulado que te rodea (y que, de hecho, ¡podría ser un tipo de escudo!). Por lo tanto, haga que el escudo sea una entidad lógica separada (lo que le permite mantener sus propios componentes).

Dale a tu escudo un par de componentes: un componente Físico / Espacial para representar su forma de colisión, y un componente DamageAffector que tiene una referencia a alguna entidad a la que aplicará un daño aumentado o reducido (por ejemplo, tu personaje jugador) cada vez que la entidad sostener el DamageAffector sufre daños. Por lo tanto, tu jugador recibe daño "por poder"

Establece la posición de la entidad de escudo en la posición del jugador en cada tic. (Escriba una clase de componente reutilizable que haga esto: escriba una vez, use muchas veces).

Tendrá que crear la entidad de escudo, por ejemplo. en recoger un powerup. Utilizo un concepto genérico llamado Emitter, que es un tipo de componente de entidad que genera nuevas entidades (generalmente mediante el uso de una EntityFactory a la que hace referencia). El lugar donde decida ubicar el emisor depende de usted, por ejemplo. póngalo en un encendido y haga que se active cuando se recoja el encendido.


¿Debería ser el escudo su propia entidad que rastrea la ubicación del jugador? Eso podría dificultar la implementación del filtro de daños. También difumina las líneas entre los componentes y las entidades adjuntas.

Existe una línea muy fina entre los subcomponentes lógicos (espacial, IA, ranuras de armas, procesamiento de entrada, etc., etc.) y los subcomponentes físicos. Debe decidir en qué lado se encuentra, ya que esto define en gran medida qué tipo de sistema de entidad tiene. Para mí, el subcomponente Física de mi Entidad maneja las relaciones jerárquicas físicas (como las extremidades en un cuerpo, piense en los nodos de escena), mientras que los controladores lógicos mencionados anteriormente son típicamente lo que están representados por los componentes de su entidad, en lugar de estos "fixtures" físicos individuales.

Ingeniero
fuente
3

¿Debería ser el escudo un componente que alberga otros componentes?

Quizás no alberga otros componentes, pero controla la vida útil de los subcomponentes. Entonces, en un pseudocódigo aproximado, su código de cliente agregaría este componente "escudo".

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}
Tétrada
fuente
No está claro qué thissignifica en su respuesta. ¿Se thisrefiere al componente Escudo o se refería a la entidad que usa el escudo, su padre? La confusión podría ser mi culpa. "Basado en componentes" es algo vago. En mi versión de entidades basadas en componentes, una entidad es simplemente un contenedor de componentes con alguna funcionalidad mínima propia (nombre de objeto, etiquetas, mensajes, etc.).
deft_code
Sería menos confuso si usara gameObjecto algo así. Es una referencia al objeto / entidad del juego actual / lo que sea que posea los componentes.
Tetrad
0

Si su sistema de componentes permite secuencias de comandos, el componente de protección podría ser casi un súper componente que solo llama a una secuencia de comandos por su parámetro "efecto". De esta forma, mantiene la simplicidad de un solo componente para los escudos y descarga toda la lógica de lo que realmente hace a los archivos de script personalizados que se alimentan a los escudos mediante las definiciones de su entidad.

Hago algo similar para mi componente Moveable, contiene un campo que es de secuencia de comandos de reacción clave (una subclase de secuencia de comandos en mi motor). Esta secuencia de comandos define un método que sigue mi mensaje de entrada. como tal, simplemente puedo hacer algo como esto en mi archivo de definición de plantilla

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

luego, en mi componente móvil durante el registro de mensajes, registro los scripts Método Do (código en C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

Por supuesto, esto se basa en mi método Do siguiendo el patrón de funciones que toma mi RegisterHandler. En este caso es (remitente IComponent, argumento de tipo de referencia)

así que mi "script" (en mi caso también C # solo compila el tiempo de ejecución) define

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

y mi clase base KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

luego, cuando un componente de entrada envía un mensaje del tipo MessageTypes.InputUpdate con el tipo como tal

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

El método en el script que estaba vinculado a ese mensaje y tipo de datos se activará y manejará toda la lógica.

El código es bastante específico para mi motor, pero la lógica debería ser funcional en cualquier caso. Hago esto para muchos tipos para mantener la estructura de componentes simple y flexible.

exnihilo1031
fuente