Compare commits

...

10 Commits

Author SHA1 Message Date
Alexander Popov f67def6a8d
add webmanifest 2023-04-30 15:30:09 +03:00
Alexander Popov 28adc41ca4
compile it 2023-04-30 15:26:00 +03:00
Alexander Popov fa74956afa
tiled y sprite, sprite angel 2023-04-23 21:18:27 +03:00
Alexander Popov d2ea90101b
options for scenelayer 2023-04-23 19:41:18 +03:00
Alexander Popov 96c73f49be
add monogram font 2023-04-23 19:28:58 +03:00
Alexander Popov f19dafdd37
update example 2023-04-23 19:13:53 +03:00
Alexander Popov e652dfba1f
clean old draw code 2023-04-23 18:55:22 +03:00
Alexander Popov 4ad80bcc7d
new draw logic 2023-04-23 18:50:36 +03:00
Alexander Popov a4bb1f7dc9
add feedback 2023-04-23 17:11:41 +03:00
Alexander Popov a740e6c863
ticker 2023-04-23 17:01:17 +03:00
19 changed files with 278 additions and 58 deletions

View File

@ -12,7 +12,7 @@ insert_final_newline = true
indent_style = space
indent_size = 2
[{*.html,*.css,*.json}]
[{*.html,*.css,*.json,*.webmanifest}]
indent_style = tab
indent_size = 4
@ -20,6 +20,10 @@ indent_size = 4
indent_style = space
indent_size = 2
[*.webmanifest]
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false

View File

@ -2,3 +2,4 @@
**/node_modules
*.html
*.css

12
README.md Normal file
View File

@ -0,0 +1,12 @@
![engine_icon](/src/icons/apple-touch-icon.png)
## Build `ENGINE`
```sh
# install rollup
npm install --global rollup
# compile engine
cd src/js/
rollup main.js --file engine.js --format es
```

2
src/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/fonts/monogram*
/assets/

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/icons/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
src/icons/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -5,6 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ujs</title>
<link rel="stylesheet" type="text/css" href="styles.css">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<script src="./js/game.js" type="module"></script>
</head>
<body>

2
src/js/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
game.js*
engine.js

62
src/js/game.example.js Normal file
View File

@ -0,0 +1,62 @@
import { App } from './app.js';
import { Scene, SceneLayer } from './scene.js';
import { Rect, StrokeRect, Sprite } from './objects.js';
// init player
let Player = {
x: 400 / 2 - 5,
y: 400 / 2 - 5,
rect: null,
};
Player.rect = new Rect(Player.x, Player.y, 10, 10, 'black');
Player.rect.ticker = () => {
Player.rect.y = Player.y;
Player.rect.x = Player.x;
};
// init scene layers
let layerBg = new SceneLayer('background', [
new Rect(50, 50, 100, 100, 'red'),
new StrokeRect(150, 150, 40, 40, 'green', 'blue', 1),
]);
let layerHud = new SceneLayer('hud', [
new Sprite('compass.png', 15, 15),
new Sprite('compass-arrow.png', 27, 21),
]);
let layerInstances = new SceneLayer('Instances', [Player.rect], { debug: true });
// init app
let app = new App(document.querySelector('canvas'), 400, 400);
// init scene
let firstScene = new Scene(app.canvas, app.context, 400, 400);
firstScene.addLayer(layerBg);
firstScene.addLayer(layerInstances);
firstScene.addLayer(layerHud);
app.scene = firstScene;
// add app view in document
window.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(app.view);
});
// player key press listener
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'ArrowUp':
Player.y -= 1;
break;
case 'ArrowDown':
Player.y += 1;
break;
case 'ArrowLeft':
Player.x -= 1;
break;
case 'ArrowRight':
Player.x += 1;
break;
}
});

View File

@ -1,19 +0,0 @@
import { App } from './app.js';
import { Scene, SceneLayer } from './scene.js';
import { Rect, StrokeRect } from './objects.js';
let app = new App(document.querySelector('canvas'), 400, 400);
let firstScene = new Scene(app.canvas, app.context, 400, 400);
let firstLayer = new SceneLayer('background', [
new Rect(50, 50, 100, 100, 'red'),
new StrokeRect(150, 150, 40, 40, 'green', 'blue', 1),
]);
firstScene.addLayer(firstLayer);
app.scene = firstScene;
// add app view in document
window.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(app.view);
});

View File

@ -1,25 +1,45 @@
import { Scene } from './scene.js';
import { Settings } from './settings.js';
import { Pointer } from './pointer.js';
import { Scene, SceneLayer } from './scene.js';
import { Rect, StrokeRect, Sprite, TiledSprite } from './objects.js';
export class App {
#version;
constructor(
canvas,
w,
h,
options = {
backgroundColor: '#ffcc68',
welcome: true,
}
) {
this.#version = '0.1.0';
this.view = document.createElement('canvas');
this.canvas = this.view;
this.context = this.canvas.getContext('2d');
this.options = options;
// check options || FIXIT:
if (typeof this.options.backgroundColor === 'undefined') {
this.options.backgroundColor = '#ffcc68';
}
if (typeof this.options.welcome === 'undefined') {
this.options.welcome = true;
}
this.scene = new Scene(this.canvas, this.context, w, h);
this.prevTime = Date.now();
if (this.options.welcome) {
console.log('ujs engine');
console.log('version:', this.#version);
console.log('feedback:', 'iiiypuk {dog} fastmail.fm');
}
Pointer.init();
this.run();
@ -47,3 +67,5 @@ export class App {
requestAnimationFrame(this.run);
};
}
export { Scene, SceneLayer, Rect, StrokeRect, Sprite, TiledSprite };

View File

@ -4,6 +4,7 @@ class Object {
this.y = y;
this.width = w;
this.height = h;
this.ticker = null;
}
}
@ -13,6 +14,15 @@ export class Rect extends Object {
this.fillColor = fillColor;
}
draw(context, debug = false) {
context.fillStyle = this.fillColor;
context.fillRect(this.x, this.y, this.width, this.height);
if (debug) {
drawDebug(context, this.x, this.y, this.width, this.height);
}
}
}
export class StrokeRect extends Rect {
@ -22,4 +32,109 @@ export class StrokeRect extends Rect {
this.strokeWidth = strokeWidth;
this.strokeColor = strokeColor;
}
draw(context, debug = false) {
context.fillStyle = this.fillColor;
context.fillRect(this.x, this.y, this.width, this.height);
context.lineWidth = this.strokeWidth;
context.strokeStyle = this.strokeColor;
context.strokeRect(this.x, this.y, this.width, this.height);
if (debug) {
drawDebug(context, this.x, this.y, this.width, this.height);
}
}
}
export class Sprite {
#loaded;
#image;
constructor(src = null, x, y) {
this.x = x;
this.y = y;
this.width = null;
this.height = null;
this.angle = 0;
this.loaded = false;
this.#image = new Image();
this.#image.onload = this.#onload();
this.#image.src = src;
}
#onload() {
this.width = this.#image.width;
this.height = this.#image.height;
this.loaded = true;
}
image() {
return this.#image;
}
draw(context, debug = false) {
if (this.loaded != true) {
return -1;
}
context.save();
context.translate(this.x + this.#image.width / 2, this.y + this.#image.height / 2);
context.rotate((this.angle * Math.PI) / 180);
context.drawImage(this.#image, -this.#image.width / 2, -this.#image.height / 2);
context.restore();
if (debug) {
drawDebug(context, this.x, this.y, this.#image.width, this.#image.height);
}
}
}
export class TiledSprite extends Sprite {
constructor(src = null, x, y) {
super(src, x, y);
}
draw(context, debug = false) {
if (this.loaded != true) {
return -1;
}
context.drawImage(this.image(), this.x, this.y);
let copyUpByY = Math.ceil(this.y / this.image().height);
let copyDownByY = Math.ceil(
(context.canvas.height - this.y - this.image().height) / this.image().height
);
for (let i = 0; i <= copyUpByY; i++) {
if (copyUpByY === Infinity) {
break;
}
context.drawImage(this.image(), this.x, this.y - this.image().height * i);
}
for (let i = 0; i <= copyDownByY; i++) {
if (copyDownByY === Infinity) {
break;
}
context.drawImage(this.image(), this.x, this.y + this.image().height * i);
}
}
}
function drawDebug(context, x, y, w, h) {
context.font = '16px Monogram';
context.fillStyle = '#ffffff';
context.fillText(`${x}x${y}`, x + 1, y - 2);
context.fillStyle = '#000000';
context.fillText(`${x}x${y}`, x, y - 3);
context.lineWidth = 1;
context.strokeStyle = 'blue';
context.strokeRect(x, y, w, h);
}

View File

@ -14,50 +14,21 @@ export class Scene {
}
run() {
this.#scene();
}
#scene() {
// read layers & draw items
this.#layers.forEach((layer) => {
layer.objects().forEach((item) => {
switch (item.constructor.name) {
case 'Rect':
this.#drawRect(item.x, item.y, item.width, item.height, item.fillColor);
break;
if (typeof item.ticker == 'function') {
item.ticker();
}
case 'StrokeRect':
this.#drawStrokeRect(
item.x,
item.y,
item.width,
item.height,
item.fillColor,
item.strokeColor,
item.strokeWidth
);
break;
default:
console.log(`⛔ Error display '${item.constructor.name}' object.`);
if (typeof item.draw == 'function') {
item.draw(this.#context, layer.options.debug);
} else {
console.log(`⛔ Error display '${item.constructor.name}' object.`);
}
});
});
}
#drawRect(x, y, w, h, fillColor) {
this.#context.fillStyle = fillColor;
this.#context.fillRect(x, y, w, h);
}
#drawStrokeRect(x, y, w, h, fillColor, strokeColor, strokeWidth) {
this.#drawRect(x, y, w, h, fillColor);
this.#context.lineWidth = strokeWidth;
this.#context.strokeStyle = strokeColor;
this.#context.strokeRect(x, y, w, h);
}
addLayer(layer) {
this.#layers.push(layer);
}
@ -66,6 +37,7 @@ export class Scene {
this.#layers = Array();
}
// TODO: remove it
setScreenSize(w, h) {
this.#canvas.width = w;
this.#canvas.height = h;
@ -75,9 +47,21 @@ export class Scene {
export class SceneLayer {
#objects;
constructor(name, objects = Array()) {
constructor(
name,
objects = Array(),
options = {
debug: false,
}
) {
// TODO: check types
this.#objects = Array();
this.options = options;
// check options || FIXIT:
if (typeof this.options.debug === 'undefined') {
this.options.debug = false;
}
objects.forEach((object) => {
this.#objects.push(object);

11
src/site.webmanifest Normal file
View File

@ -0,0 +1,11 @@
{
"name": "",
"short_name": "",
"icons": [
{ "src": "/icons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -0,0 +1,20 @@
@font-face {
font-family: 'Monogram';
src: url('/fonts/monogramextended.woff2') format('woff2'),
url('/fonts/monogramextended.woff') format('woff'),
url('/fonts/monogramextended.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
body {
display: flex;
gap: 8px;
justify-content: center;
}
canvas {
image-rendering: crisp-edges;
image-rendering: pixelated;
}