mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-23 10:35:35 -04:00
a1b840a4fa
Allow a Platformer Object to simulate a "Release Ladder" action
1675 lines
50 KiB
TypeScript
1675 lines
50 KiB
TypeScript
/*
|
|
GDevelop - Platform Behavior Extension
|
|
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
|
*/
|
|
namespace gdjs {
|
|
/**
|
|
* PlatformerObjectRuntimeBehavior represents a behavior allowing objects to be
|
|
* considered as a platform by objects having PlatformerObject Behavior.
|
|
*/
|
|
export class PlatformerObjectRuntimeBehavior extends gdjs.RuntimeBehavior {
|
|
// To achieve pixel-perfect precision when positioning object on platform or
|
|
// handling collision with "walls", edges of the hitboxes must be ignored during
|
|
// collision checks, so that two overlapping edges are not considered as colliding.
|
|
// For example, if a character is 10px width and is at position (0, 0), it must not be
|
|
// considered as colliding with a platform which is at position (10, 0). Edges will
|
|
// still be overlapping (because character hitbox right edge is at X position 10 and
|
|
// platform hitbox left edge is also at X position 10).
|
|
// This parameter "_ignoreTouchingEdges" will be passed to all collision handling functions.
|
|
_ignoreTouchingEdges: boolean = true;
|
|
_roundCoordinates: boolean;
|
|
_gravity: float;
|
|
private _maxFallingSpeed: float;
|
|
_ladderClimbingSpeed: float;
|
|
private _acceleration: float;
|
|
private _deceleration: float;
|
|
private _maxSpeed: float;
|
|
_jumpSpeed: float;
|
|
_canGrabPlatforms: boolean;
|
|
private _yGrabOffset: any;
|
|
private _xGrabTolerance: any;
|
|
_jumpSustainTime: float;
|
|
_currentFallSpeed: float = 0;
|
|
private _currentSpeed: float = 0;
|
|
_canJump: boolean = false;
|
|
|
|
private _ignoreDefaultControls: boolean;
|
|
private _leftKey: boolean = false;
|
|
private _rightKey: boolean = false;
|
|
private _ladderKey: boolean = false;
|
|
_upKey: boolean = false;
|
|
_downKey: boolean = false;
|
|
_jumpKey: boolean = false;
|
|
_releasePlatformKey: boolean = false;
|
|
_releaseLadderKey: boolean = false;
|
|
|
|
private _state: State;
|
|
_falling: Falling;
|
|
_onFloor: OnFloor;
|
|
_jumping: Jumping;
|
|
_grabbingPlatform: GrabbingPlatform;
|
|
_onLadder: OnLadder;
|
|
|
|
/** Platforms near the object, updated with `_updatePotentialCollidingObjects`. */
|
|
_potentialCollidingObjects: Array<gdjs.PlatformRuntimeBehavior>;
|
|
|
|
/** Overlapped jump-thru platforms, updated with `_updateOverlappedJumpThru`. */
|
|
private _overlappedJumpThru: Array<gdjs.PlatformRuntimeBehavior>;
|
|
|
|
private _hasReallyMoved: boolean = false;
|
|
private _manager: gdjs.PlatformObjectsManager;
|
|
|
|
private _slopeMaxAngle: float;
|
|
_slopeClimbingFactor: float = 1;
|
|
|
|
_requestedDeltaX: float = 0;
|
|
_requestedDeltaY: float = 0;
|
|
_lastDeltaY: float = 0;
|
|
|
|
constructor(
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
behaviorData,
|
|
owner: gdjs.RuntimeObject
|
|
) {
|
|
super(runtimeScene, behaviorData, owner);
|
|
this._roundCoordinates = behaviorData.roundCoordinates;
|
|
this._gravity = behaviorData.gravity;
|
|
this._maxFallingSpeed = behaviorData.maxFallingSpeed;
|
|
this._ladderClimbingSpeed = behaviorData.ladderClimbingSpeed || 150;
|
|
this._acceleration = behaviorData.acceleration;
|
|
this._deceleration = behaviorData.deceleration;
|
|
this._maxSpeed = behaviorData.maxSpeed;
|
|
this._jumpSpeed = behaviorData.jumpSpeed;
|
|
this._canGrabPlatforms = behaviorData.canGrabPlatforms || false;
|
|
this._yGrabOffset = behaviorData.yGrabOffset || 0;
|
|
this._xGrabTolerance = behaviorData.xGrabTolerance || 10;
|
|
this._jumpSustainTime = behaviorData.jumpSustainTime || 0;
|
|
this._ignoreDefaultControls = behaviorData.ignoreDefaultControls;
|
|
this._potentialCollidingObjects = [];
|
|
|
|
this._overlappedJumpThru = [];
|
|
this._slopeMaxAngle = 0;
|
|
this.setSlopeMaxAngle(behaviorData.slopeMaxAngle);
|
|
this._manager = gdjs.PlatformObjectsManager.getManager(runtimeScene);
|
|
|
|
this._falling = new Falling(this);
|
|
this._onFloor = new OnFloor(this);
|
|
this._jumping = new Jumping(this);
|
|
this._grabbingPlatform = new GrabbingPlatform(this);
|
|
this._onLadder = new OnLadder(this);
|
|
this._state = this._falling;
|
|
}
|
|
|
|
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
|
if (
|
|
oldBehaviorData.roundCoordinates !== newBehaviorData.roundCoordinates
|
|
) {
|
|
this._roundCoordinates = newBehaviorData.roundCoordinates;
|
|
}
|
|
if (oldBehaviorData.gravity !== newBehaviorData.gravity) {
|
|
this.setGravity(newBehaviorData.gravity);
|
|
}
|
|
if (oldBehaviorData.maxFallingSpeed !== newBehaviorData.maxFallingSpeed) {
|
|
this.setMaxFallingSpeed(newBehaviorData.maxFallingSpeed);
|
|
}
|
|
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
|
|
this.setAcceleration(newBehaviorData.acceleration);
|
|
}
|
|
if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) {
|
|
this.setDeceleration(newBehaviorData.deceleration);
|
|
}
|
|
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
|
|
this.setMaxSpeed(newBehaviorData.maxSpeed);
|
|
}
|
|
if (oldBehaviorData.jumpSpeed !== newBehaviorData.jumpSpeed) {
|
|
this.setJumpSpeed(newBehaviorData.jumpSpeed);
|
|
}
|
|
if (
|
|
oldBehaviorData.canGrabPlatforms !== newBehaviorData.canGrabPlatforms
|
|
) {
|
|
this.setCanGrabPlatforms(newBehaviorData.canGrabPlatforms);
|
|
}
|
|
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
|
|
this._yGrabOffset = newBehaviorData.yGrabOffset;
|
|
}
|
|
if (oldBehaviorData.xGrabTolerance !== newBehaviorData.xGrabTolerance) {
|
|
this._xGrabTolerance = newBehaviorData.xGrabTolerance;
|
|
}
|
|
if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) {
|
|
this.setJumpSustainTime(newBehaviorData.jumpSustainTime);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
|
const LEFTKEY = 37;
|
|
const UPKEY = 38;
|
|
const RIGHTKEY = 39;
|
|
const DOWNKEY = 40;
|
|
const LSHIFTKEY = 1016;
|
|
const RSHIFTKEY = 2016;
|
|
const SPACEKEY = 32;
|
|
const object = this.owner;
|
|
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
|
|
|
//0.1) Get the player input:
|
|
this._requestedDeltaX = 0;
|
|
this._requestedDeltaY = 0;
|
|
|
|
const inputManager = runtimeScene.getGame().getInputManager();
|
|
this._leftKey ||
|
|
(this._leftKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(LEFTKEY));
|
|
this._rightKey ||
|
|
(this._rightKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(RIGHTKEY));
|
|
|
|
this._jumpKey ||
|
|
(this._jumpKey =
|
|
!this._ignoreDefaultControls &&
|
|
(inputManager.isKeyPressed(LSHIFTKEY) ||
|
|
inputManager.isKeyPressed(RSHIFTKEY) ||
|
|
inputManager.isKeyPressed(SPACEKEY)));
|
|
|
|
this._ladderKey ||
|
|
(this._ladderKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(UPKEY));
|
|
|
|
this._upKey ||
|
|
(this._upKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(UPKEY));
|
|
this._downKey ||
|
|
(this._downKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(DOWNKEY));
|
|
|
|
this._releasePlatformKey ||
|
|
(this._releasePlatformKey =
|
|
!this._ignoreDefaultControls && inputManager.isKeyPressed(DOWNKEY));
|
|
|
|
this._requestedDeltaX += this._updateSpeed(timeDelta);
|
|
|
|
//0.2) Track changes in object size
|
|
this._state.beforeUpdatingObstacles(timeDelta);
|
|
this._onFloor._oldHeight = object.getHeight();
|
|
|
|
//0.3) Update list of platforms around/related to the object
|
|
|
|
//Compute the list of the objects that will be used
|
|
this._updatePotentialCollidingObjects(
|
|
Math.max(this._requestedDeltaX, this._maxFallingSpeed * timeDelta)
|
|
);
|
|
this._updateOverlappedJumpThru();
|
|
|
|
//1) X axis:
|
|
this._state.checkTransitionBeforeX();
|
|
this._state.beforeMovingX();
|
|
|
|
//Ensure the object is not stuck
|
|
if (this._separateFromPlatforms(this._potentialCollidingObjects, true)) {
|
|
//After being unstuck, the object must be able to jump again.
|
|
this._canJump = true;
|
|
}
|
|
|
|
const oldX = object.getX();
|
|
this._moveX();
|
|
|
|
//2) Y axis:
|
|
this._state.checkTransitionBeforeY(timeDelta);
|
|
this._state.beforeMovingY(timeDelta, oldX);
|
|
|
|
const oldY = object.getY();
|
|
this._moveY();
|
|
|
|
//3) Update the current floor data for the next tick:
|
|
this._updateOverlappedJumpThru();
|
|
//TODO what about a moving platforms, remove this condition to do the same as for grabbing?
|
|
if (this._state !== this._onLadder) {
|
|
this._checkTransitionOnFloorOrFalling();
|
|
}
|
|
|
|
//4) Do not forget to reset pressed keys
|
|
this._leftKey = false;
|
|
this._rightKey = false;
|
|
this._ladderKey = false;
|
|
this._releaseLadderKey = false;
|
|
this._upKey = false;
|
|
this._downKey = false;
|
|
this._releasePlatformKey = false;
|
|
this._jumpKey = false;
|
|
|
|
//5) Track the movement
|
|
this._hasReallyMoved =
|
|
Math.abs(object.getX() - oldX) >= 1 ||
|
|
Math.abs(object.getY() - oldY) >= 1;
|
|
this._lastDeltaY = object.getY() - oldY;
|
|
}
|
|
|
|
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
|
|
|
private _updateSpeed(timeDelta: float): float {
|
|
//Change the speed according to the player's input.
|
|
// @ts-ignore
|
|
if (this._leftKey) {
|
|
this._currentSpeed -= this._acceleration * timeDelta;
|
|
}
|
|
if (this._rightKey) {
|
|
this._currentSpeed += this._acceleration * timeDelta;
|
|
}
|
|
|
|
//Take deceleration into account only if no key is pressed.
|
|
if (this._leftKey === this._rightKey) {
|
|
const wasPositive = this._currentSpeed > 0;
|
|
this._currentSpeed -=
|
|
this._deceleration * timeDelta * (wasPositive ? 1.0 : -1.0);
|
|
|
|
//Set the speed to 0 if the speed was too low.
|
|
if (wasPositive && this._currentSpeed < 0) {
|
|
this._currentSpeed = 0;
|
|
}
|
|
if (!wasPositive && this._currentSpeed > 0) {
|
|
this._currentSpeed = 0;
|
|
}
|
|
}
|
|
if (this._currentSpeed > this._maxSpeed) {
|
|
this._currentSpeed = this._maxSpeed;
|
|
}
|
|
if (this._currentSpeed < -this._maxSpeed) {
|
|
this._currentSpeed = -this._maxSpeed;
|
|
}
|
|
return this._currentSpeed * timeDelta;
|
|
}
|
|
|
|
private _moveX() {
|
|
const object = this.owner;
|
|
//Move the object on x axis.
|
|
const oldX = object.getX();
|
|
if (this._requestedDeltaX !== 0) {
|
|
const floorPlatformId =
|
|
this._onFloor.getFloorPlatform() !== null
|
|
? this._onFloor.getFloorPlatform()!.owner.id
|
|
: null;
|
|
object.setX(object.getX() + this._requestedDeltaX);
|
|
let tryRounding = true;
|
|
|
|
//Colliding: Try to push out from the solid.
|
|
//Note that jump thru are never obstacle on X axis.
|
|
while (
|
|
this._isCollidingWithOneOf(
|
|
this._potentialCollidingObjects,
|
|
floorPlatformId,
|
|
/*excludeJumpthrus=*/
|
|
true
|
|
)
|
|
) {
|
|
if (
|
|
(this._requestedDeltaX > 0 && object.getX() <= oldX) ||
|
|
(this._requestedDeltaX < 0 && object.getX() >= oldX)
|
|
) {
|
|
object.setX(
|
|
//Unable to move the object without being stuck in an obstacle.
|
|
oldX
|
|
);
|
|
break;
|
|
}
|
|
|
|
//If on floor: try get up a bit to bypass not perfectly aligned floors.
|
|
if (this._state === this._onFloor) {
|
|
object.setY(object.getY() - 1);
|
|
if (
|
|
!this._isCollidingWithOneOf(
|
|
this._potentialCollidingObjects,
|
|
floorPlatformId,
|
|
/*excludeJumpthrus=*/
|
|
true
|
|
)
|
|
) {
|
|
break;
|
|
}
|
|
object.setY(object.getY() + 1);
|
|
}
|
|
if (tryRounding) {
|
|
// First try rounding the position as this might be sufficient to get the object
|
|
// out of the wall.
|
|
object.setX(Math.round(object.getX()));
|
|
tryRounding = false;
|
|
} else {
|
|
object.setX(
|
|
Math.round(object.getX()) + (this._requestedDeltaX > 0 ? -1 : 1)
|
|
);
|
|
}
|
|
this._currentSpeed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private _moveY() {
|
|
const object = this.owner;
|
|
//Move the object on Y axis
|
|
if (this._requestedDeltaY !== 0) {
|
|
let oldY = object.getY();
|
|
object.setY(object.getY() + this._requestedDeltaY);
|
|
|
|
//Stop when colliding with an obstacle.
|
|
while (
|
|
(this._requestedDeltaY < 0 &&
|
|
this._isCollidingWithOneOf(
|
|
this._potentialCollidingObjects,
|
|
null,
|
|
/*excludeJumpThrus=*/
|
|
true
|
|
)) ||
|
|
//Jumpthru = obstacle <=> Never when going up
|
|
(this._requestedDeltaY > 0 &&
|
|
this._isCollidingWithOneOfExcluding(
|
|
this._potentialCollidingObjects,
|
|
this._overlappedJumpThru
|
|
))
|
|
) {
|
|
//Jumpthru = obstacle <=> Only if not already overlapped when going down
|
|
if (this._state === this._jumping) {
|
|
this._setFalling();
|
|
}
|
|
if (
|
|
(this._requestedDeltaY > 0 && object.getY() <= oldY) ||
|
|
(this._requestedDeltaY < 0 && object.getY() >= oldY)
|
|
) {
|
|
object.setY(
|
|
//Unable to move the object without being stuck in an obstacle.
|
|
oldY
|
|
);
|
|
break;
|
|
}
|
|
object.setY(
|
|
Math.floor(object.getY()) + (this._requestedDeltaY > 0 ? -1 : 1)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
_setFalling() {
|
|
this._state.leave();
|
|
this._state = this._falling;
|
|
this._falling.enter();
|
|
}
|
|
|
|
private _setOnFloor(collidingPlatform: PlatformRuntimeBehavior) {
|
|
this._state.leave();
|
|
this._state = this._onFloor;
|
|
this._onFloor.enter(collidingPlatform);
|
|
}
|
|
|
|
private _setJumping() {
|
|
this._state.leave();
|
|
const from = this._state;
|
|
this._state = this._jumping;
|
|
this._jumping.enter(from);
|
|
}
|
|
|
|
private _setGrabbingPlatform(grabbedPlatform: PlatformRuntimeBehavior) {
|
|
this._state.leave();
|
|
this._state = this._grabbingPlatform;
|
|
this._grabbingPlatform.enter(grabbedPlatform);
|
|
}
|
|
|
|
private _setOnLadder() {
|
|
this._state.leave();
|
|
this._state = this._onLadder;
|
|
this._onLadder.enter();
|
|
}
|
|
|
|
_checkTransitionOnLadder() {
|
|
if (this._ladderKey && this._isOverlappingLadder()) {
|
|
this._setOnLadder();
|
|
}
|
|
}
|
|
|
|
_checkTransitionJumping() {
|
|
if (this._canJump && this._jumpKey) {
|
|
this._setJumping();
|
|
}
|
|
}
|
|
|
|
_checkGrabPlatform() {
|
|
const object = this.owner;
|
|
|
|
let oldX = object.getX();
|
|
object.setX(
|
|
object.getX() +
|
|
(this._requestedDeltaX > 0
|
|
? this._xGrabTolerance
|
|
: -this._xGrabTolerance)
|
|
);
|
|
const collidingPlatforms: PlatformRuntimeBehavior[] = gdjs.staticArray(
|
|
PlatformerObjectRuntimeBehavior.prototype._checkGrabPlatform
|
|
);
|
|
collidingPlatforms.length = 0;
|
|
for (const platform of this._potentialCollidingObjects) {
|
|
if (this._isCollidingWith(platform) && this._canGrab(platform)) {
|
|
collidingPlatforms.push(platform);
|
|
}
|
|
}
|
|
object.setX(oldX);
|
|
|
|
//Check if we can grab the collided platform
|
|
let oldY = object.getY();
|
|
for (const collidingPlatform of collidingPlatforms) {
|
|
object.setY(
|
|
collidingPlatform.owner.getY() +
|
|
collidingPlatform.getYGrabOffset() -
|
|
this._yGrabOffset
|
|
);
|
|
if (
|
|
!this._isCollidingWithOneOf(
|
|
this._potentialCollidingObjects,
|
|
null,
|
|
/*excludeJumpthrus=*/
|
|
true
|
|
)
|
|
) {
|
|
this._setGrabbingPlatform(collidingPlatform);
|
|
this._requestedDeltaY = 0;
|
|
collidingPlatforms.length = 0;
|
|
return;
|
|
}
|
|
object.setY(oldY);
|
|
}
|
|
collidingPlatforms.length = 0;
|
|
}
|
|
|
|
private _checkTransitionOnFloorOrFalling() {
|
|
const object = this.owner;
|
|
//Check if the object is on a floor:
|
|
//In priority, check if the last floor platform is still the floor.
|
|
let oldY = object.getY();
|
|
object.setY(object.getY() + 1);
|
|
if (
|
|
this._state === this._onFloor &&
|
|
gdjs.RuntimeObject.collisionTest(
|
|
object,
|
|
this._onFloor.getFloorPlatform()!.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
//Still on the same floor
|
|
this._onFloor.updateFloorPosition();
|
|
} else {
|
|
// Avoid landing on a platform if the object is not going down.
|
|
// (which could happen for Jumpthru, when the object jump and pass just at the top
|
|
// of a jumpthru, it could be considered as landing if not this this extra check).
|
|
const canLand = this._requestedDeltaY >= 0;
|
|
|
|
//Check if landing on a new floor: (Exclude already overlapped jump thru)
|
|
let collidingPlatform = this._getCollidingPlatform();
|
|
if (canLand && collidingPlatform !== null) {
|
|
//Register the colliding platform as the floor.
|
|
this._setOnFloor(collidingPlatform);
|
|
} else if (this._state === this._onFloor) {
|
|
// don't fall if GrabbingPlatform or OnLadder
|
|
this._setFalling();
|
|
}
|
|
}
|
|
object.setY(oldY);
|
|
}
|
|
|
|
_fall(timeDelta: float) {
|
|
this._currentFallSpeed += this._gravity * timeDelta;
|
|
if (this._currentFallSpeed > this._maxFallingSpeed) {
|
|
this._currentFallSpeed = this._maxFallingSpeed;
|
|
}
|
|
this._requestedDeltaY += this._currentFallSpeed * timeDelta;
|
|
this._requestedDeltaY = Math.min(
|
|
this._requestedDeltaY,
|
|
this._maxFallingSpeed * timeDelta
|
|
);
|
|
}
|
|
|
|
//Scene change is not supported
|
|
/*
|
|
if ( parentScene != &scene ) //Parent scene has changed
|
|
{
|
|
parentScene = &scene;
|
|
sceneManager = parentScene ? &ScenePlatformObjectsManager::managers[&scene] : null;
|
|
floorPlatform = null;
|
|
}
|
|
*/
|
|
/**
|
|
* Return true if the object owning the behavior can grab the specified platform. There must be a collision
|
|
* between the object and the platform.
|
|
* @param platform The platform the object is in collision with
|
|
* @param y The value in pixels on Y axis the object wants to move to
|
|
*/
|
|
private _canGrab(platform: gdjs.PlatformRuntimeBehavior) {
|
|
const y1 = this.owner.getY() + this._yGrabOffset - this._lastDeltaY;
|
|
const y2 = this.owner.getY() + this._yGrabOffset;
|
|
const platformY = platform.owner.getY() + platform.getYGrabOffset();
|
|
return (
|
|
platform.canBeGrabbed() &&
|
|
((y1 < platformY && platformY < y2) ||
|
|
(y2 < platformY && platformY < y1))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mark the platformer object as not grabbing any platform.
|
|
*/
|
|
_releaseGrabbedPlatform() {
|
|
if (this._state === this._grabbingPlatform) {
|
|
this._setFalling();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark the platformer object as falling if on a ladder.
|
|
*/
|
|
_releaseLadder() {
|
|
if (this._state === this._onLadder) {
|
|
this._setFalling();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Separate the object from all platforms passed in parameter.
|
|
* @param candidates The platform to be tested for collision
|
|
* @param excludeJumpThrus If set to true, jumpthru platforms are excluded. false if not defined.
|
|
*/
|
|
private _separateFromPlatforms(
|
|
candidates: gdjs.PlatformRuntimeBehavior[],
|
|
excludeJumpThrus: boolean
|
|
) {
|
|
excludeJumpThrus = !!excludeJumpThrus;
|
|
const objects = gdjs.staticArray(
|
|
PlatformerObjectRuntimeBehavior.prototype._separateFromPlatforms
|
|
);
|
|
objects.length = 0;
|
|
for (let i = 0; i < candidates.length; ++i) {
|
|
const platform = candidates[i];
|
|
if (
|
|
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.LADDER
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
excludeJumpThrus &&
|
|
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.JUMPTHRU
|
|
) {
|
|
continue;
|
|
}
|
|
objects.push(platform.owner);
|
|
}
|
|
return this.owner.separateFromObjects(objects, this._ignoreTouchingEdges);
|
|
}
|
|
|
|
/**
|
|
* Among the platforms passed in parameter, return true if there is a platform colliding with the object.
|
|
* Ladders are *always* excluded from the test.
|
|
* @param candidates The platform to be tested for collision
|
|
* @param exceptThisOne The object identifier of a platform to be excluded from the check. Can be null.
|
|
* @param excludeJumpThrus If set to true, jumpthru platforms are excluded. false if not defined.
|
|
*/
|
|
_isCollidingWithOneOf(
|
|
candidates: gdjs.PlatformRuntimeBehavior[],
|
|
exceptThisOne?: number | null,
|
|
excludeJumpThrus?: boolean
|
|
) {
|
|
excludeJumpThrus = !!excludeJumpThrus;
|
|
for (let i = 0; i < candidates.length; ++i) {
|
|
const platform = candidates[i];
|
|
if (platform.owner.id === exceptThisOne) {
|
|
continue;
|
|
}
|
|
if (
|
|
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.LADDER
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
excludeJumpThrus &&
|
|
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.JUMPTHRU
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Among the platforms passed in parameter, return true if there is a platform colliding with the object.
|
|
* Ladders are *always* excluded from the test.
|
|
* @param candidates The platform to be tested for collision
|
|
* @param exceptTheseOnes The platforms to be excluded from the test
|
|
*/
|
|
private _isCollidingWithOneOfExcluding(
|
|
candidates: gdjs.PlatformRuntimeBehavior[],
|
|
exceptTheseOnes: gdjs.PlatformRuntimeBehavior[]
|
|
) {
|
|
for (let i = 0; i < candidates.length; ++i) {
|
|
const platform = candidates[i];
|
|
if (exceptTheseOnes && this._isIn(exceptTheseOnes, platform.owner.id)) {
|
|
continue;
|
|
}
|
|
if (
|
|
platform.getPlatformType() === gdjs.PlatformRuntimeBehavior.LADDER
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return (one of) the platform which is colliding with the behavior owner object.
|
|
* Overlapped jump thru and ladders are excluded.
|
|
* _updatePotentialCollidingObjects and _updateOverlappedJumpThru should have been called before.
|
|
*/
|
|
private _getCollidingPlatform() {
|
|
for (let i = 0; i < this._potentialCollidingObjects.length; ++i) {
|
|
const platform = this._potentialCollidingObjects[i];
|
|
if (
|
|
platform.getPlatformType() !== gdjs.PlatformRuntimeBehavior.LADDER &&
|
|
!this._isIn(this._overlappedJumpThru, platform.owner.id) &&
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
return platform;
|
|
}
|
|
}
|
|
|
|
//Nothing is being colliding with the behavior object.
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return true if the platform is colliding with the behavior owner object.
|
|
* Overlapped jump thru and ladders are excluded.
|
|
* @param platform The platform to be tested for collision
|
|
*/
|
|
private _isCollidingWith(platform: gdjs.PlatformRuntimeBehavior): boolean {
|
|
return (
|
|
platform.getPlatformType() !== gdjs.PlatformRuntimeBehavior.LADDER &&
|
|
!this._isIn(this._overlappedJumpThru, platform.owner.id) &&
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update _overlappedJumpThru member, so that it contains all the jumpthru platforms colliding with
|
|
* the behavior owner object.
|
|
* Note: _updatePotentialCollidingObjects must have been called before.
|
|
*/
|
|
private _updateOverlappedJumpThru() {
|
|
this._overlappedJumpThru.length = 0;
|
|
for (let i = 0; i < this._potentialCollidingObjects.length; ++i) {
|
|
const platform = this._potentialCollidingObjects[i];
|
|
if (
|
|
platform.getPlatformType() ===
|
|
gdjs.PlatformRuntimeBehavior.JUMPTHRU &&
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
this._overlappedJumpThru.push(platform);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the object is overlapping a ladder.
|
|
* Note: _updatePotentialCollidingObjects must have been called before.
|
|
*/
|
|
_isOverlappingLadder() {
|
|
for (let i = 0; i < this._potentialCollidingObjects.length; ++i) {
|
|
const platform = this._potentialCollidingObjects[i];
|
|
if (
|
|
platform.getPlatformType() !== gdjs.PlatformRuntimeBehavior.LADDER
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
this.owner,
|
|
platform.owner,
|
|
this._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_isIn(platformArray: gdjs.PlatformRuntimeBehavior[], id: integer) {
|
|
for (let i = 0; i < platformArray.length; ++i) {
|
|
if (platformArray[i].owner.id === id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Update _potentialCollidingObjects member with platforms near the object.
|
|
*/
|
|
private _updatePotentialCollidingObjects(maxMovementLength: float) {
|
|
this._manager.getAllPlatformsAround(
|
|
this.owner,
|
|
maxMovementLength,
|
|
this._potentialCollidingObjects
|
|
);
|
|
|
|
// Filter the potential colliding platforms to ensure that the object owning the behavior
|
|
// is not considered as colliding with itself, in the case that it also has the
|
|
// platform behavior.
|
|
for (let i = 0; i < this._potentialCollidingObjects.length; ) {
|
|
if (this._potentialCollidingObjects[i].owner === this.owner) {
|
|
this._potentialCollidingObjects.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulate a control action in the Platformer Object by specifying an input.
|
|
* @param input The string expression of the control action [Left,Right,Up,Down,Ladder,Jump,Release,Release Ladder].
|
|
*/
|
|
simulateControl(input: string) {
|
|
if (input === 'Left') {
|
|
this._leftKey = true;
|
|
} else if (input === 'Right') {
|
|
this._rightKey = true;
|
|
} else if (input === 'Up') {
|
|
this._upKey = true;
|
|
} else if (input === 'Down') {
|
|
this._downKey = true;
|
|
} else if (input === 'Ladder') {
|
|
this._ladderKey = true;
|
|
} else if (input === 'Jump') {
|
|
this._jumpKey = true;
|
|
} else if (input === 'Release') {
|
|
this._releasePlatformKey = true;
|
|
} else if (input === 'Release Ladder') {
|
|
this._releaseLadderKey = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the gravity of the Platformer Object.
|
|
* @returns The current gravity.
|
|
*/
|
|
getGravity(): float {
|
|
return this._gravity;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum falling speed of the Platformer Object.
|
|
* @returns The maximum falling speed.
|
|
*/
|
|
getMaxFallingSpeed(): float {
|
|
return this._maxFallingSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the speed used to move on Y axis when climbing a ladder.
|
|
* @returns The speed of ladder climbing.
|
|
*/
|
|
getLadderClimbingSpeed(): float {
|
|
return this._ladderClimbingSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the acceleration value of the Platformer Object.
|
|
* @returns The current acceleration.
|
|
*/
|
|
getAcceleration(): float {
|
|
return this._acceleration;
|
|
}
|
|
|
|
/**
|
|
* Get the deceleration of the Platformer Object.
|
|
* @returns The current deceleration.
|
|
*/
|
|
getDeceleration(): float {
|
|
return this._deceleration;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum speed of the Platformer Object.
|
|
* @returns The maximum speed.
|
|
*/
|
|
getMaxSpeed(): float {
|
|
return this._maxSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the jump speed of the Platformer Object.
|
|
* @returns The jump speed.
|
|
*/
|
|
getJumpSpeed(): float {
|
|
return this._jumpSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the jump sustain time of the Platformer Object.
|
|
* @returns The jump sustain time.
|
|
*/
|
|
getJumpSustainTime(): float {
|
|
return this._jumpSustainTime;
|
|
}
|
|
|
|
/**
|
|
* Get the speed at which the object is falling. It is 0 when the object is on a floor, and non 0 as soon as the object leaves the floor.
|
|
* @returns The current fall speed.
|
|
*/
|
|
getCurrentFallSpeed(): float {
|
|
return this._currentFallSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the current speed of the Platformer Object.
|
|
* @returns The current speed.
|
|
*/
|
|
getCurrentSpeed(): float {
|
|
return this._currentSpeed;
|
|
}
|
|
|
|
/**
|
|
* Get the current jump speed of the Platformer Object.
|
|
* @returns The current jump speed.
|
|
*/
|
|
getCurrentJumpSpeed(): float {
|
|
return this._jumping.getCurrentJumpSpeed();
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object can grab the platforms.
|
|
* @returns Returns true if the object can grab the platforms.
|
|
*/
|
|
canGrabPlatforms(): boolean {
|
|
return this._canGrabPlatforms;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object can jump.
|
|
* @returns Returns true if the object can jump.
|
|
*/
|
|
canJump(): boolean {
|
|
return this._canJump;
|
|
}
|
|
|
|
/**
|
|
* Set the gravity of the Platformer Object.
|
|
* @param gravity The new gravity.
|
|
*/
|
|
setGravity(gravity: float): void {
|
|
this._gravity = gravity;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum falling speed of the Platformer Object.
|
|
* @param maxFallingSpeed The maximum falling speed.
|
|
*/
|
|
setMaxFallingSpeed(maxFallingSpeed: float): void {
|
|
this._maxFallingSpeed = maxFallingSpeed;
|
|
}
|
|
|
|
/**
|
|
* Set the speed used to move on Y axis when climbing a ladder.
|
|
* @param ladderClimbingSpeed The speed of ladder climbing.
|
|
*/
|
|
setLadderClimbingSpeed(ladderClimbingSpeed: float): void {
|
|
this._ladderClimbingSpeed = ladderClimbingSpeed;
|
|
}
|
|
|
|
/**
|
|
* Set the acceleration of the Platformer Object.
|
|
* @param acceleration The new acceleration.
|
|
*/
|
|
setAcceleration(acceleration: float): void {
|
|
this._acceleration = acceleration;
|
|
}
|
|
|
|
/**
|
|
* Set the deceleration of the Platformer Object.
|
|
* @param deceleration The new deceleration.
|
|
*/
|
|
setDeceleration(deceleration: float): void {
|
|
this._deceleration = deceleration;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum speed of the Platformer Object.
|
|
* @param maxSpeed The new maximum speed.
|
|
*/
|
|
setMaxSpeed(maxSpeed: float): void {
|
|
this._maxSpeed = maxSpeed;
|
|
}
|
|
|
|
/**
|
|
* Set the jump speed of the Platformer Object.
|
|
* @param jumpSpeed The new jump speed.
|
|
*/
|
|
setJumpSpeed(jumpSpeed: float): void {
|
|
this._jumpSpeed = jumpSpeed;
|
|
}
|
|
|
|
/**
|
|
* Set the jump sustain time of the Platformer Object.
|
|
* @param jumpSpeed The new jump sustain time.
|
|
*/
|
|
setJumpSustainTime(jumpSustainTime: float): void {
|
|
this._jumpSustainTime = jumpSustainTime;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum slope angle of the Platformer Object.
|
|
* @param slopeMaxAngle The new maximum slope angle.
|
|
*/
|
|
setSlopeMaxAngle(slopeMaxAngle: float): void {
|
|
if (slopeMaxAngle < 0 || slopeMaxAngle >= 90) {
|
|
return;
|
|
}
|
|
this._slopeMaxAngle = slopeMaxAngle;
|
|
|
|
//Avoid rounding errors
|
|
if (slopeMaxAngle === 45) {
|
|
this._slopeClimbingFactor = 1;
|
|
} else {
|
|
this._slopeClimbingFactor = Math.tan(
|
|
(slopeMaxAngle * 3.1415926) / 180.0
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allow the Platformer Object to jump again.
|
|
*/
|
|
setCanJump(): void {
|
|
this._canJump = true;
|
|
}
|
|
|
|
/**
|
|
* Forbid the Platformer Object to air jump.
|
|
*/
|
|
setCanNotAirJump(): void {
|
|
if (this._state === this._jumping || this._state === this._falling) {
|
|
this._canJump = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set if the Platformer Object can grab platforms.
|
|
* @param enable Enable / Disable grabbing of platforms.
|
|
*/
|
|
setCanGrabPlatforms(enable: boolean): void {
|
|
this._canGrabPlatforms = enable;
|
|
if (!this._canGrabPlatforms) {
|
|
this._releaseGrabbedPlatform();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ignore the default controls of the Platformer Object.
|
|
* @param ignore Enable / Disable default controls.
|
|
*/
|
|
ignoreDefaultControls(ignore: boolean) {
|
|
this._ignoreDefaultControls = ignore;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Left" control of the Platformer Object.
|
|
*/
|
|
simulateLeftKey() {
|
|
this._leftKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Right" control of the Platformer Object.
|
|
*/
|
|
simulateRightKey() {
|
|
this._rightKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Ladder" control of the Platformer Object.
|
|
*/
|
|
simulateLadderKey() {
|
|
this._ladderKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Release Ladder" control of the Platformer Object.
|
|
*/
|
|
simulateReleaseLadderKey() {
|
|
this._releaseLadderKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Up" control of the Platformer Object.
|
|
*/
|
|
simulateUpKey() {
|
|
this._upKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Down" control of the Platformer Object.
|
|
*/
|
|
simulateDownKey() {
|
|
this._downKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Jump" control of the Platformer Object.
|
|
*/
|
|
simulateJumpKey() {
|
|
this._jumpKey = true;
|
|
}
|
|
|
|
/**
|
|
* Simulate the "Release" control of the Platformer Object.
|
|
*/
|
|
simulateReleasePlatformKey() {
|
|
this._releasePlatformKey = true;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is on the floor.
|
|
* @returns Returns true if on the floor and false if not.
|
|
*/
|
|
isOnFloor(): boolean {
|
|
return this._state === this._onFloor;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is on a ladder.
|
|
* @returns Returns true if on a ladder and false if not.
|
|
*/
|
|
isOnLadder(): boolean {
|
|
return this._state === this._onLadder;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is jumping.
|
|
* @returns Returns true if jumping and false if not.
|
|
*/
|
|
isJumping(): boolean {
|
|
return this._state === this._jumping;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is grabbing a platform.
|
|
* @returns Returns true if a platform is grabbed and false if not.
|
|
*/
|
|
isGrabbingPlatform(): boolean {
|
|
return this._state === this._grabbingPlatform;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is in the falling state. This is false
|
|
* if the object is jumping, even if the object is going down after reaching
|
|
* the jump peak.
|
|
* @returns Returns true if it is falling and false if not.
|
|
*/
|
|
isFallingWithoutJumping(): boolean {
|
|
return this._state === this._falling;
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is "going down", either because it's in the
|
|
* falling state *or* because it's jumping but reached the jump peak and
|
|
* is now going down (because the jump speed can't compensate anymore the
|
|
* falling speed).
|
|
*
|
|
* If you want to check if the object is falling outside of a jump (or because
|
|
* the jump is entirely finished and there is no jump speed applied to the object
|
|
* anymore), consider using `isFallingWithoutJumping`.
|
|
*
|
|
* @returns Returns true if it is "going down" and false if not.
|
|
*/
|
|
isFalling(): boolean {
|
|
return (
|
|
this._state === this._falling ||
|
|
(this._state === this._jumping &&
|
|
this._currentFallSpeed > this._jumping.getCurrentJumpSpeed())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if the Platformer Object is moving.
|
|
* @returns Returns true if it is moving and false if not.
|
|
*/
|
|
isMoving(): boolean {
|
|
return (
|
|
(this._hasReallyMoved &&
|
|
(this._currentSpeed !== 0 || this._state === this._onLadder)) ||
|
|
this._jumping.getCurrentJumpSpeed() !== 0 ||
|
|
this._currentFallSpeed !== 0
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The object can take 5 states: OnFloor, Falling, Jumping, GrabbingPlatform and OnLadder.
|
|
* The implementations of this interface hold the specific behaviors and internal state of theses 5 states.
|
|
* @see PlatformerObjectRuntimeBehavior.doStepPreEvents to understand how the functions are called.
|
|
*/
|
|
interface State {
|
|
/**
|
|
* Called when the object leaves this state.
|
|
* It's a good place to reset the internal state.
|
|
* @see OnFloor.enter that is not part of the interface because it takes specific parameters.
|
|
*/
|
|
leave(): void;
|
|
/**
|
|
* Called before the obstacle search.
|
|
* The object position may need adjustments to handle external changes.
|
|
*/
|
|
beforeUpdatingObstacles(timeDelta: float): void;
|
|
/**
|
|
* Check if transitions to other states are needed and apply them before moving horizontally.
|
|
*/
|
|
checkTransitionBeforeX(): void;
|
|
/**
|
|
* Use _requestedDeltaX and _requestedDeltaY to choose the movement that suits the state before moving horizontally.
|
|
*/
|
|
beforeMovingX(): void;
|
|
/**
|
|
* Check if transitions to other states are needed and apply them before moving vertically.
|
|
*/
|
|
checkTransitionBeforeY(timeDelta: float): void;
|
|
/**
|
|
* Use _requestedDeltaY to choose the movement that suits the state before moving vertically.
|
|
*/
|
|
beforeMovingY(timeDelta: float, oldX: float): void;
|
|
}
|
|
|
|
/**
|
|
* The object is on the floor standing or walking.
|
|
*/
|
|
class OnFloor implements State {
|
|
private _behavior: PlatformerObjectRuntimeBehavior;
|
|
private _floorPlatform: PlatformRuntimeBehavior | null = null;
|
|
private _floorLastX: float = 0;
|
|
private _floorLastY: float = 0;
|
|
_oldHeight: float = 0;
|
|
|
|
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
|
this._behavior = behavior;
|
|
}
|
|
|
|
getFloorPlatform() {
|
|
return this._floorPlatform;
|
|
}
|
|
|
|
enter(floorPlatform: PlatformRuntimeBehavior) {
|
|
this._floorPlatform = floorPlatform;
|
|
this.updateFloorPosition();
|
|
this._behavior._canJump = true;
|
|
this._behavior._currentFallSpeed = 0;
|
|
}
|
|
|
|
leave() {
|
|
this._floorPlatform = null;
|
|
}
|
|
|
|
updateFloorPosition() {
|
|
this._floorLastX = this._floorPlatform!.owner.getX();
|
|
this._floorLastY = this._floorPlatform!.owner.getY();
|
|
}
|
|
|
|
beforeUpdatingObstacles(timeDelta: float) {
|
|
const object = this._behavior.owner;
|
|
//Stick the object to the floor if its height has changed.
|
|
if (this._oldHeight !== object.getHeight()) {
|
|
object.setY(
|
|
this._floorLastY -
|
|
object.getHeight() +
|
|
(object.getY() - object.getDrawableY()) -
|
|
1
|
|
);
|
|
}
|
|
}
|
|
|
|
checkTransitionBeforeX() {
|
|
const behavior = this._behavior;
|
|
//Check that the floor object still exists and is near the object.
|
|
if (
|
|
!behavior._isIn(
|
|
behavior._potentialCollidingObjects,
|
|
this._floorPlatform!.owner.id
|
|
)
|
|
) {
|
|
behavior._setFalling();
|
|
}
|
|
}
|
|
|
|
beforeMovingX() {
|
|
const behavior = this._behavior;
|
|
//Shift the object according to the floor movement.
|
|
behavior._requestedDeltaX +=
|
|
this._floorPlatform!.owner.getX() - this._floorLastX;
|
|
behavior._requestedDeltaY +=
|
|
this._floorPlatform!.owner.getY() - this._floorLastY;
|
|
}
|
|
|
|
checkTransitionBeforeY(timeDelta: float) {
|
|
const behavior = this._behavior;
|
|
//Go on a ladder
|
|
behavior._checkTransitionOnLadder();
|
|
//Jumping
|
|
behavior._checkTransitionJumping();
|
|
}
|
|
|
|
beforeMovingY(timeDelta: float, oldX: float) {
|
|
const behavior = this._behavior;
|
|
const object = behavior.owner;
|
|
|
|
//Follow the floor
|
|
if (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
object,
|
|
this._floorPlatform!.owner,
|
|
behavior._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
//Floor is getting up, as the object is colliding with it.
|
|
let oldY = object.getY();
|
|
let step = 0;
|
|
let stillInFloor = false;
|
|
do {
|
|
if (
|
|
step >=
|
|
Math.floor(
|
|
Math.abs(
|
|
behavior._requestedDeltaX * behavior._slopeClimbingFactor
|
|
)
|
|
)
|
|
) {
|
|
//Slope is too step ( > 50% )
|
|
object.setY(
|
|
object.getY() -
|
|
(Math.abs(
|
|
behavior._requestedDeltaX * behavior._slopeClimbingFactor
|
|
) -
|
|
step)
|
|
);
|
|
|
|
//Try to add the decimal part.
|
|
if (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
object,
|
|
this._floorPlatform!.owner,
|
|
behavior._ignoreTouchingEdges
|
|
)
|
|
) {
|
|
stillInFloor = true;
|
|
}
|
|
|
|
//Too steep.
|
|
break;
|
|
}
|
|
|
|
//Try to get out of the floor.
|
|
object.setY(object.getY() - 1);
|
|
step++;
|
|
} while (
|
|
gdjs.RuntimeObject.collisionTest(
|
|
object,
|
|
this._floorPlatform!.owner,
|
|
behavior._ignoreTouchingEdges
|
|
)
|
|
);
|
|
if (stillInFloor) {
|
|
object.setY(
|
|
//Unable to follow the floor ( too steep ): Go back to the original position.
|
|
oldY
|
|
);
|
|
object.setX(
|
|
//And also revert the shift on X axis.
|
|
oldX
|
|
);
|
|
}
|
|
} else {
|
|
//Floor is flat or get down.
|
|
let oldY = object.getY();
|
|
const tentativeStartY = object.getY() + 1;
|
|
object.setY(
|
|
behavior._roundCoordinates
|
|
? Math.round(tentativeStartY)
|
|
: tentativeStartY
|
|
);
|
|
let step = 0;
|
|
let noMoreOnFloor = false;
|
|
while (
|
|
!behavior._isCollidingWithOneOf(behavior._potentialCollidingObjects)
|
|
) {
|
|
if (
|
|
step >
|
|
Math.abs(behavior._requestedDeltaX * behavior._slopeClimbingFactor)
|
|
) {
|
|
//Slope is too step ( > 50% )
|
|
noMoreOnFloor = true;
|
|
break;
|
|
}
|
|
|
|
//Object was on floor, but no more: Maybe a slope, try to follow it.
|
|
object.setY(object.getY() + 1);
|
|
step++;
|
|
}
|
|
|
|
//Unable to follow the floor: Go back to the original position. Fall will be triggered next tick.
|
|
if (noMoreOnFloor) {
|
|
object.setY(oldY);
|
|
} else {
|
|
object.setY(
|
|
//Floor touched: Go back 1 pixel over.
|
|
object.getY() - 1
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
toString(): String {
|
|
return 'OnFloor';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The object is falling.
|
|
*/
|
|
class Falling implements State {
|
|
private _behavior: PlatformerObjectRuntimeBehavior;
|
|
|
|
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
|
this._behavior = behavior;
|
|
}
|
|
|
|
enter() {
|
|
this._behavior._canJump = false;
|
|
}
|
|
|
|
leave() {}
|
|
|
|
beforeUpdatingObstacles(timeDelta: float) {}
|
|
|
|
checkTransitionBeforeX() {}
|
|
|
|
beforeMovingX() {}
|
|
|
|
checkTransitionBeforeY(timeDelta: float) {
|
|
const behavior = this._behavior;
|
|
//Go on a ladder
|
|
behavior._checkTransitionOnLadder();
|
|
//Jumping
|
|
behavior._checkTransitionJumping();
|
|
|
|
//Grabbing a platform
|
|
if (behavior._canGrabPlatforms && behavior._requestedDeltaX !== 0) {
|
|
behavior._checkGrabPlatform();
|
|
}
|
|
}
|
|
|
|
beforeMovingY(timeDelta: float, oldX: float) {
|
|
//Fall
|
|
this._behavior._fall(timeDelta);
|
|
}
|
|
|
|
toString(): String {
|
|
return 'Falling';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The object is on the ascending and descending part of the jump.
|
|
* The object is considered falling when the jump continue to a lower position than the initial one.
|
|
*/
|
|
class Jumping implements State {
|
|
private _behavior: PlatformerObjectRuntimeBehavior;
|
|
private _currentJumpSpeed: number = 0;
|
|
private _timeSinceCurrentJumpStart: number = 0;
|
|
private _jumpKeyHeldSinceJumpStart: boolean = false;
|
|
private _jumpingFirstDelta: boolean = false;
|
|
|
|
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
|
this._behavior = behavior;
|
|
}
|
|
|
|
getCurrentJumpSpeed() {
|
|
return this._currentJumpSpeed;
|
|
}
|
|
|
|
enter(from: State) {
|
|
const behavior = this._behavior;
|
|
this._timeSinceCurrentJumpStart = 0;
|
|
this._jumpKeyHeldSinceJumpStart = true;
|
|
|
|
if (from !== behavior._jumping && from !== behavior._falling) {
|
|
this._jumpingFirstDelta = true;
|
|
}
|
|
|
|
behavior._canJump = false;
|
|
this._currentJumpSpeed = behavior._jumpSpeed;
|
|
behavior._currentFallSpeed = 0;
|
|
}
|
|
|
|
leave() {
|
|
this._currentJumpSpeed = 0;
|
|
}
|
|
|
|
beforeUpdatingObstacles(timeDelta: float) {}
|
|
|
|
checkTransitionBeforeX() {}
|
|
|
|
beforeMovingX() {}
|
|
|
|
checkTransitionBeforeY(timeDelta: float) {
|
|
const behavior = this._behavior;
|
|
//Go on a ladder
|
|
behavior._checkTransitionOnLadder();
|
|
//Jumping
|
|
behavior._checkTransitionJumping();
|
|
|
|
//Grabbing a platform
|
|
if (
|
|
behavior._canGrabPlatforms &&
|
|
behavior._requestedDeltaX !== 0 &&
|
|
behavior._lastDeltaY >= 0
|
|
) {
|
|
behavior._checkGrabPlatform();
|
|
}
|
|
}
|
|
|
|
beforeMovingY(timeDelta: float, oldX: float) {
|
|
const behavior = this._behavior;
|
|
|
|
//Fall
|
|
if (!this._jumpingFirstDelta) {
|
|
behavior._fall(timeDelta);
|
|
}
|
|
this._jumpingFirstDelta = false;
|
|
|
|
// Check if the jump key is continuously held since
|
|
// the beginning of the jump.
|
|
if (!behavior._jumpKey) {
|
|
this._jumpKeyHeldSinceJumpStart = false;
|
|
}
|
|
this._timeSinceCurrentJumpStart += timeDelta;
|
|
behavior._requestedDeltaY -= this._currentJumpSpeed * timeDelta;
|
|
|
|
// Decrease jump speed after the (optional) jump sustain time is over.
|
|
const sustainJumpSpeed =
|
|
this._jumpKeyHeldSinceJumpStart &&
|
|
this._timeSinceCurrentJumpStart < behavior._jumpSustainTime;
|
|
if (!sustainJumpSpeed) {
|
|
this._currentJumpSpeed -= behavior._gravity * timeDelta;
|
|
}
|
|
if (this._currentJumpSpeed < 0) {
|
|
behavior._setFalling();
|
|
}
|
|
}
|
|
|
|
toString(): String {
|
|
return 'Jumping';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The object grabbed the edge of a platform and is standing there.
|
|
*/
|
|
class GrabbingPlatform implements State {
|
|
private _behavior: PlatformerObjectRuntimeBehavior;
|
|
private _grabbedPlatform: any = null;
|
|
private _grabbedPlatformLastX: any;
|
|
private _grabbedPlatformLastY: any;
|
|
|
|
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
|
this._behavior = behavior;
|
|
}
|
|
|
|
enter(grabbedPlatform: PlatformRuntimeBehavior) {
|
|
this._grabbedPlatform = grabbedPlatform;
|
|
this._behavior._canJump = true;
|
|
this._behavior._currentFallSpeed = 0;
|
|
}
|
|
|
|
leave() {
|
|
this._grabbedPlatform = null;
|
|
}
|
|
|
|
beforeUpdatingObstacles(timeDelta: float) {}
|
|
|
|
checkTransitionBeforeX() {
|
|
const behavior = this._behavior;
|
|
//Check that the grabbed platform object still exists and is near the object.
|
|
if (
|
|
!behavior._isIn(
|
|
behavior._potentialCollidingObjects,
|
|
this._grabbedPlatform.owner.id
|
|
)
|
|
) {
|
|
behavior._releaseGrabbedPlatform();
|
|
}
|
|
}
|
|
|
|
beforeMovingX() {
|
|
const behavior = this._behavior;
|
|
//Shift the object according to the grabbed platform movement.
|
|
// this erases any other movement
|
|
behavior._requestedDeltaX =
|
|
this._grabbedPlatform.owner.getX() - this._grabbedPlatformLastX;
|
|
behavior._requestedDeltaY =
|
|
this._grabbedPlatform.owner.getY() - this._grabbedPlatformLastY;
|
|
}
|
|
|
|
checkTransitionBeforeY(timeDelta: float) {
|
|
const behavior = this._behavior;
|
|
//Go on a ladder
|
|
behavior._checkTransitionOnLadder();
|
|
|
|
//Release the platform
|
|
if (behavior._releasePlatformKey) {
|
|
behavior._releaseGrabbedPlatform();
|
|
}
|
|
|
|
//Jumping
|
|
behavior._checkTransitionJumping();
|
|
}
|
|
|
|
beforeMovingY(timeDelta: float, oldX: float) {
|
|
this._grabbedPlatformLastX = this._grabbedPlatform.owner.getX();
|
|
this._grabbedPlatformLastY = this._grabbedPlatform.owner.getY();
|
|
}
|
|
|
|
toString(): String {
|
|
return 'GrabbingPlatform';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The object grabbed a ladder. It can stand or move in 8 directions.
|
|
*/
|
|
class OnLadder implements State {
|
|
private _behavior: PlatformerObjectRuntimeBehavior;
|
|
|
|
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
|
this._behavior = behavior;
|
|
}
|
|
|
|
enter() {
|
|
this._behavior._canJump = true;
|
|
this._behavior._currentFallSpeed = 0;
|
|
}
|
|
|
|
leave() {}
|
|
|
|
beforeUpdatingObstacles(timeDelta: float) {}
|
|
|
|
checkTransitionBeforeX() {}
|
|
|
|
beforeMovingX() {}
|
|
|
|
checkTransitionBeforeY(timeDelta: float) {
|
|
const behavior = this._behavior;
|
|
//Coming to an extremity of a ladder
|
|
if (!behavior._isOverlappingLadder()) {
|
|
behavior._setFalling();
|
|
}
|
|
|
|
//Jumping
|
|
behavior._checkTransitionJumping();
|
|
|
|
//Release the ladder
|
|
if (behavior._releaseLadderKey) {
|
|
behavior._releaseLadder();
|
|
}
|
|
}
|
|
|
|
beforeMovingY(timeDelta: float, oldX: float) {
|
|
const behavior = this._behavior;
|
|
|
|
// TODO: we could consider supporting acceleration for ladder climbing in the future.
|
|
if (behavior._upKey) {
|
|
behavior._requestedDeltaY -= behavior._ladderClimbingSpeed * timeDelta;
|
|
}
|
|
if (behavior._downKey) {
|
|
behavior._requestedDeltaY += behavior._ladderClimbingSpeed * timeDelta;
|
|
}
|
|
}
|
|
|
|
toString(): String {
|
|
return 'OnLadder';
|
|
}
|
|
}
|
|
|
|
gdjs.registerBehavior(
|
|
'PlatformBehavior::PlatformerObjectBehavior',
|
|
gdjs.PlatformerObjectRuntimeBehavior
|
|
);
|
|
}
|