Як я можу змусити героя ходити по нерівних стінах у 2D платформері?


11

Я хочу мати граючого персонажа, який може «ходити» на органічній поверхні під будь-яким кутом, включаючи збоку і догори ногами. За "органічними" рівнями з похилими і вигнутими рисами замість прямих ліній під кутом 90 градусів.

В даний час я працюю в AS3 (помірний досвід любителів) і використовую Nape (в значній мірі новачок) для основної фізики на основі гравітації, для якої цей механік ходіння стане очевидним винятком.

Чи є процедурний спосіб зробити такий тип механіки ходіння, можливо, використовуючи обмеження Нейпа? Або краще було б створити чіткі пішохідні «доріжки» за контурами рівних поверхонь і використовувати їх для обмеження руху ходьби?


Для уточнення: Ви хочете, щоб ваш персонаж міг «прилипати» до стін і стелі на своєму рівні?
Qqwy

Це правильно.
Ерік Н

Відповіді:


9

Ось мій повний досвід навчання, в результаті якого я отримав досить функціональну версію руху, який я хотів, і все використовував внутрішні методи Nape. Весь цей код знаходиться в моєму класі Spider, витягуючи деякі властивості з його батьківського, рівня рівня.

Більшість інших класів та методів є частиною пакету Нейп. Ось відповідна частина мого списку імпорту:

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

Відклики змінюють властивість "стану" павука, що є набором булевих, і записують будь-яких арбітрів зіткнення Nape для подальшого використання в моїй логіці ходіння. Вони також встановлюють і чіткий toTimer, який дозволяє павуку втрачати контакт із рівневою поверхнею до 100 мс, перш ніж дозволяти світовій силою тяжіння знову зайнятися.

    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.
Ерік Н

Вибачте, я теж не знайомий. Можливо, щось потрібно, щоб утримувати себе під час генерації рельєфу.
MichaelHouse

2
Мені вдалося реалізувати цю ідею, оскільки я можу виявити точки зіткнення Нейпа і зіставити їх за середнім рівнем, якщо їх більше. Здається, це не потрібно для переміщення по плоских або опуклих поверхнях, але це вирішило моє найбільше питання: що робити, коли мій павук стикається з гострим кутом. Як було сказано в моїй новій відповіді, я можу спробувати варіацію цієї ідеї, щоб допомогти зорієнтувати графіку мого павука.
Ерік Н
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.