diff --git a/package.json b/package.json index e5f971a..216614a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "devDependencies": { "@types/howler": "^2.2.13", "@types/three": "^0.184.1", + "lil-gui": "^0.21.0", "rollup": "^4.61.0", "typescript": "^6.0.3", "vite": "^6.4.3" diff --git a/src/configUIParams/globalSettings.ts b/src/configUIParams/globalSettings.ts index 74a1702..26391f8 100644 --- a/src/configUIParams/globalSettings.ts +++ b/src/configUIParams/globalSettings.ts @@ -30,7 +30,7 @@ export const globalSettings: ConfigUiParamsCategories[] = [ values: [ 30, 150, - 60 + 50 ] }, { @@ -41,19 +41,19 @@ export const globalSettings: ConfigUiParamsCategories[] = [ visible: "position", values: [ [ - -10, - 10, - 0 + -50, + 50, + -4.79 ], [ - -10, - 10, - 1 + -50, + 50, + 11.71 ], [ - -10, - 10, - 5 + -50, + 50, + -5.61 ] ] }, @@ -67,17 +67,17 @@ export const globalSettings: ConfigUiParamsCategories[] = [ [ -360, 360, - 0 + -116 ], [ -360, 360, - 0 + -20 ], [ -360, 360, - 0 + -144 ] ] }, @@ -88,7 +88,7 @@ export const globalSettings: ConfigUiParamsCategories[] = [ values: [ 30, 150, - 60 + 65 ] }, { @@ -99,19 +99,19 @@ export const globalSettings: ConfigUiParamsCategories[] = [ visible: "position", values: [ [ - -10, - 10, - 0 + -50, + 50, + -2.9 ], [ - -10, - 10, - 1 + -50, + 50, + 7.0 ], [ - -10, - 10, - 5 + -50, + 50, + -3.4 ] ] }, @@ -125,17 +125,17 @@ export const globalSettings: ConfigUiParamsCategories[] = [ [ -360, 360, - 0 + -116 ], [ -360, 360, - 0 + -20 ], [ -360, 360, - 0 + -144 ] ] } diff --git a/src/controllers/CameraDebugUI.ts b/src/controllers/CameraDebugUI.ts new file mode 100644 index 0000000..0dffd95 --- /dev/null +++ b/src/controllers/CameraDebugUI.ts @@ -0,0 +1,103 @@ +import GUI from "lil-gui"; +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; +import { CameraC_internal, Delegate, UpdateController } from "@24tools/playable_template"; +import { FollowCameraC } from "./FollowCameraC"; +import { TestSceneC } from "./TestSceneC"; + +// Transparent overlay for OrbitControls — sits above canvas but below GUI panel. +// Allows the GUI to keep receiving clicks while OrbitControls is active. +function createOrbitOverlay(): HTMLDivElement { + const div = document.createElement("div"); + div.style.cssText = ` + position: fixed; inset: 0; + z-index: 9000; + touch-action: auto; + cursor: grab; + `; + return div; +} + +export class CameraDebugUI { + static init() { + const gui = new GUI({ title: "📷 Camera Debug", width: 280 }); + gui.domElement.style.zIndex = "99999"; + gui.domElement.style.position = "fixed"; + + const offsetFolder = gui.addFolder("Offset — camera position (from config)"); + offsetFolder.add(FollowCameraC.offset, "x", -10, 10, 0.1).name("X (left / right)"); + offsetFolder.add(FollowCameraC.offset, "y", -10, 10, 0.1).name("Y (height)"); + offsetFolder.add(FollowCameraC.offset, "z", -10, 10, 0.1).name("Z (distance behind)"); + + gui.add(FollowCameraC, "lerpSpeed", 1, 15, 0.5).name("Follow smoothness [1–15]"); + + const charFolder = gui.addFolder("Character"); + const scaleProxy = { scale: TestSceneC.characterObject.scale.x }; + charFolder + .add(scaleProxy, "scale", 0.1, 3, 0.01) + .name("Scale [0.1–3]") + .onChange((v: number) => TestSceneC.characterObject.scale.setScalar(v)); + + let orbitControls: OrbitControls | null = null; + let orbitDelegate: Delegate | null = null; + let orbitOverlay: HTMLDivElement | null = null; + + const orbitProxy = { enabled: false }; + gui + .add(orbitProxy, "enabled") + .name("🔭 Free camera (OrbitControls)") + .onChange((enabled: boolean) => { + if (enabled) { + FollowCameraC.paused = true; + + orbitOverlay = createOrbitOverlay(); + document.body.appendChild(orbitOverlay); + + orbitControls = new OrbitControls(CameraC_internal.camera!, orbitOverlay); + orbitControls.update(); + + orbitDelegate = UpdateController.Instance.onUpdate.addDelegate(() => { + orbitControls?.update(); + }); + } else { + if (orbitDelegate !== null) { + UpdateController.Instance.onUpdate.removeListeners(orbitDelegate); + orbitDelegate = null; + } + orbitControls?.dispose(); + orbitControls = null; + + orbitOverlay?.remove(); + orbitOverlay = null; + + FollowCameraC.paused = false; + FollowCameraC.snapToTarget(); + } + }); + + gui + .add( + { + log: () => { + const cam = CameraC_internal.camera!; + const s = TestSceneC.characterObject.scale.x; + + const p = cam.position; + // Convert radians to degrees for config + const rx = Math.round(cam.rotation.x * (180 / Math.PI)); + const ry = Math.round(cam.rotation.y * (180 / Math.PI)); + const rz = Math.round(cam.rotation.z * (180 / Math.PI)); + + console.log( + `%c[CameraDebug] Paste into globalSettings.ts: + camera_position: x=${p.x.toFixed(2)}, y=${p.y.toFixed(2)}, z=${p.z.toFixed(2)} + camera_rotation: x=${rx}°, y=${ry}°, z=${rz}° + charScale: ${s.toFixed(2)}`, + "color: #7cf; font-weight: bold" + ); + }, + }, + "log" + ) + .name("📋 Print values to console"); + } +} diff --git a/src/controllers/FollowCameraC.ts b/src/controllers/FollowCameraC.ts new file mode 100644 index 0000000..80c86a3 --- /dev/null +++ b/src/controllers/FollowCameraC.ts @@ -0,0 +1,60 @@ +import { CameraC_internal, UpdateController } from "@24tools/playable_template"; +import { Object3D, Vector3 } from "three"; + +const _targetWorldPos = new Vector3(); + +export class FollowCameraC { + // Populated in init() from the camera position set by CameraC config + static offset = new Vector3(); + static lerpSpeed = 6; + static paused = false; + + private static target: Object3D | null = null; + + static init(target: Object3D) { + this.target = target; + + const camera = CameraC_internal.camera!; + target.getWorldPosition(_targetWorldPos); + + // Offset = config camera position minus character world position. + // Rotation is already set by config — do not modify. + this.offset.copy(camera.position).sub(_targetWorldPos); + + UpdateController.Instance.onUpdate.addDelegate((delta) => { + this.update(delta); + }); + } + + // Called from resizeCb after CameraC.setCamera(). + // Re-syncs offset from the new config (portrait/landscape may differ), + // camera position is already correct after setCamera so no snap needed. + static syncAndSnap() { + const camera = CameraC_internal.camera; + if (!this.target || !camera) return; + this.target.getWorldPosition(_targetWorldPos); + // CameraC.setCamera() placed camera.position at the config world position. + // Recompute offset as the difference between that position and current character position. + this.offset.copy(camera.position).sub(_targetWorldPos); + } + + // Instantly moves camera to character + offset without lerp. + static snapToTarget() { + const camera = CameraC_internal.camera; + if (!this.target || !camera) return; + this.target.getWorldPosition(_targetWorldPos); + camera.position.copy(_targetWorldPos.clone().add(this.offset)); + } + + private static update(delta: number) { + if (this.paused) return; + const camera = CameraC_internal.camera; + if (!this.target || !camera) return; + + this.target.getWorldPosition(_targetWorldPos); + + // Position only. Rotation is fixed by config. + const desired = _targetWorldPos.clone().add(this.offset); + camera.position.lerp(desired, Math.min(1, this.lerpSpeed * delta)); + } +} diff --git a/src/controllers/TestSceneC.ts b/src/controllers/TestSceneC.ts index d91db27..45ae996 100644 --- a/src/controllers/TestSceneC.ts +++ b/src/controllers/TestSceneC.ts @@ -1,42 +1,31 @@ -import { BoxGeometry, Mesh, MeshStandardMaterial, Vector3 } from "three"; import { ThreeC } from "./ThreeC"; -import { InputC, JoystickC } from "@24tools/playable_template"; +import { InputC } from "@24tools/playable_template"; +import { Object3D } from "three"; export class TestSceneC { - static init() { - this.createPrimitive(); + static mapObject: Object3D; + static characterObject: Object3D; + + static init() { + this.loadScene(); + this.loadCharacter(); - // example of using InputC events InputC.onTouchDown.addDelegate((event) => { console.log("onMouseDown", event); }); - - // if you have update in your controller - // UpdateController.Instance.onUpdate.addDelegate(() => { - // this.update(); - // }); } - private static createPrimitive() { - const geometry = new BoxGeometry(1, 1, 1); - const material = new MeshStandardMaterial({ color: 0xcc0000 }); - const cube = new Mesh(geometry, material); + private static loadScene() { + this.mapObject = ThreeC.getObject("scene"); + ThreeC.setShadowsStateForChildren(this.mapObject, true, true); + ThreeC.addToScene(this.mapObject); + } - const geometryPlane = new BoxGeometry(5, 0.1, 7); - const materialPlane = new MeshStandardMaterial({ color: 0xaaaaaa }); - const plane = new Mesh(geometryPlane, materialPlane); - - let planePosition = plane.position.clone(); - - ThreeC.setShadowsStateForChildren(cube, true, false); - - ThreeC.setShadowsStateForChildren(plane, false, true); - - plane.position.copy( - new Vector3(planePosition.x, planePosition.y - 0.5, planePosition.z - 1.5) - ); - - ThreeC.addToScene(cube); - ThreeC.addToScene(plane); + private static loadCharacter() { + this.characterObject = ThreeC.getObject("character"); + ThreeC.setShadowsStateForChildren(this.characterObject, true, true); + this.characterObject.position.set(0, 0, 0); + this.characterObject.scale.setScalar(1); + ThreeC.addToScene(this.characterObject); } } diff --git a/src/resources/OnbordingUI/Icon_Zombie_Head.webp b/src/resources/OnbordingUI/Icon_Zombie_Head.webp new file mode 100644 index 0000000..83246d0 Binary files /dev/null and b/src/resources/OnbordingUI/Icon_Zombie_Head.webp differ diff --git a/src/resources/OnbordingUI/REF/REF_1.png b/src/resources/OnbordingUI/REF/REF_1.png new file mode 100644 index 0000000..9d56903 Binary files /dev/null and b/src/resources/OnbordingUI/REF/REF_1.png differ diff --git a/src/resources/OnbordingUI/REF/REF_2.png b/src/resources/OnbordingUI/REF/REF_2.png new file mode 100644 index 0000000..9408522 Binary files /dev/null and b/src/resources/OnbordingUI/REF/REF_2.png differ diff --git a/src/resources/OnbordingUI/ResourceBackground_Metal.webp b/src/resources/OnbordingUI/ResourceBackground_Metal.webp new file mode 100644 index 0000000..a2974e0 Binary files /dev/null and b/src/resources/OnbordingUI/ResourceBackground_Metal.webp differ diff --git a/src/resources/OnbordingUI/ResourceBackground_Wood.webp b/src/resources/OnbordingUI/ResourceBackground_Wood.webp new file mode 100644 index 0000000..3b1a946 Binary files /dev/null and b/src/resources/OnbordingUI/ResourceBackground_Wood.webp differ diff --git a/src/resources/OnbordingUI/Tool_1.webp b/src/resources/OnbordingUI/Tool_1.webp new file mode 100644 index 0000000..05bf079 Binary files /dev/null and b/src/resources/OnbordingUI/Tool_1.webp differ diff --git a/src/resources/OnbordingUI/Tool_15.webp b/src/resources/OnbordingUI/Tool_15.webp new file mode 100644 index 0000000..5a7d3b7 Binary files /dev/null and b/src/resources/OnbordingUI/Tool_15.webp differ diff --git a/src/resources/OnbordingUI/Tool_2.webp b/src/resources/OnbordingUI/Tool_2.webp new file mode 100644 index 0000000..a0d8071 Binary files /dev/null and b/src/resources/OnbordingUI/Tool_2.webp differ diff --git a/src/resources/OnbordingUI/Tool_Backgtound.webp b/src/resources/OnbordingUI/Tool_Backgtound.webp new file mode 100644 index 0000000..5291056 Binary files /dev/null and b/src/resources/OnbordingUI/Tool_Backgtound.webp differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp b/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp new file mode 100644 index 0000000..0c6b664 Binary files /dev/null and b/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp b/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp new file mode 100644 index 0000000..d0cc0c9 Binary files /dev/null and b/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp b/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp new file mode 100644 index 0000000..1315173 Binary files /dev/null and b/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp differ diff --git a/src/resources/meshes/ZombiePunk_Character.glb b/src/resources/meshes/ZombiePunk_Character.glb new file mode 100644 index 0000000..be8e122 Binary files /dev/null and b/src/resources/meshes/ZombiePunk_Character.glb differ diff --git a/src/resources/meshes/ZombiePunk_Map.glb b/src/resources/meshes/ZombiePunk_Map.glb new file mode 100644 index 0000000..e733f57 Binary files /dev/null and b/src/resources/meshes/ZombiePunk_Map.glb differ diff --git a/src/resources/meshes/meshes.ts b/src/resources/meshes/meshes.ts index dafac7a..b00e04d 100644 --- a/src/resources/meshes/meshes.ts +++ b/src/resources/meshes/meshes.ts @@ -1,14 +1,18 @@ -// import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; +import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; import { ConvertResourceType, Template3d } from "@24tools/playable_template"; export const meshes : ConvertResourceType = { type: "mesh", resources: [ - // { - // name: "scene", - // value: ConvertToBase64WhenRelease("./SceneСombo.glb"), - // }, + { + name: "scene", + value: ConvertToBase64WhenRelease("./ZombiePunk_Map.glb"), + }, + { + name: "character", + value: ConvertToBase64WhenRelease("./ZombiePunk_Character.glb"), + }, ], loader: Template3d.meshLoader } \ No newline at end of file diff --git a/src/resources/vfx/VfxManager.ts b/src/resources/vfx/VfxManager.ts new file mode 100644 index 0000000..5dd7c2d --- /dev/null +++ b/src/resources/vfx/VfxManager.ts @@ -0,0 +1,62 @@ +import { BatchedRenderer, QuarksLoader, QuarksUtil } from "three.quarks"; +import { Object3D, Euler, Vector3 } from "three"; +import { ResourcesC, UpdateController } from "@24tools/playable_template"; +import { ThreeC } from "../../ThreeC"; +import { TimeC } from "../Timers/TimeC"; +import { VFXType } from "../Enums/VFXType"; +import { ResourcesType } from "../Enums/ResourcesType"; +import { vfxTest } from "./RunTimeTest"; + +export class VfxManager { + static batchRenderer: BatchedRenderer; + static loader: QuarksLoader; + + static Init() { + this.batchRenderer = new BatchedRenderer(); + this.loader = new QuarksLoader(); + ThreeC.addToScene(this.batchRenderer); + const updateDelegate = UpdateController.Instance.onUpdate.addDelegate(this.update.bind(this)); + // GameTimer.onDelayGameEnd.addDelegate(() => UpdateController.Instance.onUpdate.removeListeners(updateDelegate)); + // initTrailEffect(); + } + + static Remove(vfx: Object3D) { + vfx.removeFromParent(); + vfx.parent = null; + } + + static update(delta: number) { + delta *= TimeC.TimeScale; + this.batchRenderer.update(delta); + } + + static Play(type: VFXType|string, parent: Object3D | null = null, position: Vector3 | null = null, rotation: Euler | null = null, scale: Vector3 | null = null, odred: number | null = null) { + let loaded = (ResourcesC.getResource(ResourcesType.VFX, type.toString()) as { obj: any }).obj; + if (!loaded) return new Object3D(); + // console.error("Type non loaded " + loaded); + const effect = loaded.clone(true) as Object3D; + QuarksUtil.setAutoDestroy(effect, true); + QuarksUtil.addToBatchRenderer(effect, this.batchRenderer); + + if (parent) parent.add(effect) + else ThreeC.addToScene(effect); + if (position) effect.position.copy(position); + if (rotation) effect.rotation.copy(rotation); + if (scale) effect.scale.copy(scale); + if (odred) effect.renderOrder = odred; + + return effect; + } + + static StopEmision(effect: Object3D) { + QuarksUtil.stop(effect); + } + static Restart(effect: Object3D) { + QuarksUtil.play(effect); + } + static Pause(effect: Object3D) { + QuarksUtil.pause(effect); + } + +} + diff --git a/src/resources/vfx/test.json b/src/resources/vfx/test.json new file mode 100644 index 0000000..32c50a2 --- /dev/null +++ b/src/resources/vfx/test.json @@ -0,0 +1 @@ +{"metadata":{"version":4.6,"type":"Object","generator":"Object3D.toJSON"},"geometries":[{"uuid":"f5ca51c8-abf9-435d-84ee-fdf7860d7569","type":"PlaneGeometry","name":"_geometry","width":1,"height":1,"widthSegments":1,"heightSegments":1}],"materials":[{"uuid":"23fd987d-6965-492d-8b94-5f1577a50984","type":"MeshBasicMaterial","color":16777215,"map":"0eba679e-9eec-43a0-bea5-06c1316765f8","envMapRotation":[0,0,0,"XYZ"],"reflectivity":1,"refractionRatio":0.98,"transparent":true,"blendColor":0}],"textures":[{"uuid":"0eba679e-9eec-43a0-bea5-06c1316765f8","name":"IcePeace1.webp","image":"8e527362-6b8b-41a1-8c90-6fe53e32065b","mapping":300,"channel":0,"repeat":[1,1],"offset":[0,0],"center":[0,0],"rotation":0,"wrap":[1001,1001],"format":1023,"internalFormat":null,"type":1009,"colorSpace":"","minFilter":1008,"magFilter":1006,"anisotropy":1,"flipY":true,"generateMipmaps":true,"premultiplyAlpha":false,"unpackAlignment":4}],"images":[{"uuid":"8e527362-6b8b-41a1-8c90-6fe53e32065b","url":"data:image/webp;base64,UklGRvoEAABXRUJQVlA4WAoAAAAQAAAATwAAJwAAQUxQSHQCAAAN1+SwbdtAesq9//13vrtfISLy43GY29C4llx+67jveMaQW4IOohiCCIe0ZiupVdvgfLvyB7Jl2zZtp899bduMr22bsW3bto5Pbvae33uzFb9F9H8CcNh0Kf/WkQf8MpN218DOqczDV+K/hvKLbFo93Xj+1KdfQM8MP84f6X9w5MVPlnEC+yntU4Ejt8yfqO4UzhumKk+eC/wkxsI2XC0db71x7N3PUHgB19P7x14fve+7Xet4aeyYzDl8Oe4k5+uGB6mHCvC6arrp7G6117nn3NlPblWex49JD3GYbQ4NPD/+xJ1n+PFND07zw9A8VHr0asSFVD/cW8FxThhFskda7u/94siPJ2/hPC/EptJcdtx336ctXMwNgYqqSMvdsM++dONqbhBQMSyk87K/3vbhamJSVAHFUAYvmn66v4y7WREVAAWMLXcc3NnqxambuJwTAQXBEljc72D0QJpr5swGbueHDBVUVERV6p/a+3hqr1tfO3A/K2yJCioooqtr9jhRW+/O+y48zA8jimEilgEpxe/smSPHk914MI+XOSFDRS0DFVBZ2mePV7cXXDhzHW/CWIgaCipo0cZne+xpqXBizkbwNi+igoqigkLC+FEH8YnTYu9rF17nRkBQFVFExap5H7TH41cjtt734HleBFFERUFFxei+5IDF8SIbDxfwPjcKiogFgqLS9HDDQWDp+I/OXsN7yYiAoCKoIoKVtP2mA27FWjcx5wP4MCsmqKgooqKIitV2XR0wtZoJBLrxZXYMQcEKh6PhaOhrLBL5lmg6+XTwIHzoxp+feoOhYAyvz5c0PprDp4GX+FGHa27wa31/iP/TVlA4IAoCAACQCgCdASpQACgAPi0ShkKhoQ1Vm5gMAWJYgCD/+VatRtqOQAtWZnxAiP/xJySr81ANA7Zb2v/8irT+tqtXW42V3mQDf4aBR+FefL2eHjdWMTvPbZHJLcwFhg6vN0AA/v+5UjfH6MtA9ReSqowrhxlTDDAa5Sb2pg9FeXZTBAWGPGXOZ/+Jh2Wnq2zYs3SK0nUwSLntyJjgEeFoO7tNkPl76bHNPP+eDeO38o/xL3kV63G7/iTsXP/+eAY2+wr0wne0jq0VO0u01KNA6atlDEAAU04KEqpiD2MTjelfqs/QPZbMG5c6Y/k3Gpt61DhkpZjDU8uQovLwBATZQIIZHbPfPWcHbKiWv87grXusL+FfRFprxagHtzSyImwaqu/pz/9riH/r8f/w5tlf8U356YZeYiDMFj/sZVzxKW+Wus3/h6iL7TC/gk32bxqnPN+01OfGxfz8E686eD8B6SKBwn6J6w528/6mwA5jiJLbNFaP26+oh4ijcbV9XPStfkei4leMgL5W94CJDf/k0K3y7z/CmwQder53NFetyQt8XgCZOaT2f7OdiXqpaDZhesJSz9ck8cGwYtluRbKbFCQLgTq1u+flme4Tp/mBYhbybf5vzARJpKgt+rO+KP1qd9Cev/G1yX+E2ZAGP/49KWmyub1qTlEbbvFqh/Le+kJqmzVrZxoOEEjDFgAAAABQU0FJTgAAADhCSU0D7QAAAAAAEABIAAAAAQACAEgAAAABAAI4QklNBCgAAAAAAAwAAAACP/AAAAAAAAA4QklNBEMAAAAAAA5QYmVXARAABgBaAAAAAA=="}],"object":{"uuid":"9f5762a0-0edd-4440-9b9f-6b2d1b7c7860","type":"Group","name":"Ice","layers":1,"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],"up":[0,1,0],"children":[{"uuid":"c4a36eaf-745d-4175-ba13-2467f4af94e8","type":"ParticleEmitter","name":"small","userData":{"script":" // Randomize tile index for spawning\n const tileColumns = 3;\n const tileRows = 3;\n const randomTileIndex = Math.floor(Math.random() * (tileColumns * tileRows));\n \n // Check and set tile index (if uniforms are available)\n if (this.material && this.material.uniforms) {\n if (!this.material.uniforms.tileIndex) {\n this.material.uniforms.tileIndex = { value: 0 };\n }\n this.material.uniforms.tileIndex.value = randomTileIndex;\n }\n // Alternative: if using a particle attribute or property\n else if (this.material && this.material.map) {\n // For MeshBasicMaterial, try setting via user data\n this.userData.tileIndex = randomTileIndex;\n }"},"layers":1,"matrix":[1,0,0,0,0,2.220446049250313e-16,-1,0,0,1,2.220446049250313e-16,0,0,0,0,1],"up":[0,1,0],"ps":{"version":"3.0","autoDestroy":false,"looping":false,"prewarm":false,"duration":0.1,"shape":{"type":"point"},"startLife":{"type":"ConstantValue","value":0.6},"startSpeed":{"type":"ConstantValue","value":1.5},"startRotation":{"type":"IntervalValue","a":0,"b":360},"startSize":{"type":"IntervalValue","a":0.6,"b":0.9},"startColor":{"type":"ConstantColor","color":{"r":1,"g":1,"b":1,"a":1}},"emissionOverTime":{"type":"ConstantValue","value":0},"emissionOverDistance":{"type":"ConstantValue","value":0},"emissionBursts":[{"time":0,"count":{"type":"ConstantValue","value":15},"probability":1,"interval":0,"cycle":0}],"onlyUsedByOther":false,"instancingGeometry":"f5ca51c8-abf9-435d-84ee-fdf7860d7569","renderOrder":0,"renderMode":0,"rendererEmitterSettings":{},"material":"23fd987d-6965-492d-8b94-5f1577a50984","layers":1,"startTileIndex":{"type":"ConstantValue","value":1},"uTileCount":2,"vTileCount":1,"blendTiles":false,"softParticles":false,"softFarFade":0,"softNearFade":0,"behaviors":[{"type":"SizeOverLife","size":{"type":"PiecewiseBezier","functions":[{"function":{"p0":1,"p1":1,"p2":1.084211735305272,"p3":0.5676373167042756},"start":0},{"function":{"p0":0.5676373167042756,"p1":0.5071181379560418,"p2":0,"p3":0},"start":0.8357291666666666}]}},{"type":"ApplyForce","direction":[0,1,0],"magnitude":{"type":"ConstantValue","value":-2}}],"worldSpace":true}},{"uuid":"67f2841c-b97b-4678-9bb4-576951006a38","type":"ParticleEmitter","name":"big","userData":{"script":" // Randomize tile index for spawning\n const tileColumns = 3;\n const tileRows = 3;\n const randomTileIndex = Math.floor(Math.random() * (tileColumns * tileRows));\n \n // Check and set tile index (if uniforms are available)\n if (this.material && this.material.uniforms) {\n if (!this.material.uniforms.tileIndex) {\n this.material.uniforms.tileIndex = { value: 0 };\n }\n this.material.uniforms.tileIndex.value = randomTileIndex;\n }\n // Alternative: if using a particle attribute or property\n else if (this.material && this.material.map) {\n // For MeshBasicMaterial, try setting via user data\n this.userData.tileIndex = randomTileIndex;\n }"},"layers":1,"matrix":[1,0,0,0,0,2.220446049250313e-16,-1,0,0,1,2.220446049250313e-16,0,0,0,0,1],"up":[0,1,0],"ps":{"version":"3.0","autoDestroy":false,"looping":false,"prewarm":false,"duration":0.1,"shape":{"type":"point"},"startLife":{"type":"ConstantValue","value":0.6},"startSpeed":{"type":"ConstantValue","value":1.5},"startRotation":{"type":"IntervalValue","a":0,"b":360},"startSize":{"type":"IntervalValue","a":0.2,"b":0.5},"startColor":{"type":"ConstantColor","color":{"r":1,"g":1,"b":1,"a":1}},"emissionOverTime":{"type":"ConstantValue","value":0},"emissionOverDistance":{"type":"ConstantValue","value":0},"emissionBursts":[{"time":0,"count":{"type":"ConstantValue","value":15},"probability":1,"interval":0,"cycle":0}],"onlyUsedByOther":false,"instancingGeometry":"f5ca51c8-abf9-435d-84ee-fdf7860d7569","renderOrder":0,"renderMode":0,"rendererEmitterSettings":{},"material":"23fd987d-6965-492d-8b94-5f1577a50984","layers":1,"startTileIndex":{"type":"ConstantValue","value":0},"uTileCount":2,"vTileCount":1,"blendTiles":false,"softParticles":false,"softFarFade":0,"softNearFade":0,"behaviors":[{"type":"SizeOverLife","size":{"type":"PiecewiseBezier","functions":[{"function":{"p0":1,"p1":1,"p2":0.9344334039973435,"p3":0.6430620376937233},"start":0},{"function":{"p0":0.6430620376937233,"p1":0.46121988403206604,"p2":0,"p3":0},"start":0.7935069444444445}]}},{"type":"ApplyForce","direction":[0,1,0],"magnitude":{"type":"ConstantValue","value":-2}}],"worldSpace":true}}]}} \ No newline at end of file diff --git a/src/templateConfig/afterResourcesLoadedCb.ts b/src/templateConfig/afterResourcesLoadedCb.ts index eb346c6..6d74968 100644 --- a/src/templateConfig/afterResourcesLoadedCb.ts +++ b/src/templateConfig/afterResourcesLoadedCb.ts @@ -1,8 +1,17 @@ import { TestSceneC } from "../controllers/TestSceneC"; +import { FollowCameraC } from "../controllers/FollowCameraC"; import { SoundC, Template } from "@24tools/playable_template"; -export const afterResourcesLoadedCb: (() => void) | undefined = () => { +export const afterResourcesLoadedCb: (() => void) | undefined = async () => { TestSceneC.init(); SoundC.init(); + + FollowCameraC.init(TestSceneC.characterObject); + + if (import.meta.env.DEV) { + const { CameraDebugUI } = await import("../controllers/CameraDebugUI"); + CameraDebugUI.init(); + } + Template.disableLoader(); }; diff --git a/src/templateConfig/resizeCb.ts b/src/templateConfig/resizeCb.ts index d143387..6205af7 100644 --- a/src/templateConfig/resizeCb.ts +++ b/src/templateConfig/resizeCb.ts @@ -1,5 +1,10 @@ import { Template3d } from "@24tools/playable_template"; +import { CameraC } from "../controllers/CameraC"; +import { FollowCameraC } from "../controllers/FollowCameraC"; export const resizeCb = () => { Template3d.resize(); + CameraC.setCamera(window.screenSize.portrait); + // Re-sync offset from the new config (portrait/landscape) and correct camera position + FollowCameraC.syncAndSnap(); }; diff --git a/tsconfig.json b/tsconfig.json index bb391ca..31121c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "noUnusedLocals": false, "noUnusedParameters": false, "noImplicitAny": false, - "allowJs": true + "allowJs": true, + "types": ["vite/client"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "templateLibs", "build", "zip"]