initial commit

This commit is contained in:
Mykyta Slobodianiuk
2026-06-02 18:10:09 +03:00
parent 34122811dd
commit 0003919f4c
25 changed files with 298 additions and 63 deletions
+1
View File
@@ -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"
+26 -26
View File
@@ -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
]
]
}
+103
View File
@@ -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 [115]");
const charFolder = gui.addFolder("Character");
const scaleProxy = { scale: TestSceneC.characterObject.scale.x };
charFolder
.add(scaleProxy, "scale", 0.1, 3, 0.01)
.name("Scale [0.13]")
.onChange((v: number) => TestSceneC.characterObject.scale.setScalar(v));
let orbitControls: OrbitControls | null = null;
let orbitDelegate: Delegate<number> | 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");
}
}
+60
View File
@@ -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));
}
}
+19 -30
View File
@@ -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);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.
Binary file not shown.
+9 -5
View File
@@ -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
}
+62
View File
@@ -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);
}
}
File diff suppressed because one or more lines are too long
+10 -1
View File
@@ -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();
};
+5
View File
@@ -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();
};
+2 -1
View File
@@ -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"]