Mi objetivo es crear un sistema de elementos modular / tan genérico como sea posible que pueda manejar cosas como:
- Artículos actualizables (+6 Katana)
- Modificadores de estadísticas (+15 de destreza)
- Modificadores de objeto (% X posibilidad de hacer daño Y, posibilidad de congelar)
- Artículos recargables (personal mágico con 30 usos)
- Establecer elementos (Equipar 4 piezas de conjunto X para activar la función Y)
- Rareza (común, única, legendaria)
- Desencantable (se rompe en algunos materiales de artesanía)
- Craftable (puede fabricarse con ciertos materiales)
- Consumible (5min% de poder de ataque X, cura +15 hp)
* Pude resolver características que están en negrita en la siguiente configuración.
Ahora intenté agregar muchas opciones para reflejar lo que tengo en mente. No planeo agregar todas estas características necesarias, pero me gustaría poder implementarlas como mejor me parezca. Estos también deben ser compatibles con el sistema de inventario y la serialización de datos.
Estoy planeando no usar la herencia en absoluto, sino más bien un enfoque basado en datos / componente de entidad. Inicialmente pensé en un sistema que tiene:
- BaseStat: una clase genérica que contiene estadísticas sobre la marcha (también se puede usar para artículos y estadísticas de personajes)
- Elemento: una clase que contiene datos como la lista de, nombre, tipo de elemento y cosas relacionadas con la interfaz de usuario, actionName, descripción, etc.
- IWeapon: interfaz para arma. Cada arma tendrá su propia clase con IWeapon implementado. Esto tendrá Ataque y una referencia a las estadísticas de los personajes. Cuando el arma está equipada, sus datos (estadística de la clase de artículo) se inyectarán en la estadística de personaje (cualquiera que sea el BaseStat que tenga, se agregará a la clase de personaje como una bonificación de estadística) Entonces, por ejemplo, queremos producir una espada (pensando en producir clases de ítems con json) para que espada agregue 5 ataques a las estadísticas de los personajes. Entonces tenemos un BaseStat como ("Attack", 5) (también podemos usar enum). Esta estadística se agregará a la estadística de "Ataque" del personaje como BonusStat (que sería una clase diferente) al equiparla. Entonces, una clase llamada Sword implementa IWeapon se creará cuando 'Se crea la clase de artículo . Por lo tanto, podemos inyectar estadísticas de personajes en esta espada y al atacar, puede recuperar la estadística de ataque total de la estadística de personaje e infligir daño en el método de ataque.
- BonusStat: es una forma de agregar estadísticas como bonificaciones sin tocar BaseStat.
- IConsumable: la misma lógica que con IWeapon. Agregar estadísticas directas es bastante fácil (+15 hp), pero no estoy seguro de agregar armas temporales con esta configuración (% x para atacar durante 5 min).
- IUpgradeable: esto se puede implementar con esta configuración. Estoy pensando en UpgradeLevel como una estadística base, que se incrementa al actualizar el arma. Cuando se actualiza, podemos volver a calcular el BaseStat del arma para que coincida con su nivel de actualización.
Hasta este punto, puedo ver que el sistema es bastante bueno. Pero para otras funciones, creo que necesitamos algo más, porque, por ejemplo, no puedo implementar la función Craftable en esto, ya que mi BaseStat no podría manejar esta función y aquí es donde me quedé atascado. Puedo agregar todos los ingredientes como una estadística, pero eso no tendría sentido.
Para facilitarle su contribución, aquí hay algunas preguntas con las que puede ayudar:
- ¿Debo continuar con esta configuración para implementar otras funciones? ¿Sería posible sin herencia?
- ¿Hay alguna forma de pensar para implementar todas estas características sin herencia?
- Acerca de los modificadores de elementos, ¿cómo podría uno lograr eso? Porque es muy genérico en su naturaleza.
- ¿Qué se puede hacer para facilitar el proceso de construcción de este tipo de arquitectura, alguna recomendación?
- ¿Hay alguna fuente que pueda cavar que esté relacionada con este problema?
- Realmente trato de evitar la herencia, pero ¿crees que esto se resolvería / lograría con la herencia con facilidad y al mismo tiempo lo mantendría bastante sostenible?
No dude en responder una sola pregunta, ya que mantuve preguntas muy amplias para poder obtener conocimiento de diferentes aspectos / personas.
EDITAR
Siguiendo la respuesta de @ jjimenezg93, creé un sistema muy básico en C # para probar, ¡funciona! Vea si puede agregarle algo:
public interface IItem
{
List<IAttribute> Components { get; set; }
void ReceiveMessage<T>(T message);
}
public interface IAttribute
{
IItem source { get; set; }
void ReceiveMessage<T>(T message);
}
Hasta ahora, IItem e IAttribute son interfaces base. No había necesidad (que se me ocurriera) de tener una interfaz / atributo base para el mensaje, por lo que crearemos directamente una clase de mensaje de prueba. Ahora para las clases de prueba:
public class TestItem : IItem
{
private List<IAttribute> _components = new List<IAttribute>();
public List<IAttribute> Components
{
get
{
return _components;
}
set
{
_components = value;
}
}
public void ReceiveMessage<T>(T message)
{
foreach (IAttribute attribute in Components)
{
attribute.ReceiveMessage(message);
}
}
}
public class TestAttribute : IAttribute
{
string _infoRequiredFromMessage;
public TestAttribute(IItem source)
{
_source = source;
}
private IItem _source;
public IItem source
{
get
{
return _source;
}
set
{
_source = value;
}
}
public void ReceiveMessage<T>(T message)
{
TestMessage convertedMessage = message as TestMessage;
if (convertedMessage != null)
{
convertedMessage.Execute();
_infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
Debug.Log("Message passed : " + _infoRequiredFromMessage);
}
}
}
public class TestMessage
{
private string _messageString;
private int _messageInt;
public string _particularInformationThatNeedsToBePassed;
public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
{
_messageString = messageString;
_messageInt = messageInt;
_particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
}
//messages should not have methods, so this is here for fun and testing.
public void Execute()
{
Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
}
}
Estas son las configuraciones necesarias. Ahora podemos usar el sistema (lo siguiente es para Unity).
public class TestManager : MonoBehaviour
{
// Use this for initialization
void Start()
{
TestItem testItem = new TestItem();
TestAttribute testAttribute = new TestAttribute(testItem);
testItem.Components.Add(testAttribute);
TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
testItem.ReceiveMessage(testMessage);
}
}
Adjunte esta secuencia de comandos TestManager a un componente en la escena y podrá ver en la depuración que el mensaje se pasó con éxito.
Para explicar las cosas: cada elemento del juego implementará la interfaz IItem y cada atributo (el nombre no debe confundirte, significa que la característica / sistema del elemento. Al igual que Actualizable o desencantable) implementará IAttribute. Luego tenemos un método para procesar el mensaje (se explicará por qué necesitamos un mensaje en otro ejemplo). Entonces, en contexto, puede adjuntar atributos a un elemento y dejar que el resto lo haga por usted. Lo cual es muy flexible, porque puede agregar / eliminar atributos a gusto. Entonces un pseudoejemplo sería desencantable. Tendremos una clase llamada Disenchantable (IAttribute) y en el método Disenchant, pedirá:
- Enumere los ingredientes (cuando el artículo está desencantado, qué artículo se debe dar al jugador) nota: IItem se debe extender para que tenga ItemType, ItemID, etc.
- int resultModifier (si implementa una especie de impulso de la función desencantar, puede pasar un int aquí para aumentar los ingredientes recibidos cuando está desencantado)
- int failureChance (si el proceso de desencantar tiene una posibilidad de falla)
etc.
Esta información será proporcionada por una clase llamada DisenchantManager, recibirá el elemento y formará este mensaje de acuerdo con el elemento (ingredientes del elemento cuando esté desencantado) y la progresión del jugador (resultModifier y failureChance). Para pasar este mensaje, crearemos una clase DisenchantMessage, que actuará como un cuerpo para este mensaje. Entonces DisenchantManager completará un DisenchantMessage y lo enviará al elemento. El artículo recibirá el mensaje y lo pasará a todos sus atributos adjuntos. Dado que el método ReceiveMessage de la clase Disenchantable buscará un DisenchantMessage, solo el atributo Disenchantable recibirá este mensaje y actuará en consecuencia. Espero que esto aclare las cosas tanto como lo hizo para mí :).