Add experimental "navmesh-based" pathfinding behavior for 2D and 3D (#8943)

- This pathfinding works in 2D and 3D is more flexible than the "grid-based" existing pathfinding.
- For 3D models, you can optionally choose, like for 3D physics, to follow the exact mesh of the 3D model for pathfinding - allowing characters and objects to move on or around the 3D model naturally.
- It handles "crowds" of characters, which can all go to a destination avoiding each others.
This commit is contained in:
D8H
2026-08-11 11:11:44 +02:00
committed by GitHub
parent 9b331ac4bc
commit 361f6b3a87
27 changed files with 8128 additions and 106 deletions
-14
View File
@@ -186,20 +186,6 @@ namespace gdjs {
content: Object3DDataContent;
}
/** @category Objects > 3D Objects */
export namespace Base3DHandler {
export const is3D = (
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject &
gdjs.Base3DHandler &
gdjs.Resizable &
gdjs.Scalable &
gdjs.Flippable => {
//@ts-ignore We are checking if the methods are present.
return object.getZ && object.setZ;
};
}
/**
* A behavior that forwards the Base3D interface to its object.
* @category Core Engine > Behavior
@@ -0,0 +1,905 @@
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
* ℹ️ Changes in this file are watched and automatically imported if the editor
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
*
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
* ⚠️ If you make a change and the extension is not loaded, open the developer console
* and search for any errors.
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
'NavMeshPathfinding',
_('NavMesh pathfinding behavior'),
'Pathfinding allows to compute an efficient path for objects, avoiding obstacles on the way.',
'',
'Open source (MIT License)'
)
.setShortDescription(
'Nav-mesh based pathfinding : compute path avoiding obstacles and handle crowds.'
)
.setDimension('2D/3D')
.setCategory('Movement')
.setTags('pathfinding, obstacle, collision')
.setExtensionHelpPath('/behaviors/nav-mesh-pathfinding');
{
const behavior = new gd.BehaviorJsImplementation();
behavior.updateProperty = function (
behaviorContent,
propertyName,
newValue
) {
if (propertyName === 'acceleration') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('acceleration')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'maxSpeed') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('maxSpeed')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'angularMaxSpeed') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('angularMaxSpeed')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'rotateObject') {
behaviorContent
.getOrCreateChild('rotateObject')
.setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'angleOffset') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('angleOffset')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'radius') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('radius')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'avoidanceSightRange') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getOrCreateChild('avoidanceSightRange')
.setDoubleValue(newValueAsNumber);
return true;
}
return false;
};
behavior.getProperties = function (behaviorContent) {
const behaviorProperties = new gd.MapStringPropertyDescriptor();
behaviorProperties
.getOrCreate('acceleration')
.setValue(behaviorContent.getChild('acceleration').getStringValue())
.setLabel(_('Acceleration'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixelAcceleration());
behaviorProperties
.getOrCreate('maxSpeed')
.setValue(behaviorContent.getChild('maxSpeed').getStringValue())
.setLabel(_('Max. speed'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixelSpeed());
behaviorProperties
.getOrCreate('angularMaxSpeed')
.setValue(
behaviorContent.getChild('angularMaxSpeed').getStringValue()
)
.setLabel(_('Rotation speed'))
.setGroup(_('Rotation'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getAngularSpeed());
behaviorProperties
.getOrCreate('rotateObject')
.setValue(
behaviorContent.getChild('rotateObject').getBoolValue()
? 'true'
: 'false'
)
.setLabel(_('Rotate object'))
.setGroup(_('Rotation'))
.setType('Boolean');
behaviorProperties
.getOrCreate('angleOffset')
.setValue(behaviorContent.getChild('angleOffset').getStringValue())
.setLabel(_('Angle offset'))
.setGroup(_('Rotation'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getDegreeAngle());
behaviorProperties
.getOrCreate('radius')
.setValue(behaviorContent.getChild('radius').getStringValue())
.setLabel(_('Radius'))
.setDescription(
_(
'Use the circle inside the object width and height when left to 0.'
)
)
.setGroup(_('Collision'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel());
behaviorProperties
.getOrCreate('avoidanceSightRange')
.setValue(
behaviorContent.getChild('avoidanceSightRange').getStringValue()
)
.setLabel(_('Avoidance sight range'))
.setGroup(_('Collision'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel());
return behaviorProperties;
};
behavior.initializeContent = function (behaviorContent) {
behaviorContent.addChild('acceleration').setDoubleValue(400);
behaviorContent.addChild('maxSpeed').setDoubleValue(200);
behaviorContent.addChild('angularMaxSpeed').setDoubleValue(180);
behaviorContent.addChild('rotateObject').setBoolValue(true);
behaviorContent.addChild('angleOffset').setDoubleValue(0);
behaviorContent.addChild('radius').setDoubleValue(0);
behaviorContent.addChild('avoidanceSightRange').setDoubleValue(120);
};
const sharedData = new gd.BehaviorSharedDataJsImplementation();
sharedData.updateProperty = function (
sharedContent,
propertyName,
newValue
) {
if (propertyName === 'cellSize') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('cellSize')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'cellDepth') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('cellDepth')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'slopeMaxAngle') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('slopeMaxAngle')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'stairHeightMax') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('stairHeightMax')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'walkableRadius') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('walkableRadius')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'walkableDepth') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('walkableDepth')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'speedScaleY') {
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent
.getOrCreateChild('speedScaleY')
.setDoubleValue(newValueAsNumber);
return true;
}
return false;
};
sharedData.getProperties = function (sharedContent) {
const sharedProperties = new gd.MapStringPropertyDescriptor();
sharedProperties
.getOrCreate('cellSize')
.setLabel(_('Cell size'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setValue(
sharedContent.getChild('cellSize').getDoubleValue().toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('cellDepth')
.setLabel(_('Cell depth'))
.setGroup(_('3D only'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setValue(
sharedContent.getChild('cellDepth').getDoubleValue().toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('slopeMaxAngle')
.setLabel(_('Slope max. angle'))
.setGroup(_('3D only'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getDegreeAngle())
.setValue(
sharedContent
.getChild('slopeMaxAngle')
.getDoubleValue()
.toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('stairHeightMax')
.setLabel(_('Max. stair height'))
.setGroup(_('3D only'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setValue(
sharedContent
.getChild('stairHeightMax')
.getDoubleValue()
.toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('walkableRadius')
.setLabel(_('Walkable radius'))
.setDescription(
_(
'The biggest character radius is automatically used when left negative.'
)
)
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setValue(
sharedContent
.getChild('walkableRadius')
.getDoubleValue()
.toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('walkableDepth')
.setLabel(_('Walkable depth'))
.setDescription(
_(
'Minimum floor to ceiling height that will still allow the floor area to be considered walkable.'
)
)
.setGroup(_('3D only'))
.setType('Number')
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
.setValue(
sharedContent
.getChild('walkableDepth')
.getDoubleValue()
.toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
sharedProperties
.getOrCreate('speedScaleY')
.setLabel(_('Y speed scale'))
.setDescription(
_(
'Allow a depth effect for 2D games. Usually set to 0.5 for isometry.'
)
)
.setGroup(_('2D only'))
.setType('Number')
.setValue(
sharedContent.getChild('speedScaleY').getDoubleValue().toString(10)
)
.setAdvanced(true)
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden);
return sharedProperties;
};
sharedData.initializeContent = function (sharedContent) {
sharedContent.addChild('cellSize').setDoubleValue(10);
sharedContent.addChild('cellDepth').setDoubleValue(10);
sharedContent.addChild('slopeMaxAngle').setDoubleValue(50);
sharedContent.addChild('stairHeightMax').setDoubleValue(20);
sharedContent.addChild('walkableRadius').setDoubleValue(-1);
sharedContent.addChild('walkableDepth').setDoubleValue(150);
sharedContent.addChild('speedScaleY').setDoubleValue(1);
};
const aut = extension
.addBehavior(
'NavMeshCharacterBehavior',
_('Pathfinding character (nav-mesh based)'),
'NavMeshCharacter',
_(
'Move objects to a target while avoiding all objects that are flagged as obstacles.'
),
'',
'JsPlatform/Extensions/nav-mesh-character.svg',
'NavMeshCharacterBehavior',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.addIncludeFile(
'Extensions/NavMeshPathfinding/NavMeshCharacterRuntimeBehavior.js'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/NavMeshObstacleRuntimeBehavior.js'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/recast-navigation.wasm.js'
)
.addRequiredFile(
'Extensions/NavMeshPathfinding/recast-navigation.wasm.wasm'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/recast-navigation-generators.js'
);
aut
.addAction(
'MoveTo',
_('Move to a position'),
_('Move the object to a position'),
_('Move _PARAM0_ to _PARAM2_ ; _PARAM3_ ; _PARAM4_'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('expression', _('Destination X position'))
.addParameter('expression', _('Destination Y position'))
.addParameter('expression', _('Destination Z position'))
.setFunctionName('moveTo');
aut
.addCondition(
'PathFound',
_('Path found'),
_('Check if a path has been found.'),
_('A path has been found for _PARAM0_'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('pathFound');
aut
.addCondition(
'DestinationReached',
_('Destination reached'),
_('Check if the destination was reached.'),
_('_PARAM0_ reached its destination'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('destinationReached');
aut
.addExpressionAndConditionAndAction(
'number',
'Acceleration',
_('Acceleration'),
_('the acceleration when moving the object'),
_('the acceleration on the path'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setAcceleration')
.setGetter('getAcceleration');
aut
.addExpressionAndConditionAndAction(
'number',
'MaxSpeed',
_('Maximum speed'),
_('the maximum speed when moving the object'),
_('the max. speed on the path'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Max speed (in pixels per second)')
)
)
.setFunctionName('setMaxSpeed')
.setGetter('getMaxSpeed');
aut
.addExpressionAndConditionAndAction(
'number',
'Speed',
_('Speed'),
_('Change the speed of the object on the path'),
_('the speed on the path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Speed (in pixels per second)')
)
)
.setFunctionName('setSpeed')
.setGetter('getSpeed');
aut
.addScopedCondition(
'MovementAngleIsAround',
_('Angle of movement on its path'),
_('Compare the angle of movement of an object on its path.'),
_('Angle of movement of _PARAM0_ is _PARAM2_ ± _PARAM3_°'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('expression', _('Angle, in degrees'))
.addParameter('expression', _('Tolerance, in degrees'));
aut
.addExpressionAndConditionAndAction(
'number',
'AngularMaxSpeed',
_('Angular maximum speed'),
_('the maximum angular speed when moving the object'),
_('the max. angular speed on the path'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Max angular speed (in degrees per second)')
)
)
.setFunctionName('setAngularMaxSpeed')
.setGetter('getAngularMaxSpeed');
aut
.addExpressionAndConditionAndAction(
'number',
'AngleOffset',
_('Rotation offset'),
_('the rotation offset applied when moving the object'),
_('the rotation offset on the path'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setAngleOffset')
.setGetter('getAngleOffset');
aut
.addScopedAction(
'RotateObject',
_('Rotate the object'),
_('Enable or disable rotation of the object on the path'),
_('Enable rotation of _PARAM0_ on the path: _PARAM2_'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('yesorno', _('Rotate object?'))
.setFunctionName('setRotateObject');
aut
.addScopedCondition(
'ObjectRotated',
_('Object rotated'),
_('Check if the object is rotated when traveling on its path.'),
_('_PARAM0_ is rotated when traveling on its path'),
_('Pathfinding configuration'),
'JsPlatform/Extensions/nav-mesh-character.svg',
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('isObjectRotated');
aut
.addExpression(
'GetNodeX',
_('Get a waypoint X position'),
_('Get a waypoint X position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('expression', _('Node index (start at 0!)'))
.setFunctionName('getNodeX');
aut
.addExpression(
'GetNodeY',
_('Get a waypoint Y position'),
_('Get a waypoint Y position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('expression', _('Node index (start at 0!)'))
.setFunctionName('getNodeY');
aut
.addExpression(
'GetNodeZ',
_('Get a waypoint Z position'),
_('Get a waypoint Z position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.addParameter('expression', _('Node index (start at 0!)'))
.setFunctionName('getNodeZ');
aut
.addExpression(
'NextNodeIndex',
_('Index of the next waypoint'),
_('Get the index of the next waypoint to reach'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getNextNodeIndex');
aut
.addExpression(
'NodeCount',
_('Waypoint count'),
_('Get the number of waypoints on the path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getNodeCount');
aut
.addExpression(
'NextNodeX',
_('Get next waypoint X position'),
_('Get next waypoint X position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getNextNodeX');
aut
.addExpression(
'NextNodeY',
_('Get next waypoint Y position'),
_('Get next waypoint Y position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getNextNodeY');
aut
.addExpression(
'NextNodeZ',
_('Get next waypoint Z position'),
_('Get next waypoint Z position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getNextNodeZ');
aut
.addExpression(
'PreviousNodeX',
_('Previous waypoint X position'),
_('Previous waypoint X position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getPreviousNodeX');
aut
.addExpression(
'PreviousNodeY',
_('Previous waypoint Y position'),
_('Previous waypoint Y position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getPreviousNodeY');
aut
.addExpression(
'PreviousNodeZ',
_('Previous waypoint Z position'),
_('Previous waypoint Z position'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getPreviousNodeZ');
aut
.addExpression(
'DestinationX',
_('Destination X position'),
_('Destination X position of the path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getDestinationX');
aut
.addExpression(
'DestinationY',
_('Destination Y position'),
_('Destination Y position of the path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getDestinationY');
aut
.addExpression(
'DestinationZ',
_('Destination Z position'),
_('Destination Z position of the path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getDestinationZ');
aut
.addExpression(
'MovementAngle',
_('Angle of movement on its path'),
_('Angle of movement on its path'),
_('Movement on the path'),
'JsPlatform/Extensions/nav-mesh-character.svg'
)
.addParameter('object', _('Object'))
.addParameter('behavior', _('Behavior'), 'NavMeshCharacterBehavior')
.setFunctionName('getMovementAngle');
}
{
const behavior = new gd.BehaviorJsImplementation();
behavior.updateProperty = function (
behaviorContent,
propertyName,
newValue
) {
if (propertyName === 'shape') {
const normalizedValue = newValue.toLowerCase();
let shapeValue = '';
if (normalizedValue === 'box') shapeValue = 'Box';
else if (normalizedValue === 'mesh') shapeValue = 'Mesh';
else return false;
behaviorContent.getOrCreateChild('shape').setStringValue(shapeValue);
return true;
}
if (propertyName === 'meshShapeResourceName') {
behaviorContent
.getOrCreateChild('meshShapeResourceName')
.setStringValue(newValue);
return true;
}
return false;
};
behavior.getProperties = function (behaviorContent) {
const behaviorProperties = new gd.MapStringPropertyDescriptor();
behaviorProperties
.getOrCreate('shape')
.setValue(behaviorContent.getChild('shape').getStringValue())
.setType('Choice')
.setLabel('Shape')
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
.addChoice('Box', _('Box'))
.addChoice('Mesh', _('Mesh'));
behaviorProperties
.getOrCreate('meshShapeResourceName')
.setValue(
behaviorContent.getChild('meshShapeResourceName').getStringValue()
)
.setType('resource')
.addExtraInfo('model3D')
.setLabel(_("Simplified 3D model (leave empty to use object's one)"));
return behaviorProperties;
};
behavior.initializeContent = function (behaviorContent) {
behaviorContent.addChild('shape').setStringValue('Box');
behaviorContent.addChild('meshShapeResourceName').setStringValue('');
};
const sharedData = new gd.BehaviorSharedDataJsImplementation();
sharedData.updateProperty = function (
sharedContent,
propertyName,
newValue
) {
return false;
};
sharedData.getProperties = function (sharedContent) {
const sharedProperties = new gd.MapStringPropertyDescriptor();
return sharedProperties;
};
sharedData.initializeContent = function (sharedContent) {};
extension
.addBehavior(
'NavMeshObstacleBehavior',
_('Obstacle for pathfinding (nav-mesh based)'),
'NavMeshObstacle',
_('Flag objects as being obstacles for nav-mesh pathfinding.'),
'',
'JsPlatform/Extensions/nav-mesh-obstacle.svg',
'NavMeshObstacle',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.addIncludeFile(
'Extensions/NavMeshPathfinding/NavMeshObstacleRuntimeBehavior.js'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/recast-navigation.wasm.js'
)
.addRequiredFile(
'Extensions/NavMeshPathfinding/recast-navigation.wasm.wasm'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/recast-navigation-generators.js'
);
}
extension
.addAction(
'EnableDebugDraw',
_('Draw pathfinding walkable area'),
_('This activates the display of the walkable area (in blue).'),
_('Enable debugging view of pathfinding: _PARAM1_'),
'',
'res/actions/planicon24.png',
'res/actions/planicon.png'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('yesorno', _('Enable debug draw'), '', true)
.getCodeExtraInformation()
.addIncludeFile(
'Extensions/NavMeshPathfinding/NavMeshObstacleRuntimeBehavior.js'
)
.addIncludeFile('Extensions/NavMeshPathfinding/recast-navigation.wasm.js')
.addIncludeFile(
'Extensions/NavMeshPathfinding/recast-navigation-generators.js'
)
.addIncludeFile(
'Extensions/NavMeshPathfinding/NavMeshDebugPixiRenderer.js'
)
.setFunctionName('gdjs.NavMeshObstaclesManager.enableDebugDraw');
return extension;
},
runExtensionSanityTests: function (gd, extension) {
return [];
},
};
@@ -0,0 +1,631 @@
namespace gdjs {
const loadRecast = async () => {
try {
const module = await import('./recast-navigation.wasm.js');
const initializeRecast = module.default;
if (!initializeRecast) {
throw new Error('No default export found in Recast.');
}
const Recast = await initializeRecast();
await RecastNav.init();
//@ts-ignore
window.Recast = Recast;
} catch (err) {
console.error('Unable to load Recast navigation mesh library.', err);
throw err;
}
};
gdjs.registerAsynchronouslyLoadingLibraryPromise(loadRecast());
interface NavMeshCharacterNetworkSyncDataType {
// Syncing the path and its position on it should be enough to have a good prediction.
d: RecastNav.Vector3;
pf: boolean;
as: number;
re: boolean;
ma: number;
}
/** @category Behaviors > NavMesh pathfinding */
export interface NavMeshCharacterNetworkSyncData
extends BehaviorNetworkSyncData {
props: NavMeshCharacterNetworkSyncDataType;
}
/**
* NavMeshCharacterRuntimeBehavior represents a behavior allowing objects to
* follow a path computed to avoid obstacles.
* @category Behaviors > NavMesh pathfinding
*/
export class NavMeshCharacterRuntimeBehavior extends gdjs.RuntimeBehavior {
_path: Array<RecastNav.Vector3> = [];
// Behavior configuration:
_angularMaxSpeed: float;
_rotateObject: boolean;
_angleOffset: float;
_radius: float;
_agent: RecastNav.CrowdAgent | null = null;
_crowdAgentParams: Partial<RecastNav.CrowdAgentParams> = {
radius: 40,
height: 100,
maxAcceleration: 1000,
maxSpeed: 300,
collisionQueryRange: 120,
separationWeight: 1.0,
};
_oldX: float = 0;
_oldY: float = 0;
_oldZ: float = 0;
// Attributes used for traveling on the path:
_pathFound: boolean = false;
_reachedEnd: boolean = false;
_movementAngle: float = 0;
_manager: NavMeshObstaclesManager;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(instanceContainer, behaviorData, owner);
this._angularMaxSpeed = behaviorData.angularMaxSpeed;
this._rotateObject = behaviorData.rotateObject;
this._angleOffset = behaviorData.angleOffset;
this._radius = behaviorData.radius;
this._crowdAgentParams.maxAcceleration = behaviorData.acceleration;
this._crowdAgentParams.maxSpeed = behaviorData.maxSpeed;
this._crowdAgentParams.collisionQueryRange =
behaviorData.avoidanceSightRange;
this._manager =
gdjs.NavMeshObstaclesManager.getManager(instanceContainer);
this._manager.addCharacter(this);
}
override applyBehaviorOverriding(behaviorData): boolean {
if (behaviorData.acceleration !== undefined) {
this.setAcceleration(behaviorData.acceleration);
}
if (behaviorData.maxSpeed !== undefined) {
this.setMaxSpeed(behaviorData.maxSpeed);
}
if (behaviorData.angularMaxSpeed !== undefined) {
this.setAngularMaxSpeed(behaviorData.angularMaxSpeed);
}
if (behaviorData.rotateObject !== undefined) {
this.setRotateObject(behaviorData.rotateObject);
}
if (behaviorData.angleOffset !== undefined) {
this.setAngleOffset(behaviorData.angleOffset);
}
if (behaviorData.radius !== undefined) {
this._radius = behaviorData.radius;
this._manager.invalidateNavMesh();
}
if (behaviorData.avoidanceSightRange !== undefined) {
this._crowdAgentParams.collisionQueryRange =
behaviorData.avoidanceSightRange;
}
return true;
}
override getNetworkSyncData(
options: GetNetworkSyncDataOptions
): NavMeshCharacterNetworkSyncData {
return {
...super.getNetworkSyncData(options),
props: {
d: this._path[this._path.length - 1] || null,
pf: this._pathFound,
as: this._angularMaxSpeed,
re: this._reachedEnd,
ma: this._movementAngle,
},
};
}
override updateFromNetworkSyncData(
networkSyncData: NavMeshCharacterNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.d !== undefined) {
// TODO Try a more reliable synchronization by overriding the path using the low level API.
const destination = this._path[this._path.length - 1];
if (behaviorSpecificProps.d === null) {
if (destination) {
this.stop();
}
} else if (
!destination ||
destination.x !== behaviorSpecificProps.d.x ||
destination.y !== behaviorSpecificProps.d.y ||
destination.z !== behaviorSpecificProps.d.z
) {
this.moveTo(
behaviorSpecificProps.d.x,
behaviorSpecificProps.d.y,
behaviorSpecificProps.d.z
);
}
}
if (behaviorSpecificProps.pf !== undefined) {
this._pathFound = behaviorSpecificProps.pf;
}
if (behaviorSpecificProps.as !== undefined) {
this._angularMaxSpeed = behaviorSpecificProps.as;
}
if (behaviorSpecificProps.re !== undefined) {
this._reachedEnd = behaviorSpecificProps.re;
}
if (behaviorSpecificProps.ma !== undefined) {
this._movementAngle = behaviorSpecificProps.ma;
}
}
override onActivate() {
this._manager.addCharacter(this);
}
override onDeActivate() {
this._manager.removeCharacter(this);
}
override onDestroy() {
this._manager.removeCharacter(this);
}
stop(): void {
if (!this._agent) {
return;
}
this._path = [];
this._pathFound = false;
this._reachedEnd = false;
this._agent.resetMoveTarget();
}
/**
* Compute and move on the path to the specified destination.
*/
moveTo(x: float, y: float, z: float): void {
this._path = [];
this._manager.rebuildNavMeshIfNeeded();
if (!this._manager.navMesh || !this._agent) {
this._pathFound = false;
return;
}
const navMeshQuery = new RecastNav.NavMeshQuery(this._manager.navMesh);
const { success: hasFoundOrigin, point: origin } =
navMeshQuery.findClosestPoint(
{
x: this.owner.getX(),
y: gdjs.Base3DHandler.is3D(this.owner) ? this.owner.getZ() : 0,
z: this._manager.is3D
? this.owner.getY()
: this.owner.getY() * this._manager.inverseSpeedScaleY,
},
{
halfExtents: {
x: 100,
y: this._manager.is3D
? 100
: gdjs.NavMeshObstaclesManager.cellHeightFor2D,
z: 100,
},
}
);
if (!hasFoundOrigin) {
navMeshQuery.destroy();
this._pathFound = false;
return;
}
const agentInitialPosition = this._agent.position();
// Teleporting the agent resets its velocity and move target,
// so it's only done (and rolled back) when actually needed.
let hasTeleported = false;
if (
agentInitialPosition.x !== origin.x ||
agentInitialPosition.y !== origin.y ||
agentInitialPosition.z !== origin.z
) {
this._agent.teleport(origin);
hasTeleported = true;
}
const { success: hasFoundDestination, point: destination } =
navMeshQuery.findClosestPoint(
{
x,
y: z,
z: this._manager.is3D ? y : y * this._manager.inverseSpeedScaleY,
},
{
halfExtents: {
x: 100,
y: this._manager.is3D
? 100
: gdjs.NavMeshObstaclesManager.cellHeightFor2D,
z: 100,
},
}
);
navMeshQuery.destroy();
if (!hasFoundDestination) {
this._pathFound = false;
if (hasTeleported) {
this._agent.teleport(agentInitialPosition);
}
return;
}
this._pathFound = this._agent.requestMoveTarget(destination);
if (this._pathFound) {
this._reachedEnd = false;
const newX = origin.x;
const newY = this._manager.is3D
? origin.z
: origin.z * this._manager.speedScaleY;
const newZ = origin.y;
this.owner.setX(newX);
this.owner.setY(newY);
if (gdjs.Base3DHandler.is3D(this.owner)) {
this.owner.setZ(newZ);
}
this._oldX = newX;
this._oldY = newY;
this._oldZ = newZ;
} else if (hasTeleported) {
this._agent.teleport(agentInitialPosition);
}
}
teleportAgentToObjectIfNeeded(): void {
if (
this._oldX === this.owner.getX() &&
this._oldY === this.owner.getY() &&
(!gdjs.Base3DHandler.is3D(this.owner) ||
this._oldZ === this.owner.getZ())
) {
return;
}
if (!this._agent) {
// Try to create the agent at the new object position.
this._manager.buildCharacterAgent(this);
return;
}
const agent = this._agent;
const oldX = this.owner.getX();
const oldY = this.owner.getY();
// For 2D we keep the agent position for Z because the ground may not be
// at 0 because of rasterization.
const oldZ = gdjs.Base3DHandler.is3D(this.owner)
? this.owner.getZ()
: agent.raw.get_npos(1);
agent.teleport({
x: oldX,
y: oldZ,
z: this._manager.is3D ? oldY : oldY * this._manager.inverseSpeedScaleY,
});
this._path = [];
this._pathFound = false;
}
override doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
const timeDelta = this.owner.getElapsedTime() / 1000;
// The wrapper interpolation seems bugged, so we don't use it.
this._manager.step(timeDelta);
const agent = this._agent;
if (!agent) {
return;
}
// Path found: memorize it
// We can't get the agent path before a first step, so we do it here.
if (this._path.length === 0 && agent.raw.ncorners > 0) {
const path = agent.corners();
path.push(agent.target());
for (const point of path) {
const y = point.y;
point.y = point.z;
point.z = y;
if (!this._manager.is3D) {
point.y *= this._manager.speedScaleY;
}
}
this._path = path;
}
let newX = agent.raw.get_npos(0);
let newY = this._manager.is3D
? agent.raw.get_npos(2)
: agent.raw.get_npos(2) * this._manager.speedScaleY;
let newZ = agent.raw.get_npos(1);
const destinationX = this.getDestinationX();
const destinationY = this.getDestinationY();
const destinationZ = this.getDestinationZ();
const velocity = agent.desiredVelocity();
const velocityX = velocity.x;
const velocityY = velocity.z;
if (
Math.abs(velocityX) + Math.abs(velocityY) > 0 &&
// Avoid to rotate strangely at the end of the path
(Math.abs(destinationX - newX) > 3 || Math.abs(destinationY - newY) > 3)
) {
this._movementAngle = gdjs.evtTools.common.mod(
gdjs.toDegrees(Math.atan2(velocityY, velocityX)),
360
);
}
if (
this._rotateObject &&
this.owner.getAngle() !== this._movementAngle + this._angleOffset
) {
this.owner.rotateTowardAngle(
this._movementAngle + this._angleOffset,
this._angularMaxSpeed
);
}
if (
Math.abs(newX - destinationX) < 1 &&
Math.abs(newY - destinationY) < 1 &&
(!gdjs.Base3DHandler.is3D(this.owner) ||
Math.abs(newZ - destinationZ) < 1)
) {
this._reachedEnd = true;
agent.resetMoveTarget();
}
this.owner.setX(newX);
this.owner.setY(newY);
if (gdjs.Base3DHandler.is3D(this.owner)) {
this.owner.setZ(newZ);
}
this._oldX = newX;
this._oldY = newY;
this._oldZ = newZ;
}
override doStepPostEvents(
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._manager.hasStepped = false;
}
getRadius(): float {
return (
this._radius ||
Math.min(this.owner.getWidth(), this.owner.getHeight()) / 2
);
}
setAcceleration(acceleration: float): void {
this._crowdAgentParams.maxAcceleration = acceleration;
if (this._agent) {
this._agent.setParameters(this._crowdAgentParams);
}
}
getAcceleration(): float {
return this._crowdAgentParams.maxAcceleration || 0;
}
setMaxSpeed(maxSpeed: float): void {
this._crowdAgentParams.maxSpeed = maxSpeed;
if (this._agent) {
this._agent.setParameters(this._crowdAgentParams);
}
}
getMaxSpeed(): float {
return this._crowdAgentParams.maxSpeed || 0;
}
setSpeed(speed: float): void {
if (this._agent) {
const velocityX = this._agent.raw.get_vel(0);
const velocityY = this._agent.raw.get_vel(1);
const velocityZ = this._agent.raw.get_vel(2);
const oldSpeed = Math.hypot(velocityX, velocityY, velocityZ);
if (oldSpeed === 0) {
this._agent.requestMoveVelocity({ x: speed, y: 0, z: 0 });
} else {
const ratio = speed / oldSpeed;
this._agent.raw.set_vel(0, velocityX * ratio);
this._agent.raw.set_vel(1, velocityY * ratio);
this._agent.raw.set_vel(2, velocityZ * ratio);
}
}
}
getSpeed(): float {
if (!this._agent) {
return 0;
}
const velocity = this._agent.desiredVelocity();
return Math.hypot(velocity.x, velocity.y, velocity.z);
}
getMovementAngle(): float {
return this._movementAngle;
}
movementAngleIsAround(degreeAngle: float, tolerance: float): boolean {
return (
Math.abs(
gdjs.evtTools.common.angleDifference(this._movementAngle, degreeAngle)
) <= tolerance
);
}
setAngularMaxSpeed(angularMaxSpeed: float): void {
this._angularMaxSpeed = angularMaxSpeed;
}
getAngularMaxSpeed(): float {
return this._angularMaxSpeed;
}
setAngleOffset(angleOffset: float): void {
this._angleOffset = angleOffset;
}
getAngleOffset(): float {
return this._angleOffset;
}
setRotateObject(allow: boolean): void {
this._rotateObject = allow;
}
isObjectRotated(): boolean {
return this._rotateObject;
}
getNodeX(index: integer): float {
if (index < this._path.length) {
return this._path[index].x;
}
return 0;
}
getNodeY(index: integer): float {
if (index < this._path.length) {
return this._path[index].y;
}
return 0;
}
getNodeZ(index: integer): float {
if (index < this._path.length) {
return this._path[index].z;
}
return 0;
}
getNextNodeIndex(): integer {
if (!this._agent) {
return 0;
}
const remainingNodes = this._agent.raw.ncorners;
return remainingNodes === 0
? this._path.length - 1
: this._path.length - remainingNodes;
}
getNodeCount(): integer {
return this._path.length;
}
getNextNodeX(): float {
if (this._path.length === 0) {
return 0;
}
const nextNodeIndex = this.getNextNodeIndex();
return this._path[nextNodeIndex].x;
}
getNextNodeY(): float {
if (this._path.length === 0) {
return 0;
}
const nextNodeIndex = this.getNextNodeIndex();
return this._path[nextNodeIndex].y;
}
getNextNodeZ(): float {
if (this._path.length === 0) {
return 0;
}
const nextNodeIndex = this.getNextNodeIndex();
return this._path[nextNodeIndex].z;
}
getPreviousNodeIndex(): integer {
if (!this._agent) {
return 0;
}
const remainingNodes = this._agent.raw.ncorners;
return remainingNodes === this._path.length
? 0
: this._path.length - remainingNodes - 1;
}
getPreviousNodeX(): float {
if (this._path.length < 2) {
return 0;
}
const previousNodeIndex = this.getPreviousNodeIndex();
return this._path[previousNodeIndex].x;
}
getPreviousNodeY(): float {
if (this._path.length < 2) {
return 0;
}
const previousNodeIndex = this.getPreviousNodeIndex();
return this._path[previousNodeIndex].y;
}
getPreviousNodeZ(): float {
if (this._path.length < 2) {
return 0;
}
const previousNodeIndex = this.getPreviousNodeIndex();
return this._path[previousNodeIndex].z;
}
getDestinationX(): float {
if (this._path.length === 0) {
return 0;
}
return this._path[this._path.length - 1].x;
}
getDestinationY(): float {
if (this._path.length === 0) {
return 0;
}
return this._path[this._path.length - 1].y;
}
getDestinationZ(): float {
if (this._path.length === 0) {
return 0;
}
return this._path[this._path.length - 1].z;
}
/**
* Return true if the latest call to moveTo succeeded.
*/
pathFound(): boolean {
return this._pathFound;
}
/**
* Return true if the object reached its destination.
*/
destinationReached(): boolean {
return this._reachedEnd;
}
}
gdjs.registerBehavior(
'NavMeshPathfinding::NavMeshCharacterBehavior',
gdjs.NavMeshCharacterRuntimeBehavior
);
}
@@ -0,0 +1,192 @@
/*
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2013-2016 Florian Rival ([email protected])
*/
namespace gdjs {
class NavMeshDebuggerPixiRenderer {
obstaclesManager: NavMeshObstaclesManager;
debugDrawerUtils: RecastNav.DebugDrawerUtils | null = null;
enabled = false;
meshes: Array<THREE.Mesh> = [];
material: THREE.MeshStandardMaterial | null = null;
isRegisteredFor2D = false;
constructor(obstaclesManager: NavMeshObstaclesManager) {
this.obstaclesManager = obstaclesManager;
}
setEnabled(
instanceContainer: gdjs.RuntimeInstanceContainer,
enabled: boolean
): void {
this.enabled = enabled;
if (enabled) {
this.renderFor3D();
if (!this.isRegisteredFor2D) {
this.isRegisteredFor2D = true;
this.registerFor2D(instanceContainer);
}
} else {
this.removeFor3D();
}
}
private registerFor2D(instanceContainer: gdjs.RuntimeInstanceContainer) {
instanceContainer
.getScene()
.registerDebugDrawHook((rendererObject: PIXI.Graphics) => {
if (!this.enabled || this.obstaclesManager.is3D) {
return;
}
const { navMesh, obstacles } = this.obstaclesManager;
if (!navMesh) {
return;
}
const firstObstacle: gdjs.NavMeshObstacleRuntimeBehavior = obstacles
.values()
.next().value;
if (!firstObstacle) {
return;
}
const scene = firstObstacle.owner.getRuntimeScene();
const layer = scene.getLayer(firstObstacle.owner.getLayer());
const workingPoint: FloatPoint = [0, 0];
rendererObject.beginFill();
rendererObject.fill.alpha = 0.5;
rendererObject.fill.color = 0x778ee8;
if (!this.debugDrawerUtils) {
this.debugDrawerUtils = new RecastNav.DebugDrawerUtils();
}
const primitives = this.debugDrawerUtils.drawNavMesh(navMesh);
const speedScaleY = this.obstaclesManager.speedScaleY;
for (const primitive of primitives) {
switch (primitive.type) {
case 'tris':
for (let i = 0; i + 2 < primitive.vertices.length; i += 3) {
const [x0, y0, z0] = primitive.vertices[i];
if (y0 < 1) {
const [x1, _y1, z1] = primitive.vertices[i + 1];
const [x2, _y2, z2] = primitive.vertices[i + 2];
const positions: Array<float> = [];
positions.push.apply(
positions,
layer.applyLayerTransformation(
x0,
z0 * speedScaleY,
0,
workingPoint
)
);
positions.push.apply(
positions,
layer.applyLayerTransformation(
x1,
z1 * speedScaleY,
0,
workingPoint
)
);
positions.push.apply(
positions,
layer.applyLayerTransformation(
x2,
z2 * speedScaleY,
0,
workingPoint
)
);
rendererObject.drawPolygon(positions);
}
}
break;
}
}
rendererObject.endFill();
});
}
removeFor3D() {
for (const mesh of this.meshes) {
mesh.removeFromParent();
}
this.meshes.length = 0;
}
renderFor3D() {
this.removeFor3D();
if (!this.enabled || !this.obstaclesManager.is3D) {
return;
}
const { navMesh, obstacles } = this.obstaclesManager;
if (!navMesh) {
return;
}
const firstObstacle: gdjs.NavMeshObstacleRuntimeBehavior = obstacles
.values()
.next().value;
const scene = firstObstacle.owner.getRuntimeScene();
const layer = scene.getLayer(firstObstacle.owner.getLayer());
const rendererObject = layer.get3DRendererObject();
if (!rendererObject) {
return;
}
if (!this.debugDrawerUtils) {
this.debugDrawerUtils = new RecastNav.DebugDrawerUtils();
}
const primitives = this.debugDrawerUtils.drawNavMesh(navMesh);
for (const primitive of primitives) {
switch (primitive.type) {
case 'tris':
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(primitive.vertices.length * 3);
for (let i = 0; i < primitive.vertices.length; i++) {
const [x, y, z] = primitive.vertices[i];
// Invert triangles side
const triangleVertexIndex = i % 3;
const vertexIndex =
triangleVertexIndex === 0
? i + 1
: triangleVertexIndex === 1
? i - 1
: i;
positions[vertexIndex * 3 + 0] = x;
positions[vertexIndex * 3 + 1] = z;
positions[vertexIndex * 3 + 2] = y;
}
geometry.setAttribute(
'position',
new THREE.BufferAttribute(positions, 3)
);
if (!this.material) {
this.material = new THREE.MeshStandardMaterial({
color: 0x778ee8,
});
}
const mesh = new THREE.Mesh(geometry, this.material);
this.meshes.push(mesh);
rendererObject.add(mesh);
break;
}
}
}
}
// Register the class to let the engine use it.
/**
* @category Debugging > Debugger Renderer
*/
export type NavMeshDebuggerRenderer = NavMeshDebuggerPixiRenderer;
/**
* @category Debugging > Debugger Renderer
*/
export const NavMeshDebuggerRenderer = NavMeshDebuggerPixiRenderer;
}
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
# NavMesh pathfinding extension
To update the library:
- Clone this fork https://github.com/D8H/recast-navigation-js
- Check out `main`
- Get the wasm build with npm
- Copy the wasm build into `recast-navigation-js/packages/recast-navigation-wasm/build`
- Run `yarn build`
- Check out `umd-build`
- Run `yarn build`
- Copy `recast-navigation-js/packages/recast-navigation/recast-navigation-generators.js` and the wasm build into this folder
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,399 @@
// @ts-check
describe('gdjs.NavMeshCharacterRuntimeBehavior', function () {
const characterBehaviorName = 'NavMeshCharacter';
before(async function () {
// Give some time for the Recast navigation library (WASM) to be loaded.
this.timeout(30000);
await gdjs.getAllAsynchronouslyLoadingLibraryPromise();
});
const createScene = (framePerSecond = 60) => {
const runtimeGame = gdjs.getPixiRuntimeGame();
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
sceneData: {
layers: [
{
name: '',
visibility: true,
effects: [],
cameras: [],
ambientLightColorR: 0,
ambientLightColorG: 0,
ambientLightColorB: 0,
isLightingLayer: false,
followBaseLayerCamera: true,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [],
objectsGroups: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});
const characterSharedData = {
name: characterBehaviorName,
type: 'NavMeshPathfinding::NavMeshCharacterBehavior',
cellSize: 10,
cellDepth: 10,
slopeMaxAngle: 50,
stairHeightMax: 20,
walkableRadius: -1,
walkableDepth: 150,
speedScaleY: 1,
};
runtimeScene.setInitialSharedDataForBehavior(
characterBehaviorName,
characterSharedData
);
setFramePerSecond(runtimeScene, framePerSecond);
return runtimeScene;
};
const setFramePerSecond = (runtimeScene, framePerSecond) => {
runtimeScene._timeManager.getElapsedTime = function () {
return 1000 / framePerSecond;
};
};
const addCharacter = (runtimeScene) => {
const character = new gdjs.RuntimeObject(
runtimeScene,
{
name: 'character',
type: '',
behaviors: [
{
type: 'NavMeshPathfinding::NavMeshCharacterBehavior',
name: characterBehaviorName,
// @ts-ignore - properties are not typed
acceleration: 400,
maxSpeed: 200,
angularMaxSpeed: 180,
rotateObject: false,
angleOffset: 0,
radius: 0,
avoidanceSightRange: 120,
},
],
effects: [],
variables: [],
},
undefined
);
character.getWidth = function () {
return 20;
};
character.getHeight = function () {
return 20;
};
runtimeScene.addObject(character);
return character;
};
const addObstacle = (runtimeScene, width = 100, height = 100) => {
const obstacle = new gdjs.RuntimeObject(
runtimeScene,
{
name: 'obstacle',
type: '',
behaviors: [
{
type: 'NavMeshPathfinding::NavMeshObstacleBehavior',
name: 'NavMeshObstacle',
// @ts-ignore - properties are not typed
shape: 'Box',
meshShapeResourceName: '',
},
],
effects: [],
variables: [],
},
undefined
);
obstacle.getWidth = function () {
return width;
};
obstacle.getHeight = function () {
return height;
};
runtimeScene.addObject(obstacle);
return obstacle;
};
/**
* Add obstacles far in 2 corners to define the walkable area:
* the ground used by the nav mesh covers the bounding box of all
* the obstacles.
*/
const addGroundDelimiters = (runtimeScene) => {
const topLeftObstacle = addObstacle(runtimeScene);
topLeftObstacle.setPosition(-300, -300);
const bottomRightObstacle = addObstacle(runtimeScene);
bottomRightObstacle.setPosition(1000, 700);
};
/** @returns {gdjs.NavMeshCharacterRuntimeBehavior} */
const getCharacterBehavior = (character) =>
// @ts-ignore - the behavior is from this extension.
character.getBehavior(characterBehaviorName);
/**
* The path nodes are the remaining corners of the path (not including
* the current character position), so the path length is measured
* from the character position.
*/
const getPathLength = (character) => {
const behavior = getCharacterBehavior(character);
if (behavior.getNodeCount() < 1) {
return 0;
}
let pathLength = 0;
let previousNodeX = character.getX();
let previousNodeY = character.getY();
for (let index = 0; index < behavior.getNodeCount(); index++) {
const nodeX = behavior.getNodeX(index);
const nodeY = behavior.getNodeY(index);
pathLength += Math.hypot(nodeX - previousNodeX, nodeY - previousNodeY);
previousNodeX = nodeX;
previousNodeY = nodeY;
}
return pathLength;
};
const stepUntilDestinationIsReached = (runtimeScene, character) => {
const behavior = getCharacterBehavior(character);
for (
let stepIndex = 0;
stepIndex < 600 && !behavior.destinationReached();
stepIndex++
) {
runtimeScene.renderAndStep(1000 / 60);
}
};
let runtimeScene;
let character;
beforeEach(function () {
runtimeScene = createScene();
character = addCharacter(runtimeScene);
addGroundDelimiters(runtimeScene);
});
it('can find a path without any obstacle in the way and reach the destination', function () {
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
getCharacterBehavior(character).moveTo(600, 300, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(true);
// The path is extracted from the agent at the 1st step following moveTo.
runtimeScene.renderAndStep(1000 / 60);
// The origin and destination are snapped on the nav mesh grid,
// so allow a margin on the path length.
expect(getPathLength(character)).to.be.within(180, 220);
stepUntilDestinationIsReached(runtimeScene, character);
expect(getCharacterBehavior(character).destinationReached()).to.be(true);
expect(character.getX()).to.be.within(580, 620);
expect(character.getY()).to.be.within(280, 320);
});
it('can find a path around an obstacle in the way and reach the destination', function () {
const blockingObstacle = addObstacle(runtimeScene, 50, 300);
blockingObstacle.setPosition(475, 150);
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
getCharacterBehavior(character).moveTo(600, 300, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(true);
// The path is extracted from the agent at the 1st step following moveTo.
runtimeScene.renderAndStep(1000 / 60);
// The path can't be a straight line, it has to go around the obstacle.
expect(getPathLength(character)).to.be.above(240);
stepUntilDestinationIsReached(runtimeScene, character);
expect(getCharacterBehavior(character).destinationReached()).to.be(true);
expect(character.getX()).to.be.within(580, 620);
expect(character.getY()).to.be.within(280, 320);
});
it('must not find a path to a destination outside of the walkable area', function () {
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// The destination is way outside of the walkable area
// (which is the bounding box of all the obstacles).
getCharacterBehavior(character).moveTo(600, 5000, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(false);
// The character must not move.
for (let stepIndex = 0; stepIndex < 10; stepIndex++) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(character.getX()).to.be.within(399, 401);
expect(character.getY()).to.be.within(299, 301);
});
it("keeps moving to its destination when a new destination can't be found", function () {
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
getCharacterBehavior(character).moveTo(600, 300, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(true);
// Let the character travel a bit.
for (let stepIndex = 0; stepIndex < 30; stepIndex++) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(character.getX()).to.be.above(420);
// Ask for a destination that can't be reached.
getCharacterBehavior(character).moveTo(600, 5000, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(false);
// The character keeps moving to its previous destination.
stepUntilDestinationIsReached(runtimeScene, character);
expect(getCharacterBehavior(character).destinationReached()).to.be(true);
expect(character.getX()).to.be.within(580, 620);
expect(character.getY()).to.be.within(280, 320);
});
it('can find a shorter path when an obstacle is deactivated', function () {
const blockingObstacle = addObstacle(runtimeScene, 50, 300);
blockingObstacle.setPosition(475, 150);
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// To build the nav mesh a 1st time with the blocking obstacle.
runtimeScene.renderAndStep(1000 / 60);
const obstacleBehavior =
/** @type {gdjs.NavMeshObstacleRuntimeBehavior} */ (
blockingObstacle.getBehavior('NavMeshObstacle')
);
obstacleBehavior.activate(false);
// The nav mesh is rebuilt at most once per second.
for (let stepIndex = 0; stepIndex < 61; stepIndex++) {
runtimeScene.renderAndStep(1000 / 60);
}
getCharacterBehavior(character).moveTo(600, 300, 0);
expect(getCharacterBehavior(character).pathFound()).to.be(true);
// The path is extracted from the agent at the 1st step following moveTo.
runtimeScene.renderAndStep(1000 / 60);
// The path is a straight line as the obstacle is gone.
expect(getPathLength(character)).to.be.within(180, 220);
});
describe('(network synchronization)', function () {
it('can sync an idle character over an idle character without error', function () {
const otherCharacter = addCharacter(runtimeScene);
otherCharacter.setPosition(200, 300);
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// Neither characters moved: their path is empty.
const syncData = getCharacterBehavior(character).getNetworkSyncData({});
expect(syncData.props.d).to.be(null);
// This must not crash nor start any movement.
getCharacterBehavior(otherCharacter).updateFromNetworkSyncData(
syncData,
{}
);
expect(getCharacterBehavior(otherCharacter).pathFound()).to.be(false);
expect(getCharacterBehavior(otherCharacter).getNodeCount()).to.be(0);
});
it('can sync a moving character over an idle character', function () {
const otherCharacter = addCharacter(runtimeScene);
otherCharacter.setPosition(300, 200);
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
getCharacterBehavior(character).moveTo(600, 300, 0);
// Let the path be extracted from the agent.
runtimeScene.renderAndStep(1000 / 60);
expect(getCharacterBehavior(character).getNodeCount()).to.be.above(1);
const syncData = getCharacterBehavior(character).getNetworkSyncData({});
getCharacterBehavior(otherCharacter).updateFromNetworkSyncData(
syncData,
{}
);
expect(getCharacterBehavior(otherCharacter).pathFound()).to.be(true);
stepUntilDestinationIsReached(runtimeScene, otherCharacter);
expect(getCharacterBehavior(otherCharacter).destinationReached()).to.be(
true
);
expect(otherCharacter.getX()).to.be.within(580, 620);
expect(otherCharacter.getY()).to.be.within(280, 320);
});
it('can sync an idle character over a moving character to stop it', function () {
const otherCharacter = addCharacter(runtimeScene);
otherCharacter.setPosition(400, 300);
character.setPosition(400, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
getCharacterBehavior(otherCharacter).moveTo(600, 300, 0);
// Let the path be extracted from the agent.
runtimeScene.renderAndStep(1000 / 60);
expect(getCharacterBehavior(otherCharacter).getNodeCount()).to.be.above(
1
);
// The (idle) character path is empty.
const syncData = getCharacterBehavior(character).getNetworkSyncData({});
expect(syncData.props.d).to.be(null);
getCharacterBehavior(otherCharacter).updateFromNetworkSyncData(
syncData,
{}
);
expect(getCharacterBehavior(otherCharacter).pathFound()).to.be(false);
expect(getCharacterBehavior(otherCharacter).getNodeCount()).to.be(0);
// The character must not move anymore.
const oldX = otherCharacter.getX();
const oldY = otherCharacter.getY();
for (let stepIndex = 0; stepIndex < 10; stepIndex++) {
runtimeScene.renderAndStep(1000 / 60);
}
expect(otherCharacter.getX()).to.be.within(oldX - 1, oldX + 1);
expect(otherCharacter.getY()).to.be.within(oldY - 1, oldY + 1);
});
});
});
+85 -85
View File
@@ -27,18 +27,18 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetTags("pathfinding, obstacle, collision")
.SetExtensionHelpPath("/behaviors/pathfinding");
extension.AddInstructionOrExpressionGroupMetadata(_("Pathfinding behavior"))
.SetIcon("CppPlatform/Extensions/AStaricon16.png");
.SetIcon("CppPlatform/Extensions/pathfinding-character.svg");
{
gd::BehaviorMetadata& aut =
extension.AddBehavior("PathfindingBehavior",
_("Pathfinding"),
_("Pathfinding character (grid-based)"),
"Pathfinding",
_("Move objects to a target "
"while avoiding all objects that are "
"flagged as obstacles."),
"",
"CppPlatform/Extensions/AStaricon.png",
"CppPlatform/Extensions/pathfinding-character.svg",
"PathfindingBehavior",
std::make_shared<PathfindingBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
@@ -48,8 +48,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Move the object to a position"),
_("Move _PARAM0_ to _PARAM3_;_PARAM4_"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddCodeOnlyParameter("currentScene", "")
@@ -63,8 +63,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if a path has been found."),
_("A path has been found for _PARAM0_"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -75,8 +75,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the destination was reached."),
_("_PARAM0_ reached its destination"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -87,8 +87,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the width of the cells of the virtual grid."),
_("the width of the virtual cells"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -102,8 +102,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the width of the cells of the virtual grid."),
_("the width of the virtual cells"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -116,8 +116,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the height of the cells of the virtual grid."),
_("the height of the virtual cells"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -131,8 +131,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the height of the cells of the virtual grid."),
_("the height of the virtual cells"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -145,8 +145,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the acceleration when moving the object"),
_("the acceleration on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -160,8 +160,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the acceleration when moving the object"),
_("the acceleration"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -174,8 +174,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the maximum speed when moving the object"),
_("the max. speed on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -191,8 +191,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the maximum speed when moving the object"),
_("the max. speed"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -207,8 +207,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the speed of the object on the path"),
_("the speed on the path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -224,8 +224,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the speed of the object on its path."),
_("the speed"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -241,8 +241,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the angle of movement of an object on its path."),
_("Angle of movement of _PARAM0_ is _PARAM2_ ± _PARAM3_°"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -254,8 +254,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the maximum angular speed when moving the object"),
_("the max. angular speed on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -272,8 +272,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the maximum angular speed when moving the object"),
_("the max. angular speed"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -289,8 +289,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the rotation offset applied when moving the object"),
_("the rotation offset on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -306,8 +306,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the rotation offset when moving the object"),
_("the rotation offset"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -324,8 +324,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"planning a path"),
_("the size of the extra border on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -340,8 +340,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"object when planning a path"),
_("the size of the extra border on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -355,8 +355,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Allow or restrict diagonal movement on the path"),
_("Allow diagonal movement for _PARAM0_ on the path: _PARAM2_"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -369,8 +369,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"diagonally on the path"),
_("Diagonal moves allowed for _PARAM0_"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -381,8 +381,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Enable or disable rotation of the object on the path"),
_("Enable rotation of _PARAM0_ on the path: _PARAM2_"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -395,8 +395,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
"its path."),
_("_PARAM0_ is rotated when traveling on its path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon24.png",
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg",
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
@@ -404,9 +404,9 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("GetNodeX",
_("Get a waypoint X position"),
_("Get next waypoint X position"),
_("Get a waypoint X position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
@@ -414,9 +414,9 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("GetNodeY",
_("Get a waypoint Y position"),
_("Get next waypoint Y position"),
_("Get a waypoint Y position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
@@ -426,7 +426,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Index of the next waypoint"),
_("Get the index of the next waypoint to reach"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeIndex");
@@ -435,7 +435,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Waypoint count"),
_("Get the number of waypoints on the path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNodeCount");
@@ -444,7 +444,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Get next waypoint X position"),
_("Get next waypoint X position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeX");
@@ -453,7 +453,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Get next waypoint Y position"),
_("Get next waypoint Y position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeY");
@@ -462,7 +462,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Last waypoint X position"),
_("Last waypoint X position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeX");
@@ -471,7 +471,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Last waypoint Y position"),
_("Last waypoint Y position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeY");
@@ -480,7 +480,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Destination X position"),
_("Destination X position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationX");
@@ -489,7 +489,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Destination Y position"),
_("Destination Y position"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationY");
@@ -498,7 +498,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Acceleration of the object on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAcceleration");
@@ -507,7 +507,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum speed"),
_("Maximum speed of the object on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetMaxSpeed");
@@ -516,7 +516,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Speed"),
_("Speed of the object on the path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetSpeed");
@@ -525,7 +525,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Angular maximum speed"),
_("Angular maximum speed of the object on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngularMaxSpeed");
@@ -534,7 +534,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotation offset"),
_("Rotation offset applied the object on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngleOffset");
@@ -543,7 +543,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Extra border size"),
_("Extra border applied the object on the path"),
_("Pathfinding configuration"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetExtraBorder");
@@ -552,7 +552,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Width of a cell"),
_("Width of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellWidth");
@@ -561,7 +561,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Height of a cell"),
_("Height of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellHeight");
@@ -570,7 +570,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Angle of movement on its path"),
_("Angle of movement on its path"),
_("Movement on the path"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior");
@@ -580,7 +580,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("X offset of the virtual grid"),
_("X offset of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
@@ -591,7 +591,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Y offset of the virtual grid"),
_("Y offset of the virtual grid"),
_("Virtual grid"),
"CppPlatform/Extensions/AStaricon24.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
@@ -600,11 +600,11 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
{
gd::BehaviorMetadata& aut = extension.AddBehavior(
"PathfindingObstacleBehavior",
_("Obstacle for pathfinding"),
_("Obstacle for pathfinding (grid-based)"),
"PathfindingObstacle",
_("Flag objects as being obstacles for pathfinding."),
"",
"CppPlatform/Extensions/pathfindingobstacleicon.png",
"CppPlatform/Extensions/pathfinding-obstacle.svg",
"PathfindingObstacleBehavior",
std::make_shared<PathfindingObstacleBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
@@ -614,8 +614,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the cost of going through the object."),
_("the cost"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",
"CppPlatform/Extensions/pathfindingobstacleicon16.png")
"CppPlatform/Extensions/pathfinding-obstacle.svg",
"CppPlatform/Extensions/pathfinding-obstacle.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
@@ -629,8 +629,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the cost of going through the object"),
_("the cost"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",
"CppPlatform/Extensions/pathfindingobstacleicon16.png")
"CppPlatform/Extensions/pathfinding-obstacle.svg",
"CppPlatform/Extensions/pathfinding-obstacle.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
@@ -643,8 +643,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Decide if the object is an impassable obstacle."),
_("Set _PARAM0_ as an impassable obstacle: _PARAM2_"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",
"CppPlatform/Extensions/pathfindingobstacleicon16.png")
"CppPlatform/Extensions/pathfinding-obstacle.svg",
"CppPlatform/Extensions/pathfinding-obstacle.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
@@ -656,8 +656,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the obstacle is impassable."),
_("_PARAM0_ is impassable"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",
"CppPlatform/Extensions/pathfindingobstacleicon16.png")
"CppPlatform/Extensions/pathfinding-obstacle.svg",
"CppPlatform/Extensions/pathfinding-obstacle.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
@@ -667,7 +667,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
_("Cost"),
_("Obstacle cost"),
_("Obstacles"),
"CppPlatform/Extensions/AStaricon16.png")
"CppPlatform/Extensions/pathfinding-character.svg")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.SetFunctionName("GetCost");
@@ -14,8 +14,7 @@ namespace gdjs {
* having a pathfinding behavior: In particular, the obstacles behaviors are
* required to declare themselves (see
* `PathfindingObstaclesManager.addObstacle`) to the manager of their
* associated container (see
* `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`).
* associated container (see `PathfindingObstaclesManager.getManager`).
* @category Behaviors > 2D Pathfinding
*/
export class PathfindingObstaclesManager {
@@ -10,7 +10,7 @@ namespace gdjs {
* Manages the common objects shared by objects having a
* platform behavior: in particular, the platforms behaviors are required to
* declare themselves (see PlatformObjectsManager.addPlatform) to the manager
* of their associated container (see PlatformRuntimeBehavior.getManager).
* of their associated container (see PlatformObjectsManager.getManager).
* @category Behaviors > Platform
*/
export class PlatformObjectsManager {
@@ -280,9 +280,11 @@ namespace gdjs {
if (this._debugDrawEnabled) {
this._debuggerRenderer.renderDebugDraw(
allInstancesList,
this._debugDrawShowHitBoxes,
this._debugDrawShowHiddenInstances,
this._debugDrawShowPointsNames,
this._debugDrawShowCustomPoints
this._debugDrawShowCustomPoints,
this._debugDrawHooks
);
}
+7
View File
@@ -52,9 +52,11 @@ namespace gdjs {
// Options for the debug draw:
_debugDrawEnabled: boolean = false;
_debugDrawShowHitBoxes: boolean = false;
_debugDrawShowHiddenInstances: boolean = false;
_debugDrawShowPointsNames: boolean = false;
_debugDrawShowCustomPoints: boolean = false;
_debugDrawHooks: Array<(rendererObject: any) => void> = [];
_onceTriggers: OnceTriggers;
@@ -243,11 +245,16 @@ namespace gdjs {
}
this._debugDrawEnabled = enableDebugDraw;
this._debugDrawShowHitBoxes = enableDebugDraw;
this._debugDrawShowHiddenInstances = showHiddenInstances;
this._debugDrawShowPointsNames = showPointsNames;
this._debugDrawShowCustomPoints = showCustomPoints;
}
registerDebugDrawHook(render: (rendererObject: any) => void) {
this._debugDrawHooks.push(render);
}
/**
* Check if an object is registered, meaning that instances of it can be
* created and lives in the container.
@@ -6,6 +6,7 @@ namespace gdjs {
* @category Debugging > Debugger Renderer
*/
export class DebuggerPixiRenderer {
// as this is for debug draw.
_instanceContainer: gdjs.RuntimeInstanceContainer;
_debugDraw: PIXI.Graphics | null = null;
_debugDrawContainer: PIXI.Container | null = null;
@@ -34,9 +35,11 @@ namespace gdjs {
*/
renderDebugDraw(
instances: gdjs.RuntimeObject[],
showHitBoxes: boolean,
showHiddenInstances: boolean,
showPointsNames: boolean,
showCustomPoints: boolean
showCustomPoints: boolean,
debugDrawHooks: Array<(rendererObject: any) => void>
) {
const pixiContainer = this._instanceContainer
.getRenderer()
@@ -84,6 +87,15 @@ namespace gdjs {
};
debugDraw.clear();
for (const render of debugDrawHooks) {
render(debugDraw);
}
if (!showHitBoxes) {
return;
}
debugDraw.beginFill();
debugDraw.alpha = 0.8;
debugDraw.lineStyle(2, 0x0000ff, 1);
+15
View File
@@ -4,6 +4,21 @@
* This project is released under the MIT License.
*/
namespace gdjs {
export type AbstractRuntimeObject3D = gdjs.RuntimeObject &
gdjs.Base3DHandler &
gdjs.Resizable &
gdjs.Scalable &
gdjs.Flippable;
/** @category Objects > 3D Objects */
export namespace Base3DHandler {
export const is3D = (
object: gdjs.RuntimeObject
): object is AbstractRuntimeObject3D => {
//@ts-ignore We are checking if the methods are present.
return object.getZ && object.setZ;
};
}
/**
* An axis-aligned bounding box. Used to represents a box around an object for example.
* @category Core Engine > Object
+3 -1
View File
@@ -478,9 +478,11 @@ namespace gdjs {
if (this._debugDrawEnabled) {
this._debuggerRenderer.renderDebugDraw(
this.getAdhocListOfAllInstances(),
this._debugDrawShowHitBoxes,
this._debugDrawShowHiddenInstances,
this._debugDrawShowPointsNames,
this._debugDrawShowCustomPoints
this._debugDrawShowCustomPoints,
this._debugDrawHooks
);
}
+174
View File
@@ -0,0 +1,174 @@
import {
init,
DebugDrawerUtils,
RecastBuildContext,
RecastChunkyTriMesh,
RecastSpan,
RecastSpanPool,
RecastHeightfield,
RecastCompactCell,
RecastCompactSpan,
RecastCompactHeightfield,
RecastContour,
RecastContourSet,
RecastHeightfieldLayer,
RecastHeightfieldLayerSet,
RecastPolyMesh,
RecastPolyMeshDetail,
IntArray,
UnsignedIntArray,
UnsignedCharArray,
UnsignedShortArray,
FloatArray,
QueryFilter,
NavMeshQuery,
CrowdAgent,
Crowd,
NavMeshGetTilesAtResult,
NavMeshRemoveTileResult,
NavMeshCalcTileLocResult,
NavMeshStoreTileStateResult,
NavMeshParams,
NavMesh,
DetourTileCacheParams,
TileCache,
TileCacheMeshProcess,
DetourPolyDetail,
DetourLink,
DetourBVNode,
DetourOffMeshConnection,
DetourMeshHeader,
DetourPoly,
DetourMeshTile,
NavMeshCreateParams,
DebugDrawerPrimitiveType,
DebugDrawerPrimitive,
ObstacleRef,
BoxObstacle,
CylinderObstacle,
AddObstacleResult,
RemoveObstacleResult,
Obstacle,
TileCacheParamsType,
TileCacheUpdateResult,
NavMeshDecodePolyIdResult,
NavMeshParamsType,
ImportNavMeshResult,
ImportTileCacheResult,
Pretty,
RecastConfig,
OffMeshConnectionParams,
Vector3,
Vector3Tuple,
Vector2,
Vector2Tuple,
CrowdAgentParams,
CrowdParams,
NavMeshQueryParams,
} from 'recast-navigation';
import {
buildTiledNavMeshRcConfig,
createDefaultTileCacheMeshProcess,
dtIlog2,
dtNextPow2,
generateSoloNavMesh,
generateSoloNavMeshData,
generateTileCache,
generateTileNavMeshData,
generateTiledNavMesh,
getBoundingBox,
mergePositionsAndIndices,
soloNavMeshGeneratorConfigDefaults,
tileCacheGeneratorConfigDefaults,
tiledNavMeshGeneratorConfigDefaults,
SoloNavMeshGeneratorConfig,
} from 'recast-navigation/generators';
declare global {
namespace RecastNav {
export {
init,
DebugDrawerUtils,
RecastBuildContext,
RecastChunkyTriMesh,
RecastSpan,
RecastSpanPool,
RecastHeightfield,
RecastCompactCell,
RecastCompactSpan,
RecastCompactHeightfield,
RecastContour,
RecastContourSet,
RecastHeightfieldLayer,
RecastHeightfieldLayerSet,
RecastPolyMesh,
RecastPolyMeshDetail,
IntArray,
UnsignedIntArray,
UnsignedCharArray,
UnsignedShortArray,
FloatArray,
QueryFilter,
NavMeshQuery,
CrowdAgent,
Crowd,
NavMeshGetTilesAtResult,
NavMeshRemoveTileResult,
NavMeshCalcTileLocResult,
NavMeshStoreTileStateResult,
NavMeshParams,
NavMesh,
DetourTileCacheParams,
TileCache,
TileCacheMeshProcess,
DetourPolyDetail,
DetourLink,
DetourBVNode,
DetourOffMeshConnection,
DetourMeshHeader,
DetourPoly,
DetourMeshTile,
NavMeshCreateParams,
DebugDrawerPrimitiveType,
DebugDrawerPrimitive,
ObstacleRef,
BoxObstacle,
CylinderObstacle,
AddObstacleResult,
RemoveObstacleResult,
Obstacle,
TileCacheParamsType,
TileCacheUpdateResult,
NavMeshDecodePolyIdResult,
NavMeshParamsType,
ImportNavMeshResult,
ImportTileCacheResult,
Pretty,
RecastConfig,
OffMeshConnectionParams,
Vector3,
Vector3Tuple,
Vector2,
Vector2Tuple,
CrowdAgentParams,
CrowdParams,
NavMeshQueryParams,
buildTiledNavMeshRcConfig,
createDefaultTileCacheMeshProcess,
dtIlog2,
dtNextPow2,
generateSoloNavMesh,
generateSoloNavMeshData,
generateTileCache,
generateTileNavMeshData,
generateTiledNavMesh,
getBoundingBox,
mergePositionsAndIndices,
soloNavMeshGeneratorConfigDefaults,
tileCacheGeneratorConfigDefaults,
tiledNavMeshGeneratorConfigDefaults,
SoloNavMeshGeneratorConfig,
};
}
}
+40
View File
@@ -22,6 +22,7 @@
"patch-package": "^6.4.7",
"pixi.js": "7.4.2",
"prettier": "3.4.2",
"recast-navigation": "0.43.1",
"recursive-readdir": "^2.2.2",
"shelljs": "^0.8.4",
"typedoc": "^0.25.13",
@@ -285,6 +286,34 @@
"node": ">=14"
}
},
"node_modules/@recast-navigation/core": {
"version": "0.43.1",
"resolved": "https://registry.npmjs.org/@recast-navigation/core/-/core-0.43.1.tgz",
"integrity": "sha512-4igfPgnoV90O92sDSIA0xHa6tanh3z/udlgGmD0SqGQ6PKV4JS2l9jWO8YnxfZuXP/NV52H4c5FU6pSdZc9WDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@recast-navigation/wasm": "0.43.1"
}
},
"node_modules/@recast-navigation/generators": {
"version": "0.43.1",
"resolved": "https://registry.npmjs.org/@recast-navigation/generators/-/generators-0.43.1.tgz",
"integrity": "sha512-w7r6k/A93wWxuSN+Lg+PmlsUVXq06r5bqXmBglolxqDt2O/Y776BIhYtb6P2P6pbYqDWORu9NCZZJzfY4FNpaA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@recast-navigation/core": "0.43.1",
"@recast-navigation/wasm": "0.43.1"
}
},
"node_modules/@recast-navigation/wasm": {
"version": "0.43.1",
"resolved": "https://registry.npmjs.org/@recast-navigation/wasm/-/wasm-0.43.1.tgz",
"integrity": "sha512-XFL6PhO8JodwXUHBSAkfaLUdUEweIkJsdP3HTmtJvMs5fkqEWxWJmBskZlACHJepv55NAriqWxOtuLlG3t7+Hg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/css-font-loading-module": {
"version": "0.0.12",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
@@ -2252,6 +2281,17 @@
"node": ">= 4"
}
},
"node_modules/recast-navigation": {
"version": "0.43.1",
"resolved": "https://registry.npmjs.org/recast-navigation/-/recast-navigation-0.43.1.tgz",
"integrity": "sha512-BVBQEHE6uqD36opJomVkI5TxMVZ8bBLdDn90mYtBUYJnNlqEuNFOL8DH8lLOksfVVaC+kjykYuS57P6MrxVB7A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@recast-navigation/core": "0.43.1",
"@recast-navigation/generators": "0.43.1"
}
},
"node_modules/rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+1
View File
@@ -17,6 +17,7 @@
"patch-package": "^6.4.7",
"@esotericsoftware/spine-pixi-v7": "~4.2.116",
"pixi.js": "7.4.2",
"recast-navigation": "0.43.1",
"prettier": "3.4.2",
"recursive-readdir": "^2.2.2",
"shelljs": "^0.8.4",
+19
View File
@@ -144,6 +144,25 @@ module.exports = function (config) {
'./newIDE/app/resources/GDJS/Runtime/Extensions/PathfindingBehavior/PathTools.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/PathfindingBehavior/pathfindingobstacleruntimebehavior.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/PathfindingBehavior/pathfindingruntimebehavior.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/NavMeshPathfinding/recast-navigation-generators.js',
{
pattern:
'./newIDE/app/resources/GDJS/Runtime/Extensions/NavMeshPathfinding/recast-navigation.wasm.js',
watched: true,
included: false,
served: true,
nocache: false,
},
{
pattern:
'./newIDE/app/resources/GDJS/Runtime/Extensions/NavMeshPathfinding/recast-navigation.wasm.wasm',
watched: true,
included: false,
served: true,
nocache: false,
},
'./newIDE/app/resources/GDJS/Runtime/Extensions/NavMeshPathfinding/NavMeshObstacleRuntimeBehavior.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/NavMeshPathfinding/NavMeshCharacterRuntimeBehavior.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/PrimitiveDrawing/shapepainterruntimeobject.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/PrimitiveDrawing/shapepainterruntimeobject-pixi-renderer.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/TextInput/textinputruntimeobject.js',
@@ -0,0 +1,22 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 0H0V14H14V0Z" fill="#27AAE1"/>
<path d="M10 9.5H9V13H10V9.5Z" fill="#2B3990"/>
<path d="M13 11H9V9H13L12 10L13 11Z" fill="#2B3990"/>
<path d="M3 5C4.10457 5 5 4.10457 5 3C5 1.89543 4.10457 1 3 1C1.89543 1 1 1.89543 1 3C1 4.10457 1.89543 5 3 5Z" fill="#2B3990"/>
<path d="M3 4C3.55228 4 4 3.55228 4 3C4 2.44772 3.55228 2 3 2C2.44772 2 2 2.44772 2 3C2 3.55228 2.44772 4 3 4Z" fill="white"/>
<path d="M11 2.5H10V4.5H11V2.5Z" fill="white"/>
<path d="M8 2.5H6V3.5H8V2.5Z" fill="white"/>
<path d="M11 2.5H9V3.5H11V2.5Z" fill="white"/>
<path d="M11 5.5H10V7.5H11V5.5Z" fill="white"/>
<path d="M11 6.5H9V7.5H11V6.5Z" fill="white"/>
<path d="M8 6.5H6V7.5H8V6.5Z" fill="white"/>
<path d="M5 6.5H3V7.5H5V6.5Z" fill="white"/>
<path d="M4 6.5H3V8.5H4V6.5Z" fill="white"/>
<path d="M4 9.5H3V11.5H4V9.5Z" fill="white"/>
<path d="M5 10.5H3V11.5H5V10.5Z" fill="white"/>
<path d="M8 10.5H6V11.5H8V10.5Z" fill="white"/>
<g opacity="0.1">
<path d="M14 14L0 0V14H14Z" fill="black"/>
<path d="M14 0H0L14 14V0Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14" height="14" fill="none" version="1.1" viewBox="0 0 14 14" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g fill="#2b3990"><path d="m7 6.5h-1v2h1z"/><path d="m7 9.5h-1v2h1z"/><path d="m7 10.5h-2v1h2z"/><path d="m4 10.5h-2v1h2z"/><path d="m7 3.5h-1v2h1z"/><path d="m6.5 2-1.5 2h3z" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/></g><g transform="matrix(0 .85714 -.875 0 28.375 5.2857)"><path d="m1 17-4 4h2l4-4z" fill="#27aae1"/><path d="m-1 17-4 4h2l4-4z" fill="#2b3990"/><path d="m-3 17-2 2v2l4-4z" fill="#27aae1"/><path d="m-3 17h-2v2z" fill="#2b3990"/><path d="m7 21h2v-2z" fill="#2b3990"/><path d="m9 17-4 4h2l2-2z" fill="#27aae1"/><path d="m7 17-4 4h2l4-4z" fill="#2b3990"/><path d="m5 17-4 4h2l4-4z" fill="#27aae1"/><path d="m3 17-4 4h2l4-4z" fill="#2b3990"/></g></svg>

After

Width:  |  Height:  |  Size: 861 B

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14" height="14" fill="none" version="1.1" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
<path d="M14 0H0V14H14V0Z" fill="#27AAE1"/>
<g fill="#2B3990">
<path d="M10 9.5H9V13H10V9.5Z"/>
<path d="m13 11h-4v-2h4l-1 1 1 1z"/>
<path d="m3 5c1.1046 0 2-0.89543 2-2s-0.89543-2-2-2-2 0.89543-2 2 0.89543 2 2 2z"/>
</g>
<g fill="#fff">
<path d="m3 4c0.55228 0 1-0.44772 1-1s-0.44772-1-1-1-1 0.44772-1 1 0.44772 1 1 1z"/>
<path d="m10.93 4.1018h-1v2h1z"/>
<path d="m8.1107 3.0757-1.8794-0.68404-0.34202 0.93969 1.8794 0.68404z"/>
<path d="m10.93 4.1018-1.8794-0.68404-0.34202 0.93969 1.8794 0.68404z"/>
</g>
<g transform="rotate(-20,7,7)" fill="#fff">
<path d="m11 6.5h-2v1h2z"/>
<path d="m8 6.5h-2v1h2z"/>
<path d="m5 6.5h-2v1h2z"/>
</g>
<g fill="#fff">
<path d="m4.0702 7.8982h-1v2h1z"/>
<path d="m5.2916 9.6426-1.8794-0.68404-0.34202 0.93969 1.8794 0.68404z"/>
<path d="m8.1107 10.669-1.8794-0.68404-0.34202 0.93969 1.8794 0.68404z"/>
</g>
<g opacity=".1">
<path d="m14 14-14-14v14z" fill="#000"/>
<path d="m14 0h-14l14 14z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14" height="14" fill="none" version="1.1" viewBox="0 0 14 14" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m4 10.5h-2v1h2z" fill="#2b3990"/><g transform="rotate(20 5.5 2.9931)" fill="#2b3990"><path d="m7 6.5h-1v2h1z"/><path d="m7 9.5h-1v2h1z"/><path d="m7 3.5h-1v2h1z"/><path d="m6.5 2-1.5 2h3z" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/></g><g stroke-width=".85714"><path d="m13 4.4286-2.1429-2.1429-0.42857 1.2857 2.5714 2.5714z" fill="#27aae1"/><path d="m13 2.7143-1.7143-1.7143-0.42857 1.2857 2.1429 2.1429z" fill="#2b3990"/><path d="m13 1h-1.7143l1.7143 1.7143z" fill="#27aae1"/><path d="m8.7143 8.7143-0.42857 1.2857 3 3h1.7143z" fill="#2b3990"/><path d="m7.8571 11.286-0.42857 1.2857-0.14314 0.42857h2.286z" fill="#2b3990"/><path d="m13 11.286-3.8571-3.8571-0.42857 1.2857 4.2857 4.2857z" fill="#27aae1"/><path d="m11.286 13-3-3-0.42857 1.2857 1.7143 1.7143z" fill="#27aae1"/><path d="m13 9.5714-3.4286-3.4286-0.42857 1.2857 3.8571 3.8571z" fill="#2b3990"/><path d="m13 7.8571-3-3-0.42857 1.2857 3.4286 3.4286z" fill="#27aae1"/><path d="m13 6.1429-2.5714-2.5714-0.42857 1.2857 3 3z" fill="#2b3990"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -31,7 +31,7 @@ type GetExpectedNumberOfJSExtensionModulesArguments = {|
function getExpectedNumberOfJSExtensionModules(
{ filterExamples } /*: GetExpectedNumberOfJSExtensionModulesArguments*/
) /*:number*/ {
return 29 + (filterExamples ? 0 : 1);
return 30 + (filterExamples ? 0 : 1);
}
/**