Bir 2D platformda düzensiz duvarlarda bir karakteri nasıl yürütebilirim?


11

Yan ve baş aşağı olmak üzere her açıdan organik bir yüzey üzerinde "yürüyebilen" bir oynanabilir karakter istiyorum. 90 derecelik açılarda düz çizgiler yerine eğimli ve kavisli özelliklere sahip "organik" seviyeler.

Şu anda AS3'te (ılımlı amatör deneyim) çalışıyorum ve bu yürüyüş mekaniğinin bariz bir istisna olacağı temel yerçekimi tabanlı fizik için Nape'i (neredeyse bir acemi) kullanıyorum.

Belki de Nape kısıtlamalarını kullanarak bu tür bir yürüyüş mekaniği yapmanın prosedürel bir yolu var mı? Yoksa düz yüzeylerin dış hatlarını izleyerek açık yürüyüş "yolları" oluşturmak ve bunları yürüyüş hareketini kısıtlamak için kullanmak en iyisi olur mu?


Açıklığa kavuşturmak için: Karakterinizin seviyenizdeki duvarlara ve tavanlara yapışmasını mı istiyorsunuz?
Qqwy

Bu doğru.
Eric N

Yanıtlar:


9

İşte tam öğrenme deneyimim, istediğim hareketin neredeyse hepsi Nape'in dahili yöntemlerini kullanarak işlevsel bir versiyonuyla sonuçlandı. Bu kodun tamamı Spider sınıfımın içinde, üst düzeyden bir Level sınıfı olan bazı özellikleri çekiyor.

Diğer sınıfların ve yöntemlerin çoğu Nape paketinin bir parçasıdır. İçe aktarma listemin ilgili kısmı:

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;

İlk olarak, örümcek sahneye eklendiğinde, çarpışmalar için Nape dünyasına dinleyiciler ekliyorum. Gelişim aşamasına geçtikçe, çarpışma gruplarını farklılaştırmam gerekecek; Şimdilik, bu geri aramalar, HERHANGİ bir beden başka bir bedenle çarpıştığında teknik olarak yürütülecektir.

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

Geri aramalar, bir dizi boolean olan örümceğin "state" özelliğini değiştirir ve yürüyüş mantığımda daha sonra kullanmak için Nape çarpışma hakemlerini kaydeder. Ayrıca, örümceklerin dünya yerçekiminin tekrar tutulmasına izin vermeden önce 100 ms'ye kadar seviye yüzeyiyle temasını kaybetmesine izin veren Zamanlayıcı'yı ayarlayıp temizlerler.

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

Son olarak, örümceklere hangi kuvvetlerin uygulanacağını, durumuna ve seviye geometrisiyle ilişkisine göre hesaplıyorum. Ben çoğunlukla yorumların kendileri için konuşmasına izin vereceğim.

    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;

            }
        }
    }

Bulduğum gerçek yapışkan kısım, hareket açısının, örümceğin keskin bir açıya ulaştığı veya derin bir vadide oturduğu çoklu temas noktası senaryosunda istenen gerçek hareket yönünde olması gerektiğiydi. Özellikle yapışma kuvveti için topladığım vektörlerim göz önüne alındığında, bu kuvvet AWAY'ı ona dik olmak yerine hareket etmek istediğimiz yönden çekeceğinden, buna karşı koymalıyız. Bu yüzden hareket vektörünün açısı için temel olarak kullanılacak temas noktalarından birini seçmek için mantığa ihtiyacım vardı.

Yapışma kuvvetinin "çekilmesi" nin bir yan etkisi, örümcek keskin bir içbükey açıya / eğriye ulaştığında hafif bir tereddüttür, ancak bu aslında bir görünüm ve his açısından gerçekçi bir şeydir, bu yüzden yolda sorunlara neden olmadıkça olduğu gibi bırakın. Gerekirse, yapışma kuvvetini hesaplamak için bu yöntemde bir varyasyon kullanabilirim.

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

Bu mantık hemen hemen "mükemmel" dir, ancak şu ana kadar yapmasını istediğim şeyi yapıyor gibi görünüyor. Bununla birlikte, kalıcı bir kozmetik sorun var, çünkü örümceğin grafiğini yapışma veya hareket kuvvetlerine hizalamaya çalışırsam, örümceğin hareket yönünde "eğildiğini" görürsem, eğer iki bacaklı atletik sprinter ama değil, ve açıları arazideki değişikliklere karşı oldukça hassastır, bu yüzden örümcek en ufak bir yumrudan geçtiğinde titriyor. Örümceğin yönünü daha pürüzsüz ve daha gerçekçi hale getirmek için Byte56'nın çözümünde, yakınlardaki manzaradan örnek alarak ve bu açıları ortalayarak bir varyasyon izleyebilirim.


1
Aferin, gelecekteki ziyaretçiler için ayrıntıları buraya gönderdiğiniz için teşekkürler.
MichaelHouse

8

Bir karakterin dokunduğu herhangi bir "çubuk" yüzeyi yapmaya ne dersiniz, yüzeyin ters normaline bir kuvvet uygulayabilirsiniz? Kuvvet, yüzeyle temas ettikleri sürece kalır ve aktif olduğu sürece yer çekimini geçersiz kılar. Yani tavandan atlamak, yere düşmek gibi bir etki yaratacaktır.

Muhtemelen bunun sorunsuz çalışması ve uygulanması daha kolay olması için başka bazı özellikler uygulamak istersiniz. Örneğin, karakterin dokunduğu şey yerine karakterin etrafında bir daire kullanın ve ters çevrilmiş normalleri toplayın. Bu berbat boya görüntüsünün gösterdiği gibi:

resim açıklamasını buraya girin

(Temsil edilen örümcek benzerliği Byte56'nın mülküdür)

Mavi çizgiler, o noktada yüzeye ters normallerdir. Yeşil çizgi, örümceğe uygulanan toplam kuvvettir. Kırmızı daire, örümceğin normalleri aradığı aralığı temsil eder.

Bu, örümcek "tutuşunu kaybetmeden" arazide biraz şişkinliğe izin verir. Bu boyut için daire boyutu ve şekli ile ilgili deneyim, belki sadece örümcek aşağı dönük yarım bir daire kullanın, belki sadece bacakları kapsayan bir dikdörtgen.

Bu çözüm, karakterin takip edebileceği belirli yollarla uğraşmanıza gerek kalmadan fiziği açık tutmanıza izin verir. Ayrıca elde edilmesi ve yorumlanması oldukça kolay olan bilgiler (normaller) kullanır. Son olarak, dinamik. Dünyanın şeklini değiştirmek bile hesaba katılabilir, çünkü çizdiğiniz geometri için kolayca normale ulaşabilirsiniz.

Örümcek menzili içinde hiçbir yüz olmadığında normal yer çekiminin devraldığını unutmayın.


Özetlenmiş normaller muhtemelen mevcut çözümümün keskin içbükey köşelerde sahip olduğu sorunları çözecektir, ancak bunları AS3'te nasıl elde ettiğinizi bilmiyorum.
Eric N

Üzgünüm, ben de tanıdık değilim. Araziyi oluştururken muhtemelen kendinizi korumanız gereken bir şey.
MichaelHouse

2
Bu fikri, Nape'in çarpışma temas noktalarını tespit edebildiğim ve birden fazla varsa ortalayabildiğim kadar uygulamayı başardım. Düz veya dışbükey yüzeylerde hareket etmek için gerekli görünmüyor, ancak en büyük sorunumu çözdü: örümcekim keskin bir köşeyle karşılaştığında ne yapmalı. Yeni cevabımda belirtildiği gibi, örümceğimin grafiğini yönlendirmeye yardımcı olmak için bu fikir üzerinde bir varyasyon deneyebilirim.
Eric N
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.