feat: fields and colors
This commit is contained in:
parent
a4949bd7a0
commit
30caab6c9e
|
@ -1,9 +1,13 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
import {hexToHsl, hslToHex, hslToRgb, rgbToHsl} from '@/util/color.js';
|
||||
|
||||
export default class Sprite extends Component {
|
||||
instanceFromSchema() {
|
||||
return class SpriteInstance extends super.instanceFromSchema() {
|
||||
$$anchor = {x: 0.5, y: 0.5};
|
||||
$$hue = 0;
|
||||
$$saturation = 0;
|
||||
$$lightness = 1;
|
||||
$$scale = {x: 1, y: 1};
|
||||
$$sourceJson = {};
|
||||
get anchor() {
|
||||
|
@ -59,6 +63,20 @@ export default class Sprite extends Component {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
get hue() {
|
||||
return this.$$hue;
|
||||
}
|
||||
set hue(hue) {
|
||||
const [, s, l] = hexToHsl(this.$$tint);
|
||||
this.tint = hslToHex(hue, s, l);
|
||||
}
|
||||
get lightness() {
|
||||
return this.$$lightness;
|
||||
}
|
||||
set lightness(lightness) {
|
||||
const [h, s] = hexToHsl(this.$$tint);
|
||||
this.tint = hslToHex(h, s, lightness);
|
||||
}
|
||||
get rotates() {
|
||||
if (!this.$$sourceJson.meta) {
|
||||
return false;
|
||||
|
@ -71,6 +89,13 @@ export default class Sprite extends Component {
|
|||
}
|
||||
return this.$$sourceJson.meta.rotation;
|
||||
}
|
||||
get saturation() {
|
||||
return this.$$saturation;
|
||||
}
|
||||
set saturation(saturation) {
|
||||
const [h, , l] = hexToHsl(this.$$tint);
|
||||
this.tint = hslToHex(h, saturation, l);
|
||||
}
|
||||
get scale() {
|
||||
return this.$$scale;
|
||||
}
|
||||
|
@ -97,6 +122,17 @@ export default class Sprite extends Component {
|
|||
: '';
|
||||
return this.$$sourceJson.frames[frame].sourceSize;
|
||||
}
|
||||
get tint() {
|
||||
return super.tint;
|
||||
}
|
||||
set tint(tint) {
|
||||
[
|
||||
this.$$hue,
|
||||
this.$$saturation,
|
||||
this.$$lightness,
|
||||
] = hexToHsl(tint)
|
||||
super.tint = tint;
|
||||
}
|
||||
toNet(recipient, data) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {elapsed, ...rest} = super.toNet(recipient, data);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import K from 'kefir';
|
||||
|
||||
import * as easings from '@/util/easing.js';
|
||||
|
||||
export default class Emitter {
|
||||
constructor(ecs) {
|
||||
this.ecs = ecs;
|
||||
this.scheduled = [];
|
||||
}
|
||||
async allocate({entity, shape}) {
|
||||
async allocate({entity, fields, shape}) {
|
||||
const allocated = this.ecs.get(await this.ecs.create(entity));
|
||||
if (shape) {
|
||||
switch (shape.type) {
|
||||
|
@ -16,6 +18,19 @@ export default class Emitter {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (fields) {
|
||||
for (const {easing = 'linear', path, value} of fields) {
|
||||
let walk = allocated;
|
||||
const pathCopy = path.slice(0);
|
||||
const final = pathCopy.pop();
|
||||
for (const key of pathCopy) {
|
||||
walk = walk[key];
|
||||
}
|
||||
const c = Math.random() * (value.length - 1);
|
||||
const i = Math.floor(c);
|
||||
walk[final] = easings[easing](c - i, value[i], value[i + 1] - value[i], 1);
|
||||
}
|
||||
}
|
||||
return allocated;
|
||||
}
|
||||
emit(particle) {
|
||||
|
|
61
app/util/color.js
Normal file
61
app/util/color.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
const {min, max, round} = Math;
|
||||
|
||||
export function hslToHex(h, s, l) {
|
||||
const [r, g, b] = hslToRgb(h, s, l);
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
export function hslToRgb(h, s, l) {
|
||||
let r, g, b;
|
||||
if (s === 0) {
|
||||
r = g = b = l;
|
||||
}
|
||||
else {
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = hueToRgb(p, q, h + 1/3);
|
||||
g = hueToRgb(p, q, h);
|
||||
b = hueToRgb(p, q, h - 1/3);
|
||||
}
|
||||
return [round(r * 255), round(g * 255), round(b * 255)];
|
||||
}
|
||||
|
||||
function hueToRgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||
if (t < 1/2) return q;
|
||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
export function hexToHsl(i) {
|
||||
const [r, g, b] = hexToRgb(i);
|
||||
return rgbToHsl(r, g, b);
|
||||
}
|
||||
|
||||
export function hexToRgb(i) {
|
||||
const o = i > 16777215 ? 8 : 0;
|
||||
return [
|
||||
(i >> o + 16) & 255,
|
||||
(i >> o + 8 ) & 255,
|
||||
(i >> o + 0 ) & 255,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export function rgbToHsl(r, g, b) {
|
||||
(r /= 255), (g /= 255), (b /= 255);
|
||||
const vmax = max(r, g, b), vmin = min(r, g, b);
|
||||
let h, s, l = (vmax + vmin) / 2;
|
||||
if (vmax === vmin) {
|
||||
return [0, 0, l];
|
||||
}
|
||||
const d = vmax - vmin;
|
||||
s = l > 0.5 ? d / (2 - vmax - vmin) : d / (vmax + vmin);
|
||||
if (vmax === r) h = (g - b) / d + (g < b ? 6 : 0);
|
||||
if (vmax === g) h = (b - r) / d + 2;
|
||||
if (vmax === b) h = (r - g) / d + 4;
|
||||
h /= 6;
|
||||
return [h, s, l];
|
||||
}
|
|
@ -2,6 +2,7 @@ import {parse as acornParse} from 'acorn';
|
|||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
import Sandbox from '@/astride/sandbox.js';
|
||||
import * as color from '@/util/color.js';
|
||||
import delta from '@/util/delta.js';
|
||||
import lfo from '@/util/lfo.js';
|
||||
import * as MathUtil from '@/util/math.js';
|
||||
|
@ -40,6 +41,7 @@ export default class Script {
|
|||
|
||||
static contextDefaults() {
|
||||
return {
|
||||
color,
|
||||
console,
|
||||
delta,
|
||||
lfo,
|
||||
|
|
|
@ -13,14 +13,32 @@ if (projected?.length > 0) {
|
|||
Sound.play('/assets/hoe/dig.wav');
|
||||
for (const {x, y} of projected) {
|
||||
Emitter.emit({
|
||||
count: 25,
|
||||
frequency: 0.01,
|
||||
count: 75,
|
||||
fields: [
|
||||
{
|
||||
path: ['Sprite', 'lightness'],
|
||||
value: [0.05, 0.25],
|
||||
},
|
||||
{
|
||||
path: ['Sprite', 'alpha'],
|
||||
value: [0.3, 1],
|
||||
},
|
||||
{
|
||||
path: ['Sprite', 'scaleX'],
|
||||
value: [0.05, 0.3],
|
||||
},
|
||||
{
|
||||
path: ['Sprite', 'scaleY'],
|
||||
value: [0.05, 0.3],
|
||||
},
|
||||
],
|
||||
frequency: 0.004,
|
||||
shape: {
|
||||
type: 'filledRect',
|
||||
payload: {width: 16, height: 16},
|
||||
},
|
||||
entity: {
|
||||
Forces: {forceY: -50},
|
||||
Forces: {forceY: -40},
|
||||
Position: {
|
||||
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
|
||||
y: y * layer.tileSize.y + (layer.tileSize.y / 2),
|
||||
|
|
|
@ -14,24 +14,30 @@ if (projected?.length > 0) {
|
|||
|
||||
for (const {x, y} of projected) {
|
||||
Emitter.emit({
|
||||
count: 25,
|
||||
frequency: 0.01,
|
||||
count: 100,
|
||||
fields: [
|
||||
{
|
||||
path: ['Sprite', 'lightness'],
|
||||
value: [0.111, 0.666],
|
||||
},
|
||||
],
|
||||
frequency: 0.005,
|
||||
shape: {
|
||||
type: 'filledRect',
|
||||
payload: {width: 16, height: 16},
|
||||
},
|
||||
entity: {
|
||||
Forces: {forceY: 100},
|
||||
Forces: {forceY: 400},
|
||||
Position: {
|
||||
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
|
||||
y: y * layer.tileSize.y - layer.tileSize.y,
|
||||
},
|
||||
Sprite: {
|
||||
scaleX: 0.2,
|
||||
scaleY: 0.2,
|
||||
scaleX: 0.1,
|
||||
scaleY: 0.3,
|
||||
tint: 0x0022aa,
|
||||
},
|
||||
Ttl: {ttl: 0.25},
|
||||
Ttl: {ttl: 0.0625},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user