From 2b803ac34454a9eac9578d79194ff10cc8b130c8 Mon Sep 17 00:00:00 2001 From: PANDAKO-GitHub <68310169+PANDAKO-GitHub@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:43:36 +0900 Subject: [PATCH] [Experimental] [Advanced 3D Features] Added actions to rotate toward a 3D object. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added “👀Rotate toward 3D object (Experimental)” action. - Added “👀Rotate child toward 3D object (Experimental)” action. - Added “👀Rotate toward 3D object on axis (Experimental)” action. - Added “👀Rotate child toward 3D object on axis (Experimental)” action. - Improved initialization process. - Added a variable for garbage collection. --- extensions/community/A3F.json | 514 +++++++++++++++++++++++++++++++++- 1 file changed, 513 insertions(+), 1 deletion(-) diff --git a/extensions/community/A3F.json b/extensions/community/A3F.json index c997fd41e..a4377ee6b 100644 --- a/extensions/community/A3F.json +++ b/extensions/community/A3F.json @@ -10,7 +10,7 @@ "name": "A3F", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Graphic Design/f4c71080f9213188ee5556b1acb45ad46fe6e5225947301c363105b080fca008_Graphic Design_3d_cube_isometric.svg", "shortDescription": "This extension adds features to the built-in 3D.", - "version": "1.4.2", + "version": "1.4.3", "description": [ "3D features added by this extension: ", "- Lighting", @@ -231,6 +231,44 @@ "useStrict": true, "eventsSheetExpanded": false }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Garbage collection 対策" + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs._A3F.Vec) return;\r", + "//\r", + "gdjs._A3F.Vec1 = new THREE.Vector3();\r", + "gdjs._A3F.Vec2 = new THREE.Vector3();\r", + "gdjs._A3F.Vec3 = new THREE.Vector3();\r", + "gdjs._A3F.Vec4 = new THREE.Vector3();\r", + "gdjs._A3F.Vec5 = new THREE.Vector3();\r", + "\r", + "gdjs._A3F.Qua1 = new THREE.Quaternion();\r", + "gdjs._A3F.Qua2 = new THREE.Quaternion();\r", + "gdjs._A3F.Qua3 = new THREE.Quaternion();\r", + "gdjs._A3F.Qua4 = new THREE.Quaternion();\r", + "gdjs._A3F.Qua5 = new THREE.Quaternion();\r", + "\r", + "gdjs._A3F.Eul1 = new THREE.Euler();\r", + "gdjs._A3F.Eul2 = new THREE.Euler();\r", + "\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + }, { "type": "BuiltinCommonInstructions::Comment", "color": { @@ -246,6 +284,7 @@ { "type": "BuiltinCommonInstructions::JsCode", "inlineCode": [ + "if (gdjs._A3F.AudioBufferCache) return;", "/**", " * CustomAudioListenerForGD", " * ", @@ -3290,6 +3329,467 @@ } ], "objectGroups": [] + }, + { + "description": "Unlike \"👀Look at 3D object\", this action rotates toward the target at a specified speed without locking the up direction.", + "fullName": "👀Rotate toward 3D object (Experimental)", + "functionType": "Action", + "group": "Angle", + "name": "RotateTowardObject", + "sentence": "👀Rotate toward 3D object (_PARAM1_, _PARAM3_, _PARAM4_, _PARAM6_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2Ds = objects;", + "const Target2Ds = eventsFunctionContext.getObjects(\"Target\");", + "if (Object2Ds.length === 0 || Target2Ds.length === 0) {", + " return;", + "}", + "const Target2D = Target2Ds[0];", + "const Target3D = Target2D.get3DRendererObject();", + "const Angle = eventsFunctionContext.getArgument(\"Angle\");", + "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", + "const TarPos = gdjs._A3F.Vec1;", + "if (TargetPoint === \"Center point\") {", + " TarPos.set(Target2D.getCenterXInScene(), Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + "} else {", + " TarPos.set(Target2D.getX(), Target2D.getY(), Target2D.getZ());", + "}", + "", + "for (const Object2D of Object2Ds) {", + " const Object3D = Object2D.get3DRendererObject();", + "", + " // オブジェクトの「正面」ベクトルを取得", + " const ObjDir = gdjs._A3F.Vec2.set(1, 0, 0);", + " ObjDir.applyQuaternion(Object3D.quaternion);", + " // ターゲットへの理想的な方向ベクトルを計算", + " const TarDir = gdjs._A3F.Vec3;", + " TarDir.subVectors(TarPos, Object3D.position).normalize();", + " // 「現在の向き」から「ターゲットの向き」への差分回転(Quaternion)を計算", + " const Qua = gdjs._A3F.Qua1;", + " Qua.setFromUnitVectors(ObjDir, TarDir);", + " // 「最短距離で向く」という目標のクォータニオンを算出", + " const TarQua = Object3D.quaternion.clone().premultiply(Qua);", + " // 一定の角速度で回転させる", + " Object3D.quaternion.rotateTowards(TarQua, gdjs.toRad(Angle));", + " // 反映", + " Object2D._rotationX = gdjs.toDegrees(Object3D.rotation.x);", + " Object2D._rotationY = gdjs.toDegrees(Object3D.rotation.y);", + " Object2D.angle = gdjs.toDegrees(Object3D.rotation.z);", + " Object2D.getRenderer().updateRotation();", + "}", + "", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Capability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Target 3D object", + "name": "Target", + "type": "objectList" + }, + { + "description": "Target 3D capability", + "name": "TargetCapability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Points of the target 3D object", + "name": "TargetPoint", + "supplementaryInformation": "[\"Center point\",\"Origin point\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the child towards the 3D object.\nUnlike \"👀Look at 3D object\", this action rotates toward the target at a specified speed without locking the up direction.\nNote that if a child is affected by a playing animation, it will override this change. In that case, please pause the animation.", + "fullName": "👀Rotate child toward 3D object (Experimental)", + "functionType": "Action", + "group": "Angle", + "name": "RotateChildTowardObject", + "sentence": "👀Rotate child toward 3D object (_PARAM1_, _PARAM3_, _PARAM4_, _PARAM5_, _PARAM6_, _PARAM8_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2Ds = objects;", + "const Target2Ds = eventsFunctionContext.getObjects(\"Target\");", + "if (Object2Ds.length === 0 || Target2Ds.length === 0) {", + " return;", + "}", + "const Target2D = Target2Ds[0];", + "const Target3D = Target2D.get3DRendererObject();", + "const ChildName = eventsFunctionContext.getArgument(\"Child\");", + "const Front = eventsFunctionContext.getArgument(\"Front\");", + "const Angle = eventsFunctionContext.getArgument(\"Angle\");", + "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", + "const TarWorPos = gdjs._A3F.Vec1;", + "", + "if (TargetPoint === \"Center point\") {", + " TarWorPos.set(Target2D.getCenterXInScene(), -Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + "} else {", + " TarWorPos.set(Target2D.getX(), -Target2D.getY(), Target2D.getZ());", + "}", + "", + "const ObjWorQua = gdjs._A3F.Qua1;", + "const TarQua = gdjs._A3F.Qua2;", + "const Qua = gdjs._A3F.Qua3;", + "", + "const ObjDir = gdjs._A3F.Vec2;", + "if (Front == \"x\") {", + " ObjDir.set(1, 0, 0);", + "} else if (Front == \"y\") {", + " ObjDir.set(0, 1, 0);", + "} else if (Front == \"z\") {", + " ObjDir.set(0, 0, 1);", + "} else if (Front == \"-x\") {", + " ObjDir.set(-1, 0, 0);", + "} else if (Front == \"-y\") {", + " ObjDir.set(0, -1, 0);", + "} else { //Front == \"-z\"", + " ObjDir.set(0, 0, -1);", + "}", + "const ObjWorDir = gdjs._A3F.Vec3;", + "const TarWorDir = gdjs._A3F.Vec4;", + "const ObjWorPos = gdjs._A3F.Vec5;", + "", + "for (const Object2D of Object2Ds) {", + " const Object3D = Object2D.get3DRendererObject();", + " const Child = Object3D.getObjectByName(ChildName);", + " if (!Child) {continue};", + "", + " Child.getWorldQuaternion(ObjWorQua);", + " Child.getWorldPosition(ObjWorPos);", + " ObjWorDir.copy(ObjDir).applyQuaternion(ObjWorQua).normalize(); // 現在の前方(ワールド空間)", + " TarWorDir.subVectors(TarWorPos, ObjWorPos).normalize(); // 目標方向(ワールド空間)", + " TarQua.setFromUnitVectors(ObjWorDir, TarWorDir).multiply(ObjWorQua); // 目標ワールド回転", + " ObjWorQua.rotateTowards(TarQua, gdjs.toRad(Angle)); // 角度制限付き回転", + " Child.parent.getWorldQuaternion(Qua).invert();", + " Child.quaternion.copy(Qua).multiply(ObjWorQua); // ローカルに変換", + "}", + "", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Capability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Child name", + "name": "Child", + "type": "string" + }, + { + "description": "Child front", + "name": "Front", + "supplementaryInformation": "[\"x\",\"y\",\"z\",\"-x\",\"-y\",\"-z\"]", + "type": "stringWithSelector" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Target 3D object", + "name": "Target", + "type": "objectList" + }, + { + "description": "Target 3D capability", + "name": "TargetCapability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Points of the target 3D object", + "name": "TargetPoint", + "supplementaryInformation": "[\"Center point\",\"Origin point\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Rotates an object toward a target along a single specified axis.\nThe rotation is constrained to the given axis (y, or z), leaving\nthe other axes unchanged.", + "fullName": "👀Rotate toward 3D object on axis (Experimental)", + "functionType": "Action", + "group": "Angle", + "name": "RotateTowardOnAxis", + "sentence": "👀Rotate toward 3D object on axis (_PARAM1_, _PARAM3_, _PARAM4_, _PARAM5_, _PARAM7_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2Ds = objects;", + "const Target2Ds = eventsFunctionContext.getObjects(\"Target\");", + "if (Object2Ds.length === 0 || Target2Ds.length === 0) {", + " return;", + "}", + "const Target2D = Target2Ds[0];", + "const Target3D = Target2D.get3DRendererObject();", + "const Axis = eventsFunctionContext.getArgument(\"Axis\");", + "const Angle = eventsFunctionContext.getArgument(\"Angle\");", + "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", + "const TarPos = gdjs._A3F.Vec1;", + "if (TargetPoint === \"Center point\") {", + " TarPos.set(Target2D.getCenterXInScene(), -Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + "} else {", + " TarPos.set(Target2D.getX(), -Target2D.getY(), Target2D.getZ());", + "}", + "", + "for (const Object2D of Object2Ds) {", + " const Object3D = Object2D.get3DRendererObject();", + " const LocalPos = gdjs._A3F.Vec3;", + " LocalPos.copy(TarPos);", + " // 1. ターゲットの座標を自分のローカル座標系に変換(!TarPosを直接書き換えないよう注意)", + " const TargetLocalPos = Object3D.worldToLocal(LocalPos);", + " // 2. ローカル空間でのターゲットへの方向(正規化)", + " const Direction = TargetLocalPos.normalize();", + " //", + " if (Axis == \"y\") {", + " const AxisVec = gdjs._A3F.Vec2;", + " const Qua = gdjs._A3F.Qua1;", + " AxisVec.set(0, 1, 0);", + " // 自分の正面(+X)と、ターゲットのローカルXZ平面上の位置から、必要な回転角を計算", + " let Diff = Math.atan2(-Direction.z, Direction.x);", + " // -π 〜 π の範囲に補正(最短ルートの計算)", + " if (Diff > Math.PI) Diff -= Math.PI * 2;", + " if (Diff < -Math.PI) Diff += Math.PI * 2;", + " // 1フレームで回転する量を制限", + " const Step = Math.sign(Diff) * Math.min(Math.abs(Diff), gdjs.toRad(Angle));", + " // 現在の姿勢の「右側」から掛け合わせることで、常にローカルY軸で回転させる", + " Object3D.quaternion.multiply(Qua.setFromAxisAngle(AxisVec, Step));", + " } else {", + " const AxisVec = gdjs._A3F.Vec2;", + " const Qua = gdjs._A3F.Qua1;", + " AxisVec.set(0, 0, 1);", + " let Diff = Math.atan2(Direction.y, Direction.x);", + " if (Diff > Math.PI) Diff -= Math.PI * 2;", + " if (Diff < -Math.PI) Diff += Math.PI * 2;", + " const Step = Math.sign(Diff) * Math.min(Math.abs(Diff), gdjs.toRad(Angle));", + " Object3D.quaternion.multiply(Qua.setFromAxisAngle(AxisVec, Step));", + " }", + " // 反映", + " Object2D._rotationX = gdjs.toDegrees(Object3D.rotation.x);", + " Object2D._rotationY = gdjs.toDegrees(Object3D.rotation.y);", + " Object2D.angle = gdjs.toDegrees(Object3D.rotation.z);", + " Object2D.getRenderer().updateRotation();", + "}", + "", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Capability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Axis", + "name": "Axis", + "supplementaryInformation": "[\"y\",\"z\"]", + "type": "stringWithSelector" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Target 3D object", + "name": "Target", + "type": "objectList" + }, + { + "description": "Target 3D capability", + "name": "TargetCapability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Points of the target 3D object", + "name": "TargetPoint", + "supplementaryInformation": "[\"Center point\",\"Origin point\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Rotates a child toward a target along a single specified axis.\nThe rotation is constrained to the given axis leaving the other axes unchanged.\nNote that if a child is affected by a playing animation, it will override this change. In that case, please pause the animation.", + "fullName": "👀Rotate child toward 3D object on axis (Experimental)", + "functionType": "Action", + "group": "Angle", + "name": "RotateChildTowardOnAxis", + "sentence": "👀Rotate child toward 3D object on axis (_PARAM1_, _PARAM3_, _PARAM4_, _PARAM5_, _PARAM6_, _PARAM7_, _PARAM9_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2Ds = objects;", + "const Target2Ds = eventsFunctionContext.getObjects(\"Target\");", + "if (Object2Ds.length === 0 || Target2Ds.length === 0) return;", + "", + "const Target2D = Target2Ds[0];", + "const ChildName = eventsFunctionContext.getArgument(\"Child\");", + "const Front = eventsFunctionContext.getArgument(\"Front\");", + "const Axis = eventsFunctionContext.getArgument(\"Axis\");", + "const Angle = eventsFunctionContext.getArgument(\"Angle\");", + "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", + "", + "const frontAxis = Front.replace(\"-\", \"\");", + "if (frontAxis === Axis) return;", + "", + "const TarWorPos = gdjs._A3F.Vec1;", + "if (TargetPoint === \"Center point\") {", + " TarWorPos.set(Target2D.getCenterXInScene(), -Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + "} else {", + " TarWorPos.set(Target2D.getX(), -Target2D.getY(), Target2D.getZ());", + "}", + "", + "const idxMap = { x: 0, y: 1, z: 2 };", + "const frontNeg = Front.startsWith(\"-\");", + "const frontIdx = idxMap[frontAxis];", + "const rotIdx = idxMap[Axis];", + "const sideIdx = [0, 1, 2].find(i => i !== frontIdx && i !== rotIdx);", + "const sign = ((frontIdx + 1) % 3 === sideIdx) ? -1 : 1;", + "const maxStep = gdjs.toRad(Angle);", + "", + "const AxisVec = gdjs._A3F.Vec2;", + "AxisVec.set(Axis === \"x\" ? 1 : 0, Axis === \"y\" ? 1 : 0, Axis === \"z\" ? 1 : 0);", + "", + "for (const Object2D of Object2Ds) {", + " const Object3D = Object2D.get3DRendererObject();", + " const Child = Object3D.getObjectByName(ChildName);", + " if (!Child) continue;", + "", + " const lp = gdjs._A3F.Vec3;", + " lp.copy(TarWorPos);", + " Child.worldToLocal(lp);", + " if (lp.lengthSq() < 1e-10) continue;", + " lp.normalize();", + "", + " const d = [lp.x, lp.y, lp.z];", + " const angle = Math.atan2(-d[sideIdx] * sign, frontNeg ? -d[frontIdx] : d[frontIdx]);", + " const step = Math.max(-maxStep, Math.min(maxStep, angle));", + " if (Math.abs(step) < 1e-10) continue;", + "", + " gdjs._A3F.Qua1.setFromAxisAngle(AxisVec, step);", + " Child.quaternion.multiply(gdjs._A3F.Qua1);", + "", + " // 反映", + " Object2D._rotationX = gdjs.toDegrees(Object3D.rotation.x);", + " Object2D._rotationY = gdjs.toDegrees(Object3D.rotation.y);", + " Object2D.angle = gdjs.toDegrees(Object3D.rotation.z);", + " Object2D.getRenderer().updateRotation();", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Capability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Child name", + "name": "Child", + "type": "string" + }, + { + "description": "Child front", + "name": "Front", + "supplementaryInformation": "[\"x\",\"y\",\"z\",\"-x\",\"-y\",\"-z\"]", + "type": "stringWithSelector" + }, + { + "description": "Axis", + "name": "Axis", + "supplementaryInformation": "[\"x\",\"y\",\"z\"]", + "type": "stringWithSelector" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Target 3D object", + "name": "Target", + "type": "objectList" + }, + { + "description": "Target 3D capability", + "name": "TargetCapability3d", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Points of the target 3D object", + "name": "TargetPoint", + "supplementaryInformation": "[\"Center point\",\"Origin point\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] } ], "eventsFunctionsFolderStructure": { @@ -3389,6 +3889,18 @@ }, { "functionName": "AngleToObjects" + }, + { + "functionName": "RotateTowardObject" + }, + { + "functionName": "RotateChildTowardObject" + }, + { + "functionName": "RotateTowardOnAxis" + }, + { + "functionName": "RotateChildTowardOnAxis" } ] },