Програмування бойової послідовності в рольовій грі


13

Я намагаюся написати коротку «гру», де гравець їде навколо і бореться з монстрами, але я не маю уявлення, як впоратися з боєм.

Наприклад, скажіть, що у мене є "Воїн" і "Тролль". Як двоє воюють між собою? Я знаю, що можу зробити щось на кшталт

Conan = Warrior.new();
CaveTroll = Troll.new();
Conan.attack(CaveTroll);
CaveTroll.attack(Conan);

Але яка частина гри контролює монстра? Чи я просто вклеюю вищевказану послідовність у цикл, поки одна з них не вмирає? Або в грі «двигуна» потрібно мати частину, яка стосується саме бою? Або це аспект штучного інтелекту Троля, який повинен дбати про свої дії?

Також, хто / що визначає дії, які виконує монстр? Можливо, Тролл може бити, бити, кусати, кидати заклинання, пити зілля, використовувати магічний предмет. Чи визначає ігровий движок, яку дію здійснює Тролл, чи це щось, чим керує клас Тролль?

Вибачте, я не можу бути більш конкретним, але мені потрібні певні вказівки, в якому напрямку рухатись.


круто! не знав, що сайт існує. чи є спосіб я перенести своє питання туди? чи я повинен просто вирізати / вставити його туди?

Не хвилюйтесь, мод повинен перенести це досить скоро! Або ви можете видалити тут питання і знову створити його в Game Dev
LiamB

@Fendo Вибачаюсь за запитання, але який сайт ви маєте на увазі? Гра Dev?
користувач712092

Відповіді:


12

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

void gameLoop() {
    while(gameRunning) {
        if (state == EXPLORATION) {
            // Perform actions for when player is simply walking around
            // ...
        }
        else if (state == IN_BATTLE) {
            // Perform actions for when player is in battle
            currentBattle.HandleTurn()
        }
        else if (state == IN_DIALOGUE) {
            // Perform actions for when player is talking with npcs
            // ...
        }
    }

}

Клас послідовності боїв виглядатиме так:

class BattleSequence {
    public:
        BattleSequence(Entity player, Entity enemy);
        void HandleTurn();
        bool battleFinished();

    private:
        Entity currentlyAttacking;
        Entity currentlyReceiving;
        bool finished;
}

Ваш Тролл і Воїн успадковують від загального суперкласу, який називається Сутність. У межах HandleTurn атакуючому об'єкту дозволяється рухатися. Це еквівалентно AI-мисленню.

void HandleTurn() {
    // Perform turn actions
    currentlyAttacking.fight(currentlyReceiving);

    // Switch sides
    Entity temp = currentlyAttacking;
    currentlyAttacking = currentlyReceiving;
    currentlyReceiving = temp;

    // Battle end condition
    if (currentlyReceiving.isDead() || currentlyAttacking.hasFled()) {
        finished = true;
    }
}

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

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

class Group {
    public:
        void fight(Group opponents) {
            // Loop through all group members so everyone gets
            // a shot at the opponents
            for (int i = 0; i < memberCount; i++) {
                Entity attacker = members[i];
                attacker.fight(opponents);
            }
        }

        Entity get(int targetID) {
            // TODO: Bounds checking
            return members[targetID];
        }

        bool isDead() {
            bool dead = true;
            for (int i = 0; i < memberCount; i++) {
                dead = dead && members[i].isDead();
            }
            return dead;
        }

        bool hasFled() {
            bool fled = true;
            for (int i = 0; i < memberCount; i++) {
                fled = fled && members[i].hasFled();
            }
            return fled;
        }

    private:
        Entity[] members;
        int memberCount;
}

Клас Group замінить усі виникнення сутності в класі BattleSequence. Вибір та атака буде здійснюватися самим класом Entity, тому AI може врахувати всю групу при виборі найкращого курсу дій.

class Entity {
    public:
        void fight(Group opponents) {
            // Algorithm for selecting an entity from the group
            // ...
            int targetID = 0; // Or just pick the first one

            Entity target = opponents.get(targetID);

            // Fighting algorithm
            target.applyDamage(10);
        }
}

Я припускаю, що це буде працювати лише для одного гравця проти одного монстра. Або легко було б оновити це для роботи одного гравця проти кількох монстрів?
Харв

Додавати підтримку групам з обох сторін монстра як гравця досить просто (у вашій ситуації група гравців буде містити лише одного члена: персонажа гравця). Я оновив відповідь на цей сценарій.
привид

1

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

Що стосується вжитих дій, то, звичайно, можна просто зробити це випадковим, але монстру з повним НР мало би сенсу кинути цілющий заклинання. Слід мати деяку основну логіку для визначення того, які дії потрібно вжити. Наприклад, деякі дії можуть мати більший пріоритет, ніж інші (наприклад, троль б'є 30% часу), а також інші умови, щоб зробити бої більш цікавими (наприклад, коли тролль HP менше 10% від повного HP, є 20% шанс закинути зцілення заклинанням, інакше шанс 1%). Це може бути настільки складно, як вам подобається.

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

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


1

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

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

Щодо ШІ, я думаю, що двигун повинен сам справлятися з ним, так що, скажімо, у вас є кілька видів ворога, який може зробити те ж саме (кусати), ви можете просто призначити AI іншому монстру і там ви йдете!


0

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

Зберігайте єдиний головний об'єкт Model, який містить усі дані, що описують ваш світ. Він буде містити загальну світову інформацію, таку як складність, фізичні параметри тощо. Він також буде містити список / масив даних конкретних утворень , як я описав вище. Ця основна модель може складатися з безлічі суб’єктів, щоб описати ваш світ. Ніде у вашій моделі не повинно бути функцій, які керують логікою гри чи логікою відображення; Отримати виняток - єдиний виняток, і він буде використовуватися лише для того, щоб ви могли швидше отримувати дані з моделі (якщо громадські члени вже не роблять фокус).

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

class Main
{

//...members variables...
var model:GameModel = new GameModel();

//...member functions...
function realTimeUpdate() //called x times per second, on a timer.
{
    for each (var entity in model.entities)
    {
        //command processing
        if (entity == player)
            decideActionsFromPlayerInput(entity);
        else //everyone else is your enemy!
            decideActionsThroughDeviousAI(entity);

        act(entity);
    }
}
//OR
function turnBasedUpdate()
{
    if (model.whoseTurn == "player")
    {
        decideActionsFromInput(model.player); //may be some movement or none at all
        act(player);
    }
    else
    {
        var enemy;
        for each (var entity in model.entities)
        {
            if (entity != model.player)
            {
                enemy = entity;
                decideActions(enemy);
                act(enemy);
            }
        }
    }
}

//AND THEN... (common to both turn-based and real-time)
function decideActionsThroughDeviousAI(enemy)
{
    if (distanceBetween(enemy, player) <= enemy.maximumAttackDistance)
        storeAttackCommand(enemy, "kidney punch", model.player);
    else
        storeMoveCommand(player, getVectorFromTo(enemy, model.player));

}

function decideActionsFromPlayerInput(player)
{
    //store commands to your player data based on keyboard input
    if (KeyManager.isKeyDown("A"))
        storeMoveCommand(player, getForwardVector(player));
    if (KeyManager.isKeyDown("space"))
        storeAttackCommand(player, "groin slam", currentlyHighlightedEnemy);
}
function storeAttackCommand(entity, attackType, target)
{
    entity.target = target;

    entity.currentAttack = attackType;
    //OR
    entity.attackQueue.add(attackType);
}
function storeMoveCommand(entity, motionVector)
{
    entity.motionVector = motionVector;
}
function act(entity)
{
    entity.position += entity.motionVector;
    attack(entity.target, entity.currentAttack);
}
}

class GameModel
{
    var entities:Array = []; //or List<Entity> or whatever!
    var player:Entity; //will often also appear in the entity list, above
    var difficultyLevel:int;
    var globalMaxAttackDamage:int;
    var whoseTurn:Boolean; //if turnbased
    //etc.

}

Остаточне зауваження полягає в тому, що також корисно зберігати логіку відображення окремо від логіки гри. Логіка відображення буде: "Де я малюю це на екрані і в якому кольорі?" проти логіки гри - це те, що я окреслив у псевдокоді вище.

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


1
"Зберігайте єдиний, основний об'єкт Model, який містить усі дані, що описують ваш світ. Він буде містити загальну інформацію про світ, таку як складність, фізичні параметри тощо". Параметри складності та фізики? Говоріть про плутанину проблем! -1.

2
@Joe - Ви хочете, щоб я окреслив йому всю ієрархію конфігурації? Тут ми просте, чи не так? Буду вдячний, якщо ви подумаєте перед тим, як звернутись до зворотного зв'язку.
Інженер

3
Що ж, решта поста - це химерна спроба прикрити MVC, не охоплюючи V або щось, що зазвичай розпізнається як C, і я не думаю, що MVC є гарною порадою для програмування ігор в першу чергу. Буду вдячний, якщо ви подумаєте, перш ніж відповісти, але ми не завжди можемо отримати те, що хочемо.

1
@Joe: Я погоджуюся, що MVC є грубим вибором для гри, але я впевнений, що роль V тут очевидна.
Зак Конн

4
@Zach: Коли висловлюються твердження на кшталт "FP - це остаточний MVC", нічого очевидного, крім, можливо, плакат не розуміє як MVC, так і функціональне програмування.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.