Программирование боевой последовательности в ролевой игре

13

Я пытаюсь написать короткую «игру», в которой игрок ходит вокруг и сражается с монстрами, но я не знаю, как справиться с боем.

Например, скажем, у меня есть «Воин» и «Тролль». Как они сражаются друг с другом? Я знаю, что могу сделать что-то вроде

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

Но какая часть игры управляет монстром? Должен ли я просто вставить вышеуказанную последовательность в цикл, пока один из них не умрет? Или в игровом «движке» должна быть часть, специально предназначенная для боя? Или это аспект искусственного интеллекта Тролля, который должен заботиться о его действиях?

Кроме того, кто / что определяет действия монстра? Возможно, тролль может избивать, пинать, кусать, накладывать заклинания, пить зелья, использовать магический предмет. Определяет ли игровой движок, какое действие выполняет Тролль, или этим управляет класс Тролля?

Извините, я не могу быть более конкретным, но мне нужно какое-то руководство, в каком направлении идти с этим.

Харв
источник
круто! не знал, что сайт существует. Есть ли способ перенести мой вопрос туда? или я должен просто вырезать / вставить его там?
Не беспокойтесь, мод должен переместить это довольно скоро! Или вы можете удалить вопрос здесь и воссоздать заново в Game Dev
LiamB
@Fendo Я прошу прощения за вопрос, но какой сайт Вы имеете в виду? Разработка игр?
user712092

Ответы:

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 атакующему объекту разрешено двигаться. Это эквивалентно мыслительной программе ИИ.

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 заменит все вхождения Entity в классе BattleSequence. Выбор и атака будут обрабатываться самим классом сущностей, поэтому ИИ может принять во внимание всю группу при выборе наилучшего курса действий.

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);
        }
}
призрак
источник
Я предполагаю, что это будет работать только для одного игрока против одного монстра. Или было бы легко обновить это, чтобы работать для одного игрока против нескольких монстров?
Harv
Довольно просто добавить поддержку групп как на стороне монстра, так и на стороне игрока (в вашей ситуации группа игроков будет содержать только одного участника: персонаж игрока). Я обновил ответ для этого сценария.
призрак
1

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

Что касается предпринятых действий, вы, конечно, можете просто сделать это случайным, но для монстра с полным HP не имеет смысла разыгрывать исцеляющее заклинание. Полезно иметь некоторую базовую логику для определения того, какое действие предпринять. Например, некоторые действия могут иметь больший приоритет, чем другие (например, удары троллей в 30% случаев), а также другие условия, делающие сражения более интересными (например, когда HP троллей составляет менее 10% от полного HP, есть 20% шанс наложить исцеляющее заклинание, в противном случае - 1%). Это может быть так сложно, как вам нравится.

Я думаю, что класс монстров должен обрабатывать выбор того, какое действие сделать, объект сражения просит монстра о действии, и монстр делает выбор, а затем продолжает его применять. Одна идея состоит в том, чтобы иметь стратегический объект, который вы подключаете к монстрам и который выбирает из списка возможных действий монстров на основе приоритетов, категорий и условий, назначенных каждому боевому действию. Тогда у вас может быть класс OffensiveStrategy, например, который расставляет приоритеты по атакам над защитными навыками, и другой CauensiveStrategy, который с большей вероятностью лечит. Босс может быть в состоянии динамически менять стратегию в зависимости от его текущего состояния.

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

Firas Assaad
источник
1

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

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

Что касается ИИ, я думаю, что движок должен сам с ним справиться, так что, скажем, у вас есть несколько видов врагов, которые могут делать одно и то же (кусаться), вы можете просто назначить ИИ другому монстру, и все готово!

Daggio
источник
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.

}

И последнее замечание: полезно также отделить логику отображения от логики игры. Логика дисплея будет такой: «Где я могу нарисовать это на экране и каким цветом?» против игровой логики то, что я обрисовал в общих чертах в псевдокоде выше.

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

инженер
источник
1
«Держите один основной объект Model, который содержит все данные, описывающие ваш мир. Он будет содержать общую информацию о мире, такую ​​как сложность, физические параметры и т. Д.» Сложность и физические параметры? Разговор о смешении проблем! -1.
2
@Joe - Вы хотите, чтобы я обрисовал ему всю иерархию конфигурации? Здесь все просто, не так ли? Я был бы признателен, если бы вы подумали, прежде чем понизить.
инженер
3
Что ж, остальная часть поста - это странная попытка охватить MVC без учета V или чего-то, что обычно распознается как C, и я не думаю, что MVC - это хороший совет для программирования игр. Я был бы признателен, если бы вы подумали, прежде чем ответить, но мы не всегда можем получить то, что хотим.
1
@Joe: я согласен, что MVC - это грубый выбор для игры, но я почти уверен, что роль V здесь очевидна.
Зак Конн
4
@ Зач: Когда высказываются такие утверждения, как «FP - это максимальный MVC», ничто не становится очевидным, разве что постер не понимает как MVC, так и функциональное программирование.