การเขียนโปรแกรมลำดับการต่อสู้ในเกมสวมบทบาท


13

ฉันพยายามเขียน "เกม" สั้น ๆ ที่ผู้เล่นไปรอบ ๆ และต่อสู้กับสัตว์ประหลาด แต่ฉันไม่รู้ว่าจะจัดการกับการต่อสู้ได้อย่างไร

ตัวอย่างเช่นสมมติว่าฉันมี "นักรบ" และ "หมุนรอบ" ทั้งสองต่อสู้กันอย่างไร ฉันรู้ว่าฉันสามารถทำอะไรบางอย่างเช่น

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

แต่ส่วนใดของเกมที่ควบคุมสัตว์ประหลาด? ฉันเพียงแค่ติดลำดับข้างต้นในลูปจนกว่าหนึ่งในนั้นจะตาย? หรือ "เครื่องมือ" เกมจำเป็นต้องมีส่วนที่เกี่ยวข้องกับการต่อสู้โดยเฉพาะหรือไม่? หรือนี่คือแง่มุมของปัญญาประดิษฐ์ของ Troll ที่ต้องดูแลการกระทำของมัน?

ใครเป็นผู้ตัดสินการกระทำของสัตว์ประหลาด? บางทีโทรลล์อาจทุบตีเตะกัดเสกคาถาดื่มยาใช้สิ่งของวิเศษ เอ็นจิ้นของเกมเป็นตัวกำหนดสิ่งที่ Troll ใช้หรือเป็นสิ่งที่คลาส Troll จัดการหรือไม่

ขออภัยฉันไม่สามารถระบุได้มากขึ้น แต่ฉันต้องการคำแนะนำเกี่ยวกับทิศทางที่จะไปกับสิ่งนี้


เย็น! ไม่รู้ว่ามีเว็บไซต์นี้อยู่ มีวิธีที่ฉันสามารถย้ายคำถามของฉันไปที่นั่นหรือไม่ หรือฉันควรจะตัด / วางที่นั่นหรือไม่

ไม่ต้องกังวล mod ควรย้ายไปในไม่ช้า! หรือคุณสามารถลบคำถามได้ที่นี่และสร้างใหม่อีกครั้งที่ Game Dev
LiamB

@Fendo ฉันขอโทษที่ถาม แต่คุณหมายถึงเว็บไซต์ใด เกม Dev
user712092

คำตอบ:


12

ฉันจินตนาการลำดับการต่อสู้เป็น minigame ในเกมของคุณ การอัพเดตเห็บ (หรือจุดเปลี่ยน) ถูกนำไปยังส่วนประกอบที่จัดการเหตุการณ์เหล่านี้ วิธีการนี้จะสรุปตรรกะลำดับการต่อสู้ในคลาสแยกออกจากเกมหลักของคุณโดยอิสระเพื่อเปลี่ยนระหว่างสถานะเกม

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 ของคุณทั้งสองสืบทอดมาจาก Superclass ทั่วไปที่เรียกว่า 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;
    }
}

วิธีการต่อสู้จะตัดสินว่ากิจการจะทำอะไร โปรดทราบว่าสิ่งนี้ไม่จำเป็นต้องเกี่ยวข้องกับฝ่ายตรงข้ามเช่นการดื่มยาหรือวิ่งหนี

อัปเดต:เพื่อสนับสนุนสัตว์ประหลาดหลายตัวและกลุ่มผู้เล่นคุณแนะนำคลาสกลุ่ม:

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

คลาสกลุ่มจะแทนที่การเกิดขึ้นทั้งหมดของเอนทิตีในคลาส BattleSequence การเลือกและการจู่โจมจะได้รับการจัดการโดยหน่วยงานระดับเองเพื่อให้ 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 น้อยกว่า 10% ของ HP เต็มมี 20% โอกาสในการร่ายเวทย์มนตร์รักษามิฉะนั้นโอกาสคือ 1%) สิ่งนี้อาจซับซ้อนเหมือนที่คุณต้องการ

ฉันคิดว่าคลาสสัตว์ประหลาดควรจัดการกับการเลือกสิ่งที่จะทำวัตถุการต่อสู้ขอให้สัตว์ประหลาดสำหรับการกระทำและสัตว์ประหลาดเลือกแล้วดำเนินการเพื่อนำไปใช้ แนวคิดหนึ่งคือมีวัตถุกลยุทธ์ที่คุณเสียบเข้ากับสัตว์ประหลาดและเลือกจากรายการการกระทำของสัตว์ประหลาดที่เป็นไปได้ตามลำดับความสำคัญหมวดหมู่และเงื่อนไขที่กำหนดให้กับการต่อสู้แต่ละการกระทำ จากนั้นคุณสามารถมีคลาส OffensiveStrategy ซึ่งจัดลำดับความสำคัญของการโจมตีเกี่ยวกับทักษะการป้องกันและ CautiousStrategy อื่นที่มีแนวโน้มที่จะรักษา เจ้านายอาจสามารถเปลี่ยนกลยุทธ์ได้ตามสภาพปัจจุบัน

สิ่งสุดท้าย. คุณอาจต้องการทั้งตัวละครผู้เล่นและมอนสเตอร์ที่สืบทอดมาจากคลาสเดียวกัน, เป็นอินสแตนซ์ของคลาสเดียวกัน (ตัวอย่างเช่นนักแสดงหรือนักสู้) หรือแบ่งปันวัตถุทั่วไปที่ห่อหุ้มการทำงานทั่วไป สิ่งนี้จะช่วยลดการทำสำเนารหัสและยังช่วยให้คุณมี NPC ที่ควบคุมโดย AI ซึ่งอยู่ด้านข้างของคุณซึ่งสามารถใช้กลยุทธ์เดียวกับที่คุณใช้รหัสสำหรับสัตว์ประหลาด


1

ใช่คุณต้องมีส่วนพิเศษในเครื่องยนต์ของคุณที่จัดการกับการต่อสู้

ฉันไม่รู้ว่าคุณกำลังต่อสู้อย่างไร แต่ฉันจะถือว่าผู้เล่นท่องไปในโลกของเกมพบกับสัตว์ประหลาดและการต่อสู้เป็นไปตามเวลาจริง ถ้าเป็นเช่นนั้นโทรลล์จำเป็นต้องรู้สภาพแวดล้อมภายในบางพื้นที่บางทีอาจกำหนดว่าโทรลล์สามารถมองเห็นบางสิ่งที่อยู่ข้างหน้าได้ไกลแค่ไหน (โทรลล์จัดการกับสิ่งนี้)

เกี่ยวกับ AI ฉันคิดว่าเครื่องยนต์จำเป็นต้องจัดการมันด้วยตัวเองดังนั้นสมมติว่าคุณมีศัตรูมากกว่าหนึ่งชนิดที่สามารถทำสิ่งเดียวกันได้ (กัด) คุณสามารถกำหนด AI ให้กับสัตว์ประหลาดตัวอื่นแล้วไปที่นั่น!


0

ผู้เล่นและโทรลล์ของคุณไม่มีอะไรนอกจากชุดข้อมูลเราเรียกโมเดลข้อมูลซึ่งอธิบายโลกของคุณ ชีวิต, สินค้าคงคลัง, ความสามารถในการโจมตี, ความรู้ของพวกเขาทั่วโลก - ทั้งหมดประกอบด้วยในรูปแบบข้อมูล

เก็บวัตถุโมเดลหลักชิ้นเดียวซึ่งเก็บข้อมูลทั้งหมดที่อธิบายโลกของคุณ มันจะเก็บข้อมูลโลกทั่วไปเช่นความยากลำบากพารามิเตอร์ทางฟิสิกส์ ฯลฯ มันจะเก็บรายการ / อาร์เรย์ของข้อมูลเอนทิตีเฉพาะตามที่ฉันได้อธิบายไว้ข้างต้น โมเดลหลักนี้อาจประกอบด้วย subobjects มากมายเพื่ออธิบายโลกของคุณ คุณควรมีฟังก์ชั่นใด ๆ ที่ควบคุมตรรกะของเกมหรือตรรกะการแสดงผล 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.

}

หมายเหตุสุดท้ายก็คือมันยังมีประโยชน์ในการแยกตรรกะการแสดงของคุณออกจากตรรกะเกมของคุณ ตรรกะการแสดงผลคือ "ฉันจะวาดสิ่งนี้บนหน้าจอและสีใด?" เทียบกับตรรกะของเกมเป็นสิ่งที่ฉันได้อธิบายไว้ในรหัสเทียมด้านบน

(หมายเหตุของ Dev: ในขณะที่ใช้คลาสนี้จะทำตามวิธีการเขียนโปรแกรมที่ใช้งานได้ซึ่งคิดว่าวิธีการทั้งหมดเป็นแบบไร้สัญชาติทำให้สามารถสร้างแบบจำลองข้อมูลที่สะอาดและวิธีการประมวลผลที่ลดข้อผิดพลาดที่เกิด เป้าหมายการแยกข้อกังวลอย่างชัดเจนดูคำถามนี้)


1
"เก็บวัตถุโมเดลหลักชิ้นเดียวซึ่งเก็บข้อมูลทั้งหมดที่อธิบายโลกของคุณมันจะเก็บข้อมูลโลกทั่วไปเช่นความยากพารามิเตอร์ทางฟิสิกส์เป็นต้น" พารามิเตอร์ความยากลำบากและฟิสิกส์? พูดคุยเกี่ยวกับปัญหาที่เกิดจากความกังวล! -1

2
@Joe - คุณต้องการให้ฉันจัดลำดับชั้นการกำหนดค่าทั้งหมดให้เขาหรือไม่ เราทำให้มันง่ายที่นี่เราไม่ได้? ฉันจะซาบซึ้งถ้าคุณคิดก่อน downvoting
วิศวกร

3
ส่วนที่เหลือของโพสต์เป็นความพยายามที่แปลกประหลาดที่จะครอบคลุม MVC โดยไม่ครอบคลุม V หรืออะไรก็ตามที่เป็นที่รู้จักในฐานะ C และฉันไม่คิดว่า MVC เป็นคำแนะนำที่ดีสำหรับการเขียนโปรแกรมเกมในตอนแรก ฉันยินดีถ้าคุณคิดก่อนตอบ แต่เราไม่สามารถรับสิ่งที่เราต้องการได้เสมอ

1
@ โจ: ฉันยอมรับว่า MVC เป็นตัวเลือกคร่าวๆสำหรับเกม แต่ฉันค่อนข้างมั่นใจว่าบทบาทของ V ที่นี่ชัดเจน
Zach Conn

4
@ Zach: เมื่อการเรียกร้องเช่น "FP คือสุดยอด MVC" ถูกสร้างขึ้นไม่มีอะไรที่ชัดเจนยกเว้นบางทีโปสเตอร์อาจไม่เข้าใจทั้ง MVC และการเขียนโปรแกรมการทำงาน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.