GDevelop-extensions icon indicating copy to clipboard operation
GDevelop-extensions copied to clipboard

New extension PlatformLedgeGrabber.

Open D8H opened this issue 3 years ago • 4 comments

This behavior makes the platformer character automatically grabs platforms ledges when they are within reach.

Additional constraints can be added to:

  • make the grabbing range smaller
  • only grab ledges the character is facing

Note that it won't work with a custom collision mask, but it's not advised to use one with a platformer character anyway.

This extension tries to answer this ticket:

  • https://trello.com/c/zezNvn1J/269-add-an-option-to-automatically-grab-the-ledge-of-platforms

Example

  • Project: https://www.dropbox.com/s/7mz13em7a4dr05a/platformer-autograb.zip?dl=1
  • Build: https://liluo.io/instant-builds/f952fb6a-81b5-493d-a029-c8746c966d79

JavaScript

const behaviorName = eventsFunctionContext.getBehaviorName("Behavior");
const object = objects[0];
const behavior = object.getBehavior(behaviorName);

/** @type {gdjs.PlatformerObjectRuntimeBehavior} */
const character = object.getBehavior(behavior._getPlatformerCharacter());

const isUsingLeft = character.isUsingControl("Left");
const isUsingRight = character.isUsingControl("Right");

behavior.usedLeftLast = (behavior.usedLeftLast || isUsingLeft) && !isUsingRight;

if (!character.isFalling()
    || (isUsingLeft && !behavior._getIsMovingLeftAutomatically())
    || (isUsingRight && !behavior._getIsMovingRightAutomatically())) {
    return;
}
const wasMovingRightAutomatically = behavior._getIsMovingRightAutomatically();
const wasMovingLeftAutomatically = behavior._getIsMovingLeftAutomatically();
behavior._setIsMovingRightAutomatically(false);
behavior._setIsMovingLeftAutomatically(false);

/** @type {gdjs.PlatformObjectsManager} */
const manager = gdjs.PlatformObjectsManager.getManager(runtimeScene);
/** @type {float} */
const margin = behavior._getLedgeMarginX();
/** @type {boolean} */
const isOnlyGabbingFacingLedges = behavior._getIsOnlyGabbingFacingLedges();

if (!behavior.potentialGabbablePlatforms) {
    behavior.potentialGabbablePlatforms = [];
}
/** @type {PlatformRuntimeBehavior[]} */
const platforms = behavior.potentialGabbablePlatforms;
manager.getAllPlatformsAround(object, margin, platforms);

// Find the nearest platform that can be grabbed.
let platformDistanceMin = margin;
let shouldMoveLeft = false;
let shouldMoveRight = false;
const canGrabLeft = !isOnlyGabbingFacingLedges || !behavior.usedLeftLast;
const canGrabRight = !isOnlyGabbingFacingLedges || behavior.usedLeftLast;
const aabb = object.getAABB();
for (const platform of platforms) {
    /** @type {gdjs.AABB} */
    const platformAABB = platform.owner.getAABB();
    const characterIsAtTheLedgeLevel = aabb.max[1] > platformAABB.min[1] && aabb.min[1] <= platformAABB.min[1];
    if (characterIsAtTheLedgeLevel) {
        const leftDistance = platformAABB.min[0] - aabb.max[0];
        if (canGrabLeft && 0 <= leftDistance && leftDistance <= platformDistanceMin) {
            shouldMoveRight = true;
            shouldMoveLeft = false;
            platformDistanceMin = leftDistance;
        }
        const rightDistance = aabb.min[0] - platformAABB.max[0];
        if (canGrabRight && 0 <= rightDistance && rightDistance <= platformDistanceMin) {
            shouldMoveLeft = true;
            shouldMoveRight = false;
            platformDistanceMin = rightDistance;
        }
    }
}

// Don't try to move to a ledge that is too far to be actually grabbed.
let canReachLedge = false;
if (shouldMoveLeft || shouldMoveRight) {
    const alreadyDecided = wasMovingLeftAutomatically || wasMovingRightAutomatically;
    if (alreadyDecided) {
        canReachLedge = true;
    }
    else {
        // Check if the ledge is actually reachable
        const currentSpeedY = Math.abs(character.getCurrentFallSpeed() - character.getCurrentJumpSpeed());
        const timeDelta = object.getElapsedTime(runtimeScene) / 1000;
        // Rough approximation of the time frame
        const t = (aabb.max[0] - aabb.min[0]) / ((currentSpeedY + character.getMaxFallingSpeed()) / 2) - 2 * timeDelta;
        //console.log("time frame: " + t);

        const accelerationX = character.getAcceleration();
        const maxSpeedX = character.getMaxSpeed();
        const currentSpeedX = Math.abs(character.getCurrentSpeed());

        let displacementMaxX = 0;
        if (currentSpeedX === maxSpeedX) {
            displacementMaxX = maxSpeedX * t;
        }
        else {
            const maxSpeedXTime = Math.min(0, (maxSpeedX - currentSpeedX) / accelerationX);
            if (t < maxSpeedXTime) {
                displacementMaxX = currentSpeedX * t + accelerationX * t * t / 2
            }
            else {
                displacementMaxX = currentSpeedX * maxSpeedXTime
                    + accelerationX * maxSpeedXTime * maxSpeedXTime / 2
                    + maxSpeedX * (t - maxSpeedXTime);
            }
        }
        //console.log((platformDistanceMin - character._xGrabTolerance) + " < " + displacementMaxX);
        canReachLedge = platformDistanceMin - character._xGrabTolerance < displacementMaxX;
    }
}
// Move toward the ledge.
if (canReachLedge) {
    if (shouldMoveLeft) {
        //console.log("Move left");
        behavior._setIsMovingLeftAutomatically(true);
        character.simulateLeftKey();
    }
    else if (shouldMoveRight) {
        //console.log("Move right");
        behavior._setIsMovingRightAutomatically(true);
        character.simulateRightKey();
    }
}
// Need to hold the key one frame after reaching the ledge to grab it.
if (wasMovingLeftAutomatically && !shouldMoveRight) {
    character.simulateLeftKey();
}
else if (wasMovingRightAutomatically && !shouldMoveLeft) {
    character.simulateRightKey();
}

D8H avatar Jan 17 '22 16:01 D8H

This isn't better in the engine itself? On the PlatformerObject we have already a few settings about ledge grabbing. image

Bouh avatar Jan 17 '22 23:01 Bouh

This isn't better in the engine itself? On the PlatformerObject we have already a few settings about ledge grabbing.

Yes, we should at least add an option to grab without pressing Left or Right in the engine, but this extension also moves the character automatically toward the ledge and I'm not sure if this part should be in the engine. The huge benefit of having it in the engine would be to avoid this 2nd spacial search (which also breaks encapsulation a bit).

Do you think the whole extension should be integrated in the engine?

D8H avatar Jan 18 '22 14:01 D8H

Hello @D8H 👋

A review has started for this extension, but a new option has presented itself in the meantime. We now have a list of unreviewed "Community extensions", and your extension can be added to it, bypassing the review phase.

If you chose to not take a review, you need to keep the following in mind:

  • As of now, community extensions are not yet shown to users, but they should be until next update
  • Community extensions will never be shown to GDevelop users directly, they will have to check a checkbox for them to be shown
  • All community extension have a warning about them being unstable and unreviewed displayed on their store page
  • Your extension will be added immediately, no question asked, and without you needing to do any changes to your extension
  • You will still need to make an extension that passes the bot's tests (meaning you still should respect the extension best practices)

tl;dr You will not need to do any more (or only minimal) work on your extension, but it might not meet the standards of all users, and reach less users.

If you chose to continue on with the review:

  • The reviewing process might require of you to make some changes to your extension until it meets our reviewer's standards
  • Your extension will reach more users
  • You will be delivering a better extension to the community
  • It will take a bit more time for your extension to be added to the store

tl;dr You will need to do a bit more work but deliver a high quality contribution that will reach more people

The choice is yours, please tell us how you would like to continue.

arthuro555 avatar Jul 03 '22 14:07 arthuro555

I'll continue with the review process, but probably push it in the community folder too at some point.

D8H avatar Jul 03 '22 19:07 D8H

I think this extension doesn't worth a full review.

D8H avatar Sep 10 '23 15:09 D8H