Strutturazione di un sistema di archiviazione per un gioco

2

Sto lavorando a un gioco multiplayer , in cui un giocatore ha molti depositi in cui archiviare gli oggetti.

archiviazione

Lo storage è il sistema che consente all'utente di depositare oggetti e riprenderli. Esempio di tipi di archiviazione:

  • Spazio pubblicitario : come lo spazio pubblicitario di WoW, puoi aggiungere elementi, rilasciare oggetti, spostare oggetti dallo slot allo slot.
  • Banca : il Bank è ciò che puoi chiamare, un super-inventario, che ha un limite di circa 300 articoli per scheda . La banca può avere un massimo di 10 schede che memorizzeranno gli articoli. gli elementi totali consentiti nella banca sono 3000 (10 schede moltiplicate per 300 voci per scheda) . Come mai, in banca, tutti gli articoli sono impilabili.
  • Equipaggiamento : l'equipaggiamento è uguale all'inventario, solo con gli slot ordinati, cioè il timone, il corpo, le gambe e ci sono un totale di 8 slot.
  • Petti e altro : come banco senza schede.

Un oggetto in gioco

Ogni elemento ha il proprio ID oggetto. Solo alcuni elementi possono essere impilabili, a meno che il tipo di archiviazione non sia Bank o chest.

Ho creato un sistema che mi faciliterà la gestione degli archivi, e c'è la classe astratta, che ogni classe di tipi di memoria la estenderà:

public abstract class PlayerItemStorage {

    private int size;

    /**
     * The items
     */
    private PlayerItem[] items;

    /**
     * Sometimes, by default, the storage items are id + 1, due to
     * the client's interfaces. No exact explanation on it, but the only
     * feature that doesn't do that is equipment and possible BoB
     */
    private boolean increasedItem = true;

    public PlayerItemStorage(int storageSize) {
        this.items = new PlayerItem[storageSize];
        this.size = storageSize;
    }

    public PlayerItemStorage(PlayerItem[] items) {
        this.items  = items;
        this.size = items.length;
    }

    /**
     * Resets the storage
     */
    public void resetStorage() {
        this.items = new PlayerItem[this.size];
    }

    /**
     * Gets the item list
     * @return PlayerItem list
     */
    public PlayerItem[] getItems() {
        return this.items;
    }

    /**
     * Counts how many items there are with the same id.
     * @param id Item id
     * @return count
     */
    public int count(int id) {
        int count = 0;
        for (PlayerItem item : this.items) {
            if (this.getId(item) == id) {
                count++;
            }
        }
        return count;
    }

    /**
     * Counts how many items there are in the inventory
     * not including amount, just the item objects themself.
     * @return amount
     */
    public int countItems() {
        int count = 0;
        for (PlayerItem item : this.items) {
            if (this.getId(item) > 0 && item.getAmount() > 0) {
                count++;
            }
        }
        return count;       
    }

    /**
     * Gets the length of the storage
     * @return
     */
    public int getLength() {
        return this.items.length;
    }

    /**
     * Checks how many free slots the storage has
     * @return
     */
    public int freeSlots() {
        int slots = 0;
        for (PlayerItem item : this.items) {
            if (this.getId(item) <= 0) {
                slots++;
            }
        }       
        return slots;
    }

    /**
     * Finds an item id in the earliest slot.
     * @param id Item ID
     * @return Slot id
     */
    public int find(int id) {
        for (int i = 0; i < this.items.length; i++) {
            PlayerItem item = this.items[i];
            if ( this.getId(item) == id && item.getAmount() > 0) {
                return i;
            }
        }
        return -1;      
    }

    /**
     * Gets the earliest free slot
     * @return slot id
     */
    public int getFreeSlot() {
        for (int i = 0; i < this.items.length; i++) {
            if (this.getId(i) <= 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Checks if item is existing in the given slot, with the given amount and 
     * item id.
     * @param id Item ID
     * @param amount Item Amount
     * @param slot Slot id
     * @return boolean
     */
    public boolean containsInSlot(int id, int amount, int slot) {
        int found = 0;
        if (this.items[slot].getAlpha() == id) {
            for (int i = 0; i < this.items.length; i++) {
                PlayerItem item = this.items[i];
                if (this.getId(item) == id) {
                    if (item.getAmount() >= amount) {
                        return true;
                    } else {
                        found++;
                    }
                }
            }
            if (found >= amount) {
                return true;
            }
            return false;
        }
        return false;
    }

    /**
     * Checks if item exists in the storage
     */
    public boolean contains(int id) {
        for (PlayerItem item : this.items) {
            if (this.getId(item) == id) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@link #increasedItem}
     * @param i Index
     * @return real item id
     */
    private int getId(int i) {
        if (this.increasedItem) {
            return this.items[i].getAlpha();
        }
        return this.items[i].getId();
    }

    /**
     * {@link #increasedItem}
     * @param item PlayerItem object
     * @return real item id
     */
    private int getId(PlayerItem item) {
        if (this.increasedItem) {
            return item.getAlpha();
        }
        return item.getId();    
    }

    public abstract boolean add(int id, int amount);
    public abstract boolean remove(int id, int amount, int fromSlot);
}

Ho anche una classe container che è responsabile del controllo di tutti gli archivi:

/**
 * Container class for all storages of a player
 * @author Ben
 */
public class PlayerStorageContainer {

    /**
     * Player object (Client extends Player extends Entity)
     */
    public Player player;

    /**
     * boolCatach: If enabled, when removing/adding item to ALL of the storages,
     * it will check if the action returned false, if yes it will stop right away and
     * return false.
     */
    private boolean boolCatch;

    /**
     * All of the storages the player will contain
     */
    private Map<String, PlayerItemStorage> storages = 
            new HashMap<String, PlayerItemStorage>();

    public PlayerStorageContainer(Player p) {
        this.player = p;
        this.initializePlayerStorages();
    }

    /**
     * Initializes the existing storage for player.
     */
    private void initializePlayerStorages() {
        this.storages.put("inventory", new Inventory(this.player));
    }

    /**
     * Gets the required PlayerItemStorage implementation
     * @param name Name of the implementing object
     * @return The object that extends PlayerItemStorage
     */
    public PlayerItemStorage getStorage(String name) {
        return this.storages.get(name);
    }

    /**
     * Removes an item from ALL storages
     * @param id        The Item ID
     * @param amount    The amount of the item
     * @return Returns a boolean if action was successful without any falses in the
     * middle of the execution, will return always true if {!boolCatch}
     */
    public boolean remove(int id, int amount) {
        for (PlayerItemStorage storage : this.storages.values()) {
            if (!storage.remove(id, amount) && this.boolCatch) {
                return false;
            }
        }
        return true;
    }

    /**
     * Adds an item to ALL storages
     * @param id        The Item ID
     * @param amount    The amount of the item
     * @return Returns a boolean if action was successful without any falses in the
     * middle of the execution, will return always true if {!boolCatch}
     */
    public boolean add(int id, int amount) {
        for (PlayerItemStorage storage : this.storages.values()) {
            if (!storage.add(id, amount) && this.boolCatch) {
                return false;
            }
        }
        return true;
    }

    /**
     * Resets all storages
     */
    public void reset() {
        for (PlayerItemStorage storage : this.storages.values()) {
            storage.resetStorage();
        }
    }

    /**
     * Toogles {@link PlayerItemContainer#boolCatch} for errors
     * @param b
     */
    public void setErrorCatch(boolean b) {
        this.boolCatch = b;
    }
}

Il problema

Questo sistema funziona bene per inventario e attrezzature - tuttavia, questo sarà un problema e causerà la duplicazione di un grosso codice algoritmico per il tipo di banca.

Poiché lo storage di tipo banca può avere più schede fino a 10, sarà necessario creare una classe denominata Tab e implementarla PlayerItemStorage e inserire lo stesso algoritmo nelle due classi (Bank e Tab) o disporre di un metodo in Bank , e poi avere una variabile denominata "focusedTab" che manterrà l'id della scheda selezionata, e rendere il metodo add accetta 3 argomentazioni (itemId, amount, tabId) .

Ma accettando 3 argomenti si creerà un problema, dovrò o ignorare il metodo astratto add, e creare il proprio metodo per esso, oppure creare 2 metodi di aggiunta astratta, uno con 2 parametri, uno con 3 che io trovare troppo disordinato.

Ho progettato questa cosa in modo errato che ho questi problemi? Come posso progettare una struttura adeguata per questa intera funzionalità di archiviazione in modo che funzioni con tutti i tipi di memorie a cui riesco a pensare?

Se non riesci a capire il problema, potresti trovare utile il mio thread di revisione del codice

Sfondo su come il server gestisce gli elementi

Quando il cliente fa clic su qualsiasi elemento, in qualsiasi interfaccia di gioco che supporti la memorizzazione degli elementi, invia l'ID dello slot su cui il giocatore ha fatto clic per motivi di sicurezza. Il server utilizza l'ID dello slot come indice dell'array PlayerItem [] per ottenere le informazioni sull'elemento.

    
posta Ben Beri 30.06.2014 - 14:58
fonte

1 risposta

1

Penso che tu abbia confuso di avere un'interfaccia per il tuo programma da chiamare per aggiungere / rimuovere elementi per un particolare giocatore e poter gestire tutti i singoli casi. L'interfaccia con il resto del programma dovrebbe rimanere semplice. Per semplice intendo che dovrebbe essere un unico scopo e gli unici metodi pubblici saranno quelli che verranno chiamati.

A quanto ho capito, è PlayerItemStorage che gestisce la logica relativa all'adattamento e al layout fisico di tali elementi. Questo tipo di complicazione dovrebbe rimanere all'interno di PlayerItemStorage. Per i chiamanti di PlayerItemStorage , dovrebbero interessarsi solo degli articoli presenti, se un articolo è stato rimosso o aggiunto. Quando ciò accade, attraverso un'interfaccia PlayerItemStorageChangeHandler , è possibile registrare qualsiasi istanza nel tuo programma che desideri conoscere questo evento. Il tuo PlayerStorageContainer userebbe questo come mezzo per ottenere queste informazioni e agire di conseguenza.

Qualsiasi convalida sull'opportunità o meno di adattarlo allo spazio di archiviazione dovrebbe essere interamente gestita da PlayerItemStorage . Nota che ho detto che se un elemento è stato rimosso o aggiunto deve essere gestito da PlayerItemStorage . Non essere tentato di mettere la logica in PlayerStorageContainer poiché non dovrebbe sapere se è possibile o meno.

Certo, penso ancora che aggiungere e rimuovere elementi da PlayerStorageContainer dovrebbe essere possibile, tuttavia dovresti consentire la possibilità di un fallimento. Tutto ciò che vorresti accadesse come conseguenza dell'aggiunta a PlayerItemStorage dovrebbe essere messo nello stesso posto del tuo PlayerItemStorageChangeHandler listener per i nuovi add / remover (di nuovo, evitando di duplicare il tuo codice). Inoltre, presumo che l'interfaccia principale da chiamare quando un giocatore aggiunge / rimuove gli oggetti nel suo spazio di archiviazione dovrebbe essere PlayerItemStorage e non PlayerStorageContainer per la maggior parte delle operazioni, tuttavia suppongo che tu voglia aggiungere un oggetto regalo a un giocatore stoccaggio, sarebbe preferibile non preoccuparsi di quale deposito è (basta lanciarlo ovunque dove c'è spazio).

Una volta stabilita questa relazione, vedrai che un concetto di banca con schede non è altro che una raccolta di PlayerItemStorage . Creo quindi un tipo speciale di PlayerItemStorage chiamato PlayerBankItemStorage . Sostituirebbe le stesse operazioni chiamate da PlayerStorageContainer , ma potrebbe anche mantenere lo stato della scheda aperta. Cerca di mantenere la logica semplice però. Dovresti inoltrare la maggior parte delle chiamate a PlayerItemStorage . PlayerBankItemStorage ascolta PlayerItemStorageChangeHandler e poi lancia l'evento stesso (se deve gestire almeno gli eventi, altrimenti l'aggiunta di un nuovo listener dovrebbe semplicemente inoltrare a ogni singolo PlayerItemStorage .

La cosa fondamentale da ricordare è evitare la duplicazione della logica. Tieni i ruoli di classe chiari nella tua mente e scoprirai che è più semplice delegare compiti alle tue classi e mantenere la logica in un unico posto.

    
risposta data 30.06.2014 - 15:33
fonte

Leggi altre domande sui tag