mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
2 Commits
d474c2a47e
...
lifecycle-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f8f692fff4 | ||
![]() |
d051aa4302 |
@@ -113,6 +113,10 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isNeedingLifecycleFunctions()) {
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
@@ -194,6 +198,10 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
return super.isNeedingLifecycleFunctions() || this._animations.length > 0;
|
||||
}
|
||||
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
const elapsedTime = this.getElapsedTime() / 1000;
|
||||
this._renderer.updateAnimation(elapsedTime * this._animationSpeedScale);
|
||||
|
@@ -1,11 +1,9 @@
|
||||
namespace gdjs {
|
||||
declare var rbush: any;
|
||||
|
||||
export class LightObstaclesManager {
|
||||
_obstacleRBush: any;
|
||||
_obstacleRBush: RBush<LightObstacleRuntimeBehavior>;
|
||||
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._obstacleRBush = new rbush();
|
||||
this._obstacleRBush = new RBush<LightObstacleRuntimeBehavior>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,6 +39,9 @@ namespace gdjs {
|
||||
* added before.
|
||||
*/
|
||||
removeObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
|
||||
if (!obstacle.currentRBushAABB) {
|
||||
return;
|
||||
}
|
||||
this._obstacleRBush.remove(obstacle.currentRBushAABB);
|
||||
}
|
||||
|
||||
@@ -59,9 +60,9 @@ namespace gdjs {
|
||||
// is not necessarily in the middle of the object (for sprites for example).
|
||||
const x = object.getX();
|
||||
const y = object.getY();
|
||||
const searchArea = gdjs.staticObject(
|
||||
const searchArea: SearchArea = gdjs.staticObject(
|
||||
LightObstaclesManager.prototype.getAllObstaclesAround
|
||||
);
|
||||
) as SearchArea;
|
||||
// @ts-ignore
|
||||
searchArea.minX = x - radius;
|
||||
// @ts-ignore
|
||||
@@ -70,13 +71,8 @@ namespace gdjs {
|
||||
searchArea.maxX = x + radius;
|
||||
// @ts-ignore
|
||||
searchArea.maxY = y + radius;
|
||||
const nearbyObstacles: gdjs.BehaviorRBushAABB<
|
||||
gdjs.LightObstacleRuntimeBehavior
|
||||
>[] = this._obstacleRBush.search(searchArea);
|
||||
result.length = 0;
|
||||
nearbyObstacles.forEach((nearbyObstacle) =>
|
||||
result.push(nearbyObstacle.behavior)
|
||||
);
|
||||
this._obstacleRBush.search(searchArea, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,6 @@ namespace gdjs {
|
||||
export interface RuntimeInstanceContainer {
|
||||
pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager;
|
||||
}
|
||||
declare var rbush: any;
|
||||
|
||||
/**
|
||||
* PathfindingObstaclesManager manages the common objects shared by objects
|
||||
@@ -18,10 +17,10 @@ namespace gdjs {
|
||||
* `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`).
|
||||
*/
|
||||
export class PathfindingObstaclesManager {
|
||||
_obstaclesRBush: any;
|
||||
_obstaclesRBush: RBush<PathfindingObstacleRuntimeBehavior>;
|
||||
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._obstaclesRBush = new rbush();
|
||||
this._obstaclesRBush = new RBush<PathfindingObstacleRuntimeBehavior>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +59,9 @@ namespace gdjs {
|
||||
removeObstacle(
|
||||
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
|
||||
) {
|
||||
if (!pathfindingObstacleBehavior.currentRBushAABB) {
|
||||
return;
|
||||
}
|
||||
this._obstaclesRBush.remove(pathfindingObstacleBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
@@ -74,9 +76,9 @@ namespace gdjs {
|
||||
radius: float,
|
||||
result: gdjs.PathfindingObstacleRuntimeBehavior[]
|
||||
): void {
|
||||
const searchArea = gdjs.staticObject(
|
||||
const searchArea: SearchArea = gdjs.staticObject(
|
||||
PathfindingObstaclesManager.prototype.getAllObstaclesAround
|
||||
);
|
||||
) as SearchArea;
|
||||
// @ts-ignore
|
||||
searchArea.minX = x - radius;
|
||||
// @ts-ignore
|
||||
@@ -85,13 +87,8 @@ namespace gdjs {
|
||||
searchArea.maxX = x + radius;
|
||||
// @ts-ignore
|
||||
searchArea.maxY = y + radius;
|
||||
const nearbyObstacles: gdjs.BehaviorRBushAABB<
|
||||
gdjs.PathfindingObstacleRuntimeBehavior
|
||||
>[] = this._obstaclesRBush.search(searchArea);
|
||||
result.length = 0;
|
||||
nearbyObstacles.forEach((nearbyObstacle) =>
|
||||
result.push(nearbyObstacle.behavior)
|
||||
);
|
||||
this._obstaclesRBush.search(searchArea, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -211,6 +211,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
// Update platforms locations.
|
||||
this._manager.doStepPreEvents();
|
||||
|
||||
const LEFTKEY = 37;
|
||||
const UPKEY = 38;
|
||||
const RIGHTKEY = 39;
|
||||
|
@@ -3,7 +3,6 @@ GDevelop - Platform Behavior Extension
|
||||
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
declare var rbush: any;
|
||||
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
|
||||
|
||||
/**
|
||||
@@ -13,10 +12,12 @@ namespace gdjs {
|
||||
* of their associated container (see PlatformRuntimeBehavior.getManager).
|
||||
*/
|
||||
export class PlatformObjectsManager {
|
||||
private _platformRBush: any;
|
||||
private _platformRBush: RBush<PlatformRuntimeBehavior>;
|
||||
private movedPlatforms: Array<gdjs.PlatformRuntimeBehavior>;
|
||||
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
this._platformRBush = new rbush();
|
||||
this._platformRBush = new RBush<PlatformRuntimeBehavior>();
|
||||
this.movedPlatforms = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Add a platform to the list of existing platforms.
|
||||
*/
|
||||
addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
private addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
if (platformBehavior.currentRBushAABB)
|
||||
platformBehavior.currentRBushAABB.updateAABBFromOwner();
|
||||
else
|
||||
@@ -52,10 +53,39 @@ namespace gdjs {
|
||||
* Remove a platform from the list of existing platforms. Be sure that the platform was
|
||||
* added before.
|
||||
*/
|
||||
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
private removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
if (!platformBehavior.currentRBushAABB) {
|
||||
return;
|
||||
}
|
||||
this._platformRBush.remove(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
invalidatePlatformHitbox(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this.movedPlatforms.push(platformBehavior);
|
||||
}
|
||||
|
||||
onDestroy(platformBehavior: gdjs.PlatformRuntimeBehavior): void {
|
||||
if (!platformBehavior.activated()) {
|
||||
return;
|
||||
}
|
||||
if (platformBehavior.isAABBInvalidated()) {
|
||||
const index = this.movedPlatforms.indexOf(platformBehavior);
|
||||
this.movedPlatforms.splice(index, 1);
|
||||
}
|
||||
this.removePlatform(platformBehavior);
|
||||
}
|
||||
|
||||
doStepPreEvents() {
|
||||
for (const platformBehavior of this.movedPlatforms) {
|
||||
this.removePlatform(platformBehavior);
|
||||
if (platformBehavior.activated()) {
|
||||
this.addPlatform(platformBehavior);
|
||||
}
|
||||
platformBehavior.onHitboxUpdatedInTree();
|
||||
}
|
||||
this.movedPlatforms.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the platforms around the specified object.
|
||||
* @param maxMovementLength The maximum distance, in pixels, the object is going to do.
|
||||
@@ -75,21 +105,19 @@ namespace gdjs {
|
||||
const searchArea: SearchArea = gdjs.staticObject(
|
||||
PlatformObjectsManager.prototype.getAllPlatformsAround
|
||||
) as SearchArea;
|
||||
result.length = 0;
|
||||
searchArea.minX = x - ow / 2 - maxMovementLength;
|
||||
searchArea.minY = y - oh / 2 - maxMovementLength;
|
||||
searchArea.maxX = x + ow / 2 + maxMovementLength;
|
||||
searchArea.maxY = y + oh / 2 + maxMovementLength;
|
||||
const nearbyPlatforms: gdjs.BehaviorRBushAABB<
|
||||
PlatformRuntimeBehavior
|
||||
>[] = this._platformRBush.search(searchArea);
|
||||
|
||||
result.length = 0;
|
||||
this._platformRBush.search(searchArea, result);
|
||||
|
||||
// Extra check on the platform owner AABB
|
||||
// TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need
|
||||
// for this extra check once merged.
|
||||
for (let i = 0; i < nearbyPlatforms.length; i++) {
|
||||
const platform = nearbyPlatforms[i].behavior;
|
||||
let writtenIndex = 0;
|
||||
for (let readIndex = 0; readIndex < result.length; readIndex++) {
|
||||
const platform = result[readIndex];
|
||||
const platformAABB = platform.owner.getAABB();
|
||||
const platformIsStillAround =
|
||||
platformAABB.min[0] <= searchArea.maxX &&
|
||||
@@ -100,9 +128,11 @@ namespace gdjs {
|
||||
// This can happen because platforms are not updated in the RBush before that
|
||||
// characters movement are being processed.
|
||||
if (platformIsStillAround) {
|
||||
result.push(platform);
|
||||
result[writtenIndex] = platform;
|
||||
writtenIndex++;
|
||||
}
|
||||
}
|
||||
result.length = writtenIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +157,7 @@ namespace gdjs {
|
||||
> | null = null;
|
||||
_manager: gdjs.PlatformObjectsManager;
|
||||
_registeredInManager: boolean = false;
|
||||
_isAABBInvalidated = false;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -145,6 +176,10 @@ namespace gdjs {
|
||||
this._canBeGrabbed = behaviorData.canBeGrabbed || false;
|
||||
this._yGrabOffset = behaviorData.yGrabOffset || 0;
|
||||
this._manager = PlatformObjectsManager.getManager(instanceContainer);
|
||||
this.owner.registerHitboxChangedCallback((object) =>
|
||||
this.onHitboxChanged()
|
||||
);
|
||||
this.onHitboxChanged();
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
@@ -160,10 +195,12 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
if (this._manager && this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
}
|
||||
onDestroy(): void {
|
||||
this._manager.onDestroy(this);
|
||||
}
|
||||
|
||||
usesLifecycleFunction(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
@@ -176,54 +213,32 @@ namespace gdjs {
|
||||
sceneManager = parentScene ? &ScenePlatformObjectsManager::managers[&scene] : NULL;
|
||||
registeredInManager = false;
|
||||
}*/
|
||||
|
||||
//Make sure the platform is or is not in the platforms manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
this._registeredInManager = false;
|
||||
} else {
|
||||
if (this.activated() && !this._registeredInManager) {
|
||||
this._manager.addPlatform(this);
|
||||
this._registeredInManager = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Track changes in size or position
|
||||
if (
|
||||
this._oldX !== this.owner.getX() ||
|
||||
this._oldY !== this.owner.getY() ||
|
||||
this._oldWidth !== this.owner.getWidth() ||
|
||||
this._oldHeight !== this.owner.getHeight() ||
|
||||
this._oldAngle !== this.owner.getAngle()
|
||||
) {
|
||||
if (this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
this._manager.addPlatform(this);
|
||||
}
|
||||
this._oldX = this.owner.getX();
|
||||
this._oldY = this.owner.getY();
|
||||
this._oldWidth = this.owner.getWidth();
|
||||
this._oldHeight = this.owner.getHeight();
|
||||
this._oldAngle = this.owner.getAngle();
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
onActivate() {
|
||||
if (this._registeredInManager) {
|
||||
return;
|
||||
}
|
||||
this._manager.addPlatform(this);
|
||||
this._registeredInManager = true;
|
||||
this.onHitboxChanged();
|
||||
}
|
||||
|
||||
onDeActivate() {
|
||||
if (!this._registeredInManager) {
|
||||
this.onHitboxChanged();
|
||||
}
|
||||
|
||||
onHitboxChanged() {
|
||||
if (this._isAABBInvalidated || !this.owner.isAlive()) {
|
||||
return;
|
||||
}
|
||||
this._manager.removePlatform(this);
|
||||
this._registeredInManager = false;
|
||||
this._isAABBInvalidated = true;
|
||||
this._manager.invalidatePlatformHitbox(this);
|
||||
}
|
||||
|
||||
onHitboxUpdatedInTree() {
|
||||
this._isAABBInvalidated = false;
|
||||
}
|
||||
|
||||
isAABBInvalidated() {
|
||||
return !this._isAABBInvalidated;
|
||||
}
|
||||
|
||||
changePlatformType(platformType: string) {
|
||||
|
@@ -1,8 +1,11 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
describe('(falling)', function () {
|
||||
/** @type {gdjs.RuntimeScene} */
|
||||
let runtimeScene;
|
||||
/** @type {gdjs.RuntimeObject} */
|
||||
let object;
|
||||
/** @type {gdjs.RuntimeObject} */
|
||||
let platform;
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -114,7 +117,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Remove the platform
|
||||
runtimeScene.markObjectForDeletion(platform);
|
||||
platform.deleteFromScene(runtimeScene);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
|
@@ -39,6 +39,10 @@ namespace gdjs {
|
||||
);
|
||||
this._updateTileMap();
|
||||
|
||||
if (this.isNeedingLifecycleFunctions()) {
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
@@ -47,6 +51,11 @@ namespace gdjs {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
// TODO Tile maps without animated tiles should return false.
|
||||
return true;
|
||||
}
|
||||
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (this._animationSpeedScale <= 0 || this._animationFps === 0) {
|
||||
return;
|
||||
|
@@ -670,6 +670,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "ResourceCache.js");
|
||||
InsertUnique(includesFiles, "timemanager.js");
|
||||
InsertUnique(includesFiles, "polygon.js");
|
||||
InsertUnique(includesFiles, "ObjectSleepState.js");
|
||||
InsertUnique(includesFiles, "runtimeobject.js");
|
||||
InsertUnique(includesFiles, "profiler.js");
|
||||
InsertUnique(includesFiles, "RuntimeInstanceContainer.js");
|
||||
|
@@ -60,6 +60,10 @@ namespace gdjs {
|
||||
this._instanceContainer.loadFrom(objectData);
|
||||
this.getRenderer().reinitialize(this, parent);
|
||||
|
||||
if (this.isNeedingLifecycleFunctions()) {
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
// The generated code calls onCreated at the constructor end
|
||||
// and onCreated calls its super implementation at its end.
|
||||
}
|
||||
@@ -120,6 +124,10 @@ namespace gdjs {
|
||||
*/
|
||||
onHotReloading(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is only to handle trigger once.
|
||||
doStepPreEvents(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
|
@@ -235,32 +235,6 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the objects before launching the events.
|
||||
*/
|
||||
_updateObjectsPreEvents() {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const obj = allInstancesList[i];
|
||||
const elapsedTime = obj.getElapsedTime();
|
||||
if (!obj.hasNoForces()) {
|
||||
const averageForce = obj.getAverageForce();
|
||||
const elapsedTimeInSeconds = elapsedTime / 1000;
|
||||
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
|
||||
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
|
||||
obj.update(this);
|
||||
obj.updateForces(elapsedTimeInSeconds);
|
||||
} else {
|
||||
obj.update(this);
|
||||
}
|
||||
obj.updateTimers(elapsedTime);
|
||||
obj.stepBehaviorsPreEvents(this);
|
||||
}
|
||||
|
||||
// Some behaviors may have request objects to be deleted.
|
||||
this._cacheOrClearRemovedInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the renderer associated to the RuntimeScene.
|
||||
*/
|
||||
|
119
GDJS/Runtime/ObjectSleepState.ts
Normal file
119
GDJS/Runtime/ObjectSleepState.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2023-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
export class ObjectSleepState {
|
||||
private static readonly framesBeforeSleep = 60;
|
||||
private _object: RuntimeObject;
|
||||
private _isNeedingToBeAwake: () => boolean;
|
||||
private _state: ObjectSleepState.State;
|
||||
private _lastActivityFrameIndex: integer;
|
||||
_onWakingUpCallbacks: Array<(object: RuntimeObject) => void> = [];
|
||||
|
||||
constructor(
|
||||
object: RuntimeObject,
|
||||
isNeedingToBeAwake: () => boolean,
|
||||
initialSleepState: ObjectSleepState.State
|
||||
) {
|
||||
this._object = object;
|
||||
this._isNeedingToBeAwake = isNeedingToBeAwake;
|
||||
this._state = initialSleepState;
|
||||
this._lastActivityFrameIndex = this._object
|
||||
.getRuntimeScene()
|
||||
.getFrameIndex();
|
||||
}
|
||||
|
||||
_reinitialize(
|
||||
initialSleepState: ObjectSleepState.State
|
||||
) : void {
|
||||
this._onWakingUpCallbacks.length = 0;
|
||||
this._state = initialSleepState;
|
||||
this._lastActivityFrameIndex = this._object
|
||||
.getRuntimeScene()
|
||||
.getFrameIndex();
|
||||
}
|
||||
|
||||
_destroy(): void {
|
||||
this._state = gdjs.ObjectSleepState.State.Destroyed;
|
||||
this._onWakingUpCallbacks.length = 0;
|
||||
}
|
||||
|
||||
canSleep(): boolean {
|
||||
return (
|
||||
this._state === gdjs.ObjectSleepState.State.CanSleepThisFrame ||
|
||||
this._object.getRuntimeScene().getFrameIndex() -
|
||||
this._lastActivityFrameIndex >=
|
||||
ObjectSleepState.framesBeforeSleep
|
||||
);
|
||||
}
|
||||
|
||||
isAwake(): boolean {
|
||||
return this._state === gdjs.ObjectSleepState.State.AWake ||
|
||||
this._state === gdjs.ObjectSleepState.State.CanSleepThisFrame;
|
||||
}
|
||||
|
||||
wakeUp() {
|
||||
const object = this._object;
|
||||
this._lastActivityFrameIndex = object.getRuntimeScene().getFrameIndex();
|
||||
if (this.isAwake()) {
|
||||
return;
|
||||
}
|
||||
this._state = gdjs.ObjectSleepState.State.AWake;
|
||||
for (const onWakingUp of this._onWakingUpCallbacks) {
|
||||
onWakingUp(object);
|
||||
}
|
||||
}
|
||||
|
||||
registerOnWakingUp(onWakingUp: (object: RuntimeObject) => void) {
|
||||
this._onWakingUpCallbacks.push(onWakingUp);
|
||||
}
|
||||
|
||||
tryToSleep(): void {
|
||||
if (this._isNeedingToBeAwake()) {
|
||||
this._lastActivityFrameIndex = this._object
|
||||
.getRuntimeScene()
|
||||
.getFrameIndex();
|
||||
}
|
||||
}
|
||||
|
||||
static updateAwakeObjects(
|
||||
awakeObjects: Array<RuntimeObject>,
|
||||
getSleepState: (object: RuntimeObject) => ObjectSleepState,
|
||||
onFallenAsleep: (object: RuntimeObject) => void,
|
||||
onWakingUp: (object: RuntimeObject) => void
|
||||
) {
|
||||
let writeIndex = 0;
|
||||
for (let readIndex = 0; readIndex < awakeObjects.length; readIndex++) {
|
||||
const object = awakeObjects[readIndex];
|
||||
const sleepState = getSleepState(object);
|
||||
sleepState.tryToSleep();
|
||||
if (sleepState.canSleep() || !sleepState.isAwake()) {
|
||||
if (sleepState.isAwake()) {
|
||||
// Avoid onWakingUp to be called if some managers didn't have time
|
||||
// to update their awake object list.
|
||||
sleepState._onWakingUpCallbacks.length = 0;
|
||||
}
|
||||
sleepState._state = gdjs.ObjectSleepState.State.ASleep;
|
||||
onFallenAsleep(object);
|
||||
sleepState.registerOnWakingUp(onWakingUp);
|
||||
} else {
|
||||
awakeObjects[writeIndex] = object;
|
||||
writeIndex++;
|
||||
}
|
||||
}
|
||||
awakeObjects.length = writeIndex;
|
||||
return awakeObjects;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ObjectSleepState {
|
||||
export enum State {
|
||||
ASleep,
|
||||
CanSleepThisFrame,
|
||||
AWake,
|
||||
Destroyed,
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,6 +15,8 @@ namespace gdjs {
|
||||
/** Contains the instances living on the container */
|
||||
_instances: Hashtable<RuntimeObject[]>;
|
||||
|
||||
_activeInstances: Array<RuntimeObject> = [];
|
||||
|
||||
/**
|
||||
* An array used to create a list of all instance when necessary.
|
||||
* @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances}
|
||||
@@ -485,13 +487,23 @@ namespace gdjs {
|
||||
return this._allInstancesList;
|
||||
}
|
||||
|
||||
getActiveInstances(): gdjs.RuntimeObject[] {
|
||||
gdjs.ObjectSleepState.updateAwakeObjects(
|
||||
this._activeInstances,
|
||||
(object) => object.getLifecycleSleepState(),
|
||||
(object) => {},
|
||||
(object) => this._activeInstances.push(object)
|
||||
);
|
||||
return this._activeInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the objects before launching the events.
|
||||
*/
|
||||
_updateObjectsPreEvents() {
|
||||
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
|
||||
// may delete the objects.
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
const allInstancesList = this.getActiveInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const obj = allInstancesList[i];
|
||||
const elapsedTime = obj.getElapsedTime();
|
||||
@@ -506,7 +518,7 @@ namespace gdjs {
|
||||
obj.update(this);
|
||||
}
|
||||
obj.updateTimers(elapsedTime);
|
||||
allInstancesList[i].stepBehaviorsPreEvents(this);
|
||||
obj.stepBehaviorsPreEvents(this);
|
||||
}
|
||||
|
||||
// Some behaviors may have request objects to be deleted.
|
||||
@@ -521,7 +533,7 @@ namespace gdjs {
|
||||
|
||||
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
|
||||
// may delete the objects.
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
const allInstancesList = this.getActiveInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
allInstancesList[i].stepBehaviorsPostEvents(this);
|
||||
}
|
||||
@@ -535,11 +547,20 @@ namespace gdjs {
|
||||
* @param obj The object to be added.
|
||||
*/
|
||||
addObject(obj: gdjs.RuntimeObject) {
|
||||
if (!this._instances.containsKey(obj.name)) {
|
||||
this._instances.put(obj.name, []);
|
||||
let instances = this._instances.get(obj.name);
|
||||
if (!instances) {
|
||||
instances = [];
|
||||
this._instances.put(obj.name, instances);
|
||||
}
|
||||
instances.push(obj);
|
||||
this._allInstancesList.push(obj);
|
||||
if (obj.getLifecycleSleepState().isAwake()) {
|
||||
this._activeInstances.push(obj);
|
||||
} else {
|
||||
obj
|
||||
.getLifecycleSleepState()
|
||||
.registerOnWakingUp((object) => this._activeInstances.push(object));
|
||||
}
|
||||
this._instances.get(obj.name).push(obj);
|
||||
this._allInstancesListIsUpToDate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -717,19 +738,22 @@ namespace gdjs {
|
||||
* Update the objects positions according to their forces
|
||||
*/
|
||||
updateObjectsForces(): void {
|
||||
for (const name in this._instances.items) {
|
||||
if (this._instances.items.hasOwnProperty(name)) {
|
||||
const list = this._instances.items[name];
|
||||
for (let j = 0, listLen = list.length; j < listLen; ++j) {
|
||||
const obj = list[j];
|
||||
if (!obj.hasNoForces()) {
|
||||
const averageForce = obj.getAverageForce();
|
||||
const elapsedTimeInSeconds = obj.getElapsedTime() / 1000;
|
||||
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
|
||||
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
|
||||
obj.updateForces(elapsedTimeInSeconds);
|
||||
}
|
||||
}
|
||||
for (
|
||||
let i = 0, listLen = this._activeInstances.length;
|
||||
i < listLen;
|
||||
++i
|
||||
) {
|
||||
const object = this._activeInstances[i];
|
||||
if (!object.hasNoForces()) {
|
||||
const averageForce = object.getAverageForce();
|
||||
const elapsedTimeInSeconds = object.getElapsedTime() / 1000;
|
||||
object.setX(
|
||||
object.getX() + averageForce.getX() * elapsedTimeInSeconds
|
||||
);
|
||||
object.setY(
|
||||
object.getY() + averageForce.getY() * elapsedTimeInSeconds
|
||||
);
|
||||
object.updateForces(elapsedTimeInSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -16,18 +16,18 @@ namespace gdjs {
|
||||
minY: float = 0;
|
||||
maxX: float = 0;
|
||||
maxY: float = 0;
|
||||
behavior: T;
|
||||
source: T;
|
||||
|
||||
constructor(behavior: T) {
|
||||
this.behavior = behavior;
|
||||
this.source = behavior;
|
||||
this.updateAABBFromOwner();
|
||||
}
|
||||
|
||||
updateAABBFromOwner() {
|
||||
this.minX = this.behavior.owner.getAABB().min[0];
|
||||
this.minY = this.behavior.owner.getAABB().min[1];
|
||||
this.maxX = this.behavior.owner.getAABB().max[0];
|
||||
this.maxY = this.behavior.owner.getAABB().max[1];
|
||||
this.minX = this.source.owner.getAABB().min[0];
|
||||
this.minY = this.source.owner.getAABB().min[1];
|
||||
this.maxX = this.source.owner.getAABB().max[0];
|
||||
this.maxY = this.source.owner.getAABB().max[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -147,6 +147,8 @@ namespace gdjs {
|
||||
return true;
|
||||
};
|
||||
|
||||
type RuntimeObjectCallback = (object: gdjs.RuntimeObject) => void;
|
||||
|
||||
/**
|
||||
* RuntimeObject represents an object being used on a RuntimeScene.
|
||||
*
|
||||
@@ -164,9 +166,12 @@ namespace gdjs {
|
||||
layer: string = '';
|
||||
protected _nameId: integer;
|
||||
protected _livingOnScene: boolean = true;
|
||||
protected _lifecycleSleepState: ObjectSleepState;
|
||||
|
||||
readonly id: integer;
|
||||
private destroyCallbacks = new Set<() => void>();
|
||||
// HitboxChanges happen a lot, an Array is faster to iterate.
|
||||
private hitBoxChangedCallbacks: Array<RuntimeObjectCallback> = [];
|
||||
_runtimeScene: gdjs.RuntimeInstanceContainer;
|
||||
|
||||
/**
|
||||
@@ -181,12 +186,15 @@ namespace gdjs {
|
||||
* not "thread safe" or "re-entrant algorithm" safe.
|
||||
*/
|
||||
pick: boolean = false;
|
||||
pickingId: integer = 0;
|
||||
|
||||
//Hit boxes:
|
||||
protected _defaultHitBoxes: gdjs.Polygon[] = [];
|
||||
protected hitBoxes: gdjs.Polygon[];
|
||||
protected hitBoxesDirty: boolean = true;
|
||||
// TODO use a different AABB for collision mask and rendered image.
|
||||
protected aabb: AABB = { min: [0, 0], max: [0, 0] };
|
||||
|
||||
protected _isIncludedInParentCollisionMask = true;
|
||||
|
||||
//Variables:
|
||||
@@ -212,6 +220,7 @@ namespace gdjs {
|
||||
* are never used.
|
||||
*/
|
||||
protected _behaviors: gdjs.RuntimeBehavior[] = [];
|
||||
protected _activeBehaviors: gdjs.RuntimeBehavior[] = [];
|
||||
/**
|
||||
* Contains the behaviors of the object by name.
|
||||
*
|
||||
@@ -229,10 +238,11 @@ namespace gdjs {
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: ObjectData & any
|
||||
) {
|
||||
const scene = instanceContainer.getScene();
|
||||
this.name = objectData.name || '';
|
||||
this.type = objectData.type || '';
|
||||
this._nameId = RuntimeObject.getNameIdentifier(this.name);
|
||||
this.id = instanceContainer.getScene().createNewUniqueId();
|
||||
this.id = scene.createNewUniqueId();
|
||||
this._runtimeScene = instanceContainer;
|
||||
this._defaultHitBoxes.push(gdjs.Polygon.createRectangle(0, 0));
|
||||
this.hitBoxes = this._defaultHitBoxes;
|
||||
@@ -241,8 +251,14 @@ namespace gdjs {
|
||||
);
|
||||
this._totalForce = new gdjs.Force(0, 0, 0);
|
||||
this._behaviorsTable = new Hashtable();
|
||||
this._timers = new Hashtable();
|
||||
this._lifecycleSleepState = new gdjs.ObjectSleepState(
|
||||
this,
|
||||
() => this.isNeedingLifecycleFunctions(),
|
||||
gdjs.ObjectSleepState.State.ASleep
|
||||
);
|
||||
for (let i = 0; i < objectData.effects.length; ++i) {
|
||||
this._runtimeScene
|
||||
scene
|
||||
.getGame()
|
||||
.getEffectsManager()
|
||||
.initializeEffect(objectData.effects[i], this._rendererEffects, this);
|
||||
@@ -253,12 +269,13 @@ namespace gdjs {
|
||||
const autoData = objectData.behaviors[i];
|
||||
const Ctor = gdjs.getBehaviorConstructor(autoData.type);
|
||||
const behavior = new Ctor(instanceContainer, autoData, this);
|
||||
this._behaviors.push(behavior);
|
||||
if (behavior.usesLifecycleFunction()) {
|
||||
this._behaviors.push(behavior);
|
||||
this._activeBehaviors.push(behavior);
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
this._behaviorsTable.put(autoData.name, behavior);
|
||||
}
|
||||
this._timers = new Hashtable();
|
||||
}
|
||||
|
||||
//Common members functions related to the object and its runtimeScene :
|
||||
@@ -322,10 +339,12 @@ namespace gdjs {
|
||||
this.aabb.max[1] = 0;
|
||||
this._variables = new gdjs.VariablesContainer(objectData.variables);
|
||||
this.clearForces();
|
||||
this._lifecycleSleepState._reinitialize(gdjs.ObjectSleepState.State.ASleep);
|
||||
|
||||
// Reinitialize behaviors.
|
||||
this._behaviorsTable.clear();
|
||||
const behaviorsDataCount = objectData.behaviors.length;
|
||||
let behaviorsCount = 0;
|
||||
let behaviorsUsingLifecycleFunctionCount = 0;
|
||||
for (
|
||||
let behaviorDataIndex = 0;
|
||||
@@ -336,17 +355,29 @@ namespace gdjs {
|
||||
const Ctor = gdjs.getBehaviorConstructor(behaviorData.type);
|
||||
// TODO: Add support for behavior recycling with a `reinitialize` method.
|
||||
const behavior = new Ctor(runtimeScene, behaviorData, this);
|
||||
if (behaviorsCount < this._behaviors.length) {
|
||||
this._behaviors[behaviorsCount] = behavior;
|
||||
} else {
|
||||
this._behaviors.push(behavior);
|
||||
}
|
||||
behaviorsCount++;
|
||||
if (behavior.usesLifecycleFunction()) {
|
||||
if (behaviorsUsingLifecycleFunctionCount < this._behaviors.length) {
|
||||
this._behaviors[behaviorsUsingLifecycleFunctionCount] = behavior;
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
if (
|
||||
behaviorsUsingLifecycleFunctionCount < this._activeBehaviors.length
|
||||
) {
|
||||
this._activeBehaviors[
|
||||
behaviorsUsingLifecycleFunctionCount
|
||||
] = behavior;
|
||||
} else {
|
||||
this._behaviors.push(behavior);
|
||||
this._activeBehaviors.push(behavior);
|
||||
}
|
||||
behaviorsUsingLifecycleFunctionCount++;
|
||||
}
|
||||
this._behaviorsTable.put(behaviorData.name, behavior);
|
||||
}
|
||||
this._behaviors.length = behaviorsUsingLifecycleFunctionCount;
|
||||
this._behaviors.length = behaviorsCount;
|
||||
this._activeBehaviors.length = behaviorsUsingLifecycleFunctionCount;
|
||||
|
||||
// Reinitialize effects.
|
||||
for (let i = 0; i < objectData.effects.length; ++i) {
|
||||
@@ -439,6 +470,22 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
return (
|
||||
this._activeBehaviors.length > 0 ||
|
||||
!this.hasNoForces() ||
|
||||
!!this._timers.firstKey()
|
||||
);
|
||||
}
|
||||
|
||||
getLifecycleSleepState(): ObjectSleepState {
|
||||
return this._lifecycleSleepState;
|
||||
}
|
||||
|
||||
isAlive(): boolean {
|
||||
return this._livingOnScene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object from a scene.
|
||||
*
|
||||
@@ -449,6 +496,7 @@ namespace gdjs {
|
||||
if (this._livingOnScene) {
|
||||
instanceContainer.markObjectForDeletion(this);
|
||||
this._livingOnScene = false;
|
||||
this._lifecycleSleepState._destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +534,30 @@ namespace gdjs {
|
||||
|
||||
onDestroyed(): void {}
|
||||
|
||||
registerHitboxChangedCallback(callback: RuntimeObjectCallback) {
|
||||
if (this.hitBoxChangedCallbacks.includes(callback)) {
|
||||
return;
|
||||
}
|
||||
this.hitBoxChangedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signal that the object hitboxes are no longer up to date.
|
||||
*
|
||||
* The signal is propagated to parents so
|
||||
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
|
||||
* directly.
|
||||
*/
|
||||
invalidateHitboxes(): void {
|
||||
// TODO EBO Check that no community extension set hitBoxesDirty to true
|
||||
// directly.
|
||||
this.hitBoxesDirty = true;
|
||||
this._runtimeScene.onChildrenLocationChanged();
|
||||
for (const callback of this.hitBoxChangedCallbacks) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the scene owning the object is paused.
|
||||
* This should *not* impact objects, but some may need to inform their renderer.
|
||||
@@ -570,20 +642,6 @@ namespace gdjs {
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signal that the object hitboxes are no longer up to date.
|
||||
*
|
||||
* The signal is propagated to parents so
|
||||
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
|
||||
* directly.
|
||||
*/
|
||||
invalidateHitboxes(): void {
|
||||
// TODO EBO Check that no community extension set hitBoxesDirty to true
|
||||
// directly.
|
||||
this.hitBoxesDirty = true;
|
||||
this._runtimeScene.onChildrenLocationChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X position of the object.
|
||||
*
|
||||
@@ -1363,6 +1421,7 @@ namespace gdjs {
|
||||
// (or the 1st instant force).
|
||||
this._instantForces.push(this._getRecycledForce(x, y, multiplier));
|
||||
}
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1781,8 +1840,8 @@ namespace gdjs {
|
||||
stepBehaviorsPreEvents(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
for (let i = 0, len = this._behaviors.length; i < len; ++i) {
|
||||
this._behaviors[i].stepPreEvents(instanceContainer);
|
||||
for (let i = 0, len = this._activeBehaviors.length; i < len; ++i) {
|
||||
this._activeBehaviors[i].stepPreEvents(instanceContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1792,8 +1851,8 @@ namespace gdjs {
|
||||
stepBehaviorsPostEvents(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
for (let i = 0, len = this._behaviors.length; i < len; ++i) {
|
||||
this._behaviors[i].stepPostEvents(instanceContainer);
|
||||
for (let i = 0, len = this._activeBehaviors.length; i < len; ++i) {
|
||||
this._activeBehaviors[i].stepPostEvents(instanceContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1870,9 +1929,17 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
behavior.onDestroy();
|
||||
const behaviorIndex = this._behaviors.indexOf(behavior);
|
||||
if (behaviorIndex !== -1) {
|
||||
this._behaviors.splice(behaviorIndex, 1);
|
||||
{
|
||||
const behaviorIndex = this._behaviors.indexOf(behavior);
|
||||
if (behaviorIndex !== -1) {
|
||||
this._behaviors.splice(behaviorIndex, 1);
|
||||
}
|
||||
}
|
||||
{
|
||||
const behaviorIndex = this._activeBehaviors.indexOf(behavior);
|
||||
if (behaviorIndex !== -1) {
|
||||
this._activeBehaviors.splice(behaviorIndex, 1);
|
||||
}
|
||||
}
|
||||
this._behaviorsTable.remove(name);
|
||||
return true;
|
||||
@@ -1926,6 +1993,7 @@ namespace gdjs {
|
||||
timerElapsedTime(timerName: string, timeInSeconds: float): boolean {
|
||||
if (!this._timers.containsKey(timerName)) {
|
||||
this._timers.put(timerName, new gdjs.Timer(timerName));
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
return false;
|
||||
}
|
||||
return this.getTimerElapsedTimeInSeconds(timerName) >= timeInSeconds;
|
||||
@@ -1950,6 +2018,7 @@ namespace gdjs {
|
||||
resetTimer(timerName: string): void {
|
||||
if (!this._timers.containsKey(timerName)) {
|
||||
this._timers.put(timerName, new gdjs.Timer(timerName));
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
this._timers.get(timerName).reset();
|
||||
}
|
||||
@@ -1961,6 +2030,7 @@ namespace gdjs {
|
||||
pauseTimer(timerName: string): void {
|
||||
if (!this._timers.containsKey(timerName)) {
|
||||
this._timers.put(timerName, new gdjs.Timer(timerName));
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
this._timers.get(timerName).setPaused(true);
|
||||
}
|
||||
@@ -1972,6 +2042,7 @@ namespace gdjs {
|
||||
unpauseTimer(timerName: string): void {
|
||||
if (!this._timers.containsKey(timerName)) {
|
||||
this._timers.put(timerName, new gdjs.Timer(timerName));
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
this._timers.get(timerName).setPaused(false);
|
||||
}
|
||||
|
@@ -42,6 +42,8 @@ namespace gdjs {
|
||||
// Set to `new gdjs.Profiler()` to have profiling done on the scene.
|
||||
_onProfilerStopped: null | ((oldProfiler: gdjs.Profiler) => void) = null;
|
||||
|
||||
private _frameIndex: integer = 0;
|
||||
|
||||
_cachedGameResolutionWidth: integer;
|
||||
_cachedGameResolutionHeight: integer;
|
||||
|
||||
@@ -427,6 +429,7 @@ namespace gdjs {
|
||||
if (this._profiler) {
|
||||
this._profiler.endFrame();
|
||||
}
|
||||
this._frameIndex++;
|
||||
return !!this.getRequestedChange();
|
||||
}
|
||||
|
||||
@@ -734,6 +737,10 @@ namespace gdjs {
|
||||
sceneJustResumed(): boolean {
|
||||
return this._isJustResumed;
|
||||
}
|
||||
|
||||
getFrameIndex(): integer {
|
||||
return this._frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
//The flags to describe the change request by a scene:
|
||||
|
@@ -348,6 +348,10 @@ namespace gdjs {
|
||||
);
|
||||
this._updateAnimationFrame();
|
||||
|
||||
if (this.isNeedingLifecycleFunctions()) {
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
@@ -452,6 +456,24 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
if (super.isNeedingLifecycleFunctions()) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.isAnimationPaused() ||
|
||||
this.hasAnimationEnded() ||
|
||||
this._currentAnimation >= this._animations.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const animation = this._animations[this._currentAnimation];
|
||||
if (this._currentDirection > animation.directions.length) {
|
||||
return false;
|
||||
}
|
||||
return animation.directions[this._currentDirection].frames.length > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current frame of the object according to the elapsed time on the scene.
|
||||
*/
|
||||
@@ -617,6 +639,7 @@ namespace gdjs {
|
||||
this._currentAnimation = newAnimation;
|
||||
this._currentFrame = 0;
|
||||
this._animationElapsedTime = 0;
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
|
||||
//TODO: This may be unnecessary.
|
||||
this._renderer.update();
|
||||
@@ -868,6 +891,7 @@ namespace gdjs {
|
||||
|
||||
resumeAnimation(): void {
|
||||
this._animationPaused = false;
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
getAnimationSpeedScale() {
|
||||
|
19
GDJS/Runtime/types/rbush.d.ts
vendored
Normal file
19
GDJS/Runtime/types/rbush.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
|
||||
type SearchedItem<T> = {
|
||||
source: T;
|
||||
minX: float;
|
||||
minY: float;
|
||||
maxX: float;
|
||||
maxY: float;
|
||||
};
|
||||
|
||||
declare class RBush<T> {
|
||||
constructor(maxEntries?: number);
|
||||
search(bbox: SearchArea, result?: Array<T>): Array<T>;
|
||||
insert(item: SearchedItem<T>): RBush<T>;
|
||||
clear(): RBush<T>;
|
||||
remove(
|
||||
item: SearchedItem<T>,
|
||||
equalsFn?: (item: SearchedItem<T>, otherItem: SearchedItem<T>) => boolean
|
||||
): RBush<T>;
|
||||
}
|
@@ -53,6 +53,7 @@ module.exports = function (config) {
|
||||
'./newIDE/app/resources/GDJS/Runtime/ResourceCache.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/timemanager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/polygon.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/ObjectSleepState.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/runtimeobject.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/runtimescene.js',
|
||||
|
78
GDJS/tests/tests/runtimescene.active.js
Normal file
78
GDJS/tests/tests/runtimescene.active.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// @ts-check
|
||||
describe.only('gdjs.RuntimeScene active objects tests', () => {
|
||||
|
||||
const spriteConfiguration = {
|
||||
name: 'MySprite',
|
||||
type: 'Sprite',
|
||||
behaviors: [],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 0, y: 0 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 0, y: 0 },
|
||||
],
|
||||
hasCustomCollisionMask: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
it('can recycle a sprite without duplication in the active objects', () => {
|
||||
const game = gdjs.getPixiRuntimeGame();
|
||||
const scene = new gdjs.TestRuntimeScene(game);
|
||||
|
||||
scene.registerObject(spriteConfiguration);
|
||||
let object = scene.createObject('MySprite');
|
||||
object.resetTimer("MyTimer");
|
||||
|
||||
scene.renderAndStep(1000 / 60);
|
||||
expect(scene._activeInstances.length).to.be(1);
|
||||
|
||||
object.deleteFromScene(scene);
|
||||
scene.renderAndStep(1000 / 60);
|
||||
expect(scene._activeInstances.length).to.be(0);
|
||||
// The object is not destroyed because it is recycled.
|
||||
expect(object.getLifecycleSleepState()._onWakingUpCallbacks.length).to.be(1);
|
||||
|
||||
let object2 = scene.createObject('MySprite');
|
||||
expect(object === object2).to.be(true);
|
||||
object.resetTimer("MyTimer");
|
||||
|
||||
expect(scene._activeInstances.length).to.be(1);
|
||||
expect(object.getLifecycleSleepState()._onWakingUpCallbacks.length).to.be(1);
|
||||
});
|
||||
|
||||
it('can keep an object awake to handle its timer', () => {
|
||||
const game = gdjs.getPixiRuntimeGame();
|
||||
const scene = new gdjs.TestRuntimeScene(game);
|
||||
|
||||
scene.registerObject(spriteConfiguration);
|
||||
let object = scene.createObject('MySprite');
|
||||
object.resetTimer("MyTimer");
|
||||
|
||||
for (let index = 0; index < 60; index++) {
|
||||
scene.renderAndStep(1000 / 60);
|
||||
expect(scene._activeInstances.length).to.be(1);
|
||||
}
|
||||
|
||||
object.removeTimer("MyTimer");
|
||||
for (let index = 0; index < 59; index++) {
|
||||
scene.renderAndStep(1000 / 60);
|
||||
expect(scene._activeInstances.length).to.be(1);
|
||||
}
|
||||
scene.renderAndStep(1000 / 60);
|
||||
expect(scene._activeInstances.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user