สถาปัตยกรรม OOP สำหรับฮีโร่ที่มีคุณสมบัติมากมาย


14

ฉันกำลังจะเริ่มเกมสวมบท RPG แบบเบราว์เซอร์ที่มีตัวละครที่สามารถต่อสู้กับคนอื่นได้ สิ่งนี้เกี่ยวข้องกับรายการทักษะ 10 อย่างเช่นความแข็งแกร่งความคล่องแคล่วและอื่น ๆ พร้อมด้วยทักษะเพิ่มเติมสำหรับอาวุธที่แตกต่างกัน

มีวิธีที่ดีกว่าในการออกแบบคลาสของตัวละครนี้แล้วเพียงแค่มีทักษะเหล่านั้นเป็นคุณลักษณะคลาส? ดูเหมือนง่าย แต่ฉันลังเลเพราะความซุ่มซ่าม

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

1
คุณควรตรวจสอบคำแนะนำทั้งหมดนี้สำหรับการเขียนเกมและลักษณะของคลาสทั่วไปอาจมีลักษณะเป็นลิงก์อย่างไร
มังกร

@dragons ขอบคุณสำหรับลิงค์ที่น่าสนใจ แต่ฉันไม่เห็นคำอธิบายที่ลึกซึ้งยิ่งขึ้นสำหรับการออกแบบCharactorชั้นเรียนหรือไม่
Sven

1
คุณพบอะไรที่ "เงอะงะ" เกี่ยวกับการออกแบบนี้
โทมัส

คำตอบ:


17

ตราบใดที่คุณทำให้ระบบของคุณค่อนข้างเรียบง่ายสิ่งนี้จะทำงานได้ แต่เมื่อคุณเพิ่มสิ่งต่าง ๆ เช่นตัวดัดแปลงทักษะชั่วคราวคุณจะเห็นรหัสซ้ำจำนวนมากในไม่ช้า คุณจะพบปัญหาเกี่ยวกับอาวุธที่แตกต่างกันโดยใช้ทักษะที่แตกต่างกัน เนื่องจากทักษะแต่ละอย่างเป็นตัวแปรที่แตกต่างกันคุณจะต้องเขียนโค้ดที่แตกต่างกันสำหรับแต่ละประเภททักษะซึ่งโดยทั่วไปแล้วจะเหมือนกัน (หรือใช้แฮ็กการสะท้อนกลับที่น่าเกลียดบางอย่าง

ด้วยเหตุนี้ฉันขอแนะนำให้คุณเก็บทั้งทักษะและความเชี่ยวชาญในโครงสร้างข้อมูลที่เชื่อมโยงซึ่งแมปค่าคงที่ทักษะกับค่า วิธีการทำนั้นแตกต่างอย่างสวยงามจากภาษาโปรแกรมไปจนถึงภาษาโปรแกรม enumเมื่อภาษาของคุณสนับสนุนค่าคงที่ที่ควรจะอยู่ใน

เพื่อให้ตัวอย่างแก่คุณว่าจะใช้งานอย่างไรในทางปฏิบัติรหัสของคุณสำหรับการคำนวณความเสียหายจากการโจมตีจะมีลักษณะดังนี้:

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

การสร้างวิธีการที่เหมือนgetSkill()ไปกับหลักการพื้นฐานของการเขียนโปรแกรมเชิงวัตถุ orientated
Jephir

4

ทำไมไม่ใช้อาเรย์ที่เกี่ยวข้อง? นี่จะให้ประโยชน์ในการขยายอย่างง่ายดาย (โดยใช้ตัวอย่าง PHP)

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

สำหรับสิ่งต่าง ๆ เช่นอาวุธคุณอาจต้องการสร้างคลาสพื้นฐานบางอย่าง

อาวุธ -> MeleeWeapon, RangedWeapon

จากนั้นสร้างอาวุธของคุณจากที่นั่น

ผลลัพธ์สุดท้ายที่ฉันต้องการคือคลาสที่มีลักษณะเช่นนี้

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;

    }
}

คุณสามารถเก็บทุกอย่างไว้ในอาร์เรย์ได้หากคุณต้องการเช่นกัน


นั่นเป็นสิ่งที่ @Phippipp พูดออกมามาก
AturSams

1
@Philipp แนะนำให้ใช้ enums อาร์เรย์เป็นอีกทางเลือกหนึ่ง
Grimston

จริงๆแล้วมันบอกว่า: "... โครงสร้างข้อมูลเชื่อมโยงซึ่งแมปค่าคงที่ทักษะกับค่า" ค่าคงที่ในพจนานุกรมอาจเป็นสตริง
AturSams

3

ฉันจะพยายามตอบคำถามนี้ให้เป็นวิธีที่ดีที่สุด (หรืออย่างน้อยก็ฉันคิดว่ามันจะเป็น) มันอาจ overkill สมบูรณ์ขึ้นอยู่กับวิวัฒนาการที่คุณเห็นเกี่ยวกับสถิติ

คุณสามารถจินตนาการคลาส SkillSet (หรือสถิติ ) (ฉันใช้ไวยากรณ์เหมือน 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();

    }

}

จากนั้นฮีโร่จะมีฟิลด์ intrinsicStats ของประเภท 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-like นี้เป็นอย่างไร
bogglez

@ bogglez: ในทำนองเดียวกันฉันมักจะใช้ "C'ish" เพื่ออ้างถึง pseudocode ที่มีลักษณะคล้ายกับภาษาหยิก - ปีกกาอย่างหลวม ๆ แม้ว่าจะเกี่ยวข้องกับชั้นเรียนก็ตาม คุณมีประเด็นอยู่ตรงนี้: มันดูเหมือนกับวิธีที่เฉพาะเจาะจงกว่าของซินแท็กซ์ - มันเป็นการเปลี่ยนแปลงเล็กน้อยหรือสองอย่างจากการคอมไพล์ Java
cHao

ฉันไม่คิดว่าฉันเข้มงวดเกินไปที่นี่ นี่ไม่ใช่แค่ความแตกต่างในไวยากรณ์ แต่เป็นความแตกต่างในความหมาย ใน C ไม่มีสิ่งเช่นคลาสเทมเพลตตัวป้องกันที่มีคุณสมบัติวิธีการฟังก์ชันเสมือน ฯลฯ ฉันไม่ชอบข้อตกลงที่ไม่เหมาะสมนี้
bogglez

1
ดังที่คนอื่น ๆ พูดไว้ซินแท็กซ์ - รั้งรั้งมาจากซีไวยากรณ์ของ Java (หรือ C # สำหรับเรื่องนั้น) ได้รับแรงบันดาลใจอย่างสูงจากสไตล์นี้
Pierre Arlaud

3

วิธีที่ OOP ที่สุดในการทำสิ่งต่าง ๆ อาจจะเป็นการทำสิ่งที่มีมรดก คลาสพื้นฐานของคุณ (หรือคลาสซูเปอร์ขึ้นอยู่กับภาษา) จะเป็นบุคคลดังนั้นบางทีคนร้ายและฮีโร่ที่สืบทอดมาจากคลาสพื้นฐาน จากนั้นฮีโร่ที่แข็งแกร่งของคุณและฮีโร่ที่ใช้เครื่องบินจะแตกแขนงออกไปเนื่องจากโหมดการขนส่งของพวกเขานั้นแตกต่างกันเช่น นี่เป็นโบนัสเพิ่มเติมที่ผู้เล่นคอมพิวเตอร์ของคุณสามารถมีคลาสพื้นฐานเหมือนกับผู้เล่นที่เป็นมนุษย์และหวังว่าจะทำให้ชีวิตของคุณง่ายขึ้น

อีกสิ่งหนึ่งที่เกี่ยวกับคุณลักษณะและนี่คือ OOP ที่เจาะจงน้อยกว่าคือการแสดงคุณลักษณะของตัวละครของคุณในรายการดังนั้นคุณไม่จำเป็นต้องให้มันถูกกำหนดอย่างชัดเจนในรหัสของคุณ ดังนั้นบางทีคุณอาจมีรายการอาวุธและรายการคุณสมบัติทางกายภาพ สร้างคลาสพื้นฐานบางอย่างสำหรับแอตทริบิวต์เหล่านี้เพื่อให้สามารถโต้ตอบกันได้ดังนั้นแต่ละคนจะถูกกำหนดในแง่ของความเสียหายต้นทุนด้านพลังงาน ฯลฯ ดังนั้นเมื่อบุคคลสองคนมารวมกันมันค่อนข้างชัดเจนว่าการโต้ตอบจะเกิดขึ้นอย่างไร คุณจะวนซ้ำในรายการของรายชื่อของตัวละครแต่ละตัวและคำนวณความเสียหายที่ตัวละครทำกับคนอื่นด้วยระดับความน่าจะเป็นในแต่ละการโต้ตอบ

การใช้รายการจะช่วยให้คุณหลีกเลี่ยงการเขียนโค้ดจำนวนมากอีกครั้งเพื่อเพิ่มตัวละครที่มีคุณสมบัติที่คุณไม่เคยคิดมาก่อนคุณต้องแน่ใจว่ามันมีการโต้ตอบกับระบบที่คุณมีอยู่


4
ฉันคิดว่าประเด็นคือการแยกสถิติออกจากชั้นเรียนของฮีโร่ การรับมรดกไม่จำเป็นต้องเป็นทางออกที่ดีที่สุดของ OOP (ในคำตอบของฉันฉันใช้การแต่งเพลงแทน)
Pierre Arlaud

1
ฉันสองความคิดเห็นก่อนหน้า จะเกิดอะไรขึ้นถ้าตัวอักษร C มีคุณสมบัติจากตัวอักษร A และ B (ซึ่งทั้งคู่มีคลาสพื้นฐานเดียวกัน) คุณทั้งรหัสที่ซ้ำกันหรือใบหน้าปัญหาบางอย่างที่เกี่ยวข้องกับมรดกหลาย ฉันจะชอบองค์ประกอบมากกว่ามรดกในกรณีนี้
ComFreek

2
ยุติธรรมพอสมควร ฉันแค่ให้ OP บางตัวเลือก ดูเหมือนว่าจะมีหินในระดับนี้มากและฉันก็ไม่รู้ระดับประสบการณ์ของพวกเขา มันอาจจะเป็นเรื่องเล็กน้อยที่จะคาดหวังว่าผู้เริ่มต้นจะดึงความแตกต่างที่เต็มไปด้วยระเบิด แต่การสืบทอดนั้นง่ายพอสำหรับผู้เริ่มต้นที่จะเข้าใจ ฉันพยายามที่จะช่วยแก้ไขปัญหาความรู้สึกของรหัส 'เงอะงะ' ซึ่งฉันสามารถสันนิษฐานได้ว่าหมายถึงการมีฟิลด์ที่เขียนโค้ดอย่างหนักซึ่งอาจจะมีหรือไม่มีประโยชน์ก็ได้ การใช้รายการสำหรับค่าที่ไม่ได้กำหนดชนิดเหล่านี้ imho เป็นตัวเลือกที่ดี
GenericJam

1
-1: "วิธีที่ OOP ที่สุดในการทำสิ่งต่าง ๆ อาจจะเป็นการทำอะไรบางอย่างที่มีการสืบทอด" การสืบทอดนั้นไม่ได้เน้นที่วัตถุเป็นพิเศษ
Sean Middleditch

3

ฉันขอแนะนำตัวจัดการประเภทสถิติที่มีประชากรจาก datafile (เช่นฉันใช้ XML) และวัตถุ Stat ด้วยประเภทและค่าที่เก็บไว้ในตัวละครที่ไม่ได้เป็น hashtable ด้วย ID ที่ไม่ซ้ำกันประเภทสถิติเป็นกุญแจสำคัญ

แก้ไข: รหัส 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;
    }
}

ผมคิดว่าStatTypeการเรียนในที่ไม่จำเป็นเพียงแค่ใช้nameของStatTypeเป็นสำคัญ ตามที่ #Grimston ทำ Statsเช่นเดียวกันกับ
giannis christofakis

ฉันชอบที่จะใช้คลาสในอินสแตนซ์เช่นนี้เพื่อให้คุณสามารถแก้ไขชื่อได้อย่างอิสระ (ในขณะที่ id ยังคงไม่เปลี่ยนแปลง) overkill ในกรณีนี้? บางที แต่เนื่องจากเทคนิคนี้อาจใช้กับบางสิ่งที่คล้ายกันในโปรแกรมฉันเลยทำแบบนี้เพื่อความมั่นคง
DFreeman

0

Ιจะพยายามยกตัวอย่างวิธีการออกแบบคลังแสงและคลังแสงของคุณ

เป้าหมายของเราคือการแยกหน่วยงานออกดังนั้นอาวุธควรเป็นส่วนต่อประสาน

interface Weapon {
    public int getDamage();
}

สมมติว่าผู้เล่นแต่ละคนสามารถครอบครองอาวุธได้เพียงตัวเดียวเท่านั้นเราสามารถใช้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;
    }
}

อีกรูปแบบที่มีประโยชน์จะเป็นรูปแบบวัตถุ Nullในกรณีที่ผู้เล่นไม่มีอาวุธ

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

สำหรับ decoupling ฉันสร้างอินเตอร์เฟสสำหรับผู้พิทักษ์

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

Voila!


2
คำตอบนี้จะเป็นประโยชน์มากขึ้นเมื่อคุณจะอธิบายเหตุผลของตัวเลือกการออกแบบของคุณ
Philipp
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.