Il mio obiettivo è creare un sistema di articoli modulare / il più generico possibile in grado di gestire cose come:
- Oggetti aggiornabili (+6 Katana)
- Modificatori stat (+15 destrezza)
- Modificatori oggetto (% X possibilità di infliggere Y danni, possibilità di congelare)
- Articoli ricaricabili (personale magico con 30 utilizzi)
- Imposta oggetti (equipaggia 4 pezzi di set X per attivare la funzione Y)
- Rarità (comune, unico, leggendario)
- Disenchantable (si rompe in alcuni materiali di fabbricazione)
- Craftable (può essere realizzato con determinati materiali)
- Consumabili (5min% X potenza d'attacco, cure +15 cv)
* Sono stato in grado di risolvere funzionalità che sono audaci nella seguente configurazione.
Ora ho provato ad aggiungere molte opzioni per riflettere ciò che ho in mente. Non ho intenzione di aggiungere tutte queste funzionalità necessarie, ma vorrei essere in grado di implementarle a mio avviso. Anche questi dovrebbero essere compatibili con il sistema di inventario e la serializzazione dei dati.
Sto pianificando di non usare l'ereditarietà ma piuttosto un approccio basato su entità / dati. Inizialmente ho pensato a un sistema che ha:
- BaseStat: una classe generica che contiene statistiche in movimento (può essere utilizzata anche per statistiche di oggetti e personaggi)
- Item: una classe che contiene dati come elenco di, nome, tipo di elemento e cose relative a interfaccia utente, actionName, descrizione ecc.
- IWeapon: interfaccia per l'arma. Ogni arma avrà la sua classe con IWeapon implementato. Questo avrà Attacco e un riferimento alle statistiche del personaggio. Quando l'arma è equipaggiata, i suoi dati (stat della classe dell'oggetto) verranno iniettati nella stat del personaggio (qualunque sia BaseStat, verranno aggiunti alla classe del personaggio come bonus Stat) Quindi, per esempio, vogliamo produrre una spada (pensando di produce classi di oggetti con json), quindi la spada aggiungerà 5 attacchi alle statistiche del personaggio. Quindi abbiamo un BaseStat come ("Attack", 5) (possiamo usare anche enum). Questa statistica verrà aggiunta alla statistica "Attacco" del personaggio come BonusStat (che sarebbe una classe diversa) dopo averla equipaggiata. Quindi una classe chiamata Sword implementa IWeapon verrà creata quando 'La classe dell'oggetto è stata creata. Quindi possiamo iniettare le statistiche dei personaggi in questa spada e quando attacca, può recuperare le statistiche di attacco totali dalle statistiche dei personaggi e infliggere danni nel metodo di attacco.
- BonusStat: è un modo per aggiungere statistiche come bonus senza toccare BaseStat.
- IConsumable: stessa logica di IWeapon. L'aggiunta di statistiche dirette è abbastanza semplice (+15 CV) ma non sono sicuro di aggiungere armi temporanee con questa configurazione (% x per attaccare per 5 minuti).
- IUpgradeable: questo può essere implementato con questa configurazione. Sto pensando a UpgradeLevel come una statistica di base, che aumenta con l'aggiornamento dell'arma. Una volta aggiornato, possiamo ricalcolare la BaseStat dell'arma in modo che corrisponda al suo livello di Aggiornamento.
Fino a questo punto, posso vedere che il sistema è abbastanza buono. Ma per altre funzionalità, penso che abbiamo bisogno di qualcos'altro, perché ad esempio non riesco a implementare la funzionalità Craftable in questo in quanto il mio BaseStat non sarebbe in grado di gestire questa funzione ed è qui che mi sono bloccato. Posso aggiungere tutti gli ingredienti come Stat ma non avrebbe senso.
Per semplificare il tuo contributo, ecco alcune domande che potresti aiutare con:
- Devo continuare con questa configurazione per implementare altre funzionalità? Sarebbe possibile senza eredità?
- C'è un modo in cui ti viene in mente di implementare tutte queste funzionalità senza ereditarietà?
- A proposito dei modificatori di oggetti, come si può ottenere questo? Perché è molto generico nella sua natura.
- Cosa si può fare per facilitare il processo di costruzione di questo tipo di architettura, eventuali raccomandazioni?
- Ci sono fonti che posso scavare che sono legate a questo problema?
- Cerco davvero di evitare l'ereditarietà, ma pensi che questi sarebbero risolti / raggiunti con eredità con facilità, mantenendola abbastanza mantenibile?
Sentiti libero di rispondere a una sola domanda mentre ho tenuto le domande molto ampie in modo da poter ottenere conoscenze da diversi aspetti / persone.
MODIFICARE
Seguendo la risposta di @ jjimenezg93, ho creato un sistema molto semplice in C # per i test, funziona! Vedi se puoi aggiungere qualcosa:
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);
}
Finora, IItem e IAttribute sono interfacce di base. Non c'era bisogno (che io possa pensare) di avere un'interfaccia / attributo di base per il messaggio, quindi creeremo direttamente una classe di messaggi di prova. Ora per le classi di test:
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);
}
}
Queste sono le impostazioni necessarie. Ora possiamo usare il sistema (segue è per 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);
}
}
Allega questo script TestManager a un componente nella scena e puoi vedere nel debug che il messaggio è stato passato con successo.
Per spiegare le cose: ogni oggetto del gioco implementerà l'interfaccia IItem e ogni attributo (il nome non deve confonderti, significa che l'elemento / il sistema dell'elemento. Come aggiornabile o disincantabile) implementerà IAttribute. Quindi abbiamo un metodo per elaborare il messaggio (il motivo per cui abbiamo bisogno del messaggio verrà spiegato in un ulteriore esempio). Quindi, nel contesto, puoi associare gli attributi a un elemento e lasciare che il resto faccia per te. Che è molto flessibile, perché puoi aggiungere / rimuovere gli attributi a tuo agio. Quindi uno pseudo-esempio sarebbe Disenchantable. Avremo una classe chiamata Disenchantable (IAttribute) e nel metodo Disenchant, chiederà:
- Elenca gli ingredienti (quando l'oggetto è disincantato, quale oggetto dovrebbe essere dato al giocatore) nota: IItem dovrebbe essere esteso per avere ItemType, ItemID ecc.
- int resultModifier (se si implementa una sorta di potenziamento della funzione di disincanto, è possibile passare un int qui per aumentare gli ingredienti ricevuti quando disincantati)
- int failureChance (se il processo di disincanto ha una probabilità di errore)
eccetera.
Queste informazioni saranno fornite da una classe chiamata DisenchantManager, riceverà l'oggetto e formerà questo messaggio in base all'oggetto (ingredienti dell'articolo quando disincantato) e alla progressione del giocatore (resultModifier e failureChance). Per passare questo messaggio, creeremo una classe DisenchantMessage, che fungerà da corpo per questo messaggio. Quindi DisenchantManager popolerà un DisenchantMessage e lo invierà all'elemento. L'articolo riceverà il messaggio e lo passerà a tutti i suoi attributi allegati. Poiché il metodo ReceiveMessage della classe Disenchantable cercherà un DisenchantMessage, solo l'attributo Disenchantable riceverà questo messaggio e agirà su di esso. Spero che questo cancella le cose tanto quanto ha fatto per me :).