Kiến trúc OOP cho Hero có nhiều thuộc tính


14

Tôi sắp bắt đầu một game nhập vai văn bản trình duyệt đơn giản, với các nhân vật có thể (thụ động) chiến đấu với người khác. Điều này liên quan đến một danh sách khoảng 10 kỹ năng như sức mạnh, sự khéo léo, v.v., với sự thành thạo bổ sung cho các vũ khí khác nhau.

Có cách nào tốt hơn để thiết kế lớp nhân vật này sau đó chỉ cần có những kỹ năng đó như thuộc tính của lớp không? Có vẻ dễ, nhưng tôi miễn cưỡng vì nó vụng về.

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

1
Bạn nên kiểm tra hướng dẫn hoàn toàn này để viết trò chơi và làm thế nào một số lớp phổ biến có thể trông giống như liên kết
rồng

@dragons Cảm ơn liên kết thú vị, nhưng tôi không thấy giải thích sâu hơn cho việc thiết kế Charactorlớp học?
Sven

1
Chính xác thì bạn thấy "vụng về" về thiết kế này là gì?
Thomas

Câu trả lời:


17

Miễn là bạn giữ cho hệ thống của bạn tương đối đơn giản, điều này sẽ hoạt động. Nhưng khi bạn thêm những thứ như sửa đổi kỹ năng tạm thời, bạn sẽ sớm thấy rất nhiều mã trùng lặp. Bạn cũng sẽ gặp vấn đề với các vũ khí khác nhau bằng cách sử dụng thành thạo khác nhau. Bởi vì mỗi kỹ năng là một biến khác nhau, bạn sẽ phải viết mã khác nhau cho từng loại kỹ năng về cơ bản giống nhau (hoặc sử dụng một số hack phản chiếu xấu xí - trong điều kiện ngôn ngữ lập trình của bạn hỗ trợ chúng).

Vì lý do đó, tôi khuyên bạn nên lưu trữ cả kỹ năng và trình độ trong cấu trúc dữ liệu kết hợp để ánh xạ các hằng số kỹ năng thành các giá trị. Làm thế nào để làm điều đó thanh lịch khác nhau từ ngôn ngữ lập trình đến ngôn ngữ lập trình. Khi ngôn ngữ của bạn hỗ trợ nó, các hằng số sẽ ở trong một enum.

Để cho bạn một ví dụ về cách thức hoạt động của nó trong thực tế, mã của bạn để tính toán thiệt hại tấn công sau đó sẽ trông giống như thế này:

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

Tạo một phương thức như getSkill()đi ngược lại nguyên tắc cơ bản của lập trình hướng đối tượng .
Jephir

4

Tại sao không sử dụng các mảng liên quan?, Điều này mang lại lợi ích của việc dễ dàng mở rộng (ví dụ sử dụng PHP)

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

đối với những thứ như vũ khí, có lẽ bạn sẽ muốn tạo một số lớp cơ sở

Vũ khí -> MeleeWeapon, RangedWeapon

và sau đó tạo vũ khí của bạn từ đó.

Kết quả cuối cùng tôi muốn nhắm đến là một lớp học như thế này

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;

    }
}

Bạn có thể lưu trữ mọi thứ trong một mảng nếu bạn thực sự muốn.


Đó là khá nhiều những gì @Philipp nói?
sói

1
@Philipp đề xuất sử dụng enums, mảng là một lựa chọn khác.
Grimston

Nó thực sự nói: "... cấu trúc dữ liệu liên kết ánh xạ các hằng số kỹ năng thành các giá trị", các hằng số trong từ điển có thể là các chuỗi.
sói

3

Tôi sẽ cố gắng trả lời câu hỏi này theo cách OOP nhất (hoặc ít nhất là những gì tôi nghĩ nó sẽ xảy ra). Nó có thể hoàn toàn quá mức, tùy thuộc vào diễn biến bạn thấy về các số liệu thống kê.

Bạn có thể tưởng tượng một lớp Skillet (hoặc Thống kê ) (Tôi đang sử dụng cú pháp giống như C cho câu trả lời này):

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

    }

}

Sau đó, anh hùng sẽ có một trường IntatsicStats thuộc loại Skillset. Một vũ khí có thể có một kỹ năng sửa đổi là tốt.

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

}

Tất nhiên đây là một ví dụ để cung cấp cho bạn ý tưởng. Bạn cũng có thể cân nhắc sử dụng mẫu thiết kế Trình trang trí, để các công cụ sửa đổi trên số liệu thống kê hoạt động như "bộ lọc" được áp dụng sau bộ lọc khác


Làm thế nào là giống như C này?
bogglez

@bogglez: Tôi cũng có xu hướng sử dụng "C'ish" để chỉ mã giả giống như một ngôn ngữ niềng răng, ngay cả khi các lớp học có liên quan. Mặc dù vậy, bạn có một điểm: đây có vẻ giống như cú pháp cụ thể hơn - đó là một thay đổi nhỏ hoặc cách hai lần để có thể biên dịch được Java.
cHao

Tôi không nghĩ rằng tôi quá nghiêm khắc ở đây. Đây không chỉ là một sự khác biệt về cú pháp, mà là một sự khác biệt về ngữ nghĩa. Trong C không có thứ gọi là lớp, mẫu, vòng loại được bảo vệ, phương thức, hàm ảo, v.v. Tôi chỉ không thích sự lạm dụng thuật ngữ thông thường này.
bogglez

1
Như những người khác đã nói, cú pháp cú đúp xuất phát từ C. Cú pháp của Java (hoặc C # cho vấn đề đó) được truyền cảm hứng rất nhiều bởi phong cách này.
Pierre Arlaud

3

Cách làm OOP nhất có lẽ sẽ là làm một cái gì đó với sự kế thừa. Lớp cơ sở của bạn (hoặc siêu hạng tùy thuộc vào ngôn ngữ) sẽ là người, sau đó có thể là nhân vật phản diện và anh hùng thừa hưởng từ lớp cơ sở. Sau đó, các anh hùng dựa trên sức mạnh của bạn và các anh hùng dựa trên chuyến bay sẽ phân nhánh vì phương thức vận chuyển của họ là khác nhau. Điều này có thêm phần thưởng rằng người chơi máy tính của bạn có thể có cùng lớp cơ sở với người chơi và hy vọng nó sẽ đơn giản hóa cuộc sống của bạn.

Một điều khác liên quan đến các thuộc tính và điều này ít cụ thể hơn về OOP sẽ là biểu thị các thuộc tính ký tự của bạn dưới dạng một danh sách để bạn không cần phải xác định rõ ràng tất cả chúng trong mã của mình. Vì vậy, có thể bạn sẽ có một danh sách cho vũ khí và một danh sách cho các thuộc tính vật lý. Tạo một số loại cơ sở cho các thuộc tính này để chúng có thể tương tác, do đó mỗi thuộc tính được xác định theo mức độ thiệt hại, chi phí năng lượng, v.v. vì vậy khi hai cá nhân đến với nhau, tương đối rõ ràng sẽ diễn ra tương tác như thế nào. Bạn sẽ lặp qua danh sách các thuộc tính của từng nhân vật và tính toán thiệt hại mà người này gây ra cho người kia với một mức độ xác suất trong mỗi tương tác.

Sử dụng danh sách sẽ giúp bạn tránh viết lại rất nhiều mã vì để thêm một ký tự với thuộc tính mà bạn chưa từng nghĩ đến, bạn chỉ cần đảm bảo rằng nó có tương tác hoạt động với hệ thống hiện tại của bạn.


4
Tôi nghĩ vấn đề là tách rời các chỉ số từ lớp anh hùng. Kế thừa không nhất thiết là giải pháp OOP tốt nhất (trong câu trả lời của tôi, tôi đã sử dụng thành phần thay thế).
Pierre Arlaud

1
Tôi thứ hai bình luận trước. Điều gì xảy ra nếu một nhân vật C có các thuộc tính từ nhân vật A và B (cả hai đều có cùng một lớp cơ sở)? Bạn có thể sao chép mã hoặc đối mặt với một số vấn đề liên quan đến nhiều kế thừa . Tôi sẽ ủng hộ thành phần hơn thừa kế trong trường hợp này.
ComFalet

2
Đủ công bằng. Tôi chỉ đưa ra cho OP một số tùy chọn. Có vẻ như không có nhiều điều thiết lập ở giai đoạn này và tôi không biết mức độ kinh nghiệm của họ. Có lẽ sẽ hơi nhiều khi mong đợi một người mới bắt đầu loại bỏ đa hình thổi toàn bộ, nhưng sự kế thừa đủ đơn giản để người mới bắt đầu nắm bắt. Tôi đã cố gắng giúp giải quyết vấn đề về mã cảm giác 'vụng về' mà ​​tôi chỉ có thể giả sử đề cập đến việc có các trường được mã hóa cứng có thể hoặc không thể sử dụng. Sử dụng danh sách cho các loại giá trị không xác định này imho là một lựa chọn tốt.
GenericJam

1
-1: "Cách làm OOP nhiều nhất có lẽ là làm việc gì đó với sự kế thừa." Kế thừa là rất không đặc biệt hướng đối tượng.
Sean Middleditch

3

Tôi muốn giới thiệu một trình quản lý loại stat, được điền từ một tệp dữ liệu (ví dụ: tôi sử dụng XML) và các đối tượng Stat, với một loại và một giá trị được lưu trữ trong ký tự không phải là hàm băm, với ID duy nhất của loại stat làm khóa.

Chỉnh sửa: Mã 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;
    }
}

Tôi nghĩ rằng StatTypelớp trong không cần thiết, chỉ cần sử dụng namecủa StatTypelàm khóa. Như #Grimston đã làm. Tương tự với Stats.
gianni christofakis 26/03 '

Tôi thích sử dụng một lớp trong các trường hợp như thế này để bạn có thể tự do sửa đổi tên (trong khi id không đổi). Quá mức trong trường hợp này? Có lẽ, nhưng vì kỹ thuật này có thể được sử dụng cho một cái gì đó tương tự ở nơi khác trong một chương trình, tôi sẽ làm nó như thế này để thống nhất.
DFreeman

0

Sẽ cố gắng đưa ra một ví dụ về cách bạn có thể thiết kế kho vũ khí và kho vũ khí của mình.

Mục tiêu của chúng tôi là tách rời các thực thể, do đó vũ khí nên là một giao diện.

interface Weapon {
    public int getDamage();
}

Giả sử rằng mỗi người chơi chỉ có thể sở hữu một vũ khí, chúng ta có thể sử dụng Strategy patternđể thay đổi vũ khí một cách dễ dàng.

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

Một mẫu hữu ích khác sẽ là Mẫu đối tượng Null trong trường hợp người chơi không vũ trang.

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

Về phần vũ khí, chúng ta có thể mặc nhiều thiết bị phòng thủ.

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

Để tách rời, tôi tạo một giao diện cho hậu vệ.

interface Defender {
    int getDefended();
}

Và cả Playerlớp.

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

}

Hãy thêm một số trò chơi.

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
Câu trả lời này sẽ hữu ích hơn khi bạn giải thích lý do của bạn đằng sau các lựa chọn thiết kế của bạn.
Philipp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.