在Galgame Image Cover Tool程序大体框架开发完毕后,接下来开始Galgame引擎Artemis Engine立绘合成的适配工作。

之前,我已经对引擎Artemis Engine立绘合成的坐标型规则进行了系统性的分析,详情可以去看我2024年2月19日的博客——关于Galgame引擎——Artemis批量合成人物立绘问题 - 岱中鹏的小站 (sgdzp.top)

分析图像资源

Galgame Image Cover Tool在开发之前的基本规划是用户直接导入整个Galgame人物立绘的解包后资源,文件分布都是以默认的方式进行读取。所以需要对解包后资源进行固定形式的检索。

这是灵感满溢的甜蜜创想的人物立绘解包后资源:

Galgame Image Cover Tools开发日记二——引擎Artemis Engine的适配1.jpg

Galgame Image Cover Tools开发日记二——引擎Artemis Engine的适配2.jpg

根据分析,引擎Artemis Engine人物立绘的解包资源中,文件夹路径一般为./fg/***(角色英文名称缩写,通常为三个字母组合),然后角色人物立绘对应的类型在角色文件夹下又分为fa、no、z1和z2,这里根据Galgame制作的质量的不同,立绘类型可能有所不同,文件夹个数也有所不同。

在灵感满溢的甜蜜创想中,fa为角色脸部图像,no、z1和z2为完整立绘图像,且分辨率(图像质量)不断增加。

检索图像资源

角色名称的获取

接下来,开始图像资源检索代码的编写。

首先,编写用户导入图像资源文件夹的子窗口:

import wx
###……
def choicedirclick(self,event):
        global ImageResourcePath
        dialog = wx.DirDialog(self, "选择文件夹", os.getcwd(), wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST)
        if dialog.ShowModal() == wx.ID_OK:
            self.txt1.SetValue(dialog.GetPath())
            ImageResourcePath = dialog.GetPath()
            dialog.Destroy()

然后,将相应信息写入Setting.ini文件中,便于后面代码块的引用:

def okb_btn_onclick(self,event):
    ###……
    with open("Setting.ini","w") as log:
        log.write(ImageResourcePath)
        log.write("\n")

紧接着,_tmp.py代码块充当模块的功能,执行读取Setting.ini的操作,方便导入后面的主窗口代码块中:

with open("Setting.ini","r") as log:
    logdatas = log.readlines()
    log.close()

ResourcePath = logdatas[0].replace("\n","") #导入图像资源的绝对路径
GalEngine = logdatas[1].replace("\n","") #Galgame引擎的选择
ModleChoice = logdatas[2].replace("\n","") #合成模式(CG和人物立绘)

然后,因为引擎Artemis Engine的fg文件夹下的一级文件夹名称代表的都是角色名称,所以直接循环检索fg文件夹的一级文件夹获取角色名称,以列表形式存储,并传递给主窗口的CharactersCombobox,供用户选择角色:

if GalEngine == "Artemis Engine":
    CharactersList = os.listdir(ResourcePath)
    a = 0
    for i in CharactersList:
        if Galgame == "灵感满溢的甜蜜创想":
            if i in CharactersDict.HAMIDASHICREATIVECharactersDict.keys():
                CharactersList[a] = CharactersDict.HAMIDASHICREATIVECharactersDict[i]
        a = a + 1

在这里,因为本人已经推过灵感满溢的甜蜜创想了,所以顺手把灵感满溢的甜蜜创想单独适配了出来,也就是将文件夹的英文缩写和角色全称对应,使用字典存储,便于用户更好的分辨角色并选择:

HAMIDASHICREATIVECharactersDict = {"ame":"龙闲天梨(竜閑天梨)","asu":"锦亚澄(錦あすみ)","hir":"新川广梦(新川広夢)","hiy":"和泉妃爱(いずみひより)","kan":"常磐华乃(常磐華乃)"
        ,"mir":"和泉里(いずみ みり)","rir":"圣莉莉子(聖莉々子)","shi":"镰仓诗樱(鎌倉詩桜)","suk":"权太","yuk":"雪景式(雪景シキ)"}

立绘类型的获取

在前面对图像资源的分析中,我们可以知道立绘类型一般都为角色文件夹下的一级文件夹名称,所以这里直接循环检索即可,和角色名称的检索过程基本一致:

if _tmp.Galgame == "灵感满溢的甜蜜创想":
    CharactersDictList = CharactersDict.HAMIDASHICREATIVECharactersDict
###……
def CharactersCombobox(self,event):
    ###……
    CharactersName = event.GetString()
    if _tmp.GalEngine == "Artemis Engine":
        if _tmp.Galgame == "None" or CharactersName not in CharactersDictList.values():
            CharactersDir = _tmp.ResourcePath + "\\" + CharactersName
        else:
            CharactersDir = _tmp.ResourcePath + "\\" + list(CharactersDictList.keys()) [list (CharactersDictList.values()).index (CharactersName)]
        StyleList = os.listdir(CharactersDir)

其中,因为立绘类型要和用户前面选择的角色对应,所以需要获取用户选择的角色名称并得立绘类型文件夹所在的父路径。但,如果合成的资源来源于已经适配的Galgame,这里就需要把字典里面的值value转换成对应的key,使用list(CharactersDictList.keys()) [list (CharactersDictList.values()).index (CharactersName)]代码进行转换,并和路径的字符串拼接起来,从而获得正确的路径。

罗列身体差分

在选择了立绘类型后,接下来就要罗列出身体差分了,也就是人物立绘图像合成的底板,这样就可以和再下一步的表情差分的罗列相对应。

打开其中一个立绘类型文件夹后,身体差分和表情差分如下图:

Galgame Image Cover Tools开发日记二——引擎Artemis Engine的适配3.jpg

从中我们可以发现,身体差分图像和角色差分图像文件名称唯一不同的地方就是有无下划线。根据这一点我们就可以轻易区分身体差分图像和表情差分图像了!

def StyleCombobox(self,event):
    ###……
    StyleName = event.GetString()
    if _tmp.GalEngine == "Artemis Engine":
        StylePath = CharactersDir + "\\" + StyleName
        for i in os.listdir(StylePath):
            if "_" in i:
                if i[-4:] == ".png":
                    BaseplateList.append(i.replace(".png",""))

罗列表情差分

最后,就是要罗列出表情差分了。

根据身体差分和表情差分的对应情况,对于灵感满溢的甜蜜创想,分为a和b两中对应关系:

Galgame Image Cover Tools开发日记二——引擎Artemis Engine的适配4.jpg

所以,在罗列表情差分的时候,需要和前面用户选择的身体差分对应,不然会出现合成错误的情况。这里采用了字符串切割的办法区分表情差分是否为a或者b类型:

def BaseplateCombobox(self,event):
    ###……
    BaseplateName = event.GetString()
    if _tmp.GalEngine == "Artemis Engine":
        BaseplateNameCheck = str(BaseplateName)[6:7]
        for i in os.listdir(StylePath):
            if "_" not in i:
                if i[:1] == BaseplateNameCheck:
                    if i[-4:] == ".png":
                        PartImageList.append(i.replace(".png",""))
        PartImagechoice.SetItems(PartImageList)

合成图像

最后,因为引擎Artemis Engine解包出来的图像文件都为普通图像文件,而不是需要进行二次解析转换的特殊文件,所以该合成图像的过程就比较简单了:

def PartImageCombobox(self,event):
    ###……
    PartImageName = event.GetString()
    if _tmp.GalEngine == "Artemis Engine":
        PartImagePath = StylePath + "\\" + PartImageName + ".png"
        BaseplatePath = StylePath + "\\" + BaseplateName + ".png"
        image1 = Image.open(PartImagePath)
        image2 = Image.open(BaseplatePath)
        image2.alpha_composite(image1,(ArtemisEngine.ArtemisEngine(PartImagePath,BaseplatePath)))

到这里,引擎Artemis Engine的适配代码编写工作就基本结束了🎉🎉🎉

Galgame Image Cover Tools开发日记二——引擎Artemis Engine的适配5.jpg

根据后期调查,使用引擎Artemis Engine的部分Galgame存在多差分图像合成的情况,所以在后面的开发工作中也一并适配上吧。