Cách cấu trúc mã cho nhiều vũ khí / phép thuật / sức mạnh độc đáo


22

Tôi là một lập trình viên thiếu kinh nghiệm tạo ra một trò chơi "giống như roguelike" trong mạch của FTL , sử dụng Python (không có PyGame cho đến khi tôi vẫn chỉ quan tâm đến văn bản).

Trò chơi của tôi sẽ chứa một số lượng lớn vũ khí (khoảng 50 cho người mới bắt đầu) mang lại khả năng độc đáo. Tôi đang đấu tranh để hiểu cách cấu trúc mã đối tượng theo cách vừa mạnh mẽ (về mặt cho phép vũ khí có hiệu ứng hoàn toàn khác nhau) và có thể mở rộng (để tôi có thể dễ dàng thêm nhiều vũ khí hơn sau đó bằng cách thả chúng vào một thư mục ).

Bản năng đầu tiên của tôi là có một lớp BasicWeapon và có những vũ khí khác nhau được thừa hưởng từ lớp đó. Tuy nhiên, điều này có vẻ có vấn đề với tôi: hoặc tôi phải làm cho lớp BasicWeapon trở nên vô dụng (về cơ bản, tất cả các loại vũ khí đều có chung là tên và loại (súng lục, rìu, v.v.)), hoặc tôi phải dự đoán mọi hiệu ứng độc đáo mà tôi sẽ đưa ra và mã hóa nó vào BasicWeapon.

Cái sau rõ ràng là không thể, nhưng cái trước vẫn có thể được làm việc. Tuy nhiên, điều đó để lại cho tôi câu hỏi: tôi phải đặt mã cho vũ khí cá nhân ở đâu?

Tôi có tạo plasmarifle.py, rocketlauncher.py, swarmofbees.py, v.v. và thả tất cả chúng vào một thư mục từ đó trò chơi có thể nhập chúng không?

Hoặc có cách nào để có tệp kiểu cơ sở dữ liệu (có thể đơn giản như bảng tính Excel) bằng cách nào đó chứa mã duy nhất cho mỗi vũ khí - mà không cần phải dùng đến eval / exec?

Về giải pháp sau (cơ sở dữ liệu), tôi nghĩ vấn đề cơ bản mà tôi đang đấu tranh là trong khi tôi hiểu rằng việc duy trì sự tách biệt giữa mã và dữ liệu là điều mong muốn, tôi cảm thấy như vũ khí làm mờ ranh giới giữa "mã" và "dữ liệu" một chút; chúng đại diện cho rất nhiều thứ tương tự có thể tìm thấy trong trò chơi, theo nghĩa chúng giống như dữ liệu, nhưng hầu hết chúng sẽ yêu cầu ít nhất một số mã duy nhất không được chia sẻ với bất kỳ mục nào khác, theo nghĩa tự nhiên, chúng là, mã.

Một giải pháp một phần tôi đã tìm thấy ở nơi khác trên trang web này đề nghị cung cấp cho lớp BasicWeapon một loạt các phương thức trống - on_round_start (), on_attack (), on_move (), v.v. - và sau đó ghi đè các phương thức đó cho mỗi vũ khí. Ở giai đoạn có liên quan của chu kỳ chiến đấu, trò chơi sẽ gọi phương thức thích hợp cho mọi vũ khí của nhân vật và chỉ những người có phương pháp được xác định mới thực sự làm được điều gì đó. Điều này có ích, nhưng nó vẫn không cho tôi biết nơi tôi phải đặt mã và / hoặc dữ liệu cho mỗi vũ khí.

Có một ngôn ngữ hoặc công cụ khác mà tôi có thể sử dụng như một loại chimera nửa dữ liệu, nửa mã? Tôi hoàn toàn tàn sát thực hành lập trình tốt?

Sự hiểu biết của tôi về OOP là sơ sài, vì vậy tôi sẽ đánh giá cao những phản hồi không quá khoa học máy tính.

EDIT: Vaughan Hilts đã nói rõ trong bài viết của mình dưới đây rằng những gì tôi chủ yếu nói về lập trình dựa trên dữ liệu. Bản chất của câu hỏi của tôi là: làm thế nào tôi có thể thực hiện thiết kế dựa trên dữ liệu theo cách mà dữ liệu có thể chứa tập lệnh, cho phép vũ khí mới thực hiện những điều mới mà không thay đổi mã chương trình chính?



@ Byte56 Liên quan; nhưng tôi nghĩ đây là điều mà OP đang cố tránh. Tôi nghĩ họ đang cố gắng tìm một cách tiếp cận dựa trên dữ liệu nhiều hơn. Sửa lỗi cho tôi nếu tôi sai.
Vaughan Hilts

Tôi đồng ý họ đang cố gắng tìm một cách tiếp cận theo định hướng dữ liệu hơn. Cụ thể, tôi thích câu trả lời của Josh cho câu hỏi đó: gamedev.stackexchange.com/a/17286/7191
MichaelHouse

À, xin lỗi về điều đó. :) Tôi có một thói quen xấu là đọc "câu trả lời được chấp nhận".
Vaughan Hilts

Câu trả lời:


17

Bạn muốn một cách tiếp cận dựa trên dữ liệu gần như chắc chắn trừ khi trò chơi của bạn sẽ hoàn toàn không được mong đợi và / hoặc thủ tục được tạo ra đến cốt lõi.

Về cơ bản, điều này liên quan đến việc lưu trữ thông tin về vũ khí của bạn bằng ngôn ngữ đánh dấu hoặc định dạng tệp bạn chọn. XML và JSON đều là những lựa chọn tốt, có thể đọc được, có thể được sử dụng để chỉnh sửa khá đơn giản mà không cần các trình soạn thảo phức tạp nếu bạn chỉ đang cố gắng bắt đầu nhanh. ( Và Python cũng có thể phân tích cú pháp XML khá dễ dàng! ) Bạn đã đặt thuộc tính như 'sức mạnh', 'phòng thủ', 'chi phí' và 'thống kê' đều có liên quan. Cách bạn cấu trúc dữ liệu của bạn sẽ tùy thuộc vào bạn.

Nếu vũ khí cần thêm hiệu ứng trạng thái, cung cấp cho nó nút hiệu ứng Trạng thái, sau đó chỉ định hiệu ứng của hiệu ứng trạng thái thông qua một đối tượng điều khiển dữ liệu khác. Điều này sẽ làm cho mã của bạn bớt phụ thuộc vào trò chơi cụ thể và khiến việc chỉnh sửa và kiểm tra trò chơi của bạn trở nên tầm thường. Không phải biên dịch lại tất cả thời gian là một phần thưởng, quá.

Đọc thêm có sẵn dưới đây:


2
Giống như một hệ thống dựa trên thành phần, trong đó các thành phần được đọc thông qua các tập lệnh. Như thế này: gamedev.stackexchange.com/questions/33453/ trên
MichaelHouse

2
Và trong khi bạn đang ở đó, hãy tạo một phần kịch bản của dữ liệu đó để vũ khí mới có thể thực hiện những điều mới mà không cần thay đổi mã chính.
Patrick Hughes

@Vaughan Hilts: cảm ơn bạn, điều khiển dữ liệu dường như chính xác là những gì tôi trực giác hiểu được tôi cần. Tôi để câu hỏi mở trong một thời gian lâu hơn vì tôi vẫn cần câu trả lời, nhưng có lẽ sẽ chọn đây là câu trả lời tốt nhất.
henrebotha

@Patrick Hughes: đó chính xác là những gì tôi muốn! Làm thế nào để làm điều đó? Bạn có thể chỉ cho tôi một ví dụ đơn giản hoặc hướng dẫn?
henrebotha

1
Trước tiên, bạn cần một công cụ kịch bản trong công cụ của mình, nhiều người chọn LUA, truy cập các hệ thống chơi trò chơi như hiệu ứng và thống kê. Sau đó, vì bạn đã tạo lại các đối tượng của mình từ một mô tả dữ liệu, bạn có thể nhúng tập lệnh mà công cụ của bạn gọi bất cứ khi nào đối tượng mới của bạn được kích hoạt. Vào thời xa xưa của MUD, cái này được gọi là "Proc" (viết tắt của Process). Phần khó là làm cho các tính năng chơi trò chơi của bạn trong công cụ đủ linh hoạt để được gọi từ bên ngoài và có đủ các tính năng.
Patrick Hughes

6

(Tôi xin lỗi để gửi câu trả lời thay vì nhận xét, nhưng tôi chưa có đại diện.)

Câu trả lời của Vaughan rất hay, nhưng tôi muốn thêm hai xu của mình.

Một trong những lý do chính mà bạn muốn sử dụng XML hoặc JSON và phân tích cú pháp trong thời gian chạy là để thay đổi và thử nghiệm các giá trị mới mà không phải biên dịch lại mã. Vì Python được diễn giải và, theo tôi, khá dễ đọc, bạn có thể có dữ liệu thô trong một tệp có từ điển và mọi thứ được sắp xếp:

weapons = {
           'megaLazer' : {
                          'name' : "Mega Lazer XPTO"
                          'damage' : 100
                       },
           'ultraCannon' : {
                          'name' : "Ultra Awesome Cannon",
                          'damage' : 200
                       }
          }

Bằng cách này, bạn chỉ cần nhập tệp / mô-đun và sử dụng nó như một từ điển bình thường.

Nếu bạn muốn thêm tập lệnh, bạn có thể sử dụng tính chất động của hàm Python và lớp 1. Bạn có thể làm một cái gì đó như thế này:

def special_shot():
    ...

weapons = { 'megalazer' : { ......
                            shoot_gun = special_shot
                          }
          }

Mặc dù tôi tin rằng điều đó sẽ chống lại thiết kế điều khiển dữ liệu. Để được 100% DDD, bạn có thông tin (dữ liệu) chỉ định chức năng và mã mà vũ khí cụ thể sẽ sử dụng là gì. Bằng cách này, bạn không phá vỡ DDD, vì bạn không trộn dữ liệu với chức năng.


Cảm ơn bạn. Chỉ cần nhìn thấy một ví dụ mã đơn giản đã giúp nó nhấp chuột.
henrebotha

1
+1 cho câu trả lời hay và để bạn có đủ đại diện để bình luận. ;) Chào mừng.
ver

4

Thiết kế hướng dữ liệu

Tôi đã gửi một cái gì đó như câu hỏi này để xem xét mã gần đây.

Sau một số gợi ý và cải tiến, kết quả là một mã đơn giản cho phép sự linh hoạt tương đối trong việc tạo vũ khí dựa trên từ điển (hoặc JSON). Dữ liệu được diễn giải trong thời gian chạy và việc xác minh đơn giản được thực hiện bởi Weaponchính lớp đó, mà không cần phải dựa vào toàn bộ trình thông dịch tập lệnh.

Data-Driven Design, mặc dù Python là ngôn ngữ được dịch (cả tệp nguồn và dữ liệu đều có thể được chỉnh sửa mà không cần biên dịch lại chúng), nghe có vẻ đúng trong trường hợp như bạn đã trình bày. Câu hỏi này đi sâu vào chi tiết hơn về khái niệm, ưu và nhược điểm của nó. Có một bài thuyết trình hay trên Đại học Cornell về nó.

So với các ngôn ngữ khác, chẳng hạn như C ++, có thể sẽ sử dụng ngôn ngữ kịch bản lệnh (như LUA) để xử lý tương tác dữ liệu và động cơ dữ liệu nói chung và một định dạng dữ liệu nhất định (như XML) để lưu trữ dữ liệu, Python thực sự có thể làm được tất cả chỉ có một mình (xem xét tiêu chuẩn dictmà còn weakref, sau này đặc biệt cho tải tài nguyên và bộ đệm).

Tuy nhiên, một nhà phát triển độc lập có thể không đưa cách tiếp cận dựa trên dữ liệu đến mức cực đoan như đề xuất trong bài viết này :

Tôi có bao nhiêu về thiết kế dựa trên dữ liệu? Tôi không nghĩ rằng một công cụ trò chơi nên chứa một dòng mã dành riêng cho trò chơi. Không một. Không có loại vũ khí mã hóa cứng. Không có bố cục HUD mã hóa cứng. Không có AI đơn vị mã hóa cứng. Nada. Zip. Zilch.

Có lẽ, với Python, người ta có thể hưởng lợi từ cách tiếp cận hướng đối tượng và hướng dữ liệu tốt nhất, hướng đến cả năng suất và khả năng mở rộng.

Xử lý mẫu đơn giản

Trong trường hợp cụ thể được thảo luận về đánh giá mã, một từ điển sẽ lưu trữ cả "thuộc tính tĩnh" và logic cần được giải thích - nếu vũ khí có bất kỳ hành vi có điều kiện nào.

Trong ví dụ bên dưới, một thanh kiếm sẽ có một số khả năng và chỉ số trong tay các nhân vật của lớp 'antipaladin', và không có hiệu ứng, với chỉ số thấp hơn khi được sử dụng bởi các nhân vật khác):

WEAPONS = {
    "bastard's sting": {
        # magic enhancement, weight, value, dmg, and other attributes would go here.
        "magic": 2,

        # Those lists would contain the name of effects the weapon provides by default.
        # They are empty because, in this example, the effects are only available in a
        # specific condition.    
        "on_turn_actions": [],
        "on_hit_actions": [],
        "on_equip": [
            {
                "type": "check",
                "condition": {
                    'object': 'owner',
                    'attribute': 'char_class',
                    'value': "antipaladin"
                },
                True: [
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_hit",
                            "actions": ["unholy"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "add_to",
                        "args": {
                            "category": "on_turn",
                            "actions": ["unholy aurea"]
                        }
                    },
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 5
                        }
                    }
                ],
                False: [
                    {
                        "type": "action",
                        "action": "set_attribute",
                        "args": {
                            "field": "magic",
                            "value": 2
                        }
                    }
                ]
            }
        ],
        "on_unequip": [
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_hit",
                    "actions": ["unholy"]
                },
            },
            {
                "type": "action",
                "action": "remove_from",
                "args": {
                    "category": "on_turn",
                    "actions": ["unholy aurea"]
                },
            },
            {
                "type": "action",
                "action": "set_attribute",
                "args": ["magic", 2]
            }
        ]
    }
}

Với mục đích thử nghiệm, tôi đã tạo ra các lớp đơn giản PlayerWeapon: các lớp đầu tiên giữ / trang bị vũ khí (do đó gọi cài đặt on_equip có điều kiện của nó) và lớp sau là một lớp duy nhất lấy dữ liệu từ từ điển, dựa trên tên vật phẩm được truyền dưới dạng đối số trong quá trình Weaponkhởi tạo. Chúng không phản ánh thiết kế các lớp trò chơi phù hợp, nhưng vẫn có thể hữu ích để kiểm tra dữ liệu:

class Player:
    """Represent the player character."""

    inventory = []

    def __init__(self, char_class):
        """For this example, we just store the class on the instance."""
        self.char_class = char_class

    def pick_up(self, item):
        """Pick an object, put in inventory, set its owner."""
        self.inventory.append(item)
        item.owner = self


class Weapon:
    """A type of item that can be equipped/used to attack."""

    equipped = False
    action_lists = {
        "on_hit": "on_hit_actions",
        "on_turn": "on_turn_actions",
    }

    def __init__(self, template):
        """Set the parameters based on a template."""
        self.__dict__.update(WEAPONS[template])

    def toggle_equip(self):
        """Set item status and call its equip/unequip functions."""
        if self.equipped:
            self.equipped = False
            actions = self.on_unequip
        else:
            self.equipped = True
            actions = self.on_equip

        for action in actions:
            if action['type'] == "check":
                self.check(action)
            elif action['type'] == "action":
                self.action(action)

    def check(self, dic):
        """Check a condition and call an action according to it."""
        obj = getattr(self, dic['condition']['object'])
        compared_att = getattr(obj, dic['condition']['attribute'])
        value = dic['condition']['value']
        result = compared_att == value

        self.action(*dic[result])

    def action(self, *dicts):
        """Perform action with args, both specified on dicts."""
        for dic in dicts:
            act = getattr(self, dic['action'])
            args = dic['args']
            if isinstance(args, list):
                act(*args)
            elif isinstance(args, dict):
                act(**args)

    def set_attribute(self, field, value):
        """Set the specified field with the given value."""
        setattr(self, field, value)

    def add_to(self, category, actions):
        """Add one or more actions to the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action not in action_list:
                action_list.append(action)

    def remove_from(self, category, actions):
        """Remove one or more actions from the category's list."""
        action_list = getattr(self, self.action_lists[category])

        for action in actions:
            if action in action_list:
                action_list.remove(action)

Với một số cải tiến trong tương lai, tôi hy vọng rằng điều này thậm chí sẽ cho phép tôi có một hệ thống chế tạo năng động vào một ngày nào đó, xử lý các thành phần vũ khí thay vì toàn bộ vũ khí ...

Kiểm tra

  1. Nhân vật Một vũ khí chọn, trang bị cho nó (chúng tôi in các chỉ số của nó), sau đó thả nó xuống;
  2. Nhân vật B chọn cùng một vũ khí, trang bị cho nó (và chúng tôi in lại chỉ số của nó để cho thấy chúng khác nhau như thế nào).

Như thế này:

def test():
    """A simple test.

    Item features should be printed differently for each player.
    """
    weapon = Weapon("bastard's sting")
    player1 = Player("bard")
    player1.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
    weapon.toggle_equip()

    player2 = Player("antipaladin")
    player2.pick_up(weapon)
    weapon.toggle_equip()
    print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
        weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))

if __name__ == '__main__':
    test()

Nó nên in:

Cho một lần đặt cược

Cải tiến: 2, Hiệu ứng trúng: [], Hiệu ứng khác: []

Đối với một antipaladin

Tăng cường: 5, Hiệu ứng hit: ['u ám'], Hiệu ứng khác: ['aurea aurea']

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.