diff --git a/Extensions/Physics3DBehavior/JsExtension.js b/Extensions/Physics3DBehavior/JsExtension.js index 59b8a65fbdaf..68b31b7af715 100644 --- a/Extensions/Physics3DBehavior/JsExtension.js +++ b/Extensions/Physics3DBehavior/JsExtension.js @@ -1551,6 +1551,13 @@ module.exports = { return true; } + if (propertyName === 'canBePushed') { + behaviorContent + .getChild('canBePushed') + .setBoolValue(newValue === '1'); + return true; + } + return false; }; behavior.getProperties = function (behaviorContent) { @@ -1745,6 +1752,22 @@ module.exports = { .setAdvanced(true) .setQuickCustomizationVisibility(gd.QuickCustomization.Hidden); + if (!behaviorContent.hasChild('canBePushed')) { + behaviorContent.addChild('canBePushed').setBoolValue(true); + } + behaviorProperties + .getOrCreate('canBePushed') + .setLabel('Can be pushed by other characters') + .setGroup(_('Walk')) + .setType('Boolean') + .setValue( + behaviorContent.getChild('canBePushed').getBoolValue() + ? 'true' + : 'false' + ) + .setAdvanced(true) + .setQuickCustomizationVisibility(gd.QuickCustomization.Hidden); + return behaviorProperties; }; @@ -1765,6 +1788,7 @@ module.exports = { behaviorContent .addChild('shouldBindObjectAndForwardAngle') .setBoolValue(true); + behaviorContent.addChild('canBePushed').setBoolValue(true); }; const aut = extension diff --git a/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts b/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts index 1306991344c2..280d3e8c97a4 100644 --- a/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts +++ b/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts @@ -807,15 +807,25 @@ namespace gdjs { */ getBodyLayer(): number { return Jolt.ObjectLayerPairFilterMask.prototype.sGetObjectLayer( - // Make sure objects don't register in the wrong layer group. - this.isStatic() - ? this.layers & gdjs.Physics3DSharedData.staticLayersMask - : this.layers & gdjs.Physics3DSharedData.dynamicLayersMask, - // Static objects accept all collisions as it's the mask of dynamic objects that matters. - this.isStatic() ? gdjs.Physics3DSharedData.allLayersMask : this.masks + this.getLayersAccordingToBodyType(), + this.getMasksAccordingToBodyType() ); } + private getLayersAccordingToBodyType(): integer { + // Make sure objects don't register in the wrong layer group. + return this.isStatic() + ? this.layers & gdjs.Physics3DSharedData.staticLayersMask + : this.layers & gdjs.Physics3DSharedData.dynamicLayersMask; + } + + private getMasksAccordingToBodyType(): integer { + // Static objects accept all collisions as it's the mask of dynamic objects that matters. + return this.isStatic() + ? gdjs.Physics3DSharedData.allLayersMask + : this.masks; + } + doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { // Step the world if not done this frame yet. // Don't step at the first frame to allow events to handle overlapping objects. @@ -1689,6 +1699,14 @@ namespace gdjs { } } + canCollideAgainst(otherBehavior: gdjs.Physics3DRuntimeBehavior): boolean { + return ( + (this.getMasksAccordingToBodyType() & + otherBehavior.getLayersAccordingToBodyType()) !== + 0 + ); + } + static areObjectsColliding( object1: gdjs.RuntimeObject, object2: gdjs.RuntimeObject, diff --git a/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts b/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts index b3f3e9bb2a33..f7059815e26a 100644 --- a/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts +++ b/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.ts @@ -72,6 +72,7 @@ namespace gdjs { private _jumpSpeed: float; private _jumpSustainTime: float; private _stairHeightMax: float; + _canBePushed: boolean; private _hasPressedForwardKey: boolean = false; private _hasPressedBackwardKey: boolean = false; @@ -145,6 +146,10 @@ namespace gdjs { behaviorData.stairHeightMax === undefined ? 20 : behaviorData.stairHeightMax; + this._canBePushed = + behaviorData.canBePushed === undefined + ? true + : behaviorData.canBePushed; } private getVec3(x: float, y: float, z: float): Jolt.Vec3 { @@ -1324,6 +1329,8 @@ namespace gdjs { export namespace PhysicsCharacter3DRuntimeBehavior { export class CharacterBodyUpdater { characterBehavior: gdjs.PhysicsCharacter3DRuntimeBehavior; + /** Handle collisions between characters that can push each other. */ + static characterVsCharacterCollision: Jolt.CharacterVsCharacterCollisionSimple | null = null; constructor(characterBehavior: gdjs.PhysicsCharacter3DRuntimeBehavior) { this.characterBehavior = characterBehavior; @@ -1336,7 +1343,13 @@ namespace gdjs { const shape = behavior.createShape(); const settings = new Jolt.CharacterVirtualSettings(); - settings.mInnerBodyLayer = behavior.getBodyLayer(); + // Characters innerBody are Kinematic body, they don't allow other + // characters to push them. + // The layer 0 doesn't allow any collision as masking them always result to 0. + // This allows CharacterVsCharacterCollisionSimple to handle the collisions. + settings.mInnerBodyLayer = this.characterBehavior._canBePushed + ? 0 + : behavior.getBodyLayer(); settings.mInnerBodyShape = shape; settings.mMass = shape.GetMassProperties().get_mMass(); settings.mMaxSlopeAngle = gdjs.toRad(_slopeMaxAngle); @@ -1376,6 +1389,106 @@ namespace gdjs { .GetBodyLockInterface() .TryGetBody(character.GetInnerBodyID()); this.characterBehavior.character = character; + + if (this.characterBehavior._canBePushed) { + // CharacterVsCharacterCollisionSimple handle characters pushing each other. + let characterVsCharacterCollision = + CharacterBodyUpdater.characterVsCharacterCollision; + if (!characterVsCharacterCollision) { + characterVsCharacterCollision = new Jolt.CharacterVsCharacterCollisionSimple(); + CharacterBodyUpdater.characterVsCharacterCollision = characterVsCharacterCollision; + } + characterVsCharacterCollision.Add(character); + character.SetCharacterVsCharacterCollision( + characterVsCharacterCollision + ); + + const characterContactListener = new Jolt.CharacterContactListenerJS(); + characterContactListener.OnAdjustBodyVelocity = ( + character, + body2Ptr, + linearVelocityPtr, + angularVelocity + ) => {}; + characterContactListener.OnContactValidate = ( + character, + bodyID2, + subShapeID2 + ) => { + return true; + }; + characterContactListener.OnCharacterContactValidate = ( + characterPtr, + otherCharacterPtr, + subShapeID2 + ) => { + // CharacterVsCharacterCollisionSimple doesn't handle collision layers. + // We have to filter characters ourself. + const character = Jolt.wrapPointer( + characterPtr, + Jolt.CharacterVirtual + ); + const otherCharacter = Jolt.wrapPointer( + otherCharacterPtr, + Jolt.CharacterVirtual + ); + + const body = _sharedData.physicsSystem + .GetBodyLockInterface() + .TryGetBody(character.GetInnerBodyID()); + const otherBody = _sharedData.physicsSystem + .GetBodyLockInterface() + .TryGetBody(otherCharacter.GetInnerBodyID()); + + const physicsBehavior = body.gdjsAssociatedBehavior; + const otherPhysicsBehavior = otherBody.gdjsAssociatedBehavior; + + if (!physicsBehavior || !otherPhysicsBehavior) { + return true; + } + return physicsBehavior.canCollideAgainst(otherPhysicsBehavior); + }; + characterContactListener.OnContactAdded = ( + character, + bodyID2, + subShapeID2, + contactPosition, + contactNormal, + settings + ) => {}; + characterContactListener.OnCharacterContactAdded = ( + character, + otherCharacter, + subShapeID2, + contactPosition, + contactNormal, + settings + ) => {}; + characterContactListener.OnContactSolve = ( + character, + bodyID2, + subShapeID2, + contactPosition, + contactNormal, + contactVelocity, + contactMaterial, + characterVelocity, + newCharacterVelocity + ) => {}; + characterContactListener.OnCharacterContactSolve = ( + character, + otherCharacter, + subShapeID2, + contactPosition, + contactNormal, + contactVelocity, + contactMaterial, + characterVelocityPtr, + newCharacterVelocityPtr + ) => {}; + character.SetListener(characterContactListener); + } + // TODO This is not really reliable. We could choose to disable it and force user to use the "is on platform" condition. //body.SetCollideKinematicVsNonDynamic(true); return body;