在角色扮演游戏中设定战斗顺序


13

我正在尝试写一个简短的“游戏”,玩家要四处游荡并与怪物战斗,但我不知道如何处理战斗。

例如,假设我有一个“战士”和一个“巨魔”。两者如何打架?我知道我可以做类似的事情

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

但是游戏的哪一部分控制怪物?我是否只是将上述序列循环粘贴直到其中一个死亡?还是游戏“引擎”需要专门处理战斗的部分?还是这是巨魔人工智能中需要照顾其行为的一个方面?

另外,谁/什么决定怪物采取的行动?也许巨魔可以扑,踢,咬,施展咒语,喝药水,使用魔法物品。游戏引擎会确定Troll采取什么动作还是Troll类管理的事情?

抱歉,我不能更具体,但是我需要一些指导,以解决这个问题。


凉!不知道该网站存在。有什么办法可以将我的问题移到那儿吗?还是应该在此处剪切/粘贴?

不用担心,mod应该很快将其移动!或者,您可以在此处删除问题,然后在Game Dev
LiamB

我对@Fendo表示歉意,但您指的是什么网站?游戏开发人员?
user712092 2011年

Answers:


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;
}

您的Troll和Warrior都继承自称为Entity的公共超类。在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;
    }
}

战斗方法决定实体将要做什么。请注意,这不需要涉及对方实体,例如喝药水或逃跑。

更新:为了支持多个怪物和一个玩家聚会,您引入了一个Group类:

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。选择和攻击将由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);
        }
}

我假设这只会对一个玩家对一个怪物起作用。还是将其更新为适用于一个玩家或多个怪物的游戏容易吗?
Harv

在怪物侧和玩家侧都添加对组的支持非常容易(在您的情况下,玩家组将只包含一个成员:玩家角色)。我已经针对这种情况更新了答案。

1

我会有一个专门的Combat对象来管理战斗。它封装了完整的战斗状态,包括玩家角色列表,敌人列表,当前回合,战斗地形等。然后,战斗可以具有管理战斗逻辑的更新方法。仅仅将战斗代码放在一个简单的循环中不是一个好主意,因为它会很快结束。通常,您会有一些时间安排和不同的战斗阶段。

对于所采取的动作,您当然可以将其随机化,但是对于拥有完整HP的怪物来说,施放治疗法术毫无意义。拥有一些确定要采取的动作的基本逻辑是值得的。例如,某些动作可能比其他动作具有更高的优先级(例如,巨魔踢出30%的时间),以及使战斗变得更有趣的其他条件(例如,巨魔的HP小于全部HP的10%时,则有20%释放治疗咒语的几率,否则几率为1%)。这可能很复杂。

我认为怪物类应该处理选择要做出的动作,战斗对象要求怪物做出一个动作,然后怪物做出选择,然后继续应用。一种想法是有一个战略对象,您可以将其插入怪物中,并根据分配给每个战斗动作的优先级,类别和条件从可能的怪物动作列表中进行选择。然后,您可以拥有一个OffensiveStrategy类,该类将攻击优先于防御技能,而另一个CautiousStrategy更可能治愈。老板可能能够根据其当前状况动态更改策略。

最后一件事。您可能想让玩家角色和怪物都从同一个类继承,成为同一个类的实例(例如演员或战斗员),或者共享一个封装了通用功能的通用对象。这样可以减少代码重复,并且还可以让您拥有AI控制的NPC,从而可以实现与已经为怪物编码的相同策略。


1

是的,您需要在引擎中有一个特殊部分来处理战斗。

我不知道您的战斗情况如何,但我假设玩家在游戏世界中漫游,与怪物会面,战斗实时进行。如果是这样的话,巨魔需要了解某个区域内的周围环境,也许要定义巨魔可以看到多远的东西(巨魔可以处理)。

关于AI,我认为引擎需要自行处理,因此,假设您有多种敌人可以做相同的事情(咬伤),您可以将AI分配给另一个怪物,然后就可以了!


0

您的玩家和巨魔不过是数据集,我们称之为描述您的世界的数据模型。生命,库存,攻击能力,甚至对世界的了解-都包含在数据模型中。

保留一个主要的Model对象,其中包含描述您的世界的所有数据。它将保存一般的世界信息,例如难度,物理参数等。它还将保存特定实体数据的列表/数组,如上所述。这个主要模型可以包含许多子对象,以描述您的世界。在模型中的任何地方都不应具有控制游戏逻辑或显示逻辑的任何功能。getters是唯一的例外,并且仅用于使您更容易地从模型中获取数据(如果公共成员尚未做到这一点)。

接下来,在一个或多个“控制器”类中创建函数;您可以在主类中将它们全部编写为辅助函数,尽管过一会儿可能会变得有些大。这些将称为每次更新,以出于不同目的(移动,攻击等)作用于实体的数据。将这些功能保留在实体类之外会更节省资源,一旦您知道描述实体的内容,您就会自动知道需要对它执行哪些功能。

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.

}

最后一点是,将显示逻辑与游戏逻辑分开也是很有用的。显示逻辑为:“我该在屏幕上以什么颜色绘制它?” vs.游戏逻辑就是我在上面的伪代码中概述的内容。

(Dev的注解:在使用类时,这大致遵循一种函数式编程方法,该方法将所有方法都视为理想的无状态方法,从而提供了一种干净的数据模型和处理方法,可最大程度地减少由保留状态引起的错误。FP是最终的MVC,因为它实现了MVC关注点分离的目标。请参阅此问题。)


1
“保留一个单一的主要Model对象,该对象保存描述您的世界的所有数据。它将保存一般的世界信息,例如难度,物理参数等。” 难度和物理参数?谈论担忧的融合!-1。

2
@Joe-您要我向他概述整个配置层次结构吗?我们在这里保持简单,不是吗?如果您能在投票之前考虑一下,我将不胜感激。
工程师

3
好吧,本文的其余部分是在不涵盖V或通常可识别为C的任何内容的情况下涵盖MVC的一种奇怪尝试,而且我不认为MVC首先是游戏编程的好建议。如果您能在回答之前先想一想,我们将不胜感激,但我们始终无法获得想要的东西。

1
@Joe:我同意MVC是游戏的一个粗略选择,但是我很确定V在这里的作用是显而易见的。
扎克·康

4
@Zach:提出诸如“ FP是终极MVC”之类的主张时,没有什么是显而易见的,只是张贴者可能无法理解MVC和函数式编程。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.