pixospritz-core 0.10.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -286
- package/dist/bundle.js +13 -3
- package/dist/bundle.js.map +1 -1
- package/dist/style.css +1 -0
- package/package.json +43 -44
- package/src/components/WebGLView.jsx +318 -0
- package/src/css/pixos.css +372 -0
- package/src/engine/actions/animate.js +41 -0
- package/src/engine/actions/changezone.js +135 -0
- package/src/engine/actions/chat.js +109 -0
- package/src/engine/actions/dialogue.js +90 -0
- package/src/engine/actions/face.js +22 -0
- package/src/engine/actions/greeting.js +28 -0
- package/src/engine/actions/interact.js +86 -0
- package/src/engine/actions/move.js +67 -0
- package/src/engine/actions/patrol.js +109 -0
- package/src/engine/actions/prompt.js +185 -0
- package/src/engine/actions/script.js +42 -0
- package/src/engine/core/audio/AudioSystem.js +543 -0
- package/src/engine/core/cutscene/PxcPlayer.js +956 -0
- package/src/engine/core/cutscene/manager.js +243 -0
- package/src/engine/core/database/index.js +75 -0
- package/src/engine/core/debug/index.js +371 -0
- package/src/engine/core/hud/index.js +765 -0
- package/src/engine/core/index.js +540 -0
- package/src/engine/core/input/gamepad/Controller.js +71 -0
- package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
- package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
- package/src/engine/core/input/gamepad/index.js +592 -0
- package/src/engine/core/input/keyboard.js +196 -0
- package/src/engine/core/input/manager.js +485 -0
- package/src/engine/core/input/mouse.js +203 -0
- package/src/engine/core/input/touch.js +175 -0
- package/src/engine/core/mode/manager.js +199 -0
- package/src/engine/core/net/manager.js +535 -0
- package/src/engine/core/queue/action.js +83 -0
- package/src/engine/core/queue/event.js +82 -0
- package/src/engine/core/queue/index.js +44 -0
- package/src/engine/core/queue/loadable.js +33 -0
- package/src/engine/core/render/CameraEffects.js +494 -0
- package/src/engine/core/render/FrustumCuller.js +417 -0
- package/src/engine/core/render/LODManager.js +285 -0
- package/src/engine/core/render/ParticleManager.js +529 -0
- package/src/engine/core/render/TextureAtlas.js +465 -0
- package/src/engine/core/render/camera.js +338 -0
- package/src/engine/core/render/light.js +197 -0
- package/src/engine/core/render/manager.js +1079 -0
- package/src/engine/core/render/shaders.js +110 -0
- package/src/engine/core/render/skybox.js +342 -0
- package/src/engine/core/resource/manager.js +133 -0
- package/src/engine/core/resource/object.js +611 -0
- package/src/engine/core/resource/texture.js +103 -0
- package/src/engine/core/resource/tileset.js +177 -0
- package/src/engine/core/scene/avatar.js +215 -0
- package/src/engine/core/scene/speech.js +138 -0
- package/src/engine/core/scene/sprite.js +702 -0
- package/src/engine/core/scene/spritz.js +189 -0
- package/src/engine/core/scene/world.js +681 -0
- package/src/engine/core/scene/zone.js +1167 -0
- package/src/engine/core/store/index.js +110 -0
- package/src/engine/dynamic/animatedSprite.js +64 -0
- package/src/engine/dynamic/animatedTile.js +98 -0
- package/src/engine/dynamic/avatar.js +110 -0
- package/src/engine/dynamic/map.js +174 -0
- package/src/engine/dynamic/sprite.js +255 -0
- package/src/engine/dynamic/spritz.js +119 -0
- package/src/engine/events/EventSystem.js +609 -0
- package/src/engine/events/camera.js +142 -0
- package/src/engine/events/chat.js +75 -0
- package/src/engine/events/menu.js +186 -0
- package/src/engine/scripting/CallbackManager.js +514 -0
- package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
- package/src/engine/scripting/PixoScriptLibrary.js +704 -0
- package/src/engine/shaders/effects/index.js +450 -0
- package/src/engine/shaders/fs.js +222 -0
- package/src/engine/shaders/particles/fs.js +41 -0
- package/src/engine/shaders/particles/vs.js +61 -0
- package/src/engine/shaders/picker/fs.js +34 -0
- package/src/engine/shaders/picker/init.js +62 -0
- package/src/engine/shaders/picker/vs.js +42 -0
- package/src/engine/shaders/pxsl/README.md +250 -0
- package/src/engine/shaders/pxsl/index.js +25 -0
- package/src/engine/shaders/pxsl/library.js +608 -0
- package/src/engine/shaders/pxsl/manager.js +338 -0
- package/src/engine/shaders/pxsl/specification.js +363 -0
- package/src/engine/shaders/pxsl/transpiler.js +753 -0
- package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
- package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
- package/src/engine/shaders/skybox/matrix/fs.js +127 -0
- package/src/engine/shaders/skybox/matrix/vs.js +23 -0
- package/src/engine/shaders/skybox/morning/fs.js +109 -0
- package/src/engine/shaders/skybox/morning/vs.js +23 -0
- package/src/engine/shaders/skybox/neon/fs.js +119 -0
- package/src/engine/shaders/skybox/neon/vs.js +23 -0
- package/src/engine/shaders/skybox/sky/fs.js +114 -0
- package/src/engine/shaders/skybox/sky/vs.js +23 -0
- package/src/engine/shaders/skybox/sunset/fs.js +101 -0
- package/src/engine/shaders/skybox/sunset/vs.js +23 -0
- package/src/engine/shaders/transition/blur/fs.js +42 -0
- package/src/engine/shaders/transition/blur/vs.js +26 -0
- package/src/engine/shaders/transition/cross/fs.js +36 -0
- package/src/engine/shaders/transition/cross/vs.js +26 -0
- package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
- package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
- package/src/engine/shaders/transition/dissolve/fs.js +78 -0
- package/src/engine/shaders/transition/dissolve/vs.js +24 -0
- package/src/engine/shaders/transition/fade/fs.js +31 -0
- package/src/engine/shaders/transition/fade/vs.js +27 -0
- package/src/engine/shaders/transition/iris/fs.js +52 -0
- package/src/engine/shaders/transition/iris/vs.js +24 -0
- package/src/engine/shaders/transition/pixelate/fs.js +44 -0
- package/src/engine/shaders/transition/pixelate/vs.js +24 -0
- package/src/engine/shaders/transition/slide/fs.js +53 -0
- package/src/engine/shaders/transition/slide/vs.js +24 -0
- package/src/engine/shaders/transition/swirl/fs.js +39 -0
- package/src/engine/shaders/transition/swirl/vs.js +26 -0
- package/src/engine/shaders/transition/wipe/fs.js +50 -0
- package/src/engine/shaders/transition/wipe/vs.js +24 -0
- package/src/engine/shaders/vs.js +60 -0
- package/src/engine/utils/CameraController.js +506 -0
- package/src/engine/utils/ObjHelper.js +551 -0
- package/src/engine/utils/debug-logger.js +110 -0
- package/src/engine/utils/enums.js +305 -0
- package/src/engine/utils/generator.js +156 -0
- package/src/engine/utils/index.js +21 -0
- package/src/engine/utils/loaders/ActionLoader.js +77 -0
- package/src/engine/utils/loaders/AudioLoader.js +157 -0
- package/src/engine/utils/loaders/EventLoader.js +66 -0
- package/src/engine/utils/loaders/ObjectLoader.js +67 -0
- package/src/engine/utils/loaders/SpriteLoader.js +77 -0
- package/src/engine/utils/loaders/TilesetLoader.js +103 -0
- package/src/engine/utils/loaders/index.js +21 -0
- package/src/engine/utils/math/matrix4.js +367 -0
- package/src/engine/utils/math/vector.js +458 -0
- package/src/engine/utils/obj/_old_js/index.js +46 -0
- package/src/engine/utils/obj/_old_js/layout.js +308 -0
- package/src/engine/utils/obj/_old_js/material.js +711 -0
- package/src/engine/utils/obj/_old_js/mesh.js +761 -0
- package/src/engine/utils/obj/_old_js/utils.js +647 -0
- package/src/engine/utils/obj/index.js +24 -0
- package/src/engine/utils/obj/js/index.js +277 -0
- package/src/engine/utils/obj/js/loader.js +232 -0
- package/src/engine/utils/obj/layout.js +246 -0
- package/src/engine/utils/obj/material.js +665 -0
- package/src/engine/utils/obj/mesh.js +657 -0
- package/src/engine/utils/obj/ts/index.ts +72 -0
- package/src/engine/utils/obj/ts/layout.ts +265 -0
- package/src/engine/utils/obj/ts/material.ts +760 -0
- package/src/engine/utils/obj/ts/mesh.ts +785 -0
- package/src/engine/utils/obj/ts/utils.ts +501 -0
- package/src/engine/utils/obj/utils.js +428 -0
- package/src/engine/utils/resources.js +18 -0
- package/src/index.jsx +55 -0
- package/src/spritz/player.js +18 -0
- package/src/spritz/readme.md +18 -0
- package/LICENSE +0 -437
- package/dist/bundle.js.LICENSE.txt +0 -31
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* *\
|
|
2
|
+
** ----------------------------------------------- **
|
|
3
|
+
** Calliope - Pixos Game Engine **
|
|
4
|
+
** ----------------------------------------------- **
|
|
5
|
+
** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
|
|
6
|
+
** **
|
|
7
|
+
** Any unauthorized distribution or transfer **
|
|
8
|
+
** of this work is strictly prohibited. **
|
|
9
|
+
** **
|
|
10
|
+
** All Rights Reserved. **
|
|
11
|
+
** ----------------------------------------------- **
|
|
12
|
+
\* */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ActionQueue - Manages a queue of actions or events for sequential execution.
|
|
16
|
+
*/
|
|
17
|
+
export default class ActionQueue {
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of ActionQueue.
|
|
20
|
+
*/
|
|
21
|
+
constructor() {
|
|
22
|
+
/** @type {Array<Function>} */
|
|
23
|
+
this.actions = [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Adds an action to the queue.
|
|
28
|
+
* @param {Function} action - The action function to add.
|
|
29
|
+
*/
|
|
30
|
+
add(action) {
|
|
31
|
+
this.actions.push(action);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runs all actions in the queue, filtering out completed ones.
|
|
36
|
+
* @param {...any} args - Arguments to pass to each action.
|
|
37
|
+
*/
|
|
38
|
+
run() {
|
|
39
|
+
let args = arguments;
|
|
40
|
+
this.actions = this.actions.filter((action) => {
|
|
41
|
+
return action(args);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* *\
|
|
2
|
+
** ----------------------------------------------- **
|
|
3
|
+
** Calliope - Pixos Game Engine **
|
|
4
|
+
** ----------------------------------------------- **
|
|
5
|
+
** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
|
|
6
|
+
** **
|
|
7
|
+
** Any unauthorized distribution or transfer **
|
|
8
|
+
** of this work is strictly prohibited. **
|
|
9
|
+
** **
|
|
10
|
+
** All Rights Reserved. **
|
|
11
|
+
** ----------------------------------------------- **
|
|
12
|
+
\* */
|
|
13
|
+
/**
|
|
14
|
+
* Loadable - Base class for objects that can be loaded asynchronously, with support for queuing actions until loaded.
|
|
15
|
+
*/
|
|
16
|
+
export default class Loadable {
|
|
17
|
+
/**
|
|
18
|
+
* Runs an action immediately if loaded, otherwise adds it to the onLoadActions queue.
|
|
19
|
+
* @param {Function} action - The action to run or queue.
|
|
20
|
+
*/
|
|
21
|
+
runWhenLoaded(action) {
|
|
22
|
+
if (this.loaded) action();
|
|
23
|
+
else this.onLoadActions.add(action);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Updates the object by assigning new properties.
|
|
28
|
+
* @param {*} data - The data object to merge into this instance.
|
|
29
|
+
*/
|
|
30
|
+
update(data) {
|
|
31
|
+
Object.assign(this, data);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/* *\
|
|
2
|
+
** ----------------------------------------------- **
|
|
3
|
+
** Calliope - Pixos Game Engine **
|
|
4
|
+
** ----------------------------------------------- **
|
|
5
|
+
** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
|
|
6
|
+
** **
|
|
7
|
+
** Any unauthorized distribution or transfer **
|
|
8
|
+
** of this work is strictly prohibited. **
|
|
9
|
+
** **
|
|
10
|
+
** All Rights Reserved. **
|
|
11
|
+
** ----------------------------------------------- **
|
|
12
|
+
\* */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CameraEffects - Visual effects system for the camera.
|
|
16
|
+
* Includes screen shake, smooth follow, and other camera-based effects.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Vector } from '../../utils/math/vector.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Easing functions for smooth animations
|
|
23
|
+
*/
|
|
24
|
+
const Easing = {
|
|
25
|
+
linear: t => t,
|
|
26
|
+
easeInQuad: t => t * t,
|
|
27
|
+
easeOutQuad: t => t * (2 - t),
|
|
28
|
+
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
29
|
+
easeInCubic: t => t * t * t,
|
|
30
|
+
easeOutCubic: t => (--t) * t * t + 1,
|
|
31
|
+
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
|
|
32
|
+
easeOutElastic: t => {
|
|
33
|
+
const p = 0.3;
|
|
34
|
+
return Math.pow(2, -10 * t) * Math.sin((t - p / 4) * (2 * Math.PI) / p) + 1;
|
|
35
|
+
},
|
|
36
|
+
easeOutBounce: t => {
|
|
37
|
+
if (t < 1 / 2.75) return 7.5625 * t * t;
|
|
38
|
+
if (t < 2 / 2.75) return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
39
|
+
if (t < 2.5 / 2.75) return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
40
|
+
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* CameraEffects - Manages camera visual effects
|
|
46
|
+
*/
|
|
47
|
+
export default class CameraEffects {
|
|
48
|
+
/**
|
|
49
|
+
* @param {Object} camera - Reference to the Camera instance
|
|
50
|
+
*/
|
|
51
|
+
constructor(camera) {
|
|
52
|
+
this.camera = camera;
|
|
53
|
+
|
|
54
|
+
// Screen shake state
|
|
55
|
+
this.shake = {
|
|
56
|
+
active: false,
|
|
57
|
+
intensity: 0,
|
|
58
|
+
decay: 0.9,
|
|
59
|
+
duration: 0,
|
|
60
|
+
elapsed: 0,
|
|
61
|
+
offset: new Vector(0, 0, 0),
|
|
62
|
+
type: 'random', // 'random', 'horizontal', 'vertical', 'rotational'
|
|
63
|
+
frequency: 1,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Smooth follow state
|
|
67
|
+
this.follow = {
|
|
68
|
+
active: false,
|
|
69
|
+
target: null,
|
|
70
|
+
offset: new Vector(0, 0, 0),
|
|
71
|
+
smoothness: 0.1, // 0-1, lower = smoother
|
|
72
|
+
deadzone: new Vector(0, 0, 0),
|
|
73
|
+
bounds: null, // { min: Vector, max: Vector }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Zoom effect state
|
|
77
|
+
this.zoom = {
|
|
78
|
+
active: false,
|
|
79
|
+
targetZoom: 1,
|
|
80
|
+
currentZoom: 1,
|
|
81
|
+
speed: 0.1,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Flash effect state
|
|
85
|
+
this.flash = {
|
|
86
|
+
active: false,
|
|
87
|
+
color: [1, 1, 1, 1],
|
|
88
|
+
duration: 0,
|
|
89
|
+
elapsed: 0,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Fade effect state
|
|
93
|
+
this.fade = {
|
|
94
|
+
active: false,
|
|
95
|
+
color: [0, 0, 0, 1],
|
|
96
|
+
targetAlpha: 0,
|
|
97
|
+
currentAlpha: 0,
|
|
98
|
+
duration: 0,
|
|
99
|
+
elapsed: 0,
|
|
100
|
+
easing: 'easeInOutQuad',
|
|
101
|
+
onComplete: null,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Punch effect (quick offset then return)
|
|
105
|
+
this.punch = {
|
|
106
|
+
active: false,
|
|
107
|
+
direction: new Vector(0, 0, 0),
|
|
108
|
+
intensity: 0,
|
|
109
|
+
duration: 0,
|
|
110
|
+
elapsed: 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update all active effects (call once per frame)
|
|
116
|
+
* @param {number} deltaTime - Time since last frame in seconds
|
|
117
|
+
*/
|
|
118
|
+
update(deltaTime) {
|
|
119
|
+
this._updateShake(deltaTime);
|
|
120
|
+
this._updateFollow(deltaTime);
|
|
121
|
+
this._updateZoom(deltaTime);
|
|
122
|
+
this._updateFlash(deltaTime);
|
|
123
|
+
this._updateFade(deltaTime);
|
|
124
|
+
this._updatePunch(deltaTime);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ===================== SCREEN SHAKE =====================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Start a screen shake effect
|
|
131
|
+
* @param {Object} options - Shake configuration
|
|
132
|
+
* @param {number} options.intensity - Shake intensity (pixels/units)
|
|
133
|
+
* @param {number} options.duration - Duration in seconds
|
|
134
|
+
* @param {number} [options.decay=0.9] - Decay rate per frame (0-1)
|
|
135
|
+
* @param {string} [options.type='random'] - Shake type
|
|
136
|
+
* @param {number} [options.frequency=1] - Shake frequency multiplier
|
|
137
|
+
*/
|
|
138
|
+
startShake(options) {
|
|
139
|
+
this.shake.active = true;
|
|
140
|
+
this.shake.intensity = options.intensity || 1;
|
|
141
|
+
this.shake.duration = options.duration || 0.5;
|
|
142
|
+
this.shake.elapsed = 0;
|
|
143
|
+
this.shake.decay = options.decay ?? 0.9;
|
|
144
|
+
this.shake.type = options.type || 'random';
|
|
145
|
+
this.shake.frequency = options.frequency || 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Stop the current screen shake
|
|
150
|
+
*/
|
|
151
|
+
stopShake() {
|
|
152
|
+
this.shake.active = false;
|
|
153
|
+
this.shake.offset = new Vector(0, 0, 0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get current shake offset (apply to camera position)
|
|
158
|
+
* @returns {Vector}
|
|
159
|
+
*/
|
|
160
|
+
getShakeOffset() {
|
|
161
|
+
return this.shake.offset;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_updateShake(deltaTime) {
|
|
165
|
+
if (!this.shake.active) return;
|
|
166
|
+
|
|
167
|
+
this.shake.elapsed += deltaTime;
|
|
168
|
+
|
|
169
|
+
if (this.shake.elapsed >= this.shake.duration) {
|
|
170
|
+
this.stopShake();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Calculate current intensity with decay
|
|
175
|
+
const progress = this.shake.elapsed / this.shake.duration;
|
|
176
|
+
const currentIntensity = this.shake.intensity * (1 - progress) * this.shake.decay;
|
|
177
|
+
|
|
178
|
+
// Generate shake offset based on type
|
|
179
|
+
const time = this.shake.elapsed * this.shake.frequency * 10;
|
|
180
|
+
|
|
181
|
+
switch (this.shake.type) {
|
|
182
|
+
case 'horizontal':
|
|
183
|
+
this.shake.offset = new Vector(
|
|
184
|
+
(Math.sin(time) * 2 - 1) * currentIntensity,
|
|
185
|
+
0,
|
|
186
|
+
0
|
|
187
|
+
);
|
|
188
|
+
break;
|
|
189
|
+
case 'vertical':
|
|
190
|
+
this.shake.offset = new Vector(
|
|
191
|
+
0,
|
|
192
|
+
(Math.sin(time) * 2 - 1) * currentIntensity,
|
|
193
|
+
0
|
|
194
|
+
);
|
|
195
|
+
break;
|
|
196
|
+
case 'rotational':
|
|
197
|
+
// Apply rotation shake (stored as z offset for now)
|
|
198
|
+
this.shake.offset = new Vector(
|
|
199
|
+
0,
|
|
200
|
+
0,
|
|
201
|
+
(Math.sin(time) * 2 - 1) * currentIntensity * 0.1
|
|
202
|
+
);
|
|
203
|
+
break;
|
|
204
|
+
case 'random':
|
|
205
|
+
default:
|
|
206
|
+
this.shake.offset = new Vector(
|
|
207
|
+
(Math.random() * 2 - 1) * currentIntensity,
|
|
208
|
+
(Math.random() * 2 - 1) * currentIntensity,
|
|
209
|
+
0
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ===================== SMOOTH FOLLOW =====================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Start smooth camera follow
|
|
219
|
+
* @param {Object} target - Object to follow (must have position property)
|
|
220
|
+
* @param {Object} options - Follow configuration
|
|
221
|
+
* @param {Vector} [options.offset] - Offset from target
|
|
222
|
+
* @param {number} [options.smoothness=0.1] - Follow smoothness (0-1)
|
|
223
|
+
* @param {Vector} [options.deadzone] - Deadzone before camera moves
|
|
224
|
+
* @param {{min: Vector, max: Vector}} [options.bounds] - Camera bounds
|
|
225
|
+
*/
|
|
226
|
+
startFollow(target, options = {}) {
|
|
227
|
+
this.follow.active = true;
|
|
228
|
+
this.follow.target = target;
|
|
229
|
+
this.follow.offset = options.offset || new Vector(0, 0, 0);
|
|
230
|
+
this.follow.smoothness = options.smoothness ?? 0.1;
|
|
231
|
+
this.follow.deadzone = options.deadzone || new Vector(0, 0, 0);
|
|
232
|
+
this.follow.bounds = options.bounds || null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Stop following
|
|
237
|
+
*/
|
|
238
|
+
stopFollow() {
|
|
239
|
+
this.follow.active = false;
|
|
240
|
+
this.follow.target = null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get the target position for smooth follow
|
|
245
|
+
* @returns {Vector|null}
|
|
246
|
+
*/
|
|
247
|
+
getFollowTarget() {
|
|
248
|
+
if (!this.follow.active || !this.follow.target) return null;
|
|
249
|
+
|
|
250
|
+
const targetPos = this.follow.target.position || this.follow.target;
|
|
251
|
+
return targetPos.add ? targetPos.add(this.follow.offset) :
|
|
252
|
+
new Vector(targetPos.x + this.follow.offset.x,
|
|
253
|
+
targetPos.y + this.follow.offset.y,
|
|
254
|
+
targetPos.z + this.follow.offset.z);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_updateFollow(deltaTime) {
|
|
258
|
+
if (!this.follow.active || !this.follow.target) return;
|
|
259
|
+
|
|
260
|
+
const targetPos = this.getFollowTarget();
|
|
261
|
+
if (!targetPos) return;
|
|
262
|
+
|
|
263
|
+
const currentPos = this.camera.cameraTarget || this.camera.cameraPosition;
|
|
264
|
+
|
|
265
|
+
// Calculate difference
|
|
266
|
+
let dx = targetPos.x - currentPos.x;
|
|
267
|
+
let dy = targetPos.y - currentPos.y;
|
|
268
|
+
let dz = targetPos.z - currentPos.z;
|
|
269
|
+
|
|
270
|
+
// Apply deadzone
|
|
271
|
+
if (Math.abs(dx) < this.follow.deadzone.x) dx = 0;
|
|
272
|
+
if (Math.abs(dy) < this.follow.deadzone.y) dy = 0;
|
|
273
|
+
if (Math.abs(dz) < this.follow.deadzone.z) dz = 0;
|
|
274
|
+
|
|
275
|
+
// Smooth interpolation
|
|
276
|
+
const smoothFactor = 1 - Math.pow(1 - this.follow.smoothness, deltaTime * 60);
|
|
277
|
+
|
|
278
|
+
let newX = currentPos.x + dx * smoothFactor;
|
|
279
|
+
let newY = currentPos.y + dy * smoothFactor;
|
|
280
|
+
let newZ = currentPos.z + dz * smoothFactor;
|
|
281
|
+
|
|
282
|
+
// Apply bounds
|
|
283
|
+
if (this.follow.bounds) {
|
|
284
|
+
newX = Math.max(this.follow.bounds.min.x, Math.min(this.follow.bounds.max.x, newX));
|
|
285
|
+
newY = Math.max(this.follow.bounds.min.y, Math.min(this.follow.bounds.max.y, newY));
|
|
286
|
+
newZ = Math.max(this.follow.bounds.min.z, Math.min(this.follow.bounds.max.z, newZ));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Update camera target
|
|
290
|
+
if (this.camera.setTarget) {
|
|
291
|
+
this.camera.setTarget(new Vector(newX, newY, newZ));
|
|
292
|
+
} else {
|
|
293
|
+
this.camera.cameraTarget = new Vector(newX, newY, newZ);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ===================== ZOOM EFFECT =====================
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Smoothly zoom to a target level
|
|
301
|
+
* @param {number} targetZoom - Target zoom level
|
|
302
|
+
* @param {number} [speed=0.1] - Zoom speed
|
|
303
|
+
*/
|
|
304
|
+
zoomTo(targetZoom, speed = 0.1) {
|
|
305
|
+
this.zoom.active = true;
|
|
306
|
+
this.zoom.targetZoom = targetZoom;
|
|
307
|
+
this.zoom.speed = speed;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_updateZoom(deltaTime) {
|
|
311
|
+
if (!this.zoom.active) return;
|
|
312
|
+
|
|
313
|
+
const diff = this.zoom.targetZoom - this.zoom.currentZoom;
|
|
314
|
+
if (Math.abs(diff) < 0.001) {
|
|
315
|
+
this.zoom.currentZoom = this.zoom.targetZoom;
|
|
316
|
+
this.zoom.active = false;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.zoom.currentZoom += diff * this.zoom.speed * deltaTime * 60;
|
|
321
|
+
|
|
322
|
+
// Apply zoom to camera
|
|
323
|
+
if (this.camera.zoom) {
|
|
324
|
+
this.camera.cameraDistance = 15 / this.zoom.currentZoom;
|
|
325
|
+
this.camera.updateViewFromAngles?.();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ===================== FLASH EFFECT =====================
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Flash the screen with a color
|
|
333
|
+
* @param {Object} options - Flash configuration
|
|
334
|
+
* @param {number[]} [options.color=[1,1,1,1]] - RGBA color
|
|
335
|
+
* @param {number} [options.duration=0.1] - Duration in seconds
|
|
336
|
+
*/
|
|
337
|
+
flash(options = {}) {
|
|
338
|
+
this.flash.active = true;
|
|
339
|
+
this.flash.color = options.color || [1, 1, 1, 1];
|
|
340
|
+
this.flash.duration = options.duration || 0.1;
|
|
341
|
+
this.flash.elapsed = 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get flash overlay color (apply as post-process)
|
|
346
|
+
* @returns {number[]|null} RGBA color or null if not flashing
|
|
347
|
+
*/
|
|
348
|
+
getFlashColor() {
|
|
349
|
+
if (!this.flash.active) return null;
|
|
350
|
+
|
|
351
|
+
const progress = this.flash.elapsed / this.flash.duration;
|
|
352
|
+
const alpha = this.flash.color[3] * (1 - progress);
|
|
353
|
+
return [this.flash.color[0], this.flash.color[1], this.flash.color[2], alpha];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
_updateFlash(deltaTime) {
|
|
357
|
+
if (!this.flash.active) return;
|
|
358
|
+
|
|
359
|
+
this.flash.elapsed += deltaTime;
|
|
360
|
+
if (this.flash.elapsed >= this.flash.duration) {
|
|
361
|
+
this.flash.active = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ===================== FADE EFFECT =====================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Fade the screen to/from a color
|
|
369
|
+
* @param {Object} options - Fade configuration
|
|
370
|
+
* @param {number} options.targetAlpha - Target alpha (0 = transparent, 1 = opaque)
|
|
371
|
+
* @param {number} [options.duration=1] - Duration in seconds
|
|
372
|
+
* @param {number[]} [options.color=[0,0,0,1]] - RGB color
|
|
373
|
+
* @param {string} [options.easing='easeInOutQuad'] - Easing function
|
|
374
|
+
* @param {Function} [options.onComplete] - Callback when complete
|
|
375
|
+
*/
|
|
376
|
+
fadeTo(options) {
|
|
377
|
+
this.fade.active = true;
|
|
378
|
+
this.fade.targetAlpha = options.targetAlpha;
|
|
379
|
+
this.fade.duration = options.duration || 1;
|
|
380
|
+
this.fade.elapsed = 0;
|
|
381
|
+
this.fade.color = options.color || [0, 0, 0, 1];
|
|
382
|
+
this.fade.easing = options.easing || 'easeInOutQuad';
|
|
383
|
+
this.fade.onComplete = options.onComplete || null;
|
|
384
|
+
this.fade.startAlpha = this.fade.currentAlpha;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Convenience method for fade to black
|
|
389
|
+
*/
|
|
390
|
+
fadeToBlack(duration = 1, onComplete = null) {
|
|
391
|
+
this.fadeTo({ targetAlpha: 1, duration, color: [0, 0, 0, 1], onComplete });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Convenience method for fade from black
|
|
396
|
+
*/
|
|
397
|
+
fadeFromBlack(duration = 1, onComplete = null) {
|
|
398
|
+
this.fade.currentAlpha = 1;
|
|
399
|
+
this.fadeTo({ targetAlpha: 0, duration, color: [0, 0, 0, 1], onComplete });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get fade overlay color
|
|
404
|
+
* @returns {number[]|null}
|
|
405
|
+
*/
|
|
406
|
+
getFadeColor() {
|
|
407
|
+
if (this.fade.currentAlpha <= 0) return null;
|
|
408
|
+
return [this.fade.color[0], this.fade.color[1], this.fade.color[2], this.fade.currentAlpha];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_updateFade(deltaTime) {
|
|
412
|
+
if (!this.fade.active) return;
|
|
413
|
+
|
|
414
|
+
this.fade.elapsed += deltaTime;
|
|
415
|
+
|
|
416
|
+
if (this.fade.elapsed >= this.fade.duration) {
|
|
417
|
+
this.fade.currentAlpha = this.fade.targetAlpha;
|
|
418
|
+
this.fade.active = false;
|
|
419
|
+
if (this.fade.onComplete) {
|
|
420
|
+
this.fade.onComplete();
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const progress = this.fade.elapsed / this.fade.duration;
|
|
426
|
+
const easingFn = Easing[this.fade.easing] || Easing.linear;
|
|
427
|
+
const easedProgress = easingFn(progress);
|
|
428
|
+
|
|
429
|
+
const startAlpha = this.fade.startAlpha ?? 0;
|
|
430
|
+
this.fade.currentAlpha = startAlpha + (this.fade.targetAlpha - startAlpha) * easedProgress;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ===================== PUNCH EFFECT =====================
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Quick punch effect (offset then return)
|
|
437
|
+
* @param {Vector} direction - Punch direction
|
|
438
|
+
* @param {number} intensity - Punch intensity
|
|
439
|
+
* @param {number} duration - Duration in seconds
|
|
440
|
+
*/
|
|
441
|
+
punch(direction, intensity = 1, duration = 0.2) {
|
|
442
|
+
this.punch.active = true;
|
|
443
|
+
this.punch.direction = direction.normal ? direction.normal() : direction;
|
|
444
|
+
this.punch.intensity = intensity;
|
|
445
|
+
this.punch.duration = duration;
|
|
446
|
+
this.punch.elapsed = 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Get punch offset (apply to camera position)
|
|
451
|
+
* @returns {Vector}
|
|
452
|
+
*/
|
|
453
|
+
getPunchOffset() {
|
|
454
|
+
if (!this.punch.active) return new Vector(0, 0, 0);
|
|
455
|
+
|
|
456
|
+
const progress = this.punch.elapsed / this.punch.duration;
|
|
457
|
+
const easedProgress = Easing.easeOutElastic(progress);
|
|
458
|
+
const currentIntensity = this.punch.intensity * (1 - easedProgress);
|
|
459
|
+
|
|
460
|
+
return new Vector(
|
|
461
|
+
this.punch.direction.x * currentIntensity,
|
|
462
|
+
this.punch.direction.y * currentIntensity,
|
|
463
|
+
this.punch.direction.z * currentIntensity
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
_updatePunch(deltaTime) {
|
|
468
|
+
if (!this.punch.active) return;
|
|
469
|
+
|
|
470
|
+
this.punch.elapsed += deltaTime;
|
|
471
|
+
if (this.punch.elapsed >= this.punch.duration) {
|
|
472
|
+
this.punch.active = false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ===================== COMBINED OFFSET =====================
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get combined camera offset from all effects
|
|
480
|
+
* @returns {Vector}
|
|
481
|
+
*/
|
|
482
|
+
getTotalOffset() {
|
|
483
|
+
const shake = this.getShakeOffset();
|
|
484
|
+
const punch = this.getPunchOffset();
|
|
485
|
+
|
|
486
|
+
return new Vector(
|
|
487
|
+
shake.x + punch.x,
|
|
488
|
+
shake.y + punch.y,
|
|
489
|
+
shake.z + punch.z
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export { Easing };
|