138 lines
6.2 KiB
TypeScript
138 lines
6.2 KiB
TypeScript
import {
|
|
CameraC_internal,
|
|
CameraType,
|
|
JoystickC,
|
|
Physics_internal,
|
|
Template,
|
|
Template3d,
|
|
ThreeC_internal,
|
|
TweenC,
|
|
FilterScene,
|
|
InstallBanner,
|
|
} from "@hitplay/playable_template";
|
|
import { CameraC } from "../controllers/CameraC";
|
|
import { CameraFollowC } from "../controllers/CameraFollowC";
|
|
import { ThreeC } from "../controllers/ThreeC";
|
|
import { Color } from "three";
|
|
import { Vec3 } from "cannon-es";
|
|
export const beforeResourcesLoadedCb = () => {
|
|
FilterScene.init();
|
|
InstallBanner.init();
|
|
|
|
Template3d.init();
|
|
CameraC_internal.init(CameraType.perspective);
|
|
CameraC.setCamera(window.screenSize.portrait);
|
|
ThreeC_internal.init();
|
|
ThreeC.createBaseLights();
|
|
ThreeC.setupDirectionalLight();
|
|
|
|
Physics_internal.init(new Vec3(0, -9.81, 0));
|
|
TweenC.init();
|
|
|
|
// zone is omitted on purpose: JoystickC auto-creates a full-screen zone
|
|
// when none is given, so the joystick can appear wherever the player taps
|
|
JoystickC.init({
|
|
mode: "dynamic",
|
|
fadeTime: 100,
|
|
restJoystick: true,
|
|
restOpacity: 0,
|
|
follow: false,
|
|
// nipplejs's own default is 100; the inner thumb is always exactly
|
|
// half of this by nipplejs's own rule — irrelevant here since .front's
|
|
// size is overridden independently in main.css.
|
|
size: 105,
|
|
// "front" (the thumb/finger position) is a single flat color. Its
|
|
// actual on-screen SIZE is overridden separately in main.css, since
|
|
// nipplejs ties it to a hardcoded half of "size" above with no
|
|
// independent option. "back" (the ring) is a single flat, darkened
|
|
// color around most of its circumference, except a wide arc right
|
|
// where the thumb sits, which brightens toward the thumb's own color
|
|
// as if lit by it. --joy-angle (WHERE) and --joy-force (HOW STRONG)
|
|
// are live CSS vars driven from onJoysticMove below. The ring's
|
|
// transparent middle and the thumb's forced-opaque override
|
|
// (nipplejs hardcodes the thumb at 50% opacity) also live in
|
|
// main.css, since `color` can't express either.
|
|
//
|
|
// Alpha is fixed (not interpolated to 0) at every stop, only the RGB
|
|
// channels shift with --joy-force — interpolating alpha down to 0
|
|
// at rest made the highlight's position read as a see-through gap
|
|
// instead of matching the base color. Alpha itself still ramps up
|
|
// slightly with force (0.75 -> 0.95) so the highlight reads as the
|
|
// thumb's actual saturated color rather than diluted by the sandy
|
|
// background showing through it.
|
|
color: {
|
|
back: "conic-gradient(from var(--joy-angle, 0deg), rgba(calc(70 + 185 * var(--joy-force, 0)), calc(55 + 85 * var(--joy-force, 0)), calc(45 - 5 * var(--joy-force, 0)), calc(0.75 + 0.2 * var(--joy-force, 0))) 0deg, rgba(70, 55, 45, 0.75) 85deg, rgba(70, 55, 45, 0.75) 275deg, rgba(calc(70 + 185 * var(--joy-force, 0)), calc(55 + 85 * var(--joy-force, 0)), calc(45 - 5 * var(--joy-force, 0)), calc(0.75 + 0.2 * var(--joy-force, 0))) 360deg)",
|
|
front: "#ff8c28",
|
|
},
|
|
});
|
|
|
|
// Keeps --joy-angle/--joy-force (read by the conic-gradient above)
|
|
// tracking the thumb in real time.
|
|
//
|
|
// Angle: uses the thumb's own pixel offset from center (frontPosition —
|
|
// what nipplejs itself uses to `translate()` the thumb) through
|
|
// Math.atan2, not nipplejs's own `angle` field (flipped into a
|
|
// "human-readable" convention internally and rotated again for
|
|
// orientation-lock logical space — neither matches plain CSS/screen
|
|
// angle math). atan2 and CSS conic-gradient both sweep clockwise as the
|
|
// angle increases, but their zero points differ by 90° (atan2's 0° is
|
|
// 3 o'clock/east; conic-gradient's `from 0deg` is 12 o'clock/north) — the
|
|
// fix is a flat +90 added once, not a sign flip.
|
|
//
|
|
// Force: nipplejs documents `force` as normalized 0..1, but it's
|
|
// actually computed from the RAW pre-clamp touch distance, while
|
|
// frontPosition (and so the angle above) uses the CLAMPED distance — so
|
|
// force keeps climbing past 1 the further you drag beyond the ring's
|
|
// edge even though the thumb itself is visibly pinned there. Every
|
|
// calc() above assumes force is 0..1, so it's clamped once here rather
|
|
// than in every calc() in the gradient string.
|
|
//
|
|
// `data.instance` isn't in the SDK's own (stale) type declaration for
|
|
// this event, though it's really there on every nipplejs 1.x payload —
|
|
// the cast below bridges that gap.
|
|
JoystickC.onJoysticMove.addDelegate(({ data }) => {
|
|
const { instance, force } = data as unknown as {
|
|
instance?: { frontPosition: { x: number; y: number }; ui: { el: HTMLElement } };
|
|
force?: number;
|
|
};
|
|
if (!instance) return;
|
|
const { x, y } = instance.frontPosition;
|
|
const angleDeg = Math.atan2(y, x) * (180 / Math.PI) + 90;
|
|
const clampedForce = Math.min(Math.max(force ?? 0, 0), 1);
|
|
instance.ui.el.style.setProperty("--joy-angle", `${angleDeg}deg`);
|
|
instance.ui.el.style.setProperty("--joy-force", `${clampedForce}`);
|
|
});
|
|
|
|
Template.updateVariableConfig.addDelegate(([category, variable, value]) => {
|
|
if (category === "global") {
|
|
switch (variable) {
|
|
case "light_intensity":
|
|
ThreeC_internal.defaultAmbientLight.intensity = Number(value);
|
|
break;
|
|
|
|
case "light_color":
|
|
ThreeC_internal.defaultAmbientLight.color = new Color(value as any);
|
|
break;
|
|
|
|
default:
|
|
// The camera's config is split between two owners on purpose:
|
|
// FOV lives on the camera itself (CameraC), while the rig —
|
|
// offset and angle — belongs to CameraFollowC, which is the only
|
|
// thing allowed to write camera.rotation. Routing each field to
|
|
// its own owner is what keeps them from stomping each other.
|
|
if (["camera_fov_p", "camera_fov_l"].includes(variable)) {
|
|
CameraC.setCamera(window.screenSize.portrait);
|
|
} else if (
|
|
["camera_position_p", "camera_position_l", "camera_rotation_p", "camera_rotation_l"].includes(variable)
|
|
) {
|
|
// No-ops safely before the scene exists: CameraFollowC reads
|
|
// the same config again in its own init().
|
|
CameraFollowC.applyRigFromConfig();
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
});
|
|
};
|