kippy 0.2.1 → 0.3.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/README.md CHANGED
@@ -64,8 +64,7 @@ import { Entity } from "kippy";
64
64
 
65
65
  const entity = new Entity({
66
66
  sprite, // Entity's sprite to be rendered, type Sprite
67
- x, // Entity's x position (centered), type number
68
- y, // Entity's y position (centered), type number
67
+ position, // Entity's position (centered), type Vector2
69
68
  rotation, // Entity's rotation in radians, type number
70
69
  body, // Entity's physical body, type EntityBody
71
70
  });
@@ -76,8 +75,10 @@ scene.addEntity(entity);
76
75
  scene.removeEntity(entity);
77
76
 
78
77
  // These props contain movement info and you can mutate them to edit its position
79
- entity.x; // Initialized from the "x" param above, 0 if not specified
80
- entity.y; // Initialized from the "y" param above, 0 if not specified
78
+ entity.position; // Initialized from the "position" param above, Vector2(0, 0) if not specified
79
+ // You can mutate these directly:
80
+ entity.position.x;
81
+ entity.position.y;
81
82
  entity.rotation; // Initialized from the "rotation" param above, 0 if not specified
82
83
  ```
83
84
 
@@ -99,7 +100,7 @@ entity.sprite = sprite;
99
100
 
100
101
  ### Add controls
101
102
 
102
- Game controls like mouse presses, key presses, and mouse cursor traking (in the game canvas, not the web window) can be done by using the input handler from your `game` instance:
103
+ Game controls like mouse presses, key presses, touch, and cursor traking (in the game canvas, not the web window) can be done by using the input handler from your `game` instance:
103
104
  ```js
104
105
  const input = game.input;
105
106
  ```
@@ -110,12 +111,50 @@ Then in a scene's `update` method, you can use these utilities to check for key
110
111
  input.isKeyDown(/* Character/key here */); // true if key is held, false otherwise
111
112
  input.isKeyPressed(/* Character/key here */); // true if key is pressed, false otherwise
112
113
  input.isKeyReleased(/* Character/key here */); // true if key is released, false otherwise
113
- // Mouse
114
- input.isMouseDown(/* 0 for left, 1 for right */); // true if mouse is held, false otherwise
115
- input.isMousePressed(/* 0 for left, 1 for right */); // true if mouse is pressed, false otherwise
116
- input.isMouseReleased(/* 0 for left, 1 for right */); // true if mouse is released, false otherwise
117
- input.mouseX; // Current X position of mouse
118
- input.mouseY; // Current Y position of mouse
114
+ // Mouse/touch
115
+ input.isPointerDown(/* 0 for left, 1 for right, 2 for touch */); // true if held, false otherwise
116
+ input.isPointerPressed(/* 0 for left, 1 for right, 2 for touch */); // true if pressed, false otherwise
117
+ input.isPointerReleased(/* 0 for left, 1 for right, 2 for touch */); // true if released, false otherwise
118
+ input.pointerX; // Current X position of mouse/touch
119
+ input.pointerY; // Current Y position of mouse/touch
120
+ ```
121
+
122
+ ### Vectors
123
+
124
+ To work with positions and movements in Kippy, it is best to know about `Vector2` first. Positions, velocities, forces, etc are all represented as vectors in Kippy. And here is how you can create a 2D vector and some vector math utilities that comes along with it:
125
+ ```js
126
+ import { Vector2 } from "kippy";
127
+
128
+ const vect = new Vector2(/* x coordinate, number */, /*y coordinate, number */);
129
+
130
+ // Props
131
+ vect.x; // X coordinate
132
+ vect.y; // Y coordinate
133
+
134
+ // Utilities
135
+ vect.add(otherVect); // Add another vector and return the result vector
136
+ vect.sub(otherVect); // Subtract another vector and return the result vector
137
+ vect.scale(scale); // Multiply with scale and return the result vector
138
+ vect.magnitude(); // Return the magnitude/length of vector
139
+ vect.normalize(); // Return the normalized vector by magnitude
140
+ vect.dot(otherVect); // Return dot product with another vector
141
+ vect.distance(otherVect); // Return distance to another vector
142
+ vect.copy(); // Return a copy (same coordinates, different reference)
143
+ vect.lerp(otherVect, scale); // Apply linear interpolation and return
144
+ vect.clamp(maxLength); // Clamp vector to have length below maxLength
145
+ vect.rotate(angle); // Return rotated vector by provided angle
146
+ vect.angle(); // Return angle of vector.
147
+ vect.angleTo(otherVec); // Return angle between this and another vector
148
+ vect.reflect(otherVect); // Return reflection/bounce back vector
149
+ vect.equals(otherVect); // Check if two vectors are equal
150
+
151
+ // Useful constants
152
+ Vector2.ZERO; // Vector2(0, 0)
153
+ Vector2.ONE; // Vector2(1, 1);
154
+ Vector2.UP; // Vector2(0, -1);
155
+ Vector2.DOWN; // Vector2(0, 1);
156
+ Vector2.LEFT; // Vector2(-1, 0);
157
+ Vector2.RIGHT; // Vector2(1, 0);
119
158
  ```
120
159
 
121
160
  ### Physics
@@ -124,13 +163,11 @@ For movements, currently you can create a `RigidBody`:
124
163
  ```js
125
164
  // Create a rigid body
126
165
  const rigidBody = new RigidBody({
127
- velocityX, // X velocity, type number
128
- velocityY, // Y velocity, type number
129
- rotationVelocity, // Angular/rotation velocity, type number
166
+ velocity, // Entity's velocity vector, type Vector2
167
+ rotationVelocity, // Entity's ngular/rotation velocity, type number
130
168
  mass, // Entity's mass, type number
131
169
  inertia, // Entity's inertia, type number
132
- forceX, // Entity's force on X axis, type number
133
- forceY, // Entity's force on Y axis, type number
170
+ force, // Entity's force vector, type Vector2
134
171
  torque, // Entity's torque/rotational force, type number
135
172
  });
136
173
 
@@ -138,13 +175,12 @@ const rigidBody = new RigidBody({
138
175
  entity.body = rigidBody;
139
176
 
140
177
  // And you can mutate these props to update movement every frame
141
- entity.body.velocityX; // Set with the matching parameter above, default is 0
142
- entity.body.velocityY; // Set with the matching parameter above, default is 0
178
+ entity.body.velocity; // Set with the matching parameter above, default is Vector2(0, 0)
143
179
  entity.body.rotationVelocity; // Set with the matching parameter above, default is 0
144
180
  entity.body.mass; // Set with the matching parameter above, default is 1
145
181
  entity.body.inertia; // Set with the matching parameter above, default is 1
146
- entity.body.forceX; // Set with the matching parameter above, default is 0
147
- entity.body.forceY; // Set with the matching parameter above, default is 0
182
+ // Note that forces are reset after every frame
183
+ entity.body.force; // Set with the matching parameter above, default is Vector2(0, 0)
148
184
  entity.body.torque; // Set with the matching parameter above, default is 0
149
185
  ```
150
186
 
@@ -158,6 +194,10 @@ To be added, for now mutate `entity.sprite` to swap sprites and create animation
158
194
 
159
195
  To be added, for now use web's built-in `Audio` class.
160
196
 
197
+ ### Asset management
198
+
199
+ To be added.
200
+
161
201
  ## Copyrights and License
162
202
 
163
203
  Copyrights © 2026 Nguyen Phu Minh.
package/dist/entity.d.ts CHANGED
@@ -1,16 +1,15 @@
1
1
  import { EntityBody } from "./physics.js";
2
2
  import { Sprite } from "./sprite.js";
3
+ import { Vector2 } from "./vector.js";
3
4
  export interface EntityOptions {
4
5
  sprite?: Sprite;
5
- x?: number;
6
- y?: number;
6
+ position?: Vector2;
7
7
  rotation?: number;
8
8
  body?: EntityBody;
9
9
  }
10
10
  export declare class Entity {
11
11
  sprite?: Sprite;
12
- x: number;
13
- y: number;
12
+ position: Vector2;
14
13
  rotation: number;
15
14
  body?: EntityBody;
16
15
  constructor(options?: EntityOptions);
package/dist/entity.js CHANGED
@@ -1,20 +1,19 @@
1
+ import { Vector2 } from "./vector.js";
1
2
  export class Entity {
2
3
  sprite;
3
- x;
4
- y;
4
+ position;
5
5
  rotation;
6
6
  body;
7
7
  constructor(options = {}) {
8
8
  this.sprite = options.sprite;
9
- this.x = options.x ?? 0;
10
- this.y = options.y ?? 0;
9
+ this.position = options.position ?? new Vector2(0, 0);
11
10
  this.rotation = options.rotation ?? 0;
12
11
  this.body = options.body;
13
12
  }
14
13
  render(ctx) {
15
14
  if (this.sprite) {
16
15
  ctx.save();
17
- ctx.translate(this.x, this.y);
16
+ ctx.translate(this.position.x, this.position.y);
18
17
  ctx.rotate(this.rotation);
19
18
  ctx.drawImage(this.sprite.texture, -this.sprite.width / 2, -this.sprite.height / 2);
20
19
  ctx.restore();
package/dist/input.d.ts CHANGED
@@ -6,17 +6,17 @@ export declare class Input {
6
6
  keys: Set<string>;
7
7
  keysPressed: Set<string>;
8
8
  keysReleased: Set<string>;
9
- mouseX: number;
10
- mouseY: number;
11
- mouseButtons: Set<number>;
12
- mousePressed: Set<number>;
13
- mouseReleased: Set<number>;
9
+ pointerX: number;
10
+ pointerY: number;
11
+ pointers: Set<number>;
12
+ pointersPressed: Set<number>;
13
+ pointersReleased: Set<number>;
14
14
  constructor(options: InputOptions);
15
15
  update(): void;
16
16
  isKeyDown(key: string): boolean;
17
17
  isKeyPressed(key: string): boolean;
18
18
  isKeyReleased(key: string): boolean;
19
- isMouseDown(button?: number): boolean;
20
- isMousePressed(button?: number): boolean;
21
- isMouseReleased(button?: number): boolean;
19
+ isPointerDown(button?: number): boolean;
20
+ isPointerPressed(button?: number): boolean;
21
+ isPointerReleased(button?: number): boolean;
22
22
  }
package/dist/input.js CHANGED
@@ -3,11 +3,11 @@ export class Input {
3
3
  keys = new Set(); // Key on hold
4
4
  keysPressed = new Set(); // Key pressed
5
5
  keysReleased = new Set(); // Key released
6
- mouseX = 0; // Mouse coord x in canvas
7
- mouseY = 0; // Mouse coord y in canvas
8
- mouseButtons = new Set(); // Mouse button on hold
9
- mousePressed = new Set(); // Mouse button pressed
10
- mouseReleased = new Set(); // Mouse button released
6
+ pointerX = 0; // Mouse/touch coord x in canvas
7
+ pointerY = 0; // Mouse/touch coord y in canvas
8
+ pointers = new Set(); // Mouse/touch on hold
9
+ pointersPressed = new Set(); // Mouse/touch pressed
10
+ pointersReleased = new Set(); // Mouse/touch released
11
11
  constructor(options) {
12
12
  this.canvas = options.canvas;
13
13
  // Keyboard
@@ -21,21 +21,40 @@ export class Input {
21
21
  this.keys.delete(e.key);
22
22
  this.keysReleased.add(e.key);
23
23
  });
24
- // Mouse
24
+ // Mouse and touch
25
25
  this.canvas.addEventListener("mousemove", (e) => {
26
26
  const rect = this.canvas.getBoundingClientRect();
27
- this.mouseX = e.clientX - rect.left;
28
- this.mouseY = e.clientY - rect.top;
27
+ this.pointerX = e.clientX - rect.left;
28
+ this.pointerY = e.clientY - rect.top;
29
29
  });
30
30
  this.canvas.addEventListener("mousedown", (e) => {
31
- if (!this.mouseButtons.has(e.button)) {
32
- this.mousePressed.add(e.button);
31
+ if (!this.pointers.has(e.button)) {
32
+ this.pointersPressed.add(e.button);
33
33
  }
34
- this.mouseButtons.add(e.button);
34
+ this.pointers.add(e.button);
35
35
  });
36
36
  this.canvas.addEventListener("mouseup", (e) => {
37
- this.mouseButtons.delete(e.button);
38
- this.mouseReleased.add(e.button);
37
+ this.pointers.delete(e.button);
38
+ this.pointersReleased.add(e.button);
39
+ });
40
+ this.canvas.addEventListener("touchmove", (e) => {
41
+ e.preventDefault();
42
+ const rect = this.canvas.getBoundingClientRect();
43
+ const touch = e.touches[0];
44
+ this.pointerX = touch.clientX - rect.left;
45
+ this.pointerY = touch.clientY - rect.top;
46
+ });
47
+ this.canvas.addEventListener("touchstart", (e) => {
48
+ e.preventDefault();
49
+ if (!this.pointers.has(2)) {
50
+ this.pointersPressed.add(2);
51
+ }
52
+ this.pointers.add(2);
53
+ });
54
+ this.canvas.addEventListener("touchend", (e) => {
55
+ e.preventDefault();
56
+ this.pointers.delete(2);
57
+ this.pointersReleased.add(2);
39
58
  });
40
59
  // Prevent right-click menu
41
60
  this.canvas.addEventListener("contextmenu", (e) => {
@@ -46,7 +65,8 @@ export class Input {
46
65
  update() {
47
66
  this.keysPressed.clear();
48
67
  this.keysReleased.clear();
49
- this.mousePressed.clear();
68
+ this.pointersPressed.clear();
69
+ this.pointersReleased.clear();
50
70
  }
51
71
  // Helper methods
52
72
  isKeyDown(key) {
@@ -58,13 +78,13 @@ export class Input {
58
78
  isKeyReleased(key) {
59
79
  return this.keysReleased.has(key);
60
80
  }
61
- isMouseDown(button = 0) {
62
- return this.mouseButtons.has(button);
81
+ isPointerDown(button = 0) {
82
+ return this.pointers.has(button);
63
83
  }
64
- isMousePressed(button = 0) {
65
- return this.mousePressed.has(button);
84
+ isPointerPressed(button = 0) {
85
+ return this.pointersPressed.has(button);
66
86
  }
67
- isMouseReleased(button = 0) {
68
- return this.mouseReleased.has(button);
87
+ isPointerReleased(button = 0) {
88
+ return this.pointersReleased.has(button);
69
89
  }
70
90
  }
package/dist/physics.d.ts CHANGED
@@ -1,22 +1,19 @@
1
1
  import { Entity } from "./entity";
2
+ import { Vector2 } from "./vector";
2
3
  export interface RigidBodyOptions {
3
- velocityX?: number;
4
- velocityY?: number;
4
+ velocity?: Vector2;
5
5
  rotationVelocity?: number;
6
6
  mass?: number;
7
7
  inertia?: number;
8
- forceX?: number;
9
- forceY?: number;
8
+ force?: Vector2;
10
9
  torque?: number;
11
10
  }
12
11
  export declare class RigidBody {
13
- velocityX: number;
14
- velocityY: number;
12
+ velocity: Vector2;
15
13
  rotationVelocity: number;
16
14
  mass: number;
17
15
  inertia: number;
18
- forceX: number;
19
- forceY: number;
16
+ force: Vector2;
20
17
  torque: number;
21
18
  constructor(options?: RigidBodyOptions);
22
19
  }
package/dist/physics.js CHANGED
@@ -1,20 +1,17 @@
1
+ import { Vector2 } from "./vector";
1
2
  export class RigidBody {
2
- velocityX;
3
- velocityY;
3
+ velocity;
4
4
  rotationVelocity;
5
5
  mass;
6
6
  inertia;
7
- forceX;
8
- forceY;
7
+ force;
9
8
  torque;
10
9
  constructor(options = {}) {
11
- this.velocityX = options.velocityX ?? 0;
12
- this.velocityY = options.velocityY ?? 0;
10
+ this.velocity = options.velocity ?? new Vector2(0, 0);
13
11
  this.rotationVelocity = options.rotationVelocity ?? 0;
14
12
  this.mass = options.mass ?? 1;
15
13
  this.inertia = options.inertia ?? 1;
16
- this.forceX = options.forceX ?? 0;
17
- this.forceY = options.forceY ?? 0;
14
+ this.force = options.force ?? new Vector2(0, 0);
18
15
  this.torque = options.torque ?? 0;
19
16
  }
20
17
  }
@@ -23,13 +20,17 @@ export class Physics {
23
20
  for (const entity of entities) {
24
21
  if (entity.body instanceof RigidBody) {
25
22
  // Acceleration/apply force
26
- entity.body.velocityX += entity.body.forceX / entity.body.mass;
27
- entity.body.velocityY += entity.body.forceY / entity.body.mass;
23
+ entity.body.velocity.x += entity.body.force.x / entity.body.mass;
24
+ entity.body.velocity.y += entity.body.force.y / entity.body.mass;
28
25
  entity.body.rotationVelocity += entity.body.torque / entity.body.inertia;
29
26
  // Positional update
30
- entity.x += entity.body.velocityX;
31
- entity.y += entity.body.velocityY;
27
+ entity.position.x += entity.body.velocity.x;
28
+ entity.position.y += entity.body.velocity.y;
32
29
  entity.rotation += entity.body.rotationVelocity;
30
+ // Clear force
31
+ entity.body.force.x = 0;
32
+ entity.body.force.y = 0;
33
+ entity.body.torque = 0;
33
34
  }
34
35
  }
35
36
  }
@@ -0,0 +1,26 @@
1
+ export declare class Vector2 {
2
+ x: number;
3
+ y: number;
4
+ static ZERO: Vector2;
5
+ static ONE: Vector2;
6
+ static UP: Vector2;
7
+ static DOWN: Vector2;
8
+ static LEFT: Vector2;
9
+ static RIGHT: Vector2;
10
+ constructor(x: number, y: number);
11
+ add(other: Vector2): Vector2;
12
+ sub(other: Vector2): Vector2;
13
+ scale(scale: number): Vector2;
14
+ magnitude(): number;
15
+ normalize(): Vector2;
16
+ dot(other: Vector2): number;
17
+ distance(other: Vector2): number;
18
+ copy(): Vector2;
19
+ lerp(other: Vector2, scale: number): Vector2;
20
+ clamp(maxLength: number): Vector2;
21
+ rotate(angle: number): Vector2;
22
+ angle(): number;
23
+ angleTo(other: Vector2): number;
24
+ reflect(normal: Vector2): Vector2;
25
+ equals(other: Vector2): boolean;
26
+ }
package/dist/vector.js ADDED
@@ -0,0 +1,64 @@
1
+ export class Vector2 {
2
+ x;
3
+ y;
4
+ static ZERO = new Vector2(0, 0);
5
+ static ONE = new Vector2(1, 1);
6
+ static UP = new Vector2(0, -1);
7
+ static DOWN = new Vector2(0, 1);
8
+ static LEFT = new Vector2(-1, 0);
9
+ static RIGHT = new Vector2(1, 0);
10
+ constructor(x, y) {
11
+ this.x = x;
12
+ this.y = y;
13
+ }
14
+ add(other) {
15
+ return new Vector2(this.x + other.x, this.y + other.y);
16
+ }
17
+ sub(other) {
18
+ return new Vector2(this.x - other.x, this.y - other.y);
19
+ }
20
+ scale(scale) {
21
+ return new Vector2(this.x * scale, this.y * scale);
22
+ }
23
+ magnitude() {
24
+ return Math.sqrt(this.x * this.x + this.y * this.y);
25
+ }
26
+ normalize() {
27
+ const mag = this.magnitude();
28
+ return mag > 0 ? new Vector2(this.x / mag, this.y / mag) : new Vector2(0, 0);
29
+ }
30
+ dot(other) {
31
+ return this.x * other.x + this.y * other.y;
32
+ }
33
+ distance(other) {
34
+ return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
35
+ }
36
+ copy() {
37
+ return new Vector2(this.x, this.y);
38
+ }
39
+ lerp(other, scale) {
40
+ return this.add(other.sub(this).scale(scale));
41
+ }
42
+ clamp(maxLength) {
43
+ const mag = this.magnitude();
44
+ return mag > maxLength ? this.scale(maxLength / mag) : this.copy();
45
+ }
46
+ rotate(angle) {
47
+ const cos = Math.cos(angle);
48
+ const sin = Math.sin(angle);
49
+ return new Vector2(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
50
+ }
51
+ angle() {
52
+ return Math.atan2(this.y, this.x);
53
+ }
54
+ angleTo(other) {
55
+ return Math.atan2(other.y - this.y, other.x - this.x);
56
+ }
57
+ reflect(normal) {
58
+ const d = this.dot(normal);
59
+ return this.sub(normal.scale(2 * d));
60
+ }
61
+ equals(other) {
62
+ return this.x === other.x && this.y === other.y;
63
+ }
64
+ }
package/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from "./dist/scene.js";
3
3
  export * from "./dist/entity.js";
4
4
  export * from "./dist/sprite.js";
5
5
  export * from "./dist/input.js";
6
- export * from "./dist/physics.js";
6
+ export * from "./dist/physics.js";
7
+ export * from "./dist/vector.js";
package/index.js CHANGED
@@ -4,3 +4,4 @@ export * from "./dist/entity.js";
4
4
  export * from "./dist/sprite.js";
5
5
  export * from "./dist/input.js";
6
6
  export * from "./dist/physics.js";
7
+ export * from "./dist/vector.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kippy",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Kippy 2D web game engine for JS",
5
5
  "keywords": [
6
6
  "kippy",