diff --git a/src/controllers/CombatC.ts b/src/controllers/CombatC.ts index 0ec5c30..f58cf23 100644 --- a/src/controllers/CombatC.ts +++ b/src/controllers/CombatC.ts @@ -1,8 +1,9 @@ -import { UpdateController } from "@24tools/playable_template"; import { Vector3 } from "three"; import { PlayerC } from "./PlayerC"; import { Crate, LootableC } from "./LootableC"; import { Trigger } from "./TriggerC"; +import { GameEvents } from "../core/GameEvents"; +import { GameLoop } from "../core/GameLoop"; const ATTACK_DAMAGE = 10; // damage per bat-tip touch const CONTACT_DIST = 0.8; // bat tip → crate distance that counts as a touch @@ -35,7 +36,7 @@ export class CombatC { ); } - UpdateController.Instance.onUpdate.addDelegate(() => this.update()); + GameLoop.register(() => this.update()); } private static update() { @@ -85,7 +86,7 @@ export class CombatC { crate.root.getWorldPosition(_scratch); if (Math.hypot(tip.x - _scratch.x, tip.z - _scratch.z) <= CONTACT_DIST) { this.hitThisSwing.add(crate); - LootableC.damageCrate(crate, ATTACK_DAMAGE); + GameEvents.onCrateHit.Invoke({ crate, damage: ATTACK_DAMAGE }); } } this.pruneBroken(); diff --git a/src/controllers/FollowCameraC.ts b/src/controllers/FollowCameraC.ts index e7efd16..f6e1926 100644 --- a/src/controllers/FollowCameraC.ts +++ b/src/controllers/FollowCameraC.ts @@ -1,5 +1,6 @@ -import { CameraC_internal, UpdateController } from "@24tools/playable_template"; +import { CameraC_internal } from "@24tools/playable_template"; import { Object3D, Vector3 } from "three"; +import { GameLoop } from "../core/GameLoop"; const _targetWorldPos = new Vector3(); const _lookAheadTarget = new Vector3(); @@ -29,7 +30,7 @@ export class FollowCameraC { // Immediately place camera relative to the actual character position // (not config origin) so the character is visible from frame one. camera.position.copy(_targetWorldPos).add(this.offset); - UpdateController.Instance.onUpdate.addDelegate((delta) => this.update(delta)); + GameLoop.register((delta) => this.update(delta)); } // Re-syncs base offset after a config-driven camera move (e.g. resize). diff --git a/src/controllers/HealthBarC.ts b/src/controllers/HealthBarC.ts index 07b35a8..064b581 100644 --- a/src/controllers/HealthBarC.ts +++ b/src/controllers/HealthBarC.ts @@ -1,12 +1,14 @@ import { Box3, DoubleSide, Group, Material, Mesh, Object3D, Vector3 } from "three"; import * as TWEEN from "@tweenjs/tween.js"; -import { UpdateController, CameraC_internal } from "@24tools/playable_template"; +import { CameraC_internal } from "@24tools/playable_template"; import { ThreeC } from "./ThreeC"; +import { GameEvents } from "../core/GameEvents"; +import { GameLoop } from "../core/GameLoop"; import type { Crate } from "./LootableC"; // --- Placement --- -const HEIGHT_ABOVE = 1.4; // world Y offset of the bar above the crate origin -const BAR_SCALE = 1.2; // world scale of the cloned bar (prototype is ~1m wide) +const HEIGHT_ABOVE = 1.4; // world Y offset of the bar above the crate origin +const BAR_SCALE = 1.2; // world scale of the cloned bar (prototype is ~1m wide) // --- Fill animation --- const FOREGROUND_MS = 120; // Foreground (current health) drops fast — animates first @@ -54,7 +56,12 @@ export class HealthBarC { return; } this.protoNode = prototype; - UpdateController.Instance.onUpdate.addDelegate((delta: number) => this.update(delta)); + + // Listen for health display events + GameEvents.onHealthDisplay.addDelegate(({ crate, healthFraction }) => this.showDamage(crate, healthFraction)); + GameEvents.onHealthHide.addDelegate((crate) => this.hide(crate)); + + GameLoop.register((delta: number) => this.update(delta)); } /** Show/refresh the bar for a crate at the given health fraction (0..1). */ diff --git a/src/controllers/HudC.ts b/src/controllers/HudC.ts index e0a9939..4d520f7 100644 --- a/src/controllers/HudC.ts +++ b/src/controllers/HudC.ts @@ -1,14 +1,9 @@ -import { UpdateController } from "@24tools/playable_template"; -import { ensurePassionOne } from "../fonts/passionOne"; import { PlayerC } from "./PlayerC"; import * as TWEEN from "@tweenjs/tween.js"; +import { GameLoop } from "../core/GameLoop"; import { - zombieHeadUrl, - woodPanelUrl, - metalPanelUrl, - toolPanelUrl, - toolIconUrl, -} from "../resources/OnbordingUI/onboardingUI"; + images +} from "../resources/images/images"; // Onboarding/invasion pacing. const MOVE_TO_HURRY_S = 5; // after the player starts moving, wait this long… @@ -51,8 +46,6 @@ export class HudC { private static tweens = new TWEEN.Group(); private static buttonTween: TWEEN.Tween<{ x: number; y: number }> | null = null; static init() { - ensurePassionOne(); - const hud = document.createElement("div"); hud.id = "hud"; hud.innerHTML = this.markup(); @@ -71,7 +64,7 @@ export class HudC { this.showTutorial("DRAG TO MOVE"); this.invasion?.classList.add("hud-invasion--hidden", "hud-invasion--hurry-phase"); - UpdateController.Instance.onUpdate.addDelegate((delta: number) => { + GameLoop.register((delta: number) => { this.tick(delta); this.tweens.update(); }); @@ -90,22 +83,22 @@ export class HudC {
-
+
-
+
0
-
+
0
-
- +
+
LVL 0
`; @@ -197,7 +190,7 @@ export class HudC { const card = document.createElement("div"); card.className = "hud-endcard"; - card.innerHTML = ``; + card.innerHTML = ``; this.root.appendChild(card); const zombie = card.querySelector(".hud-endcard__zombie") as HTMLElement; diff --git a/src/controllers/LootC.ts b/src/controllers/LootC.ts index b7e3ffc..04d599e 100644 --- a/src/controllers/LootC.ts +++ b/src/controllers/LootC.ts @@ -1,9 +1,11 @@ import { Sprite, SpriteMaterial, Texture, TextureLoader, SRGBColorSpace, Vector3 } from "three"; import * as TWEEN from "@tweenjs/tween.js"; -import { UpdateController, CameraC_internal } from "@24tools/playable_template"; +import { CameraC_internal } from "@24tools/playable_template"; import { ThreeC } from "./ThreeC"; import { TestSceneC } from "./TestSceneC"; // for groundY (ground level) -import { woodIconUrl } from "../resources/images/woodIcon"; +import { GameEvents } from "../core/GameEvents"; +import { GameLoop } from "../core/GameLoop"; +import { images } from "../resources/resources"; import { worldToScreen } from "../utils/screen"; // Tunables — tweak here @@ -46,7 +48,7 @@ export class LootC { private static tweens = new TWEEN.Group(); static init() { - this.texture = new TextureLoader().load(woodIconUrl); + this.texture = new TextureLoader().load(images.woodIconUrl); this.texture.colorSpace = SRGBColorSpace; // correct color // The wood icon/count live in the HUD (built by HudC). We just reference @@ -55,8 +57,11 @@ export class LootC { this.countEl = document.getElementById("wood-count"); this.renderCount(); + // Listen for loot spawns (from crate breaks or state changes) + GameEvents.onLootSpawn.addDelegate(({ position, count }) => this.spawn(position, count)); + // ⚠️ Key: pump our group every frame, otherwise the tweens don't advance. - UpdateController.Instance.onUpdate.addDelegate(() => this.tweens.update()); + GameLoop.register(() => this.tweens.update()); } /** Current spendable wood. */ @@ -69,6 +74,9 @@ export class LootC { const taken = Math.min(amount, this.balance); this.balance -= taken; this.renderCount(); + if (taken > 0) { + GameEvents.onWoodSpent.Invoke({ amount: taken, balance: this.balance }); + } return taken; } @@ -209,7 +217,7 @@ export class LootC { this.remove(piece); const flier = document.createElement("img"); - flier.src = woodIconUrl; + flier.src = images.woodIconUrl; flier.style.cssText = `position:fixed; left:0; top:0; width:${sizePx}px; height:${sizePx}px;` + // above #hud (z-index 9999) so the wood clearly flies on top of, and into, the icon @@ -218,7 +226,7 @@ export class LootC { // White "glint" copy that rides on top of the flier and fades out as it moves. const flash = document.createElement("img"); - flash.src = woodIconUrl; + flash.src = images.woodIconUrl; flash.style.cssText = flier.style.cssText; flash.style.filter = "brightness(0) invert(1)"; // solid white silhouette flash.style.zIndex = "10001"; @@ -255,7 +263,14 @@ export class LootC { .to({ progress: 1 }, FLY_MS) .easing(TWEEN.Easing.Quadratic.InOut) .onUpdate(apply) - .onComplete(() => { flier.remove(); flash.remove(); this.balance++; this.renderCount(); this.pulseUiIcon(); }); + .onComplete(() => { + flier.remove(); + flash.remove(); + this.balance++; + this.renderCount(); + this.pulseUiIcon(); + GameEvents.onWoodCollected.Invoke({ amount: 1, balance: this.balance }); + }); // Shrink — SAME duration as the flight, so the two start AND finish together // (no "shrink first"); Sinusoidal makes the size change extra smooth. const shrink = new TWEEN.Tween(anim, this.tweens) diff --git a/src/controllers/LootableC.ts b/src/controllers/LootableC.ts index 2a7ec72..f8d7076 100644 --- a/src/controllers/LootableC.ts +++ b/src/controllers/LootableC.ts @@ -1,10 +1,9 @@ import { Mesh, Object3D, Vector3 } from "three"; import * as TWEEN from "@tweenjs/tween.js"; -import { UpdateController } from "@24tools/playable_template"; import { PhysicsBody, PhysicsLayer } from "./PhysicsC"; import { Trigger } from "./TriggerC"; -import { LootC } from "./LootC"; -import { HealthBarC } from "./HealthBarC"; +import { GameEvents } from "../core/GameEvents"; +import { GameLoop } from "../core/GameLoop"; import { VfxManager } from "../resources/vfx/VfxManager"; // Crate health, split evenly across 3 damage states (S1/S2/S3), broken at 0. @@ -50,8 +49,11 @@ export class LootableC { return; } + // Listen for crate hits from combat system + GameEvents.onCrateHit.addDelegate(({ crate, damage }) => this.damageCrate(crate, damage)); + // Drive our juice tweens (hit punch, flash, break) every frame. - UpdateController.Instance.onUpdate.addDelegate(() => this.tweens.update()); + GameLoop.register(() => this.tweens.update()); lootableGroup.visible = true; lootableGroup.updateWorldMatrix(true, true); // collider world positions must be current @@ -116,7 +118,7 @@ export class LootableC { this.flashCrate(crate); // Show/refresh the floating health bar above the crate. - HealthBarC.showDamage(crate, crate.health / crate.maxHealth); + GameEvents.onHealthDisplay.Invoke({ crate, healthFraction: crate.health / crate.maxHealth }); // Map remaining health to a level, never below where this crate started. let level = Math.floor((1 - crate.health / crate.maxHealth) * LEVELS); @@ -125,7 +127,8 @@ export class LootableC { crate.level = level; crate.statesByLevel.forEach((state, lvl) => { if (state) state.visible = lvl === level; }); // Loot drops on every state change, not only on destruction. - LootC.spawn(crate.root.getWorldPosition(new Vector3())); + const position = crate.root.getWorldPosition(new Vector3()); + GameEvents.onLootSpawn.Invoke({ position }); } } @@ -135,7 +138,7 @@ export class LootableC { crate.broken = true; // Drop the floating health bar. - HealthBarC.hide(crate); + GameEvents.onHealthHide.Invoke(crate); // Stop the hit-punch so it doesn't fight the break animation. crate.hitTween?.stop(); @@ -147,7 +150,8 @@ export class LootableC { crate.trigger = null; // Drop loot at the crate's spot. - LootC.spawn(crate.root.getWorldPosition(new Vector3())); + const position = crate.root.getWorldPosition(new Vector3()); + GameEvents.onLootSpawn.Invoke({ position }); // Shrink to nothing while spinning, then hide the meshes. const anim = { progress: 0 }; diff --git a/src/controllers/PayZoneC.ts b/src/controllers/PayZoneC.ts index 722a41c..d71001f 100644 --- a/src/controllers/PayZoneC.ts +++ b/src/controllers/PayZoneC.ts @@ -1,10 +1,11 @@ import { Object3D, Raycaster, Vector3 } from "three"; import * as TWEEN from "@tweenjs/tween.js"; -import { UpdateController, CameraC_internal } from "@24tools/playable_template"; +import { CameraC_internal } from "@24tools/playable_template"; import { Trigger } from "./TriggerC"; import { TestSceneC } from "./TestSceneC"; import { LootC } from "./LootC"; -import { woodIconUrl } from "../resources/images/woodIcon"; +import { GameLoop } from "../core/GameLoop"; +import { images } from "../resources/resources"; import { worldToScreen } from "../utils/screen"; // --- Tunables --- @@ -75,7 +76,7 @@ export class PayZoneC { { onEnter: () => { this.inside = true; }, onExit: () => { this.inside = false; } }, ); - UpdateController.Instance.onUpdate.addDelegate((delta) => this.update(delta)); + GameLoop.register((delta) => this.update(delta)); } private static update(delta: number) { @@ -103,7 +104,7 @@ export class PayZoneC { if (!to) return; const img = document.createElement("img"); - img.src = woodIconUrl; + img.src = images.woodIconUrl; img.style.cssText = `position:fixed; left:0; top:0; width:${PLANK_PX}px; height:${PLANK_PX}px;` + `z-index:1000; pointer-events:none; transform:translate(-50%,-50%); will-change:left,top;`; diff --git a/src/controllers/PhysicsC.ts b/src/controllers/PhysicsC.ts index b4f684b..c86cfd6 100644 --- a/src/controllers/PhysicsC.ts +++ b/src/controllers/PhysicsC.ts @@ -12,6 +12,36 @@ export enum PhysicsLayer { Enemy = 8, } +/** + * Physics world API facade. All direct Physics_internal access goes through here. + */ +export class PhysicsC { + /** Add a body to the physics world. */ + static addBody(body: Body) { + Physics_internal.physicsWorld?.addBody(body); + } + + /** Remove a body from the physics world. */ + static removeBody(body: Body) { + Physics_internal.physicsWorld?.removeBody(body); + } + + /** Listen for collision begin events. */ + static onContactBegin(callback: (event: any) => void) { + Physics_internal.physicsWorld?.addEventListener("beginContact", callback); + } + + /** Listen for collision end events. */ + static onContactEnd(callback: (event: any) => void) { + Physics_internal.physicsWorld?.addEventListener("endContact", callback); + } + + /** Get the raw physics world (for advanced usage). */ + static get world() { + return Physics_internal.physicsWorld; + } +} + /** * Wraps a three.js object in a cannon-es rigid body. The shape is derived from * the object: the player gets a Sphere (rolls smoothly along walls/floor), @@ -57,7 +87,7 @@ export class PhysicsBody { "XYZ", ); - Physics_internal.physicsWorld?.addBody(this.body); + PhysicsC.addBody(this.body); } /** The underlying cannon body (for direct velocity/position control). */ @@ -67,6 +97,6 @@ export class PhysicsBody { /** Remove the body from the physics world (e.g. when a crate breaks). */ destroy() { - Physics_internal.physicsWorld?.removeBody(this.body); + PhysicsC.removeBody(this.body); } } diff --git a/src/controllers/PlayerC.ts b/src/controllers/PlayerC.ts index 4d0d616..496d46a 100644 --- a/src/controllers/PlayerC.ts +++ b/src/controllers/PlayerC.ts @@ -1,7 +1,8 @@ -import { CameraC_internal, JoystickC, ThreeC_internal, UpdateController } from "@24tools/playable_template"; +import { CameraC_internal, JoystickC, ThreeC_internal } from "@24tools/playable_template"; import { AnimationAction, AnimationMixer, LoopOnce, Mesh, Object3D, Raycaster, Vector3 } from "three"; import { Body } from "cannon-es"; import { PhysicsBody, PhysicsLayer } from "./PhysicsC"; +import { GameLoop } from "../core/GameLoop"; const ANIM_NAMES: Record = { idle: ["idle", "Idle", "IDLE", "stand", "Stand"], @@ -92,7 +93,7 @@ export class PlayerC { this.setupWeapon(); this.setupAnimations(); this.setupJoystick(); - UpdateController.Instance.onUpdate.addDelegate((delta) => this.update(delta)); + GameLoop.register((delta) => this.update(delta)); } // Toggle the bat between the back (idle/normal) and the hand (attack). diff --git a/src/controllers/TestSceneC.ts b/src/controllers/TestSceneC.ts index 89e9850..0f4a65c 100644 --- a/src/controllers/TestSceneC.ts +++ b/src/controllers/TestSceneC.ts @@ -1,8 +1,8 @@ import { ThreeC } from "./ThreeC"; -import { InputC, Physics_internal } from "@24tools/playable_template"; +import { InputC } from "@24tools/playable_template"; import { Box3, Mesh, Object3D, Vector3 } from "three"; import { Body, Box, Vec3 } from "cannon-es"; -import { PhysicsBody, PhysicsLayer } from "./PhysicsC"; +import { PhysicsBody, PhysicsC, PhysicsLayer } from "./PhysicsC"; export class TestSceneC { static mapObject: Object3D; @@ -136,7 +136,7 @@ export class TestSceneC { // player in. Bounds are read from the floor proxy so the walls always match // the authored map, even if it changes. private static buildBoundaryWalls() { - if (!this.colliderGroup || !Physics_internal.physicsWorld) return; + if (!this.colliderGroup || !PhysicsC.world) return; const bounds = new Box3().setFromObject(this.colliderGroup); if (bounds.isEmpty()) return; @@ -169,7 +169,7 @@ export class TestSceneC { collisionFilterMask: PhysicsLayer.Player, }); body.position.set(wallX, midY, wallZ); - Physics_internal.physicsWorld.addBody(body); + PhysicsC.addBody(body); this.boundaryBodies.push(body); } diff --git a/src/controllers/TriggerC.ts b/src/controllers/TriggerC.ts index a6f3120..993c005 100644 --- a/src/controllers/TriggerC.ts +++ b/src/controllers/TriggerC.ts @@ -1,6 +1,5 @@ -import { Physics_internal } from "@24tools/playable_template"; import { Body, Box, Vec3 } from "cannon-es"; -import { PhysicsLayer } from "./PhysicsC"; +import { PhysicsC, PhysicsLayer } from "./PhysicsC"; /** * A single invisible trigger zone. @@ -32,13 +31,13 @@ export class Trigger { }); this.body.position.set(center.x, center.y, center.z); - Physics_internal.physicsWorld?.addBody(this.body); + PhysicsC.addBody(this.body); TriggerC.register(this); } destroy() { TriggerC.unregister(this); - Physics_internal.physicsWorld?.removeBody(this.body); + PhysicsC.removeBody(this.body); } } @@ -60,10 +59,9 @@ export class TriggerC { this.playerBody = playerBody; if (this.started) return; - const world = Physics_internal.physicsWorld; - if (!world) return; - world.addEventListener("beginContact", this.onBegin); - world.addEventListener("endContact", this.onEnd); + if (!PhysicsC.world) return; + PhysicsC.onContactBegin(this.onBegin); + PhysicsC.onContactEnd(this.onEnd); this.started = true; } diff --git a/src/core/GameEvents.ts b/src/core/GameEvents.ts new file mode 100644 index 0000000..bfd28cf --- /dev/null +++ b/src/core/GameEvents.ts @@ -0,0 +1,71 @@ +import { EasyEvent } from "@24tools/playable_template"; +import { Vector3 } from "three"; +import type { Crate } from "../controllers/LootableC"; + +/** + * Centralized event system. Controllers emit events instead of calling each other directly. + * This decouples systems and makes it easy to add new listeners (e.g., sound, particles, analytics). + * + * Uses EasyEvent from @24tools/playable_template (same pattern as UpdateController, JoystickC). + * Subscribe: event.addDelegate(callback) + * Emit: event.Invoke(data) + */ + +// Event data types +export interface CrateHitData { + crate: Crate; + damage: number; +} + +export interface CrateBrokenData { + crate: Crate; + position: Vector3; +} + +export interface LootSpawnData { + position: Vector3; + count?: number; +} + +export interface HealthDisplayData { + crate: Crate; + healthFraction: number; +} + +export interface WoodCollectedData { + amount: number; + balance: number; +} + +export interface WoodSpentData { + amount: number; + balance: number; +} + +/** + * Global game events. Use like: + * GameEvents.onCrateHit.Invoke({ crate, damage: 10 }); + * GameEvents.onCrateHit.addDelegate(({ crate, damage }) => { ... }); + */ +export class GameEvents { + // Crate hit: damage applied, state may change + static onCrateHit = new EasyEvent(); + + // Crate destroyed: physics removed, loot will drop + static onCrateBroken = new EasyEvent(); + + // Health bar shown/updated for a crate + static onHealthDisplay = new EasyEvent(); + + // Health bar hidden + static onHealthHide = new EasyEvent(); + + // Loot drops at a position (may be from crate break or state change) + static onLootSpawn = new EasyEvent(); + + // Wood piece collected into UI counter + static onWoodCollected = new EasyEvent(); + + // Wood spent from counter (e.g., plank flying to pay zone) + static onWoodSpent = new EasyEvent(); +} diff --git a/src/core/GameLoop.ts b/src/core/GameLoop.ts new file mode 100644 index 0000000..9fecb0b --- /dev/null +++ b/src/core/GameLoop.ts @@ -0,0 +1,34 @@ +import { UpdateController } from "@24tools/playable_template"; + +/** + * Centralized game loop. Instead of each controller registering separately with UpdateController, + * they register with GameLoop. This gives us: + * - Single point of entry for all per-frame updates + * - Explicit ordering of controller ticks + * - Easy to add/remove/reorder without touching UpdateController + */ + +type UpdateFn = (delta: number) => void; + +export class GameLoop { + private static updates: UpdateFn[] = []; + private static started = false; + + /** + * Register an update function to be called each frame. + * The first call to register() sets up the UpdateController listener. + */ + static register(fn: UpdateFn) { + this.updates.push(fn); + if (!this.started) { + UpdateController.Instance.onUpdate.addDelegate((delta) => this.tick(delta)); + this.started = true; + } + } + + private static tick(delta: number) { + for (const fn of this.updates) { + fn(delta); + } + } +} diff --git a/src/css/ui.scss b/src/css/ui.scss index c101bde..2a0c2ac 100644 --- a/src/css/ui.scss +++ b/src/css/ui.scss @@ -61,7 +61,9 @@ inset: 0; z-index: 9999; pointer-events: none; - font-family: "PassionOne", "Roboto", sans-serif; + // "gameFont" is the family name the template registers our custom font under + // (set in src/fonts/customFont.ts → PassionOne Black, inlined on release). + font-family: "gameFont", "Roboto", sans-serif; // Portrait: every fluid size scales with viewport width. --fluid-unit: 1vw; diff --git a/src/fonts/customFont.ts b/src/fonts/customFont.ts index da1041a..3ab82e5 100644 --- a/src/fonts/customFont.ts +++ b/src/fonts/customFont.ts @@ -1,3 +1,3 @@ import { FontFamily, formFontFamily } from "@24tools/ads_common"; -export const customFont: undefined | Promise = undefined +export const customFont: Promise = formFontFamily("PassionOne", "./PassionOne-Black.otf"); diff --git a/src/fonts/passionOne.ts b/src/fonts/passionOne.ts deleted file mode 100644 index 55ee2dc..0000000 --- a/src/fonts/passionOne.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; - -// Heavy display font used for the HUD labels (matches the REF look). Inlined as -// base64 on release. We inject an @font-face at runtime so it works in dev too. -const passionOneUrl = ConvertToBase64WhenRelease("./PassionOne-Black.otf"); - -export const PASSION_ONE = "PassionOne"; - -let injected = false; - -/** Register the PassionOne font once. Safe to call multiple times. */ -export function ensurePassionOne() { - if (injected) return; - injected = true; - const style = document.createElement("style"); - style.textContent = - `@font-face{font-family:'${PASSION_ONE}';` + - `src:url(${passionOneUrl}) format('opentype');font-weight:900;font-display:swap;}`; - document.head.appendChild(style); -} diff --git a/src/resources/OnbordingUI/Tool_15.webp b/src/resources/OnbordingUI/Tool_15.webp deleted file mode 100644 index 5a7d3b7..0000000 Binary files a/src/resources/OnbordingUI/Tool_15.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/Tool_2.webp b/src/resources/OnbordingUI/Tool_2.webp deleted file mode 100644 index a0d8071..0000000 Binary files a/src/resources/OnbordingUI/Tool_2.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp b/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp deleted file mode 100644 index 0c6b664..0000000 Binary files a/src/resources/OnbordingUI/ZombiePunk_Button-Install.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp b/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp deleted file mode 100644 index d0cc0c9..0000000 Binary files a/src/resources/OnbordingUI/ZombiePunk_Icon-Bottom.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp b/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp deleted file mode 100644 index 1315173..0000000 Binary files a/src/resources/OnbordingUI/ZombiePunk_Icon-Top.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/onboardingUI.ts b/src/resources/OnbordingUI/onboardingUI.ts deleted file mode 100644 index 1f06511..0000000 --- a/src/resources/OnbordingUI/onboardingUI.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; - -// HUD image URLs. In a release build each is inlined as base64 (like the loot -// icon). Paths are relative to this file. Usable directly in or CSS url(). -export const zombieHeadUrl = ConvertToBase64WhenRelease("./Icon_Zombie_Head.webp"); // invasion bar + death end-card -export const woodPanelUrl = ConvertToBase64WhenRelease("./ResourceBackground_Wood.webp"); -export const metalPanelUrl = ConvertToBase64WhenRelease("./ResourceBackground_Metal.webp"); -export const toolPanelUrl = ConvertToBase64WhenRelease("./Tool_Backgtound.webp"); // weapon panel background -export const toolIconUrl = ConvertToBase64WhenRelease("./Tool_1.webp"); // weapon icon diff --git a/src/resources/images/CharacterSelectionUI_Logo.webp b/src/resources/images/CharacterSelectionUI_Logo.webp deleted file mode 100644 index 2a6687f..0000000 Binary files a/src/resources/images/CharacterSelectionUI_Logo.webp and /dev/null differ diff --git a/src/resources/OnbordingUI/Icon_Zombie_Head.webp b/src/resources/images/Icon_Zombie_Head.webp similarity index 100% rename from src/resources/OnbordingUI/Icon_Zombie_Head.webp rename to src/resources/images/Icon_Zombie_Head.webp diff --git a/src/resources/OnbordingUI/REF/REF_1.png b/src/resources/images/REF/REF_1.png similarity index 100% rename from src/resources/OnbordingUI/REF/REF_1.png rename to src/resources/images/REF/REF_1.png diff --git a/src/resources/OnbordingUI/REF/REF_2.png b/src/resources/images/REF/REF_2.png similarity index 100% rename from src/resources/OnbordingUI/REF/REF_2.png rename to src/resources/images/REF/REF_2.png diff --git a/src/resources/OnbordingUI/ResourceBackground_Metal.webp b/src/resources/images/ResourceBackground_Metal.webp similarity index 100% rename from src/resources/OnbordingUI/ResourceBackground_Metal.webp rename to src/resources/images/ResourceBackground_Metal.webp diff --git a/src/resources/OnbordingUI/ResourceBackground_Wood.webp b/src/resources/images/ResourceBackground_Wood.webp similarity index 100% rename from src/resources/OnbordingUI/ResourceBackground_Wood.webp rename to src/resources/images/ResourceBackground_Wood.webp diff --git a/src/resources/OnbordingUI/Tool_1.webp b/src/resources/images/Tool_1.webp similarity index 100% rename from src/resources/OnbordingUI/Tool_1.webp rename to src/resources/images/Tool_1.webp diff --git a/src/resources/OnbordingUI/Tool_Backgtound.webp b/src/resources/images/Tool_Backgtound.webp similarity index 100% rename from src/resources/OnbordingUI/Tool_Backgtound.webp rename to src/resources/images/Tool_Backgtound.webp diff --git a/src/resources/images/banner_button.webp b/src/resources/images/banner_button.webp deleted file mode 100644 index a92e3f2..0000000 Binary files a/src/resources/images/banner_button.webp and /dev/null differ diff --git a/src/resources/images/icon.webp b/src/resources/images/icon.webp deleted file mode 100644 index aef4bb9..0000000 Binary files a/src/resources/images/icon.webp and /dev/null differ diff --git a/src/resources/images/images.ts b/src/resources/images/images.ts new file mode 100644 index 0000000..3be1700 --- /dev/null +++ b/src/resources/images/images.ts @@ -0,0 +1,17 @@ +import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; + +const woodIconUrl = ConvertToBase64WhenRelease("./Icon_Wood.webp"); +const zombieHeadUrl = ConvertToBase64WhenRelease("./Icon_Zombie_Head.webp"); // invasion bar + death end-card +const woodPanelUrl = ConvertToBase64WhenRelease("./ResourceBackground_Wood.webp"); +const metalPanelUrl = ConvertToBase64WhenRelease("./ResourceBackground_Metal.webp"); +const toolPanelUrl = ConvertToBase64WhenRelease("./Tool_Backgtound.webp"); // weapon panel background +const toolIconUrl = ConvertToBase64WhenRelease("./Tool_1.webp"); // weapon icon + +export const images = { + woodIconUrl, + zombieHeadUrl, + woodPanelUrl, + metalPanelUrl, + toolPanelUrl, + toolIconUrl, +}; diff --git a/src/resources/images/logo24.png b/src/resources/images/logo24.png deleted file mode 100644 index 00056ee..0000000 Binary files a/src/resources/images/logo24.png and /dev/null differ diff --git a/src/resources/images/woodIcon.ts b/src/resources/images/woodIcon.ts deleted file mode 100644 index 2ba0874..0000000 --- a/src/resources/images/woodIcon.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ConvertToBase64WhenRelease } from "@24tools/ads_common"; - -// URL of the wood loot icon. In a release build it's inlined as base64 (like -// meshes/sounds). Path is relative to this file. -export const woodIconUrl = ConvertToBase64WhenRelease("./Icon_Wood.webp"); diff --git a/src/resources/resources.ts b/src/resources/resources.ts index e60cfcf..3f3816a 100644 --- a/src/resources/resources.ts +++ b/src/resources/resources.ts @@ -1,6 +1,9 @@ import { ConvertResourcesType } from "@24tools/playable_template"; import { meshes } from "./meshes/meshes"; import { sounds } from "./sounds/sounds"; -import { vfx_json } from "./vfx/vfx_json"; +import { vfx_json } from "./vfx/vfx_json";1 +import { images } from "./images/images"; export const resources: ConvertResourcesType = [meshes, sounds, vfx_json]; + +export { images }; diff --git a/src/resources/vfx/VfxManager.ts b/src/resources/vfx/VfxManager.ts index f4b46b5..684def4 100644 --- a/src/resources/vfx/VfxManager.ts +++ b/src/resources/vfx/VfxManager.ts @@ -1,7 +1,8 @@ import { BatchedRenderer, QuarksLoader, QuarksUtil } from "three.quarks"; import { Object3D, Euler, Vector3 } from "three"; -import { ResourcesC, UpdateController } from "@24tools/playable_template"; +import { ResourcesC } from "@24tools/playable_template"; import { ThreeC } from "../../controllers/ThreeC"; +import { GameLoop } from "../../core/GameLoop"; // Resource "type" under which Quark VFX JSONs are registered (see resources.ts). const VFX_RESOURCE_TYPE = "vfx_json"; @@ -14,7 +15,7 @@ export class VfxManager { this.batchRenderer = new BatchedRenderer(); this.loader = new QuarksLoader(); ThreeC.addToScene(this.batchRenderer); - UpdateController.Instance.onUpdate.addDelegate(this.update.bind(this)); + GameLoop.register(this.update.bind(this)); } static update(delta: number) { diff --git a/src/resources/vfx/files/test.json b/src/resources/vfx/files/test.json deleted file mode 100644 index 32c50a2..0000000 --- a/src/resources/vfx/files/test.json +++ /dev/null @@ -1 +0,0 @@ -{"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/resources/vfx/vfx_json.ts b/src/resources/vfx/vfx_json.ts index 1d776d4..18bf1fb 100644 --- a/src/resources/vfx/vfx_json.ts +++ b/src/resources/vfx/vfx_json.ts @@ -17,10 +17,6 @@ export const vfx_json: ConvertResourceType = { "resources/vfx/files/VFX_Lootable_Destroy.json", ), }, - { - name: "Test", - value: ConvertToBase64WhenRelease("resources/vfx/files/test.json"), - }, ], loader: quarksLoader, }; diff --git a/src/templateConfig/beforeResourcesLoadedCb.ts b/src/templateConfig/beforeResourcesLoadedCb.ts index 3d9d941..51c3814 100644 --- a/src/templateConfig/beforeResourcesLoadedCb.ts +++ b/src/templateConfig/beforeResourcesLoadedCb.ts @@ -28,8 +28,7 @@ export const beforeResourcesLoadedCb = () => { // The player is steered by setting its velocity directly every frame, so // ground friction would only fight the intended motion (it dropped the // effective speed to ~0.84 of 4 m/s). Disable it globally; boundary walls - // still block the player via the normal contact constraint. When crates are - // added later, give them their own ContactMaterial if they need friction. + // still block the player via the normal contact constraint if (Physics_internal.physicsWorld) { Physics_internal.physicsWorld.defaultContactMaterial.friction = 0; }