sprite refactor
This commit is contained in:
parent
b28a154a92
commit
5deb45001e
BIN
assets/files.png
BIN
assets/files.png
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
14
assets/sprites/shadow-dog/spritesheet.json
Normal file
14
assets/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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
11
assets/sprites/template/spritesheet.json
Normal file
11
assets/sprites/template/spritesheet.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"width": 50,
|
||||||
|
"height": 50,
|
||||||
|
"animations": [
|
||||||
|
{
|
||||||
|
"name": "idle",
|
||||||
|
"y": 0,
|
||||||
|
"frames": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
58
assets/sprites/texture-pack/spritesheet.json
Normal file
58
assets/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
assets/sprites/texture-pack/spritesheet.png
Normal file
BIN
assets/sprites/texture-pack/spritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -1 +1,2 @@
|
|||||||
export const FRAME_RATE = 10;
|
export const FRAME_RATE = 10;
|
||||||
|
export const GRAVITY = 9.8;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {app, BrowserWindow, screen} from "electron";
|
import {app, BrowserWindow, screen} from 'electron';
|
||||||
import * as path from "path";
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
// Window factory
|
// Window factory
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
84
src/npc.ts
Normal file
84
src/npc.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
const {GRAVITY} = require('./dist/constants.js');
|
||||||
|
const {SpriteSheet} = require('./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,7 +1,5 @@
|
|||||||
const {FRAME_RATE} = require('./dist/constants.js');
|
const {FRAME_RATE} = require('./dist/constants.js');
|
||||||
const {UnitController} = require('./dist/unit-controller.js');
|
const {NPC} = require('./dist/npc.js')
|
||||||
const {StaticSprite} = require('./dist/sprites.js');
|
|
||||||
const {sleep} = require('./dist/utils.js');
|
|
||||||
|
|
||||||
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
|
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
@ -13,21 +11,17 @@ canvas.height = screenHeight;
|
|||||||
|
|
||||||
function clearScreen() { ctx.clearRect(0, 0, screenWidth, screenHeight); }
|
function clearScreen() { ctx.clearRect(0, 0, screenWidth, screenHeight); }
|
||||||
|
|
||||||
const sprite = new UnitController(ctx, {
|
const dog = new NPC(ctx, './assets/sprites/shadow-dog/spritesheet.png', '../assets/sprites/shadow-dog/spritesheet.json', {
|
||||||
spritesheetSrc: './assets/shadow_dog.png',
|
bubbleOffset: [50, 75],
|
||||||
spriteAnimations: [
|
scale: 0.25
|
||||||
{name: 'idle', y: 0, frames: 7},
|
});
|
||||||
{name: 'right', y: 3, frames: 9},
|
|
||||||
{name: 'left', y: 3, frames: 9, reverse: true},
|
|
||||||
],
|
|
||||||
spriteScale: 0.5,
|
|
||||||
spriteWidth: 575,
|
|
||||||
spriteHeight: 523,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
let frame = 0, once = true;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
frame++;
|
||||||
clearScreen();
|
clearScreen();
|
||||||
sprite.tick();
|
|
||||||
|
dog.tick();
|
||||||
})
|
})
|
||||||
}, 1000 / FRAME_RATE);
|
}, 1000 / FRAME_RATE);
|
||||||
|
87
src/sprite-sheet.ts
Normal file
87
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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
export type SpriteAnimation = {
|
|
||||||
name: string;
|
|
||||||
y: number;
|
|
||||||
frames: number;
|
|
||||||
reverse?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AnimatedSprite {
|
|
||||||
private animation: any = null;
|
|
||||||
private animationName = '';
|
|
||||||
private frame = -1;
|
|
||||||
private spriteSheet!: HTMLImageElement;
|
|
||||||
|
|
||||||
constructor(private ctx: CanvasRenderingContext2D,
|
|
||||||
public spritesheetSrc: string,
|
|
||||||
public spriteAnimations: SpriteAnimation[],
|
|
||||||
public spriteScale = 1,
|
|
||||||
public spriteWidth = 100,
|
|
||||||
public spriteHeight = 100,
|
|
||||||
) {
|
|
||||||
this.spriteSheet = new Image();
|
|
||||||
this.spriteSheet.src = this.spritesheetSrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(x: number, y: number, animation: string = this.animationName) {
|
|
||||||
if(this.animationName != animation) {
|
|
||||||
this.frame = 0;
|
|
||||||
this.animation = this.spriteAnimations.find((a) => a.name == animation);
|
|
||||||
this.animationName = this.animation.name;
|
|
||||||
} else {
|
|
||||||
this.frame++;
|
|
||||||
if(this.frame >= this.animation.frames) this.frame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.translate(this.spriteWidth * this.spriteScale / 2 * (this.animation.reverse ? 1 : -1), 0);
|
|
||||||
this.ctx.scale(this.animation.reverse ? -1 : 1, 1);
|
|
||||||
this.ctx.drawImage(this.spriteSheet,
|
|
||||||
this.frame * this.spriteWidth, this.animation.y * this.spriteHeight, this.spriteWidth, this.spriteHeight,
|
|
||||||
x * (this.animation.reverse ? -1 : 1), this.ctx.canvas.height - (this.spriteHeight * this.spriteScale + y),
|
|
||||||
this.spriteWidth * this.spriteScale, this.spriteHeight * this.spriteScale);
|
|
||||||
this.ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StaticSprite {
|
|
||||||
private spriteSheet!: HTMLImageElement;
|
|
||||||
|
|
||||||
constructor(private ctx: CanvasRenderingContext2D,
|
|
||||||
public spritesheetSrc: string,
|
|
||||||
public sprite: [number, number],
|
|
||||||
public spriteScale = 1,
|
|
||||||
public spriteWidth = 100,
|
|
||||||
public spriteHeight = 100,
|
|
||||||
) {
|
|
||||||
this.spriteSheet = new Image();
|
|
||||||
this.spriteSheet.src = this.spritesheetSrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(x: number, y: number) {
|
|
||||||
this.ctx.drawImage(this.spriteSheet,
|
|
||||||
this.sprite[0] * this.spriteWidth, this.sprite[1] * this.spriteHeight,
|
|
||||||
this.spriteWidth, this.spriteHeight,
|
|
||||||
x, this.ctx.canvas.height - (this.spriteHeight * this.spriteScale + y),
|
|
||||||
this.spriteWidth * this.spriteScale, this.spriteHeight * this.spriteScale);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
// const {FRAME_RATE} = require('./dist/constants.js');
|
|
||||||
const {AnimatedSprite} = require('./sprites.js');
|
|
||||||
|
|
||||||
export type SpriteOptions = {
|
|
||||||
spritesheetSrc: string,
|
|
||||||
spriteAnimations: any[],
|
|
||||||
spriteScale?: number,
|
|
||||||
spriteWidth?: number,
|
|
||||||
spriteHeight?: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnitController {
|
|
||||||
private sprite!: typeof AnimatedSprite;
|
|
||||||
private random = 0;
|
|
||||||
|
|
||||||
public pos = [0, 0];
|
|
||||||
public vel = [0, 0];
|
|
||||||
|
|
||||||
constructor(private ctx: CanvasRenderingContext2D,
|
|
||||||
private spriteOptions: SpriteOptions,
|
|
||||||
) {
|
|
||||||
this.sprite = new AnimatedSprite(ctx, spriteOptions.spritesheetSrc, spriteOptions.spriteAnimations, spriteOptions.spriteScale, spriteOptions.spriteWidth, spriteOptions.spriteHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
// Randomly move the unit
|
|
||||||
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--;
|
|
||||||
|
|
||||||
// Calculate new position
|
|
||||||
this.pos = [this.pos[0] + this.vel[0], this.pos[1] + (this.vel[1] - 9.8)];
|
|
||||||
if(this.pos[0] < 0) {
|
|
||||||
this.pos[0] = 0;
|
|
||||||
this.vel[0] = 0;
|
|
||||||
} else if(this.pos[0] > this.ctx.canvas.width) {
|
|
||||||
this.pos[0] = this.ctx.canvas.width;
|
|
||||||
this.vel[0] = 0;
|
|
||||||
}
|
|
||||||
if(this.pos[1] < 0) this.pos[1] = 0;
|
|
||||||
|
|
||||||
// Decide on animation
|
|
||||||
let animation = 'idle';
|
|
||||||
if(this.vel[0] < 0) animation = 'left';
|
|
||||||
if(this.vel[0] > 0) animation = 'right';
|
|
||||||
|
|
||||||
// Draw
|
|
||||||
this.sprite.render(this.pos[0], this.pos[1], animation);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user