Birçok özelliği olan Hero için OOP mimarisi


14

Diğer insanlarla (pasif olarak) savaşabilen karakterlerle basit bir tarayıcı metin RPG'si başlatmak üzereyim. Bu, farklı silahlar için ek yeterliliklerle birlikte güç, el becerisi ve benzeri yaklaşık 10 becerinin bir listesini içerir.

Bu karakter sınıfını tasarlamanın daha iyi bir yolu var mı? Kolay görünüyor, ama isteksizim çünkü beceriksizlik.

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

1
Sen yazma oyunlar için bu alltogether rehber kontrol etmeli ve nasıl ortak sınıfların bazı gibi görünebilir bağlantı
ejderha

@dragons İlginç bağlantı için teşekkürler, ancak Charactorsınıfı tasarlamak için daha derin bir açıklama görmüyorum ?
Sven

1
Bu tasarım hakkında tam olarak ne "sakar" buluyorsunuz?
Thomas

Yanıtlar:


17

Sisteminizi nispeten basit tuttuğunuz sürece, bu işe yaramalıdır. Ancak geçici beceri değiştiricileri gibi şeyler eklediğinizde, yakında çok sayıda yinelenen kod göreceksiniz. Ayrıca farklı yeterlilikleri kullanarak farklı silahlarla ilgili problemlerle karşılaşacaksınız. Her beceri farklı bir değişken olduğundan, temel olarak aynısını yapan her beceri türü için farklı kod yazmanız gerekir (veya programlama dilinizin desteklediği koşul altında çirkin yansıma kesmek kullanın).

Bu nedenle, hem beceri hem de yeterlilikleri, beceri sabitlerini değerlerle eşleyen ilişkilendirilebilir bir veri yapısında depolamanızı öneririm. Bunun nasıl yapılacağı programlama dilinden programlama diline zarif bir şekilde farklılık gösterir. Diliniz desteklediğinde, sabitler bir enum.

Bunun pratikte nasıl işe yarayacağına dair bir örnek vermek için, saldırı hasarını hesaplama kodunuz şöyle görünecektir:

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

Gibi bir yöntem oluşturmak , nesne yönelimli programlamanın temel ilkesinegetSkill() aykırıdır .
Jephir

4

Neden ilişkili dizileri kullanmıyorsunuz? Bu, kolayca genişletilebilir (örneğin PHP kullanarak)

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

silah gibi şeyler için muhtemelen bazı temel sınıflar oluşturmak istersiniz

Silah -> Yakın Dövüş Silahı, Menzilli Silah

ve sonra silahlarınızı oradan yaratın.

Hedefleyeceğim sonuç şöyle bir sınıf

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;

    }
}

Gerçekten isterseniz bir dizide her şeyi saklayabilirsiniz.


@Philipp'in söylediği şey bu mu?
AturSams

1
@Philipp, numaralandırma kullanımını önerdi, diziler başka bir seçenek.
Grimston

Aslında şunu söylüyor: "... beceri sabitlerini değerlerle eşleyen ilişkisel veri yapısı", sözlükteki sabitler dizgiler olabilir.
AturSams

3

Bu soruya en OOP yolunu (veya en azından ne olacağını düşündüğüm) cevaplamaya çalışacağım. İstatistikler hakkında gördüğünüz evrimlere bağlı olarak tamamen aşırı olabilir.

Bir SkillSet (veya Stats ) sınıfı düşünebilirsiniz ( bu cevap için C benzeri sözdizimi kullanıyorum):

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();

    }

}

Sonra kahramanın SkillSet türünde intrinsicStats alanı olur. Bir silahın değiştirici bir yeteneği de olabilir.

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();

}

Bu elbette size fikir vermek için bir örnek. Ayrıca, Decorator tasarım modelini kullanmayı düşünebilirsiniz, böylece istatistiklerdeki değiştiriciler birbiri ardına uygulanan "filtreler" olarak çalışır ...


Bu C benzeri nasıl?
bogglez

@bogglez: Ben de sınıflar dahil olsa bile, kıvırcık ayraç diline gevşek bir şekilde benzeyen yalancı kodu ifade etmek için "C'ish" kullanma eğilimindeyim. Bununla birlikte, bir noktanız var: bu daha spesifik bir sözdizimi gibi görünüyor - küçük bir değişiklik veya derlenebilir Java olmaktan iki.
cHao

Burada aşırı derecede katı olduğumu düşünmüyorum. Bu sadece sözdizimindeki bir fark değil, anlambilimdeki bir farktır. C'de sınıf, şablonlar, korumalı niteleyiciler, yöntemler, sanal işlevler, vb. Diye bir şey yoktur. Bu terimlerin gündelik kötüye kullanımından hoşlanmıyorum.
Mart'ta bogglez

1
Diğerlerinin söylediği gibi, süslü ayraç sözdizimi C'den gelir. Java'nın sözdizimi (veya bu konu için C #) bu tarzdan büyük ölçüde ilham alır.
Pierre Arlaud

3

Bir şeyler yapmanın en OOP yolu muhtemelen kalıtımla bir şeyler yapmak olacaktır. Temel sınıfınız (veya dile bağlı olarak süper sınıfınız) kişi olabilir, o zaman kötü adamlar ve kahramanlar temel sınıftan miras alır. Örneğin, güç temelli kahramanlarınız ve uçuş tabanlı kahramanlarınız, ulaşım biçimleri farklı olduğu için dallanacaktı. Bu, bilgisayar oyuncularınızın insan oyuncularla aynı temel sınıfa sahip olabileceği ek bir bonusa sahiptir ve umarım hayatınızı basitleştirir.

Özniteliklerle ilgili diğer bir şey ve bu daha az OOP'a özgüdür, karakter özniteliklerinizi bir liste olarak temsil etmek olacaktır, böylece hepsinin kodunuzda açıkça tanımlanması gerekmez. Yani belki bir silah listesi ve fiziksel özellikler için bir listeniz olurdu. Bu nitelikler için etkileşim kurabilmeleri için bir tür temel sınıf yapın, böylece her biri hasar, enerji maliyeti vb. Olarak tanımlanır, böylece iki kişi bir araya geldiğinde etkileşimin nasıl gerçekleşeceği nispeten açıktır. Her karakterin nitelikler listesini tekrarlar ve her etkileşimde bir dereceye kadar olasılıkla kişinin yaptığı hasarı hesaplarsınız.

Bir liste kullanmak, henüz düşünmediğiniz bir özelliğe sahip bir karakter eklemek için çok fazla kod yeniden yazmamaktan kaçınmanıza yardımcı olacaktır, sadece mevcut sisteminizle çalışan bir etkileşime sahip olduğundan emin olmanız gerekir.


4
Bence mesele istatistikleri kahramanın sınıfından ayırmak. Kalıtım mutlaka en iyi OOP çözümü değildir (cevabımda bunun yerine kompozisyon kullandım).
Pierre Arlaud

1
Önceki yorumu ikinci yaptım. C karakteri A ve B karakterlerinden (her ikisi de aynı temel sınıfa sahip) özelliklere sahipse ne olur? Kodu çoğaltırsınız veya birden çok kalıtımla ilgili bazı sorunlarla karşılaşırsınız . Bu durumda kalıtım yerine kompozisyonu tercih ederim.
ComFreek

2
Yeterince adil. OP'ye sadece bazı seçenekler veriyordum. Bu aşamada çok fazla taş varmış gibi görünmüyor ve onların deneyim seviyelerini bilmiyorum. Muhtemelen bir aceminin tam şişmiş polimorfizmi çekmesini beklemek biraz fazla olacaktır, ancak miras bir aceminin kavraması için yeterince basittir. Ben sadece varsayabilir veya kullanılmayabilir sabit kodlu alanları olan anlamına gelir varsayıyorum kod duygu 'beceriksiz' sorunu çözmek için çalışıyordu. İmho bu tür tanımsız değerler için bir liste kullanmak iyi bir seçenektir.
GenericJam

1
-1: "Bir şeyler yapmanın en OOP yolu muhtemelen kalıtımla bir şeyler yapmak olacaktır." Kalıtım özellikle nesne yönelimli değildir.
Sean Middleditch

3

Bir veri dosyası (ör. XML kullanıyorum) ve Stat nesnelerinden, bir tür ve bir değer hashtable olarak karakter karmaşasında depolanan, stat türü benzersiz kimliği anahtar olarak depolanan bir istatistik türü yöneticisi öneririm.

Düzenle: Psudo kodu

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;
    }
}

Bence StatTypesınıfı gereksiz içinde, sadece kullanmak nameait StatTypebir anahtar olarak. # Grimston'ın yaptığı gibi. Aynısı Stats.
giannis christofakis

Bu tür durumlarda bir sınıf kullanmayı tercih ederim, böylece adı (kimliği sabit kalırken) serbestçe değiştirebilirsiniz. Bu durumda fazla mı? Belki de, ancak bu teknik bir programda başka bir yerde benzer bir şey için kullanılabileceğinden, bunu tutarlılık için böyle yapardım.
DFreeman

0

Ar cephaneliğinizi ve cephaneliğinizi nasıl tasarlayabileceğiniz konusunda size bir örnek vermeye çalışacağım.

Amacımız varlıkları birbirinden ayırmaktır, bu nedenle silah bir arayüz olmalıdır.

interface Weapon {
    public int getDamage();
}

Her oyuncunun sadece bir silaha sahip olabileceğini varsayalım, Strategy patternsilahları kolayca değiştirmek için kullanabiliriz.

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;
    }
}

Oynatıcının silahsız kalması durumunda bir diğer faydalı kalıp Boş Nesne Kalıbı olacaktır .

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

Cephaneliğe gelince, birden fazla savunma ekipmanı giyebiliriz.

// 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;
    }    
}

Dekuplaj için, defans oyuncusu için bir arayüz oluşturdum.

interface Defender {
    int getDefended();
}

Ve Playersınıf.

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;
    }

}

Biraz oyun ekleyelim.

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);      
    }
}

İşte bu kadar!


2
Bu cevap, tasarım tercihlerinizin ardındaki mantığınızı açıkladığınızda daha yararlı olacaktır.
Philipp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.