ロールプレイングゲームでの戦闘シーケンスのプログラミング


13

プレイヤーが周りを回ってモンスターと戦う短い「ゲーム」を書こうとしていますが、戦闘の処理方法がわかりません。

たとえば、「Warrior」と「Troll」があるとします。二人はどのように互いに戦うのですか?私は次のようなことができることを知っています

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

しかし、ゲームのどの部分がモンスターを制御しますか?上記のシーケンスをループに入れて、それらの1つが死ぬまで続けますか?または、ゲームの「エンジン」には、戦闘に特化した部分が必要ですか?または、これはその行動を処理する必要があるトロールの人工知能の側面ですか?

また、誰が/何がモンスターの行動を決定しますか?トロールは、たたく、蹴る、噛む、呪文を唱える、ポーションを飲む、魔法のアイテムを使うことができるかもしれません。ゲームエンジンは、トロールが実行するアクションを決定しますか、それともトロールクラスが管理するものですか?

申し訳ありませんが、これ以上具体的にすることはできませんが、これをどの方向に進めるかについてのガイダンスが必要です。


涼しい!サイトが存在することを知りませんでした。質問をそこに移動する方法はありますか?または、そこに切り貼りするだけですか?

心配する必要はありません。MODはすぐにそれを動かすはずです!または、ここで質問を削除して、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;
}

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

fightメソッドは、エンティティが何をしようとしているかを決定します。これは、ポーションを飲む、逃げるなど、相手のエンティティを関与させる必要がないことに注意してください。

更新:複数のモンスターとプレイヤーパーティーをサポートするには、グループクラスを導入します。

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

これは、1人のプレイヤーと1人のモンスターに対してのみ機能すると仮定しています。または、1人のプレイヤーと複数のモンスターで動作するようにこれを更新するのは簡単でしょうか?
-Harv

モンスター側とプレイヤー側の両方にグループのサポートを追加するのは非常に簡単です(状況では、プレイヤーグループには1つのメンバー(プレイヤーキャラクターのみ)が含まれます)。このシナリオの答えを更新しました。
ゴースト

1

戦闘を管理する専用の戦闘オブジェクトがあります。プレイヤーキャラクターのリスト、敵のリスト、現在のターン、戦闘地形などを含む完全な戦闘状態をカプセル化します。戦闘では、戦闘ロジックを管理する更新メソッドを使用できます。戦闘コードを単純なループに配置するのは良い考えではありません。それは本当に速く終了するからです。通常、いくつかのタイミングと異なるバトルステージがあります。

実行されるアクションについては、確かにランダムにすることができますが、HPがフルのモンスターが回復呪文を唱えることはほとんど意味がありません。どのアクションを実行するかを決定するための基本的なロジックが必要です。たとえば、一部のアクションは他のアクションよりも優先度が高くなります(たとえば、トロールキックは30%の時間)。また、戦闘をより面白くする他の条件(たとえば、トロールHPがフルHPの10%未満の場合、20%治癒呪文を唱える可能性、そうでない場合の可能性は1%です)。これは好きなだけ複雑にすることができます。

モンスタークラスは実行するアクションの選択を処理し、バトルオブジェクトはモンスターにアクションを要求し、モンスターは選択を行い、それを適用します。1つのアイデアは、モンスターにプラグインし、各戦闘アクションに割り当てられた優先順位、カテゴリ、条件に基づいて、可能なモンスターアクションのリストから選択する戦略オブジェクトを持つことです。次に、防御スキルよりも攻撃を優先するOffensiveStrategyクラス、および回復する可能性が高い別のCautiousStrategyを使用できます。上司は、現在の状態に基づいて戦略を動的に変更できる場合があります。

最後に一つだけ。プレイヤーキャラクターとモンスターの両方を同じクラスから継承するか、同じクラス(俳優や戦闘員など)のインスタンスにするか、共通機能をカプセル化する共通オブジェクトを共有することができます。これによりコードの重複が減り、また、モンスター用に既にコーディングした同じ戦略を実装できるAI制御NPCをあなたの側に置くことができます。


1

はい、戦闘を処理するエンジンに特別な部分が必要です。

あなたがどの程度正確に戦闘を行っているのかはわかりませんが、プレイヤーがゲームの世界を歩き回り、モンスターと出会い、リアルタイムで戦闘が進行すると仮定します。その場合、トロールは特定のエリア内の周囲を知る必要があります。トロールがその前にあるものをどこまで見ることができるかを定義する必要があります(トロールはこれを処理します)。

AIについては、エンジンがそれ自体を処理する必要があると思うので、同じこと(噛みつき)を行うことができる複数の種類の敵がいるとしましょう.AIを別のモンスターに割り当てるだけで、そこに行くことができます!


0

プレイヤーとトロールは、あなたの世界を記述するデータモデルと呼ばれるデータのセットにすぎません。ライフ、インベントリ、攻撃能力、世界の知識さえもすべてがデータモデルに含まれています。

あなたの世界を記述するすべてのデータを保持する単一のメインModelオブジェクトを保持します。難易度、物理パラメータなどの一般的な世界情報を保持します。特定のリスト/配列も保持します上で説明したようにエンティティのデータの。このメインモデルは、世界を記述するために多くのサブオブジェクトで構成できます。モデルのどこにも、ゲームロジックを制御したり、ロジックを表示したりする機能はありません。ゲッターは唯一の例外であり、より簡単にモデルからデータを取得できるようにするためにのみ使用されます(パブリックメンバーがまだトリックを実行していない場合)。

次に、1つ以上の「コントローラー」クラスで関数を作成します。これらはすべてメインクラスのヘルパー関数として記述できますが、しばらくすると少し大きくなる場合があります。これらは、更新のたびに呼び出され、さまざまな目的(移動、攻撃など)のためにエンティティのデータに作用します。エンティティクラスの外では、より多くのリソースを効率的であり、あなたは何を知っていれば、これらの機能を保つ説明ます。また、エンティティをするば、それに基づいて機能する必要がある機能が自動的にわかります。

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.ゲームロジックは、上記の擬似コードで説明したとおりです。

(開発者のメモ:クラスを使用している間、これは、すべてのメソッドを理想的にステートレスと見なす関数型プログラミングアプローチに従います。関心の分離の明確な目標。この質問を参照してください。


1
「あなたの世界を記述するすべてのデータを保持する単一のメインModelオブジェクトを保持します。難易度、物理パラメータなどの一般的な世界情報を保持します。」難易度と物理パラメータ 懸念の混同について話す!-1。

2
@Joe-構成階層全体の概要を説明してほしいですか?ここではシンプルにしていますが、そうではありませんか?投票する前に考えていただければ幸いです。
エンジニア

3
投稿の残りは、Vまたは通常Cとして認識されるものをカバーせずにMVCをカバーする奇妙な試みであり、MVCはそもそもゲームプログラミングのための良いアドバイスではないと思います。答える前に考えていただければ幸いですが、私たちが常に望んでいるものを手に入れることはできません。

1
@ジョー:MVCはゲームの大まかな選択であることに同意しますが、ここでのVの役割は明らかです。
ザックコン

4
@Zach:「FPは究極のMVC」のような主張がなされたとき、ポスターがMVCと関数型プログラミングの両方を理解しなかったということを除いて、何も明らかではありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.