Bagaimana saya bisa membuat karakter berjalan di dinding yang tidak rata dalam platformer 2D?


11

Saya ingin memiliki karakter yang dapat dimainkan yang dapat "berjalan" di permukaan organik di sudut mana pun, termasuk ke samping dan terbalik. Dengan tingkat "organik" dengan fitur miring dan melengkung alih-alih garis lurus pada sudut 90 derajat.

Saat ini saya bekerja di AS3 (pengalaman amatir moderat) dan menggunakan Nape (cukup banyak pemula) untuk fisika dasar berbasis gravitasi, yang mana mekanik berjalan ini merupakan pengecualian yang jelas.

Apakah ada cara prosedural untuk melakukan mekanik jalan seperti ini, mungkin menggunakan kendala Nape? Atau akan lebih baik untuk membuat "jalur" berjalan eksplisit mengikuti kontur permukaan level dan menggunakannya untuk membatasi gerakan berjalan?


Untuk memperjelas: Anda ingin membuat karakter Anda dapat 'menempel' di dinding dan langit-langit di level Anda?
Qqwy

Itu benar.
Eric N

Jawaban:


9

Inilah pengalaman belajar saya yang lengkap, menghasilkan versi fungsional dari gerakan yang saya inginkan, semuanya menggunakan metode internal Nape. Semua kode ini ada di dalam kelas Spider saya, menarik beberapa properti dari induknya, kelas Level.

Sebagian besar kelas dan metode lainnya adalah bagian dari paket Nape. Inilah bagian terkait dari daftar impor saya:

import flash.events.TimerEvent;
import flash.utils.Timer;

import nape.callbacks.CbEvent;
import nape.callbacks.CbType;
import nape.callbacks.InteractionCallback;
import nape.callbacks.InteractionListener;
import nape.callbacks.InteractionType;
import nape.callbacks.OptionType;
import nape.dynamics.Arbiter;
import nape.dynamics.ArbiterList;
import nape.geom.Geom;
import nape.geom.Vec2;

Pertama, ketika laba-laba ditambahkan ke atas panggung, saya menambahkan pendengar ke dunia Nape untuk tabrakan. Ketika saya melangkah lebih jauh ke dalam pengembangan saya perlu membedakan kelompok-kelompok tabrakan; untuk saat ini, callback ini secara teknis akan dijalankan ketika tubuh APA SAJA bertabrakan dengan badan lainnya.

        var opType:OptionType = new OptionType([CbType.ANY_BODY]);
        mass = body.mass;
        // Listen for collision with level, before, during, and after.
        var landDetect:InteractionListener =  new InteractionListener(CbEvent.BEGIN, InteractionType.COLLISION, opType, opType, spiderLand)
        var moveDetect:InteractionListener =  new InteractionListener(CbEvent.ONGOING, InteractionType.COLLISION, opType, opType, spiderMove);
        var toDetect:InteractionListener =  new InteractionListener(CbEvent.END, InteractionType.COLLISION, opType, opType, takeOff);

        Level(this.parent).world.listeners.add(landDetect);
        Level(this.parent).world.listeners.add(moveDetect);
        Level(this.parent).world.listeners.add(toDetect);

        /*
            A reference to the spider's parent level's master timer, which also drives the nape world,
            runs a callback within the spider class every frame.
        */
        Level(this.parent).nTimer.addEventListener(TimerEvent.TIMER, tick);

Callback mengubah properti "state" laba-laba, yang merupakan sekumpulan boolean, dan merekam setiap arbitrase tabrakan Nape untuk digunakan nanti dalam logika jalan saya. Mereka juga mengatur dan menghapus toTimer, yang memungkinkan laba-laba kehilangan kontak dengan permukaan level hingga 100 ms sebelum memungkinkan gravitasi dunia untuk memegang lagi.

    protected function spiderLand(callBack:InteractionCallback):void {
        tArbiters = callBack.arbiters.copy();
        state.isGrounded = true;
        state.isMidair = false;
        body.gravMass = 0;
        toTimer.stop();
        toTimer.reset();
    }

    protected function spiderMove(callBack:InteractionCallback):void {
        tArbiters = callBack.arbiters.copy();
    }

    protected function takeOff(callBack:InteractionCallback):void {
        tArbiters.clear();
        toTimer.reset();
        toTimer.start();
    }

    protected function takeOffTimer(e:TimerEvent):void {
        state.isGrounded = false;
        state.isMidair = true;
        body.gravMass = mass;
        state.isMoving = false;
    }

Akhirnya, saya menghitung kekuatan apa yang diterapkan pada laba-laba berdasarkan keadaan dan hubungannya dengan tingkat geometri. Sebagian besar saya akan membiarkan komentar berbicara sendiri.

    protected function tick(e:TimerEvent):void {
        if(state.isGrounded) {
            switch(tArbiters.length) {
                /*
                    If there are no arbiters (i.e. spider is in midair and toTimer hasn't expired),
                    aim the adhesion force at the nearest point on the level geometry.
                */
                case 0:
                    closestA = Vec2.get();
                    closestB = Vec2.get();
                    Geom.distanceBody(body, lvBody, closestA, closestB);
                    stickForce = closestA.sub(body.position, true);
                    break;
                // For one contact point, aim the adhesion force at that point.
                case 1:
                    stickForce = tArbiters.at(0).collisionArbiter.contacts.at(0).position.sub(body.position, true);
                    break;
                // For multiple contact points, add the vectors to find the average angle.
                default:
                    var taSum:Vec2 = tArbiters.at(0).collisionArbiter.contacts.at(0).position.sub(body.position, true);
                    tArbiters.copy().foreach(function(a:Arbiter):void {
                        if(taSum != a.collisionArbiter.contacts.at(0).position.sub(body.position, true))
                            taSum.addeq(a.collisionArbiter.contacts.at(0).position.sub(body.position, true));
                    });

                    stickForce=taSum.copy();
            }
            // Normalize stickForce's strength.
            stickForce.length = 1000;
            var curForce:Vec2 = new Vec2(stickForce.x, stickForce.y);

            // For graphical purposes, align the body (simulation-based rotation is disabled) with the adhesion force.
            body.rotation = stickForce.angle - Math.PI/2;

            body.applyImpulse(curForce);

            if(state.isMoving) {
                // Gives "movement force" a dummy value since (0,0) causes problems.
                mForce = new Vec2(10,10);
                mForce.length = 1000;

                // Dir is movement direction, a boolean. If true, the spider is moving left with respect to the surface; otherwise right.
                // Using the corrected "down" angle, move perpendicular to that angle
                if(dir) {
                    mForce.angle = correctAngle()+Math.PI/2;
                } else {
                    mForce.angle = correctAngle()-Math.PI/2;
                }
                // Flip the spider's graphic depending on direction.
                texture.scaleX = dir?-1:1;
                // Now apply the movement impulse and decrease speed if it goes over the max.
                body.applyImpulse(mForce);
                if(body.velocity.length > 1000) body.velocity.length = 1000;

            }
        }
    }

Bagian lengket nyata yang saya temukan adalah bahwa sudut gerakan harus sesuai dengan arah gerakan yang diinginkan dalam skenario titik kontak berganda di mana laba-laba mencapai sudut tajam atau duduk di lembah yang dalam. Terutama karena, mengingat vektor-vektor saya yang dijumlahkan untuk gaya adhesi, gaya itu akan menarik JAUH dari arah yang ingin kita bergerak alih-alih tegak lurus terhadapnya, jadi kita perlu menangkal itu. Jadi saya perlu logika untuk memilih salah satu titik kontak yang akan digunakan sebagai dasar untuk sudut vektor gerakan.

Efek samping dari "tarikan" gaya adhesi adalah sedikit keraguan ketika laba-laba mencapai sudut / kurva cekung yang tajam, tetapi itu sebenarnya agak realistis dari sudut pandang tampilan dan nuansa jadi kecuali jika menyebabkan masalah di jalan saya akan biarkan apa adanya. Jika perlu, saya dapat menggunakan variasi pada metode ini untuk menghitung gaya adhesi.

    protected function correctAngle():Number {
        var angle:Number;
        if(tArbiters.length < 2) {
            // If there is only one (or zero) contact point(s), the "corrected" angle doesn't change from stickForce's angle.
            angle = stickForce.angle;
        } else {
            /*
                For more than one contact point, we want to run perpendicular to the "new" down, so we copy all the
                contact point angles into an array...
            */
            var angArr:Array = [];
            tArbiters.copy().foreach(function(a:Arbiter):void {
                var curAng:Number = a.collisionArbiter.contacts.at(0).position.sub(body.position, true).angle;
                if (curAng < 0) curAng += Math.PI*2;
                angArr.push(curAng);
            });
            /*
                ...then we iterate through all those contact points' angles with respect to the spider's COM to figure out
                which one is more clockwise or more counterclockwise, depending, with some restrictions...
                ...Whatever, the correct one.
            */
            angle = angArr[0];
            for(var i:int = 1; i<angArr.length; i++) {
                if(dir) {
                    if(Math.abs(angArr[i]-angle) < Math.PI)
                        angle = Math.max(angle, angArr[i]);
                    else
                        angle = Math.min(angle, angArr[i]);
                }
                else {
                    if(Math.abs(angArr[i]-angle) < Math.PI)
                        angle = Math.min(angle, angArr[i]);
                    else
                        angle = Math.max(angle, angArr[i]);
                }
            }
        }

        return angle;
    }

Logika ini cukup "sempurna," sejauh ini tampaknya melakukan apa yang saya inginkan. Namun, ada masalah kosmetik yang masih melekat, yaitu jika saya mencoba menyelaraskan gambar laba-laba dengan gaya adhesi atau gerakan, saya menemukan bahwa laba-laba itu berakhir "condong" ke arah gerakan, yang akan baik-baik saja jika ia seorang pelari atletik berkaki dua tetapi dia tidak, dan sudut-sudutnya sangat rentan terhadap variasi di medan, sehingga laba-laba gelisah ketika melewati benjolan sekecil apa pun. Saya dapat mengejar variasi pada solusi Byte56, mengambil sampel lanskap terdekat dan rata-rata sudut tersebut, untuk membuat orientasi laba-laba lebih halus dan lebih realistis.


1
Bagus, terima kasih telah memposting detail di sini untuk pengunjung masa depan.
MichaelHouse

8

Bagaimana kalau membuat permukaan "tongkat" yang disentuh karakter menggunakan gaya sepanjang kebalikan dari permukaan? Gaya tetap selama mereka berhubungan dengan permukaan dan menimpa gravitasi selama itu aktif. Jadi melompat dari langit-langit akan memiliki efek yang diharapkan jatuh ke lantai.

Anda mungkin ingin menerapkan beberapa fitur lain untuk membuat ini bekerja dengan lancar dan lebih mudah untuk diterapkan. Sebagai contoh, alih-alih hanya apa yang disentuh karakter menggunakan lingkaran di sekitar karakter dan jumlah normals terbalik. Seperti yang ditunjukkan gambar cat jelek ini:

masukkan deskripsi gambar di sini

(Kesamaan laba-laba yang diwakili adalah milik Byte56)

Garis biru adalah normal terbalik ke permukaan pada saat itu. Garis hijau adalah gaya penjumlahan yang diterapkan pada laba-laba. Lingkaran merah melambangkan rentang laba-laba yang dicari normals untuk digunakan.

Ini akan memungkinkan terjadinya benturan di medan tanpa laba-laba "kehilangan cengkeramannya". Mengalami ukuran dan bentuk lingkaran dalam hal ini, mungkin hanya menggunakan setengah lingkaran berorientasi dengan laba-laba ke bawah, mungkin hanya persegi panjang yang mencakup kaki.

Solusi ini memungkinkan Anda untuk mengaktifkan fisika, tanpa harus berurusan dengan jalur khusus yang dapat diikuti karakter. Itu juga menggunakan informasi yang cukup mudah didapat dan ditafsirkan (normals). Akhirnya, ini dinamis. Bahkan mengubah bentuk dunia itu mudah diperhitungkan, karena Anda dapat dengan mudah mendapatkan normals untuk geometri apa pun yang Anda gambar.

Ingatlah bahwa ketika tidak ada wajah dalam jangkauan laba-laba, gravitasi normal mengambil alih.


Penjumlahan normals mungkin akan menyelesaikan masalah yang ada pada solusi saya saat ini dengan sudut-sudut cekung yang tajam, tapi saya tidak terbiasa dengan bagaimana Anda mendapatkannya di AS3.
Eric N

Maaf, saya juga tidak terbiasa. Mungkin sesuatu yang perlu Anda pertahankan saat menghasilkan medan.
MichaelHouse

2
Saya telah berhasil mengimplementasikan ide ini karena saya dapat mendeteksi titik kontak tabrakan Nape dan rata-rata jika ada lebih dari satu. Tampaknya tidak perlu untuk bergerak di atas permukaan datar atau cembung, tetapi hal itu memecahkan masalah terbesar saya: apa yang harus dilakukan ketika laba-laba saya menemukan sudut yang tajam. Seperti yang disebutkan dalam jawaban baru saya, saya dapat mencoba variasi pada ide ini untuk membantu mengarahkan grafik spider saya.
Eric N
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.