Compare commits
10 Commits
74e9b34253
...
f67def6a8d
Author | SHA1 | Date |
---|---|---|
Alexander Popov | f67def6a8d | |
Alexander Popov | 28adc41ca4 | |
Alexander Popov | fa74956afa | |
Alexander Popov | d2ea90101b | |
Alexander Popov | 96c73f49be | |
Alexander Popov | f19dafdd37 | |
Alexander Popov | e652dfba1f | |
Alexander Popov | 4ad80bcc7d | |
Alexander Popov | a4bb1f7dc9 | |
Alexander Popov | a740e6c863 |
|
@ -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
|
||||
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
**/node_modules
|
||||
|
||||
*.html
|
||||
*.css
|
||||
|
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
/fonts/monogram*
|
||||
/assets/
|
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 |
Binary file not shown.
After Width: | Height: | Size: 766 B |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
game.js*
|
||||
engine.js
|
|
@ -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;
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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 };
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue