jygame 0.1.0
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/LICENSE +674 -0
- package/README.md +62 -0
- package/assets/FontLoader.js +23 -0
- package/assets/ImageLoader.js +37 -0
- package/collision/Collision.js +51 -0
- package/core/Game.js +147 -0
- package/core/Scene.js +17 -0
- package/display/Group.js +107 -0
- package/display/Sprite.js +80 -0
- package/geometry/Rect.js +96 -0
- package/input/Input.js +173 -0
- package/jygame.js +14 -0
- package/math/Vec2.js +90 -0
- package/package.json +41 -0
- package/state/State.js +42 -0
- package/storage/Storage.js +29 -0
- package/time/Clock.js +35 -0
- package/time/Timer.js +54 -0
package/input/Input.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const _pressed = {};
|
|
2
|
+
const _justPressed = {};
|
|
3
|
+
const _justReleased = {};
|
|
4
|
+
|
|
5
|
+
const KEY_MAP = {
|
|
6
|
+
ArrowUp: "UP",
|
|
7
|
+
ArrowDown: "DOWN",
|
|
8
|
+
ArrowLeft: "LEFT",
|
|
9
|
+
ArrowRight: "RIGHT",
|
|
10
|
+
w: "UP",
|
|
11
|
+
W: "UP",
|
|
12
|
+
s: "DOWN",
|
|
13
|
+
S: "DOWN",
|
|
14
|
+
a: "LEFT",
|
|
15
|
+
A: "LEFT",
|
|
16
|
+
d: "RIGHT",
|
|
17
|
+
D: "RIGHT",
|
|
18
|
+
" ": "SPACE",
|
|
19
|
+
Escape: "ESCAPE",
|
|
20
|
+
Enter: "ENTER",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let _swipeListeners = [];
|
|
24
|
+
let _tapListeners = [];
|
|
25
|
+
let _trackingTouch = false;
|
|
26
|
+
let _touchStartX = 0;
|
|
27
|
+
let _touchStartY = 0;
|
|
28
|
+
let _touchStartTime = 0;
|
|
29
|
+
const MIN_SWIPE = 30;
|
|
30
|
+
const TAP_TIMEOUT = 300;
|
|
31
|
+
|
|
32
|
+
function handleKeyDown(e) {
|
|
33
|
+
const mapped = KEY_MAP[e.key];
|
|
34
|
+
if (mapped) {
|
|
35
|
+
if (!_pressed[mapped]) _justPressed[mapped] = true;
|
|
36
|
+
_pressed[mapped] = true;
|
|
37
|
+
if (["UP", "DOWN", "LEFT", "RIGHT", "SPACE"].includes(mapped)) {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleKeyUp(e) {
|
|
44
|
+
const mapped = KEY_MAP[e.key];
|
|
45
|
+
if (mapped) {
|
|
46
|
+
if (_pressed[mapped]) _justReleased[mapped] = true;
|
|
47
|
+
_pressed[mapped] = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleTouchStart(e) {
|
|
52
|
+
_trackingTouch = true;
|
|
53
|
+
const t = e.touches[0];
|
|
54
|
+
_touchStartX = t.clientX;
|
|
55
|
+
_touchStartY = t.clientY;
|
|
56
|
+
_touchStartTime = Date.now();
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleTouchMove(e) {
|
|
61
|
+
if (!_trackingTouch) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleTouchEnd(e) {
|
|
66
|
+
if (!_trackingTouch) return;
|
|
67
|
+
_trackingTouch = false;
|
|
68
|
+
|
|
69
|
+
const t = e.changedTouches[0];
|
|
70
|
+
const deltaX = t.clientX - _touchStartX;
|
|
71
|
+
const deltaY = t.clientY - _touchStartY;
|
|
72
|
+
const absDx = Math.abs(deltaX);
|
|
73
|
+
const absDy = Math.abs(deltaY);
|
|
74
|
+
const elapsed = Date.now() - _touchStartTime;
|
|
75
|
+
|
|
76
|
+
if (absDx < MIN_SWIPE && absDy < MIN_SWIPE && elapsed < TAP_TIMEOUT) {
|
|
77
|
+
for (const cb of _tapListeners) {
|
|
78
|
+
cb({ x: t.clientX, y: t.clientY });
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (absDx < MIN_SWIPE && absDy < MIN_SWIPE) return;
|
|
84
|
+
|
|
85
|
+
let dir;
|
|
86
|
+
if (absDx > absDy) {
|
|
87
|
+
dir = deltaX > 0 ? "RIGHT" : "LEFT";
|
|
88
|
+
} else {
|
|
89
|
+
dir = deltaY > 0 ? "DOWN" : "UP";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const cb of _swipeListeners) {
|
|
93
|
+
cb(dir);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const Input = {
|
|
98
|
+
buffer: [],
|
|
99
|
+
|
|
100
|
+
init(target) {
|
|
101
|
+
const el = target || document;
|
|
102
|
+
el.addEventListener("keydown", handleKeyDown);
|
|
103
|
+
el.addEventListener("keyup", handleKeyUp);
|
|
104
|
+
el.addEventListener("touchstart", handleTouchStart, { passive: false });
|
|
105
|
+
el.addEventListener("touchmove", handleTouchMove, { passive: false });
|
|
106
|
+
el.addEventListener("touchend", handleTouchEnd, { passive: false });
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
destroy(target) {
|
|
110
|
+
const el = target || document;
|
|
111
|
+
el.removeEventListener("keydown", handleKeyDown);
|
|
112
|
+
el.removeEventListener("keyup", handleKeyUp);
|
|
113
|
+
el.removeEventListener("touchstart", handleTouchStart);
|
|
114
|
+
el.removeEventListener("touchmove", handleTouchMove);
|
|
115
|
+
el.removeEventListener("touchend", handleTouchEnd);
|
|
116
|
+
_swipeListeners = [];
|
|
117
|
+
_tapListeners = [];
|
|
118
|
+
this.buffer = [];
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
updateFrame() {
|
|
122
|
+
for (const key in _justPressed) delete _justPressed[key];
|
|
123
|
+
for (const key in _justReleased) delete _justReleased[key];
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
clearJustPressed() {
|
|
127
|
+
for (const key in _justPressed) delete _justPressed[key];
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
isDown(key) {
|
|
131
|
+
return !!_pressed[key];
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
justPressed(key) {
|
|
135
|
+
return !!_justPressed[key];
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
justReleased(key) {
|
|
139
|
+
return !!_justReleased[key];
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
consumeBuffer() {
|
|
143
|
+
if (this.buffer.length === 0) return null;
|
|
144
|
+
return this.buffer.shift();
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
peekBuffer() {
|
|
148
|
+
if (this.buffer.length === 0) return null;
|
|
149
|
+
return this.buffer[0];
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
onSwipe(cb) {
|
|
153
|
+
_swipeListeners.push(cb);
|
|
154
|
+
return () => {
|
|
155
|
+
_swipeListeners = _swipeListeners.filter(l => l !== cb);
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
onTap(cb) {
|
|
160
|
+
_tapListeners.push(cb);
|
|
161
|
+
return () => {
|
|
162
|
+
_tapListeners = _tapListeners.filter(l => l !== cb);
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
removeSwipe(cb) {
|
|
167
|
+
_swipeListeners = _swipeListeners.filter(l => l !== cb);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
removeTap(cb) {
|
|
171
|
+
_tapListeners = _tapListeners.filter(l => l !== cb);
|
|
172
|
+
},
|
|
173
|
+
};
|
package/jygame.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { Vec2 } from "./math/Vec2.js";
|
|
2
|
+
export { Rect } from "./geometry/Rect.js";
|
|
3
|
+
export { Clock } from "./time/Clock.js";
|
|
4
|
+
export { Timer } from "./time/Timer.js";
|
|
5
|
+
export { State } from "./state/State.js";
|
|
6
|
+
export { Storage } from "./storage/Storage.js";
|
|
7
|
+
export { Collision } from "./collision/Collision.js";
|
|
8
|
+
export { Sprite } from "./display/Sprite.js";
|
|
9
|
+
export { Group } from "./display/Group.js";
|
|
10
|
+
export { Input } from "./input/Input.js";
|
|
11
|
+
export { ImageLoader } from "./assets/ImageLoader.js";
|
|
12
|
+
export { FontLoader } from "./assets/FontLoader.js";
|
|
13
|
+
export { Scene } from "./core/Scene.js";
|
|
14
|
+
export { Game } from "./core/Game.js";
|
package/math/Vec2.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class Vec2 {
|
|
2
|
+
constructor(x = 0, y = 0) {
|
|
3
|
+
this.x = x;
|
|
4
|
+
this.y = y;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
set(x, y) {
|
|
8
|
+
this.x = x;
|
|
9
|
+
this.y = y;
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
add(v) {
|
|
14
|
+
this.x += v.x;
|
|
15
|
+
this.y += v.y;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
sub(v) {
|
|
20
|
+
this.x -= v.x;
|
|
21
|
+
this.y -= v.y;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
scale(s) {
|
|
26
|
+
this.x *= s;
|
|
27
|
+
this.y *= s;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
dot(v) {
|
|
32
|
+
return this.x * v.x + this.y * v.y;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
magnitude() {
|
|
36
|
+
return Math.hypot(this.x, this.y);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
normalize() {
|
|
40
|
+
const m = this.magnitude();
|
|
41
|
+
if (m === 0) return this;
|
|
42
|
+
this.x /= m;
|
|
43
|
+
this.y /= m;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
angle() {
|
|
48
|
+
return Math.atan2(this.y, this.x);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setAngle(a) {
|
|
52
|
+
const m = this.magnitude();
|
|
53
|
+
this.x = Math.cos(a) * m;
|
|
54
|
+
this.y = Math.sin(a) * m;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
rotate(a) {
|
|
59
|
+
const cos = Math.cos(a);
|
|
60
|
+
const sin = Math.sin(a);
|
|
61
|
+
const rx = this.x * cos - this.y * sin;
|
|
62
|
+
const ry = this.x * sin + this.y * cos;
|
|
63
|
+
this.x = rx;
|
|
64
|
+
this.y = ry;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
perpendicular() {
|
|
69
|
+
const tx = this.x;
|
|
70
|
+
this.x = -this.y;
|
|
71
|
+
this.y = tx;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
dist(v) {
|
|
76
|
+
return Math.hypot(this.x - v.x, this.y - v.y);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clone() {
|
|
80
|
+
return new Vec2(this.x, this.y);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static fromAngle(angle, length = 1) {
|
|
84
|
+
return new Vec2(Math.cos(angle) * length, Math.sin(angle) * length);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static lerp(a, b, t) {
|
|
88
|
+
return new Vec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jygame",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight 2D game framework for the browser",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./jygame.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./jygame.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"jygame.js",
|
|
12
|
+
"math/",
|
|
13
|
+
"geometry/",
|
|
14
|
+
"time/",
|
|
15
|
+
"state/",
|
|
16
|
+
"storage/",
|
|
17
|
+
"collision/",
|
|
18
|
+
"display/",
|
|
19
|
+
"input/",
|
|
20
|
+
"assets/",
|
|
21
|
+
"core/"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"game",
|
|
25
|
+
"game-development",
|
|
26
|
+
"2d",
|
|
27
|
+
"canvas",
|
|
28
|
+
"framework"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/Bouzidi-Youssef/Jygame.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/Bouzidi-Youssef/Jygame#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/Bouzidi-Youssef/Jygame/issues"
|
|
37
|
+
},
|
|
38
|
+
"license": "GPL-3.0-only",
|
|
39
|
+
"author": "Bouzidi Youssef",
|
|
40
|
+
"sideEffects": false
|
|
41
|
+
}
|
package/state/State.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class State {
|
|
2
|
+
constructor(initial = {}) {
|
|
3
|
+
this._state = { ...initial };
|
|
4
|
+
this._listeners = [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get() {
|
|
8
|
+
return this._state;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
set(partial) {
|
|
12
|
+
this._state = { ...this._state, ...partial };
|
|
13
|
+
this._notify();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
replace(next) {
|
|
17
|
+
this._state = next;
|
|
18
|
+
this._notify();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
reset(initial) {
|
|
22
|
+
this._state = { ...initial };
|
|
23
|
+
this._notify();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
subscribe(fn) {
|
|
27
|
+
this._listeners.push(fn);
|
|
28
|
+
return () => {
|
|
29
|
+
this._listeners = this._listeners.filter(l => l !== fn);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
unsubscribe(fn) {
|
|
34
|
+
this._listeners = this._listeners.filter(l => l !== fn);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_notify() {
|
|
38
|
+
for (const fn of this._listeners) {
|
|
39
|
+
fn(this._state);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const Storage = {
|
|
2
|
+
get(key, defaultValue = null) {
|
|
3
|
+
try {
|
|
4
|
+
const raw = localStorage.getItem(key);
|
|
5
|
+
if (raw === null) return defaultValue;
|
|
6
|
+
return JSON.parse(raw);
|
|
7
|
+
} catch {
|
|
8
|
+
return defaultValue;
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
set(key, value) {
|
|
13
|
+
try {
|
|
14
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
15
|
+
} catch {}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
remove(key) {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.removeItem(key);
|
|
21
|
+
} catch {}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
clear() {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.clear();
|
|
27
|
+
} catch {}
|
|
28
|
+
},
|
|
29
|
+
};
|
package/time/Clock.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class Clock {
|
|
2
|
+
constructor(fps = 60) {
|
|
3
|
+
this._fps = fps;
|
|
4
|
+
this._fixedDt = 1 / fps;
|
|
5
|
+
this._maxDelta = 0.2;
|
|
6
|
+
this._accumulator = 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get fps() {
|
|
10
|
+
return this._fps;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
set fps(v) {
|
|
14
|
+
this._fps = v;
|
|
15
|
+
this._fixedDt = 1 / v;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get fixedDt() {
|
|
19
|
+
return this._fixedDt;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
tick(realDt) {
|
|
23
|
+
this._accumulator += Math.min(realDt, this._maxDelta);
|
|
24
|
+
let count = 0;
|
|
25
|
+
while (this._accumulator >= this._fixedDt) {
|
|
26
|
+
this._accumulator -= this._fixedDt;
|
|
27
|
+
count++;
|
|
28
|
+
}
|
|
29
|
+
return count;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
reset() {
|
|
33
|
+
this._accumulator = 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/time/Timer.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class Timer {
|
|
2
|
+
constructor(duration, { loop = false, autoStart = true } = {}) {
|
|
3
|
+
this._duration = duration;
|
|
4
|
+
this._loop = loop;
|
|
5
|
+
this._elapsed = 0;
|
|
6
|
+
this._running = autoStart;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get done() {
|
|
10
|
+
return this._running && this._elapsed >= this._duration;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get progress() {
|
|
14
|
+
return Math.min(this._elapsed / this._duration, 1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get remaining() {
|
|
18
|
+
return Math.max(this._duration - this._elapsed, 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get running() {
|
|
22
|
+
return this._running;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
tick(dt) {
|
|
26
|
+
if (!this._running) return;
|
|
27
|
+
|
|
28
|
+
this._elapsed += dt;
|
|
29
|
+
|
|
30
|
+
if (this._elapsed >= this._duration) {
|
|
31
|
+
if (this._loop) {
|
|
32
|
+
this._elapsed -= this._duration;
|
|
33
|
+
} else {
|
|
34
|
+
this._elapsed = this._duration;
|
|
35
|
+
this._running = false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
start() {
|
|
43
|
+
this._running = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stop() {
|
|
47
|
+
this._running = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
reset() {
|
|
51
|
+
this._elapsed = 0;
|
|
52
|
+
this._running = true;
|
|
53
|
+
}
|
|
54
|
+
}
|