롤 플레잉 게임에서 전투 시퀀스 프로그래밍


13

플레이어가 돌아 다니면서 몬스터와 싸우는 짧은 "게임"을 작성하려고하는데 전투를 어떻게 처리해야할지 모르겠습니다.

예를 들어, "Warrior"와 "Troll"이 있다고 가정합니다. 두 사람은 어떻게 서로 싸우나요? 나는 내가 할 수있는 일을 알고있다

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 내에서 공격 엔티티를 이동할 수 있습니다. 이것은 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의 모든 발생을 대체합니다. 선택과 공격은 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

전투를 관리하는 전용 전투 개체가 있습니다. 플레이어 캐릭터 목록, 적 목록, 현재 차례, 전투 지형 등을 포함한 전체 전투 상태를 요약합니다. 전투는 전투 논리를 관리하는 업데이트 방법을 가질 수 있습니다. 전투 코드를 간단한 루프에 넣는 것은 좋은 생각이 아닙니다. 정말 빠르기 때문입니다. 일반적으로 타이밍과 전투 단계가 다릅니다.

취한 행동의 경우 확실히 무작위로 만들 수는 있지만 HP가 많은 몬스터가 치유 주문을하는 것은 거의 의미가 없습니다. 수행 할 조치를 결정하기위한 몇 가지 기본 논리가 필요합니다. 예를 들어, 일부 액션은 다른 액션보다 우선 순위가 높을 수 있으며 (예 : 트롤 킥 시간의 30 %) 전투를 더욱 흥미롭게 만드는 다른 조건 (예 : 트롤 HP가 HP 전체의 10 % 미만인 경우 20 %) 치유 주문 시전 확률, 그렇지 않으면 확률 1 %) 이것은 원하는만큼 복잡 할 수 있습니다.

몬스터 클래스는 어떤 행동을 취해야하는지 결정해야하고, 전투 개체는 몬스터에게 행동을 요구하고, 몬스터는 선택을 한 다음 적용을 계속해야한다고 생각합니다. 한 가지 아이디어는 몬스터에 연결하고 각 전투 액션에 할당 된 우선 순위, 범주 및 조건에 따라 가능한 몬스터 액션 목록에서 선택하는 전략 객체를 갖는 것입니다. 그런 다음 방어 기술보다 공격 우선 순위를 정하는 OffensiveStrategy 클래스와 치유 가능성이 높은 다른 CautiousStrategy를 사용할 수 있습니다. 보스는 현재 상태에 따라 전략을 동적으로 변경할 수 있습니다.

마지막 한가지. 플레이어 캐릭터와 몬스터를 같은 클래스에서 상속 받거나 같은 클래스의 인스턴스 (예 : 배우 또는 전투원) 또는 공통 기능을 캡슐화하는 공통 객체를 공유 할 수 있습니다. 이렇게하면 코드 중복이 줄어들고 이미 몬스터를 위해 코딩 한 전략과 동일한 전략을 구현할 수있는 AI 제어 NPC를 사용할 수 있습니다.


1

예, 전투를 처리하는 엔진에 특수 부품이 있어야합니다.

나는 당신이 정확히 어떻게 전투를하고 있는지 모르겠지만 플레이어가 게임 세계를 돌아 다니며 괴물을 만나고 전투가 실시간으로 진행된다고 가정합니다. 그렇다면 트롤이 특정 지역의 주변 환경을 알아야합니다. 트롤이 먼 곳에서 무언가를 볼 수있는 거리를 정의하십시오 (트롤이 처리).

AI에 관해서는 엔진이 자체적으로 처리해야한다고 생각하므로 같은 일 (물기)을 할 수있는 하나 이상의 적을 가지고 있다고 가정하면 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.

}

마지막으로 디스플레이 로직을 게임 로직과 분리하여 유지하는 것도 유용합니다. 디스플레이 로직은 "어디에서 화면에 어떤 색으로 그려야합니까?"입니다. vs. 게임 로직은 위의 의사 코드에서 설명한 것입니다.

(Dev 's note : 클래스를 사용하는 동안 이것은 모든 메소드를 이상적인 상태 비 저장으로 간주하는 함수형 프로그래밍 방식을 느슨하게 따르며 유지 된 상태로 인한 버그를 최소화하는 깨끗한 데이터 모델 및 처리 방식을 허용합니다. FP는 MVC를 달성하므로 궁극적 인 MVC입니다. 우려를 명확하게 구분하는 목표.이 질문을 참조하십시오 .)


1
"세계를 설명하는 모든 데이터를 보유하는 단일 기본 Model 객체를 유지하십시오. 난이도, 물리 매개 변수 등과 같은 일반 세계 정보를 보유합니다." 어려움과 물리 매개 변수? 우려의 혼란에 대해 이야기하십시오! -1.

2
@Joe-전체 구성 계층을 그에게 설명 하시겠습니까? 우리는 여기서 간단하게 유지하고 있습니까? 당신이 downvoting 전에 생각한다면 감사합니다.
엔지니어

3
글의 나머지 부분은 V 또는 일반적으로 C로 인식 가능한 것을 다루지 않고 MVC를 다루는 기괴한 시도이며, MVC가 처음에는 게임 프로그래밍에 대한 좋은 조언이라고 생각하지 않습니다. 대답하기 전에 생각하면 감사하지만 항상 원하는 것을 얻을 수는 없습니다.

1
@Joe : MVC가 게임을위한 대단한 선택이라는 데 동의하지만 여기서 V의 역할이 분명하다고 확신합니다.
Zach Conn

4
@Zach : "FP is the ultimate MVC"와 같은 주장이 제기 될 때, 포스터가 MVC와 함수형 프로그래밍을 모두 이해하지 못한다는 것을 제외하고는 분명하지 않습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.