Last active
March 19, 2018 23:16
-
-
Save tuaplicacionpropia/8839638f6c97ecc1e53d89396d5589ef to your computer and use it in GitHub Desktop.
Create Character from spritesheet to godot (special craftpix)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#coding:utf-8 | |
from __future__ import print_function | |
from PIL import Image | |
import math | |
import os | |
import tempfile | |
import shutil | |
from threading import Thread | |
import sys | |
import libarchive.public | |
import shutil | |
import psd_tools | |
import itertools | |
ANIM_LIST = ["alert", "hurt", "idle", "dead", "attack", "jump", "run", "walk"] | |
ANIM_SYNONYMS = { | |
"hit": "hurt", | |
"die": "dead", | |
"stand": "idle" | |
} | |
class FileTools: | |
@staticmethod | |
def createTmpFile (filename): | |
result = None | |
tmpdir = tempfile.mkdtemp(prefix='.tmp_') | |
result = os.path.join(tmpdir, filename) | |
return result | |
@staticmethod | |
def createTmpFolder (folderName): | |
result = None | |
tmpdir = tempfile.mkdtemp(prefix='.tmp_') | |
directory = os.path.join(tmpdir, folderName) | |
if not os.path.exists(directory): | |
os.makedirs(directory) | |
result = directory | |
return result | |
@staticmethod | |
def getFileBasename (fullPath): | |
result = None | |
fullName = os.path.basename(fullPath) | |
result = os.path.splitext(fullName)[0] | |
return result | |
@staticmethod | |
def getFileName (fullPath): | |
result = None | |
result = os.path.basename(fullPath) | |
return result | |
@staticmethod | |
def existsFile (path): | |
result = False | |
if (os.path.isfile(path)): | |
result = True | |
return result | |
@staticmethod | |
def remove (path): | |
result = False | |
if (os.path.isfile(path)): | |
os.remove(path) | |
elif (os.path.isdir(path)): | |
shutil.rmtree(path) | |
if not os.path.exists(path): | |
result = True | |
return result | |
@staticmethod | |
def getFolder (path): | |
result = None | |
result = os.path.dirname(path) | |
return result | |
@staticmethod | |
def mkdir (parent, folderName): | |
result = None | |
folder = os.path.join(parent, folderName) | |
if not os.path.exists(folder): | |
os.makedirs(folder) | |
result = folder | |
return result | |
@staticmethod | |
def copyFile (path): | |
result = None | |
result = FileTools.createTmpFile(FileTools.getFileName(path)) | |
shutil.copy2(path, result) | |
return result | |
@staticmethod | |
def decompress(path): | |
result = None | |
folder = FileTools.createTmpFolder("tmpzip") | |
#print("folder " + folder) | |
for entry in libarchive.public.file_pour(path): | |
print(entry) | |
itemFile = os.path.join(folder, str(entry)) | |
#print("middle= " + itemFile) | |
if itemFile.find("/") >= 0: | |
FileTools.mkdir(folder, os.path.dirname(itemFile)) | |
if not itemFile.endswith("/"): | |
#print("writing... " + itemFile) | |
out = open(itemFile, 'wb') | |
for block in entry.get_blocks(): | |
out.write(block) | |
result = folder | |
if path.endswith("tar.7z"): | |
newFile = os.path.join(folder, FileTools.getFileBasename(path)) | |
result = FileTools.decompress(newFile) | |
elif path.endswith("t7z"): | |
newFile = os.path.join(folder, FileTools.getFileBasename(path) + ".tar") | |
result = FileTools.decompress(newFile) | |
return result | |
@staticmethod | |
def _checkProcessing (path, array): | |
result = False | |
if (path != None and array != None): | |
for item in array: | |
if (path.find(item) == 0): | |
result = True | |
break | |
return result | |
@staticmethod | |
def getFileSize (path): | |
result = 0L | |
if (path != None): | |
result = os.stat(path).st_size | |
return result | |
class Character: | |
def __init__ (self): | |
pass | |
def loadMaxFolderAnim (self, folder): | |
result = [0, 0] | |
if os.path.isdir(folder): | |
maxWidth = 0 | |
maxHeight = 0 | |
for filename in os.listdir(folder): | |
if filename.endswith(".png"): | |
path = os.path.join(folder, filename) | |
im = Image.open(path) | |
maxWidth = max(maxWidth, im.width) | |
maxHeight = max(maxHeight, im.height) | |
im.close() | |
result = [maxWidth, maxHeight] | |
return result | |
def adjustFolderAnim (self, folder, maxWH=None): | |
if os.path.isdir(folder): | |
if maxWH == None: | |
maxWH = self.loadMaxFolderAnim(folder) | |
maxWidth = maxWH[0] | |
maxHeight = maxWH[1] | |
for filename in os.listdir(folder): | |
if filename.endswith(".png"): | |
path = os.path.join(folder, filename) | |
im = Image.open(path) | |
newImage = Image.new("RGBA", (maxWidth, maxHeight)) | |
newY = maxHeight - im.height | |
newX = (maxWidth - im.width) / 2 | |
newImage.paste(im, (newX, newY)) | |
im.close() | |
newImage.save(path) | |
newImage.close() | |
def adjustFoldersAnim (self, folder): | |
maxWH = [0, 0] | |
for filename in os.listdir(folder): | |
path = os.path.join(folder, filename) | |
if os.path.isdir(path): | |
folderMaxWH = self.loadMaxFolderAnim(path) | |
maxWH = [max(maxWH[0], folderMaxWH[0]), max(maxWH[1], folderMaxWH[1])] | |
for filename in os.listdir(folder): | |
path = os.path.join(folder, filename) | |
if os.path.isdir(path): | |
self.adjustFolderAnim(path, maxWH) | |
def copyAllFolderAnim (self, folder, target): | |
if os.path.isdir(folder): | |
sprites = [] | |
for filename in os.listdir(folder): | |
if filename.endswith(".png"): | |
sprites.append(filename) | |
sprites.sort() | |
if len(sprites) > 0: | |
if not os.path.exists(target): | |
os.makedirs(target) | |
idx = 0 | |
for sprite in sprites: | |
spritePath = os.path.join(folder, sprite) | |
spriteTargetName = str(idx) + ".png" | |
spriteTarget = os.path.join(target, spriteTargetName) | |
self.autocrop(spritePath, spriteTarget) | |
#shutil.copy2(spritePath, spriteTarget) | |
idx += 1 | |
def copyAllFoldersAnim (self, folder, target): | |
for filename in os.listdir(folder): | |
path = os.path.join(folder, filename) | |
if (os.path.isdir(path)): | |
folderAnim = os.path.join(target, filename) | |
self.copyAllFolderAnim(path, folderAnim) | |
def copyMainSprite (self, target): | |
targetCharacter = os.path.join(target, "character.png") | |
idleCharacter = os.path.join(os.path.join(target, "idle"), "0.png") | |
if os.path.exists(idleCharacter): | |
self.autocrop(idleCharacter, targetCharacter) | |
#shutil.copy2(idleCharacter, targetCharacter) | |
else: | |
for filename in os.listdir(target): | |
path = os.path.join(target, filename) | |
if (os.path.isdir(path)): | |
otherCharacter = os.path.join(path, "0.png") | |
if os.path.exists(otherCharacter): | |
self.autocrop(otherCharacter, targetCharacter) | |
#shutil.copy2(otherCharacter, targetCharacter) | |
break | |
def copyShadowSprite (self, target): | |
folderScript = os.path.dirname(os.path.abspath(__file__)) | |
shadowSprite = os.path.join(folderScript, "shadow.png") | |
shadowTarget = os.path.join(target, "shadow.png") | |
shutil.copy2(shadowSprite, shadowTarget) | |
def generateAnim (self, folderAnim, target): | |
result = None | |
numSprites = 0 | |
for filename in os.listdir(folderAnim): | |
if filename.endswith(".png"): | |
numSprites += 1 | |
durationSprite = 0.07 | |
lengthSprites = durationSprite * float(numSprites) | |
spritesName = FileTools.getFileBasename(target)#"zombie1" | |
animName = FileTools.getFileBasename(folderAnim)#"idle" | |
checkAttack = False | |
if animName.find("attack") >= 0: | |
checkAttack = True | |
checkDead = False | |
if animName.find("dead") >= 0: | |
checkDead = True | |
checkHurt = False | |
if animName.find("hurt") >= 0: | |
checkHurt = True | |
checkJump = False | |
if animName.find("jump") >= 0: | |
checkJump = True | |
result = os.path.join(target, "anim_" + animName + ".tres") | |
f = open(result, 'w') | |
f.write('[gd_resource type="Animation" load_steps=' + str(numSprites + 1) + ' format=1]\n') | |
f.write('\n') | |
for i in range(numSprites): | |
sid = str(i + 1) | |
f.write('[ext_resource path="res://art/' + spritesName + '/' + animName + '/' + str(i)+ '.png" type="Texture" id=' + sid + ']\n') | |
f.write('\n') | |
f.write('[resource]\n') | |
f.write('\n') | |
f.write('resource/name = "' + animName + '"\n') | |
f.write('length = ' + str(lengthSprites) + '\n') | |
f.write('loop = ' + ('false' if (checkAttack or checkDead or checkHurt or checkJump) else 'true') + '\n') | |
f.write('step = ' + str(durationSprite) + '\n') | |
f.write('tracks/0/type = "value"\n') | |
f.write('tracks/0/path = NodePath("sprite:texture")\n') | |
f.write('tracks/0/interp = 1\n') | |
f.write('tracks/0/imported = false\n') | |
f.write('tracks/0/keys = {\n') | |
times = ""#0, 0.07, 0.14, 0.21, 0.28, 0.35 | |
transitions = ""#1, 1, 1, 1, 1, 1 | |
values = ""#ExtResource( 1 ), ExtResource( 2 ), ExtResource( 3 ), ExtResource( 4 ), ExtResource( 5 ), ExtResource( 6 ) | |
for i in range(numSprites): | |
newTime = durationSprite * float(i) | |
sid = str(i + 1) | |
times += (', ' if len(times) > 0 else '') + str(newTime) | |
transitions += (', ' if len(transitions) > 0 else '') + '1' | |
values += (', ' if len(values) > 0 else '') + 'ExtResource( ' + sid + ' )' | |
f.write('"times": FloatArray( ' + times + ' ),\n') | |
f.write('"transitions": FloatArray( ' + transitions + ' ),\n') | |
f.write('"update": 1,\n') | |
f.write('"values": [ ' + values + ' ]\n') | |
f.write('}\n') | |
if checkAttack: | |
f.write('tracks/1/type = "method"\n') | |
f.write('tracks/1/path = NodePath(".")\n') | |
f.write('tracks/1/interp = 1\n') | |
f.write('tracks/1/imported = false\n') | |
f.write('tracks/1/keys = {\n') | |
times = ""#0.14, 0.21, 0.28 | |
if numSprites > 5: | |
middleSprite = (numSprites / 2) | |
offsetSprite = durationSprite * float(middleSprite - 1) | |
times += str(offsetSprite) | |
times += ', ' | |
offsetSprite = durationSprite * float(middleSprite) | |
times += str(offsetSprite) | |
times += ', ' | |
offsetSprite = durationSprite * float(middleSprite + 1) | |
times += str(offsetSprite) | |
else: | |
middleSprite = (numSprites / 2) | |
offsetSprite = durationSprite * float(middleSprite) | |
times = str(offsetSprite) | |
f.write('"times": FloatArray( ' + times + ' ),\n') | |
f.write('"transitions": FloatArray( 1 ),\n') | |
f.write('"values": [ {\n') | |
f.write('"args": [ ],\n') | |
f.write('"method": "hit"\n') | |
f.write('} ]\n') | |
f.write('}\n') | |
f.close() | |
return result | |
def generateAnims (self, target): | |
for filename in os.listdir(target): | |
path = os.path.join(target, filename) | |
if (os.path.isdir(path)): | |
folderAnim = os.path.join(target, filename) | |
self.generateAnim(folderAnim, target) | |
def generateCharacter (self, folder): | |
result = None | |
name = FileTools.getFileBasename(folder) | |
anims = [] | |
for filename in os.listdir(folder): | |
path = os.path.join(folder, filename) | |
if filename.find("anim_") == 0 and filename.endswith(".tres"): | |
animName = filename[len("anim_"):] | |
animName = animName[0:len(animName)-len(".tres")] | |
anims.append(animName) | |
anims.sort() | |
result = os.path.join(folder, "character_" + name + ".tscn") | |
f = open(result, 'w') | |
f.write('[gd_scene load_steps=' + str(3 + len(anims) + 1) + ' format=1]\n') | |
f.write('\n') | |
f.write('[ext_resource path="res://character.tscn" type="PackedScene" id=1]\n') | |
f.write('[ext_resource path="res://art/' + name + '/shadow.png" type="Texture" id=2]\n') | |
f.write('[ext_resource path="res://art/' + name + '/character.png" type="Texture" id=3]\n') | |
idx = 4 | |
for animName in anims: | |
f.write('[ext_resource path="res://art/' + name + '/anim_' + animName + '.tres" type="Animation" id=' + str(idx) + ']\n') | |
idx += 1 | |
f.write('\n') | |
f.write('[node name="character" instance=ExtResource( 1 )]\n') | |
f.write('\n') | |
f.write('spritesheet = "' + name + '"\n') | |
f.write('transform/pos = Vector2( 0.0, 0.0 )\n') | |
f.write('\n') | |
f.write('[node name="shadow" parent="."]\n') | |
f.write('\n') | |
f.write('transform/pos = Vector2( -29.7565, 104.648 )\n') | |
f.write('texture = ExtResource( 2 )\n') | |
f.write('\n') | |
f.write('[node name="sprite" parent="."]\n') | |
f.write('\n') | |
f.write('transform/pos = Vector2( 1.36035, -6.29718 )\n') | |
f.write('texture = ExtResource( 3 )\n') | |
f.write('\n') | |
f.write('[node name="feet" parent="."]\n') | |
f.write('\n') | |
f.write('transform/pos = Vector2( -23.6395, 109.119 )\n') | |
f.write('\n') | |
f.write('[node name="animation" parent="."]\n') | |
f.write('\n') | |
idx = 4 | |
for animName in anims: | |
f.write('anims/' + animName + ' = ExtResource( ' + str(idx) + ' )\n') | |
idx += 1 | |
f.write('\n') | |
f.write('[node name="weapon_attack" parent="hitbox"]\n') | |
f.write('\n') | |
f.write('transform/pos = Vector2( 106.038, 1.2886 )\n') | |
f.write('transform/scale = Vector2( 6, 11.1592 )\n') | |
f.write('\n') | |
f.write('[node name="posFeet" parent="."]\n') | |
f.write('\n') | |
f.write('transform/pos = Vector2( -25, 108.406 )\n') | |
f.write('\n') | |
f.close() | |
return result | |
def generateFromFolder (self, folder): | |
result = None | |
result = FileTools.createTmpFolder(FileTools.getFileBasename(folder)) | |
self.copyAllFoldersAnim(folder, result) | |
self.renameFolderActions(result) | |
self.adjustFoldersAnim(result) | |
self.copyMainSprite(result) | |
self.copyShadowSprite(result) | |
self.generateAnims(result) | |
return result | |
def autocrop (self, srcFile, targetFile): | |
im = Image.open(srcFile) | |
if im.mode != "RGBA": | |
im = im.convert("RGBA") | |
xmin = None | |
ymin = None | |
xmax = None | |
ymax = None | |
maxTransparency = im.getpixel((0, 0))[3] | |
for y in range(0, im.height, 1): | |
for x in range(0, im.width, 1): | |
if im.getpixel((x, y))[3] > maxTransparency: | |
ymin = y | |
break | |
if ymin != None: | |
break | |
for y in range(im.height - 1, -1, -1): | |
for x in range(0, im.width, 1): | |
if im.getpixel((x, y))[3] > maxTransparency: | |
ymax = y | |
break | |
if ymax != None: | |
break | |
for x in range(0, im.width, 1): | |
for y in range(0, im.height, 1): | |
if im.getpixel((x, y))[3] > maxTransparency: | |
xmin = x | |
break | |
if xmin != None: | |
break | |
for x in range(im.width - 1, -1, -1): | |
for y in range(0, im.height, 1): | |
if im.getpixel((x, y))[3] > maxTransparency: | |
xmax = x | |
break | |
if xmax != None: | |
break | |
''' | |
print("width = " + str(im.width)) | |
print("height = " + str(im.height)) | |
print("xmin = " + str(xmin)) | |
print("ymin = " + str(ymin)) | |
print("xmax = " + str(xmax)) | |
print("ymax = " + str(ymax)) | |
''' | |
imSprite = im.crop((xmin, ymin, xmax + 1, ymax + 1)) | |
imSprite.save(targetFile) | |
imSprite.close() | |
im.close() | |
def _psdGroup2png (self, psd, group, target): | |
result = None | |
targetPath = os.path.join(target, group.name.lower()) | |
if not os.path.exists(targetPath): | |
os.makedirs(targetPath) | |
tmpFolder = FileTools.createTmpFolder(FileTools.getFileBasename(target)) | |
for spriteLayer in group.layers: | |
spriteName = spriteLayer.name.lower() | |
imgLayer = spriteLayer.as_PIL() | |
tmpSpriteFile = os.path.join(tmpFolder, spriteName + ".png") | |
imgLayer.save(tmpSpriteFile) | |
imgLayer.close() | |
spriteFile = os.path.join(targetPath, spriteName + ".png") | |
self.autocrop(tmpSpriteFile, spriteFile) | |
return result | |
def plainpsd (self, psdFile): | |
result = None | |
psd = psd_tools.PSDImage.load(psdFile) | |
visibleLayers = [] | |
for layer in psd.layers: | |
if layer.visible: | |
sublayers = [] | |
try: | |
sublayers = layer.layers | |
except: | |
pass | |
if len(sublayers) > 0: | |
visibleLayers.append(layer) | |
if len(visibleLayers) > 0: | |
result = FileTools.createTmpFolder(FileTools.getFileBasename(psdFile)) | |
for group in visibleLayers: | |
self._psdGroup2png(psd, group, result) | |
return result | |
def _findAnySynonymous (self, word, synonyms): | |
result = None | |
result = word.lower() | |
for key in synonyms: | |
if word.lower().find(key) >= 0: | |
result = synonyms[key].lower() | |
break | |
return result | |
def _findAnyAnim (self, word, anims): | |
result = None | |
result = word.lower() | |
for name in anims: | |
if word.lower().find(name) >= 0: | |
result = name.lower() | |
break | |
return result | |
def renameFolderActions (self, folder): | |
counterAnim = {} | |
renames = {} | |
for filename in os.listdir(folder): | |
path = os.path.join(folder, filename) | |
if os.path.isdir(path): | |
pivot = filename | |
pivot = self._findAnySynonymous(pivot, ANIM_SYNONYMS) | |
pivot = self._findAnyAnim(pivot, ANIM_LIST) | |
counter = 1 | |
if pivot in counterAnim: | |
counter = counterAnim[pivot] | |
newname = pivot | |
if counter > 1: | |
newname += str(counter) | |
renames[filename] = newname | |
counter += 1 | |
counterAnim[pivot] = counter | |
for renameKey in renames: | |
oldname = os.path.join(folder, renameKey) | |
newname = os.path.join(folder, renames[renameKey]) | |
os.rename(oldname, newname) | |
def generateFromPsd (self, psdFile): | |
result = None | |
folderPsd = self.plainpsd(psdFile) | |
self.renameFolderActions(folderPsd) | |
result = self.generateFromFolder(folderPsd) | |
return result | |
def generate (self, path): | |
result = None | |
if os.path.isdir(path): | |
result = self.generateFromFolder(path) | |
elif path.endswith(".psd"): | |
result = self.generateFromPsd(path) | |
return result | |
def execute (self, method, args): | |
result = None | |
func = getattr(self, method, None) | |
if (func == None): | |
print("method '" + method + "' not found") | |
else: | |
result = func(*args) | |
print(result) | |
return result | |
def proc (self, arg1, arg2): | |
print('arg1 = ' + str(arg1) + "'; arg2 = '" + str(arg2) + "'") | |
print(sys.argv) | |
#./spritesheet.py fastGenerateFolder /tmp/.tmp_oxag1I/prepare | |
Character().execute(sys.argv[1], sys.argv[2:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment