多くの属性を持つヒーロー向けのOOPアーキテクチャ


14

他の人と(受動的に)戦うことができるキャラクターで、単純なブラウザーテキストRPGを開始しようとしています。これには、強さ、器用さなどの約10のスキルのリストが含まれ、さまざまな武器の追加の熟練度が含まれます。

これらのスキルをクラス属性として持つだけで、このキャラクタークラスを設計するより良い方法はありますか?簡単そうに思えますが、不器用なので嫌がります。

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

1
ゲームを書くためのこのすべてのガイドと、一般的なクラスの一部がリンクの
ドラゴンズ14年

@dragons興味深いリンクをお寄せいただきありがとうございますが、Charactorクラスの設計に関する詳細な説明はありませんか?
スベン

1
このデザインについて「不器用」な点は何ですか?
トーマス14年

回答:


17

システムを比較的シンプルに保つ限り、これは機能するはずです。しかし、一時的なスキル修飾子などを追加すると、すぐに多くの重複したコードが表示されます。また、さまざまなスキルを使用してさまざまな武器の問題に遭遇します。各スキルは異なる変数であるため、基本的に同じことを行うスキルタイプごとに異なるコードを記述する必要があります(または、プログラミング言語がサポートする条件の下で、someいリフレクションハックを使用します)。

そのため、スキル定数を値にマップする関連データ構造にスキルと習熟度の両方を保存することをお勧めします。その方法は、プログラミング言語ごとにエレガントに異なります。言語でサポートされている場合、定数はにある必要がありenumます。

これが実際にどのように機能するか例を示すために、攻撃によるダメージを計算するコードは次のようになります。

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

のようなメソッドを作成するgetSkill()と、オブジェクト指向プログラミングの基本原則に反します
エフィール14年

4

関連付けられた配列を使用しないのはなぜですか?これにより、(たとえばPHPを使用して)簡単に拡張できるという利点が得られます

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

武器などの場合は、おそらくいくつかの基本クラスを作成する必要があります

武器->近接武器、遠隔武器

そしてそこから武器を作成します。

私が目指す最終結果は、このようなクラスです

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;

    }
}

本当に望むなら、すべてを配列に保存することもできます。


それは、@ Philippが言ったこととほぼ同じですか?
wolfdawn 14年

1
@Philippは列挙型の使用を提案しました。配列は別のオプションです。
グリムストン14年

実際には、「...スキル定数を値にマップする連想データ構造」と書かれています。辞書の定数は文字列です。
wolfdawn 14年

3

私はこの質問に最もOOPの方法(または少なくとも私が思うに答える)に答えようとします。あなたが統計について見る進化に応じて、それは完全にやり過ぎかもしれません。

SkillSet(またはStats)クラスを想像できます(この回答にはCのような構文を使用しています)。

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

    }

}

その後、ヒーローはSkillSetタイプのintrinsicStatsフィールドを持つことになります。武器には、modifier skillSetも設定できます。

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

}

これはもちろんあなたにアイデアを与えるための例です。また、デコレータデザインパターンの使用を検討することもできます。これにより、統計の修飾子が次々に適用される「フィルタ」として機能します…


このCはどうですか?
bogglez 14年

@bogglez:同様に、「C'ish」を使用して、クラスが含まれている場合でも、中括弧言語に大まかに似た擬似コードを参照する傾向があります。ただし、ポイントがあります。これは、より具体的な構文のように見えます。これは、コンパイル可能なJavaになるまでのわずかな変更です。
cHao 14年

ここで厳しすぎるとは思わない。これは単に構文の違いではなく、セマンティクスの違いです。Cには、クラス、テンプレート、保護された修飾子、メソッド、仮想関数などのようなものはありません。この偶然の用語の乱用は好きではありません。
bogglez 14年

1
他の人が言ったように、中括弧構文はCから来ています。Java(またはC#)の構文は、このスタイルに非常に影響を受けています。
ピエールアラウド14年

3

物事を行う最もOOPの方法は、おそらく継承で何かをすることでしょう。基本クラス(または言語に応じてスーパークラス)は人になり、悪役やヒーローは基本クラスを継承します。次に、たとえば、輸送モードが異なるため、あなたの強さベースのヒーローと飛行ベースのヒーローが分岐します。これには、コンピュータープレーヤーが人間のプレーヤーと同じ基本クラスを持つことができるという追加のボーナスがあり、うまくいけばあなたの人生が簡素化されます。

属性に関する他のこと、およびこれはOOP固有ではないので、文字属性をリストとして表現するので、コードですべてを明示的に定義する必要はありません。したがって、武器のリストと物理的な属性のリストがあるでしょう。これらの属性の何らかの基本クラスを作成して相互作用できるようにするため、各属性は損傷、エネルギーコストなどの観点から定義されるため、2人の個人が集まったときに相互作用がどのように発生するかは比較的明確です。各キャラクターの属性リストのリストを反復処理し、各インタラクションである程度の確率で一方が他方に与えるダメージを計算します。

リストを使用すると、まだ考えていなかった属性を持つキャラクターを追加するために、既存のシステムと連動する相互作用があることを確認するだけなので、多くのコードを書き直すことを避けるのに役立ちます。


4
私はポイントが主人公のクラスから統計を切り離すことだと思います。継承は必ずしも最適なOOPソリューションではありません(私の答えでは、代わりに構成を使用しました)。
ピエールアラード

1
私は前のコメントを二番目にします。文字Cが文字AとB(両方とも同じ基本クラスを持っている)のプロパティを持っている場合はどうなりますか?コードを複製するか、多重継承に関する問題に直面します。この場合、継承よりも合成を優先します。
ComFreek 14年

2
けっこうだ。私はOPにいくつかのオプションを与えていました。この段階では石に多くのセットがあるように見えません、そして、私は彼らの経験レベルを知りません。初心者が本格的なポリモーフィズムをやめることを期待するのはおそらく多分でしょうが、継承は初心者が理解するのに十分なほど簡単です。私は、コードが「不器用」であるという問題に対処しようとしていましたが、これは、使用される可能性のある、または使用されない可能性のあるフィールドをハードコーディングすることを指すと推測できます。これらのタイプの未定義の値にリストを使用するのは、良い選択肢です。
GenericJam 14年

1
-1:「物事を行う最もOOPの方法は、おそらく継承を伴うことです。」継承は、特にオブジェクト指向ではありません。
ショーンミドルディッチ14年

3

データファイル(XMLを使用するなど)およびStatオブジェクトから生成されたstatタイプマネージャーをお勧めします。タイプと値は、ハッシュテーブルとして文字insatnceに格納され、statタイプの一意のIDをキーとして使用します。

編集:擬似コード

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

私が思うにStatType、クラスが不要で、単に使うnameのをStatTypeキーとして。#Grimstonがしたように。と同じStatsです。
ジャンニスクリストファキス14年

このようなインスタンスでクラスを使用して、名前を自由に変更できるようにします(IDは一定のままです)。このインスタンスでやり過ぎ?おそらく、しかし、この手法はプログラムの他の場所で似たようなものに使用される可能性があるため、一貫性を保つためにこのようにします。
DFreeman 14年

0

あなたの武器庫と武器庫を設計する方法の例を挙げようとします。

私たちの目標は、エンティティを分離することです。したがって、武器はインターフェースでなければなりません。

interface Weapon {
    public int getDamage();
}

各プレイヤーは武器を1つしか所有できないと仮定しStrategy patternます。武器を簡単に変更するために使用できます。

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

別の有用なパターンは、プレイヤーが武装していない場合のヌルオブジェクトパターンです。

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

武器庫については、複数の防衛装備を着用できます。

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

デカップリングのために、ディフェンダー用のインターフェイスを作成しました。

interface Defender {
    int getDefended();
}

そしてPlayerクラス。

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

}

ゲームプレイを追加しましょう。

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

出来上がり!


2
この回答は、設計の選択の背後にある理由を説明するときに役立ちます。
フィリップ14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.