Compare commits

...

2 Commits

Author SHA1 Message Date
2b87a57d43 doc rotate and stuff 2025-10-15 00:34:54 +02:00
935f99c22d add rotate and stuff 2025-10-15 00:34:45 +02:00
2 changed files with 180 additions and 9 deletions

View File

@ -197,6 +197,22 @@ Registers a gameloop function, that is called on every tick.
See also: [`Models.LoopFunc`](#Models-LoopFunc)
### `width`
```ts
lib.width -> number;
```
Get canvas width in pixels.
### `height`
```ts
lib.width -> number;
```
Get canvas height in pixels.
### `isPressed`
```ts
@ -311,6 +327,22 @@ lib.startGameLoop(function () {
});
```
Note: Sprite images have to be loaded. This is done asynchronously, meaning a sprite might not appear on the canvas after the first call to `drawSprite`.
Note: The sprite images are cached internally on it's `name`, `width` and `height` property. If these are values are different between calls, new sprite images will be loaded. This means that after a resize, the sprite may not appear immediately.
### `drawSpriteRotated`
```ts
lib.drawSpriteRotated(x: number, y: number, width: number, height: number, name: string, angle: number) -> void
```
Same as `drawSprite`, but the sprite is rotate according to the `angle` parameter, specified in radians (meaning a full rotation is `2 * Math.PI`.) The sprite is rotated around the **center**.
Note: Angles have a granularity/precision of 360 steps per rotation. This is becuse sprite images are cached internally on `angle` in addition to `name`, `width` and `height`.
See also [`drawSprite`](#Lib-drawSprite).
### `drawRect`
```ts
@ -335,6 +367,45 @@ lib.startGameLoop(function () {
});
```
### `drawLine`
```ts
lib.drawLine(x0: number, y0: number, x1: number, y1: number, thickness: number, color: string) -> void
```
Draws a line.
### `drawPath`
```ts
lib.drawPath(path: [number, number][], color: string) -> void
```
Fills an arbitrary polygon with [`Color`](#Models-Color).
Example:
```ts
lib.drawPath([
[0, 0],
[10, 0],
[10, 10],
[0, 10],
], "blue");
```
Note: The `path` array must contain at minimum 1 element.
### `drawPathLine`
```ts
lib.drawPathLine(path: [number, number][], thickness: number, color: string) -> void
```
Draws the outline of an arbitrary polygon.
See also [`drawPath`](#Lib-drawPath).
### `onMouseMove`
```ts

View File

@ -106,19 +106,15 @@ export class Gamelib {
ev.preventDefault();
}
drawSprite(x, y, width, height, name) {
const cx = this.cx;
#loadSprite(width, height, name) {
const spriteId = `${name}_${width}x${height}`;
if (this.spriteCache.has(spriteId)) {
const sprite = this.spriteCache.get(spriteId);
cx.drawImage(sprite, x, y);
return;
return this.spriteCache.get(spriteId);
}
// start by caching an empty canvas
const canvas = new OffscreenCanvas(width, height);
this.spriteCache.set(spriteId, canvas);
const sprite = new OffscreenCanvas(width, height);
this.spriteCache.set(spriteId, sprite);
const image = new Image();
image.src = this.assetProvider.url(name);
@ -127,8 +123,60 @@ export class Gamelib {
const spriteCx = sprite.getContext("2d");
spriteCx.drawImage(image, 0, 0);
// does it make sense to draw post fectum?
const invalidatedKeys = this.spriteCache.keys()
.filter((key) => key.startsWith(`${spriteId}r`));
for (const key of invalidatedKeys) {
this.spriteCache.delete(key);
}
};
return sprite;
}
drawSprite(x, y, width, height, name) {
const cx = this.cx;
const sprite = this.#loadSprite(width, height, name);
cx.drawImage(sprite, x, y);
}
drawSpriteRotated(x, y, width, height, name, angle) {
const cx = this.cx;
const angleIncrement = Math.PI * 2 / 360;
const angleNormalized = Math.floor((angle % (Math.PI * 2)) / angleIncrement) *
angleIncrement;
const rotatedSpriteId = `${name}_${width}x${height}r${angleNormalized}`;
if (this.spriteCache.has(rotatedSpriteId)) {
const sprite = this.spriteCache.get(rotatedSpriteId);
cx.drawImage(sprite, x, y);
return;
}
const sprite = this.#loadSprite(width, height, name);
const newSprite = new OffscreenCanvas(sprite.width, sprite.height);
const newSpriteCx = newSprite.getContext("2d");
newSpriteCx.imageSmoothingEnabled = false;
newSpriteCx.save();
newSpriteCx.translate(sprite.width / 2, sprite.height / 2);
newSpriteCx.rotate(angleNormalized);
newSpriteCx.drawImage(
sprite,
-sprite.width / 2,
-sprite.height / 2,
sprite.width,
sprite.height,
);
newSpriteCx.restore();
cx.drawImage(newSprite, x, y);
this.spriteCache.set(rotatedSpriteId, newSprite);
}
clear(color) {
@ -145,6 +193,42 @@ export class Gamelib {
cx.fillRect(x, y, width, height);
}
drawLine(x0, y0, x1, y1, thickness, color) {
const cx = this.cx;
cx.strokeStyle = color;
cx.lineWidth = thickness;
cx.beginPath();
cx.moveTo(x0, y0);
cx.lineTo(x1, y1);
cx.stroke();
}
drawPath(path, color) {
const cx = this.cx;
cx.fillStyle = color;
cx.beginPath();
cx.moveTo(path[0][0], path[0][1]);
for (const [x, y] of path.slice(1)) {
cx.lineTo(x, y);
}
cx.fill();
}
drawPathLine(path, thickness, color) {
const cx = this.cx;
cx.strokeStyle = color;
cx.lineWidth = thickness;
cx.beginPath();
cx.moveTo(path[0][0], path[0][1]);
for (const [x, y] of path.slice(1)) {
cx.lineTo(x, y);
}
cx.stroke();
}
drawText(x, y, text, style = {}) {
const cx = this.cx;
@ -239,6 +323,10 @@ export class GamelibAdapter {
this.gamelib.drawSprite(x, y, width, height, name);
}
drawSpriteRotated(x, y, width, height, name, angle) {
this.gamelib.drawSpriteRotated(x, y, width, height, name, angle);
}
rgb(red, green, blue) {
return `rgb(${red}, ${green}, ${blue})`;
}
@ -251,6 +339,18 @@ export class GamelibAdapter {
this.gamelib.drawRect(x, y, width, height, color);
}
drawLine(x0, y0, x1, y1, thickness, color) {
this.gamelib.drawLine(x0, y0, x1, y1, thickness, color);
}
drawPath(path, color) {
this.gamelib.drawPath(path, color);
}
drawPathLine(path, thickness, color) {
this.gamelib.drawPathLine(path, thickness, color);
}
drawText(x, y, text, style = {}) {
this.gamelib.drawText(x, y, text, style);
}