Lập trình trình tự chiến đấu trong một game nhập vai


13

Tôi đang cố gắng viết một "trò chơi" ngắn trong đó một người chơi đi xung quanh và chiến đấu với quái vật nhưng tôi không biết làm thế nào để điều khiển trận chiến.

Ví dụ: giả sử tôi có "Chiến binh" và "Quỷ". Làm thế nào để hai người chiến đấu với nhau? Tôi biết tôi có thể làm một cái gì đó như

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

Nhưng phần nào của trò chơi điều khiển quái vật? Tôi chỉ cần dán chuỗi trên trong một vòng lặp cho đến khi một trong số họ chết? Hay trò chơi "engine" cần phải có một phần liên quan cụ thể đến chiến đấu? Hay đây là một khía cạnh của trí tuệ nhân tạo của Troll cần quan tâm đến hành động của nó?

Ngoài ra, ai / cái gì quyết định hành động mà quái vật thực hiện? Có lẽ một con quỷ có thể bash, đá, cắn, bỏ bùa, uống thuốc, sử dụng vật phẩm ma thuật. Liệu công cụ trò chơi có xác định hành động nào mà Troll thực hiện hay đó là thứ mà lớp Troll quản lý?

Xin lỗi tôi không thể cụ thể hơn nhưng tôi cần một số hướng dẫn về hướng đi này.


mát mẻ! không biết rằng trang web tồn tại. Có cách nào tôi có thể di chuyển câu hỏi của tôi ở đó không? hoặc tôi chỉ nên cắt / dán nó ở đó?

Đừng lo lắng, một mod nên di chuyển nó khá sớm! Hoặc bạn có thể xóa câu hỏi tại đây và tạo lại tại Game Dev
LiamB

@Fendo Tôi xin lỗi vì đã hỏi, nhưng ý bạn là trang nào? Trò chơi Dev?
dùng712092

Câu trả lời:


12

Tôi tưởng tượng một chuỗi trận chiến như một minigame trong trò chơi của bạn. Cập nhật tick (hoặc tick tick) được hướng đến một thành phần xử lý các sự kiện này. Cách tiếp cận này gói gọn logic chuỗi trận chiến trong một lớp riêng biệt, để vòng lặp trò chơi chính của bạn tự do chuyển đổi giữa các trạng thái trò chơi.

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
            // ...
        }
    }

}

Lớp trình tự chiến đấu sẽ như thế này:

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

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

Cả Troll và Warrior của bạn đều được thừa hưởng từ một siêu lớp thông thường có tên là Entity. Trong HandTurn, thực thể tấn công được phép di chuyển. Điều này tương đương với một thói quen suy nghĩ 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;
    }
}

Phương pháp chiến đấu quyết định những gì thực thể sẽ làm. Lưu ý rằng điều này không cần liên quan đến thực thể đối nghịch, như uống thuốc hay chạy trốn.

Cập nhật: Để hỗ trợ nhiều quái vật và một nhóm người chơi, bạn giới thiệu một lớp Nhóm:

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

Lớp Nhóm sẽ thay thế tất cả các lần xuất hiện của Thực thể trong lớp BattleSequence. Việc chọn và tấn công sẽ được xử lý bởi chính lớp Thực thể, do đó AI có thể đưa cả nhóm vào tài khoản khi chọn hướng hành động tốt nhất.

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

Tôi cho rằng điều này sẽ chỉ hoạt động cho một người chơi và một quái vật. Hoặc có thể dễ dàng cập nhật cái này để hoạt động cho một người chơi và nhiều quái vật?
Harvest

Khá dễ dàng để thêm hỗ trợ cho các nhóm ở cả hai phía quái vật là phía người chơi (trong tình huống của bạn, nhóm người chơi sẽ chỉ chứa một thành viên: nhân vật người chơi). Tôi đã cập nhật câu trả lời cho kịch bản này.
ma

1

Tôi sẽ có một đối tượng Chiến đấu chuyên dụng quản lý chiến đấu. Nó gói gọn toàn bộ trạng thái chiến đấu bao gồm những thứ như danh sách nhân vật người chơi, danh sách kẻ thù, lượt hiện tại, địa hình chiến đấu, v.v. Chiến đấu sau đó có thể có một phương pháp cập nhật quản lý logic chiến đấu. Không nên để mã chiến đấu vào một vòng lặp đơn giản, bởi vì nó sẽ kết thúc rất nhanh. Thông thường bạn sẽ có một số giai đoạn chiến đấu và thời gian khác nhau.

Đối với các hành động được thực hiện, bạn chắc chắn có thể làm cho nó ngẫu nhiên, nhưng sẽ không có ý nghĩa gì với một con quái vật có đầy đủ HP để sử dụng phép thuật chữa bệnh. Nó trả tiền để có một số logic cơ bản để xác định hành động nào cần thực hiện. Ví dụ: một số hành động có thể được ưu tiên hơn các hành động khác (ví dụ: troll đá 30% thời gian), cũng như các điều kiện khác để khiến các trận chiến trở nên thú vị hơn (ví dụ: khi troll HP dưới 10% HP đầy đủ, có 20% cơ hội dùng phép chữa bệnh, nếu không thì cơ hội là 1%). Điều này có thể phức tạp như bạn muốn.

Tôi nghĩ rằng lớp quái vật nên xử lý việc lựa chọn hành động để thực hiện, đối tượng chiến đấu yêu cầu quái vật hành động và quái vật đưa ra lựa chọn và sau đó tiến hành áp dụng nó. Một ý tưởng là có một đối tượng chiến lược mà bạn cắm vào quái vật và chọn từ danh sách các hành động quái vật có thể dựa trên các ưu tiên, danh mục và điều kiện được chỉ định cho mỗi hành động chiến đấu. Sau đó, bạn có thể có một lớp OffensiveStrargety, ví dụ như ưu tiên các cuộc tấn công hơn các kỹ năng phòng thủ và một CautiousStrargety khác có khả năng chữa lành cao hơn. Một ông chủ có thể có thể thay đổi chiến lược một cách linh hoạt dựa trên tình trạng hiện tại của nó.

Một điều cuối cùng. Bạn có thể muốn cả hai nhân vật người chơi và quái vật được thừa hưởng từ cùng một lớp, là các thể hiện của cùng một lớp (ví dụ như diễn viên hoặc chiến binh) hoặc chia sẻ một đối tượng chung gói gọn chức năng chung. Điều này làm giảm sự sao chép mã và nó cũng cho phép bạn có các NPC do AI kiểm soát về phía bạn, điều này có thể thực hiện các chiến lược giống như bạn đã mã hóa cho quái vật.


1

Vâng, bạn cần phải có một phần đặc biệt trong động cơ của bạn để xử lý chiến đấu.

Tôi không biết chính xác bạn đang chiến đấu như thế nào nhưng tôi sẽ cho rằng người chơi lang thang trong thế giới trò chơi, gặp gỡ quái vật và trận chiến diễn ra theo thời gian thực. Nếu vậy thì, troll cần biết xung quanh trong một khu vực nhất định, có thể xác định khoảng cách mà troll có thể nhìn thấy thứ gì đó ở phía trước nó (troll xử lý việc này).

Về AI, tôi nghĩ rằng động cơ cần phải tự xử lý nó, vì vậy, giả sử bạn có nhiều hơn một loại kẻ thù có thể làm điều tương tự (cắn), bạn có thể chỉ định AI cho một quái vật khác và bạn sẽ đi!


0

Trình phát và troll của bạn không là gì ngoài bộ dữ liệu, cái mà chúng ta gọi là Mô hình dữ liệu mô tả thế giới của bạn. Cuộc sống, hàng tồn kho, khả năng tấn công, kiến ​​thức của họ về thế giới thậm chí - tất cả bao gồm trong mô hình dữ liệu.

Giữ một đối tượng Mô hình chính duy nhất chứa tất cả dữ liệu mô tả thế giới của bạn. Nó sẽ chứa thông tin chung về thế giới như độ khó, thông số vật lý, v.v. Nó cũng sẽ chứa một danh sách / mảng dữ liệu của các thực thể cụ thể như tôi đã mô tả ở trên. Mô hình chính này có thể bao gồm nhiều tiểu dự án để mô tả thế giới của bạn. Không ở đâu trong mô hình của bạn, bạn nên có bất kỳ chức năng nào điều khiển logic trò chơi hoặc logic hiển thị; getters là ngoại lệ duy nhất và sẽ chỉ được sử dụng để cho phép bạn lấy dữ liệu từ mô hình dễ dàng hơn (nếu các thành viên công cộng chưa thực hiện thủ thuật này).

Tiếp theo, tạo các hàm trong một hoặc nhiều lớp "trình điều khiển"; bạn có thể viết tất cả chúng dưới dạng các hàm trợ giúp trong lớp chính của mình, mặc dù điều này có thể hơi lớn sau một thời gian. Chúng sẽ được gọi mỗi bản cập nhật để hành động theo dữ liệu của các thực thể cho các mục đích khác nhau (di chuyển, tấn công, v.v.). Giữ các chức năng này bên ngoài một lớp thực thể hiệu quả hơn về tài nguyên và khi bạn biết những gì mô tả thực thể của mình, bạn sẽ tự động biết những chức năng nào cần phải hành động trên nó.

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.

}

Một lưu ý cuối cùng là nó cũng hữu ích để giữ logic hiển thị của bạn tách biệt với logic trò chơi của bạn. Logic hiển thị sẽ là "Tôi vẽ cái này trên màn hình ở đâu và màu gì?" so với logic trò chơi là những gì tôi đã phác thảo trong mã giả ở trên.

. Mục tiêu của việc phân tách các mối quan tâm rõ ràng. Xem câu hỏi này .)


1
"Giữ một đối tượng Mô hình chính duy nhất chứa tất cả dữ liệu mô tả thế giới của bạn. Nó sẽ chứa thông tin chung về thế giới như độ khó, thông số vật lý, v.v." Độ khó và thông số vật lý? Nói về sự kết hợp của mối quan tâm! -1.

2
@Joe - Bạn có muốn tôi phác thảo cho anh ấy toàn bộ cấu trúc phân cấp không? Chúng tôi đang giữ nó đơn giản ở đây, phải không? Tôi sẽ đánh giá cao nếu bạn nghĩ trước khi hạ cấp.
Kỹ sư

3
Phần còn lại của bài viết là một nỗ lực kỳ lạ để bao trùm MVC mà không bao gồm V hoặc bất cứ điều gì thường được nhận ra là C, và tôi không nghĩ rằng MVC là lời khuyên tốt cho lập trình trò chơi ngay từ đầu. Tôi đánh giá cao nếu bạn nghĩ trước khi trả lời, nhưng chúng ta không thể luôn có được những gì chúng ta muốn.

1
@Joe: Tôi đồng ý rằng MVC là một lựa chọn sơ bộ cho một trò chơi, nhưng tôi khá chắc chắn rằng vai trò của V ở đây là rõ ràng.
Zach Conn

4
@Zach: Khi các tuyên bố như "FP là MVC cuối cùng" được đưa ra, không có gì là rõ ràng, ngoại trừ có thể người đăng không hiểu cả MVC và lập trình chức năng.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.