행동에 부작용이있는 턴제 게임 디자인


19

나는 Dominion 게임의 컴퓨터 버전을 쓰고있다 . 액션 카드, 보물 카드 및 승점 카드가 플레이어의 개인 데크에 쌓이는 턴 기반 카드 게임입니다. 수업 구조가 꽤 잘 발달되어 있고 게임 로직을 디자인하기 시작했습니다. 나는 파이썬을 사용하고 있으며 나중에 파이 게임으로 간단한 GUI를 추가 할 수 있습니다.

플레이어의 턴 순서는 매우 간단한 상태 머신에 의해 관리됩니다. 패스를 시계 방향으로 돌리면 플레이어는 게임이 끝나기 전에 게임을 종료 할 수 없습니다. 단일 턴 플레이는 상태 머신이기도합니다. 일반적으로 플레이어는 "동작 단계", "구매 단계"및 "정리 단계"(순서대로)를 통과합니다. 턴 기반 게임 엔진을 구현하는 방법 이라는 질문에 대한 답변을 바탕으로 상태 머신은이 상황에 대한 표준 기술입니다.

내 문제는 플레이어의 액션 단계에서 부작용이있는 액션 카드를 자신이나 다른 플레이어에게 사용할 수 있다는 것입니다. 예를 들어, 액션 카드 한 장을 사용하면 현재 턴이 끝난 직후 플레이어가 두 번째 턴을 할 수 있습니다. 다른 행동 카드는 다른 모든 플레이어가 자신의 손에서 두 장의 카드를 버립니다. 또 다른 액션 카드는 현재 턴에는 아무런 영향을 미치지 않지만 다음 턴에 추가 카드를 뽑을 수 있습니다. 일을 더욱 복잡하게하기 위해 새로운 카드를 추가하는 게임이 자주 확장됩니다. 모든 액션 카드의 결과를 게임의 상태 머신에 하드 코딩하는 것은 추악하고 적응할 수없는 것 같습니다. 턴제 전략 루프에 대한 답변 이 문제를 해결하기 위해 설계를 다루는 세부 수준으로 들어 가지 않습니다.

턴에서 일어나는 행동에 의해 턴을하기위한 일반적인 패턴이 수정 될 수 있다는 사실을 포괄하기 위해 어떤 종류의 프로그래밍 모델을 사용해야합니까? 게임 오브젝트가 모든 액션 카드의 효과를 추적해야합니까? 또는 카드가 자체 효과를 구현해야하는 경우 (예 : 인터페이스를 구현하여) 충분한 전력을 공급하기 위해 어떤 설정이 필요합니까? 나는이 문제에 대한 몇 가지 해결책을 생각해 보았지만 그것을 해결할 표준 방법이 있는지 궁금합니다. 구체적으로 말하면, 액션 카드의 결과로 모든 플레이어가해야 할 행동을 추적하는 데 어떤 객체 / 클래스 / 무엇이 있는지, 그리고 일반적인 순서의 일시적인 변화와 어떻게 관련이 있는지 알고 싶습니다. 회전 상태 머신.


2
Apis Utilis 님, 안녕하세요. GDSE에 오신 것을 환영합니다. 귀하의 질문은 잘 작성되었으며 관련 질문을 참조한 것이 좋습니다. 그러나 귀하의 질문은 많은 다른 문제를 다루고 있으며, 그것을 완전히 다루기 위해서는 아마도 엄청난 질문이 필요할 것입니다. 여전히 좋은 답변을 얻을 수 있지만 문제를 좀 더 세분화하면 자신과 사이트가 도움이됩니다. 아마도 더 간단한 게임을 만드는 것으로 시작해서 Dominion까지 쌓을 수 있습니까?
michael.bartnett

1
나는 각 카드에 게임의 상태를 수정하는 스크립트를 시작하고 이상한 일이 일어나지 않으면 기본 턴 규칙으로 돌아갑니다 ...
Jari Komppa

답변:


11

저는 Jari Komppa에 강력한 스크립팅 언어로 카드 효과를 정의하는 것이 좋습니다. 그러나 유연성을 극대화하는 열쇠는 스크립트 가능한 이벤트 처리라고 생각합니다.

카드가 이후 게임 이벤트와 상호 작용할 수 있도록하기 위해 스크립팅 API를 추가하여 게임 단계의 시작과 끝 또는 플레이어가 수행 할 수있는 특정 작업과 같은 특정 이벤트에 "스크립트 후크"를 추가 할 수 있습니다. 이는 카드가 재생 될 때 실행되는 스크립트가 다음에 특정 단계에 도달 할 때 호출되는 기능을 등록 할 수 있음을 의미합니다. 각 이벤트에 등록 할 수있는 기능의 수에는 제한이 없습니다. 둘 이상이 있으면 등록 순서대로 호출됩니다 (물론 다른 게임 코어 규칙이없는 한).

이 훅은 모든 플레이어 또는 특정 플레이어에게만 등록 할 수 있어야합니다. 또한 후크를 계속 호출해야하는지 여부를 후크가 스스로 결정할 수있는 가능성을 추가 할 것을 제안합니다. 이 예에서는 후크 함수의 반환 값 (true 또는 false)이이를 표현하는 데 사용됩니다.

그러면 더블 턴 카드는 다음과 같이됩니다 :

add_event_hook('cleanup_phase_end', current_player, function {
     setNextPlayer(current_player); // make the player take another turn
     return false; // unregister this hook afterwards
});

(Dominion에 "정리 단계"와 같은 것이 있는지는 알 수 없습니다.이 예에서는 플레이어 차례의 가상 마지막 단계입니다)

모든 플레이어가 드로우 단계 시작시 추가 카드를 뽑을 수있는 카드는 다음과 같습니다.

add_event_hook('draw_phase_begin', NULL, function {
    drawCard(current_player); // draw a card
    return true; // keep doing this until the hook is removed explicitely
});

대상 플레이어가 카드를 사용할 때마다 적중률을 잃게하는 카드는 다음과 같습니다.

add_event_hook('play_card', target_player, function {
    changeHitPoints(target_player, -1); // remove a hit point
    return true; 
});

"카드를 뽑는 것"의 완전한 정의가 핵심 게임 메커니즘의 일부이기 때문에 카드 그리기 또는 적중률 상실과 같은 일부 게임 동작을 하드 코딩하지 않아도됩니다. 예를 들어, 어떤 이유에서든 카드를 뽑아야하고 덱이 비었을 때 게임을 잃는 TCG를 알고 있습니다. 이 룰은 룰북에 있기 때문에 카드를 뽑는 모든 카드에 인쇄되지는 않습니다. 따라서 모든 카드의 스크립트에서 잃어버린 상태를 확인할 필요는 없습니다. 이와 같은 것을 확인하는 것은 하드 코딩 된 drawCard()기능 의 일부 여야합니다 (어쨌든 후크 가능한 이벤트의 좋은 후보가 될 것입니다).

그건 그렇고 : 모든 모호한 정비사 미래 판이 나올 수있는 계획을 세울 수는 없을 것입니다. 따라 무엇을 하든지간에 미래 판에 새로운 기능을 한 번에 추가해야합니다. 경우, 색종이 던지는 미니 게임).


1
와우. 그 혼돈 색종이.
Jari Komppa

@Philipp과 같은 훌륭한 답변으로 Dominion에서 많은 일을 처리합니다. 그러나 카드가 재생 될 때 즉시 수행해야하는 작업이 있습니다. 즉, 다른 플레이어가 자신의 라이브러리의 맨 위 카드를 넘겨 현재 플레이어가 "계속"또는 "삭제"라고 할 수 있도록하는 카드가 재생됩니다. 그러한 즉각적인 조치를 처리하기 위해 이벤트 후크를 작성 하시겠습니까? 아니면 카드를 스크립팅하는 추가 방법이 필요합니까?
fnord

2
무언가가 즉시 발생해야하는 경우, 스크립트는 적절한 함수를 직접 호출하고 후크 함수를 등록하지 않아야합니다.
Philipp

@JariKomppa : Unglued 세트는 의도적으로 말도 안되는 의미가 있고 미친 카드로 가득 차있었습니다. 내가 가장 좋아하는 카드는 모두가 특정 단어를 말할 때 모든 사람이 피해를 입게 만드는 카드였습니다. 나는 'the'를 선택했다.
Jack Aidley

9

나는 유연한 컴퓨터 화 된 카드 게임 엔진이라는이 문제를 몇 년 전에 생각했다.

우선 Chez Geek 나 Fluxx (그리고 Dominion)와 같은 복잡한 카드 게임에서는 카드를 스크립팅 할 수 있어야합니다. 기본적으로 각 카드에는 다양한 방식으로 게임 상태를 변경할 수있는 자체 스크립트가 제공됩니다. 이 스크립트를 사용하면 현재 생각할 수없는 작업을 수행 할 수 있지만 향후 확장이 이루어질 수 있으므로 시스템에 미래를 보장 할 수 있습니다.

둘째, 단단한 "회전"으로 인해 문제가 발생할 수 있습니다.

"discard 2 cards"와 같은 "특별한 턴"을 포함하는 일종의 "턴 스택"이 필요합니다. 스택이 비어 있으면 기본 노멀 턴이 계속됩니다.

Fluxx에서는 한 번의 차례가 다음과 같이 진행될 수 있습니다.

  • N 카드 선택 (현재 규칙에 명시된대로 카드를 통해 변경 가능)
  • N 개의 카드를 플레이하십시오 (현재 규칙에 명시된대로 카드를 통해 변경 가능)
    • 카드 중 하나는 "3을 가지고, 그들 중 2를 재생할 수 있습니다"
      • 그 카드 중 하나가 "다른 방향으로 전환"될 수도 있습니다.
    • 카드 중 하나는 "삭제 및 추첨"일 수 있습니다
  • 턴을 시작할 때보 다 더 많은 카드를 선택하도록 규칙을 변경하는 경우 더 많은 카드를 선택하십시오
  • 적은 수의 카드에 대한 규칙을 변경하면 다른 사람이 즉시 카드를 폐기해야합니다
  • 당신의 차례가 끝나면, N 카드가 될 때까지 카드를 버리고 (카드를 통해 다시 변경할 수 있음), 다시 한 번 돕습니다 (위에서 엉망으로 "다른 턴을"했을 경우).

.. 등등. 따라서 위의 남용을 처리 할 수있는 턴 구조를 설계하는 것은 다소 까다로울 수 있습니다. 여기에 "언제나"카드 (예 : "체즈 괴짜"와 같은)가있는 수많은 게임은 "언제나"카드가 예를 들어 마지막으로 사용한 카드를 취소하여 정상적인 흐름을 방해 할 수 있습니다.

그래서 기본적으로 저는 매우 유연한 턴 구조를 설계하는 것부터 시작하여 스크립트로 설명 할 수 있도록 설계합니다 (각 게임에는 기본 게임 구조를 처리하는 자체 "마스터 스크립트"가 필요하므로). 그런 다음 모든 카드는 스크립팅 가능해야합니다. 대부분의 카드는 아마도 이상한 일을하지 않지만 다른 카드는 할 것입니다. 카드는 다양한 속성을 가질 수 있습니다-카드를 손에 넣거나 "언제나"재생할 수 있는지, 플럭스 '키퍼'와 같은 자산으로 저장할 수 있는지 또는 음식과 같은 'chez geek'의 다양한 것들 등 ...

나는 실제로 이것을 구현하기 시작하지 않았으므로 실제로 다른 많은 도전을 찾을 수 있습니다. 가장 쉬운 시작 방법은 구현하려는 시스템에 대해 알고있는 것부터 시작하여 가능한 한 적은 돌을 설정하여 스크립트 방식으로 구현하는 것입니다. 따라서 확장이 이루어지면 수정하지 않아도됩니다. 기본 시스템-많이. =)


이것은 훌륭한 답변이며, 가능하다면 둘 다 받아 들였을 것입니다. 나는 더 낮은 평판을 가진 사람의 답변을 받아 동점을 끊었다 :)
Apis Utilis

아니 prob, 난 지금까지 익숙해 .. =)
Jari Komppa

0

하스 스톤은 관련성이 있고 정직하게 일하는 것 같습니다. 유연성을 달성하는 가장 좋은 방법은 데이터 지향 디자인의 ECS 엔진을 이용하는 것입니다. 귀환석 복제품을 만들려고했지만 그렇지 않다는 것이 증명되었습니다. 모든 에지 케이스. 이 이상한 경우를 많이 접하고 있다면 아마도 가장 좋은 방법 일 것입니다. 나는이 기술을 시도한 최근의 경험에서 꽤 편견입니다.

편집 : 원하는 유연성과 최적화의 종류에 따라 ECS가 필요하지 않을 수도 있습니다. 이것을 달성하는 한 가지 방법 일뿐입니다. 국방부 나는 많은 관련이 있지만 절차 적 프로그래밍으로 잘못 생각했습니다. 내가 말하고 싶은 건. OOP를 완전히 또는 대부분 제거하는 것을 고려해야하며 대신 데이터 및 데이터 구성 방식에주의를 집중하십시오. 상속과 방법을 피하십시오. 대신 공공 데이터 (시스템)에 집중하여 카드 데이터를 조작하십시오. 각 작업은 템플릿 화 된 것 또는 어떤 종류의 논리가 아니라 원시 데이터입니다. 시스템이 로직을 수행하기 위해 사용하는 곳. 함수 스위치 배열에 액세스하기 위해 정수 스위치 케이스 또는 정수를 사용하면 입력 데이터에서 원하는 논리를 효율적으로 파악할 수 있습니다.

따라야 할 기본 규칙은 데이터와 직접 로직을 묶는 것을 피하고, 가능한 한 서로 데이터에 의존하게 만드는 것을 피해야하며 (예외가 적용될 수 있음), 도달 할 수없는 유연한 로직을 원할 때 ... 데이터로 변환하는 것을 고려하십시오.

이렇게하면 이점이 있습니다. 각 카드는 그들의 행동을 나타내는 열거 형 값 또는 문자열을 가질 수 있습니다. 이 인턴을 사용하면 텍스트 또는 json 파일을 통해 카드를 디자인하고 프로그램에서 자동으로 카드를 가져올 수 있습니다. 플레이어 액션을 데이터 목록으로 만들면 특히 카드가 하스 스톤과 같은 과거의 논리에 의존하거나 어떤 시점에서 게임이나 게임의 재생을 저장하려는 경우 더욱 융통성이 있습니다. AI를보다 쉽게 ​​만들 수있는 잠재력이 있습니다. 특히 "동작 트리"대신 "유틸리티 시스템"을 사용하는 경우. 다형성 개체 전체를 와이어로 전송하는 방법과 사실 이후 직렬화가 어떻게 설정되는지를 파악할 필요가 없기 때문에 네트워킹도 쉬워졌습니다. 이미 게임 오브젝트를 가지고있는 것은 단순한 데이터에 지나지 않습니다. 마지막으로 확실히 코드를 걱정하는 대신 데이터를 더 잘 정리할 수 있으므로 프로세서가 처리하는 데 시간이 더 걸리기 때문에 더 쉽게 최적화 할 수 있습니다. 파이썬은 여기에 문제가있을 수 있지만 "캐시 라인"과 게임 개발자와의 관련성을 찾으십시오. 프로토 타입 제작에는 중요하지 않지만 길을 따라 가면 편리하게 사용할 수 있습니다.

유용한 링크들.

참고 : ECS를 사용하면 런타임에 변수 (구성 요소)를 동적으로 추가 / 제거 할 수 있습니다. ECS가 어떻게 "모여"보이는지에 대한 예제 c 프로그램 (그 방법은 매우 다양합니다).

unsigned int textureID = ECSRegisterComponent("texture", sizeof(struct Texture));
unsigned int positionID = ECSRegisterComponent("position", sizeof(struct Point2DI));
for (unsigned int i = 0; i < 10; i++) {
    void *newEnt = ECSGetNewEntity();
    struct Point2DI pos = { 0 + i * 64, 0 };
    struct Texture tex;
    getTexture("test.png", &tex);
    ECSAddComponentToEntity(newEnt, &pos, positionID);
    ECSAddComponentToEntity(newEnt, &tex, textureID);
}
void *ent = ECSGetParentEntity(textureID, 3);
ECSDestroyEntity(ent);

텍스처 및 위치 데이터를 사용하여 여러 엔티티를 만들고 결국 텍스처 컴포넌트 배열의 세 번째 인덱스에있는 텍스처 컴포넌트를 가진 엔티티를 제거합니다. 기발한 것처럼 보이지만 일을하는 한 가지 방법입니다. 다음은 텍스처 구성 요소가있는 모든 것을 렌더링하는 방법의 예입니다.

unsigned int textureCount;
unsigned int positionID = ECSGetComponentTypeFromName("position");
unsigned int textureID = ECSGetComponentTypeFromName("texture");
struct Texture *textures = ECSGetAllComponentsOfType(textureID, &textureCount);
for (unsigned int i = 0; i < textureCount; i++) {
    void *parentEntity = ECSGetParentEntity(textureID, i);
    struct Point2DI *drawPos = ECSGetComponentFromEntity(positionID, parentEntity);
    if (drawPos) {
        struct Texture *t = &textures[i];
        drawTexture(t, drawPos->x, drawPos->y);
    }
}

1
이 답변은 데이터 지향 ECS를 설정하고이 특정 문제를 해결하기 위해 적용 하는 방법 에 대해 좀 더 자세히 설명 하면 더 좋습니다.
DMGregory

지적 해 주셔서 감사합니다.
Blue_Pyro

일반적으로 누군가에게 이런 종류의 접근 방식을 설정하는 방법을 알려주는 대신 자신의 솔루션을 디자인하도록하는 것은 좋지 않다고 생각합니다. 그것은 연습의 좋은 방법이며, 문제에 대한 잠재적으로 더 나은 해결책을 허용합니다. 이러한 방식으로 논리 이상의 데이터에 대해 생각할 때 동일한 것을 달성하는 많은 방법이 있으며 결과는 응용 프로그램의 요구에 달려 있습니다. 프로그래머 시간 / 지식뿐만 아니라
Blue_Pyro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.