Compare commits
1 Commits
develop
...
laptop-bac
Author | SHA1 | Date | |
---|---|---|---|
cece0ee870 |
18901
v2/package-lock.json
generated
Normal file
18901
v2/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
v2/src/constants.ts
Normal file
2
v2/src/constants.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const FRAME_RATE = 10;
|
||||
export const GRAVITY = 9.8;
|
@ -1,12 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Hello World!</title>
|
||||
<title>Desktop Daemon</title>
|
||||
<link rel="icon" type="img/png" src="assets/logo.png">
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>💖 Hello World!</h1>
|
||||
<p>Welcome to your Electron application.</p>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,54 +1,45 @@
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
|
||||
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
|
||||
// whether you're running in development or production).
|
||||
import {app, BrowserWindow, screen} from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
|
||||
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) {
|
||||
// eslint-disable-line global-require
|
||||
app.quit();
|
||||
}
|
||||
if(require('electron-squirrel-startup')) app.quit();
|
||||
|
||||
const createWindow = (): void => {
|
||||
// Create the browser window.
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const height = 250;
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
x: 0,
|
||||
y: primaryDisplay.workArea.height - height,
|
||||
height: height,
|
||||
width: primaryDisplay.size.width,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
roundedCorners: false,
|
||||
resizable: false,
|
||||
fullscreen: false,
|
||||
alwaysOnTop: true,
|
||||
icon: path.join(__dirname, "../assets/logo.png"),
|
||||
title: 'Desktop Daemon',
|
||||
webPreferences: {
|
||||
// nodeIntegration: true,
|
||||
// contextIsolation: false,
|
||||
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
|
||||
},
|
||||
});
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
||||
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
|
84
v2/src/npc.ts
Normal file
84
v2/src/npc.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {GRAVITY} from './constants';
|
||||
import {SpriteSheet} from './sprite-sheet';
|
||||
|
||||
export type NPCOptions = {
|
||||
bubbleOffset?: [number, number];
|
||||
noBounds?: boolean;
|
||||
noGravity?: boolean;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export class NPC {
|
||||
private readonly emojis = [
|
||||
0x1F600, 0x1F601, 0x1F603, 0x1F603, 0x1F604, 0x1F605, 0x1F606,
|
||||
0x1F607, 0x1F609, 0x1F60A, 0x1F642, 0x1F643, 0x1F355, 0x1F354,
|
||||
];
|
||||
private readonly texturePack: any;
|
||||
|
||||
private random: number = 0;
|
||||
private sprite!: any;
|
||||
private text: string;
|
||||
|
||||
public pos = [0, 0];
|
||||
public vel = [0, 0];
|
||||
|
||||
constructor(private readonly ctx: CanvasRenderingContext2D,
|
||||
public readonly spriteSheetPath: string,
|
||||
public readonly spriteDefPath: string,
|
||||
public readonly options: NPCOptions = {},
|
||||
) {
|
||||
this.texturePack = new SpriteSheet(this.ctx,
|
||||
'./assets/sprites/texture-pack/spritesheet.png',
|
||||
'../assets/sprites/texture-pack/spritesheet.json');
|
||||
|
||||
if(this.options.bubbleOffset == null) this.options.bubbleOffset = [0, 0];
|
||||
if(this.options.scale == null) this.options.scale = 1;
|
||||
this.sprite = new SpriteSheet(ctx, spriteSheetPath, spriteDefPath);
|
||||
|
||||
setInterval(() => {
|
||||
this.message(String.fromCodePoint(this.emojis[~~(Math.random() * this.emojis.length)]));
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
animate(name: string, reverse = false) {
|
||||
if(this.text) {
|
||||
const bubble = this.texturePack.definitions.sprites['bubble'];
|
||||
const x = this.pos[0] + this.options.bubbleOffset[0] * (reverse ? -1 : 1);
|
||||
const y = this.pos[1] + this.options.bubbleOffset[1];
|
||||
this.texturePack.drawSprite(x, y, 'bubble', reverse);
|
||||
this.ctx.save();
|
||||
this.ctx.textAlign = 'center';
|
||||
this.ctx.textBaseline = 'middle';
|
||||
this.ctx.font = '24px serif';
|
||||
this.ctx.fillText(this.text, x + (bubble.width / 2 * (reverse ? -1 : 1)), y + (bubble.height / 2));
|
||||
this.ctx.restore();
|
||||
}
|
||||
this.sprite.animate(this.pos[0], this.pos[1], name, reverse, this.options.scale);
|
||||
}
|
||||
|
||||
tick() {
|
||||
if(this.random <= 0) {
|
||||
const move = Math.random();
|
||||
if(move < 0.333) this.vel = [0, 0];
|
||||
else if(move < 0.666) this.vel = [-10, 0];
|
||||
else this.vel = [10, 0];
|
||||
this.random = Math.random() * 50;
|
||||
}
|
||||
this.random--;
|
||||
|
||||
this.pos = [this.pos[0] + this.vel[0], this.pos[1] + this.vel[1]];
|
||||
if(!this.options.noGravity) this.vel[1] -= GRAVITY;
|
||||
if(this.pos[1] < 0) this.pos[1] = 0;
|
||||
if(!this.options.noBounds) {
|
||||
if(this.pos[0] < 0) this.pos[0] = 0;
|
||||
if(this.pos[0] > this.ctx.canvas.width) this.pos[0] = this.ctx.canvas.width;
|
||||
}
|
||||
|
||||
this.animate(this.vel[0] == 0 ? 'idle' : 'walk', this.vel[0] < 0);
|
||||
}
|
||||
|
||||
message(text: string, ms = 5000) {
|
||||
setTimeout(() => this.text = null, ms);
|
||||
this.text = text;
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
// See the Electron documentation for details on how to use preload scripts:
|
||||
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
@ -1,31 +1,28 @@
|
||||
/**
|
||||
* This file will automatically be loaded by webpack and run in the "renderer" context.
|
||||
* To learn more about the differences between the "main" and the "renderer" context in
|
||||
* Electron, visit:
|
||||
*
|
||||
* https://electronjs.org/docs/latest/tutorial/process-model
|
||||
*
|
||||
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
|
||||
* in a renderer process, please be aware of potential security implications. You can read
|
||||
* more about security risks here:
|
||||
*
|
||||
* https://electronjs.org/docs/tutorial/security
|
||||
*
|
||||
* To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
|
||||
* flag:
|
||||
*
|
||||
* ```
|
||||
* // Create the browser window.
|
||||
* mainWindow = new BrowserWindow({
|
||||
* width: 800,
|
||||
* height: 600,
|
||||
* webPreferences: {
|
||||
* nodeIntegration: true
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import './index.css';
|
||||
import {FRAME_RATE} from './constants';
|
||||
import {NPC} from './npc';
|
||||
|
||||
console.log('👋 This message is being logged by "renderer.js", included via webpack');
|
||||
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const screenHeight = window.innerHeight;
|
||||
const screenWidth = window.innerWidth;
|
||||
|
||||
canvas.width = screenWidth;
|
||||
canvas.height = screenHeight;
|
||||
|
||||
function clearScreen() { ctx.clearRect(0, 0, screenWidth, screenHeight); }
|
||||
|
||||
const dog = new NPC(ctx, '/static/sprites/shadow-dog/spritesheet.png', '/static/sprites/shadow-dog/spritesheet.json', {
|
||||
bubbleOffset: [50, 75],
|
||||
scale: 0.25
|
||||
});
|
||||
|
||||
let frame = 0, once = true;
|
||||
setInterval(() => {
|
||||
requestAnimationFrame(() => {
|
||||
frame++;
|
||||
clearScreen();
|
||||
|
||||
dog.tick();
|
||||
})
|
||||
}, 1000 / FRAME_RATE);
|
||||
|
87
v2/src/sprite-sheet.ts
Normal file
87
v2/src/sprite-sheet.ts
Normal file
@ -0,0 +1,87 @@
|
||||
export type SpriteDefinitions = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
animations?: {[key: string]: {y: number, frames: number}};
|
||||
sprites?: {[key: string]: {x: number, y: number, width?: number, height?: number}};
|
||||
}
|
||||
|
||||
export class SpriteSheet {
|
||||
private readonly definitions!: SpriteDefinitions;
|
||||
private readonly spriteSheet!: HTMLImageElement;
|
||||
|
||||
private animation = '';
|
||||
private frame = -1;
|
||||
|
||||
constructor(private readonly ctx: CanvasRenderingContext2D,
|
||||
public readonly spriteSheetPath: string,
|
||||
public readonly spriteDefPath: string,
|
||||
) {
|
||||
this.spriteSheet = new Image();
|
||||
this.spriteSheet.src = spriteSheetPath;
|
||||
this.definitions = require(spriteDefPath);
|
||||
|
||||
// Backfill coordinates if shorthand is used
|
||||
if(this.definitions.sprites?.length) {
|
||||
Object.entries(this.definitions.sprites).forEach(sprite => {
|
||||
const [name, def] = sprite;
|
||||
if(def.width == null) {
|
||||
if(this.definitions.width == null)
|
||||
throw new Error(`Sprite is missing it's width: ${name}`);
|
||||
this.definitions.sprites[name].x = this.definitions.width * def.x;
|
||||
this.definitions.sprites[name].width = this.definitions.width;
|
||||
}
|
||||
if(def.height == null) {
|
||||
if(this.definitions.height == null)
|
||||
throw new Error(`Sprite is missing it's height: ${name}`);
|
||||
this.definitions.sprites[name].y = this.definitions.height * def.y;
|
||||
this.definitions.sprites[name].height = this.definitions.height;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
animate(x: number, y: number, name: string = this.animation, flip = false, scale = 1) {
|
||||
const animation = this.definitions.animations[name];
|
||||
if(!animation) throw new Error(`Animation doesn't exist: ${name}`);
|
||||
if(name == this.animation) {
|
||||
this.frame++;
|
||||
if(this.frame >= animation.frames) this.frame = 0;
|
||||
} else {
|
||||
this.animation = name;
|
||||
this.frame = 0;
|
||||
}
|
||||
this.drawFrame(x, y, this.animation, this.frame, flip, scale);
|
||||
}
|
||||
|
||||
drawFrame(x: number, y: number, name: string, frame: number, flip = false, scale = 1, centerX = true, centerY = false) {
|
||||
const sprite = this.definitions.animations[name];
|
||||
if(!sprite) throw new Error(`Animation doesn't exist: ${name}`);
|
||||
this.ctx.save();
|
||||
this.ctx.translate(centerX ?
|
||||
this.definitions.width * scale / 2 * (flip ? 1 : -1) :
|
||||
flip ? this.definitions.width : 0,
|
||||
centerY ? this.definitions.height / -2 : 0);
|
||||
this.ctx.scale(flip ? -1 : 1, 1);
|
||||
this.ctx.drawImage(this.spriteSheet,
|
||||
frame * this.definitions.width, sprite.y * this.definitions.height, this.definitions.width, this.definitions.height,
|
||||
x * (flip ? -1 : 1), this.ctx.canvas.height - (this.definitions.height * scale + y),
|
||||
this.definitions.width * scale, this.definitions.height * scale);
|
||||
this.ctx.restore();
|
||||
}
|
||||
|
||||
drawSprite(x: number, y: number, name: string, flip = false, scale = 1, centerX = true, centerY = false) {
|
||||
const sprite = this.definitions.sprites[name];
|
||||
if(!sprite) throw new Error(`Sprite doesn't exist: ${name}`);
|
||||
this.ctx.save();
|
||||
this.ctx.translate(centerX ?
|
||||
this.definitions.width * scale / 2 * (flip ? 1 : -1) :
|
||||
flip ? this.definitions.width : 0,
|
||||
centerY ? this.definitions.height / -2 : 0);
|
||||
this.ctx.scale(flip ? -1 : 1, 1);
|
||||
this.ctx.drawImage(this.spriteSheet,
|
||||
sprite.x, sprite.y, sprite.width, sprite.height,
|
||||
x * (flip ? -1 : 1), this.ctx.canvas.height - (sprite.height * scale + y),
|
||||
sprite.width * scale, sprite.height * scale);
|
||||
this.ctx.restore();
|
||||
}
|
||||
}
|
3
v2/src/utils.ts
Normal file
3
v2/src/utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(res => setTimeout(res, ms));
|
||||
}
|
BIN
v2/static/logo.png
Normal file
BIN
v2/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1018 B |
14
v2/static/sprites/shadow-dog/spritesheet.json
Normal file
14
v2/static/sprites/shadow-dog/spritesheet.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"width": 575,
|
||||
"height": 523,
|
||||
"animations": {
|
||||
"idle": {
|
||||
"y": 0,
|
||||
"frames": 6
|
||||
},
|
||||
"walk": {
|
||||
"y": 3,
|
||||
"frames": 8
|
||||
}
|
||||
}
|
||||
}
|
BIN
v2/static/sprites/shadow-dog/spritesheet.png
Normal file
BIN
v2/static/sprites/shadow-dog/spritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
11
v2/static/sprites/template/spritesheet.json
Normal file
11
v2/static/sprites/template/spritesheet.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"animations": [
|
||||
{
|
||||
"name": "idle",
|
||||
"y": 0,
|
||||
"frames": 8
|
||||
}
|
||||
]
|
||||
}
|
BIN
v2/static/sprites/template/spritesheet.png
Normal file
BIN
v2/static/sprites/template/spritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 B |
58
v2/static/sprites/texture-pack/spritesheet.json
Normal file
58
v2/static/sprites/texture-pack/spritesheet.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"sprites": {
|
||||
"generic": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"text": {
|
||||
"x": 100,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"code": {
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"zip": {
|
||||
"x": 300,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"image": {
|
||||
"x": 400,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"audio": {
|
||||
"x": 500,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"video": {
|
||||
"x": 600,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"pdf": {
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"bubble": {
|
||||
"x": 0,
|
||||
"y": 100,
|
||||
"width": 100,
|
||||
"height": 100
|
||||
}
|
||||
}
|
||||
}
|
BIN
v2/static/sprites/texture-pack/spritesheet.png
Normal file
BIN
v2/static/sprites/texture-pack/spritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Loading…
Reference in New Issue
Block a user