fixed bugs
This commit is contained in:
@@ -88,7 +88,7 @@ export const globalSettings: ConfigUiParamsCategories[] = [
|
||||
values: [
|
||||
30,
|
||||
150,
|
||||
30
|
||||
33
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -106,7 +106,7 @@ export const globalSettings: ConfigUiParamsCategories[] = [
|
||||
[
|
||||
0,
|
||||
15,
|
||||
9
|
||||
8
|
||||
],
|
||||
[
|
||||
-10,
|
||||
@@ -125,7 +125,7 @@ export const globalSettings: ConfigUiParamsCategories[] = [
|
||||
[
|
||||
-360,
|
||||
360,
|
||||
5
|
||||
7
|
||||
],
|
||||
[
|
||||
-360,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { EasyEvent, JoystickC, UpdateController } from "@24tools/playable_templa
|
||||
import { Vector3 } from "three";
|
||||
import { PhysicsTriggerC } from "./PhysicsTriggerC";
|
||||
import { PropC, PropRegistry } from "./PropC";
|
||||
import { PropHpUIC } from "./PropHpUIC";
|
||||
import { Player } from "../Presets/Player";
|
||||
|
||||
export class GatherC {
|
||||
@@ -119,6 +120,7 @@ export class GatherC {
|
||||
if (Player.isMoving() || Player.isCombatBusy()) return;
|
||||
|
||||
const targetPosition = this.getTargetPosition(props);
|
||||
PropHpUIC.showFor(props);
|
||||
Player.startAutoAttack(
|
||||
() => this.onAttackStrike(),
|
||||
() => {},
|
||||
|
||||
@@ -57,7 +57,12 @@ export class InteractiveZoneC {
|
||||
if ("color" in cloned) {
|
||||
this.baseColors.set(cloned, (cloned.color as Color).clone());
|
||||
}
|
||||
cloned.depthWrite = true;
|
||||
cloned.polygonOffset = true;
|
||||
cloned.polygonOffsetFactor = 2;
|
||||
cloned.polygonOffsetUnits = 2;
|
||||
mesh.material = cloned;
|
||||
mesh.renderOrder = 0;
|
||||
});
|
||||
|
||||
this.refreshFillVisual();
|
||||
|
||||
@@ -22,12 +22,18 @@ export class Map {
|
||||
mapObject.position.set(0, 0, 0);
|
||||
ThreeC.setShadowsStateForChildren(mapObject, true, true);
|
||||
ThreeC.addToScene(mapObject);
|
||||
this.hideReferenceHpUi(mapObject);
|
||||
|
||||
PhysicsTriggerC.init();
|
||||
this.buildPhysics(mapObject);
|
||||
InteractiveZoneC.init(mapObject);
|
||||
}
|
||||
|
||||
private static hideReferenceHpUi(mapObject: Object3D) {
|
||||
const ui = mapObject.getObjectByName("UI");
|
||||
if (ui) ui.visible = false;
|
||||
}
|
||||
|
||||
private static buildPhysics(mapObject: Object3D) {
|
||||
for (const layerName of MAP_PHYSICS_LAYERS) {
|
||||
const layer = mapObject.getObjectByName(layerName);
|
||||
|
||||
@@ -1,42 +1,108 @@
|
||||
import { Box3, Mesh, Object3D, Vector3 } from "three";
|
||||
import { CameraC } from "../CameraC";
|
||||
import {
|
||||
Box3,
|
||||
Color,
|
||||
DoubleSide,
|
||||
Material,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
MeshStandardMaterial,
|
||||
Object3D,
|
||||
Quaternion,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import { ResourcesC } from "@24tools/playable_template";
|
||||
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { ThreeC } from "../ThreeC";
|
||||
import { ResourcesType } from "../Presets/Enums/ResourcesType";
|
||||
import { PropC } from "./PropC";
|
||||
|
||||
const BAR_Y_OFFSET = 0.8;
|
||||
const BAR_X_OFFSET = -0.3;
|
||||
const HP_BAR_MESH = "hpBar";
|
||||
const NODE_UI = "UI";
|
||||
const NODE_BACKGROUND = "UI_Background";
|
||||
const NODE_FOREGROUND = "UI_Foreground";
|
||||
const NODE_MIDDLEGROUND = "UI_Middleground";
|
||||
|
||||
const BAR_Y_OFFSET = 0.55;
|
||||
const BAR_X_OFFSET = -0.2;
|
||||
const CATCHUP_SPEED = 2.5;
|
||||
const BAR_WORLD_SCALE = 1;
|
||||
|
||||
/** Transparent pass + renderOrder above all map decals. */
|
||||
export const OVERLAY_RENDER_ORDER = 10000;
|
||||
const RENDER_ORDER_BACKGROUND = OVERLAY_RENDER_ORDER;
|
||||
const RENDER_ORDER_MIDDLEGROUND = OVERLAY_RENDER_ORDER + 1;
|
||||
const RENDER_ORDER_FOREGROUND = OVERLAY_RENDER_ORDER + 2;
|
||||
|
||||
type FillPart = {
|
||||
node: Object3D;
|
||||
baseScaleX: number;
|
||||
basePosX: number;
|
||||
/** Local mesh min.x (negative); keeps left edge fixed when scaling. */
|
||||
anchorMinX: number;
|
||||
};
|
||||
|
||||
export class PropHpBar {
|
||||
static readonly OVERLAY_RENDER_ORDER = OVERLAY_RENDER_ORDER;
|
||||
private static readonly identityMatrix = new Matrix4();
|
||||
private static readonly tempMatrix = new Matrix4();
|
||||
private static readonly tempPosition = new Vector3();
|
||||
private static readonly tempQuaternion = new Quaternion();
|
||||
private static readonly tempScale = new Vector3();
|
||||
private static readonly worldAnchor = new Vector3();
|
||||
|
||||
private readonly prop: PropC;
|
||||
private readonly root: HTMLElement;
|
||||
private readonly currentEl: HTMLElement;
|
||||
private readonly delayedEl: HTMLElement;
|
||||
private readonly anchor = new Vector3();
|
||||
private readonly root: Object3D;
|
||||
private readonly foreground: FillPart | null;
|
||||
private readonly middleground: FillPart | null;
|
||||
private readonly bounds = new Box3();
|
||||
|
||||
private displayedHealth: number;
|
||||
private visible = false;
|
||||
|
||||
constructor(prop: PropC, parent: HTMLElement) {
|
||||
static getPrefab(): Object3D | null {
|
||||
const fromScene = ThreeC.getObject(HP_BAR_MESH);
|
||||
if (fromScene) return fromScene;
|
||||
|
||||
const gltf = ResourcesC.getResource<GLTF>(ResourcesType.Mesh, HP_BAR_MESH);
|
||||
return gltf?.scene ?? null;
|
||||
}
|
||||
|
||||
constructor(prop: PropC, parent: Object3D) {
|
||||
const template = PropHpBar.getPrefab();
|
||||
if (!template) {
|
||||
throw new Error(`HP bar mesh resource not found: ${HP_BAR_MESH}`);
|
||||
}
|
||||
|
||||
const sourceUi = template.getObjectByName(NODE_UI);
|
||||
if (!sourceUi) {
|
||||
throw new Error(`HP bar node not found: ${NODE_UI}`);
|
||||
}
|
||||
|
||||
this.prop = prop;
|
||||
this.displayedHealth = prop.health;
|
||||
|
||||
this.root = document.createElement("div");
|
||||
this.root.className = "prop-hp-bar";
|
||||
this.root = new Object3D();
|
||||
this.root.visible = false;
|
||||
this.root.scale.setScalar(BAR_WORLD_SCALE);
|
||||
this.root.frustumCulled = false;
|
||||
this.root.renderOrder = RENDER_ORDER_FOREGROUND;
|
||||
|
||||
const track = document.createElement("div");
|
||||
track.className = "prop-hp-bar__track";
|
||||
const ui = sourceUi.clone(true);
|
||||
this.bakeMatrices(ui);
|
||||
this.centerGroup(ui);
|
||||
this.prepareMeshes(ui);
|
||||
|
||||
this.delayedEl = document.createElement("div");
|
||||
this.delayedEl.className = "prop-hp-bar__delayed";
|
||||
this.root.add(ui);
|
||||
parent.add(this.root);
|
||||
|
||||
this.currentEl = document.createElement("div");
|
||||
this.currentEl.className = "prop-hp-bar__current";
|
||||
const fgNode = ui.getObjectByName(NODE_FOREGROUND);
|
||||
const mgNode = ui.getObjectByName(NODE_MIDDLEGROUND);
|
||||
this.foreground = fgNode ? this.createFillPart(fgNode) : null;
|
||||
this.middleground = mgNode ? this.createFillPart(mgNode) : null;
|
||||
|
||||
track.appendChild(this.delayedEl);
|
||||
track.appendChild(this.currentEl);
|
||||
this.root.appendChild(track);
|
||||
parent.appendChild(this.root);
|
||||
const background = ui.getObjectByName(NODE_BACKGROUND);
|
||||
if (background) background.visible = true;
|
||||
|
||||
this.applyWidths();
|
||||
}
|
||||
@@ -61,32 +127,68 @@ export class PropHpBar {
|
||||
);
|
||||
}
|
||||
|
||||
this.delayedEl.style.width = `${this.getHealthRatio(this.displayedHealth) * 100}%`;
|
||||
if (this.middleground) {
|
||||
this.applyFill(this.middleground, this.getHealthRatio(this.displayedHealth));
|
||||
}
|
||||
|
||||
if (!this.visible) return;
|
||||
|
||||
this.updatePosition();
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
setVisible(value: boolean) {
|
||||
this.visible = value;
|
||||
this.root.classList.toggle("is-visible", value);
|
||||
this.root.visible = value;
|
||||
|
||||
if (value) {
|
||||
this.displayedHealth = this.prop.health;
|
||||
this.applyWidths();
|
||||
this.updatePosition();
|
||||
this.updateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.remove();
|
||||
this.root.removeFromParent();
|
||||
}
|
||||
|
||||
private applyWidths() {
|
||||
const ratio = this.getHealthRatio(this.prop.health);
|
||||
this.currentEl.style.width = `${ratio * 100}%`;
|
||||
this.delayedEl.style.width = `${this.getHealthRatio(this.displayedHealth) * 100}%`;
|
||||
if (this.foreground) {
|
||||
this.applyFill(this.foreground, this.getHealthRatio(this.prop.health));
|
||||
}
|
||||
if (this.middleground) {
|
||||
this.applyFill(this.middleground, this.getHealthRatio(this.displayedHealth));
|
||||
}
|
||||
}
|
||||
|
||||
private applyFill(part: FillPart, ratio: number) {
|
||||
const clamped = Math.max(0, Math.min(1, ratio));
|
||||
part.node.scale.x = part.baseScaleX * clamped;
|
||||
part.node.position.x =
|
||||
part.basePosX + part.anchorMinX * part.baseScaleX * (1 - clamped);
|
||||
}
|
||||
|
||||
private createFillPart(node: Object3D): FillPart {
|
||||
const meshMinX = this.getMeshMinX(node);
|
||||
const isForeground = node.name.includes(NODE_FOREGROUND);
|
||||
|
||||
node.renderOrder = isForeground
|
||||
? RENDER_ORDER_FOREGROUND
|
||||
: RENDER_ORDER_MIDDLEGROUND;
|
||||
|
||||
return {
|
||||
node,
|
||||
baseScaleX: node.scale.x,
|
||||
basePosX: node.position.x,
|
||||
anchorMinX: meshMinX,
|
||||
};
|
||||
}
|
||||
|
||||
private getMeshMinX(node: Object3D) {
|
||||
const mesh = node as Mesh;
|
||||
if (!mesh.isMesh || !mesh.geometry) return -0.935;
|
||||
|
||||
mesh.geometry.computeBoundingBox();
|
||||
return mesh.geometry.boundingBox?.min.x ?? -0.935;
|
||||
}
|
||||
|
||||
private getHealthRatio(health: number) {
|
||||
@@ -94,78 +196,138 @@ export class PropHpBar {
|
||||
return Math.max(health, 0) / this.prop.maxHealth;
|
||||
}
|
||||
|
||||
private updatePosition() {
|
||||
private updateTransform() {
|
||||
if (!this.computeVisualBounds()) {
|
||||
this.root.style.display = "none";
|
||||
this.root.visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.bounds.getCenter(this.anchor);
|
||||
this.anchor.x += BAR_X_OFFSET;
|
||||
this.anchor.y = this.bounds.max.y + BAR_Y_OFFSET;
|
||||
PropHpBar.worldAnchor.set(
|
||||
(this.bounds.min.x + this.bounds.max.x) * 0.5 + BAR_X_OFFSET,
|
||||
this.bounds.max.y + BAR_Y_OFFSET,
|
||||
(this.bounds.min.z + this.bounds.max.z) * 0.5,
|
||||
);
|
||||
|
||||
const camera = CameraC.camera;
|
||||
const projected = this.anchor.project(camera);
|
||||
|
||||
if (projected.z > 1) {
|
||||
this.root.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const screenPos = PropHpBar.worldToLayerPosition(projected, this.root.parentElement);
|
||||
if (!screenPos) {
|
||||
this.root.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.style.display = "";
|
||||
this.root.style.left = `${screenPos.x}px`;
|
||||
this.root.style.top = `${screenPos.y}px`;
|
||||
}
|
||||
|
||||
private static worldToLayerPosition(
|
||||
projected: Vector3,
|
||||
layer: HTMLElement | null,
|
||||
): { x: number; y: number } | null {
|
||||
const canvas = document.querySelector("canvas");
|
||||
if (!canvas || !layer) return null;
|
||||
|
||||
const canvasRect = canvas.getBoundingClientRect();
|
||||
const layerRect = layer.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
x:
|
||||
(projected.x * 0.5 + 0.5) * canvasRect.width +
|
||||
canvasRect.left -
|
||||
layerRect.left,
|
||||
y:
|
||||
(-projected.y * 0.5 + 0.5) * canvasRect.height +
|
||||
canvasRect.top -
|
||||
layerRect.top,
|
||||
};
|
||||
this.root.position.copy(PropHpBar.worldAnchor);
|
||||
this.root.quaternion.set(0, 0, 0, 1);
|
||||
this.root.visible = this.visible;
|
||||
}
|
||||
|
||||
private computeVisualBounds() {
|
||||
this.prop.object.updateWorldMatrix(true, true);
|
||||
this.bounds.makeEmpty();
|
||||
|
||||
const includeMesh = (node: Object3D) => {
|
||||
if (!(node as Mesh).isMesh) return;
|
||||
this.prop.object.traverse((child) => {
|
||||
if (!(child as Mesh).isMesh) return;
|
||||
|
||||
const name = node.name.toLowerCase();
|
||||
const name = child.name.toLowerCase();
|
||||
if (name.includes("collider") || name.includes("trigger") || name.includes("gathertrigger")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bounds.expandByObject(node);
|
||||
};
|
||||
|
||||
includeMesh(this.prop.object);
|
||||
this.prop.object.traverse((child) => includeMesh(child));
|
||||
this.bounds.expandByObject(child);
|
||||
});
|
||||
|
||||
if (!this.bounds.isEmpty()) return true;
|
||||
|
||||
this.bounds.setFromObject(this.prop.object);
|
||||
return !this.bounds.isEmpty();
|
||||
}
|
||||
|
||||
private bakeMatrices(ui: Object3D) {
|
||||
this.decomposeNodeMatrix(ui);
|
||||
ui.traverse((child) => {
|
||||
if (child === ui) return;
|
||||
this.decomposeNodeMatrix(child);
|
||||
});
|
||||
}
|
||||
|
||||
private decomposeNodeMatrix(node: Object3D) {
|
||||
node.updateMatrix();
|
||||
PropHpBar.tempMatrix.copy(node.matrix);
|
||||
if (PropHpBar.tempMatrix.equals(PropHpBar.identityMatrix)) return;
|
||||
|
||||
PropHpBar.tempMatrix.decompose(
|
||||
PropHpBar.tempPosition,
|
||||
PropHpBar.tempQuaternion,
|
||||
PropHpBar.tempScale,
|
||||
);
|
||||
|
||||
node.matrix.identity();
|
||||
node.position.copy(PropHpBar.tempPosition);
|
||||
node.quaternion.copy(PropHpBar.tempQuaternion);
|
||||
node.scale.copy(PropHpBar.tempScale);
|
||||
node.matrixAutoUpdate = true;
|
||||
}
|
||||
|
||||
private centerGroup(group: Object3D) {
|
||||
const box = new Box3().setFromObject(group);
|
||||
const center = new Vector3();
|
||||
box.getCenter(center);
|
||||
group.position.sub(center);
|
||||
}
|
||||
|
||||
private prepareMeshes(root: Object3D) {
|
||||
root.traverse((child) => {
|
||||
child.frustumCulled = false;
|
||||
|
||||
if (!(child as Mesh).isMesh) return;
|
||||
|
||||
const mesh = child as Mesh;
|
||||
mesh.castShadow = false;
|
||||
mesh.receiveShadow = false;
|
||||
|
||||
if (child.name.includes(NODE_BACKGROUND)) {
|
||||
child.renderOrder = RENDER_ORDER_BACKGROUND;
|
||||
} else if (child.name.includes(NODE_MIDDLEGROUND)) {
|
||||
child.renderOrder = RENDER_ORDER_MIDDLEGROUND;
|
||||
} else if (child.name.includes(NODE_FOREGROUND)) {
|
||||
child.renderOrder = RENDER_ORDER_FOREGROUND;
|
||||
}
|
||||
|
||||
if (Array.isArray(mesh.material)) {
|
||||
mesh.material = mesh.material.map((material) => this.toVisibleMaterial(material));
|
||||
} else {
|
||||
mesh.material = this.toVisibleMaterial(mesh.material);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Keeps GLB map/color; unlit basic material renders reliably in playable. */
|
||||
private toVisibleMaterial(source: Material) {
|
||||
if (source instanceof MeshBasicMaterial) {
|
||||
const cloned = source.clone();
|
||||
cloned.side = DoubleSide;
|
||||
cloned.depthTest = false;
|
||||
cloned.depthWrite = false;
|
||||
cloned.toneMapped = false;
|
||||
cloned.transparent = true;
|
||||
return cloned;
|
||||
}
|
||||
|
||||
const src = source as MeshStandardMaterial;
|
||||
const material = new MeshBasicMaterial({
|
||||
side: DoubleSide,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
toneMapped: false,
|
||||
transparent: true,
|
||||
opacity: src.opacity ?? 1,
|
||||
});
|
||||
|
||||
if (src.map) {
|
||||
material.map = src.map;
|
||||
}
|
||||
if (src.color) {
|
||||
material.color.copy(src.color);
|
||||
} else {
|
||||
material.color = new Color(0xffffff);
|
||||
}
|
||||
if (src.alphaTest) {
|
||||
material.alphaTest = src.alphaTest;
|
||||
material.transparent = true;
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Object3D } from "three";
|
||||
import { UpdateController } from "@24tools/playable_template";
|
||||
import { ThreeC } from "../ThreeC";
|
||||
import { PropC } from "./PropC";
|
||||
import { PropHpBar } from "./PropHpBar";
|
||||
|
||||
@@ -7,20 +9,15 @@ const DEBUG_ALWAYS_VISIBLE_HP_BARS = false;
|
||||
|
||||
export class PropHpUIC {
|
||||
private static inited = false;
|
||||
private static root: HTMLElement | null = null;
|
||||
private static overlayLayer: Object3D | null = null;
|
||||
private static bars = new Map<PropC, PropHpBar>();
|
||||
|
||||
static init() {
|
||||
if (this.inited) return;
|
||||
this.inited = true;
|
||||
|
||||
const uiRoot = document.getElementById("ui");
|
||||
if (!uiRoot) return;
|
||||
|
||||
this.root = document.createElement("div");
|
||||
this.root.id = "prop-hp-layer";
|
||||
this.root.className = "prop-hp-layer";
|
||||
uiRoot.appendChild(this.root);
|
||||
this.hideSceneTemplate();
|
||||
this.ensureOverlayLayer();
|
||||
|
||||
UpdateController.Instance.onUpdate.addDelegate((delta) => {
|
||||
for (const bar of this.bars.values()) {
|
||||
@@ -29,10 +26,31 @@ export class PropHpUIC {
|
||||
});
|
||||
}
|
||||
|
||||
static createBar(prop: PropC): PropHpBar | undefined {
|
||||
if (!this.root || this.bars.has(prop)) return this.bars.get(prop);
|
||||
/** Call after map/scene objects are added so overlay renders last. */
|
||||
static bringOverlayToFront() {
|
||||
this.ensureOverlayLayer();
|
||||
const layer = this.overlayLayer;
|
||||
if (!layer) return;
|
||||
|
||||
const bar = new PropHpBar(prop, this.root);
|
||||
let parent = layer.parent;
|
||||
if (!parent) {
|
||||
ThreeC.addToScene(layer);
|
||||
parent = layer.parent;
|
||||
if (!parent) return;
|
||||
}
|
||||
|
||||
parent.remove(layer);
|
||||
parent.add(layer);
|
||||
}
|
||||
|
||||
static createBar(prop: PropC): PropHpBar | undefined {
|
||||
if (!PropHpBar.getPrefab()) {
|
||||
console.warn("HP bar prefab not loaded");
|
||||
return undefined;
|
||||
}
|
||||
if (this.bars.has(prop)) return this.bars.get(prop);
|
||||
|
||||
const bar = new PropHpBar(prop, this.getOverlayLayer());
|
||||
this.bars.set(prop, bar);
|
||||
|
||||
if (DEBUG_ALWAYS_VISIBLE_HP_BARS) {
|
||||
@@ -79,4 +97,27 @@ export class PropHpUIC {
|
||||
bar.destroy();
|
||||
this.bars.delete(prop);
|
||||
}
|
||||
|
||||
private static hideSceneTemplate() {
|
||||
const template = PropHpBar.getPrefab();
|
||||
if (!template || !template.parent) return;
|
||||
|
||||
template.visible = false;
|
||||
template.removeFromParent();
|
||||
}
|
||||
|
||||
private static ensureOverlayLayer() {
|
||||
if (!this.overlayLayer) {
|
||||
this.overlayLayer = new Object3D();
|
||||
this.overlayLayer.name = "prop-hp-overlay";
|
||||
this.overlayLayer.frustumCulled = false;
|
||||
this.overlayLayer.renderOrder = PropHpBar.OVERLAY_RENDER_ORDER;
|
||||
ThreeC.addToScene(this.overlayLayer);
|
||||
}
|
||||
}
|
||||
|
||||
private static getOverlayLayer() {
|
||||
this.ensureOverlayLayer();
|
||||
return this.overlayLayer!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,21 @@ import { ResourceDepositC } from "./Resources/ResourceDepositC";
|
||||
import { PropHpUIC } from "./Map/PropHpUIC";
|
||||
import { PropVfxC } from "./Map/PropVfxC";
|
||||
import { InvasionProgressUIC } from "./UI/InvasionProgressUIC";
|
||||
import { PreGameplayUIC } from "./UI/PreGameplayUIC";
|
||||
|
||||
export class TestSceneC {
|
||||
static init() {
|
||||
InvasionProgressUIC.init();
|
||||
ResourceUIC.init();
|
||||
PreGameplayUIC.init();
|
||||
ResourceFlyC.init();
|
||||
PropHpUIC.init();
|
||||
PropVfxC.Init();
|
||||
Map.init();
|
||||
PropHpUIC.bringOverlayToFront();
|
||||
GatherC.init();
|
||||
Player.init();
|
||||
PropHpUIC.bringOverlayToFront();
|
||||
|
||||
const mapObject = ThreeC.getObject("map");
|
||||
if (mapObject) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
export class PreGameplayUIC {
|
||||
private static root: HTMLElement | null = null;
|
||||
|
||||
static init() {
|
||||
const ui = document.getElementById("ui");
|
||||
if (!ui) return;
|
||||
|
||||
ui.classList.add("is-pre-gameplay");
|
||||
|
||||
const root = document.createElement("div");
|
||||
root.className = "pre-gameplay";
|
||||
|
||||
const hurry = document.createElement("p");
|
||||
hurry.className = "pre-gameplay__hurry";
|
||||
hurry.textContent = "HURRY UP";
|
||||
|
||||
const bottom = document.createElement("div");
|
||||
bottom.className = "pre-gameplay__bottom";
|
||||
|
||||
const drag = document.createElement("p");
|
||||
drag.className = "pre-gameplay__drag";
|
||||
drag.textContent = "DRAG TO MOVE";
|
||||
|
||||
const joystick = document.createElement("div");
|
||||
joystick.className = "pre-gameplay-joystick";
|
||||
|
||||
const base = document.createElement("div");
|
||||
base.className = "pre-gameplay-joystick__base";
|
||||
|
||||
const knob = document.createElement("div");
|
||||
knob.className = "pre-gameplay-joystick__knob";
|
||||
|
||||
joystick.append(base, knob);
|
||||
bottom.append(drag, joystick);
|
||||
root.append(hurry, bottom);
|
||||
ui.appendChild(root);
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
static dismiss() {
|
||||
document.getElementById("ui")?.classList.remove("is-pre-gameplay");
|
||||
this.root?.remove();
|
||||
this.root = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#ui.is-pre-gameplay .hud-invasion-title,
|
||||
#ui.is-pre-gameplay .hud-progress,
|
||||
#ui.is-pre-gameplay .hud-top-right {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pre-gameplay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.pre-gameplay__hurry {
|
||||
position: absolute;
|
||||
top: 14vh;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin: 0;
|
||||
font-size: 5vh;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
-webkit-text-stroke: 0.14vh #000000;
|
||||
paint-order: stroke fill;
|
||||
animation: pre-gameplay-pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.pre-gameplay__bottom {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 10vh;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2.5vh;
|
||||
}
|
||||
|
||||
.pre-gameplay__drag {
|
||||
margin: 0;
|
||||
font-size: 4.2vh;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
-webkit-text-stroke: 0.12vh #000000;
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
|
||||
.pre-gameplay-joystick {
|
||||
position: relative;
|
||||
width: 22vh;
|
||||
height: 22vh;
|
||||
}
|
||||
|
||||
.pre-gameplay-joystick__base {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 0.35vh solid rgba(255, 255, 255, 0.55);
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pre-gameplay-joystick__knob {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 8vh;
|
||||
height: 8vh;
|
||||
margin-left: -4vh;
|
||||
margin-top: -4vh;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
animation: pre-gameplay-joystick-knob 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pre-gameplay-pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(-50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-50%) scale(1.06);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pre-gameplay-joystick-knob {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
25% {
|
||||
transform: translate(5vh, 0);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, -3.5vh);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-5vh, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
.pre-gameplay__hurry {
|
||||
top: 8vh;
|
||||
font-size: 4vh;
|
||||
}
|
||||
|
||||
.pre-gameplay__bottom {
|
||||
bottom: 6vh;
|
||||
gap: 1.5vh;
|
||||
}
|
||||
|
||||
.pre-gameplay__drag {
|
||||
font-size: 3.2vh;
|
||||
}
|
||||
|
||||
.pre-gameplay-joystick {
|
||||
width: 18vh;
|
||||
height: 18vh;
|
||||
}
|
||||
|
||||
.pre-gameplay-joystick__knob {
|
||||
width: 6.5vh;
|
||||
height: 6.5vh;
|
||||
margin-left: -3.25vh;
|
||||
margin-top: -3.25vh;
|
||||
}
|
||||
|
||||
@keyframes pre-gameplay-joystick-knob {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
25% {
|
||||
transform: translate(4vh, 0);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, -2.8vh);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-4vh, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,58 +184,6 @@
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.prop-hp-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.prop-hp-bar {
|
||||
position: absolute;
|
||||
width: 8vh;
|
||||
height: 1.2vh;
|
||||
transform: translate(-50%, -100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 15;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.prop-hp-bar.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.prop-hp-bar__track {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000000;
|
||||
border-radius: 0.25vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prop-hp-bar__delayed,
|
||||
.prop-hp-bar__current {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
border-radius: 0.25vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prop-hp-bar__delayed {
|
||||
background: #ffffff;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.prop-hp-bar__current {
|
||||
background: #3b8bff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
.hud-top-center {
|
||||
top: 1.2vh;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<link href="./css/loader.css" rel="stylesheet" />
|
||||
<link href="./css/main.css" rel="stylesheet" />
|
||||
<link href="./css/ui.css" rel="stylesheet" />
|
||||
<link href="./css/preGameplay.css" rel="stylesheet" />
|
||||
<style id="animations"></style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
@@ -17,6 +17,10 @@ export const meshes : ConvertResourceType = {
|
||||
name: "wood",
|
||||
value: ConvertToBase64WhenRelease("./wood.glb"),
|
||||
},
|
||||
{
|
||||
name: "hpBar",
|
||||
value: ConvertToBase64WhenRelease("./hpBar.glb"),
|
||||
},
|
||||
],
|
||||
loader: Template3d.meshLoader
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { InvasionProgressUIC } from "../controllers/UI/InvasionProgressUIC";
|
||||
import { PreGameplayUIC } from "../controllers/UI/PreGameplayUIC";
|
||||
|
||||
export const firstClickCb: () => void = () => {
|
||||
PreGameplayUIC.dismiss();
|
||||
InvasionProgressUIC.startTimer();
|
||||
};
|
||||
};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user