Реалізація поведінки у простій пригодницькій грі


11

Останнім часом я розважаю себе програмуванням простої текстової пригодницької гри, і я застряг у тому, що здається дуже простим дизайнерським питанням.

Для короткого огляду: гра розбита на Roomоб’єкти. У кожному Roomє список Entityпредметів, які знаходяться в цій кімнаті. Кожен Entityмає стан події, що представляє собою просту рядок-> булева карта та список дій, що представляє собою рядок-> функціональна карта.

Введення користувача приймає форму [action] [entity]. RoomВикористовує ім'я особи , щоб повернути відповідний Entityоб'єкт, який потім використовує ім'я дії , щоб знайти правильну функцію і виконує його.

Для створення опису кімнати кожен Roomоб'єкт відображає власну рядок опису, а потім додає рядки опису кожного Entity. EntityОпис може змінюватися в залежності від його стану ( «Двері відчинені», «Двері закриті», «Двері замкнені», і т.д.).

Ось проблема: за допомогою цього методу кількість функцій опису та дій, які мені потрібно реалізувати, швидко виходить з рук. Моя початкова кімната має близько 20 функцій між 5 об'єктами.

Я можу об'єднати всі дії в одну функцію та if-else / переключитися через них, але це все-таки дві функції на одну сутність. Я також можу створити специфічні Entityпідкласи для загальних / загальних об'єктів, таких як двері та ключі, але це поки що отримує мене.

EDIT 1: За запитом, приклади псевдокоду цих функцій дій.

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

Функції опису діють майже однаково, перевіряючи стан та повертаючи відповідний рядок.

EDIT 2: Переглянуто формулювання мого питання. Припустимо, що може бути значна кількість ігрових об’єктів, які не поділяють загальну поведінку (відповіді на державу на конкретні дії) з іншими об'єктами. Чи є спосіб я визначити ці унікальні форми поведінки більш чітким, більш досяжним способом, ніж написання спеціальної функції для кожної конкретної дії?


1
Я думаю, вам потрібно пояснити, що роблять ці "дії", і, можливо, опублікувати якийсь код, тому що я не впевнений, про що ви там говорите.
поштовх

Додано код.
Ерік

Відповіді:


5

Замість того, щоб робити окрему функцію для кожної комбінації іменників та дієслів, вам слід встановити архітектуру, де є один загальний інтерфейс, який реалізують усі об’єкти в грі.

Одним із підходів у верхній частині моєї голови було б визначити об’єкт Entity, який розширюватимуть усі конкретні об’єкти у вашій грі. Кожна організація матиме таблицю (незалежно від структури даних, яку ваша мова використовує для асоціативних масивів), яка пов'язує різні дії з різними результатами. Дії в таблиці, ймовірно, будуть рядками (наприклад, "відкритими"), хоча пов'язаний результат може бути навіть приватною функцією в об'єкті, якщо ваша мова підтримує першокласні функції.

Аналогічно стан об'єкта зберігається в різних полях об'єкта. Так, наприклад, ви можете мати масив речей у Bush, і тоді функція, пов'язана з "search", буде діяти на цей масив, або повертаючи знайдений об'єкт, або рядок "Більше нічого не було в кущах".

Тим часом, один із загальнодоступних методів - це щось на зразок Entity.actOn (String action). Потім у цьому методі порівняйте передану дію з таблицею дій для цього об'єкта; якщо ця дія є в таблиці, поверніть результат.

Тепер всі різні функції, необхідні для кожного об'єкта, будуть міститись в об'єкті, що полегшить його повторення в інших приміщеннях (наприклад, примірник об'єкта «Двері» у кожній кімнаті, де є двері)

Нарешті, визначте всі кімнати в XML чи JSON або будь-якому іншому, щоб у вас було безліч унікальних номерів, не потрібно писати окремий код для кожної окремої кімнати. Завантажте цей файл даних, коли гра починається, і проаналізуйте дані, щоб створити об'єкти, що заселяють вашу гру. Щось на зразок:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

ДОДАТОК: ага, я щойно прочитав відповідь FxIII, і цей шматочок біля кінця вискочив на мене:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

Хоча я не погоджуюся з тим, що спрацьовує пастка полум'я - це те, що відбудеться лише один раз (я міг бачити, як ця пастка повторно використовується для багатьох різних об'єктів), я думаю, що нарешті я зрозумів те, що ви мали на увазі щодо сутностей, які однозначно реагують на введення користувача. Я, мабуть, звертаюсь до таких речей, як, наприклад, щоб зробити одну двері у вашій підземеллі, щоб утворити пастку з вогняного кулі, будуючи всі мої сутності зі складовою архітектурою (детально пояснено в інших місцях).

Таким чином, кожна сукупність дверей побудована як група компонентів, і я можу гнучко змішувати і співставляти компоненти між різними об'єктами. Наприклад, більшість дверей мали б подібні конфігурації

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

але одні двері з пасткою для вогняної кулі були б

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

і тоді єдиний унікальний код, який я мав би написати для цих дверей, - це компонент FireballTrap. Він використовував би ті самі компоненти Lock і Portal, що і всі інші двері, і якщо я пізніше вирішив використовувати FireballTrap на скрині зі скарбами або щось таке просто, як додати компонент FireballTrap до цієї скрині.

Незалежно від того, чи визначаєте ви всі компоненти в складеному коді або в окремій мові сценаріїв, на мою думку не велика різниця (так чи інакше ви збираєтеся кудись писати код ), але важливо, що ви можете значно зменшити кількість унікального коду, який потрібно написати. Чортів, якщо вас не турбує гнучкість для дизайнерів / модерів рівнів (ви все-таки самі пишете цю гру), ви навіть можете зробити всі об'єкти успадкованими від Entity та додати компоненти в конструктор, а не конфігураційний файл чи сценарій або що завгодно:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}

1
Це працює для звичайних повторюваних предметів. А як щодо сутностей, які однозначно реагують на введення користувачів? Створення підкласу Entityлише для одного об’єкта групує код разом, але не зменшує кількість коду, який я повинен написати. Або це неминучий підрив у цьому плані?
Ерік

1
Я звернувся до прикладів, які ви навели. Я не можу прочитати вашу думку; які об’єкти та входи ви хочете мати?
поштовх

Відредагував свою посаду, щоб краще пояснити мої наміри. Якщо я правильно розумію ваш приклад, схоже, що кожен тег сутності відповідає деякому підкласу, Entityа атрибути визначають його початковий стан. Я здогадуюсь, що дочірні теги сутності виступають параметрами для будь-якої дії, з якою пов’язаний тег, правда?
Ерік

так, це ідея.
поштовх

Я мав би подумати, що компоненти будуть частиною рішення. Дякую за допомогу.
Ерік

1

Проблема розмірів, яку ви вирішуєте, цілком нормальна і майже неминуча. Ви хочете , щоб знайти спосіб виразити свої об'єкти , що є одночасно coincise і гнучким .

"Контейнер" (кущ у відповіді на джаккінг) - це спосіб спільної роботи, але ви бачите, що він недостатньо гнучкий .

Я не пропоную вам намагатися знайти загальний інтерфейс, а потім використовувати конфігураційні файли, щоб вказати поведінку, тому що у вас завжди буде неприємне відчуття бути між скелею (стандартні та нудні сутності, легко описати) та важким місцем ( унікальні фантастичні сутності, але занадто довгі для реалізації).

Моя пропозиція полягає в використанні в інтерпретована мова для коду поведінки.

Подумайте про приклад куща: це контейнер, але наш кущ повинен містити конкретні елементи всередині; об'єкт контейнера може мати:

  • метод для того, щоб розповідач додав предмет,
  • метод, щоб двигун показав предмет, який він містить,
  • спосіб гравця вибрати предмет.

Один із цих предметів має мотузку, яка викликає вигадку, яка в свою чергу розпалює полум'я, що спалює кущ ... (бачите, я можу прочитати вашу думку, щоб я знав, що вам подобається).

Ви можете використовувати скрипт для опису цього куща замість конфігураційного файлу, вводячи відповідний додатковий код у гачок, який ви виконуєте з вашої основної програми кожного разу, коли хтось вибирає предмет із контейнера.

Зараз у вас є багато варіантів архітектури: ви можете визначити поведінкові інструменти як базові класи за допомогою кодової мови або мови сценаріїв (такі речі, як контейнери, подібні до дверей тощо). Мета з theese речей , щоб дозволити вам описати об'єкти Іслі на загальну прості моделі поведінки і їх налаштування з допомогою прив'язок на мові сценаріїв .

Усі сутності повинні бути доступні до сценарію: ви можете пов’язати ідентифікатор з кожним об'єктом і помістити їх у контейнер, який експортується в розширення сценарію мови сценаріїв.

Використання сценаріїв сценаріїв дозволяє вам просто налаштувати конфігурацію (подібні речі <item triggerFlamesOnPicking="true">ви не будете використовувати лише один раз), дозволяючи висловлювати дивні поведінки (цікаві), додаючи рядок коду

Кілька слів: скрипти як конфігураційний файл, який може запускати код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.