Memprogram urutan pertarungan dalam permainan peran

13

Saya mencoba untuk menulis "permainan" singkat di mana seorang pemain berkeliling dan bertarung dengan monster, tetapi saya tidak tahu bagaimana menangani pertempuran.

Sebagai contoh, katakan saya memiliki "Prajurit" dan "Troll". Bagaimana keduanya saling bertarung? Saya tahu saya bisa melakukan sesuatu seperti

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

Tapi bagian mana dari permainan yang mengendalikan monster? Apakah saya hanya menempel urutan di atas dalam satu lingkaran sampai salah satu dari mereka mati? Atau apakah "mesin" game perlu memiliki bagian yang berhubungan dengan pertempuran? Atau apakah ini merupakan aspek kecerdasan buatan Troll yang perlu dijaga tindakannya?

Juga, siapa / apa yang menentukan tindakan yang dilakukan monster itu? Mungkin Troll bisa memukul, menendang, menggigit, merapal mantra, minum ramuan, menggunakan benda ajaib. Apakah mesin permainan menentukan tindakan apa yang diambil Troll atau apakah itu sesuatu yang dikelola kelas Troll?

Maaf saya tidak bisa lebih spesifik tapi saya butuh panduan tentang ke mana harus pergi dengan ini.

Harv
sumber
keren! tidak tahu situs itu ada. apakah ada cara saya bisa memindahkan pertanyaan saya di sana? atau haruskah saya memotong / menempelnya di sana?
Jangan khawatir, seorang mod harus segera memindahkannya! Atau Anda dapat menghapus pertanyaan di sini dan membuat ulang di Game Dev
LiamB
@Fendo Saya minta maaf karena bertanya, tetapi situs apa yang Anda maksud? Game Dev?
user712092

Jawaban:

12

Saya membayangkan urutan pertempuran sebagai minigame dalam gim Anda. Kutu pembaruan (atau tik tik) diarahkan ke komponen yang menangani peristiwa ini. Pendekatan ini merangkum logika urutan pertempuran di kelas yang terpisah, membuat loop game utama Anda bebas untuk transisi di antara status game.

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

}

Kelas urutan pertempuran akan terlihat seperti ini:

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

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

Troll dan Warrior Anda sama-sama mewarisi dari superclass umum yang disebut Entity. Di dalam HandleTurn, entitas yang menyerang diizinkan untuk bergerak. Ini setara dengan rutinitas berpikir 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;
    }
}

Metode pertarungan memutuskan apa yang akan dilakukan entitas. Perhatikan bahwa ini tidak perlu melibatkan entitas lawan, seperti minum ramuan atau melarikan diri.

Pembaruan: Untuk mendukung banyak monster dan pesta pemain, Anda memperkenalkan kelas Grup:

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

Kelas Grup akan menggantikan semua kejadian Entitas di kelas BattleSequence. Memilih dan menyerang akan ditangani oleh kelas Entity itu sendiri, sehingga AI dapat mempertimbangkan seluruh kelompok saat memilih tindakan terbaik.

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);
        }
}
hantu
sumber
Saya berasumsi ini hanya akan bekerja untuk satu pemain vs satu monster. Atau mudahkah memperbarui ini agar berfungsi untuk satu pemain vs banyak monster?
Harv
Sangat mudah untuk menambahkan dukungan untuk grup di kedua sisi monster sebagai sisi pemain (dalam situasi Anda, grup pemain hanya akan berisi satu anggota: karakter pemain). Saya telah memperbarui jawaban untuk skenario ini.
hantu
1

Saya akan memiliki objek Combat khusus yang mengelola pertempuran. Itu merangkum keadaan pertempuran penuh termasuk hal-hal seperti daftar karakter pemain, daftar musuh, giliran saat ini, medan pertempuran, dan sebagainya. Combat kemudian dapat memiliki metode pembaruan yang mengelola logika pertempuran. Itu bukan ide yang baik untuk hanya meletakkan kode pertempuran dalam satu loop sederhana, karena itu akan berakhir sangat cepat. Biasanya Anda akan memiliki waktu dan tahapan pertempuran yang berbeda.

Untuk tindakan yang dilakukan, Anda tentu saja dapat membuatnya secara acak, tetapi tidak masuk akal jika monster dengan HP penuh menggunakan mantra penyembuhan. Membayar untuk memiliki beberapa logika dasar untuk menentukan tindakan yang harus diambil. Misalnya, beberapa tindakan dapat memiliki prioritas lebih dari yang lain (misalnya troll menendang 30% dari waktu), serta kondisi lain untuk membuat pertempuran lebih menarik (misalnya ketika troll HP kurang dari 10% dari HP penuh, ada 20% kesempatan casting mantra penyembuhan, jika tidak kesempatan 1%). Ini bisa serumit yang Anda suka.

Saya pikir kelas monster harus menangani memilih tindakan apa yang akan dibuat, objek pertempuran meminta monster untuk tindakan dan monster membuat pilihan dan kemudian mulai menerapkannya. Satu ide adalah memiliki objek strategi yang Anda tancapkan ke monster dan yang memilih dari daftar kemungkinan tindakan monster berdasarkan prioritas, kategori, dan kondisi yang ditetapkan untuk setiap aksi pertempuran. Maka Anda dapat memiliki kelas OffensiveStrategy misalnya yang memprioritaskan serangan terhadap keterampilan defensif, dan CautiousStrategy lain yang lebih mungkin untuk sembuh. Seorang bos mungkin dapat secara dinamis mengubah strategi berdasarkan kondisi saat ini.

Satu hal terakhir. Anda mungkin ingin memiliki karakter pemain dan monster yang diwarisi dari kelas yang sama, menjadi instance dari kelas yang sama (misalnya aktor atau petarung), atau berbagi objek umum yang merangkum fungsi umum. Ini mengurangi duplikasi kode dan itu juga memungkinkan Anda untuk memiliki NPC yang dikendalikan AI di sisi Anda yang dapat menerapkan strategi yang sama dengan yang sudah Anda kodekan untuk monster.

Firas Assaad
sumber
1

Ya, Anda perlu memiliki bagian khusus di mesin Anda yang menangani pertempuran.

Saya tidak tahu bagaimana tepatnya Anda melakukan pertempuran Anda, tetapi saya akan menganggap para pemain berkeliaran di dunia permainan, bertemu dengan monster dan pertempuran berlangsung secara real time. Jika demikian maka, troll perlu mengetahui sekitarnya dalam area tertentu, mungkin menentukan seberapa jauh troll dapat melihat sesuatu di depannya (troll menangani ini).

Tentang AI, saya pikir mesin perlu menanganinya sendiri, jadi katakanlah Anda memiliki lebih dari satu jenis musuh yang dapat melakukan hal yang sama (gigitan), Anda bisa menugaskan AI ke monster lain dan begitulah!

Daggio
sumber
0

Pemain Anda dan troll Anda hanyalah kumpulan data, yang kami sebut Model data yang menggambarkan dunia Anda. Kehidupan, inventaris, kemampuan serangan, bahkan pengetahuan mereka tentang dunia - semua terdiri dari model data.

Simpan satu objek Model utama yang menyimpan semua data yang menggambarkan dunia Anda. Ini akan menyimpan informasi dunia umum seperti kesulitan, parameter fisika dll. Ia juga akan menyimpan daftar / array data entitas tertentu seperti yang telah saya jelaskan di atas. Model utama ini dapat terdiri dari banyak sub-objek untuk menggambarkan dunia Anda. Tidak ada dalam model Anda jika Anda memiliki fungsi yang mengontrol logika game atau logika tampilan; getter adalah satu-satunya pengecualian, dan akan digunakan hanya untuk memungkinkan Anda mendapatkan data dari model lebih mudah (jika anggota publik belum melakukan triknya).

Selanjutnya, buat fungsi di satu atau lebih kelas "controller"; Anda dapat menulis semuanya sebagai fungsi pembantu di kelas utama Anda, meskipun ini mungkin menjadi agak besar setelah beberapa saat. Ini akan disebut setiap pembaruan untuk bertindak berdasarkan data entitas untuk tujuan yang berbeda (pergerakan, serangan, dll.). Menjaga fungsi-fungsi ini di luar kelas entitas lebih efisien sumber daya, dan begitu Anda tahu apa yang mendeskripsikan entitas Anda, Anda akan secara otomatis tahu fungsi apa yang perlu ditindaklanjuti.

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.

}

Catatan terakhir adalah bahwa berguna juga untuk menjaga logika tampilan Anda terpisah dari logika game Anda. Logika tampilan adalah, "Di mana saya menggambar ini di layar dan dalam warna apa?" vs logika permainan menjadi apa yang telah saya uraikan dalam pseudcode di atas.

(Catatan Dev: Saat menggunakan kelas, ini secara longgar mengikuti pendekatan pemrograman fungsional yang menganggap semua metode sebagai stateless stateless, memungkinkan untuk model data bersih dan pendekatan pemrosesan yang meminimalkan bug yang disebabkan oleh keadaan yang dipertahankan. FP adalah MVC utama, karena mencapai MVC's Tujuan dari pemisahan masalah secara eksplisit. Lihat pertanyaan ini .)

Insinyur
sumber
1
"Simpan satu, objek Model utama yang menyimpan semua data yang menggambarkan duniamu. Ini akan menyimpan informasi dunia umum seperti kesulitan, parameter fisika dll." Parameter kesulitan dan fisika? Bicara tentang penggabungan keprihatinan! -1.
2
@ Jo - Apakah Anda ingin saya menguraikan kepadanya seluruh hierarki konfigurasi? Kita menjaganya tetap sederhana di sini, bukan? Saya akan menghargai jika Anda berpikir sebelum downvoting.
Insinyur
3
Yah, sisa posting adalah upaya aneh untuk menutupi MVC tanpa mencakup V atau apa pun yang biasanya dikenal sebagai C, dan saya tidak berpikir MVC adalah saran yang baik untuk pemrograman game. Saya akan menghargai jika Anda berpikir sebelum menjawab, tetapi kami tidak selalu bisa mendapatkan yang kami inginkan.
1
@ Jo: Saya setuju bahwa MVC adalah pilihan kasar untuk sebuah game, tapi saya cukup yakin peran V di sini sudah jelas.
Zach Conn
4
@Zach: Ketika klaim seperti "FP adalah MVC akhir" dibuat, tidak ada yang jelas, kecuali mungkin bahwa poster gagal memahami MVC dan pemrograman fungsional.