UnityでUVアニメーションやカラーアニメーションをつける際にUnity上でカーブをいじるのは中々やりづらいです。
そこでMayaからUnityにアニメーションを持っていけるように考えました。(まぁMayaもSIに比べるとアニメーションカーブはいじりづらいんですけど…)
とはいえ、データのやりとりをするFBXに入れられるのはtranslateXYZ, rotateXYZ, scaleXYZなどで
UVアニメーションやカラーアニメーションはそのまま入れられません。
なので、UVアニメーションやカラーアニメーションをtranslateやscaleに入れて出力できるようにしました。
※rotateも試してみましたが、FBXに出力したオイラー角がUnity内ではクオータニオンに変換されます。そのクオータニオンをオイラー角に変換し直した数値を見ても、Maya上(UnityとX軸の向きが逆ということを考慮しても)と同じにならなかったりしてうまくできなかったので今回は使っていません
ちなみに自分が試したソフトのバージョンはMaya2016、Unity5.3.5p5です。
おそらく近いバージョンなら、他のバージョンでもスクリプトは動くと思います。
まずはMaya側でアニメーションをつけるスクリプトです。
実行文
import kkCreateUVColAnim4FBX
reload(kkCreateUVColAnim4FBX)
このスクリプトを実行してもらうと下記の画像のようなウィンドウが開くので、UVアニメーションやカラーアニメーションさせたいオブジェクトかマテリアルを1つ選択した状態でどちらかのボタンをクリックしてください。
(オブジェクトを選んだ場合はそのオブジェクトに適用されているマテリアルを取得してそのマテリアルで処理をします)
クリックすると、そのマテリアル名+_animというグループノードと、
それの子に UV_+マテリアル名のロケーター と Col_+マテリアル名のロケーター が作られます。
この2つのロケーターとマテリアルのカラーとUVをエクスプレッションで繋げます。
UV_ロケーター : placed2dTextureノード
translateX = translateFrameU(Uの移動値)
translateY = translateFrameV(Vの移動値)
scaleX = repeatU(Uのリピート値)
scaleY = repeatV(Vのリピート値)
Col_ロケーター:マテリアルノード
translateX = ambientColorR
translateY = ambientColorG
translateZ = ambientColorB
scaleX = transparencyR = transparencyG = transparencyB
(transparencyはscaleXYZの3つに分けてもよかったんですが1つにまとめました)
「マテリアルからロケーター」をクリックした場合、ロケーターの移動値とスケール値でアニメーションさせます。
「ロケーターからマテリアル」をクリックした場合、placed2dTextureノードとマテリアルのアンビエントカラー&透明度でアニメーションさせます。
「ロケーターからマテリアル」の方はロケーターにアニメーションついてないので
出力時に 編集>キー>シミュレーションのベイク処理 をしてベイクしないといけないんですが、
もう一つの方法としてはその状態で「マテリアルからロケーター」を押すと
マテリアル側についていたアニメーションをロケーターに移して
エクスプレッションを作り直すような処理になっているので、それでロケーターを出力してもらってもいいです。
というか、ベイクすると余計なトコにもキーを打たれてしまうのでデータを軽くする意味でも
ベイクせずにロケーターでアニメーションつけた方が良いかなと個人的には思ってます。
※下記のPythonコードの下にUnity用のアニメーションクリップ変換のC#スクリプトがあります。
# -*- coding: utf-8 -*- import maya.cmds as mc import re from functools import partial class kkCreateUVColAnim4FBX(): def __init__(self): self.createUI() def getMaterialsFromObj(self, objList): mc.select(objList, replace=True ) mc.hyperShade(smn=True) matList = mc.ls(sl=True, mat=True) mc.select(objList, replace=True ) return matList def createExp(self, mode, *args): selObjs = mc.ls(sl=True, transforms=True) selMats = mc.ls(sl=True, materials=True) selMat = [] if len(selObjs) > 0: selMats = self.getMaterialsFromObj(selObjs) if len(selMats) > 0: selMat = selMats[0] if len(selMat) == 0: mc.warning("No Select? Please Select Mesh or Material") else: nodeList1 = mc.listConnections(selMat, destination=True, plugs=True) checkTex = False checkUV = False for connectNode1 in nodeList1: if not "outColor" in connectNode1: checkTex = False else: checkTex = True fileNode = connectNode1[0 : connectNode1.rfind("outColor")-1] nodeList2 = mc.listConnections(fileNode, connections=True, plugs=True) for connectNode2 in nodeList2: if not "outUV" in connectNode2: checkUV = False else: checkUV = True anim_p2dTex = {} anim_matCol = {} anim_Loc_UV = {} anim_Loc_Col = {} place2dTextureNode = connectNode2[0 : connectNode2.rfind("outUV")-1] locater_UVNodes = mc.ls("UV_%s" %selMat, transforms=True) locater_ColNodes = mc.ls("Col_%s" %selMat, transforms=True) # すでにUVアニメーションやカラーアニメーションがあったら取得し、削除しておく if len(place2dTextureNode) != 0: place2dTex_Anim = mc.listConnections(place2dTextureNode, type="animCurve") if place2dTex_Anim != None: anim_p2dTex = self.getAnim(place2dTextureNode) mc.delete(place2dTex_Anim) if len(selMat) != 0: matColor_Anim = mc.listConnections(selMat, type="animCurve") if matColor_Anim != None: anim_matCol = self.getAnim(selMat) mc.delete(matColor_Anim) if len(locater_UVNodes) != 0: locater_UVAnim = mc.listConnections(locater_UVNodes[0], type="animCurve") if locater_UVAnim != None: anim_Loc_UV = self.getAnim(locater_UVNodes[0]) mc.delete(locater_UVAnim) if len(locater_ColNodes) != 0: locater_ColAnim = mc.listConnections(locater_ColNodes[0], type="animCurve") if locater_ColAnim != None: anim_Loc_Col = self.getAnim(locater_ColNodes[0]) mc.delete(locater_ColAnim) # _animノードがすでにあった場合削除しておく animGruopNodes = mc.ls("%s_anim*" %selMat, transforms=True) if len(animGruopNodes) != 0: mc.delete(animGruopNodes) locater_UVanim = mc.spaceLocator(name="UV_%s" %selMat, position=[0, 0, 0])[0] locater_Colanim = mc.spaceLocator(name="Col_%s" %selMat, position=[0, 0, 0])[0] # 取得したアニメーションがあった場合、適用する if mode == 0: # UVアニメ用ロケーターに取得したUVアニメを適用する if len(anim_Loc_UV) > 0: self.applyAnim(locater_UVanim, anim_Loc_UV, False) elif len(anim_p2dTex) > 0: self.applyAnim(locater_UVanim, anim_p2dTex, True) # カラーアニメ用ロケーターに取得したカラーアニメを適用する if len(anim_Loc_Col) > 0: self.applyAnim(locater_Colanim, anim_Loc_Col, False) elif len(anim_matCol) > 0: self.applyAnim(locater_Colanim, anim_matCol, True) elif mode == 1: # place2dTextureノードに取得したUVアニメを適用する if len(anim_p2dTex) > 0: self.applyAnim(place2dTextureNode, anim_p2dTex, False) elif len(anim_Loc_UV) > 0: self.applyAnim(place2dTextureNode, anim_Loc_UV, True) # マテリアルに取得したカラーアニメを適用する if len(anim_matCol) > 0: self.applyAnim(selMat, anim_matCol, False) elif len(anim_Loc_Col) > 0: self.applyAnim(selMat, anim_Loc_Col, True) # エクスプレッションがすでにあったら作成する前に削除しておく expressionList = mc.ls(type='expression') if "UV_exp_%s"%selMat in expressionList: mc.delete("UV_exp_%s"%selMat) if "Col_exp_%s"%selMat in expressionList: mc.delete("Col_exp_%s"%selMat) # 「オブジェクトからロケーター」を押された場合のエクスプレッション作成 if mode == 0: mc.expression(name="UV_exp_%s"%selMat, alwaysEvaluate=0, string= """ %(p2T)s.translateFrameU = %(trs_uv)s.translateX; \n %(p2T)s.translateFrameV = %(trs_uv)s.translateY; \n %(p2T)s.repeatU = %(trs_uv)s.scaleX; \n %(p2T)s.repeatV = %(trs_uv)s.scaleY; \n """%{ "p2T" : place2dTextureNode, "trs_uv" : locater_UVanim} ) mc.expression(name="Col_exp_%s"%selMat, alwaysEvaluate=0, string= """ %(mat)s.ambientColorR = %(trs_col)s.translateX; \n %(mat)s.ambientColorG = %(trs_col)s.translateY; \n %(mat)s.ambientColorB = %(trs_col)s.translateZ; \n %(mat)s.transparencyR = %(trs_col)s.scaleX; \n %(mat)s.transparencyG = %(trs_col)s.scaleX; \n %(mat)s.transparencyB = %(trs_col)s.scaleX; \n """%{ "mat" : selMat, "trs_col" : locater_Colanim} ) # カラーアニメ用ロケーターの移動値、スケール値を変更してRGBAのデフォルト値を設定 mc.setAttr("%s.translateX"%locater_Colanim, 1) mc.setAttr("%s.translateY"%locater_Colanim, 1) mc.setAttr("%s.translateZ"%locater_Colanim, 1) mc.setAttr("%s.scaleX"%locater_Colanim, 0) mc.setAttr("%s.scaleY"%locater_Colanim, 0) mc.setAttr("%s.scaleZ"%locater_Colanim, 0) # カラーアニメ用ロケーターの移動値の制限を設定 mc.transformLimits(remove=True) mc.transformLimits(locater_Colanim, etx=(True, True), ety=(True, True), etz=(True, True) ) mc.transformLimits(locater_Colanim, tx=(0, 1), ty=(0, 1), tz=(0, 1) ) mc.transformLimits(locater_Colanim, esx=(True, True), esy=(True, True), esz=(True, True) ) mc.transformLimits(locater_Colanim, sx=(0, 1), sy=(0, 1), sz=(0, 1) ) # 「ロケーターからオブジェクト」を押された場合のエクスプレッション作成 else: transfU = mc.getAttr("%s.translateFrameU"%place2dTextureNode) transfV = mc.getAttr("%s.translateFrameV"%place2dTextureNode) repeatU = mc.getAttr("%s.repeatU"%place2dTextureNode) repeatV = mc.getAttr("%s.repeatV"%place2dTextureNode) ambColR = mc.getAttr("%s.ambientColorR"%selMat) ambColG = mc.getAttr("%s.ambientColorG"%selMat) ambColB = mc.getAttr("%s.ambientColorB"%selMat) trspcyR = mc.getAttr("%s.transparencyR"%selMat) trspcyG = mc.getAttr("%s.transparencyG"%selMat) trspcyB = mc.getAttr("%s.transparencyB"%selMat) mc.setAttr("%s.translateFrameU"%place2dTextureNode, 0) mc.setAttr("%s.translateFrameV"%place2dTextureNode, 0) mc.setAttr("%s.repeatU"%place2dTextureNode, 1) mc.setAttr("%s.repeatV"%place2dTextureNode, 1) mc.setAttr("%s.ambientColorR"%selMat, 0) mc.setAttr("%s.ambientColorG"%selMat, 0) mc.setAttr("%s.ambientColorB"%selMat, 0) mc.setAttr("%s.transparencyR"%selMat, 0) mc.setAttr("%s.transparencyG"%selMat, 0) mc.setAttr("%s.transparencyB"%selMat, 0) mc.expression(name="UV_exp_%s"%selMat, alwaysEvaluate=0, string= """ %(trs_uv)s.translateX = %(p2T)s.translateFrameU; \n %(trs_uv)s.translateY = %(p2T)s.translateFrameV; \n %(trs_uv)s.scaleX = %(p2T)s.repeatU; \n %(trs_uv)s.scaleY = %(p2T)s.repeatV; \n """%{ "trs_uv" : locater_UVanim, "p2T" : place2dTextureNode} ) mc.expression(name="Col_exp_%s"%selMat, alwaysEvaluate=0, string= """ %(trs_col)s.translateX = %(mat)s.ambientColorR; \n %(trs_col)s.translateY = %(mat)s.ambientColorG; \n %(trs_col)s.translateZ = %(mat)s.ambientColorB; \n %(trs_col)s.scaleX = %(mat)s.transparencyR; \n %(trs_col)s.scaleX = %(mat)s.transparencyG; \n %(trs_col)s.scaleX = %(mat)s.transparencyB; \n """%{ "trs_col" : locater_Colanim, "mat" : selMat} ) mc.setAttr("%s.translateFrameU"%place2dTextureNode, transfU) mc.setAttr("%s.translateFrameV"%place2dTextureNode, transfV) mc.setAttr("%s.repeatU"%place2dTextureNode, repeatU) mc.setAttr("%s.repeatV"%place2dTextureNode, repeatV) mc.setAttr("%s.ambientColorR"%selMat, ambColR) mc.setAttr("%s.ambientColorG"%selMat, ambColG) mc.setAttr("%s.ambientColorB"%selMat, ambColB) mc.setAttr("%s.transparencyR"%selMat, trspcyR) mc.setAttr("%s.transparencyG"%selMat, trspcyG) mc.setAttr("%s.transparencyB"%selMat, trspcyB) mc.group(locater_UVanim, locater_Colanim, name="%s_anim"%selMat) break # もしoutUVノードを見つけたらループを抜ける break # もしoutColorノードを見つけたらループを抜ける if(checkTex == False): mc.warning("No Color Texture in this material[ %s ]"%selMat) if(checkUV == False): mc.warning("No UV(place2dTexture) in this material[ %s ]"%selMat) # get animation =================================================================================== def getAnim(self, srcObj): animSRTs = mc.listConnections(srcObj, type="animCurve") animList = {} checkAttr = False for animSRT in animSRTs: lastFrame = mc.keyframe(animSRT, q=True, lastSelected=True)[0] keyFms = mc.keyframe(animSRT, q=True, t=(0, lastFrame), tc=True) # translate と rotate と scale それぞれのXYZ を入れていく animSRT_XYZ = animSRT[animSRT.rfind("_") + 1 : len(animSRT)] # SRT + XYZ の後ろに数値がついてる可能性があるのでそのときは数値をカットする if re.search('[0-9]',animSRT_XYZ) != None: if "X" in animSRT_XYZ: animSRT_XYZ = animSRT_XYZ[0:animSRT.rfind("X")-1] elif "Y" in animSRT_XYZ: animSRT_XYZ = animSRT_XYZ[0:animSRT.rfind("Y")-1] elif "Z" in animSRT_XYZ: animSRT_XYZ = animSRT_XYZ[0:animSRT.rfind("Z")-1] animList[animSRT_XYZ] = [] try: crvPreInfinite = mc.setInfinity(srcObj, at=animSRT_XYZ, q=True, preInfinite=True)[0] except: crvPreInfinite = "" try: crvPostInfinite = mc.setInfinity(srcObj, at=animSRT_XYZ, q=True, postInfinite=True)[0] except: crvPostInfinite = "" try: crvWehtTng = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, weightedTangents=True)[0] except: crvWehtTng = "" animList[animSRT_XYZ].append(["setInfinity_preInfinite :" + str(crvPreInfinite), "setInfinity_postInfinite :" + str(crvPostInfinite), "keyTangent_weightedTangents:" + str(crvWehtTng) ]) keyFmsCount = 1 for key in keyFms: animList[animSRT_XYZ].append([]) attrSRT = mc.getAttr(srcObj + "." + animSRT_XYZ, t=key) attrInTngTyp = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), inTangentType=True)[0] attrInAngl = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), inAngle=True)[0] attrInWeht = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), inWeight=True)[0] attrInTngX = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), ix=True)[0] attrInTngY = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), iy=True)[0] attrOtTngTyp = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), outTangentType=True)[0] attrOtAngl = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), outAngle=True)[0] attrOtWeht = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), outWeight=True)[0] attrOtTngX = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), ox=True)[0] attrOtTngY = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), oy=True)[0] attrLock = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), lock=True)[0] attrWehtLock = mc.keyTangent(srcObj, q=True, at=animSRT_XYZ, t=(key,key), weightLock=True)[0] # リストに入れるためのアトリビュートのチェック if "UV_" in srcObj: if animSRT_XYZ == "translateX" or animSRT_XYZ == "translateY" or \ animSRT_XYZ == "scaleX" or animSRT_XYZ == "scaleY": checkAttr =True elif "Col_" in srcObj: if animSRT_XYZ == "translateX" or animSRT_XYZ == "translateY" or \ animSRT_XYZ == "translateZ" or animSRT_XYZ == "scaleX": checkAttr =True elif "place2d" in srcObj: if animSRT_XYZ == "translateFrameU" or animSRT_XYZ == "translateFrameV" or \ animSRT_XYZ == "repeatU" or animSRT_XYZ == "repeatV": checkAttr =True else: if animSRT_XYZ == "ambientColorR" or animSRT_XYZ == "ambientColorG" or animSRT_XYZ == "ambientColorB" or \ animSRT_XYZ == "transparencyR" or animSRT_XYZ == "transparencyG" or animSRT_XYZ == "transparencyB": checkAttr =True if checkAttr == True: animList[animSRT_XYZ][keyFmsCount].append("keyframe_Frame :" + str(key) ) animList[animSRT_XYZ][keyFmsCount].append("getAttr_Value :" + str(attrSRT) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_inTangentType :" + str(attrInTngTyp) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_inAngle :" + str(attrInAngl) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_inWeight :" + str(attrInWeht) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_ix :" + str(attrInTngX) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_iy :" + str(attrInTngY) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_outTangentType :" + str(attrOtTngTyp) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_outAngle :" + str(attrOtAngl) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_outWeight :" + str(attrOtWeht) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_ox :" + str(attrOtTngX) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_oy :" + str(attrOtTngY) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_lock :" + str(attrLock) ) animList[animSRT_XYZ][keyFmsCount].append("keyTangent_weightLock :" + str(attrWehtLock) ) keyFmsCount += 1 return animList # apply animation ================================================================= def applyAnim(self, targetObj, animData, attrChange): animKeys = animData.keys() for animKey in animKeys: attrs = [ animKey[animKey.rfind("_")+1 : len(animKey)] ] if attrChange == True: if "UV_" in targetObj or "place2d" in targetObj: if attrs[0] == "translateFrameU": attrs = ["translateX"] elif attrs[0] == "translateFrameV": attrs = ["translateY"] elif attrs[0] == "repeatU": attrs = ["scaleX"] elif attrs[0] == "repeatV": attrs = ["scaleY"] elif attrs[0] == "translateX": attrs = ["translateFrameU"] elif attrs[0] == "translateY": attrs = ["translateFrameV"] elif attrs[0] == "scaleX": attrs = ["repeatU"] elif attrs[0] == "scaleY": attrs = ["repeatV"] else :# if "Col_" in targetObj or targetObj is Material if attrs[0] == "translateX": attrs = ["ambientColorR"] elif attrs[0] == "translateY": attrs = ["ambientColorG"] elif attrs[0] == "translateZ": attrs = ["ambientColorB"] elif attrs[0] == "scaleX": attrs = ["transparencyR", "transparencyG", "transparencyB"] elif attrs[0] == "ambientColorR": attrs = ["translateX"] elif attrs[0] == "ambientColorG": attrs = ["translateY"] elif attrs[0] == "ambientColorB": attrs = ["translateZ"] elif attrs[0] == "transparencyR": attrs = ["scaleX"] elif attrs[0] == "transparencyG": attrs = ["scaleX"] elif attrs[0] == "transparencyB": attrs = ["scaleX"] # scaleXから透明度RGBの3つに振り分ける必要があるため配列にいれておいてforでまわす for attr in attrs: crvPreInf = animData[animKey][0][0] crvPreInf = crvPreInf[crvPreInf.rfind(":")+1 : len(crvPreInf) ] crvPostInf = animData[animKey][0][1].encode('utf-8') crvPostInf = crvPostInf[crvPostInf.rfind(":")+1 : len(crvPostInf) ] crvWehtTng = animData[animKey][0][2] crvWehtTng = bool( crvWehtTng[crvWehtTng.rfind(":")+1 : len(crvWehtTng) ] ) for x in range(1, len(animData[animKey]) ): keyTime = animData[animKey][x][0] keyTime = float( keyTime[keyTime.rfind(":")+1 : len(keyTime) ] ) attrVal = animData[animKey][x][1] attrVal = float( attrVal[attrVal.rfind(":")+1 : len(attrVal) ] ) inTngTyp = animData[animKey][x][2] inTngTyp = inTngTyp[inTngTyp.rfind(":")+1 : len(inTngTyp) ] inAngl = animData[animKey][x][3] inAngl = float( inAngl[inAngl.rfind(":")+1 : len(inAngl) ] ) inWeht = animData[animKey][x][4] inWeht = float( inWeht[inWeht.rfind(":")+1 : len(inWeht) ] ) inTngX = animData[animKey][x][5] inTngX = float( inTngX[inTngX.rfind(":")+1 : len(inTngX) ] ) inTngY = animData[animKey][x][6] inTngY = float( inTngY[inTngY.rfind(":")+1 : len(inTngY) ] ) otTngTyp = animData[animKey][x][7] otTngTyp = otTngTyp[otTngTyp.rfind(":")+1 : len(otTngTyp) ] otAngl = animData[animKey][x][8] otAngl = float( otAngl[otAngl.rfind(":")+1 : len(otAngl) ] ) otWeht = animData[animKey][x][9] otWeht = float( otWeht[otWeht.rfind(":")+1 : len(otWeht) ] ) otTngX = animData[animKey][x][10] otTngX = float( otTngX[otTngX.rfind(":")+1 : len(otTngX) ] ) otTngY = animData[animKey][x][11] otTngY = float( otTngY[otTngY.rfind(":")+1 : len(otTngY) ] ) crvLock = animData[animKey][x][12] crvLock = bool( crvLock[crvLock.rfind(":")+1 : len(crvLock) ] ) crvWehtLock = animData[animKey][x][13] crvWehtLock = bool( crvWehtLock[crvWehtLock.rfind(":")+1 : len(crvWehtLock) ] ) # 適用するオブジェクトが何もキーがない可能性があるため1度setKeyframeしてから適用 mc.setKeyframe(targetObj, at=attr, t=keyTime, value=attrVal) if x == 1: mc.setInfinity(targetObj, e=True, at=attr, preInfinite=crvPreInf) mc.setInfinity(targetObj, e=True, at=attr, postInfinite=crvPostInf) mc.keyTangent(targetObj, e=True, at=attr, weightedTangents=crvWehtTng) # 各接線の設定 mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), lock=crvLock) mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), weightLock=crvWehtLock) mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), inAngle=inAngl, outAngle=otAngl) mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), inWeight=inWeht, outWeight=otWeht) mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), ix=inTngX, ox=otTngX) mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), iy=inTngY, oy=otTngY) # inTangentTypeとoutTangentTypeは後から変更しないとうまく適用されなかった mc.keyTangent(targetObj, e=True, at=attr, t=(keyTime, keyTime), inTangentType=inTngTyp, outTangentType=otTngTyp) # createUI ======================================================================================== def createUI(self): # すでに開いていたら消してから開く if mc.window("UVColAnimExp", ex = True): mc.deleteUI("UVColAnimExp") mc.window("UVColAnimExp", title="UVColAnimExppression", sizeable=False, widthHeight=(150 , 150) ) mc.rowColumnLayout(columnAlign=(1, "left")) mc.text(label = u" UV & カラーのエクスプレッションを") mc.text(label = u" 適用する方法を選んで下さい") mc.separator( width=150 ) # widthを設定しないとなぜか表示されない?? mc.text(label ="") mc.columnLayout( numberOfChildren=3, columnOffset=("left", 0) ) mc.button(label=u"マテリアル から ロケーター \n ( ロケーター側でアニメーション )", height=60, width=180, command=partial(self.createExp, 0) ) mc.text(label ="") mc.button(label=u"ロケーター から マテリアル \n ( マテリアル側でアニメーション )\n※出力時にベイクが必要", height=60, width=180, command=partial(self.createExp, 1) ) mc.showWindow() #================================================================================================== kkCreateUVColAnim4FBX()
おつぎは上のスクリプトを使い、出力したFBX内のtlanslateとscaleに入れられたUVアニメーションとカラーアニメーションを
Unity側で変換するスクリプトです。
Assets内のどこかのEditorフォルダ内にスクリプトを入れてもらうと、
Toolsメニューの中に「 Convert Transform to UV anim | Color anim 」と表示されます。
これを実行してもらうと下記の画像の左上のようなウィンドウが開きますので、
Mayaから書きだしたUV&カラーアニメーションのFBX内のアニメーションクリップ(何も設定してなければアニメーションクリップの名前はTake001のはずです)を選択した状態でそのウィンドウのOKボタンを押して頂くと
そのFBX名のアニメーションクリップが作成されます。
ちなみに、FBXを修正・更新した場合に再度実行してもらうとそのアニメーションクリップを上書きしますので
ファイルIDが変わったりはしません。
下記の画像の右上のInspectorのSSのところにも書いてますが、
Maya側の作業単位(プリファレンス>設定>作業単位>リニア)がセンチメートルの場合は
Unity上で書き出したFBXのScale Factorを100に、メートルの場合は1にし、
Animation TypeはGenericにした状態で、そのFBX内にあるアニメーションクリップを選択して、
スクリプトのOKボタンを押して実行して下さい。
(Resample CurvesやAnim Compression の設定はお好みですが個人的にはOFFにしておくのが良いかなと思います。)
また、LegacyでAnimation Componentにそのまま使ってもUVアニメやカラーアニメは適用されなかったので
Animatior Componentを付けてAnimation Controllerに生成したアニメーションクリップを設定して下さい。
using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using CurveExtended; using System.Collections; public class ConvertTransform2UVanim : EditorWindow { private static ConvertTransform2UVanim window; [MenuItem ("Tools/Convert Transform to UV anim | Color anim")] static void ShowWindow() { window = EditorWindow.GetWindow(); window.minSize = new Vector2 (260f, 220f); window.maxSize = new Vector2 (260f, 220f); } private int tex_radio = 0; private bool uv_bool = true, color_bool = true, foldout = false; private string color_kind = "_TintColor"; void OnGUI () { EditorGUILayout.HelpBox ("UVアニメはスケール値のxyがTilingのxy、移動値のxyがOffsetのxyに変換されます。\n" + "デフォルトはMainTexですが下記のラジオボタンで適用するテクスチャの種類を変更できます。\n" + "また、カラーアニメは移動値のxyzがRGB・スケール値のxがアルファ値に変換されます。", MessageType.Info); uv_bool = EditorGUILayout.ToggleLeft ("UVアニメ変換", uv_bool); EditorGUI.BeginDisabledGroup (!uv_bool); GUILayout.BeginHorizontal (); GUILayout.Space (20); foldout = EditorGUILayout.Foldout (foldout, "UVアニメさせるテクスチャの種類"); GUILayout.EndHorizontal (); if (foldout) { window.minSize = new Vector2 (260f, 400f); window.maxSize = new Vector2 (260f, 400f); GUILayout.BeginHorizontal (); GUILayout.Space (40); tex_radio = GUILayout.SelectionGrid ( tex_radio, new string[] { //とりあえずスタンダードシェーダーにあるやつ "Albedo (_MainTex)", "Metallic (_MetallicGlossMap)", "NormalMap (_BumpMap)", "HeightMap (_ParallaxMap)", "Occlusion (_OcclusionMap)", "Emission (_EmissionMap)", "DetailMask (_DetailMask)", "NormalMap (_DetailNormalMap)", "DetailAlbedo (_DetailAlbedoMap)" }, 1, EditorStyles.radioButton ); GUILayout.EndHorizontal (); } else { window.minSize = new Vector2 (260f, 220f); window.maxSize = new Vector2 (260f, 220f); } EditorGUI.EndDisabledGroup(); GUILayout.Label(""); color_bool = EditorGUILayout.ToggleLeft("カラーアニメ変換", color_bool); GUILayout.BeginHorizontal(); GUILayout.Space(20); color_kind = EditorGUILayout.TextField(color_kind); GUILayout.EndHorizontal(); GUILayout.Label(""); if( GUILayout.Button("OK") ){ if(uv_bool == true){ if (color_bool == false){ Debug.Log("UV Anim Only"); color_kind = ""; } if (tex_radio == 0){ ConvertTrs2UV("_MainTex", color_kind); } else if (tex_radio == 1) { ConvertTrs2UV("_MetallicGlossMap", color_kind); } else if (tex_radio == 2) { ConvertTrs2UV("_BumpMap", color_kind); } else if (tex_radio == 3) { ConvertTrs2UV("_ParallaxMap", color_kind); } else if (tex_radio == 4) { ConvertTrs2UV("_OcclusionMap", color_kind); } else if (tex_radio == 5) { ConvertTrs2UV("_EmissionMap", color_kind); } else if (tex_radio == 6) { ConvertTrs2UV("_DetailMask", color_kind); } else if (tex_radio == 7) { ConvertTrs2UV("_DetailNormalMap", color_kind); } else if (tex_radio == 8) { ConvertTrs2UV("_DetailAlbedoMap", color_kind); } }else{ Debug.Log("Color Anim Only"); ConvertTrs2UV ("", color_kind); } } } private static void ConvertTrs2UV (string texKind, string colorKind) { int animClipCount = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Unfiltered).Length; var selAnimClip = Selection.objects.OfType(); if (animClipCount == 0) { Debug.Log ("アニメーションクリップが選択されていません"); } else { foreach (var clip in selAnimClip) { var path = AssetDatabase.GetAssetPath (clip); var directory = Path.GetDirectoryName (path); var clipWrapMode = clip.wrapMode; var clone = new AnimationClip (); clone.legacy = false; clone.wrapMode = clipWrapMode; var events = AnimationUtility.GetAnimationEvents (clip); var srcClipInfo = AnimationUtility.GetAnimationClipSettings (clip); AnimationUtility.SetAnimationEvents (clone, events); AnimationUtility.SetAnimationClipSettings (clone, srcClipInfo); int i, h, tngMode; float t, val, inTng, outTng; Keyframe[] frameArray; foreach (var binding in AnimationUtility.GetCurveBindings( clip )) { AnimationCurve curve = AnimationUtility.GetEditorCurve (clip, binding); frameArray = new Keyframe[ curve.keys.Length ]; if (binding.path.Contains("UV_") && texKind != ""){ if (binding.propertyName == "m_LocalPosition.x" || binding.propertyName == "m_LocalPosition.y" || binding.propertyName == "m_LocalScale.x" || binding.propertyName == "m_LocalScale.y") { for (i = 0; i < curve.keys.Length; i++) { t = curve [i].time; val = curve [i].value; inTng = curve [i].inTangent; outTng = curve [i].outTangent; tngMode = curve [i].tangentMode; //MayaからエクスポートされたX軸が反転してしまうため-1をかける if(binding.propertyName == "m_LocalPosition.x"){ val *= -1; } frameArray [i] = new Keyframe (t, val); frameArray [i].inTangent = inTng; frameArray [i].outTangent = outTng; frameArray [i].tangentMode = tngMode; } AnimationCurve newCurve = new AnimationCurve (frameArray); for (h = 0; h < newCurve.keys.Length; h++) { CurveExtension.UpdateTangentsFromMode (newCurve, h); } // _MainTex_ST.xy は Tiling, _MainTex_ST.zw は Offset if (binding.propertyName == "m_LocalScale.x") { // AnimationUtility.SetEditorCurveは廃止されたのでSetCurveを使う clone.SetCurve ("", typeof(MeshRenderer), "material." + texKind + "_ST.x", newCurve); } else if (binding.propertyName == "m_LocalScale.y") { clone.SetCurve ("", typeof(MeshRenderer), "material." + texKind + "_ST.y", newCurve); } else if (binding.propertyName == "m_LocalPosition.x") { clone.SetCurve ("", typeof(MeshRenderer), "material." + texKind + "_ST.z", newCurve); } else if (binding.propertyName == "m_LocalPosition.y") { clone.SetCurve ("", typeof(MeshRenderer), "material." + texKind + "_ST.w", newCurve); } } } else if (binding.path.Contains("Col_") && colorKind != "") { if (binding.propertyName == "m_LocalPosition.x" || binding.propertyName == "m_LocalPosition.y" || binding.propertyName == "m_LocalPosition.z" || binding.propertyName == "m_LocalScale.x") { for (i = 0; i < curve.keys.Length; i++) { t = curve [i].time; val = curve [i].value; inTng = curve [i].inTangent; outTng = curve [i].outTangent; tngMode = curve [i].tangentMode; //MayaからエクスポートされたX軸が反転してしまうため-1をかける if(binding.propertyName == "m_LocalPosition.x"){ val *= -1; } //Mayaの透明度は0が不透明、1が透明なので数値を反転させる if(binding.propertyName == "m_LocalScale.x"){ val = 1 - val; } frameArray [i] = new Keyframe (t, val); frameArray [i].inTangent = inTng; frameArray [i].outTangent = outTng; frameArray [i].tangentMode = tngMode; } AnimationCurve newCurve = new AnimationCurve (frameArray); for (h = 0; h < newCurve.keys.Length; h++) { CurveExtension.UpdateTangentsFromMode (newCurve, h); } if (binding.propertyName == "m_LocalPosition.x") { clone.SetCurve ("", typeof(MeshRenderer), "material." + colorKind + ".r", newCurve); } else if (binding.propertyName == "m_LocalPosition.y") { clone.SetCurve ("", typeof(MeshRenderer), "material." + colorKind + ".g", newCurve); } else if (binding.propertyName == "m_LocalPosition.z") { clone.SetCurve ("", typeof(MeshRenderer), "material." + colorKind + ".b", newCurve); } else if (binding.propertyName == "m_LocalScale.x") { clone.SetCurve ("", typeof(MeshRenderer), "material." + colorKind + ".a", newCurve); } } } } var fileName = Path.GetFileNameWithoutExtension (path); string outputPath, originalFilePath; originalFilePath = directory + "/" + fileName + ".anim"; //すでに同名ファイルが存在している場合一時ファイルとして作成してその情報をコピーする if (File.Exists (originalFilePath)) { outputPath = directory + "/" + "tempClip.anim"; AssetDatabase.CreateAsset (clone, outputPath); File.Copy (outputPath, originalFilePath, true); File.Delete (outputPath); Debug.Log ("UV & Colorアニメデータを置き換えました"); } else { outputPath = directory + "/" + fileName + ".anim"; AssetDatabase.CreateAsset (clone, outputPath); Debug.Log ("UV & Colorアニメデータを新規で作りました"); } } AssetDatabase.Refresh (); } } }
Maya用のPythonファイルはMayaが認識できるフォルダ(例C:\Users\ユーザー名\Documents\maya\2016\ja_JP\scriptsなど)に、
Unity用のC#ファイルはUnityのプロジェクトのAssetsの中のScriptフォルダなどに「Editor」フォルダを作って入れて下さい。
EditorスクリプトなのでEditorフォルダに入れないとビルド時にエラー出ます。
※下記の参考サイトのUnityCommunityWikiのあるCurveExtension.csとKeyframeUntil.csも使用していますのでDL用のZipの中に含んでいます。
また、使用した際に生じた障害などに関しては責任を負いかねますので、自己責任でご使用ください。
スクリプトの書き方などでもっと効率良い方法がございましたら教えて頂けると幸いです。
参考にしたサイト様
【Unity】FBXに格納されたAnimationClipを取り出し編集可能にする - テラシュールブログ
http://tsubakit1.hateblo.jp/entry/2015/06/01/235939
【Unity】FBXからAnimationClipを取り出すエディタ拡張 - コガネブログ
http://baba-s.hatenablog.com/entry/2015/05/12/133032
EditorAnimationCurveExtension - Unify Community Wiki
http://wiki.unity3d.com/index.php/EditorAnimationCurveExtension
コメントをお書きください
step (水曜日, 28 12月 2016 08:23)
こんにちは。こちらのスクリプトを使わせて頂いております。
質問になってしまいますが、「マテリアルからロケーター」を選択した後はMAYA上での操作は何かする必要はございませんでしょうか?また、
>マテリアルのカラーとUVをエクスプレッションで繋げます。
MAYAの操作に慣れていないため、イマイチ理解出来ずにいます。こちらは「マテリアルからロケーター」を選択した後に手作業で行う内容ということで認識は合っておりますでしょうか?
初歩的な質問で申し訳ございません。
管理人 (金曜日, 30 12月 2016 15:13)
コメントありがとうございます!
説明が分かりづらかったようですみません。
ハイパーシェードウィンドウなどでマテリアルを選んでいる状態で「マテリアルからロケーター」をクリックしてもらいますと、下記の3つが生成されます。
マテリアル名+「_anim」のトランスフォームノード
--「UV_」+マテリアル名のロケーター
--「Col_」+マテリアル名のロケーター
例えば、このUV_○○のロケーターの移動のXYは
そのマテリアルに紐付いているUV(place2dTextureノード)をエクスプレッションで関連付けしていますので、
ロケーターを動かすとMaya上でそのマテリアルのUVアニメーションをさせることができます。
そして、そのロケーターの親のトランスフォームを選択した状態でFBXを書き出して、
Unity側でロケーターの移動値などをMaya側でつけたUVアニメーションと同じようにするために
UV値に変換しているという流れです。
こんな回答であってますでしょうか??
ぼけねこ (火曜日, 10 1月 2017 18:03)
こんにちは、便利なスクリプトを作ってくださりありがとうございます。
こちらを使わせていただき、MAYAからFBXデータを書き出した所うまくいかない所があったのでご助言願えないでしょうか。
MAYAで、オブジェクトにテクスチャーを貼り、こちらのスクリプトを使用して、ロケータを移動させる事でUVアニメーションをさせる所まではできました。
その後、ロケータのキーにベイク処理をかけ、FBXの書き出しもエラーがなく書き出せたのですが、書き出したFBXデータの方で、UVコントロールをするロケータのエクスプレッションが外れてしまい、キーは打たれていますが、アニメーションが動きません。(ノードエディタで確認した所、X=のマークになっている、UV_exp_Lambert(マテリアル名)のものがありません)
FBXのプリセット編集の書き出しオプションにもエクスプレッションの項目がないのですが、これはどのようにして書き出しをすれば、よいのでしょうか。
管理人 (木曜日, 12 1月 2017 09:14)
ぼけねこさん、コメントありがとうございます。
スクリプト内のボタンの「マテリアルからロケーター」を使われた感じでしょうか?
ボタンを2つ用意したのが分かりづらかったかもしれませんが、こちら(上のボタン)を使われた場合はキーをベイクしてもらわなくて大丈夫です。
「ロケーターからマテリアル」(下のボタン)をされた場合のみ、ロケーター自身にアニメのキーがないのでベイクの必要があるということです。
ちなみにベイクするとエクスプレッションは外れてしまうと思います。
書き出したFBXを開いてもらってもロケーターの移動とスケールのアニメしかありません。
なので、そちらをUnity側でUVのアニメに変換している感じです。(このページにある下のスクリプト)
こんな感じで伝わりますでしょうか?
ぼけねこ (金曜日, 13 1月 2017 23:19)
お返事ありがとうございます。
ユニティスクリプトがうまく使えず難航していますが、使えるようにできたらと思っています。
すみません、また質問をさせてください。
使用したスクリプトは、マテリアルからロケーター、です。
ベイクしてエクスプレッションが外れてしまう件についてですが、ベイクをしないでFBXを書き出しても
エクスプレッションが外れてしまいました…
MAYA上でエクスプレッションが外れた状態のFBXでもユニティのスクリプトを使えればUVアニメーションさせる事ができるのでしょうか。
エクスプレッションが外れたFBXで大丈夫なのであれば、ユニティでのスクリプト制御をがんばろうと思います。
管理人 (月曜日, 16 1月 2017 08:59)
>ぼねねこさん
はい、書き出したFBXのエクスプレッションのリンクが外れてしまうのは問題ないです!
このスクリプトで作られるエクスプレッションはあくまでMaya上で確認するためのものなので、極端に言えばMaya側のこのスクリプトを使わずに同じようなロケーターの構成を用意して、感覚で移動値にアニメーションキー打ってFBX書き出して、それをUnity側のスクリプトで変換しても問題ないです(^ ^)
ぼけねこ (火曜日, 17 1月 2017 21:51)
管理人さん
そうなんですね、MAYA上でエクスプレッションのリンクを保持しようと必死になってしまっていました・・(^^::
それでは、ユニティにスクリプトを入れてUVコントロールをしてみたいと思います。
ご丁寧にお返事くださりありがとうございました!