많은 고유 한 무기 / 주문 / 파워를위한 코드를 구성하는 방법


22

나는 파이썬을 사용하여 FTL 의 정맥에서 "도적 같은"게임을 만드는 경험이없는 프로그래머입니다 (여전히 텍스트에만 관심이있는 PyGame은 없습니다).

내 게임에는 독특한 능력을 발휘하는 수많은 무기 (초보자 약 50 명)가 포함됩니다. 강력하고 (무기마다 다른 효과를 낼 수 있다는 점에서) 강력하고 확장 가능 (나중에 폴더에 놓아 무기를 추가하여 나중에 더 쉽게 무기를 추가 할 수있는 방식)으로 오브젝트 코드를 구성하는 방법을 이해하려고 노력하고 있습니다. ).

나의 첫 번째 본능은 BasicWeapon 클래스를 갖는 것이었고, 그 클래스에서 다른 무기를 물려 받았습니다. 그러나 이것은 나에게 문제가되는 것처럼 보입니다 .BasicWeapon 클래스를 기본적으로 쓸모가 없도록 맨손으로 만들어야합니다 (모든 무기가 공통적으로 가지고있는 유일한 기능은 이름과 유형 (권총, 도끼 등)입니다) 또는 모든 것을 예측해야합니다 유니크 한 이펙트를 만들어 내고 BasicWeapon으로 코딩합니다.

후자는 분명히 불가능하지만 전자는 여전히 작동 할 수 있습니다. 그러나 그것은 나에게 질문을 남긴다 : 개별 무기에 대한 코드를 어디에 두어야합니까?

plasmarifle.py, rocketlauncher.py, swarmofbees.py 등을 만들어 게임에서 가져올 수있는 폴더에 모두 넣습니까?

또는 eval / exec를 사용하지 않고도 각 무기에 대한 고유 코드를 포함하는 데이터베이스 스타일 파일 (Excel 스프레드 시트와 같은 간단한 파일 일 수 있음)이 있습니까?

후자의 해결책 (데이터베이스)의 관점에서, 내가 고투하고있는 근본적인 문제는 코드와 데이터 사이의 분리를 유지하는 것이 바람직하다는 것을 이해하지만 무기가 "코드"사이의 경계를 흐리게하는 것처럼 느낍니다. 그리고 "데이터"를 조금; 그것들은 게임에서 찾을 수있는 매우 유사한 유사한 것들을 나타내며, 데이터와 같은 의미이지만, 대부분 다른 아이템과 공유되지 않는 고유 코드가 필요합니다. 암호.

이 사이트의 다른 곳에서 찾은 부분 솔루션은 BasicWeapon 클래스에 여러 가지 빈 메소드 (on_round_start (), on_attack (), on_move () 등)를 제공하고 각 무기에 대해 해당 메소드를 재정의하는 것을 제안합니다. 전투주기의 관련 단계에서 게임은 모든 캐릭터의 무기에 적절한 방법을 호출하며, 정의 된 방법을 가진 무기 만 실제로 무언가를 수행합니다. 이것은 도움이되지만 여전히 각 무기의 코드 및 / 또는 데이터를 어디에 두어야하는지 알려주지는 않습니다.

반 데이터, 반 코드 키메라의 일종으로 사용할 수있는 다른 언어 또는 도구가 있습니까? 좋은 프로그래밍 연습을 완전히 도용하고 있습니까?

내가없는 답변 부탁드립니다 있도록 OOP의 나의 이해는, 최상의 스케치입니다 너무 컴퓨터 과학-Y를.

편집 : Vaughan Hilts는 아래에서 자신의 입장에서 본질적으로 이야기하는 것이 데이터 중심 프로그래밍이라는 것을 분명히했습니다. 내 질문의 본질은 다음과 같습니다. 데이터에 스크립트를 포함 할 수있는 방식으로 데이터 중심 설계를 구현하여 주 프로그램 코드를 변경하지 않고 새로운 무기가 새로운 일을 할 수 있도록하려면 어떻게해야합니까?



@ Byte56 관련; 그러나 이것이 OP가 피하려고하는 것입니다. 더 많은 데이터 중심 접근 방식을 찾으려고 생각합니다. 틀 렸으면 말해줘.
Vaughan Hilts

더 많은 데이터 지향 접근 방식을 찾으려고 동의합니다. 특히, 나는이 질문에 대한 Josh의 대답을 좋아합니다 : gamedev.stackexchange.com/a/17286/7191
MichaelHouse

아 죄송합니다. :) "허용 된 답변"을 읽는 습관이 나쁩니다.
Vaughan Hilts

답변:


17

게임이 완전히 예상치 못한 및 / 또는 절차 적으로 코어에 생성되지 않는 한 거의 확실하게 데이터 중심 접근 방식을 원합니다 .

기본적으로 무기에 대한 정보를 마크 업 언어 또는 선택한 파일 형식으로 저장합니다. XML과 JSON은 모두 읽기 쉬운 선택이며, 빠른 시작을 원할 경우 복잡한 편집기 없이도 편집을 상당히 간단하게 만드는 데 사용할 수 있습니다. ( 그리고 파이썬도 XML을 매우 쉽게 파싱 할 수 있습니다! ) 당신은 'power', 'defense', 'cost', 'stats'와 같은 속성을 모두 관련시킵니다. 데이터를 구성하는 방법은 귀하에게 달려 있습니다.

무기에 상태 효과를 추가해야하는 경우 상태 효과 노드를 제공 한 다음 다른 데이터 기반 개체를 통해 상태 효과의 효과를 지정하십시오. 이를 통해 코드가 특정 게임에 덜 의존하고 게임을 편집하고 테스트하는 것이 쉽지 않습니다. 항상 다시 컴파일하지 않아도 보너스입니다.

보충 자료는 다음과 같습니다.


2
스크립트를 통해 컴포넌트를 읽는 컴포넌트 기반 시스템과 비슷합니다. 이처럼 : gamedev.stackexchange.com/questions/33453/...
MichaelHouse

2
그리고 당신이 그것을하는 동안, 새로운 무기가 주요 코드 변경없이 새로운 일을 할 수 있도록 해당 데이터의 스크립트 부분을 만드십시오.
Patrick Hughes

@Vaughan Hilts : 감사합니다. 데이터 중심은 내가 필요한 것을 직관적으로 이해 한 것 같습니다. 여전히 답변이 필요할 때 질문을 잠시 더 오래 열어두고 있지만 아마도 이것이 가장 좋은 답변으로 선택 될 것입니다.
henrebotha

@Patrick 휴즈 : 그건 정확히 내가 원하는! 어떻게합니까? 간단한 예나 튜토리얼을 보여줄 수 있습니까?
henrebotha

1
먼저 엔진에 스크립트 엔진이 필요합니다. 많은 사람들이 효과 및 통계와 같은 게임 플레이 시스템에 액세스하는 LUA를 선택합니다. 그런 다음 이미 데이터 설명에서 객체를 재생성하므로 새 객체가 활성화 될 때마다 엔진이 호출하는 스크립트를 포함 할 수 있습니다. MUD의 옛날에는 이것을 "proc"라고 불렀습니다 (프로세스의 줄임말). 어려운 점은 엔진의 게임 플레이 기능을 외부에서 호출 할 수있을만큼 유연하고 충분한 기능을 제공하는 것입니다.
Patrick Hughes

6

(댓글 대신 답변을 제출하여 죄송하지만 아직 담당자가 없습니다.)

본의 대답은 훌륭하지만 2 센트를 더하고 싶습니다.

XML 또는 JSON을 사용하고 런타임에서 구문 분석하려는 주된 이유 중 하나는 코드를 다시 컴파일하지 않고도 새로운 값을 변경하고 실험하는 것입니다. 파이썬이 해석되고 내 의견으로는 꽤 읽을 수 있기 때문에 사전과 모든 것이 구성된 파일에 원시 데이터를 가질 수 있습니다.

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

이렇게하면 파일 / 모듈을 가져 와서 일반 사전으로 사용하면됩니다.

스크립트를 추가하려는 경우 Python 및 1st 클래스 함수의 동적 특성을 사용할 수 있습니다. 다음과 같이 할 수 있습니다.

def special_shot():
    ...

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

비록 그것이 데이터 중심 디자인에 반대되는 것이라고 생각합니다. 100 % DDD가 되려면 특정 무기가 사용할 기능과 코드를 지정하는 정보 (데이터)가 있어야합니다. 이 방법으로 데이터와 기능을 혼합하지 않으므로 DDD를 중단하지 않습니다.


고맙습니다. 간단한 코드 예제를 보면 클릭하는 데 도움이되었습니다.
henrebotha

1
좋은 답변과 댓글을 달기에 충분한 담당자가 있으면 +1하십시오. ;) 환영합니다.
ver

4

데이터 중심 설계

최근 에 코드 검토 를 위해이 질문과 같은 것을 제출 했습니다.

몇 가지 제안과 개선 후 결과는 사전 (또는 JSON)을 기반으로하는 무기 생성에 대한 상대적 유연성을 허용하는 간단한 코드였습니다. 데이터는 런타임에 해석되며 Weapon전체 스크립트 인터프리터에 의존 할 필요없이 클래스 자체에서 간단한 검증을 수행합니다 .

데이터 중심 디자인, 파이썬은 해석 언어 (소스 및 데이터 파일 모두를 다시 컴파일 할 필요없이 편집 할 수 있음) 임에도 불구하고 제시 한 것과 같은 경우에 옳은 것처럼 들립니다. 이 질문 은 개념, 장단점에 대해 자세히 설명합니다. 코넬 대학 에 관한 멋진 프레젠테이션 도 있습니다.

C ++과 같은 다른 언어와 비교하여 아마도 LUA와 같은 스크립팅 언어를 사용하여 일반적으로 데이터 x 엔진 상호 작용 및 스크립팅을 처리하고 데이터를 저장하기 위해 특정 데이터 형식 (XML과 같은)을 처리 할 수 ​​있습니다. 그것은 모두 자체적으로 (표준을 고려 dict하지만 weakref후자는 특히 리소스로드 및 캐싱을 위해) 고려합니다.

그러나 독립 개발자 는이 기사에서 제안한대로 데이터 중심 접근 방식을 최대한 활용하지 못할 수 있습니다 .

데이터 중심 디자인은 어느 정도입니까? 게임 엔진에 한 줄의 게임 특정 코드가 포함되어 있다고 생각하지 않습니다. 하나도 아니야 하드 코딩 된 무기 유형이 없습니다. 하드 코드 된 HUD 레이아웃이 없습니다. 하드 코딩 된 유닛 AI가 없습니다. 나다. 지퍼. 제로.

파이썬을 사용하면 생산성과 확장 성을 모두 목표로 객체 지향 및 데이터 중심 접근 방식 중 가장 좋은 이점을 얻을 수 있습니다.

간단한 시료 처리

코드 검토에서 논의 된 특정 사례에서 사전은 "정적 속성"과 해석 할 논리를 저장합니다. 무기에 조건부 동작이있는 경우.

아래 예에서 칼은 '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, 적중 효과 : [], 기타 효과 : []

항 팔라딘의 경우

향상 : 5, 적중 효과 : [ '불량'], 기타 효과 : [ '불량'

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.