Skip to content

Instantly share code, notes, and snippets.

@tuaplicacionpropia
Last active March 19, 2018 23:16
Show Gist options
  • Save tuaplicacionpropia/8839638f6c97ecc1e53d89396d5589ef to your computer and use it in GitHub Desktop.
Save tuaplicacionpropia/8839638f6c97ecc1e53d89396d5589ef to your computer and use it in GitHub Desktop.
Create Character from spritesheet to godot (special craftpix)
#!/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