feat: custom loaders

This commit is contained in:
aarthificial
2022-03-30 01:57:35 +02:00
parent 13dc24ca69
commit 5a3ab9ad4d
8 changed files with 156 additions and 44 deletions

View File

@@ -8,9 +8,7 @@
"scripts": {
"prepare": "npm run build",
"build": "tsc",
"test:serve": "node ./tools/serve.mjs ./test/player.ts",
"test:render": "node ./tools/serve.mjs ./test/render.ts",
"test:image": "node ./tools/image.mjs ./test/img ./test/animations.json"
"test:serve": "node ./tools/serve.mjs ./test/player.ts"
},
"dependencies": {
"@types/wicg-file-system-access": "^2020.9.5",

View File

@@ -7,7 +7,7 @@ import {AnimatedGetSet, getset, KonvaNode, threadable} from '../decorators';
import {GeneratorHelper} from '../helpers';
import {ImageData} from 'canvas';
interface FrameData {
export interface SpriteData {
fileName: string;
url: string;
data: number[];
@@ -15,18 +15,12 @@ interface FrameData {
height: number;
}
interface SpriteData {
animations: Record<string, {frames: FrameData[]}>;
skins: Record<string, FrameData>;
}
export interface SpriteConfig extends LayoutShapeConfig {
animationData: SpriteData;
animation: string;
skin?: string;
animation: SpriteData[];
skin?: SpriteData;
mask?: SpriteData;
playing?: boolean;
fps?: number;
mask?: string;
maskBlend?: number;
}
@@ -36,21 +30,20 @@ const COMPUTE_CANVAS_SIZE = 1024;
@KonvaNode()
export class Sprite extends LayoutShape {
@getset('', Sprite.prototype.recalculate)
public animation: GetSet<string, this>;
@getset('', Sprite.prototype.recalculate)
public skin: GetSet<string, this>;
@getset(null, Sprite.prototype.recalculate)
public animation: GetSet<SpriteConfig['animation'], this>;
@getset(null, Sprite.prototype.recalculate)
public skin: GetSet<SpriteConfig['skin'], this>;
@getset(null, Sprite.prototype.recalculate)
public mask: GetSet<SpriteConfig['mask'], this>;
@getset(false)
public playing: GetSet<boolean, this>;
public playing: GetSet<SpriteConfig['playing'], this>;
@getset(10)
public fps: AnimatedGetSet<number, this>;
@getset('', Sprite.prototype.recalculate)
public mask: GetSet<string, this>;
@getset('', Sprite.prototype.recalculate)
public maskBlend: AnimatedGetSet<number, this>;
public fps: AnimatedGetSet<SpriteConfig['fps'], this>;
@getset(0, Sprite.prototype.recalculate)
public maskBlend: AnimatedGetSet<SpriteConfig['maskBlend'], this>;
private readonly animationData: SpriteData;
private frame: FrameData = {
private frame: SpriteData = {
height: 0,
width: 0,
url: '',
@@ -62,16 +55,14 @@ export class Sprite extends LayoutShape {
private imageData: ImageData;
private readonly computeCanvas: HTMLCanvasElement;
public get context(): CanvasRenderingContext2D {
return this.computeCanvas.getContext('2d');
}
private readonly context: CanvasRenderingContext2D;
constructor(config?: SpriteConfig) {
super(config);
this.animationData = config.animationData;
this.computeCanvas = Util.createCanvasElement();
this.computeCanvas.width = COMPUTE_CANVAS_SIZE;
this.computeCanvas.height = COMPUTE_CANVAS_SIZE;
this.context = this.computeCanvas.getContext('2d');
this.recalculate();
}
@@ -95,14 +86,14 @@ export class Sprite extends LayoutShape {
}
private recalculate() {
const skin = this.animationData?.skins[this.skin()];
const animation = this.animationData?.animations[this.animation()];
const mask = this.animationData?.skins[this.mask()];
const skin = this.skin();
const animation = this.animation();
const mask = this.mask();
const blend = this.maskBlend();
if (!animation || animation.frames.length === 0) return;
if (!this.context || !animation || animation.length === 0) return;
this.frameId %= animation.frames.length;
this.frame = animation.frames[this.frameId];
this.frameId %= animation.length;
this.frame = animation[this.frameId];
this.offset(this.getOriginOffset());
this.imageData = this.context.createImageData(

12
src/global.d.ts vendored
View File

@@ -1,9 +1,19 @@
declare module "*.png" {
const value: any;
const value: import('./components/Sprite').SpriteData;
export = value;
}
declare module "*.glsl" {
const value: string;
export = value;
}
declare module "*.label" {
const value: Record<string, number>;
export = value;
}
declare module "*.anim" {
const value: import('./components/Sprite').SpriteData[];
export = value;
}

View File

@@ -0,0 +1,29 @@
const path = require('path');
const {readdirSync} = require('fs');
const loadImage = require('../utils/load-image');
const nameRegex = /([^\d]*)\d+\.png$/;
module.exports = function () {
const callback = this.async();
const directoryPath = path.dirname(this.resourcePath);
const files = readdirSync(directoryPath)
.filter(file => nameRegex.test(file))
.map(file => path.resolve(directoryPath, file));
files.forEach(file => this.addDependency(file));
loadAnimation(files)
.then(result => callback(null, result))
.catch(error => callback(error));
};
async function loadAnimation(files) {
const frames = [];
for (const file of files) {
frames.push(await loadImage(file));
}
return `export default ${JSON.stringify(frames)};`;
}

View File

@@ -0,0 +1,24 @@
module.exports = function (source) {
const json = {};
source.split(/\r?\n/).forEach(line => {
if (!line) return;
const parts = line.split('\t');
json[parts[2]] = parseFloat(parts[0]);
});
return `
const json = ${JSON.stringify(json)};
const proxy = new Proxy(json, {
get(target, prop) {
if (target[prop]) {
return target[prop];
} else {
console.warn('Missing label:', prop);
return 0;
}
}
});
export default proxy;
`;
};

View File

@@ -0,0 +1,8 @@
const loadImage = require('../utils/load-image');
module.exports = function () {
const callback = this.async();
loadImage(this.resourcePath)
.then(sprite => callback(null, `export default ${JSON.stringify(sprite)}`))
.catch(error => callback(error));
};

View File

@@ -24,22 +24,38 @@ const compiler = webpack({
}
},
{
test: /\.(png|jpg|gif)$/i,
test: /\.glsl$/i,
type: 'asset/source',
},
{
test: /\.label$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
loader: 'label-loader',
},
],
},
{
test: /\.glsl$/i,
type: 'asset/source',
test: /\.anim$/i,
use: [
{
loader: 'animation-loader',
},
],
},
{
test: /\.png$/i,
use: [
{
loader: 'sprite-loader',
},
],
},
],
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, './loaders')]
},
resolve: {
extensions: ['.js', '.ts', '.tsx'],
alias: {
@@ -64,7 +80,13 @@ const server = new WebpackDevServer(
static: path.resolve(__dirname, '../public'),
compress: true,
port: 9000,
open: true,
setupMiddlewares: (middlewares, devServer) => {
devServer.app.get('/test', (_, response) => {
response.send('test?');
});
return middlewares;
}
},
compiler,
);

30
tools/utils/load-image.js Normal file
View File

@@ -0,0 +1,30 @@
const {promises: fs} = require('fs');
const sizeOf = require('image-size');
const nodeCanvas = require('canvas');
const SIZE = 1024;
const context = nodeCanvas.createCanvas(SIZE, SIZE).getContext('2d');
const image = new nodeCanvas.Image();
module.exports = async function (fileName) {
const data = await fs.readFile(fileName, 'base64');
const dimensions = sizeOf(fileName);
image.src = `data:image/png;base64,${data}`;
context.clearRect(0, 0, SIZE, SIZE);
context.drawImage(image, 0, 0, dimensions.width, dimensions.height);
const imageData = context.getImageData(
0,
0,
dimensions.width,
dimensions.height,
);
return {
fileName,
data: Array.from(imageData.data),
url: `data:image/png;base64,${data}`,
width: dimensions.width,
height: dimensions.height,
};
}