ฉันจะทำให้ตัวละครเดินบนกำแพงที่ไม่เรียบใน platformer 2D ได้อย่างไร


11

ฉันต้องการมีตัวละครที่เล่นได้ซึ่งสามารถ "เดิน" บนพื้นผิวอินทรีย์ได้ทุกมุมรวมถึงด้านข้างและกลับหัว โดยระดับ "อินทรีย์" ที่มีคุณสมบัติเอียงและโค้งแทนเส้นตรงที่มุม 90 องศา

ฉันกำลังทำงานใน AS3 (ประสบการณ์มือสมัครเล่นระดับปานกลาง) และใช้ Nape (มือใหม่มาก) สำหรับฟิสิกส์พื้นฐานที่ใช้แรงโน้มถ่วงซึ่งช่างเดินนี้จะเป็นข้อยกเว้นที่ชัดเจน

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


เพื่อชี้แจง: คุณต้องการให้ตัวละครของคุณสามารถ 'ติด' บนผนังและเพดานในระดับของคุณ?
Qqwy

ถูกต้อง.
Eric N

คำตอบ:


9

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

คลาสและเมธอดอื่น ๆ ส่วนใหญ่เป็นส่วนหนึ่งของแพ็คเกจ Nape นี่คือส่วนที่เกี่ยวข้องของรายการนำเข้าของฉัน:

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;

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

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

การเรียกกลับเปลี่ยนคุณสมบัติ "สถานะ" ของแมงมุมซึ่งเป็นชุดของบูลลีนและบันทึกอนุญาโตตุลาการการชนของต้นคอเพื่อใช้ในภายหลังในตรรกะการเดินของฉัน พวกเขายังตั้งค่าและชัดเจน toTimer ซึ่งอนุญาตให้แมงมุมสูญเสียการสัมผัสกับพื้นผิวระดับสูงสุดถึง 100ms ก่อนที่จะอนุญาตให้แรงโน้มถ่วงของโลกจับอีกครั้ง

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

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

    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;

            }
        }
    }

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

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

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

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


1
ทำได้ดีมากขอบคุณสำหรับการโพสต์รายละเอียดที่นี่สำหรับผู้เยี่ยมชมในอนาคต
MichaelHouse

8

วิธีการเกี่ยวกับการทำพื้นผิว "ติด" ใด ๆ ตัวละครสัมผัสใช้แรงตามแนวผกผันปกติของพื้นผิว แรงยังคงอยู่ตราบเท่าที่พวกมันสัมผัสกับพื้นผิวและแทนที่แรงโน้มถ่วงตราบใดที่มันยังทำงานอยู่ ดังนั้นการกระโดดจากเพดานจะมีผลที่คาดว่าจะตกลงไปที่พื้น

คุณอาจต้องการใช้คุณสมบัติอื่น ๆ เพื่อให้งานนี้ราบรื่นและง่ายต่อการใช้งาน ตัวอย่างเช่นแทนที่จะเป็นเพียงสิ่งที่ตัวละครสัมผัสใช้วงกลมรอบตัวละครและสรุปบรรทัดฐานคว่ำ ดังภาพสีเส็งเคร็งแสดง:

ป้อนคำอธิบายรูปภาพที่นี่

(ภาพสไปเดอร์ที่แสดงนั้นเป็นสมบัติของ Byte56)

เส้นสีฟ้าเป็นบรรทัดฐานผกผันกับพื้นผิว ณ จุดนั้น เส้นสีเขียวคือแรงรวมที่ใช้กับแมงมุม วงกลมสีแดงหมายถึงช่วงที่แมงมุมกำลังมองหาบรรทัดฐานที่จะใช้

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

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

จำไว้ว่าเมื่อไม่มีใบหน้าอยู่ในระยะของแมงมุมความโน้มถ่วงตามปกติจะเข้ามาแทนที่


บรรทัดฐานที่สรุปรวมน่าจะแก้ปัญหาที่ทางออกปัจจุบันของฉันมีมุมเว้าที่คมชัด แต่ฉันไม่คุ้นเคยกับวิธีที่คุณได้มาใน AS3
Eric N

ขออภัยฉันไม่คุ้นเคยเหมือนกัน อาจเป็นสิ่งที่คุณต้องดูแลรักษาเมื่อคุณสร้างภูมิประเทศ
MichaelHouse

2
ฉันสามารถนำแนวคิดนี้ไปใช้ได้เนื่องจากฉันสามารถตรวจหาจุดสัมผัสของการชนกันของ Nape และหาค่าเฉลี่ยถ้ามีมากกว่าหนึ่งจุด ดูเหมือนจะไม่จำเป็นสำหรับการเคลื่อนที่บนพื้นผิวเรียบหรือนูน แต่มันแก้ปัญหาที่ใหญ่ที่สุดของฉัน: สิ่งที่ต้องทำเมื่อแมงมุมของฉันพบมุมที่คมชัด ดังที่ได้กล่าวไว้ในคำตอบใหม่ของฉันฉันอาจลองเปลี่ยนความคิดนี้เพื่อช่วยปรับทิศทางกราฟิกของแมงมุม
Eric N
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.