mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
29 Commits
experiment
...
object-sle
Author | SHA1 | Date | |
---|---|---|---|
![]() |
327165482a | ||
![]() |
92d7ddf5f7 | ||
![]() |
d153378351 | ||
![]() |
5274f68432 | ||
![]() |
329d899b47 | ||
![]() |
72603a4c0e | ||
![]() |
38e187b110 | ||
![]() |
633071560d | ||
![]() |
66f12b848c | ||
![]() |
9d1233bb09 | ||
![]() |
daa1f305ee | ||
![]() |
0755493f94 | ||
![]() |
8c7331fc44 | ||
![]() |
0f8fcacf98 | ||
![]() |
23a39f1e2c | ||
![]() |
b7787d29f1 | ||
![]() |
ad0f1f163f | ||
![]() |
e34aedb15c | ||
![]() |
27f16a3db0 | ||
![]() |
6e6ddb9edd | ||
![]() |
fbf4baebe9 | ||
![]() |
2cdfc889a8 | ||
![]() |
7f7a13dee8 | ||
![]() |
e45ba9465d | ||
![]() |
cbae925680 | ||
![]() |
852fdd77dd | ||
![]() |
8afea382d4 | ||
![]() |
c3c0c1961b | ||
![]() |
40c5012a0e |
@@ -103,6 +103,11 @@ void EventsCodeGenerationContext::EmptyObjectsListNeeded(
|
||||
depthOfLastUse[objectName] = GetContextDepth();
|
||||
}
|
||||
|
||||
void EventsCodeGenerationContext::AddUsedObjectsMapNames(
|
||||
const gd::String& objectMapName) {
|
||||
usedObjectsMapNames.insert(objectMapName);
|
||||
}
|
||||
|
||||
std::set<gd::String> EventsCodeGenerationContext::GetAllObjectsToBeDeclared()
|
||||
const {
|
||||
std::set<gd::String> allObjectListsToBeDeclared(
|
||||
|
@@ -175,6 +175,12 @@ class GD_CORE_API EventsCodeGenerationContext {
|
||||
return emptyObjectsListsToBeDeclared;
|
||||
};
|
||||
|
||||
const std::set<gd::String>& GetUsedObjectsMapNames() const {
|
||||
return usedObjectsMapNames;
|
||||
};
|
||||
|
||||
void AddUsedObjectsMapNames(const gd::String& objectMapName);
|
||||
|
||||
/**
|
||||
* Return the objects lists which are already declared and can be used in the
|
||||
* current context without declaration.
|
||||
@@ -297,6 +303,8 @@ class GD_CORE_API EventsCodeGenerationContext {
|
||||
///< necessary objects can be
|
||||
///< backed up.
|
||||
|
||||
std::set<gd::String> usedObjectsMapNames;
|
||||
|
||||
std::map<gd::String, unsigned int>
|
||||
depthOfLastUse; ///< The context depth when an object was last used.
|
||||
gd::String
|
||||
|
@@ -408,6 +408,25 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
|
||||
arguments, instrInfos, returnBoolean, condition.IsInverted(), context);
|
||||
}
|
||||
|
||||
// Flag the ObjectsLists as modified.
|
||||
gd::ParameterMetadataTools::IterateOverParameters(
|
||||
condition.GetParameters(), instrInfos.parameters,
|
||||
[this, &context,
|
||||
&conditionCode](const gd::ParameterMetadata ¶meterMetadata,
|
||||
const gd::Expression ¶meterValue,
|
||||
const gd::String &lastObjectName) {
|
||||
// objectListOrEmptyWithoutPicking are only used by SceneInstancesCount
|
||||
// and PickedInstancesCount conditions. They are not pass for one
|
||||
// condition to another.
|
||||
if (parameterMetadata.GetType() == "objectList" ||
|
||||
parameterMetadata.GetType() == "objectListOrEmptyIfJustDeclared") {
|
||||
// TODO FIXME What about groups using the object?
|
||||
conditionCode +=
|
||||
GetObjectMapName(parameterValue.GetPlainString(), context) +
|
||||
".isPicked = true;\n";
|
||||
}
|
||||
});
|
||||
|
||||
return conditionCode;
|
||||
}
|
||||
|
||||
|
@@ -585,6 +585,12 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
return "fakeObjectListOf_" + objectName;
|
||||
}
|
||||
|
||||
// TODO Documentation
|
||||
virtual gd::String GetObjectMapName(const gd::String &objectName,
|
||||
gd::EventsCodeGenerationContext &context) {
|
||||
return "fakeObjectListOf_" + objectName;
|
||||
}
|
||||
|
||||
virtual gd::String GeneratePropertyGetter(
|
||||
const gd::PropertiesContainer& propertiesContainer,
|
||||
const gd::NamedPropertyDescriptor& property,
|
||||
|
@@ -1475,10 +1475,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Position"),
|
||||
"res/conditions/distance24.png",
|
||||
"res/conditions/distance.png")
|
||||
.AddParameter("objectList", _("Object"))
|
||||
.AddParameter("objectList", _("Object 2"))
|
||||
.AddParameter("objectListOrEmptyIfJustDeclared", _("Object"))
|
||||
.AddParameter("objectListOrEmptyIfJustDeclared", _("Object 2"))
|
||||
.AddParameter("expression", _("Distance"))
|
||||
.AddCodeOnlyParameter("conditionInverted", "")
|
||||
.AddCodeOnlyParameter("objectsContext", "")
|
||||
.MarkAsSimple();
|
||||
|
||||
extension
|
||||
@@ -1585,10 +1586,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Collision"),
|
||||
"res/conditions/collision24.png",
|
||||
"res/conditions/collision.png")
|
||||
.AddParameter("objectList", _("Object"))
|
||||
.AddParameter("objectList", _("Object"))
|
||||
.AddParameter("objectListOrEmptyIfJustDeclared", _("Object"))
|
||||
.AddParameter("objectListOrEmptyIfJustDeclared", _("Object"))
|
||||
.AddCodeOnlyParameter("conditionInverted", "")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddCodeOnlyParameter("objectsContext", "")
|
||||
.AddParameter("yesorno",
|
||||
_("Ignore objects that are touching each other on their "
|
||||
"edges, but are not overlapping (default: no)"),
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
namespace gdjs {
|
||||
export namespace physics2 {
|
||||
export const objectsCollide = function (
|
||||
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists1: ObjectsLists,
|
||||
behaviorName: string,
|
||||
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean
|
||||
) {
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
@@ -16,9 +16,9 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
export const haveObjectsStartedColliding = function (
|
||||
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists1: ObjectsLists,
|
||||
behaviorName: string,
|
||||
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean
|
||||
) {
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
@@ -31,9 +31,9 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
export const haveObjectsStoppedColliding = function (
|
||||
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists1: ObjectsLists,
|
||||
behaviorName: string,
|
||||
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean
|
||||
) {
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
@@ -45,7 +45,11 @@ namespace gdjs {
|
||||
);
|
||||
};
|
||||
|
||||
export const setTimeScale = function (objectsLists, behavior, timeScale) {
|
||||
export const setTimeScale = function (
|
||||
objectsLists: ObjectsLists,
|
||||
behavior: gdjs.Physics2RuntimeBehavior,
|
||||
timeScale: float
|
||||
) {
|
||||
const lists = gdjs.staticArray(gdjs.physics2.setTimeScale);
|
||||
objectsLists.values(lists);
|
||||
for (let i = 0, len = lists.length; i < len; i++) {
|
||||
|
@@ -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 = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,9 +54,28 @@ namespace gdjs {
|
||||
* added before.
|
||||
*/
|
||||
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
if (!platformBehavior.currentRBushAABB) {
|
||||
return;
|
||||
}
|
||||
this._platformRBush.remove(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
invalidatePlatformHitbox(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this.movedPlatforms.push(platformBehavior);
|
||||
}
|
||||
|
||||
doStepPreEvents() {
|
||||
for (const platformBehavior of this.movedPlatforms) {
|
||||
this.removePlatform(platformBehavior);
|
||||
// TODO What if the object is recycled before it can be removed from the tree?
|
||||
if (platformBehavior.activated() && platformBehavior.owner.isAlive()) {
|
||||
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 +95,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 +118,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 +147,7 @@ namespace gdjs {
|
||||
> | null = null;
|
||||
_manager: gdjs.PlatformObjectsManager;
|
||||
_registeredInManager: boolean = false;
|
||||
_isAABBInvalidated = false;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -145,6 +166,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 {
|
||||
@@ -161,9 +186,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
if (this._manager && this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
}
|
||||
this.onHitboxChanged();
|
||||
}
|
||||
|
||||
usesLifecycleFunction(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
@@ -176,54 +203,28 @@ 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) {
|
||||
return;
|
||||
}
|
||||
this._manager.removePlatform(this);
|
||||
this._registeredInManager = false;
|
||||
this._isAABBInvalidated = true;
|
||||
this._manager.invalidatePlatformHitbox(this);
|
||||
}
|
||||
|
||||
onHitboxUpdatedInTree() {
|
||||
this._isAABBInvalidated = false;
|
||||
}
|
||||
|
||||
changePlatformType(platformType: string) {
|
||||
|
@@ -2,9 +2,9 @@ namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace platform {
|
||||
export const isOnPlatform = function (
|
||||
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists1: ObjectsLists,
|
||||
behaviorName: string,
|
||||
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean
|
||||
) {
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
|
@@ -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);
|
||||
|
@@ -26,11 +26,12 @@ namespace gdjs {
|
||||
);
|
||||
};
|
||||
|
||||
class TextInputRuntimeObjectPixiRenderer {
|
||||
class TextInputRuntimeObjectPixiRenderer implements RendererObjectInterface {
|
||||
private _object: gdjs.TextInputRuntimeObject;
|
||||
private _input: HTMLInputElement | HTMLTextAreaElement | null = null;
|
||||
private _instanceContainer: gdjs.RuntimeInstanceContainer;
|
||||
private _runtimeGame: gdjs.RuntimeGame;
|
||||
private _isVisible = false;
|
||||
|
||||
constructor(
|
||||
runtimeObject: gdjs.TextInputRuntimeObject,
|
||||
@@ -113,14 +114,25 @@ namespace gdjs {
|
||||
this._destroyElement();
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
set visible(isVisible: boolean) {
|
||||
this._isVisible = isVisible;
|
||||
if (!this._input) return;
|
||||
this._input.style.display = isVisible ? 'initial' : 'none';
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
get visible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
updatePreRender() {
|
||||
if (!this._input) return;
|
||||
|
||||
// Hide the input entirely if the object is hidden.
|
||||
// Because this object is rendered as a DOM element (and not part of the PixiJS
|
||||
// scene graph), we have to do this manually.
|
||||
if (this._object.isHidden()) {
|
||||
this._input.style.display = 'none';
|
||||
if (!this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -102,7 +102,8 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return null;
|
||||
// The renderer is not a Pixi Object but it implements visible.
|
||||
return this._renderer;
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
|
@@ -767,6 +767,14 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
|
||||
}
|
||||
if (conditionInverted) predicate = GenerateNegatedPredicate(predicate);
|
||||
|
||||
// TODO FIXME It doesn't work because usedObjectsMapNames maybe be filled after.
|
||||
// TODO FIXME What about groups using the object?
|
||||
// Flag the picking list as modified.
|
||||
auto objectsMapName = GetObjectMapName(objectName, context);
|
||||
if (context.GetUsedObjectsMapNames().find(objectsMapName) != context.GetUsedObjectsMapNames().end()) {
|
||||
conditionCode += objectsMapName + ".isPicked = true;\n";
|
||||
}
|
||||
|
||||
// Generate whole condition code
|
||||
conditionCode +=
|
||||
"for (var i = 0, k = 0, l = " + GetObjectListName(objectName, context) +
|
||||
@@ -823,6 +831,14 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
|
||||
<< "\" requested for object \'" << objectName
|
||||
<< "\" (condition: " << instrInfos.GetFullName() << ")." << endl;
|
||||
} else {
|
||||
// TODO FIXME It doesn't work because usedObjectsMapNames maybe be filled after.
|
||||
// TODO FIXME What about groups using the object?
|
||||
// Flag the picking list as modified.
|
||||
auto objectsMapName = GetObjectMapName(objectName, context);
|
||||
if (context.GetUsedObjectsMapNames().find(objectsMapName) != context.GetUsedObjectsMapNames().end()) {
|
||||
conditionCode += objectsMapName + ".isPicked = true;\n";
|
||||
}
|
||||
|
||||
conditionCode +=
|
||||
"for (var i = 0, k = 0, l = " + GetObjectListName(objectName, context) +
|
||||
".length;i<l;++i) {\n";
|
||||
@@ -1041,7 +1057,7 @@ gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
|
||||
if (!context.ObjectAlreadyDeclaredByParents(object)) {
|
||||
objectListDeclaration += "gdjs.copyArray(" +
|
||||
GenerateAllInstancesGetterCode(object, context) +
|
||||
", " + GetObjectListName(object, context) + ");";
|
||||
", " + GetObjectListName(object, context) + ");\n";
|
||||
} else
|
||||
objectListDeclaration = declareObjectListFromParent(object, context);
|
||||
|
||||
@@ -1068,6 +1084,10 @@ gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
|
||||
|
||||
declarationsCode += objectListDeclaration + "\n";
|
||||
}
|
||||
|
||||
for (auto objectsMapName : context.GetUsedObjectsMapNames()) {
|
||||
declarationsCode += objectsMapName + ".isPicked = false;\n";
|
||||
}
|
||||
|
||||
return declarationsCode;
|
||||
}
|
||||
@@ -1183,6 +1203,26 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
return argOutput;
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GetObjectMapName(
|
||||
const gd::String& objectName,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
|
||||
std::vector<gd::String> realObjects =
|
||||
GetObjectsContainersList().ExpandObjectName(objectName,
|
||||
context.GetCurrentObject());
|
||||
|
||||
// The map name must be unique for each set of objects lists.
|
||||
// We generate it from the objects lists names.
|
||||
gd::String objectsMapName = GetCodeNamespaceAccessor() + "mapOf";
|
||||
|
||||
// Map each declared object to its list.
|
||||
for (auto &objectName : realObjects) {
|
||||
objectsMapName += ManObjListName(GetObjectListName(objectName, context));
|
||||
}
|
||||
|
||||
return objectsMapName;
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateObject(
|
||||
const gd::String& objectName,
|
||||
const gd::String& type,
|
||||
@@ -1234,6 +1274,7 @@ gd::String EventsCodeGenerator::GenerateObject(
|
||||
for (auto& objectName : realObjects) context.ObjectsListNeeded(objectName);
|
||||
|
||||
gd::String objectsMapName = declareMapOfObjects(realObjects, context);
|
||||
context.AddUsedObjectsMapNames(objectsMapName);
|
||||
output = objectsMapName;
|
||||
} else if (type == "objectListOrEmptyIfJustDeclared") {
|
||||
std::vector<gd::String> realObjects =
|
||||
@@ -1243,6 +1284,7 @@ gd::String EventsCodeGenerator::GenerateObject(
|
||||
context.ObjectsListNeededOrEmptyIfJustDeclared(objectName);
|
||||
|
||||
gd::String objectsMapName = declareMapOfObjects(realObjects, context);
|
||||
context.AddUsedObjectsMapNames(objectsMapName);
|
||||
output = objectsMapName;
|
||||
} else if (type == "objectListOrEmptyWithoutPicking") {
|
||||
std::vector<gd::String> realObjects =
|
||||
@@ -1264,6 +1306,7 @@ gd::String EventsCodeGenerator::GenerateObject(
|
||||
|
||||
gd::String objectsMapName = declareMapOfObjects(
|
||||
objectToBeDeclaredNames, context, objectNotYetDeclaredNames);
|
||||
context.AddUsedObjectsMapNames(objectsMapName);
|
||||
output = objectsMapName;
|
||||
} else if (type == "objectPtr") {
|
||||
std::vector<gd::String> realObjects =
|
||||
|
@@ -335,6 +335,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
const gd::String& type,
|
||||
gd::EventsCodeGenerationContext& context) override;
|
||||
|
||||
virtual gd::String GetObjectMapName(const gd::String &objectName,
|
||||
gd::EventsCodeGenerationContext &context) override;
|
||||
|
||||
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const override {
|
||||
return "!(" + predicate + ")";
|
||||
};
|
||||
|
@@ -205,13 +205,13 @@ BaseObjectExtension::BaseObjectExtension() {
|
||||
"gdjs.evtTools.object.getPickedInstancesCount");
|
||||
|
||||
GetAllConditions()["CollisionNP"].SetFunctionName(
|
||||
"gdjs.evtTools.object.hitBoxesCollisionTest");
|
||||
"gdjs.evtTools.object.position.hitBoxesCollisionCheck");
|
||||
GetAllConditions()["Raycast"].SetFunctionName(
|
||||
"gdjs.evtTools.object.raycastObject");
|
||||
GetAllConditions()["RaycastToPosition"].SetFunctionName(
|
||||
"gdjs.evtTools.object.raycastObjectToPosition");
|
||||
GetAllConditions()["Distance"].SetFunctionName(
|
||||
"gdjs.evtTools.object.distanceTest");
|
||||
"gdjs.evtTools.object.position.distanceCheck");
|
||||
GetAllConditions()["SeDirige"].SetFunctionName(
|
||||
"gdjs.evtTools.object.movesTowardTest");
|
||||
GetAllConditions()["EstTourne"].SetFunctionName(
|
||||
|
@@ -659,6 +659,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
// First, do not forget common includes (they must be included before events
|
||||
// generated code files).
|
||||
InsertUnique(includesFiles, "libs/jshashtable.js");
|
||||
InsertUnique(includesFiles, "ObjectsLists.js");
|
||||
InsertUnique(includesFiles, "logger.js");
|
||||
InsertUnique(includesFiles, "gd.js");
|
||||
InsertUnique(includesFiles, "libs/rbush.js");
|
||||
@@ -670,6 +671,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "ResourceCache.js");
|
||||
InsertUnique(includesFiles, "timemanager.js");
|
||||
InsertUnique(includesFiles, "polygon.js");
|
||||
InsertUnique(includesFiles, "ObjectSleepState.js");
|
||||
InsertUnique(includesFiles, "ObjectManager.js");
|
||||
InsertUnique(includesFiles, "runtimeobject.js");
|
||||
InsertUnique(includesFiles, "profiler.js");
|
||||
InsertUnique(includesFiles, "RuntimeInstanceContainer.js");
|
||||
@@ -697,6 +700,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "events-tools/runtimescenetools.js");
|
||||
InsertUnique(includesFiles, "events-tools/inputtools.js");
|
||||
InsertUnique(includesFiles, "events-tools/objecttools.js");
|
||||
InsertUnique(includesFiles, "events-tools/ObjectPositionTools.js");
|
||||
InsertUnique(includesFiles, "events-tools/cameratools.js");
|
||||
InsertUnique(includesFiles, "events-tools/soundtools.js");
|
||||
InsertUnique(includesFiles, "events-tools/storagetools.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.
|
||||
*/
|
||||
|
118
GDJS/Runtime/ObjectManager.ts
Normal file
118
GDJS/Runtime/ObjectManager.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
namespace gdjs {
|
||||
// TODO Do something like BehaviorRBushAABB
|
||||
// TODO Allow to use getVisibilityAABB or getAABB
|
||||
/**
|
||||
* Allow to do spacial searches on objects as fast as possible.
|
||||
*
|
||||
* Objects are put in an R-Tree only if they didn't move recently to avoid to
|
||||
* update the R-Tree too often.
|
||||
*/
|
||||
export class ObjectManager {
|
||||
private _allInstances: Array<RuntimeObject> = [];
|
||||
private _awakeInstances: Array<RuntimeObject> = [];
|
||||
private _rbush: RBush<RuntimeObject>;
|
||||
|
||||
constructor() {
|
||||
this._rbush = new RBush<RuntimeObject>();
|
||||
}
|
||||
|
||||
_destroy(): void {
|
||||
this._allInstances = [];
|
||||
this._awakeInstances = [];
|
||||
this._rbush.clear();
|
||||
}
|
||||
|
||||
search(
|
||||
searchArea: SearchArea,
|
||||
results: Array<RuntimeObject>
|
||||
): Array<RuntimeObject> {
|
||||
let instances = this._allInstances;
|
||||
if (instances.length >= 8) {
|
||||
this._rbush.search(searchArea, results);
|
||||
instances = this._awakeInstances;
|
||||
}
|
||||
for (const instance of instances) {
|
||||
const aabb = instance.getAABB();
|
||||
if (
|
||||
aabb.min[0] <= searchArea.maxX &&
|
||||
aabb.max[0] >= searchArea.minX &&
|
||||
aabb.min[1] <= searchArea.maxY &&
|
||||
aabb.max[1] >= searchArea.minY
|
||||
) {
|
||||
results.push(instance);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private _onWakingUp(object: RuntimeObject): void {
|
||||
this._rbush.remove(object._rtreeAABB);
|
||||
this._awakeInstances.push(object);
|
||||
}
|
||||
|
||||
private _onFallenAsleep(object: RuntimeObject): void {
|
||||
const objectAABB = object.getAABB();
|
||||
this._rbush.remove(object._rtreeAABB);
|
||||
object._rtreeAABB.minX = objectAABB.min[0];
|
||||
object._rtreeAABB.minY = objectAABB.min[1];
|
||||
object._rtreeAABB.maxX = objectAABB.max[0];
|
||||
object._rtreeAABB.maxY = objectAABB.max[1];
|
||||
this._rbush.insert(object._rtreeAABB);
|
||||
}
|
||||
|
||||
updateAwakeObjects(): void {
|
||||
gdjs.ObjectSleepState.updateAwakeObjects(
|
||||
this._awakeInstances,
|
||||
(object) => object.getSpatialSearchSleepState(),
|
||||
(object) => this._onFallenAsleep(object),
|
||||
(object) => this._onWakingUp(object)
|
||||
);
|
||||
}
|
||||
|
||||
getAllInstances(): Array<RuntimeObject> {
|
||||
return this._allInstances;
|
||||
}
|
||||
|
||||
getAwakeInstances(): Array<RuntimeObject> {
|
||||
return this._awakeInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the instances living in the container.
|
||||
* @param obj The object to be added.
|
||||
*/
|
||||
addObject(object: gdjs.RuntimeObject): void {
|
||||
this._allInstances.push(object);
|
||||
this._awakeInstances.push(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called whenever an object must be removed from the container.
|
||||
* @param object The object to be removed.
|
||||
*/
|
||||
deleteObject(object: gdjs.RuntimeObject): boolean {
|
||||
const objId = object.id;
|
||||
let isObjectDeleted = false;
|
||||
for (let i = 0, len = this._allInstances.length; i < len; ++i) {
|
||||
if (this._allInstances[i].id == objId) {
|
||||
this._allInstances.splice(i, 1);
|
||||
isObjectDeleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO Maybe the state could be used but it would be more prone to errors.
|
||||
let isAwake = false;
|
||||
for (let i = 0, len = this._awakeInstances.length; i < len; ++i) {
|
||||
if (this._awakeInstances[i].id == objId) {
|
||||
this._awakeInstances.splice(i, 1);
|
||||
isAwake = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isAwake) {
|
||||
this._rbush.remove(object._rtreeAABB);
|
||||
}
|
||||
return isObjectDeleted;
|
||||
}
|
||||
}
|
||||
}
|
112
GDJS/Runtime/ObjectSleepState.ts
Normal file
112
GDJS/Runtime/ObjectSleepState.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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;
|
||||
private _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();
|
||||
}
|
||||
|
||||
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.ASleep;
|
||||
}
|
||||
|
||||
_forceToSleep(): void {
|
||||
if (!this.isAwake()) {
|
||||
return;
|
||||
}
|
||||
this._lastActivityFrameIndex = Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
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._lastActivityFrameIndex !== Number.NEGATIVE_INFINITY &&
|
||||
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._onWakingUpCallbacks.push(onWakingUp);
|
||||
} else {
|
||||
awakeObjects[writeIndex] = object;
|
||||
writeIndex++;
|
||||
}
|
||||
}
|
||||
awakeObjects.length = writeIndex;
|
||||
return awakeObjects;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ObjectSleepState {
|
||||
export enum State {
|
||||
ASleep,
|
||||
CanSleepThisFrame,
|
||||
AWake,
|
||||
}
|
||||
}
|
||||
}
|
37
GDJS/Runtime/ObjectsLists.ts
Normal file
37
GDJS/Runtime/ObjectsLists.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Picked objects lists.
|
||||
*/
|
||||
class ObjectsLists extends Hashtable<Array<gdjs.RuntimeObject>> {
|
||||
/** Is true as soon as an instruction has pick some or every instances */
|
||||
isPicked: boolean = false;
|
||||
}
|
||||
|
||||
namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace objectsLists {
|
||||
/**
|
||||
* Construct a ObjectsLists from a JS object.
|
||||
*
|
||||
* @param items The content of the Hashtable.
|
||||
* @returns The new picked objects lists.
|
||||
*/
|
||||
export const newFrom = (
|
||||
items: {
|
||||
[key: string]: Array<gdjs.RuntimeObject>;
|
||||
},
|
||||
isPicked: boolean
|
||||
): ObjectsLists => {
|
||||
const hashtable = new ObjectsLists();
|
||||
hashtable.items = items;
|
||||
hashtable.isPicked = isPicked;
|
||||
return hashtable;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,7 +13,9 @@ namespace gdjs {
|
||||
_initialBehaviorSharedData: Hashtable<BehaviorSharedData | null>;
|
||||
|
||||
/** Contains the instances living on the container */
|
||||
_instances: Hashtable<RuntimeObject[]>;
|
||||
_instances: Hashtable<ObjectManager>;
|
||||
|
||||
_activeInstances: Array<RuntimeObject> = [];
|
||||
|
||||
/**
|
||||
* An array used to create a list of all instance when necessary.
|
||||
@@ -34,7 +36,6 @@ namespace gdjs {
|
||||
|
||||
_layers: Hashtable<RuntimeLayer>;
|
||||
_orderedLayers: RuntimeLayer[]; // TODO: should this be a single structure with _layers, to enforce its usage?
|
||||
_layersCameraCoordinates: Record<string, [float, float, float, float]> = {};
|
||||
|
||||
// Options for the debug draw:
|
||||
_debugDrawEnabled: boolean = false;
|
||||
@@ -42,6 +43,8 @@ namespace gdjs {
|
||||
_debugDrawShowPointsNames: boolean = false;
|
||||
_debugDrawShowCustomPoints: boolean = false;
|
||||
|
||||
_nextPickingId = 1;
|
||||
|
||||
constructor() {
|
||||
this._initialBehaviorSharedData = new Hashtable();
|
||||
this._instances = new Hashtable();
|
||||
@@ -178,7 +181,7 @@ namespace gdjs {
|
||||
*/
|
||||
registerObject(objectData: ObjectData) {
|
||||
this._objects.put(objectData.name, objectData);
|
||||
this._instances.put(objectData.name, []);
|
||||
this._instances.put(objectData.name, new gdjs.ObjectManager());
|
||||
|
||||
// Cache the constructor
|
||||
const Ctor = gdjs.getObjectConstructor(objectData.type);
|
||||
@@ -212,7 +215,7 @@ namespace gdjs {
|
||||
* @param objectName The name of the object to unregister.
|
||||
*/
|
||||
unregisterObject(objectName: string) {
|
||||
const instances = this._instances.get(objectName);
|
||||
const instances = this._instances.get(objectName).getAllInstances();
|
||||
if (instances) {
|
||||
// This is sub-optimal: markObjectForDeletion will search the instance to
|
||||
// remove in instances, so cost is O(n^2), n being the number of instances.
|
||||
@@ -351,26 +354,6 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
_updateLayersCameraCoordinates(scale: float) {
|
||||
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
|
||||
for (const name in this._layers.items) {
|
||||
if (this._layers.items.hasOwnProperty(name)) {
|
||||
const theLayer = this._layers.items[name];
|
||||
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
|
||||
name
|
||||
] || [0, 0, 0, 0];
|
||||
this._layersCameraCoordinates[name][0] =
|
||||
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
|
||||
this._layersCameraCoordinates[name][1] =
|
||||
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
|
||||
this._layersCameraCoordinates[name][2] =
|
||||
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
|
||||
this._layersCameraCoordinates[name][3] =
|
||||
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to update effects of layers before rendering.
|
||||
*/
|
||||
@@ -446,7 +429,7 @@ namespace gdjs {
|
||||
let currentListSize = 0;
|
||||
for (const name in this._instances.items) {
|
||||
if (this._instances.items.hasOwnProperty(name)) {
|
||||
const list = this._instances.items[name];
|
||||
const list = this._instances.items[name].getAllInstances();
|
||||
const oldSize = currentListSize;
|
||||
currentListSize += list.length;
|
||||
for (let j = 0, lenj = list.length; j < lenj; ++j) {
|
||||
@@ -467,6 +450,14 @@ namespace gdjs {
|
||||
* @returns the instances of a given object in the container.
|
||||
*/
|
||||
getInstancesOf(objectName: string): gdjs.RuntimeObject[] {
|
||||
return this._instances.items[objectName].getAllInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectName The name of the object
|
||||
* @returns the manager of a given object in the container.
|
||||
*/
|
||||
getObjectManager(objectName: string): gdjs.ObjectManager {
|
||||
return this._instances.items[objectName];
|
||||
}
|
||||
|
||||
@@ -485,13 +476,30 @@ 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() {
|
||||
// Check awake objects only once every 64 frames.
|
||||
if ((this.getScene().getFrameIndex() & 63) === 0) {
|
||||
for (const name in this._instances.items) {
|
||||
const objectManager = this._instances.items[name];
|
||||
objectManager.updateAwakeObjects();
|
||||
}
|
||||
}
|
||||
// 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 +514,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 +529,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 +543,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 objectManager = this._instances.get(obj.name);
|
||||
if (!objectManager) {
|
||||
objectManager = new gdjs.ObjectManager();
|
||||
this._instances.put(obj.name, objectManager);
|
||||
}
|
||||
objectManager.addObject(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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,9 +571,9 @@ namespace gdjs {
|
||||
name +
|
||||
'"! Adding it.'
|
||||
);
|
||||
this._instances.put(name, []);
|
||||
this._instances.put(name, new gdjs.ObjectManager());
|
||||
}
|
||||
return this._instances.get(name);
|
||||
return this._instances.get(name).getAllInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,15 +620,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// Delete from the living instances.
|
||||
if (this._instances.containsKey(obj.getName())) {
|
||||
const objId = obj.id;
|
||||
const allInstances = this._instances.get(obj.getName());
|
||||
for (let i = 0, len = allInstances.length; i < len; ++i) {
|
||||
if (allInstances[i].id == objId) {
|
||||
allInstances.splice(i, 1);
|
||||
this._allInstancesListIsUpToDate = false;
|
||||
break;
|
||||
}
|
||||
const objectManager = this._instances.get(obj.getName());
|
||||
if (objectManager) {
|
||||
const hasDeleted = objectManager.deleteObject(obj);
|
||||
if (hasDeleted) {
|
||||
this._allInstancesListIsUpToDate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,6 +638,8 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
onObjectChangedOfLayer(object: RuntimeObject, oldLayer: RuntimeLayer) {}
|
||||
|
||||
/**
|
||||
* Get the layer with the given name
|
||||
* @param name The name of the layer
|
||||
@@ -707,7 +722,7 @@ namespace gdjs {
|
||||
getInstancesCountOnScene(objectName: string): integer {
|
||||
const instances = this._instances.get(objectName);
|
||||
if (instances) {
|
||||
return instances.length;
|
||||
return instances.getAllInstances().length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -719,7 +734,7 @@ namespace gdjs {
|
||||
updateObjectsForces(): void {
|
||||
for (const name in this._instances.items) {
|
||||
if (this._instances.items.hasOwnProperty(name)) {
|
||||
const list = this._instances.items[name];
|
||||
const list = this._instances.items[name].getAwakeInstances();
|
||||
for (let j = 0, listLen = list.length; j < listLen; ++j) {
|
||||
const obj = list[j];
|
||||
if (!obj.hasNoForces()) {
|
||||
@@ -750,5 +765,14 @@ namespace gdjs {
|
||||
this._allInstancesList = [];
|
||||
this._instancesRemoved = [];
|
||||
}
|
||||
|
||||
getNewPickingId(): integer {
|
||||
const newPickingId = this._nextPickingId;
|
||||
if (this._nextPickingId === Number.MAX_SAFE_INTEGER) {
|
||||
this._nextPickingId = 0;
|
||||
}
|
||||
this._nextPickingId++;
|
||||
return newPickingId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
227
GDJS/Runtime/events-tools/ObjectPositionTools.ts
Normal file
227
GDJS/Runtime/events-tools/ObjectPositionTools.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace object {
|
||||
export namespace position {
|
||||
type SearchArea = {
|
||||
minX: float;
|
||||
minY: float;
|
||||
maxX: float;
|
||||
maxY: float;
|
||||
};
|
||||
|
||||
const searchArea = { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
||||
const nearbyObjects: Array<RuntimeObject> = [];
|
||||
|
||||
export const twoListsSpatialCheck = (
|
||||
instanceContainer: RuntimeInstanceContainer | EventsFunctionContext,
|
||||
predicate: (
|
||||
object1: gdjs.RuntimeObject,
|
||||
object2: gdjs.RuntimeObject,
|
||||
extraArg: any
|
||||
) => boolean,
|
||||
getSearchArea: (
|
||||
object: gdjs.RuntimeObject,
|
||||
searchArea: SearchArea,
|
||||
extraArg: any
|
||||
) => SearchArea,
|
||||
objectsLists1: ObjectsLists,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean,
|
||||
predicateExtraArg?: any,
|
||||
areaExtraArg?: any
|
||||
): boolean => {
|
||||
const isPicked1 = objectsLists1.isPicked;
|
||||
const isPicked2 = objectsLists2.isPicked;
|
||||
|
||||
if (isPicked1 && isPicked2) {
|
||||
// Both are already filtered fallback on the naïve check
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
predicate,
|
||||
objectsLists1,
|
||||
objectsLists2,
|
||||
inverted,
|
||||
predicateExtraArg
|
||||
);
|
||||
}
|
||||
if (
|
||||
inverted ||
|
||||
!(instanceContainer instanceof gdjs.RuntimeInstanceContainer)
|
||||
) {
|
||||
// Fast spatial conditions are using objectListOrEmptyIfJustDeclared
|
||||
// where naive spatial implementation is expecting objectList.
|
||||
if (!isPicked1) {
|
||||
fillPickedLists(instanceContainer, objectsLists1);
|
||||
}
|
||||
if (!isPicked2) {
|
||||
fillPickedLists(instanceContainer, objectsLists2);
|
||||
}
|
||||
// Both are already filtered fallback on the naïve check
|
||||
return gdjs.evtTools.object.twoListsTest(
|
||||
predicate,
|
||||
objectsLists1,
|
||||
objectsLists2,
|
||||
inverted,
|
||||
predicateExtraArg
|
||||
);
|
||||
}
|
||||
let isAnyObjectPicked = false;
|
||||
|
||||
let iteratedLists = isPicked1 ? objectsLists1 : objectsLists2;
|
||||
let treeLists = isPicked1 ? objectsLists2 : objectsLists1;
|
||||
|
||||
let objectsMaxCount1 = 0;
|
||||
if (!isPicked1) {
|
||||
for (const objectName in objectsLists1.items) {
|
||||
const objects = instanceContainer.getObjects(objectName);
|
||||
objectsMaxCount1 = Math.max(
|
||||
objectsMaxCount1,
|
||||
objects ? objects.length : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
let objectsMaxCount2 = 0;
|
||||
if (!isPicked2) {
|
||||
for (const objectName in objectsLists2.items) {
|
||||
const objects = instanceContainer.getObjects(objectName);
|
||||
objectsMaxCount2 = Math.max(
|
||||
objectsMaxCount2,
|
||||
objects ? objects.length : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isPicked1 && !isPicked2) {
|
||||
if (objectsMaxCount1 < objectsMaxCount2) {
|
||||
iteratedLists = objectsLists1;
|
||||
treeLists = objectsLists2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!iteratedLists.isPicked) {
|
||||
fillPickedLists(instanceContainer, iteratedLists);
|
||||
}
|
||||
|
||||
const pickingId = instanceContainer.getNewPickingId();
|
||||
for (const iteratedObjectName in iteratedLists.items) {
|
||||
const iteratedObjects = iteratedLists.items[iteratedObjectName];
|
||||
|
||||
let isAnyIteratedObjectPicked = false;
|
||||
for (const objectName in treeLists.items) {
|
||||
const treeObjects = treeLists.items[objectName];
|
||||
const objectManager = instanceContainer.getObjectManager(
|
||||
objectName
|
||||
);
|
||||
for (const object of iteratedObjects) {
|
||||
nearbyObjects.length = 0;
|
||||
objectManager.search(
|
||||
getSearchArea(object, searchArea, areaExtraArg),
|
||||
nearbyObjects
|
||||
);
|
||||
for (const nearbyObject of nearbyObjects) {
|
||||
if (predicate(object, nearbyObject, predicateExtraArg)) {
|
||||
isAnyObjectPicked = true;
|
||||
isAnyIteratedObjectPicked = true;
|
||||
object.pickingId = pickingId;
|
||||
if (nearbyObject.pickingId !== pickingId) {
|
||||
treeObjects.push(nearbyObject);
|
||||
nearbyObject.pickingId = pickingId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAnyIteratedObjectPicked) {
|
||||
gdjs.evtTools.object.filterPickedObjectsListWithId(
|
||||
iteratedObjects,
|
||||
pickingId
|
||||
);
|
||||
} else if (iteratedLists.isPicked) {
|
||||
iteratedObjects.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return isAnyObjectPicked;
|
||||
};
|
||||
|
||||
const fillPickedLists = (
|
||||
instanceContainer: RuntimeInstanceContainer | EventsFunctionContext,
|
||||
objectsLists: ObjectsLists
|
||||
) => {
|
||||
for (const objectName in objectsLists.items) {
|
||||
const pickedObjects = objectsLists.items[objectName];
|
||||
const allObjects = instanceContainer.getObjects(objectName);
|
||||
pickedObjects.push.apply(pickedObjects, allObjects);
|
||||
}
|
||||
};
|
||||
|
||||
const getSearchAreaForDistanceCheck = (
|
||||
object: gdjs.RuntimeObject,
|
||||
searchArea: SearchArea,
|
||||
distance: float
|
||||
): SearchArea => {
|
||||
const centerX = object.getX();
|
||||
const centerY = object.getY();
|
||||
searchArea.minX = centerX - distance;
|
||||
searchArea.maxX = centerX + distance;
|
||||
searchArea.minY = centerY - distance;
|
||||
searchArea.maxY = centerY + distance;
|
||||
return searchArea;
|
||||
};
|
||||
|
||||
export const distanceCheck = (
|
||||
objectsLists1: ObjectsLists,
|
||||
objectsLists2: ObjectsLists,
|
||||
distance: float,
|
||||
inverted: boolean,
|
||||
instanceContainer:
|
||||
| gdjs.RuntimeInstanceContainer
|
||||
| EventsFunctionContext
|
||||
): boolean => {
|
||||
return twoListsSpatialCheck(
|
||||
instanceContainer,
|
||||
gdjs.evtTools.object._distanceBetweenObjects,
|
||||
getSearchAreaForDistanceCheck,
|
||||
objectsLists1,
|
||||
objectsLists2,
|
||||
inverted,
|
||||
distance * distance,
|
||||
distance
|
||||
);
|
||||
};
|
||||
|
||||
const getSearchAreaForCollisionCheck = (
|
||||
object: gdjs.RuntimeObject,
|
||||
searchArea: SearchArea
|
||||
): SearchArea => {
|
||||
const centerX = object.getCenterXInScene();
|
||||
const centerY = object.getCenterYInScene();
|
||||
const boundingRadius = Math.sqrt(object.getSqBoundingRadius());
|
||||
searchArea.minX = centerX - boundingRadius;
|
||||
searchArea.maxX = centerX + boundingRadius;
|
||||
searchArea.minY = centerY - boundingRadius;
|
||||
searchArea.maxY = centerY + boundingRadius;
|
||||
return searchArea;
|
||||
};
|
||||
|
||||
export const hitBoxesCollisionCheck = (
|
||||
objectsLists1: ObjectsLists,
|
||||
objectsLists2: ObjectsLists,
|
||||
inverted: boolean,
|
||||
instanceContainer:
|
||||
| gdjs.RuntimeInstanceContainer
|
||||
| EventsFunctionContext,
|
||||
ignoreTouchingEdges: boolean
|
||||
): boolean => {
|
||||
return twoListsSpatialCheck(
|
||||
instanceContainer,
|
||||
gdjs.RuntimeObject.collisionTest,
|
||||
getSearchAreaForCollisionCheck,
|
||||
objectsLists1,
|
||||
objectsLists2,
|
||||
inverted,
|
||||
ignoreTouchingEdges
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -388,7 +388,7 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
export const cursorOnObject = function (
|
||||
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
|
||||
objectsLists: ObjectsLists,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
accurate: boolean,
|
||||
inverted: boolean
|
||||
|
@@ -218,6 +218,25 @@ namespace gdjs {
|
||||
arr.length = finalSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter in-place the specified array to remove objects for which
|
||||
* `pick` property is set to false.
|
||||
*/
|
||||
export const filterPickedObjectsListWithId = function (
|
||||
objects: gdjs.RuntimeObject[],
|
||||
pickingId: integer
|
||||
) {
|
||||
let finalSize = 0;
|
||||
for (let k = 0, lenk = objects.length; k < lenk; ++k) {
|
||||
const obj = objects[k];
|
||||
if (obj.pickingId === pickingId) {
|
||||
objects[finalSize] = obj;
|
||||
finalSize++;
|
||||
}
|
||||
}
|
||||
objects.length = finalSize;
|
||||
};
|
||||
|
||||
export const hitBoxesCollisionTest = function (
|
||||
objectsLists1: ObjectsLists,
|
||||
objectsLists2: ObjectsLists,
|
||||
|
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,21 +16,6 @@ namespace gdjs {
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the squared bounding radius of an object given its width/height and its center of rotation
|
||||
* (relative to the top-left of the object). The radius is relative to the center of rotation.
|
||||
*/
|
||||
const computeSqBoundingRadius = (
|
||||
width: float,
|
||||
height: float,
|
||||
centerX: float,
|
||||
centerY: float
|
||||
) => {
|
||||
const radiusX = Math.max(centerX, width - centerX);
|
||||
const radiusY = Math.max(centerY, height - centerY);
|
||||
return Math.pow(radiusX, 2) + Math.pow(radiusY, 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Arrays and data structure that are (re)used by
|
||||
* {@link RuntimeObject.separateFromObjects} to avoid any allocation.
|
||||
@@ -147,6 +132,8 @@ namespace gdjs {
|
||||
return true;
|
||||
};
|
||||
|
||||
type RuntimeObjectCallback = (object: gdjs.RuntimeObject) => void;
|
||||
|
||||
/**
|
||||
* RuntimeObject represents an object being used on a RuntimeScene.
|
||||
*
|
||||
@@ -164,9 +151,13 @@ namespace gdjs {
|
||||
layer: string = '';
|
||||
protected _nameId: integer;
|
||||
protected _livingOnScene: boolean = true;
|
||||
protected _lifecycleSleepState: ObjectSleepState;
|
||||
protected _spatialSearchSleepState: 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 +172,16 @@ 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] };
|
||||
_rtreeAABB: SearchedItem<RuntimeObject>;
|
||||
|
||||
protected _isIncludedInParentCollisionMask = true;
|
||||
|
||||
//Variables:
|
||||
@@ -212,6 +207,7 @@ namespace gdjs {
|
||||
* are never used.
|
||||
*/
|
||||
protected _behaviors: gdjs.RuntimeBehavior[] = [];
|
||||
protected _activeBehaviors: gdjs.RuntimeBehavior[] = [];
|
||||
/**
|
||||
* Contains the behaviors of the object by name.
|
||||
*
|
||||
@@ -229,10 +225,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 +238,25 @@ namespace gdjs {
|
||||
);
|
||||
this._totalForce = new gdjs.Force(0, 0, 0);
|
||||
this._behaviorsTable = new Hashtable();
|
||||
this._lifecycleSleepState = new gdjs.ObjectSleepState(
|
||||
this,
|
||||
() => this.isNeedingLifecycleFunctions(),
|
||||
gdjs.ObjectSleepState.State.ASleep
|
||||
);
|
||||
this._rtreeAABB = {
|
||||
source: this,
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
maxX: 0,
|
||||
maxY: 0,
|
||||
};
|
||||
this._spatialSearchSleepState = new gdjs.ObjectSleepState(
|
||||
this,
|
||||
() => false,
|
||||
gdjs.ObjectSleepState.State.CanSleepThisFrame
|
||||
);
|
||||
for (let i = 0; i < objectData.effects.length; ++i) {
|
||||
this._runtimeScene
|
||||
scene
|
||||
.getGame()
|
||||
.getEffectsManager()
|
||||
.initializeEffect(objectData.effects[i], this._rendererEffects, this);
|
||||
@@ -253,8 +267,10 @@ 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);
|
||||
}
|
||||
@@ -326,6 +342,7 @@ namespace gdjs {
|
||||
// Reinitialize behaviors.
|
||||
this._behaviorsTable.clear();
|
||||
const behaviorsDataCount = objectData.behaviors.length;
|
||||
let behaviorsCount = 0;
|
||||
let behaviorsUsingLifecycleFunctionCount = 0;
|
||||
for (
|
||||
let behaviorDataIndex = 0;
|
||||
@@ -336,17 +353,28 @@ 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;
|
||||
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 +467,26 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
return (
|
||||
this._activeBehaviors.length > 0 ||
|
||||
!this.hasNoForces() ||
|
||||
!!this._timers.firstKey()
|
||||
);
|
||||
}
|
||||
|
||||
getLifecycleSleepState(): ObjectSleepState {
|
||||
return this._lifecycleSleepState;
|
||||
}
|
||||
|
||||
getSpatialSearchSleepState(): ObjectSleepState {
|
||||
return this._spatialSearchSleepState;
|
||||
}
|
||||
|
||||
isAlive(): boolean {
|
||||
return this._livingOnScene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object from a scene.
|
||||
*
|
||||
@@ -449,6 +497,7 @@ namespace gdjs {
|
||||
if (this._livingOnScene) {
|
||||
instanceContainer.markObjectForDeletion(this);
|
||||
this._livingOnScene = false;
|
||||
this._lifecycleSleepState._forceToSleep();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +535,31 @@ 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._spatialSearchSleepState.wakeUp();
|
||||
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 +644,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.
|
||||
*
|
||||
@@ -758,6 +818,7 @@ namespace gdjs {
|
||||
oldLayer.getRenderer().remove3DRendererObject(rendererObject3D);
|
||||
newLayer.getRenderer().add3DRendererObject(rendererObject3D);
|
||||
}
|
||||
this._runtimeScene.onObjectChangedOfLayer(this, oldLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1363,6 +1424,7 @@ namespace gdjs {
|
||||
// (or the 1st instant force).
|
||||
this._instantForces.push(this._getRecycledForce(x, y, multiplier));
|
||||
}
|
||||
this._lifecycleSleepState.wakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1559,6 +1621,18 @@ namespace gdjs {
|
||||
return this.hitBoxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the squared bounding radius of an object given its width/height and its center of rotation
|
||||
* (relative to the top-left of the object). The radius is relative to the center of rotation.
|
||||
*/
|
||||
getSqBoundingRadius() {
|
||||
const centerX = this.getCenterX();
|
||||
const centerY = this.getCenterY();
|
||||
const radiusX = Math.max(centerX, this.getWidth() - centerX);
|
||||
const radiusY = Math.max(centerY, this.getHeight() - centerY);
|
||||
return radiusX * radiusX + radiusY * radiusY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return at least all the hit boxes that overlap a given area.
|
||||
*
|
||||
@@ -1781,8 +1855,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 +1866,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 +1944,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 +2008,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 +2033,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 +2045,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 +2057,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);
|
||||
}
|
||||
@@ -2392,25 +2478,11 @@ namespace gdjs {
|
||||
//First check if bounding circle are too far.
|
||||
const o1centerX = obj1.getCenterX();
|
||||
const o1centerY = obj1.getCenterY();
|
||||
const obj1BoundingRadius = Math.sqrt(
|
||||
computeSqBoundingRadius(
|
||||
obj1.getWidth(),
|
||||
obj1.getHeight(),
|
||||
o1centerX,
|
||||
o1centerY
|
||||
)
|
||||
);
|
||||
const obj1BoundingRadius = Math.sqrt(obj1.getSqBoundingRadius());
|
||||
|
||||
const o2centerX = obj2.getCenterX();
|
||||
const o2centerY = obj2.getCenterY();
|
||||
const obj2BoundingRadius = Math.sqrt(
|
||||
computeSqBoundingRadius(
|
||||
obj2.getWidth(),
|
||||
obj2.getHeight(),
|
||||
o2centerX,
|
||||
o2centerY
|
||||
)
|
||||
);
|
||||
const obj2BoundingRadius = Math.sqrt(obj2.getSqBoundingRadius());
|
||||
|
||||
const o1AbsoluteCenterX = obj1.getDrawableX() + o1centerX;
|
||||
const o1AbsoluteCenterY = obj1.getDrawableY() + o1centerY;
|
||||
@@ -2471,12 +2543,7 @@ namespace gdjs {
|
||||
// First check if bounding circles are too far
|
||||
const objCenterX = this.getCenterX();
|
||||
const objCenterY = this.getCenterY();
|
||||
const objSqBoundingRadius = computeSqBoundingRadius(
|
||||
this.getWidth(),
|
||||
this.getHeight(),
|
||||
objCenterX,
|
||||
objCenterY
|
||||
);
|
||||
const objSqBoundingRadius = this.getSqBoundingRadius();
|
||||
|
||||
const rayCenterWorldX = (x + endX) / 2;
|
||||
const rayCenterWorldY = (y + endY) / 2;
|
||||
|
@@ -6,6 +6,7 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('RuntimeScene');
|
||||
const setupWarningLogger = new gdjs.Logger('RuntimeScene (setup warnings)');
|
||||
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
|
||||
|
||||
/**
|
||||
* A scene being played, containing instances of objects rendered on screen.
|
||||
@@ -45,6 +46,12 @@ namespace gdjs {
|
||||
_cachedGameResolutionWidth: integer;
|
||||
_cachedGameResolutionHeight: integer;
|
||||
|
||||
_frameIndex: integer = 0;
|
||||
|
||||
_layersCameraCoordinates: Record<string, SearchArea> = {};
|
||||
_layerObjectManagers = new Map<string, ObjectManager>();
|
||||
_cameraObjects: Record<string, Array<RuntimeObject>> = {};
|
||||
|
||||
/**
|
||||
* @param runtimeGame The game associated to this scene.
|
||||
*/
|
||||
@@ -81,6 +88,43 @@ namespace gdjs {
|
||||
this._orderedLayers.push(layer);
|
||||
}
|
||||
|
||||
addObject(object: gdjs.RuntimeObject): void {
|
||||
super.addObject(object);
|
||||
this._addObjectToLayerObjectManager(object);
|
||||
}
|
||||
|
||||
onObjectChangedOfLayer(object: RuntimeObject, oldLayer: RuntimeLayer) {
|
||||
this._removeObjectFromLayerObjectManager(object, oldLayer.getName());
|
||||
this._addObjectToLayerObjectManager(object);
|
||||
}
|
||||
|
||||
private _addObjectToLayerObjectManager(object: gdjs.RuntimeObject): void {
|
||||
const layerName = object.getLayer();
|
||||
let objectManager = this._layerObjectManagers.get(layerName);
|
||||
if (!objectManager) {
|
||||
objectManager = new gdjs.ObjectManager();
|
||||
this._layerObjectManagers.set(layerName, objectManager);
|
||||
}
|
||||
objectManager.addObject(object);
|
||||
}
|
||||
|
||||
markObjectForDeletion(object: gdjs.RuntimeObject): void {
|
||||
super.markObjectForDeletion(object);
|
||||
const layerName = object.getLayer();
|
||||
this._removeObjectFromLayerObjectManager(object, layerName);
|
||||
}
|
||||
|
||||
private _removeObjectFromLayerObjectManager(
|
||||
object: gdjs.RuntimeObject,
|
||||
layerName: string
|
||||
): void {
|
||||
let objectManager = this._layerObjectManagers.get(layerName);
|
||||
if (!objectManager) {
|
||||
return;
|
||||
}
|
||||
objectManager.deleteObject(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the canvas where the scene is rendered has been resized.
|
||||
* See gdjs.RuntimeGame.startGameLoop in particular.
|
||||
@@ -427,6 +471,7 @@ namespace gdjs {
|
||||
if (this._profiler) {
|
||||
this._profiler.endFrame();
|
||||
}
|
||||
this._frameIndex++;
|
||||
return !!this.getRequestedChange();
|
||||
}
|
||||
|
||||
@@ -437,6 +482,26 @@ namespace gdjs {
|
||||
this._renderer.render();
|
||||
}
|
||||
|
||||
_updateLayersCameraCoordinates(scale: float) {
|
||||
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
|
||||
for (const name in this._layers.items) {
|
||||
if (this._layers.items.hasOwnProperty(name)) {
|
||||
const theLayer = this._layers.items[name];
|
||||
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
|
||||
name
|
||||
] || { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
||||
this._layersCameraCoordinates[name].minX =
|
||||
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
|
||||
this._layersCameraCoordinates[name].minY =
|
||||
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
|
||||
this._layersCameraCoordinates[name].maxX =
|
||||
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
|
||||
this._layersCameraCoordinates[name].maxY =
|
||||
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to update visibility of the renderers of objects
|
||||
* rendered on the scene ("culling"), update effects (of visible objects)
|
||||
@@ -446,50 +511,74 @@ namespace gdjs {
|
||||
* object is too far from the camera of its layer ("culling").
|
||||
*/
|
||||
_updateObjectsPreRender() {
|
||||
// Check awake objects only once every 64 frames.
|
||||
if ((this._frameIndex & 63) === 0) {
|
||||
for (const objectManager of this._layerObjectManagers.values()) {
|
||||
objectManager.updateAwakeObjects();
|
||||
}
|
||||
}
|
||||
if (this._timeManager.isFirstFrame()) {
|
||||
super._updateObjectsPreRender();
|
||||
return;
|
||||
} else {
|
||||
// After first frame, optimise rendering by setting only objects
|
||||
// near camera as visible.
|
||||
// TODO: For compatibility, pass a scale of `2`,
|
||||
// meaning that size of cameras will be multiplied by 2 and so objects
|
||||
// will be hidden if they are outside of this *larger* camera area.
|
||||
// This is useful for:
|
||||
// - objects not properly reporting their visibility AABB,
|
||||
// (so we have a "safety margin") but these objects should be fixed
|
||||
// instead.
|
||||
// - objects having effects rendering outside of their visibility AABB.
|
||||
|
||||
// TODO (3D) culling - add support for 3D object culling?
|
||||
this._updateLayersCameraCoordinates(2);
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
const rendererObject = object.getRendererObject();
|
||||
if (rendererObject) {
|
||||
if (object.isHidden()) {
|
||||
rendererObject.visible = false;
|
||||
} else {
|
||||
const cameraCoords = this._layersCameraCoordinates[
|
||||
object.getLayer()
|
||||
];
|
||||
if (!cameraCoords) {
|
||||
continue;
|
||||
}
|
||||
const aabb = object.getVisibilityAABB();
|
||||
rendererObject.visible =
|
||||
// If no AABB is returned, the object should always be visible
|
||||
!aabb ||
|
||||
// If an AABB is there, it must be at least partially inside
|
||||
// the camera bounds.
|
||||
!(
|
||||
aabb.min[0] > cameraCoords[2] ||
|
||||
aabb.min[1] > cameraCoords[3] ||
|
||||
aabb.max[0] < cameraCoords[0] ||
|
||||
aabb.max[1] < cameraCoords[1]
|
||||
);
|
||||
}
|
||||
rendererObject.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// After first frame, optimise rendering by setting only objects
|
||||
// near camera as visible.
|
||||
// TODO: For compatibility, pass a scale of `2`,
|
||||
// meaning that size of cameras will be multiplied by 2 and so objects
|
||||
// will be hidden if they are outside of this *larger* camera area.
|
||||
// This is useful for:
|
||||
// - objects not properly reporting their visibility AABB,
|
||||
// (so we have a "safety margin") but these objects should be fixed
|
||||
// instead.
|
||||
// - objects having effects rendering outside of their visibility AABB.
|
||||
|
||||
// TODO (3D) culling - add support for 3D object culling?
|
||||
this._updateLayersCameraCoordinates(2);
|
||||
if (this._frameIndex === 2) {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
const rendererObject = object.getRendererObject();
|
||||
if (rendererObject) {
|
||||
rendererObject.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const layerName in this._cameraObjects) {
|
||||
for (const object of this._cameraObjects[layerName]) {
|
||||
const rendererObject = object.getRendererObject();
|
||||
if (rendererObject) {
|
||||
rendererObject.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const layerName in this._layers.items) {
|
||||
const cameraAABB = this._layersCameraCoordinates[layerName];
|
||||
let cameraObjects = this._cameraObjects[layerName];
|
||||
if (cameraObjects === undefined) {
|
||||
cameraObjects = [];
|
||||
this._cameraObjects[layerName] = cameraObjects;
|
||||
}
|
||||
if (!cameraAABB) {
|
||||
continue;
|
||||
}
|
||||
const layerObjectManager = this._layerObjectManagers.get(layerName);
|
||||
if (!layerObjectManager) {
|
||||
continue;
|
||||
}
|
||||
cameraObjects.length = 0;
|
||||
layerObjectManager.search(cameraAABB, cameraObjects);
|
||||
for (const object of cameraObjects) {
|
||||
const rendererObject = object.getRendererObject();
|
||||
if (rendererObject) {
|
||||
rendererObject.visible = !object.isHidden();
|
||||
|
||||
// Update effects, only for visible objects.
|
||||
if (rendererObject.visible) {
|
||||
@@ -734,6 +823,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,17 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
isNeedingLifecycleFunctions(): boolean {
|
||||
return (
|
||||
super.isNeedingLifecycleFunctions() ||
|
||||
(!this.isAnimationPaused() &&
|
||||
this._animations[this._currentAnimation].directions[
|
||||
this._currentDirection
|
||||
].frames.length > 1 &&
|
||||
!this.hasAnimationEnded())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current frame of the object according to the elapsed time on the scene.
|
||||
*/
|
||||
@@ -617,6 +632,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 +884,7 @@ namespace gdjs {
|
||||
|
||||
resumeAnimation(): void {
|
||||
this._animationPaused = false;
|
||||
this.getLifecycleSleepState().wakeUp();
|
||||
}
|
||||
|
||||
getAnimationSpeedScale() {
|
||||
|
3
GDJS/Runtime/types/global-types.d.ts
vendored
3
GDJS/Runtime/types/global-types.d.ts
vendored
@@ -13,9 +13,6 @@ declare type float = number;
|
||||
/** A point in cartesian space. */
|
||||
declare type FloatPoint = [number, number];
|
||||
|
||||
/** A Hastable with the picked objects lists. */
|
||||
declare type ObjectsLists = Hashtable<gdjs.RuntimeObject[]>;
|
||||
|
||||
/**
|
||||
* Represents the context of the events function (or the behavior method),
|
||||
* if any. If the JavaScript code is running in a scene, this will be undefined (so you can't use this in a scene).
|
||||
|
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>;
|
||||
}
|
@@ -36,6 +36,7 @@ module.exports = function (config) {
|
||||
|
||||
//GDJS game engine files: (Order is important)
|
||||
'./newIDE/app/resources/GDJS/Runtime/libs/jshashtable.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/ObjectsLists.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/logger.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/gd.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/AsyncTasksManager.js',
|
||||
@@ -53,6 +54,8 @@ 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/ObjectManager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/runtimeobject.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/runtimescene.js',
|
||||
@@ -78,6 +81,7 @@ module.exports = function (config) {
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/inputtools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/networktools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/objecttools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/ObjectPositionTools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/cameratools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/soundtools.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/events-tools/storagetools.js',
|
||||
|
@@ -13,28 +13,37 @@ describe('gdjs.evtTools.object', function () {
|
||||
|
||||
expect(
|
||||
gdjs.evtTools.object.getPickedInstancesCount(
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(3);
|
||||
expect(
|
||||
gdjs.evtTools.object.getPickedInstancesCount(
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [],
|
||||
MyObjectB: [],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [],
|
||||
MyObjectB: [],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(0);
|
||||
|
||||
// Also test the deprecated name for this function:
|
||||
expect(
|
||||
gdjs.evtTools.object.pickedObjectsCount(
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(3);
|
||||
});
|
||||
@@ -52,43 +61,58 @@ describe('gdjs.evtTools.object', function () {
|
||||
expect(
|
||||
gdjs.evtTools.object.getSceneInstancesCount(
|
||||
runtimeScene,
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [objectB1],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [objectB1],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(2 + 1);
|
||||
expect(
|
||||
gdjs.evtTools.object.getSceneInstancesCount(
|
||||
runtimeScene,
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(2 + 1);
|
||||
expect(
|
||||
gdjs.evtTools.object.getSceneInstancesCount(
|
||||
runtimeScene,
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [objectA1],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1],
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).to.be(2);
|
||||
expect(
|
||||
gdjs.evtTools.object.getSceneInstancesCount(
|
||||
runtimeScene,
|
||||
Hashtable.newFrom({
|
||||
MyObjectA: [],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [],
|
||||
},
|
||||
false
|
||||
)
|
||||
)
|
||||
).to.be(2);
|
||||
expect(
|
||||
gdjs.evtTools.object.getSceneInstancesCount(
|
||||
runtimeScene,
|
||||
Hashtable.newFrom({
|
||||
MyObjectC: [],
|
||||
})
|
||||
gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectC: [],
|
||||
},
|
||||
false
|
||||
)
|
||||
)
|
||||
).to.be(0);
|
||||
});
|
||||
@@ -106,9 +130,12 @@ describe('gdjs.evtTools.object', function () {
|
||||
runtimeScene.createObject('MyObjectA');
|
||||
|
||||
// 1 of 2 instances are picked.
|
||||
const pickedObjectList = Hashtable.newFrom({
|
||||
MyObjectA: [objectA1],
|
||||
});
|
||||
const pickedObjectList = gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1],
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const newObjectA = gdjs.evtTools.object.createObjectOnScene(
|
||||
runtimeScene,
|
||||
@@ -134,9 +161,12 @@ describe('gdjs.evtTools.object', function () {
|
||||
runtimeScene.createObject('MyObjectA');
|
||||
|
||||
// 0 of 2 instances are picked.
|
||||
const pickedObjectList = Hashtable.newFrom({
|
||||
MyObjectA: [],
|
||||
});
|
||||
const pickedObjectList = gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [],
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const newObjectA = gdjs.evtTools.object.createObjectOnScene(
|
||||
runtimeScene,
|
||||
@@ -161,9 +191,12 @@ describe('gdjs.evtTools.object', function () {
|
||||
const objectA2 = runtimeScene.createObject('MyObjectA');
|
||||
|
||||
// All instances are picked.
|
||||
const pickedObjectList = Hashtable.newFrom({
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
});
|
||||
const pickedObjectList = gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const newObjectA = gdjs.evtTools.object.createObjectOnScene(
|
||||
runtimeScene,
|
||||
@@ -191,10 +224,13 @@ describe('gdjs.evtTools.object', function () {
|
||||
runtimeScene.createObject('MyObjectB');
|
||||
|
||||
// 2 of 3 instances are picked.
|
||||
const pickedObjectList = Hashtable.newFrom({
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [objectB1],
|
||||
});
|
||||
const pickedObjectList = gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1],
|
||||
MyObjectB: [objectB1],
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const newObjectA = gdjs.evtTools.object.createObjectOnScene(
|
||||
runtimeScene,
|
||||
@@ -224,10 +260,13 @@ describe('gdjs.evtTools.object', function () {
|
||||
const objectB1 = runtimeScene.createObject('MyObjectB');
|
||||
|
||||
// All instances are picked.
|
||||
const pickedObjectList = Hashtable.newFrom({
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
});
|
||||
const pickedObjectList = gdjs.evtTools.objectsLists.newFrom(
|
||||
{
|
||||
MyObjectA: [objectA1, objectA2],
|
||||
MyObjectB: [objectB1],
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const newObjectA = gdjs.evtTools.object.createObjectOnScene(
|
||||
runtimeScene,
|
||||
|
@@ -565,6 +565,37 @@ const getPickedInstancesCount = (objectsLists) => {
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} objectsContext
|
||||
* @param {Hashtable<RuntimeObject[]>} objectsLists
|
||||
*/
|
||||
const getVisibleInstancesCount = (objectsContext, objectsLists) => {
|
||||
let count = 0;
|
||||
|
||||
const objectNames = [];
|
||||
objectsLists.keys(objectNames);
|
||||
|
||||
const uniqueObjectNames = new Set(objectNames);
|
||||
for (const objectName of uniqueObjectNames) {
|
||||
const visibleObjects = objectsContext.getObjects(objectName);
|
||||
if (visibleObjects) {
|
||||
count += visibleObjects.length;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Hashtable<RuntimeObject[]>} objectsLists
|
||||
*/
|
||||
const clearObjectLists = (objectsLists) => {
|
||||
const lists = [];
|
||||
objectsLists.values(lists);
|
||||
for (let i = 0, len = lists.length; i < len; ++i) {
|
||||
lists[i].length = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/** A minimal implementation of gdjs.RuntimeScene for testing. */
|
||||
class RuntimeScene {
|
||||
constructor(sceneData) {
|
||||
@@ -706,6 +737,27 @@ class LongLivedObjectsList {
|
||||
}
|
||||
}
|
||||
|
||||
const clearObjectsLists = (objectsLists) => {
|
||||
for (const k in objectsLists.items) {
|
||||
if (objectsLists.items.hasOwnProperty(k)) {
|
||||
objectsLists.items[k].length = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addObject = (
|
||||
objectsLists,
|
||||
objectName,
|
||||
object
|
||||
) => {
|
||||
if (!objectsLists.isPicked) {
|
||||
// A picking starts from empty lists.
|
||||
clearObjectsLists(objectsLists);
|
||||
objectsLists.isPicked = true;
|
||||
}
|
||||
objectsLists.get(objectName).push(object);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a minimal mock of GDJS with a RuntimeScene (`gdjs.RuntimeScene`),
|
||||
* supporting setting a variable, using "Trigger Once" conditions
|
||||
@@ -729,6 +781,8 @@ function makeMinimalGDJSMock(options) {
|
||||
createObjectOnScene,
|
||||
getSceneInstancesCount,
|
||||
getPickedInstancesCount,
|
||||
getVisibleInstancesCount,
|
||||
clearObjectLists,
|
||||
},
|
||||
runtimeScene: {
|
||||
wait: () => new FakeAsyncTask(),
|
||||
@@ -737,6 +791,9 @@ function makeMinimalGDJSMock(options) {
|
||||
common: {
|
||||
resolveAsyncEventsFunction: ({ task }) => task.resolve(),
|
||||
},
|
||||
objectsLists: {
|
||||
addObject,
|
||||
}
|
||||
},
|
||||
registerBehavior: (behaviorTypeName, Ctor) => {
|
||||
behaviorCtors[behaviorTypeName] = Ctor;
|
||||
|
Reference in New Issue
Block a user