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,540 @@
|
|
|
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
|
+
import Utils from '../utils/index.js';
|
|
15
|
+
import Database from './database/index.js';
|
|
16
|
+
import Store from './store/index.js';
|
|
17
|
+
import Hud from './hud/index.js';
|
|
18
|
+
import RenderManager from './render/manager.js';
|
|
19
|
+
import ResourceManager from './resource/manager.js';
|
|
20
|
+
import CutsceneManager from './cutscene/manager.js';
|
|
21
|
+
import ModeManager from './mode/manager.js'; // Import ModeManager
|
|
22
|
+
import InputManager from './input/manager.js'; // Import InputManager
|
|
23
|
+
import NetworkManager from './net/manager.js';
|
|
24
|
+
import { attachFlagDebugInfo, attachWebglDebugInfo, updateDebugInformation } from './debug/index.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} SpritzGame
|
|
28
|
+
* @property {function(GLEngine): Promise<void>} init - Initializes the game.
|
|
29
|
+
* @property {function(GLEngine, number): void} render - Renders the game scene.
|
|
30
|
+
* @property {function(number): void} update - Updates the game state.
|
|
31
|
+
* @property {import('./scene/world.js').default} world - The game world instance.
|
|
32
|
+
* @property {object} shaders - Shader programs for the game.
|
|
33
|
+
* @property {object} effects - Visual effects for the game.
|
|
34
|
+
* @property {boolean} loaded - Indicates if the game resources are loaded.
|
|
35
|
+
* @property {object} [manifest] - Game manifest with network settings.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Core Pixos Graphics & Game Engine.
|
|
40
|
+
* Orchestrates the main game loop, rendering, input handling, and resource management.
|
|
41
|
+
*/
|
|
42
|
+
export default class GLEngine {
|
|
43
|
+
/**
|
|
44
|
+
* Creates an instance of GLEngine.
|
|
45
|
+
* @param {HTMLCanvasElement} canvas - The main WebGL canvas.
|
|
46
|
+
* @param {HTMLCanvasElement} hudCanvas - The 2D canvas for HUD elements.
|
|
47
|
+
* @param {HTMLCanvasElement} mipmap - The canvas for mipmap generation (or similar utility).
|
|
48
|
+
* @param {HTMLCanvasElement} gamepadCanvas - The 2D canvas for mobile gamepad controls.
|
|
49
|
+
* @param {HTMLInputElement} fileUpload - The file input element for resource loading.
|
|
50
|
+
* @param {number} width - The desired width of the game viewport.
|
|
51
|
+
* @param {number} height - The desired height of the game viewport.
|
|
52
|
+
*/
|
|
53
|
+
constructor(canvas, hudCanvas, mipmap, gamepadCanvas, fileUpload, width, height) {
|
|
54
|
+
/** @type {HTMLCanvasElement} */
|
|
55
|
+
this.canvas = canvas;
|
|
56
|
+
/** @type {HTMLCanvasElement} */
|
|
57
|
+
this.hudCanvas = hudCanvas;
|
|
58
|
+
/** @type {HTMLCanvasElement} */
|
|
59
|
+
this.gamepadCanvas = gamepadCanvas;
|
|
60
|
+
/** @type {HTMLCanvasElement} */
|
|
61
|
+
this.mipmap = mipmap;
|
|
62
|
+
|
|
63
|
+
/** @type {HTMLInputElement} */
|
|
64
|
+
this.fileUpload = fileUpload;
|
|
65
|
+
|
|
66
|
+
/** @type {number} */
|
|
67
|
+
this.width = width;
|
|
68
|
+
/** @type {number} */
|
|
69
|
+
this.height = height;
|
|
70
|
+
|
|
71
|
+
/** @type {object} */
|
|
72
|
+
this.utils = Utils;
|
|
73
|
+
|
|
74
|
+
/** @type {NetworkManager} */
|
|
75
|
+
this.networkManager = new NetworkManager(this);
|
|
76
|
+
// ResourceManager and RenderManager depend on a valid WebGL context
|
|
77
|
+
// and are initialized in `init()` after `this.gl` is created.
|
|
78
|
+
/** @type {ResourceManager|null} */
|
|
79
|
+
this.resourceManager = null;
|
|
80
|
+
|
|
81
|
+
/** @type {RenderManager|null} */
|
|
82
|
+
this.renderManager = null;
|
|
83
|
+
|
|
84
|
+
/** @type {Hud} */
|
|
85
|
+
this.hud = new Hud(this);
|
|
86
|
+
|
|
87
|
+
/** @type {InputManager} */
|
|
88
|
+
this.inputManager = new InputManager(this); // Initialize InputManager
|
|
89
|
+
|
|
90
|
+
/** @type {SpeechSynthesisUtterance} */
|
|
91
|
+
this.voice = new SpeechSynthesisUtterance();
|
|
92
|
+
|
|
93
|
+
/** @type {Database} */
|
|
94
|
+
this.database = new Database();
|
|
95
|
+
|
|
96
|
+
/** @type {Store} */
|
|
97
|
+
this.store = new Store();
|
|
98
|
+
|
|
99
|
+
/** @type {CutsceneManager} */
|
|
100
|
+
this.cutsceneManager = new CutsceneManager(this);
|
|
101
|
+
|
|
102
|
+
/** @type {ModeManager} */
|
|
103
|
+
this.modeManager = new ModeManager(this); // Initialize ModeManager
|
|
104
|
+
|
|
105
|
+
// Debug flags
|
|
106
|
+
/** @type {boolean} */
|
|
107
|
+
this.debug = false; // General debug mode (enables console logs)
|
|
108
|
+
/** @type {boolean} */
|
|
109
|
+
this.debugHeightOverlay = false; // Height debug overlay (shows z values on screen)
|
|
110
|
+
|
|
111
|
+
// Game Loop
|
|
112
|
+
/** @type {boolean} */
|
|
113
|
+
this.running = false;
|
|
114
|
+
|
|
115
|
+
/** @type {WebGL2RenderingContext|null} */
|
|
116
|
+
this.gl = null;
|
|
117
|
+
/** @type {CanvasRenderingContext2D|null} */
|
|
118
|
+
this.ctx = null;
|
|
119
|
+
/** @type {CanvasRenderingContext2D|null} */
|
|
120
|
+
this.gp = null;
|
|
121
|
+
/** @type {number} */
|
|
122
|
+
this.frameCount = 0;
|
|
123
|
+
/** @type {SpritzGame|null} */
|
|
124
|
+
this.spritz = null;
|
|
125
|
+
/** @type {boolean} */
|
|
126
|
+
this.fullscreen = false;
|
|
127
|
+
/** @type {number} */
|
|
128
|
+
this.time = 0;
|
|
129
|
+
/** @type {number|null} */
|
|
130
|
+
this.requestId = null; // For requestAnimationFrame
|
|
131
|
+
|
|
132
|
+
// Bind methods to the instance
|
|
133
|
+
this.screenSize = this.screenSize.bind(this);
|
|
134
|
+
this.render = this.render.bind(this);
|
|
135
|
+
this.init = this.init.bind(this);
|
|
136
|
+
this.close = this.close.bind(this);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Initializes the game engine and the Spritz game instance.
|
|
141
|
+
* @param {SpritzGame} spritz - The Spritz game object to initialize.
|
|
142
|
+
* @returns {Promise<void>} A promise that resolves when initialization is complete.
|
|
143
|
+
* @throws {Error} If WebGL, HUD canvas, or Gamepad canvas cannot be initialized.
|
|
144
|
+
*/
|
|
145
|
+
async init(spritz) {
|
|
146
|
+
/** @type {CanvasRenderingContext2D|null} */
|
|
147
|
+
const ctx = this.hudCanvas.getContext('2d');
|
|
148
|
+
/** @type {WebGL2RenderingContext|null} */
|
|
149
|
+
const gl = this.canvas.getContext('webgl2', {
|
|
150
|
+
antialias: true,
|
|
151
|
+
depth: true,
|
|
152
|
+
preserveDrawingBuffer: false
|
|
153
|
+
});
|
|
154
|
+
/** @type {CanvasRenderingContext2D|null} */
|
|
155
|
+
const gp = this.gamepadCanvas.getContext('2d');
|
|
156
|
+
|
|
157
|
+
if (!gl) {
|
|
158
|
+
throw new Error('WebGL: unable to initialize');
|
|
159
|
+
}
|
|
160
|
+
if (!ctx) {
|
|
161
|
+
throw new Error('Canvas: unable to initialize HUD');
|
|
162
|
+
}
|
|
163
|
+
if (!gp) {
|
|
164
|
+
throw new Error('Gamepad: unable to initialize Mobile Canvas');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Make HUD same size as canvas
|
|
168
|
+
ctx.canvas.width = gl.canvas.clientWidth;
|
|
169
|
+
ctx.canvas.height = gl.canvas.clientHeight;
|
|
170
|
+
|
|
171
|
+
/** @type {WebGL2RenderingContext} */
|
|
172
|
+
this.gl = gl;
|
|
173
|
+
/** @type {CanvasRenderingContext2D} */
|
|
174
|
+
this.ctx = ctx;
|
|
175
|
+
/** @type {CanvasRenderingContext2D} */
|
|
176
|
+
this.gp = gp;
|
|
177
|
+
this.frameCount = 0;
|
|
178
|
+
|
|
179
|
+
this.spritz = spritz;
|
|
180
|
+
this.fullscreen = false;
|
|
181
|
+
|
|
182
|
+
// Initial time
|
|
183
|
+
this.time = new Date().getTime();
|
|
184
|
+
|
|
185
|
+
// Init Input Manager
|
|
186
|
+
this.inputManager.init();
|
|
187
|
+
|
|
188
|
+
// Initialize HUD
|
|
189
|
+
this.hud.init();
|
|
190
|
+
|
|
191
|
+
// Initialize render manager and resource manager now that `this.gl` exists
|
|
192
|
+
if (!this.resourceManager) {
|
|
193
|
+
this.resourceManager = new ResourceManager(this);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!this.renderManager) {
|
|
197
|
+
this.renderManager = new RenderManager(this);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.renderManager.init();
|
|
201
|
+
|
|
202
|
+
// Configure Gamepad & touch - now handled through InputManager
|
|
203
|
+
// Direct access deprecated, use inputManager instead
|
|
204
|
+
/** @deprecated Use inputManager.gamepad instead. */
|
|
205
|
+
this.gamepad = this.inputManager.gamepad;
|
|
206
|
+
/** @deprecated Use inputManager.keyboard instead. */
|
|
207
|
+
this.keyboard = this.inputManager.keyboard;
|
|
208
|
+
/** @deprecated Use inputManager.mouse instead. */
|
|
209
|
+
this.mouse = this.inputManager.mouse;
|
|
210
|
+
/** @deprecated Use inputManager.touch instead. */
|
|
211
|
+
this.touch = this.inputManager.touch;
|
|
212
|
+
|
|
213
|
+
/** @deprecated Eventually move this into inputManager.touch instead. */
|
|
214
|
+
this.touchHandler = this.gamepad.listen.bind(this.gamepad);
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
// Initialize network if enabled
|
|
218
|
+
if (spritz.manifest?.network?.enabled) {
|
|
219
|
+
await this.networkManager.connect(spritz.manifest.network.url);
|
|
220
|
+
if (spritz.manifest.network.authority) {
|
|
221
|
+
this.networkManager.setAuthority(spritz.manifest.network.authority);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Initialize Spritz game
|
|
226
|
+
await spritz.init(this);
|
|
227
|
+
|
|
228
|
+
// Create and configure debug overlays. These overlays display
|
|
229
|
+
// performance information such as FPS and draw counts (toggled by F3)
|
|
230
|
+
// and flag information (toggled by F4). They are appended to the document body
|
|
231
|
+
// once the engine has been initialized and the DOM is available. The overlays
|
|
232
|
+
// remain hidden until toggled.
|
|
233
|
+
attachWebglDebugInfo(this);
|
|
234
|
+
attachFlagDebugInfo(this);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The main render loop for the game engine.
|
|
239
|
+
* Called continuously via `requestAnimationFrame` to update and draw the game.
|
|
240
|
+
* Handles debug counters, clears canvases, updates game state, renders the scene, and manages transitions.
|
|
241
|
+
*/
|
|
242
|
+
render() {
|
|
243
|
+
this.frameCount++;
|
|
244
|
+
// Reset debug counters at the start of each frame so that metrics
|
|
245
|
+
// reflect only the current frame's draw calls.
|
|
246
|
+
if (this.renderManager && this.renderManager.resetDebugCounters) {
|
|
247
|
+
this.renderManager.resetDebugCounters();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Clear canvases
|
|
251
|
+
this.hud.clearHud();
|
|
252
|
+
// Draw active mode label (if any)
|
|
253
|
+
if (this.hud.drawModeLabel) this.hud.drawModeLabel();
|
|
254
|
+
this.renderManager.clearScreen();
|
|
255
|
+
|
|
256
|
+
const timestamp = new Date().getTime();
|
|
257
|
+
|
|
258
|
+
// Update Input Manager
|
|
259
|
+
this.inputManager.update();
|
|
260
|
+
|
|
261
|
+
// Object picking pass (for selection) - only if mode has picker enabled
|
|
262
|
+
if (this.modeManager.hasPicker()) {
|
|
263
|
+
// Enable picker shader (Todo - Improve performance - make it only 1x1 pixel framebuffer - and avoid needing to reclear screen).
|
|
264
|
+
this.renderManager.activatePickerShaderProgram(false);
|
|
265
|
+
this.spritz.render(this, timestamp); // Render scene for picking pass
|
|
266
|
+
// Read pixel data immediately after picking render, before clearing screen
|
|
267
|
+
this.getSelectedObject('sprite|object|tile', false);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Update and render based on the active game mode
|
|
271
|
+
if (!this.inputManager.handleInput(timestamp)) {
|
|
272
|
+
// If mode doesn't handle input, do default update
|
|
273
|
+
this.spritz.update(timestamp);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Sync input mode with game mode
|
|
277
|
+
const currentMode = this.modeManager.getMode();
|
|
278
|
+
if (currentMode && this.inputManager.getMode() !== currentMode) {
|
|
279
|
+
this.inputManager.setMode(currentMode);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Core render loop (actually render scene to screen)
|
|
283
|
+
const gl = this.renderManager.engine.gl;
|
|
284
|
+
this.renderManager.clearScreen();
|
|
285
|
+
// Draw skybox first, with depth writes disabled
|
|
286
|
+
gl.depthMask(false);
|
|
287
|
+
this.renderManager.renderSkybox();
|
|
288
|
+
gl.depthMask(true);
|
|
289
|
+
// Now draw world tiles/objects, then sprites
|
|
290
|
+
this.renderManager.activateShaderProgram();
|
|
291
|
+
|
|
292
|
+
this.modeManager.update(timestamp); // Update active mode
|
|
293
|
+
|
|
294
|
+
// Allow particle system to update physics with a stable timestamp
|
|
295
|
+
if (this.renderManager && this.renderManager.updateParticles) {
|
|
296
|
+
try { this.renderManager.updateParticles(timestamp); } catch (e) { console.warn('updateParticles failed', e); }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.spritz.render(this); // Render scene (might be overridden by mode)
|
|
300
|
+
|
|
301
|
+
this.cutsceneManager.update(); // Update cutscene (if applicable)
|
|
302
|
+
this.renderManager.updateTransition(); // Update transitions
|
|
303
|
+
|
|
304
|
+
// Render particles after main scene but before HUD/gamepad
|
|
305
|
+
if (this.renderManager && this.renderManager.renderParticles) {
|
|
306
|
+
try { this.renderManager.renderParticles(); } catch (e) { console.warn('renderParticles failed', e); }
|
|
307
|
+
}
|
|
308
|
+
this.gamepad.render(); // Render gamepad (may be optimizable?)
|
|
309
|
+
|
|
310
|
+
// Draw height debug overlay if enabled (shows tile/sprite/object z values on screen)
|
|
311
|
+
if (this.debugHeightOverlay && this.hud.drawHeightDebugOverlay) {
|
|
312
|
+
try { this.hud.drawHeightDebugOverlay(); } catch (e) { console.warn('drawHeightDebugOverlay failed', e); }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Update debug overlay if enabled
|
|
316
|
+
updateDebugInformation(this);
|
|
317
|
+
|
|
318
|
+
this.requestId = requestAnimationFrame(this.render);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Stops the main render loop.
|
|
323
|
+
*/
|
|
324
|
+
close() {
|
|
325
|
+
if (this.requestId) {
|
|
326
|
+
cancelAnimationFrame(this.requestId);
|
|
327
|
+
this.requestId = null;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Detects and returns the selected object on screen based on mouse/touch input.
|
|
333
|
+
* Uses a color-picking technique where objects are rendered with unique IDs to an off-screen buffer,
|
|
334
|
+
* and the pixel under the cursor is read to identify the object.
|
|
335
|
+
* @param {'sprite'|'object'|'tile'|string} [type='sprite|object|tile'] - The type(s) of objects to consider for selection, pipe-separated.
|
|
336
|
+
* @param {boolean} [useFrustum=false] - Whether to use a 1x1 pixel frustum for picking (performance optimization).
|
|
337
|
+
* @returns {number|null} The ID of the selected object, or null if no object is selected or freecam is active.
|
|
338
|
+
*/
|
|
339
|
+
getSelectedObject(type = 'sprite|object|tile', useFrustum = false) {
|
|
340
|
+
// When FreeCam is active, suppress picking to avoid interfering with camera controls
|
|
341
|
+
if (this._freecamActive) return null;
|
|
342
|
+
if (this.spritz.world?.spriteList?.length <= 0 && this.spritz.world?.objectList?.length <= 0 && this.spritz.world?.zoneList?.length <= 0) {
|
|
343
|
+
return null; // No pickable objects in the scene
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const gl = this.gl;
|
|
347
|
+
const data = new Uint8Array(4);
|
|
348
|
+
const mouseX = this.gamepad.x || 0;
|
|
349
|
+
const mouseY = this.gamepad.y || 0;
|
|
350
|
+
const pixelX = useFrustum ? 0 : (mouseX * gl.canvas.width) / gl.canvas.clientWidth;
|
|
351
|
+
const pixelY = useFrustum ? 0 : gl.canvas.height - (mouseY * gl.canvas.height) / gl.canvas.clientHeight - 1;
|
|
352
|
+
|
|
353
|
+
gl.readPixels(
|
|
354
|
+
pixelX, // x
|
|
355
|
+
pixelY, // y
|
|
356
|
+
1, // width
|
|
357
|
+
1, // height
|
|
358
|
+
gl.RGBA, // format
|
|
359
|
+
gl.UNSIGNED_BYTE, // type
|
|
360
|
+
data
|
|
361
|
+
); // typed array to hold result
|
|
362
|
+
|
|
363
|
+
let id = data[0] + (data[1] << 8) + (data[2] << 16);
|
|
364
|
+
|
|
365
|
+
// Only process selection if a left click occurred this frame
|
|
366
|
+
if (!this.inputManager.isActionPressed('select')) {
|
|
367
|
+
return id;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Select type(s) based on request
|
|
371
|
+
type.split('|').forEach((t) => {
|
|
372
|
+
switch (t) {
|
|
373
|
+
case 'sprite':
|
|
374
|
+
this.spritz.world.spriteList = this.spritz.world.spriteList.map((sprite) => {
|
|
375
|
+
if (sprite.objId === id) {
|
|
376
|
+
sprite.isSelected = true;
|
|
377
|
+
if (this.spritz.world.spriteDict[sprite.id]) {
|
|
378
|
+
this.spritz.world.spriteDict[sprite.id].isSelected = true;
|
|
379
|
+
// Allow mode to handle selection first
|
|
380
|
+
if (!this.modeManager.handleSelect(sprite.zone, sprite, null, 'sprite')) {
|
|
381
|
+
// TODO: Add a new trigger method onSelect()
|
|
382
|
+
if (typeof this.spritz.world.spriteDict[sprite.id].onSelect === 'function') {
|
|
383
|
+
this.spritz.world.spriteDict[sprite.id].onSelect(sprite.zone, sprite);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
sprite.isSelected = false; // Deselect others
|
|
389
|
+
}
|
|
390
|
+
return sprite;
|
|
391
|
+
});
|
|
392
|
+
break;
|
|
393
|
+
case 'object':
|
|
394
|
+
this.spritz.world.objectList = this.spritz.world.objectList.map((obj) => {
|
|
395
|
+
if (obj.objId === id) {
|
|
396
|
+
obj.isSelected = true;
|
|
397
|
+
if (this.spritz.world.objectDict[obj.id]) {
|
|
398
|
+
this.spritz.world.objectDict[obj.id].isSelected = true;
|
|
399
|
+
// Allow mode to handle selection first
|
|
400
|
+
if (!this.modeManager.handleSelect(obj.zone, obj, null, 'object')) {
|
|
401
|
+
// TODO: Add a new trigger method onSelect()
|
|
402
|
+
if (typeof this.spritz.world.objectDict[obj.id].onSelect === 'function') {
|
|
403
|
+
this.spritz.world.objectDict[obj.id].onSelect(obj.zone, obj);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
obj.isSelected = false; // Deselect others
|
|
409
|
+
}
|
|
410
|
+
return obj;
|
|
411
|
+
});
|
|
412
|
+
break;
|
|
413
|
+
case 'tile':
|
|
414
|
+
// Read in zone, tile, and cell data from pixel
|
|
415
|
+
let zoneObjId = data[0];
|
|
416
|
+
let row = data[1];
|
|
417
|
+
let cell = data[2];
|
|
418
|
+
|
|
419
|
+
// Search zones and find selected tile
|
|
420
|
+
this.spritz.world.zoneList.forEach((zone) => {
|
|
421
|
+
if (zone.objId === zoneObjId) {
|
|
422
|
+
// Allow mode to handle selection first
|
|
423
|
+
if (!this.modeManager.handleSelect(zone, row, cell, 'tile')) {
|
|
424
|
+
if (typeof zone.onSelect === 'function') {
|
|
425
|
+
zone.onSelect(row, cell);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
if (id !== 0) { // If a valid ID was picked (not background)
|
|
431
|
+
console.log('TILE SELECTION:', { zoneObjId, row, cell, zones: this.spritz.world.zoneList });
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return id;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Sets a greeting text.
|
|
442
|
+
* @deprecated This method should be moved to a more appropriate class, e.g., `Hud` or a new `DialogueManager`.
|
|
443
|
+
* @param {string} text - The greeting text to set.
|
|
444
|
+
*/
|
|
445
|
+
setGreeting(text) {
|
|
446
|
+
if (process.env.NODE_ENV === 'development') {
|
|
447
|
+
console.log('Setting GREETING:', text);
|
|
448
|
+
}
|
|
449
|
+
// Assuming globalStore exists and is the correct place for this
|
|
450
|
+
if (this.globalStore) {
|
|
451
|
+
this.globalStore.greeting = text;
|
|
452
|
+
} else {
|
|
453
|
+
console.warn('globalStore is not available to set greeting.');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Converts text to speech using the Web Speech API.
|
|
459
|
+
* @param {string} text - The text to speak.
|
|
460
|
+
* @param {SpeechSynthesisVoice|null} [voice=null] - The voice to use. Defaults to the first available voice.
|
|
461
|
+
* @param {string} [lang='en'] - The language of the speech.
|
|
462
|
+
* @param {number|null} [rate=null] - The speed of the speech (0.1 to 10).
|
|
463
|
+
* @param {number|null} [volume=null] - The volume of the speech (0 to 1).
|
|
464
|
+
* @param {number|null} [pitch=null] - The pitch of the speech (0 to 2).
|
|
465
|
+
*/
|
|
466
|
+
speechSynthesis(text, voice = null, lang = 'en', rate = null, volume = null, pitch = null) {
|
|
467
|
+
/** @type {SpeechSynthesisUtterance} */
|
|
468
|
+
let speech = this.voice;
|
|
469
|
+
/** @type {SpeechSynthesisVoice[]} */
|
|
470
|
+
let voices = window.speechSynthesis.getVoices() ?? [];
|
|
471
|
+
// Set voice
|
|
472
|
+
speech.voice = voice || voices[0];
|
|
473
|
+
if (rate) speech.rate = rate;
|
|
474
|
+
if (volume) speech.volume = volume;
|
|
475
|
+
if (pitch) speech.pitch = pitch;
|
|
476
|
+
speech.text = text;
|
|
477
|
+
speech.lang = lang;
|
|
478
|
+
// Speak
|
|
479
|
+
window.speechSynthesis.speak(speech);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Returns the current client width and height of the main canvas.
|
|
485
|
+
* @returns {{width: number, height: number}} An object containing the width and height.
|
|
486
|
+
*/
|
|
487
|
+
screenSize() {
|
|
488
|
+
return {
|
|
489
|
+
width: this.canvas.clientWidth,
|
|
490
|
+
height: this.canvas.clientHeight,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Handles canvas resize events. Updates internal dimensions and notifies
|
|
496
|
+
* relevant subsystems (HUD, RenderManager, Gamepad) of the size change.
|
|
497
|
+
* This should be called when the canvas container is resized.
|
|
498
|
+
*/
|
|
499
|
+
handleResize() {
|
|
500
|
+
if (!this.gl || !this.ctx) return;
|
|
501
|
+
|
|
502
|
+
const displayWidth = this.canvas.clientWidth;
|
|
503
|
+
const displayHeight = this.canvas.clientHeight;
|
|
504
|
+
|
|
505
|
+
// Check if the canvas needs resizing
|
|
506
|
+
if (this.canvas.width !== displayWidth || this.canvas.height !== displayHeight) {
|
|
507
|
+
// Update canvas internal dimensions to match display size
|
|
508
|
+
this.canvas.width = displayWidth;
|
|
509
|
+
this.canvas.height = displayHeight;
|
|
510
|
+
|
|
511
|
+
// Update HUD canvas
|
|
512
|
+
if (this.hudCanvas) {
|
|
513
|
+
this.hudCanvas.width = displayWidth;
|
|
514
|
+
this.hudCanvas.height = displayHeight;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Update engine width/height
|
|
518
|
+
this.width = displayWidth;
|
|
519
|
+
this.height = displayHeight;
|
|
520
|
+
|
|
521
|
+
// Update WebGL viewport
|
|
522
|
+
this.gl.viewport(0, 0, displayWidth, displayHeight);
|
|
523
|
+
|
|
524
|
+
// Update render manager if available
|
|
525
|
+
if (this.renderManager && this.renderManager.handleResize) {
|
|
526
|
+
this.renderManager.handleResize(displayWidth, displayHeight);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Update gamepad/input manager if available
|
|
530
|
+
if (this.inputManager && this.inputManager.gamepad) {
|
|
531
|
+
this.inputManager.gamepad.resize();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Update HUD
|
|
535
|
+
if (this.hud && this.hud.handleResize) {
|
|
536
|
+
this.hud.handleResize(displayWidth, displayHeight);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
import { ControllerStick } from '@Engine/core/input/gamepad/ControllerStick.js';
|
|
15
|
+
import { ControllerButtons } from '@Engine/core/input/gamepad/ControllerButtons.js';
|
|
16
|
+
// Controller Manager for Gamepad
|
|
17
|
+
export class Controller {
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of Controller.
|
|
20
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
|
|
21
|
+
* @param {Object} buttonOffset - The offset for button placement.
|
|
22
|
+
* @param {Object.<string, any>} touches - The touch state object.
|
|
23
|
+
* @param {boolean} start - Whether start button is enabled.
|
|
24
|
+
* @param {boolean} select - Whether select button is enabled.
|
|
25
|
+
* @param {Object.<string, string>} colours - The color scheme.
|
|
26
|
+
* @param {GamePad} gamepad - The parent gamepad instance.
|
|
27
|
+
*/
|
|
28
|
+
constructor(ctx, buttonOffset, touches, start, select, colours, gamepad) {
|
|
29
|
+
/** @type {CanvasRenderingContext2D} */
|
|
30
|
+
this.ctx = ctx;
|
|
31
|
+
/** @type {GamePad} */
|
|
32
|
+
this.gamepad = gamepad;
|
|
33
|
+
/** @type {number} */
|
|
34
|
+
this.width = ctx.canvas.width;
|
|
35
|
+
/** @type {number} */
|
|
36
|
+
this.height = ctx.canvas.height;
|
|
37
|
+
/** @type {number} */
|
|
38
|
+
this.radius = ctx.canvas.width / 10;
|
|
39
|
+
/** @type {Object.<string, any>} */
|
|
40
|
+
this.touches = touches;
|
|
41
|
+
/** @type {boolean} */
|
|
42
|
+
this.start = start;
|
|
43
|
+
/** @type {boolean} */
|
|
44
|
+
this.select = select;
|
|
45
|
+
/** @type {Object} */
|
|
46
|
+
this.buttonOffset = buttonOffset;
|
|
47
|
+
/** @type {Object.<string, string>} */
|
|
48
|
+
this.colours = colours;
|
|
49
|
+
/** @type {Object} */
|
|
50
|
+
this.layout = { x: this.width - this.buttonOffset.x, y: this.height - this.buttonOffset.y };
|
|
51
|
+
/** @type {ControllerStick} */
|
|
52
|
+
this.stick = new ControllerStick(this.ctx, this.layout, this.touches, this.colours, this.radius, this.gamepad);
|
|
53
|
+
/** @type {ControllerButtons} */
|
|
54
|
+
this.buttons = new ControllerButtons(this.ctx, this.layout, this.touches, this.start, this.select, this.colours, this.radius, this.gamepad);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Initializes the controller components.
|
|
58
|
+
*/
|
|
59
|
+
init() {
|
|
60
|
+
this.stick.init();
|
|
61
|
+
this.buttons.init();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Draws the controller components.
|
|
66
|
+
*/
|
|
67
|
+
draw() {
|
|
68
|
+
this.stick.draw();
|
|
69
|
+
this.buttons.draw();
|
|
70
|
+
}
|
|
71
|
+
}
|