วิธีการจัดโครงสร้างรหัสสำหรับอาวุธ / คาถา / พลังพิเศษมากมาย


22

ฉันเป็นโปรแกรมเมอร์ที่ไม่มีประสบการณ์ในการสร้างเกม "เหมือนโร๊คไลค์" ในเส้นเลือดของFTLโดยใช้ Python (ยังไม่มี PyGame ในขณะที่ฉันยังเกี่ยวข้องกับข้อความเท่านั้น)

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

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

เห็นได้ชัดว่าเป็นไปไม่ได้ แต่อดีตยังคงสามารถทำงานได้ อย่างไรก็ตามนั่นทำให้ฉันมีคำถาม: ฉันจะวางรหัสสำหรับอาวุธแต่ละชนิดได้ที่ไหน

ฉันจะสร้าง plasmarifle.py, rocketlauncher.py, swarmofbees.py ฯลฯ และวางมันลงในโฟลเดอร์ที่เกมสามารถนำเข้าได้หรือไม่

หรือมีวิธีที่จะมีไฟล์สไตล์ฐานข้อมูล (อาจเป็นอะไรที่เรียบง่ายเหมือนกับสเปรดชีต Excel) ที่มีรหัสที่ไม่ซ้ำกันสำหรับอาวุธแต่ละอัน - โดยไม่ต้องหันไปใช้ eval / exec?

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

วิธีแก้ปัญหาบางส่วนที่ฉันพบที่อื่นในไซต์นี้แนะนำให้คลาส BasicWeapon มีวิธีการที่ว่างเปล่ามากมาย - on_round_start (), on_attack (), on_move () ฯลฯ - จากนั้นเอาชนะวิธีเหล่านั้นสำหรับอาวุธแต่ละชนิด ในช่วงที่เกี่ยวข้องของวงจรการต่อสู้เกมจะเรียกวิธีการที่เหมาะสมสำหรับอาวุธของตัวละครทุกตัวและมีเพียงคนที่มีวิธีการที่กำหนดไว้เท่านั้นที่จะทำอะไรบางอย่างจริงๆ สิ่งนี้ช่วยได้ แต่ก็ไม่ได้บอกฉันว่าต้องใส่รหัสและ / หรือข้อมูลสำหรับอาวุธแต่ละชนิดอย่างไร

มีภาษาหรือเครื่องมืออื่นที่ฉันสามารถใช้เป็นข้อมูลครึ่งหนึ่ง, ความฝันรหัสครึ่ง? ฉันกำลังฝึกฝนการเขียนโปรแกรมที่ดีอยู่หรือไม่

ความเข้าใจของฉันเกี่ยวกับ OOP นั้นสมบูรณ์แบบที่สุดดังนั้นฉันขอขอบคุณคำตอบที่ไม่วิทยาการคอมพิวเตอร์มากเกินไป

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


3
ที่เกี่ยวข้อง: gamedev.stackexchange.com/questions/17276/…
MichaelHouse

@ Byte56 เกี่ยวข้องกับ; แต่ฉันคิดว่านี่เป็นสิ่งที่ OP พยายามหลีกเลี่ยง ฉันคิดว่าพวกเขากำลังพยายามหาวิธีการขับเคลื่อนข้อมูลมากขึ้น ช่วยแก้ให้ด้วยนะถ้าฉันผิด.
Vaughan Hilts

ฉันยอมรับว่าพวกเขากำลังพยายามหาวิธีการที่มุ่งเน้นข้อมูลมากขึ้น โดยเฉพาะฉันชอบคำตอบของ Josh สำหรับคำถามนั้น: gamedev.stackexchange.com/a/17286/7191
MichaelHouse

อ่าขอโทษด้วย :) ฉันมีนิสัยที่ไม่ดีในการอ่าน "คำตอบที่ยอมรับ"
Vaughan Hilts

คำตอบ:


17

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

โดยพื้นฐานแล้วสิ่งนี้เกี่ยวข้องกับการจัดเก็บข้อมูลเกี่ยวกับอาวุธของคุณในภาษามาร์กอัปหรือรูปแบบไฟล์ที่คุณเลือก XML และ JSON เป็นทั้งตัวเลือกที่ดีและสามารถอ่านได้ซึ่งสามารถใช้ในการแก้ไขได้ค่อนข้างง่ายโดยไม่ต้องใช้เครื่องมือแก้ไขที่ซับซ้อนหากคุณแค่พยายามเริ่มต้นอย่างรวดเร็ว ( และ Python ก็สามารถแยกวิเคราะห์ XML ได้อย่างง่ายดายเช่นกัน! ) คุณต้องการตั้งค่าแอตทริบิวต์เช่น 'power', 'defense', 'cost' และ 'stats' ที่เกี่ยวข้องทั้งหมด วิธีที่คุณจัดโครงสร้างข้อมูลของคุณจะขึ้นอยู่กับคุณ

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

อ่านเพิ่มเติมมีอยู่ด้านล่าง:


2
ชนิดของระบบที่อิงองค์ประกอบซึ่งคอมโพเนนต์อ่านผ่านสคริปต์ เช่นนี้: gamedev.stackexchange.com/questions/33453/ …
MichaelHouse

2
และในขณะที่คุณอยู่ให้สร้างสคริปต์เป็นส่วนหนึ่งของข้อมูลนั้นเพื่อให้อาวุธใหม่สามารถทำสิ่งใหม่ ๆ โดยไม่มีการเปลี่ยนแปลงรหัสหลัก
Patrick Hughes

@Vaughan Hilts: ขอบคุณข้อมูลที่ขับเคลื่อนดูเหมือนจะเป็นสิ่งที่ฉันเข้าใจอย่างถ่องแท้ฉันต้องการ ฉันเปิดคำถามทิ้งไว้สักครู่เพราะฉันยังต้องการคำตอบอยู่ แต่อาจจะเลือกคำตอบนี้เป็นคำตอบที่ดีที่สุด
henrebotha

@ แพทริกฮิวจ์: ที่ว่าสิ่งที่ฉันต้องการ! ฉันจะทำอย่างไร คุณสามารถแสดงตัวอย่างหรือการสอนแบบง่ายให้ฉันดูได้ไหม
henrebotha

1
ก่อนอื่นคุณต้องมีเอ็นจิ้นสคริปต์ในเอ็นจินของคุณหลาย ๆ คนเลือก LUA ที่เข้าถึงระบบการเล่นเกมเช่นเอฟเฟกต์และสถิติ จากนั้นเมื่อคุณสร้างวัตถุของคุณใหม่จากคำอธิบายข้อมูลคุณสามารถฝังสคริปต์ที่เอ็นจิ้นของคุณเรียกใช้เมื่อใดก็ตามที่วัตถุใหม่ของคุณถูกเปิดใช้งาน ในสมัยก่อนของโคลนนี้เรียกว่า "proc" (ย่อมาจาก Process) ส่วนที่ยากคือการทำให้คุณสมบัติการเล่นเกมของคุณในเครื่องยนต์มีความยืดหยุ่นมากพอที่จะเรียกได้จากภายนอกและมีคุณสมบัติเพียงพอ
Patrick Hughes

6

(ฉันขอโทษที่ส่งคำตอบแทนความคิดเห็น แต่ฉันยังไม่มีตัวแทน)

คำตอบของวอฮ์นนั้นยอดเยี่ยม แต่ฉันต้องการเพิ่มสองเซ็นต์ของฉัน

หนึ่งในสาเหตุหลักที่คุณต้องการใช้ XML หรือ JSON และแยกวิเคราะห์ในรันไทม์คือการเปลี่ยนแปลงและทดสอบกับค่าใหม่โดยไม่ต้องคอมไพล์โค้ดใหม่ เนื่องจาก Python ถูกตีความและในความคิดของฉันอ่านได้ค่อนข้างดีคุณอาจมีข้อมูลดิบในไฟล์ที่มีพจนานุกรมและทุกอย่างถูกจัดระเบียบ:

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

วิธีนี้คุณเพียงแค่นำเข้าไฟล์ / โมดูลและใช้เป็นพจนานุกรมปกติ

หากคุณต้องการเพิ่มสคริปต์คุณสามารถใช้ประโยชน์จากธรรมชาติของฟังก์ชั่น Python และคลาสที่ 1 ได้ คุณสามารถทำสิ่งนี้:

def special_shot():
    ...

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

แม้ว่าฉันเชื่อว่าจะขัดกับการออกแบบที่ขับเคลื่อนด้วยข้อมูล ในการเป็น 100% DDD คุณจะต้องมีข้อมูล (ข้อมูล) ระบุสิ่งที่จะเป็นฟังก์ชั่นและรหัสที่อาวุธเฉพาะจะใช้ วิธีนี้คุณจะไม่ทำลาย DDD เนื่องจากคุณไม่ได้ผสมข้อมูลกับฟังก์ชันการทำงาน


ขอขอบคุณ. เพียงแค่เห็นตัวอย่างรหัสง่ายๆช่วยให้คลิก
henrebotha

1
+1 สำหรับคำตอบที่ดีและเพื่อให้คุณมีตัวแทนมากพอที่จะแสดงความคิดเห็น ;) ยินดีต้อนรับ
ver

4

การออกแบบที่ขับเคลื่อนด้วยข้อมูล

ฉันส่งบางสิ่งเช่นคำถามนี้ไปยังการตรวจสอบโค้ดเมื่อเร็ว ๆ นี้

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

Data-Driven Design แม้ว่า Python จะเป็นภาษาที่ตีความ (ทั้งซอร์สและไฟล์ข้อมูลสามารถแก้ไขได้โดยไม่จำเป็นต้องคอมไพล์ใหม่) แต่ดูเหมือนว่าสิ่งที่ถูกต้องในกรณีเช่นที่คุณนำเสนอ คำถามนี้มีรายละเอียดเพิ่มเติมเกี่ยวกับแนวคิดข้อดีข้อเสียของมัน นอกจากนี้ยังมีการนำเสนอที่ดีเกี่ยวกับ Cornell Universityเกี่ยวกับเรื่องนี้

เมื่อเทียบกับภาษาอื่นเช่น C ++ ที่อาจใช้ภาษาสคริปต์ (เช่น LUA) เพื่อจัดการการโต้ตอบระหว่าง data x engine และการเขียนสคริปต์โดยทั่วไปและรูปแบบข้อมูลบางอย่าง (เช่น XML) เพื่อเก็บข้อมูล Python สามารถทำได้จริง มันทั้งหมดด้วยตัวเอง (พิจารณามาตรฐานdictแต่ยังweakrefหลังเฉพาะสำหรับการโหลดทรัพยากรและแคช)

อย่างไรก็ตามผู้พัฒนาอิสระอาจไม่ใช้วิธีการที่ขับเคลื่อนด้วยข้อมูลเป็นอย่างมากดังที่แนะนำไว้ในบทความนี้ :

ฉันมีการออกแบบที่ขับเคลื่อนด้วยข้อมูลมากน้อยแค่ไหน? ฉันไม่คิดว่าเอ็นจิ้นเกมควรมีรหัสเฉพาะของเกมบรรทัดเดียว ไม่ใช่หนึ่งเดียว ไม่มีอาวุธประเภท hardcoded ไม่มีโครงร่าง HUD ที่ฮาร์ดโค้ด ไม่มีหน่วย hardcoded AI Nada ซิป zilch

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

การประมวลผลตัวอย่างอย่างง่าย

ในกรณีเฉพาะที่กล่าวถึงการตรวจสอบโค้ดพจนานุกรมจะเก็บทั้ง "แอตทริบิวต์คงที่" และตรรกะที่ต้องตีความ - หากอาวุธมีพฤติกรรมตามเงื่อนไขใด ๆ

ในตัวอย่างด้านล่างดาบควรมีความสามารถและสถิติอยู่ในมือของตัวละครของคลาส 'antipaladin' และไม่มีผลกระทบใด ๆ โดยมีสถิติต่ำกว่าเมื่อใช้โดยตัวละครอื่น):

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

สำหรับวัตถุประสงค์ในการทดสอบฉันสร้างวิPlayerและWeaponคลาส: ตัวแรกเพื่อเก็บ / ติดตั้งอาวุธ (ซึ่งเรียกการตั้งค่า on_equip แบบมีเงื่อนไข) และอันหลังเป็นคลาสเดียวที่จะดึงข้อมูลจากพจนานุกรมตามชื่อรายการที่ส่งผ่านเป็น อาร์กิวเมนต์ในระหว่างการWeaponเริ่มต้น พวกเขาไม่ได้สะท้อนถึงการออกแบบคลาสเกมที่เหมาะสม แต่ยังคงมีประโยชน์ในการทดสอบข้อมูล:

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)

ด้วยการปรับปรุงในอนาคตฉันหวังว่าสิ่งนี้จะทำให้ฉันมีระบบการประดิษฐ์แบบไดนามิกสักวันหนึ่งในการประมวลผลส่วนประกอบอาวุธแทนอาวุธทั้งหมด ...

ทดสอบ

  1. ตัวละคร A เลือกอาวุธติดตั้ง (เราพิมพ์สถิติ) แล้วปล่อย
  2. ตัวละคร B เลือกอาวุธเดียวกันสวมมัน (และเราจะพิมพ์สถิติของมันอีกครั้งเพื่อแสดงว่ามันแตกต่างกันอย่างไร)

อย่างนี้:

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

ควรพิมพ์:

สำหรับนักกวี

การเพิ่มประสิทธิภาพ: 2, เอฟเฟกต์ Hit: [], เอฟเฟกต์อื่น ๆ : []

สำหรับ antipaladin

การเพิ่มประสิทธิภาพ: 5, เอฟเฟกต์ Hit: ['unholy'], เอฟเฟกต์อื่น ๆ : ['unholy aurea']

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