Arsitektur OOP untuk Hero dengan banyak atribut


14

Saya akan memulai RPG teks peramban sederhana, dengan karakter yang dapat (secara pasif) melawan orang lain. Ini melibatkan daftar sekitar 10 keterampilan seperti kekuatan, ketangkasan dan sebagainya, dengan keahlian tambahan untuk senjata yang berbeda.

Apakah ada cara yang lebih baik untuk merancang kelas karakter ini daripada hanya memiliki keterampilan itu sebagai atribut kelas? Tampaknya mudah, tapi saya enggan karena kecanggungan.

class Char(self):
    int strength
    int dexterity
    int agility
    ...
    int weaponless
    int dagger
    ...

1
Anda harus memeriksa panduan ini untuk menulis game dan bagaimana beberapa kelas umum akan terlihat seperti tautan
naga

@dragons Terima kasih atas tautannya yang menarik, tapi saya tidak melihat penjelasan yang lebih dalam untuk mendesain Charactorkelas?
Sven

1
Apa sebenarnya yang Anda anggap "canggung" tentang desain ini?
Thomas

Jawaban:


17

Selama Anda menjaga sistem Anda relatif sederhana, ini akan berhasil. Tetapi ketika Anda menambahkan hal-hal seperti pengubah keterampilan sementara, Anda akan segera melihat banyak kode duplikat. Anda juga akan mengalami masalah dengan senjata yang berbeda menggunakan keahlian yang berbeda. Karena setiap keterampilan adalah variabel yang berbeda, Anda harus menulis kode yang berbeda untuk setiap jenis keterampilan yang pada dasarnya melakukan hal yang sama (atau menggunakan beberapa retasan jelek - dengan syarat bahasa pemrograman Anda mendukungnya).

Untuk alasan itu saya akan merekomendasikan Anda untuk menyimpan keterampilan dan keahlian dalam struktur data asosiatif yang memetakan konstanta keterampilan ke nilai. Cara melakukannya berbeda dari bahasa pemrograman ke bahasa pemrograman. Ketika bahasa Anda mendukungnya, konstanta harus dalam enum.

Untuk memberi Anda contoh bagaimana ini akan bekerja dalam prakteknya, kode Anda untuk menghitung kerusakan serangan akan terlihat seperti ini:

int damage = attacker.getSkill(STRENGTH) + 
             attacker.getProficiency(weapon.getProficiencyRequired()) -
             defender.getSkill(TOUGHNESS);

Membuat metode seperti getSkill()bertentangan dengan prinsip dasar pemrograman berorientasi objek .
Jephir

4

Mengapa tidak menggunakan array terkait ?, ini memberi manfaat menjadi mudah diperluas (menggunakan PHP misalnya)

$Stats["Strength"] = "8";
$Stats["Dexterity"] = "8";

untuk hal-hal seperti senjata, Anda mungkin ingin membuat beberapa kelas dasar

Senjata -> MeleeWeapon, RangedWeapon

dan kemudian buat senjata Anda dari sana.

Hasil akhir yang ingin saya tuju adalah kelas yang terlihat seperti ini

class Character
{
    public $Stats;
    public $RightHand;
    public $LeftHand;
    public $Armor;
    public $Name;
    public $MaxHealth;
    public $CurrentHealth;

    public function __construct()
    {
        //Basic
        $this->Name = "Fred";
        $this->MaxHealth = "10";
        $this->CurrentHealth = "10";

        //Stats
        $this->Stats["Strength"] = 8;
        $this->Stats["Dexterity"] = 8;
        $this->Stats["Intellect"] = 8;
        $this->Stats["Constitution"] = 8;

        //Items
        $this->RightHand = NULL;
        $this->LeftHand  = NULL;
        $this->Armor = NULL;

    }
}

Anda bisa menyimpan semuanya dalam sebuah array jika Anda benar-benar menginginkannya.


Cukup banyak yang dikatakan @Philipp?
AturSams

1
@ Pilhipp menyarankan penggunaan enums, array adalah pilihan lain.
Grimston

Ini sebenarnya mengatakan: "... struktur data asosiatif yang memetakan konstanta keterampilan ke nilai", konstanta dalam kamus bisa berupa string.
AturSams

3

Saya akan mencoba menjawab pertanyaan ini dengan cara yang paling OOP (atau setidaknya apa yang saya pikirkan). Ini mungkin sepenuhnya berlebihan, tergantung pada evolusi yang Anda lihat tentang statistik.

Anda bisa membayangkan kelas SkillSet (atau Stats ) (saya menggunakan sintaks mirip C untuk jawaban ini):

class SkillSet {

    // Consider better data encapsulation
    int strength;
    int dexterity;
    int agility;

    public static SkillSet add(SkillSet stats) {
        strength += stats.strength;
        dexterity += stats.dexterity;
        agility += stats.agility;
    }

    public static SkillSet apply(SkillModifier modifier) {
        strength *= modifier.getStrengthModifier();
        dexterity *= modifier.getDexterityModifier();
        agility *= modifier.getAgilityModifier();

    }

}

Kemudian pahlawan akan memiliki bidang intrinsikStats dari jenis SkillSet. Senjata bisa memiliki skill pengubah. Atur juga.

public abstract class Hero implements SkillSet {

    SkillSet intrinsicStats;
    Weapon weapon;

    public SkillSet getFinalStats() {
        SkillSet finalStats;
        finalStats = intrinsicStats;
        finalStats.add(weapon.getStats());
        foreach(SkillModifier modifier : getEquipmentModifiers()) {
            finalStats.apply(modifier);
        }
        return finalStats;
    }

    protected abstract List<SkillModifier> getEquipmentModifiers();

}

Ini tentu saja contoh untuk memberi Anda ide. Anda juga dapat mempertimbangkan menggunakan pola desain Penghias, sehingga pengubah pada statistik berfungsi sebagai "filter" yang diterapkan satu demi satu ...


Bagaimana C-like ini?
bogglez

@bogglez: Saya juga cenderung menggunakan "C'ish" untuk merujuk ke pseudocode yang secara longgar menyerupai bahasa kurung kurawal, bahkan jika kelas terlibat. Anda benar, tetapi ini terlihat seperti sintaks yang jauh lebih spesifik - ini adalah perubahan kecil atau dua dari Java yang dapat dikompilasi.
cao

Saya tidak berpikir saya terlalu ketat di sini. Ini bukan hanya perbedaan dalam sintaksis, tetapi perbedaan dalam semantik. Di C tidak ada yang namanya kelas, templat, kualifikasi yang dilindungi, metode, fungsi virtual, dll. Saya hanya tidak suka penyalahgunaan istilah yang biasa-biasa saja ini.
bogglez

1
Seperti yang dikatakan orang lain, sintaks curly-brace berasal dari C. Sintaks Java (atau C # dalam hal ini) sangat terinspirasi oleh gaya ini.
Pierre Arlaud

3

Cara paling OOP dalam melakukan sesuatu mungkin akan melakukan sesuatu dengan warisan. Kelas dasar Anda (atau kelas super tergantung pada bahasa) adalah orang, maka mungkin penjahat dan pahlawan mewarisi dari kelas dasar. Maka pahlawan berbasis kekuatan Anda dan pahlawan berbasis penerbangan akan bercabang karena moda transportasi mereka berbeda, misalnya. Ini memiliki bonus tambahan bahwa pemain komputer Anda dapat memiliki kelas dasar yang sama dengan pemain manusia dan diharapkan akan menyederhanakan hidup Anda.

Hal lain mengenai atribut, dan ini kurang spesifik OOP adalah untuk mewakili atribut karakter Anda sebagai daftar sehingga Anda tidak harus memiliki semuanya didefinisikan secara eksplisit dalam kode Anda. Jadi mungkin Anda akan memiliki daftar senjata dan daftar atribut fisik. Buat semacam kelas dasar untuk atribut ini sehingga mereka dapat berinteraksi, sehingga masing-masing didefinisikan dalam hal kerusakan, biaya energi, dll. Jadi ketika dua individu berkumpul bersama, itu relatif jelas bagaimana interaksi akan terjadi. Anda akan mengulangi daftar atribut setiap karakter dan menghitung kerusakan yang satu lakukan terhadap yang lain dengan tingkat probabilitas dalam setiap interaksi.

Menggunakan daftar akan membantu Anda menghindari penulisan ulang banyak kode karena untuk menambahkan karakter dengan atribut yang belum Anda pikirkan, Anda hanya perlu memastikannya memiliki interaksi yang bekerja dengan sistem Anda yang ada.


4
Saya pikir intinya adalah memisahkan statistik dari kelas pahlawan. Warisan tidak selalu merupakan solusi OOP terbaik (dalam jawaban saya, saya menggunakan komposisi sebagai gantinya).
Pierre Arlaud

1
Saya komentar kedua sebelumnya. Apa yang terjadi jika karakter C memiliki properti dari karakter A dan B (yang keduanya memiliki kelas dasar yang sama)? Anda dapat menduplikasi kode atau menghadapi beberapa masalah terkait pewarisan berganda . Saya lebih suka komposisi daripada warisan dalam hal ini.
ComFreek

2
Cukup adil. Saya hanya memberi OP beberapa opsi. Tidak terlihat ada banyak kesulitan pada tahap ini dan saya tidak tahu tingkat pengalaman mereka. Mungkin agak berlebihan untuk mengharapkan seorang pemula melakukan polimorfisme penuh, tetapi pewarisan cukup sederhana untuk dipahami oleh seorang pemula. Saya mencoba untuk membantu mengatasi masalah kode yang terasa 'kikuk' yang hanya dapat saya asumsikan merujuk pada bidang-bidang yang dikodekan secara keras yang mungkin atau mungkin tidak digunakan. Menggunakan daftar untuk jenis nilai yang tidak terdefinisi ini adalah pilihan yang bagus.
GenericJam

1
-1: "Cara paling OOP dalam melakukan sesuatu mungkin akan melakukan sesuatu dengan warisan." Warisan sangat tidak berorientasi objek.
Sean Middleditch

3

Saya akan merekomendasikan manajer tipe stat, diisi dari datafile (misalnya saya menggunakan XML) dan objek Stat, dengan tipe dan nilai yang disimpan dalam karakter insatnce sebagai hashtable, dengan tipe unik ID ID sebagai kuncinya.

Edit: Kode psudo

Class StatType
{
    int ID;
    string Name;

    public StatType(int _id, string _name)
    {
        ID = _id;
        Name = _name;
    }
}


Class StatTypeManager
{
    private static Hashtable statTypes;

    public static void Init()
    {
        statTypes = new Hashtable();

        StatType type;

        type = new StatType(0, "Strength");
        statTypes.add(type.ID, type);

        type = new StatType(1, "Dexterity");
        statTypes.add(type.ID, type);

        //etc

        //Recommended: Load your stat types from an external resource file, e.g. xml
    }

    public static StatType getType(int _id)
    {
        return (StatType)statTypes[_id];
    }
}

class Stat
{
    StatType Type;
    int Value;

    public Stat(StatType _type, int _value)
    {
        Type = _type;
        Value = _value;
    }
}

Class Char
{
    Hashtable Stats;

    public Char(Stats _stats)
    {
        Stats = _stats;
    }

    public int GetStatValue(int _id)
    {
        return ((Stat)Stats[_id]).Value;
    }
}

Saya pikir StatTypekelas dalam yang tidak perlu, hanya menggunakan namesatu StatTypesebagai kunci. Seperti yang dilakukan #Grimston. Sama dengan Stats.
giannis christofakis

Saya lebih suka menggunakan kelas dalam hal seperti ini sehingga Anda dapat dengan bebas memodifikasi nama (sementara id tetap konstan). Berlebihan dalam hal ini? Mungkin, tetapi karena teknik ini dapat digunakan untuk sesuatu yang serupa di tempat lain dalam suatu program saya akan melakukannya seperti ini untuk konsistensi.
DFreeman

0

Ι akan mencoba memberi Anda contoh tentang bagaimana Anda dapat merancang gudang senjata dan gudang senjata Anda.

Tujuan kami adalah untuk memisahkan entitas, oleh karena itu senjata harus menjadi antarmuka.

interface Weapon {
    public int getDamage();
}

Menganggap bahwa setiap pemain hanya dapat memiliki satu senjata, kita dapat menggunakan Strategy patternuntuk mengubah senjata dengan mudah.

class Knife implements Weapon {
    private int damage = 10;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

class Sword implements Weapon {
    private int damage = 40;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

Pola lain yang bermanfaat adalah Pola Objek Null jika pemain tidak bersenjata.

class Weaponless implements Weapon {
    private int damage = 0;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

Sedangkan untuk gudang senjata, kita bisa memakai beberapa peralatan pertahanan.

// Defence classes,interfaces

interface Armor {
    public int defend();
}

class Defenseless implements Armor {

    @Override
    public int defend() {
        return 0;
    }
}

abstract class Armory implements Armor {

    private Armor armory;
    protected int defence;

    public Armory() {
        this(new Defenseless());
    }

    public Armory(Armor force) {
        this.armory = force;
    }

    @Override
    public int defend() {
        return this.armory.defend() + this.defence;
    }

}

// Defence implementations

class Helmet extends Armory {
    {
        this.defence = 30;
    }    
}

class Gloves extends Armory {
    {
        this.defence = 10;
    }    
}

class Boots extends Armory {
    {
        this.defence = 10;
    }    
}

Untuk decoupling, saya membuat antarmuka untuk bek.

interface Defender {
    int getDefended();
}

Dan Playerkelasnya.

class Player implements Defender {

    private String title;

    private int health = 100;
    private Weapon weapon = new Weaponless();
    private List<Armor> armory = new ArrayList<Armor>(){{ new Defenseless(); }};


    public Player(String name) {
        this.title = name;
    }

    public Player() {
        this("John Doe");
    }

    public String getName() {
        return this.title;
    }


    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack(Player enemy) {

        System.out.println(this.getName() + " attacked " + enemy.getName());

        int attack = enemy.getDefended() + enemy.getHealth()- this.weapon.getDamage();

        int health = Math.min(enemy.getHealth(),attack);

        System.out.println("After attack " + enemy.getName() + " health is " + health);

        enemy.setHealth(health);
    }

    public int getHealth() {
        return health;
    }

    private void setHealth(int health) {
        /* Check for die */
        this.health = health;
    }

    public void addArmory(Armor armor) {
        this.armory.add(armor);
    }


    @Override
    public int getDefended() {
        int defence = this.armory.stream().mapToInt(armor -> armor.defend()).sum();
        System.out.println(this.getName() + " defended , armory points are " + defence);
        return defence;
    }

}

Mari kita tambahkan beberapa gameplay.

public class Game {
    public static void main(String[] args) {
        Player yannis = new Player("yannis");
        Player sven = new Player("sven");


        yannis.setWeapon(new Knife());
        sven.setWeapon(new Sword());


        sven.addArmory(new Helmet());
        sven.addArmory(new Boots());

        yannis.attack(sven);      
        sven.attack(yannis);      
    }
}

Voila!


2
Jawaban ini akan lebih membantu ketika Anda akan menjelaskan alasan Anda di balik pilihan desain Anda.
Philipp
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.