From 4b6d884bcae9fa2d25d2cda8a90b34dd79e4b97c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 16:14:31 +0000 Subject: [PATCH 1/3] Fire the tank shells from the end of the cannon, along it The tank of the 3D tank starters returns the position of the end of its cannon (MuzzleX/Y/Z) and the direction it points at (AimDirectionX/Y/Z), with the conversions of custom objects to their parent: the shell starts there, turned and pushed along the cannon, whatever the turret, the elevation of the cannon and the slope the tank is on. These conversions come with GDevelop 5.6.286: examples using them now need it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Tk9so3zqwFutR1h9SJnQQS --- .../starting-3d-tank/starting-3d-tank.json | 443 ++++++++++++++++-- .../starting-expanded-3d-tank.json | 443 ++++++++++++++++-- scripts/lib/ExampleGDevelopVersion.js | 89 +++- .../__tests__/ExampleGDevelopVersion.spec.js | 45 +- 4 files changed, 931 insertions(+), 89 deletions(-) diff --git a/examples/starting-3d-tank/starting-3d-tank.json b/examples/starting-3d-tank/starting-3d-tank.json index 1fb051431..53b8c4a9e 100644 --- a/examples/starting-3d-tank/starting-3d-tank.json +++ b/examples/starting-3d-tank/starting-3d-tank.json @@ -1870,8 +1870,8 @@ "parameters": [ "", "Bullet", - "PlayerTank.CenterX() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", - "PlayerTank.CenterY() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", + "PlayerTank.MuzzleX()", + "PlayerTank.MuzzleY()", "" ] }, @@ -1883,28 +1883,28 @@ "Bullet", "Object3D", "=", - "(PlayerTank.Object3D::CenterZ()) + sin(ToRad(-1 * PlayerTank.CannonAngle() - PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))) * PlayerTank.CannonWidth()" + "PlayerTank.MuzzleZ()" ] }, { "type": { - "value": "Scene3D::Base3DBehavior::SetRotationY" + "value": "SetAngle" }, "parameters": [ "Bullet", - "Object3D", "=", - "(PlayerTank.CannonAngle() + PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))" + "ToDeg(atan2(PlayerTank.AimDirectionY(), PlayerTank.AimDirectionX()))" ] }, { "type": { - "value": "SetAngle" + "value": "Scene3D::Base3DBehavior::SetRotationY" }, "parameters": [ "Bullet", + "Object3D", "=", - "PlayerTank.TopRotation() + PlayerTank.Angle()" + "-ToDeg(asin(clamp(PlayerTank.AimDirectionZ(), -1, 1)))" ] }, { @@ -1932,15 +1932,14 @@ }, { "type": { - "value": "Physics3D::Physics3DBehavior::ApplyImpulseTowardPosition" + "value": "Physics3D::Physics3DBehavior::ApplyImpulseAtCenter" }, "parameters": [ "Bullet", "Physics3D", - "15", - "Bullet.X() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", - "Bullet.Y() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", - "Bullet.Object3D::Z() + sin(ToRad(-1 * Bullet.Object3D::RotationY())) * 10" + "15 * PlayerTank.AimDirectionX()", + "15 * PlayerTank.AimDirectionY()", + "15 * PlayerTank.AimDirectionZ()" ] }, { @@ -2501,13 +2500,7 @@ "authorIds": [], "dependencies": [], "globalVariables": [], - "sceneVariables": [ - { - "name": "CannonWidth", - "type": "number", - "value": 0 - } - ], + "sceneVariables": [], "eventsFunctions": [], "eventsFunctionsFolderStructure": { "folderName": "__ROOT" @@ -2827,10 +2820,10 @@ "objectGroups": [] }, { - "description": "Return the distance between the end of the cannon and the center of the TankTop object", - "fullName": "Return CannonWidth", + "description": "Return the X position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (X)", "functionType": "Expression", - "name": "CannonWidth", + "name": "MuzzleX", "sentence": "", "events": [ { @@ -2839,19 +2832,185 @@ "actions": [ { "type": { - "value": "TankConfiguration::TankTop::CannonInfo" + "value": "SetReturnNumber" }, "parameters": [ - "TankTop_Combined", - "" + "Object.ToParentX(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" ] - }, + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (Y)", + "functionType": "Expression", + "name": "MuzzleY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentY(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (Z)", + "functionType": "Expression", + "name": "MuzzleZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ { "type": { "value": "SetReturnNumber" }, "parameters": [ - "CannonWidth" + "Object.ToParentZ(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (X)", + "functionType": "Expression", + "name": "AimDirectionX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionX(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (Y)", + "functionType": "Expression", + "name": "AimDirectionY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionY(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (Z)", + "functionType": "Expression", + "name": "AimDirectionZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionZ(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" ] } ] @@ -2892,7 +3051,22 @@ ] }, { - "functionName": "CannonWidth" + "functionName": "MuzzleX" + }, + { + "functionName": "MuzzleY" + }, + { + "functionName": "MuzzleZ" + }, + { + "functionName": "AimDirectionX" + }, + { + "functionName": "AimDirectionY" + }, + { + "functionName": "AimDirectionZ" } ] }, @@ -3127,11 +3301,11 @@ "objectGroups": [] }, { - "description": "Record variables related to the TankCannon object", - "fullName": "Record TankCannon information", - "functionType": "Action", - "name": "CannonInfo", - "sentence": "Record information for the TankCannon of _PARAM0_", + "description": "Return the X position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (X)", + "functionType": "Expression", + "name": "MuzzleX", + "sentence": "", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -3139,17 +3313,193 @@ "actions": [ { "type": { - "value": "SetNumberVariable" + "value": "SetReturnNumber" }, "parameters": [ - "CannonWidth", - "=", - "TankCanon.Width() + TankTop.Width()/2" + "Object.ToParentX(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (Y)", + "functionType": "Expression", + "name": "MuzzleY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentY(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (Z)", + "functionType": "Expression", + "name": "MuzzleZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentZ(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" ] } ] } ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (X)", + "functionType": "Expression", + "name": "AimDirectionX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionX(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (Y)", + "functionType": "Expression", + "name": "AimDirectionY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionY(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (Z)", + "functionType": "Expression", + "name": "AimDirectionZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionZ(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, "parameters": [ { "description": "Object", @@ -3168,7 +3518,22 @@ "functionName": "Elevation" }, { - "functionName": "CannonInfo" + "functionName": "MuzzleX" + }, + { + "functionName": "MuzzleY" + }, + { + "functionName": "MuzzleZ" + }, + { + "functionName": "AimDirectionX" + }, + { + "functionName": "AimDirectionY" + }, + { + "functionName": "AimDirectionZ" } ] }, diff --git a/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json index 524a63ee3..b7d9c36e3 100644 --- a/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json +++ b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json @@ -2996,8 +2996,8 @@ "parameters": [ "", "Bullet", - "PlayerTank.CenterX() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", - "PlayerTank.CenterY() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", + "PlayerTank.MuzzleX()", + "PlayerTank.MuzzleY()", "" ] }, @@ -3009,28 +3009,28 @@ "Bullet", "Object3D", "=", - "(PlayerTank.Object3D::CenterZ()) + sin(ToRad(-1 * PlayerTank.CannonAngle() - PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))) * PlayerTank.CannonWidth()" + "PlayerTank.MuzzleZ()" ] }, { "type": { - "value": "Scene3D::Base3DBehavior::SetRotationY" + "value": "SetAngle" }, "parameters": [ "Bullet", - "Object3D", "=", - "(PlayerTank.CannonAngle() + PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))" + "ToDeg(atan2(PlayerTank.AimDirectionY(), PlayerTank.AimDirectionX()))" ] }, { "type": { - "value": "SetAngle" + "value": "Scene3D::Base3DBehavior::SetRotationY" }, "parameters": [ "Bullet", + "Object3D", "=", - "PlayerTank.TopRotation() + PlayerTank.Angle()" + "-ToDeg(asin(clamp(PlayerTank.AimDirectionZ(), -1, 1)))" ] }, { @@ -3058,15 +3058,14 @@ }, { "type": { - "value": "Physics3D::Physics3DBehavior::ApplyImpulseTowardPosition" + "value": "Physics3D::Physics3DBehavior::ApplyImpulseAtCenter" }, "parameters": [ "Bullet", "Physics3D", - "15", - "Bullet.X() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", - "Bullet.Y() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", - "Bullet.Object3D::Z() + sin(ToRad(-1 * Bullet.Object3D::RotationY())) * 10" + "15 * PlayerTank.AimDirectionX()", + "15 * PlayerTank.AimDirectionY()", + "15 * PlayerTank.AimDirectionZ()" ] }, { @@ -3664,13 +3663,7 @@ "authorIds": [], "dependencies": [], "globalVariables": [], - "sceneVariables": [ - { - "name": "CannonWidth", - "type": "number", - "value": 0 - } - ], + "sceneVariables": [], "eventsFunctions": [], "eventsFunctionsFolderStructure": { "folderName": "__ROOT" @@ -3990,10 +3983,10 @@ "objectGroups": [] }, { - "description": "Return the distance between the end of the cannon and the center of the TankTop object", - "fullName": "Return CannonWidth", + "description": "Return the X position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (X)", "functionType": "Expression", - "name": "CannonWidth", + "name": "MuzzleX", "sentence": "", "events": [ { @@ -4002,19 +3995,185 @@ "actions": [ { "type": { - "value": "TankConfiguration::TankTop::CannonInfo" + "value": "SetReturnNumber" }, "parameters": [ - "TankTop_Combined", - "" + "Object.ToParentX(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" ] - }, + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (Y)", + "functionType": "Expression", + "name": "MuzzleY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentY(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z position of the end of the cannon, in the scene.", + "fullName": "Muzzle position (Z)", + "functionType": "Expression", + "name": "MuzzleZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ { "type": { "value": "SetReturnNumber" }, "parameters": [ - "CannonWidth" + "Object.ToParentZ(TankTop_Combined.MuzzleX(), TankTop_Combined.MuzzleY(), TankTop_Combined.MuzzleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (X)", + "functionType": "Expression", + "name": "AimDirectionX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionX(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (Y)", + "functionType": "Expression", + "name": "AimDirectionY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionY(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z of the direction the cannon points at (a unit vector), in the scene.", + "fullName": "Aim direction (Z)", + "functionType": "Expression", + "name": "AimDirectionZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionZ(TankTop_Combined.AimDirectionX(), TankTop_Combined.AimDirectionY(), TankTop_Combined.AimDirectionZ())" ] } ] @@ -4055,7 +4214,22 @@ ] }, { - "functionName": "CannonWidth" + "functionName": "MuzzleX" + }, + { + "functionName": "MuzzleY" + }, + { + "functionName": "MuzzleZ" + }, + { + "functionName": "AimDirectionX" + }, + { + "functionName": "AimDirectionY" + }, + { + "functionName": "AimDirectionZ" } ] }, @@ -4290,11 +4464,11 @@ "objectGroups": [] }, { - "description": "Record variables related to the TankCannon object", - "fullName": "Record TankCannon information", - "functionType": "Action", - "name": "CannonInfo", - "sentence": "Record information for the TankCannon of _PARAM0_", + "description": "Return the X position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (X)", + "functionType": "Expression", + "name": "MuzzleX", + "sentence": "", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -4302,17 +4476,193 @@ "actions": [ { "type": { - "value": "SetNumberVariable" + "value": "SetReturnNumber" }, "parameters": [ - "CannonWidth", - "=", - "TankCanon.Width() + TankTop.Width()/2" + "Object.ToParentX(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (Y)", + "functionType": "Expression", + "name": "MuzzleY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentY(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z position of the end of the cannon, in the tank containing the tank top.", + "fullName": "Muzzle position (Z)", + "functionType": "Expression", + "name": "MuzzleZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentZ(TankCanon.X() + TankCanon.Object3D::ForwardX() * TankCanon.Width(), TankCanon.Y() + TankCanon.Object3D::ForwardY() * TankCanon.Width(), TankCanon.Object3D::Z() + TankCanon.Object3D::ForwardZ() * TankCanon.Width())" ] } ] } ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (X)", + "functionType": "Expression", + "name": "AimDirectionX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionX(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (Y)", + "functionType": "Expression", + "name": "AimDirectionY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionY(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Z of the direction the cannon points at (a unit vector), in the tank containing the tank top.", + "fullName": "Aim direction (Z)", + "functionType": "Expression", + "name": "AimDirectionZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.ToParentDirectionZ(TankCanon.Object3D::ForwardX(), TankCanon.Object3D::ForwardY(), TankCanon.Object3D::ForwardZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, "parameters": [ { "description": "Object", @@ -4331,7 +4681,22 @@ "functionName": "Elevation" }, { - "functionName": "CannonInfo" + "functionName": "MuzzleX" + }, + { + "functionName": "MuzzleY" + }, + { + "functionName": "MuzzleZ" + }, + { + "functionName": "AimDirectionX" + }, + { + "functionName": "AimDirectionY" + }, + { + "functionName": "AimDirectionZ" } ] }, diff --git a/scripts/lib/ExampleGDevelopVersion.js b/scripts/lib/ExampleGDevelopVersion.js index 0135c923f..05f45b2cc 100644 --- a/scripts/lib/ExampleGDevelopVersion.js +++ b/scripts/lib/ExampleGDevelopVersion.js @@ -10,14 +10,37 @@ const gdevelopVersionsByInstructionType = { 'Physics3D::Physics3DBehavior::SetBodyType': '>=5.6.285', }; +/** + * The expressions, written in the parameters of the actions and conditions, + * that need a GDevelop more recent than the one needed by the extensions used + * by an example. + * @type {Array<{expressionPattern: RegExp, gdevelopVersion: string}>} + */ +const gdevelopVersionsByExpression = [ + { + // The conversions of a custom object to and from the space containing it, + // used by the events of the custom object. + expressionPattern: + /\bObject\.(?:To|From)Parent(?:X|Y|Z|Angle|DirectionX|DirectionY|DirectionZ)\(/, + gdevelopVersion: '>=5.6.286', + }, +]; + /** * @param {*} value A part of a project, as read from its file. * @param {Set} instructionTypes Filled with the types of the actions * and conditions found in it. + * @param {Set} instructionParameters Filled with the parameters of + * these actions and conditions. */ -const collectInstructionTypes = (value, instructionTypes) => { +const collectInstructions = ( + value, + instructionTypes, + instructionParameters +) => { if (Array.isArray(value)) { - for (const item of value) collectInstructionTypes(item, instructionTypes); + for (const item of value) + collectInstructions(item, instructionTypes, instructionParameters); return; } if (!value || typeof value !== 'object') return; @@ -27,9 +50,35 @@ const collectInstructionTypes = (value, instructionTypes) => { Array.isArray(value.parameters) ) { instructionTypes.add(value.type.value); + for (const parameter of value.parameters) { + if (typeof parameter === 'string') instructionParameters.add(parameter); + } } for (const key in value) - collectInstructionTypes(value[key], instructionTypes); + collectInstructions(value[key], instructionTypes, instructionParameters); +}; + +/** + * @param {string} gdevelopVersion Like ">=5.6.285". + * @returns {Array} + */ +const getVersionNumbers = (gdevelopVersion) => + gdevelopVersion.replace('>=', '').split('.').map(Number); + +/** + * @param {string} gdevelopVersion Like ">=5.6.286". + * @param {string} otherGDevelopVersion Like ">=5.6.285". + * @returns {boolean} + */ +const isMoreRecentThan = (gdevelopVersion, otherGDevelopVersion) => { + const versionNumbers = getVersionNumbers(gdevelopVersion); + const otherVersionNumbers = getVersionNumbers(otherGDevelopVersion); + for (let index = 0; index < versionNumbers.length; index++) { + const difference = + (versionNumbers[index] || 0) - (otherVersionNumbers[index] || 0); + if (difference !== 0) return difference > 0; + } + return false; }; /** @@ -43,13 +92,33 @@ const collectInstructionTypes = (value, instructionTypes) => { const getExampleGDevelopVersion = (projectObject, usedExtensionNames) => { /** @type {Set} */ const instructionTypes = new Set(); - collectInstructionTypes(projectObject, instructionTypes); - for (const instructionType in gdevelopVersionsByInstructionType) { - if (instructionTypes.has(instructionType)) { - return gdevelopVersionsByInstructionType[instructionType]; - } - } - return usedExtensionNames.includes('Physics3D') ? '>=5.5.220' : ''; + /** @type {Set} */ + const instructionParameters = new Set(); + collectInstructions(projectObject, instructionTypes, instructionParameters); + + const neededGDevelopVersions = [ + ...Object.keys(gdevelopVersionsByInstructionType) + .filter((instructionType) => instructionTypes.has(instructionType)) + .map( + (instructionType) => gdevelopVersionsByInstructionType[instructionType] + ), + ...gdevelopVersionsByExpression + .filter(({ expressionPattern }) => + [...instructionParameters].some((parameter) => + expressionPattern.test(parameter) + ) + ) + .map(({ gdevelopVersion }) => gdevelopVersion), + ...(usedExtensionNames.includes('Physics3D') ? ['>=5.5.220'] : []), + ]; + return neededGDevelopVersions.reduce( + (mostRecentGDevelopVersion, gdevelopVersion) => + !mostRecentGDevelopVersion || + isMoreRecentThan(gdevelopVersion, mostRecentGDevelopVersion) + ? gdevelopVersion + : mostRecentGDevelopVersion, + '' + ); }; module.exports = { getExampleGDevelopVersion }; diff --git a/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js b/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js index cd601e3c7..572630468 100644 --- a/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js +++ b/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js @@ -22,11 +22,12 @@ const createFakeEvent = (actions, subEvents = []) => ({ }); /** - * @param {{ sceneEvents?: Array<*>, extensionFunctionEvents?: Array<*> }} events + * @param {{ sceneEvents?: Array<*>, extensionFunctionEvents?: Array<*>, customObjectFunctionEvents?: Array<*> }} events */ const createFakeProjectObject = ({ sceneEvents = [], extensionFunctionEvents = [], + customObjectFunctionEvents = [], }) => ({ layouts: [{ name: 'Game Scene', objects: [], events: sceneEvents }], externalEvents: [], @@ -36,6 +37,14 @@ const createFakeProjectObject = ({ eventsFunctions: [ { name: 'MyFunction', events: extensionFunctionEvents }, ], + eventsBasedObjects: [ + { + name: 'MyTank', + eventsFunctions: [ + { name: 'MuzzleX', events: customObjectFunctionEvents }, + ], + }, + ], }, ], }); @@ -48,6 +57,9 @@ const applyImpulseAction = createFakeInstruction( 'Physics3D::Physics3DBehavior::ApplyImpulse', ['Enemy', 'Physics3D', '0', '0', '0'] ); +const returnMuzzleXAction = createFakeInstruction('SetReturnNumber', [ + 'Object.ToParentX(Cannon.X() + Cannon.Width(), Cannon.Y(), Cannon.Object3D::Z())', +]); describe('getExampleGDevelopVersion', () => { it('needs a GDevelop with the "Body type" action when a sub-event of a scene uses it', () => { @@ -82,6 +94,37 @@ describe('getExampleGDevelopVersion', () => { ); }); + it('needs a GDevelop with the conversions of custom objects when a custom object uses them', () => { + const projectObject = createFakeProjectObject({ + customObjectFunctionEvents: [createFakeEvent([returnMuzzleXAction])], + }); + + expect(getExampleGDevelopVersion(projectObject, [])).toBe('>=5.6.286'); + }); + + it('needs the most recent GDevelop of all the features used', () => { + const projectObject = createFakeProjectObject({ + sceneEvents: [createFakeEvent([setBodyTypeAction])], + customObjectFunctionEvents: [createFakeEvent([returnMuzzleXAction])], + }); + + expect(getExampleGDevelopVersion(projectObject, ['Physics3D'])).toBe( + '>=5.6.286' + ); + }); + + it('does not take a function of another object named like a conversion for one', () => { + const projectObject = createFakeProjectObject({ + sceneEvents: [ + createFakeEvent([ + createFakeInstruction('SetX', ['Bullet', '=', 'Ship.ToParentX()']), + ]), + ], + }); + + expect(getExampleGDevelopVersion(projectObject, [])).toBe(''); + }); + it('accepts any GDevelop for the other examples', () => { const projectObject = createFakeProjectObject({}); From 182d9927ccca51f81e9e5ed98b39c937cd3d144d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 18:30:37 +0000 Subject: [PATCH 2/3] Haunted house: reach the key and the door with the distance in 3D The key and the front door are reached from their own floor only: the distance in 3D replaces the distance on X/Y followed by a comparison of the Z of the objects. The reach on the same floor is unchanged (145 px between the centers of the player and of the door is 130 px on X/Y, as before). Examples using the 3D distances or "turned toward" in 3D need GDevelop 5.6.286. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Tk9so3zqwFutR1h9SJnQQS --- .../starting-expanded-3d-house-horror.json | 26 ++-------------- scripts/lib/ExampleGDevelopVersion.js | 9 ++++++ .../__tests__/ExampleGDevelopVersion.spec.js | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json b/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json index f0ef1a37c..6979cd3a8 100644 --- a/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json +++ b/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json @@ -9605,7 +9605,7 @@ "conditions": [ { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Player", @@ -9613,16 +9613,6 @@ "120", "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::CompareNumbers" - }, - "parameters": [ - "abs(Player.Object3D::Z() - Key.Object3D::Z())", - "<", - "100" - ] } ], "actions": [ @@ -9695,24 +9685,14 @@ "conditions": [ { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Player", "Door", - "130", + "145", "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::CompareNumbers" - }, - "parameters": [ - "abs(Player.Object3D::Z() - Door.Object3D::Z())", - "<", - "100" - ] } ], "actions": [], diff --git a/scripts/lib/ExampleGDevelopVersion.js b/scripts/lib/ExampleGDevelopVersion.js index 05f45b2cc..fe7c2c60c 100644 --- a/scripts/lib/ExampleGDevelopVersion.js +++ b/scripts/lib/ExampleGDevelopVersion.js @@ -8,6 +8,9 @@ const gdevelopVersionsByInstructionType = { // The "Body type" action of the 3D physics. 'Physics3D::Physics3DBehavior::SetBodyType': '>=5.6.285', + // The conditions comparing objects in 3D. + Distance3D: '>=5.6.286', + IsTurnedTowardObject3D: '>=5.6.286', }; /** @@ -24,6 +27,12 @@ const gdevelopVersionsByExpression = [ /\bObject\.(?:To|From)Parent(?:X|Y|Z|Angle|DirectionX|DirectionY|DirectionZ)\(/, gdevelopVersion: '>=5.6.286', }, + { + // The distances in 3D. + expressionPattern: + /::Distance3D\(|::DistanceToPosition3D\(|\bDistanceBetweenPositions3D\(/, + gdevelopVersion: '>=5.6.286', + }, ]; /** diff --git a/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js b/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js index 572630468..c283f8bfc 100644 --- a/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js +++ b/scripts/lib/__tests__/ExampleGDevelopVersion.spec.js @@ -102,6 +102,37 @@ describe('getExampleGDevelopVersion', () => { expect(getExampleGDevelopVersion(projectObject, [])).toBe('>=5.6.286'); }); + it('needs a GDevelop with the 3D distances when a condition or an expression uses them', () => { + const conditionProjectObject = createFakeProjectObject({ + sceneEvents: [ + { + ...createFakeEvent([]), + conditions: [ + createFakeInstruction('Distance3D', ['Player', 'Key', '120', '']), + ], + }, + ], + }); + const expressionProjectObject = createFakeProjectObject({ + sceneEvents: [ + createFakeEvent([ + createFakeInstruction('SetNumberVariable', [ + 'Range', + '=', + 'Player.Object3D::Distance3D(Key)', + ]), + ]), + ], + }); + + expect(getExampleGDevelopVersion(conditionProjectObject, [])).toBe( + '>=5.6.286' + ); + expect(getExampleGDevelopVersion(expressionProjectObject, [])).toBe( + '>=5.6.286' + ); + }); + it('needs the most recent GDevelop of all the features used', () => { const projectObject = createFakeProjectObject({ sceneEvents: [createFakeEvent([setBodyTypeAction])], From 8467f1d547c46b79a81b86b91005cbea67685414 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 18:03:27 +0000 Subject: [PATCH 3/3] Tank and GTA starters: measure the blast and the reach of cars in 3D - Tank: a shell exploding high up blows away the targets around it, not the ones below it. The push still weakens with the distance, now in 3D. - GTA: the player gets into a car next to them, not into one parked above them. On the same ground, the reach is 94 px instead of 98 px (the center of a car is 29 px higher than the feet of the player). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Tk9so3zqwFutR1h9SJnQQS --- .../starting-3d-grand-theft-auto.json | 2 +- examples/starting-3d-tank/starting-3d-tank.json | 4 ++-- .../starting-expanded-3d-grand-theft-auto.json | 2 +- .../starting-expanded-3d-tank/starting-expanded-3d-tank.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json b/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json index 58449cd91..27b8dd02c 100644 --- a/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json +++ b/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json @@ -2237,7 +2237,7 @@ }, { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Vehicles", diff --git a/examples/starting-3d-tank/starting-3d-tank.json b/examples/starting-3d-tank/starting-3d-tank.json index 11b6c3996..4f2766be8 100644 --- a/examples/starting-3d-tank/starting-3d-tank.json +++ b/examples/starting-3d-tank/starting-3d-tank.json @@ -1960,7 +1960,7 @@ "conditions": [ { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Target", @@ -1978,7 +1978,7 @@ "parameters": [ "Target", "Physics3D", - "-5 * (300 - (Target.Distance(Bullet)))", + "-5 * (300 - Target.Object3D::Distance3D(Bullet))", "Bullet.X() + XFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", "Bullet.Y() + YFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", "0" diff --git a/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json index b1838de62..f20c1c91b 100644 --- a/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json +++ b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json @@ -5000,7 +5000,7 @@ }, { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Vehicles", diff --git a/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json index b7d9c36e3..43bb77c61 100644 --- a/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json +++ b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json @@ -3197,7 +3197,7 @@ "conditions": [ { "type": { - "value": "Distance" + "value": "Distance3D" }, "parameters": [ "Target", @@ -3215,7 +3215,7 @@ "parameters": [ "Target", "Physics3D", - "-5 * (300 - (Target.Distance(Bullet)))", + "-5 * (300 - Target.Object3D::Distance3D(Bullet))", "Bullet.X() + XFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", "Bullet.Y() + YFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", "0"