Есть шаблоны для лепки настольных игр? [закрыто]

94

Ради интереса я пытаюсь написать одну из любимых настольных игр моего сына в виде программы. В конце концов, я собираюсь построить на его основе пользовательский интерфейс WPF, но сейчас я создаю машину, которая моделирует игры и их правила.

По мере того как я это делаю, я продолжаю видеть проблемы, которые, как мне кажется, являются общими для многих настольных игр, и, возможно, другие уже решили их лучше, чем я.

(Обратите внимание, что ИИ, чтобы играть в игру, и шаблоны, связанные с высокой производительностью, мне не интересны.)

Пока что мои шаблоны:

  • Несколько неизменяемых типов, представляющих объекты в игровом поле, например, кости, шашки, карты, доску, места на доске, деньги и т. Д.

  • Объект для каждого игрока, который содержит ресурсы игроков (например, деньги, счет), их имя и т. Д.

  • Объект, который представляет состояние игры: игроков, чья очередь, расположение фигур на доске и т. Д.

  • Конечный автомат, который управляет последовательностью ходов. Например, во многих играх есть небольшая предварительная игра, где каждый игрок делает бросок, чтобы увидеть, кто ходит первым; это начальное состояние. Когда начинается ход игрока, сначала они катятся, затем двигаются, затем они должны танцевать на месте, затем другие игроки угадывают, к какой породе они принадлежат, и получают очки.

Могу ли я воспользоваться каким-либо известным уровнем техники?

РЕДАКТИРОВАТЬ: Одна вещь, которую я недавно понял, заключается в том, что состояние игры можно разделить на две категории:

  • Состояние игрового артефакта . «У меня есть 10 долларов» или «моя левая рука на синем».

  • Состояние игровой последовательности . «У меня дважды выпадал дубль; следующий бросил меня в тюрьму». Здесь может иметь смысл конечный автомат.

РЕДАКТИРОВАТЬ: Я действительно ищу здесь лучший способ реализовать многопользовательские пошаговые игры, такие как Chess, Scrabble или Monopoly. Я уверен, что смогу создать такую ​​игру, просто проработав ее от начала до конца, но, как и в случае с другими шаблонами проектирования, вероятно, есть некоторые способы сделать все гораздо более плавными, что неочевидно без тщательного изучения. Я на это надеюсь.

Джей Базузи
источник
3
Вы строите что-то вроде хоки-поки, монополии, шарад?
Энтони Мастрян,
Вам понадобится конечный автомат для любого правила, которое полагается на состояние (эээ ...), как правило трех удвоений для монополии. Я бы отправил более полный ответ, но у меня нет опыта в этом. Хотя я мог бы понтификатировать об этом.
MSN

Ответы:

116

кажется, это ветка двухмесячной давности, которую я только что заметил, но какого черта. Раньше я проектировал и разрабатывал структуру игрового процесса для коммерческой сетевой настольной игры. У нас был очень приятный опыт работы с ним.

Ваша игра, вероятно, может находиться в (почти) бесконечном количестве состояний из-за перестановок таких вещей, как количество денег у игрока A, сколько денег у игрока B и т. Д. Поэтому я почти уверен, что вы хотите держаться подальше от государственных машин.

Идея, лежащая в основе нашей структуры, заключалась в том, чтобы представить состояние игры в виде структуры со всеми полями данных, которые вместе обеспечивают полное состояние игры (то есть: если вы хотите сохранить игру на диск, вы записываете эту структуру).

Мы использовали шаблон команд для представления всех допустимых игровых действий, которые может совершить игрок. Вот пример действия:

class RollDice : public Action
{
  public:
  RollDice(int player);

  virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate
  virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action
};

Итак, вы видите, что для определения допустимости хода вы можете создать это действие, а затем вызвать его функцию IsLegal, передав текущее состояние игры. Если он действителен и игрок подтверждает действие, вы можете вызвать функцию Apply, чтобы фактически изменить состояние игры. Убедившись, что ваш игровой код может изменять состояние игры только путем создания и отправки законных действий (другими словами, семейство методов Action :: Apply - единственное, что напрямую изменяет состояние игры), вы гарантируете, что ваша игра состояние никогда не будет недействительным. Кроме того, используя шаблон команды, вы даете возможность сериализовать желаемые ходы вашего игрока и отправлять их по сети для выполнения в игровых состояниях других игроков.

В результате с этой системой произошла одна ошибка, которая оказалась довольно элегантным решением. Иногда действия состоят из двух или более фаз. Например, игрок может приземлиться на участке в Монополии и теперь должен принять новое решение. Каково состояние игры между моментом, когда игрок бросил кости, и до того, как он решит купить недвижимость или нет? Мы справлялись с подобными ситуациями, добавляя в состояние игры член «Контекст действия». Контекст действия обычно имеет значение null, что указывает на то, что игра в настоящее время не находится в каком-либо особом состоянии. Когда игрок бросает кости и действие бросания кубиков применяется к состоянию игры, он поймет, что игрок приземлился на чужой собственности, и может создать новое свойство «PlayerDecideToPurchaseProperty» контекст действия, содержащий индекс игрока, от которого мы ждем решения. К моменту завершения действия RollDice наше игровое состояние представляет, что в настоящее время он ожидает, пока указанный игрок решит, покупать ли недвижимость - нет. Теперь метод IsLegal всех других действий может легко возвращать false, за исключением действий «BuyProperty» и «PassPropertyPurchaseOpportunity», которые допустимы только тогда, когда состояние игры имеет контекст действия «PlayerDecideToPurchaseProperty».

Благодаря использованию контекстов действия в настольной игре никогда не бывает ни одной точки, в которой структура состояния игры не отражала бы полностью ТОЧНО, что происходит в игре в этот момент времени. Это очень желаемое свойство вашей системы настольных игр. Вам будет намного проще писать код, когда вы сможете найти все, что хотите знать о том, что происходит в игре, исследуя только одну структуру.

Кроме того, он очень хорошо распространяется на сетевые среды, где клиенты могут отправлять свои действия по сети на хост-компьютер, который может применить действие к «официальному» игровому состоянию хоста, а затем передать это действие обратно всем другим клиентам, чтобы пусть они применит это к своим реплицированным игровым состояниям.

Надеюсь, это было кратко и полезно.

Эндрю Топ
источник
4
Я не думаю, что это кратко, но полезно! Проголосовали.
Джей Базузи
Рад, что это было полезно ... Какие части не были краткими? Я был бы рад внести уточняющую правку.
Эндрю Топ
Прямо сейчас я создаю пошаговую игру, и этот пост мне очень помог!
Кив,
Я читал, что Memento - это шаблон для отмены ... Memento vs Command Pattern для отмены, ваши мысли,
пожалуйста
Это лучший ответ, который я читал в Stackoverflow до сих пор. БЛАГОДАРНОСТЬ!
Papipo
19

Базовая структура вашего игрового движка использует State Pattern . Предметы вашего игрового ящика представляют собой синглтоны разных классов. Структура каждого состояния может использовать шаблон стратегии или метод шаблона .

Factory используется для создания игроков , которые вставляются в список игроков, другой одноплодным. Графический интерфейс пользователя будет следить за игровым движком, используя шаблон Observer, и взаимодействовать с ним, используя один из нескольких объектов Command, созданных с помощью шаблона Command . Использование Observer и Command можно использовать в контексте пассивного представления, но в зависимости от ваших предпочтений можно использовать практически любой шаблон MVP / MVC. При сохранении игры вам нужно запомнить ее текущее состояние.

Я рекомендую просмотреть некоторые шаблоны на этом сайте и посмотреть, подходят ли они вам в качестве отправной точки. Опять же, сердцем вашего игрового поля будет конечный автомат. Большинство игр будут представлены двумя состояниями: предварительная игра / настройка и сама игра. Но у вас может быть больше состояний, если игра, которую вы моделируете, имеет несколько различных режимов игры. Состояния не обязательно должны быть последовательными, например, в Wargame Axis & Battles есть поле боя, которое игроки могут использовать для разрешения сражений. Таким образом, есть три состояния перед игрой, основная доска, поле битвы, при этом игра постоянно переключается между основной доской и боевой доской. Конечно, последовательность поворотов также может быть представлена ​​конечным автоматом.

RS Конли
источник
17

Я только что закончил разработку и реализацию игры на основе состояний с использованием полиморфизма.

Использование базового абстрактного класса GamePhaseс одним важным методом

abstract public GamePhase turn();

Это означает, что каждый GamePhaseобъект содержит текущее состояние игры, а вызов функции turn()смотрит на его текущее состояние и возвращает следующее GamePhase.

У каждого бетона GamePhaseесть конструкторы, которые хранят все состояние игры. В каждом turn()методе есть немного правил игры. Несмотря на то, что это распространяет правила, они сближают связанные правила. Конечным результатом каждого из них turn()является создание следующего GamePhaseи переход в полном состоянии в следующую фазу.

Это позволяет turn()быть очень гибким. В зависимости от вашей игры данное состояние может разветвляться на множество различных типов фаз. Это формирует график всех этапов игры.

На самом высоком уровне код для управления им очень прост:

GamePhase state = ...initial phase
while(true) {
    // read the state, do some ui work
    state = state.turn();
}

Это чрезвычайно полезно, так как теперь я могу легко создать любое состояние / фазу игры для тестирования.

Теперь, чтобы ответить на вторую часть вашего вопроса, как это работает в мультиплеере? В некоторых GamePhaseс , которые требуют пользовательского ввода, вызов от turn()будет просить ток Playerих , Strategyучитывая текущее состояние / фазу. Strategyэто просто интерфейс всех возможных решений, которые Playerможно принять. Эта установка также позволяет Strategyиспользовать AI!

Также Андрей Топ сказал:

Ваша игра, вероятно, может находиться в (почти) бесконечном количестве состояний из-за перестановок таких вещей, как количество денег у игрока A, сколько денег у игрока B и т. Д. Поэтому я почти уверен, что вы хотите держаться подальше от государственных машин.

Я думаю, что это утверждение вводит в заблуждение, хотя верно то, что существует множество различных игровых состояний, но есть только несколько игровых фаз. Чтобы справиться с его примером, все, что он будет, это целочисленный параметр для конструкторов моих конкретных GamePhases.

Монополия

Примером некоторых GamePhases будет:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go и т. Д.)
  • PlayerTrades
  • PlayerPurchasesProperty
  • ИгрокПокупкиДома
  • ИгрокПокупкиОтели
  • PlayerPaysRent
  • PlayerBankrupts
  • (Все карты случайных и общественных сундуков)

И некоторые состояния в базе GamePhase:

  • Список игроков
  • Текущий игрок (чья очередь)
  • Деньги / собственность игрока
  • Дома / Гостиницы по Недвижимость
  • Положение игрока

А затем некоторые фазы будут записывать свое собственное состояние по мере необходимости, например PlayerRolls будет записывать количество раз, когда игрок бросал последовательные удвоения. Когда мы выходим из фазы PlayerRolls, нас больше не волнуют последовательные броски.

Многие фазы можно использовать повторно и связать вместе. Например, GamePhase CommunityChestAdvanceToGoбудет создана следующая фаза PlayerLandsOnGoс текущим состоянием и будет возвращена. В конструкторе PlayerLandsOnGoтекущего игрока переместится в Go, и его деньги будут увеличены на 200 долларов.

Пиролитический
источник
9

Конечно, есть много, много, много, много, много, много, много ресурсов по этой теме. Но я думаю, что вы на правильном пути, разделяя объекты и позволяя им обрабатывать свои собственные события / данные и так далее.

При создании настольных игр на основе плиток вам будет приятно иметь процедуры для сопоставления между массивом досок и строкой / столбцом и обратно, наряду с другими функциями. Я помню свою первую настольную игру (очень давно), когда я боролся с тем, как получить строку / столбец из Boardarray 5.

1  2  3  
4 (5) 6  BoardArray 5 = row 2, col 2
7  8  9  

Ностальгия. ;)

В любом случае http://www.gamedev.net/ - хорошее место для информации. http://www.gamedev.net/reference/

Стефан
источник
Почему бы вам просто не использовать двумерный массив? Тогда компилятор сделает это за вас.
Джей Базузи,
Я извиняюсь за то, что это было очень давно. ;)
Стефан
1
У gamedev есть масса вещей, но я не совсем понял, что искал.
Джей Базузи
какой язык вы использовали?
zotherstupidguy
Basic, Basica, QB, QuickBasic и так далее. ;)
Стефан
6

Многие материалы, которые я могу найти в Интернете, представляют собой списки опубликованных ссылок. В разделе публикаций Game Design Patterns есть ссылки на PDF-версии статей и тезисов. Многие из них выглядят как научные статьи, например, « Шаблоны дизайна для игр» . Также на Amazon доступна по крайней мере одна книга « Паттерны в игровом дизайне» .

Эрик Вайльнау
источник
4

Three Rings предлагает библиотеки Java для LGPL. Неня и Виля - библиотеки для игровых материалов.

Конечно, было бы полезно, если бы в вашем вопросе упоминалась платформа и / или языковые ограничения, которые у вас могут быть.

Jmucchiello
источник
«В конце концов я собираюсь создать пользовательский интерфейс WPF» - это означает .NET. По крайней мере, насколько я могу судить.
Марк Аллен,
Алфавитный суп, о котором я не знаю.
jmucchiello
Да, я занимаюсь .NET, но мой вопрос не зависит от языка или платформы.
Джей Базузи,
3

Я согласен с ответом Пиролитика, и я предпочитаю его способ делать что-то (хотя я просто просмотрел другие ответы).

По совпадению я также использовал его название «GamePhase». В основном то, что я сделал бы в случае пошаговой настольной игры, - это чтобы ваш класс GameState содержал объект абстрактного GamePhase, как упоминалось Pyrolistical.

Допустим, состояния игры:

  1. Рулон
  2. Переехать
  3. Покупать / не покупать
  4. Тюрьма

У вас могут быть конкретные производные классы для каждого состояния. Иметь виртуальные функции как минимум для:

StartPhase();
EndPhase();
Action();

В функции StartPhase () вы можете установить все начальные значения для состояния, например, отключив ввод другого игрока и так далее.

Когда вызывается roll.EndPhase (), убедитесь, что указатель GamePhase перешел в следующее состояние.

phase = new MovePhase();
phase.StartPhase();

В этом MovePhase :: StartPhase () вы, например, должны установить оставшиеся ходы активного игрока на количество, выпавшее на предыдущей фазе.

Теперь, имея такой дизайн, вы можете решить проблему «3 x двойное = тюрьма» внутри фазы Roll. Класс RollPhase может обрабатывать собственное состояние. Например

GameState state; //Set in constructor.
Die die;         // Only relevant to the roll phase.
int doublesRemainingBeforeJail;
StartPhase()
{
    die = new Die();
    doublesRemainingBeforeJail = 3;
}

Action()
{
    if(doublesRemainingBeforeJail<=0)
    {
       state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};            
       state.phase.StartPhase();
       return;
    }

    int die1 = die.Roll();
    int die2 = die.Roll();

    if(die1 == die2)
    {
       --doublesRemainingBeforeJail;
       state.activePlayer.AddMovesRemaining(die1 + die2);
       Action(); //Roll again.
    }

    state.activePlayer.AddMovesRemaining(die1 + die2);
    this.EndPhase(); // Continue to moving phase. Player has X moves remaining.
}

Я отличаюсь от Pyrolistical тем, что должна быть фаза для всего, в том числе когда игрок приземляется на сундук сообщества или что-то в этом роде. Я бы справился со всем этим в MovePhase. Это потому, что если у вас слишком много последовательных фаз, игрок, скорее всего, будет чувствовать себя слишком «управляемым». Например, если есть фаза, в которой игрок может ТОЛЬКО покупать недвижимость, а затем ТОЛЬКО покупать отели, а затем ТОЛЬКО дома, это похоже на отсутствие свободы. Просто объедините все эти части в одну BuyPhase и дайте игроку свободу покупать все, что он захочет. Класс BuyPhase может достаточно легко определить, какие покупки являются законными.

Наконец, давайте обратимся к игровой доске. Хотя 2D-массив вполне подойдет, я бы рекомендовал иметь тайловый график (где тайл - это позиция на доске). В случае монополии это был бы двусвязный список. Тогда каждая плитка будет иметь:

  1. предыдущийПлитка
  2. nextTile

Так что было бы намного проще сделать что-то вроде:

While(movesRemaining>0)
  AdvanceTo(currentTile.nextTile);

Функция AdvanceTo может обрабатывать ваши пошаговые анимации или все, что вам нравится. И, конечно же, уменьшите оставшиеся ходы.

Совет RS Конли по шаблону наблюдателя для графического интерфейса пользователя хорош.

Раньше я мало что писал. Надеюсь, это кому-то поможет.

Reasurria
источник
2

Могу ли я воспользоваться каким-либо известным уровнем техники?

Если ваш вопрос не зависит от языка или платформы. тогда я бы порекомендовал вам рассмотреть шаблоны АОП для состояния, памятки, команды и т. д.

Что .NET ответит на АОП ???

Также попробуйте найти несколько интересных сайтов, например http://www.chessbin.com

злодей
источник