Compare commits

...

2 Commits

Author SHA1 Message Date
Florian Rival
cf4f70bf48 Formatting 2025-09-30 20:03:02 +02:00
Florian Rival
43fa6aa3f5 Add a grace distance to Destroy Outside behavior to avoid deleting objects never seen near the screen 2025-09-30 17:33:19 +02:00
4 changed files with 146 additions and 55 deletions

View File

@@ -12,7 +12,8 @@ This project is released under the MIT License.
#include "GDCore/Tools/Localization.h"
void DestroyOutsideBehavior::InitializeContent(gd::SerializerElement& content) {
content.SetAttribute("extraBorder", 300);
content.SetAttribute("extraBorder", 200);
content.SetAttribute("unseenGraceDistance", 10000);
}
#if defined(GD_IDE_ONLY)
@@ -27,7 +28,15 @@ DestroyOutsideBehavior::GetProperties(
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetLabel(_("Deletion margin"))
.SetDescription(_("Margin before deleting the object, in pixels"));
.SetDescription(_("Margin before deleting the object, in pixels."));
properties["unseenGraceDistance"]
.SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("unseenGraceDistance", 0)))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetLabel(_("Unseen object grace distance"))
.SetDescription(_("If the object hasn't been visible yet, don't delete it until it travels this far beyond the screen (in pixels). Useful to avoid objects being deleted before they are visible when they spawn."));
return properties;
}
@@ -38,6 +47,8 @@ bool DestroyOutsideBehavior::UpdateProperty(
const gd::String& value) {
if (name == "extraBorder")
behaviorContent.SetAttribute("extraBorder", value.To<double>());
else if (name == "unseenGraceDistance")
behaviorContent.SetAttribute("unseenGraceDistance", value.To<double>());
else
return false;

View File

@@ -6,6 +6,7 @@ This project is released under the MIT License.
*/
#include "DestroyOutsideBehavior.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Tools/Localization.h"
@@ -19,11 +20,10 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
"outside of the bounds of the 2D camera. Useful for 2D bullets or "
"other short-lived objects. Don't use it for 3D objects in a "
"FPS/TPS game or any game with a camera not being a top view "
"(for 3D objects, prefer comparing "
"the position, for example Z position to see if an object goes "
"outside of the bound of the map). Be careful when using this "
"behavior because if the object appears outside of the screen, it "
"will be immediately removed."),
"(for 3D objects, prefer comparing the position, for example Z "
"position to see if an object goes outside of the bound of the "
"map). If the object appears outside of the screen, it's not "
"removed unless it goes beyond the unseen object grace distance."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Game mechanic")
@@ -44,34 +44,39 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
std::shared_ptr<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
aut.AddCondition("ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("Compare the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted."),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
aut.AddExpressionAndConditionAndAction(
"number",
"ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted"),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "DestroyOutside")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced()
.SetFunctionName("GetExtraBorder");
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
aut.AddAction("ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("Change the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted."),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
// Deprecated:
aut.AddDuplicatedAction("ExtraBorder", "DestroyOutside::SetExtraBorder")
.SetHidden();
aut.AddDuplicatedCondition("ExtraBorder", "DestroyOutside::ExtraBorder")
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",
"UnseenGraceDistance",
_("Unseen object grace distance"),
_("the grace distance (in pixels) before deleting the object if it "
"has "
"never been visible on the screen. Useful to avoid objects being "
"deleted before they are visible when they spawn"),
_("the unseen grace distance"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "DestroyOutside")
.UseStandardOperatorParameters("number",
gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced()
.SetFunctionName("SetExtraBorder")
.SetGetter("GetExtraBorder");
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
}

View File

@@ -5,11 +5,11 @@ Copyright (c) 2014-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include <iostream>
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Tools/Localization.h"
#include <iostream>
void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension);
/**
@@ -29,19 +29,36 @@ class DestroyOutsideBehaviorJsExtension : public gd::PlatformExtension {
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
GetAllExpressionsForBehavior(
"DestroyOutsideBehavior::DestroyOutside")["ExtraBorder"]
.SetFunctionName("getExtraBorder");
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::ExtraBorder"]
.SetFunctionName("getExtraBorder");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::SetExtraBorder"]
.SetFunctionName("setExtraBorder")
.SetGetter("getExtraBorder");
// Deprecated:
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::ExtraBorder"]
.SetFunctionName("getExtraBorder")
.SetIncludeFile(
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
.SetFunctionName("getExtraBorder");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::ExtraBorder"]
.SetFunctionName("setExtraBorder")
.SetGetter("getExtraBorder")
.SetIncludeFile(
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
.SetGetter("getExtraBorder");
GetAllExpressionsForBehavior(
"DestroyOutsideBehavior::DestroyOutside")["UnseenGraceDistance"]
.SetFunctionName("getUnseenGraceDistance");
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::UnseenGraceDistance"]
.SetFunctionName("getUnseenGraceDistance");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::SetUnseenGraceDistance"]
.SetFunctionName("setUnseenGraceDistance")
.SetGetter("getUnseenGraceDistance");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};

View File

@@ -8,7 +8,9 @@ namespace gdjs {
* The DestroyOutsideRuntimeBehavior represents a behavior that destroys the object when it leaves the screen.
*/
export class DestroyOutsideRuntimeBehavior extends gdjs.RuntimeBehavior {
_extraBorder: any;
_extraBorder: float;
_unseenGraceDistance: float;
_hasBeenOnScreen: boolean;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -17,12 +19,20 @@ namespace gdjs {
) {
super(instanceContainer, behaviorData, owner);
this._extraBorder = behaviorData.extraBorder || 0;
this._unseenGraceDistance = behaviorData.unseenGraceDistance || 0;
this._hasBeenOnScreen = false;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
this._extraBorder = newBehaviorData.extraBorder;
}
if (
oldBehaviorData.unseenGraceDistance !==
newBehaviorData.unseenGraceDistance
) {
this._unseenGraceDistance = newBehaviorData.unseenGraceDistance;
}
return true;
}
@@ -35,23 +45,47 @@ namespace gdjs {
const ocy = this.owner.getDrawableY() + this.owner.getCenterY();
const layer = instanceContainer.getLayer(this.owner.getLayer());
const boundingCircleRadius = Math.sqrt(ow * ow + oh * oh) / 2.0;
const cameraLeft = layer.getCameraX() - layer.getCameraWidth() / 2;
const cameraRight = layer.getCameraX() + layer.getCameraWidth() / 2;
const cameraTop = layer.getCameraY() - layer.getCameraHeight() / 2;
const cameraBottom = layer.getCameraY() + layer.getCameraHeight() / 2;
if (
ocx + boundingCircleRadius + this._extraBorder <
layer.getCameraX() - layer.getCameraWidth() / 2 ||
ocx - boundingCircleRadius - this._extraBorder >
layer.getCameraX() + layer.getCameraWidth() / 2 ||
ocy + boundingCircleRadius + this._extraBorder <
layer.getCameraY() - layer.getCameraHeight() / 2 ||
ocy - boundingCircleRadius - this._extraBorder >
layer.getCameraY() + layer.getCameraHeight() / 2
ocx + boundingCircleRadius + this._extraBorder < cameraLeft ||
ocx - boundingCircleRadius - this._extraBorder > cameraRight ||
ocy + boundingCircleRadius + this._extraBorder < cameraTop ||
ocy - boundingCircleRadius - this._extraBorder > cameraBottom
) {
//We are outside the camera area.
this.owner.deleteFromScene();
if (this._hasBeenOnScreen) {
// Object is outside the camera area and object was previously seen inside it:
// delete it now.
this.owner.deleteFromScene();
} else if (
ocx + boundingCircleRadius + this._unseenGraceDistance < cameraLeft ||
ocx - boundingCircleRadius - this._unseenGraceDistance >
cameraRight ||
ocy + boundingCircleRadius + this._unseenGraceDistance < cameraTop ||
ocy - boundingCircleRadius - this._unseenGraceDistance > cameraBottom
) {
// Object is outside the camera area and also outside the grace distance:
// force deletion.
this.owner.deleteFromScene();
} else {
// Object is outside the camera area but inside the grace distance
// and was never seen inside the camera area: don't delete it yet.
}
} else {
this._hasBeenOnScreen = true;
}
}
/**
* Set an additional border to the camera viewport as a buffer before the object gets destroyed.
* Set the additional border outside the camera area.
*
* If the object goes beyond the camera area and this border, it will be deleted (unless it was
* never seen inside the camera area and this border before, in which case it will be deleted
* according to the grace distance).
* @param val Border in pixels.
*/
setExtraBorder(val: number): void {
@@ -59,12 +93,36 @@ namespace gdjs {
}
/**
* Get the additional border of the camera viewport buffer which triggers the destruction of an object.
* Get the additional border outside the camera area.
* @return The additional border around the camera viewport in pixels
*/
getExtraBorder(): number {
return this._extraBorder;
}
/**
* Change the grace distance before an object is deleted if it's outside the camera area
* and was never seen inside the camera area. Typically useful to avoid objects being deleted
* before they are visible when they spawn.
*/
setUnseenGraceDistance(val: number): void {
this._unseenGraceDistance = val;
}
/**
* Get the grace distance before an object is deleted if it's outside the camera area
* and was never seen inside the camera area.
*/
getUnseenGraceDistance(): number {
return this._unseenGraceDistance;
}
/**
* Check if this object has been visible on screen (precisely: inside the camera area *including* the extra border).
*/
hasBeenOnScreen(): boolean {
return this._hasBeenOnScreen;
}
}
gdjs.registerBehavior(
'DestroyOutsideBehavior::DestroyOutside',