psd2prefab
Version:
A tool to convert psd to a unity prefab.
243 lines • 9.55 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import env from './Env.js';
export class PrefabGenerator {
async make(psd) {
const instruction = {
name: env.opts.name,
children: [],
width: psd.width,
height: psd.height,
x: 0,
y: 0
};
const simStruct = {
children: []
};
if (psd.children !== undefined) {
if (env.opts.debug !== undefined) {
await fs.ensureDir('test');
}
for (const child of psd.children) {
if (child.hidden === true)
continue;
simStruct.children.push(this.simplify(child));
}
if (env.opts.debug !== undefined) {
await fs.writeJSON(path.join('test', 'simplified.json'), simStruct, { spaces: 2 });
}
for (const child of psd.children) {
if (child.hidden === true)
continue;
await this.collectNodes(psd, instruction, child);
}
if (env.opts.debug !== undefined) {
await fs.writeJSON(path.join('test', 'beforelefttop.json'), simStruct, { spaces: 2 });
}
// convert position relative to parent left top
for (const child of instruction.children) {
this.toPositionRelativeToParentLeftTop(instruction, child);
}
if (env.opts.debug !== undefined) {
await fs.writeJSON(path.join('test', 'beforeshake.json'), instruction, { spaces: 2 });
}
// shake tree
for (let i = instruction.children.length - 1; i >= 0; i--) {
const node = instruction.children[i];
this.treeShake(instruction, i, node);
}
if (env.opts.debug !== undefined) {
await fs.writeJSON(path.join('test', 'beforefixnosize.json'), instruction, { spaces: 2 });
}
// convert position relative to parent center
for (const child of instruction.children) {
this.toPositionRelativeToParentCenter(instruction, child);
}
if (env.opts.debug !== undefined) {
await fs.writeJSON(path.join('test', 'finished.json'), instruction, { spaces: 2 });
}
}
const prefabRoot = path.join(env.opts.project, env.cfg.PREFAB_ROOT);
await fs.ensureDir(prefabRoot);
await fs.writeJSON(path.join(prefabRoot, env.opts.name + '.json'), instruction, { spaces: 2 });
}
simplify(node) {
const snode = { name: node.name, left: node.left, top: node.top, right: node.right, bottom: node.bottom, children: [] };
if (node.children !== undefined) {
for (let i = node.children.length - 1; i >= 0; i--) {
const child = node.children[i];
if (child.hidden === true) {
// remove hidden nodes
node.children.splice(i, 1);
continue;
}
snode.children.push(this.simplify(child));
}
}
return snode;
}
async collectNodes(psd, parentNode, layer) {
// remove blank layer
if (layer.children === undefined && layer.canvas === undefined)
return;
const left = layer.left ?? 0;
const top = layer.top ?? 0;
const right = layer.right ?? 0;
const bottom = layer.bottom ?? 0;
const x = left; // (left + right - psd.width) / 2
const y = top; // -(top + bottom - psd.height) / 2
const width = right - left;
const height = bottom - top;
let type;
if (layer.text !== undefined && !this.isArtFont(layer)) {
type = 'Text';
}
else if (layer.canvas !== undefined) {
type = 'Image';
}
else {
type = 'GameObject';
}
const node = {
type,
width,
height,
x,
y,
children: []
};
if (layer.name !== undefined)
node.name = layer.name;
if (layer.text !== undefined && type === 'Text') {
const textDetail = {
text: layer.text.text
};
if (layer.text.style !== undefined) {
textDetail.style = this.toITextStyle(layer.text.style, layer.text.transform);
}
if (layer.text.styleRuns !== undefined) {
const styleRuns = [];
for (const sr of layer.text.styleRuns) {
styleRuns.push({
length: sr.length,
style: this.toITextStyle(sr.style)
});
}
textDetail.styleRuns = styleRuns;
}
node.text = textDetail;
}
else if (layer.canvas !== undefined) {
const layerAlias = layer;
node.image = layerAlias.pngFile;
}
parentNode.children.push(node);
if (layer.children !== undefined) {
for (const child of layer.children) {
if (child.hidden === true)
continue;
await this.collectNodes(psd, node, child);
}
}
}
treeShake(parent, index, node) {
for (let i = node.children.length - 1; i >= 0; i--) {
const child = node.children[i];
this.treeShake(node, i, child);
}
if (node.width === 0 && node.height === 0 && node.type === 'GameObject') {
if (node.children.length <= 1) {
// single-child node without size can be shake off
console.log('shake off:', node.name);
parent.children.splice(index, 1);
for (const child of node.children) {
child.x += node.x;
child.y += node.y;
parent.children.push(child);
}
}
else {
// replace with the the first child if it's the biggest
const first = node.children[0];
const firstSize = first.width * first.height;
let canShakeOff = true;
for (let i = 1; i < node.children.length; i++) {
const child = node.children[i];
const childSize = child.width * child.height;
if (childSize === firstSize) {
canShakeOff = false;
break;
}
}
if (canShakeOff) {
console.log('shake off and replace with first child:', node.name);
for (let i = 1; i < node.children.length; i++) {
const child = node.children[i];
child.x -= first.x;
child.y -= first.y;
first.children.push(child);
}
first.x += node.x;
first.y += node.y;
parent.children.splice(index, 1, first);
}
else {
// set the size and position equal to the first child
node.width = first.width;
node.height = first.height;
const offsetX = first.x - node.x;
const offsetY = first.y - node.y;
node.x = first.x;
node.y = first.y;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
child.x -= offsetX;
child.y -= offsetY;
}
}
}
}
}
toPositionRelativeToParentLeftTop(parent, node) {
for (const child of node.children) {
this.toPositionRelativeToParentLeftTop(node, child);
}
node.x -= parent.x;
node.y -= parent.y;
}
toPositionRelativeToParentCenter(parent, node) {
for (const child of node.children) {
this.toPositionRelativeToParentCenter(node, child);
}
node.x = (node.x + node.x + node.width - parent.width) / 2;
node.y = -(node.y + node.y + node.height - parent.height) / 2;
}
toITextStyle(style, transform) {
const out = {};
if (style.font !== undefined) {
out.font = style.font.name;
}
if (style.fontSize !== undefined) {
let fontSize = style.fontSize;
if (transform !== undefined) {
// transform is suspected to be [xx, xy, yx, yy, tx, ty]
fontSize = Math.round((fontSize * transform[3]) * 100) * 0.01;
}
out.fontSize = fontSize;
}
if (style.fillColor !== undefined) {
out.color = style.fillColor;
}
if (style.strokeColor !== undefined) {
out.strokeColor = style.strokeColor;
}
return out;
}
isArtFont(layer) {
if (layer.canvas !== undefined && layer.text !== undefined && env.cfg.NOT_ART_FONTS !== undefined && layer.text.style?.font?.name !== undefined) {
return !env.cfg.NOT_ART_FONTS.includes(layer.text.style.font.name);
}
return false;
}
}
//# sourceMappingURL=PrefabGenerator.js.map