M.U.G.E.N‎ > ‎製作メモ‎ > ‎

MUGEN1.0への対応について

変なことをしなければWinMugen用のキャラクターはLinuxMugenやMUGEN1.0でも動作します。ここはMUGEN1.0への対応 (専用化) についてウチがやっていることのメモです。

SFFのV2化

とりあえずSFFはMUGEN1.0用にV2化します。幸いにもsprmake2というツールがあるので利用しましょう。さらにウチではSAEを基本的に使っているので、楽してSFFv1からSFFv2へ変換するためのスクリプトを組みました。

スクリプト

sae2sffv2.py

#!python
# -*- coding: utf-8 -*-


import sys
import os
import re
import csv

"""
指定ディレクトリの中を指定パターンで検索する
"""
def listFiles(dir, pattern):
    matcher = re.compile(pattern)
    fileList = []
    for root, dirs, files in os.walk(dir):
        for file in files:
            if matcher.match(file):
                fileList.append(os.path.join(root, file)[len(dir) + 1:].replace('\\', '/'))
    return fileList

"""
スプライトレコード
"""
class Record:

    def __init__(self, csvRow):
        self.file = csvRow[0]
        self.group = int(csvRow[1])
        self.number = int(csvRow[2])
        self.x = int(csvRow[3])
        self.y = int(csvRow[4])
        self.shared = (int(csvRow[5][:1]) is 1)

    def __str__(self):
        line = '%d, %d, %s, %d, %d' % (self.group, self.number, self.file.replace(' ', '_'), self.x, self.y)
#        if not self.shared:
#            line = line + "\t? usepal = -1"

        return line

class SpriteList:

    def __init__(self, input):
        reader = csv.reader(input)
        self.records = []
        for row in reader:
            self.records.append(Record(row))

    def __iter__(self):
        for record in self.records:
            yield record

"""
SFF version2定義クラス (for sprmake2)
"""
class SFFv2:
   
    def __init__(self, input, name):
        self.sprites = SpriteList(input)
        self.name = name
        self.compress5 = 'lz5'
        self.compress8 = 'rle8'
        self.compress24 = 'none'
        self.decompressOnLoad = True
        self.detectDuplicates = True
        self.autoCrop = True
        self.detectPaletteDuplicates = True
        self.discardDuplicatePalettes = True
        self.reversePalette = False
        self.reversePngPalette = False
        self.palettes = {}

    def addPalette(self, group, no, fileName):
        if group not in self.palettes:
            self.palettes[group] = {}
        self.palettes[group][no] = fileName

    def setDefaultPalette(self, group, no):
        self.defaultPalette = (group, no)

    def output(self, out):
        self.outputBase(out)
        self.outputDefaultOptions(out)
        self.outputPalettes(out)
        self.outputSprites(out)

    def outputBase(self, out):
        out.write("[Output]\n")
        out.write("filename = %s\n" % self.name)
        out.write("\n")

    def outputDefaultOptions(self, out):
        out.write("[Option]\n")
        out.write(";input.dir =\n")
        out.write("sprite.compress.5 = %s\n" % self.compress5)
        out.write("sprite.compress.8 = %s\n" % self.compress8)
        out.write("sprite.compress.24 = %s\n" % self.compress24)
        out.write("sprite.decompressonload = %d\n" % (1 if self.decompressOnLoad else 0))
        out.write("sprite.detectduplicates = %d\n" % (1 if self.detectDuplicates else 0))
        out.write("sprite.autocrop = %d\n" % (1 if self.autoCrop else 0))
        out.write("pal.detectduplicates = %d\n" % (1 if self.detectPaletteDuplicates else 0))
        out.write("pal.discardduplicates = %d\n" % (1 if self.discardDuplicatePalettes else 0))
        out.write("pal.reverseact = %d\n" % (1 if self.reversePalette else 0))
        out.write("pal.reversepng = %d\n" % (1 if self.reversePngPalette else 0))
        out.write("\n")

    def outputPalettes(self, out):
        out.write("[Pal]\n")
        for group in self.palettes:
            palettes = self.palettes[group]
            for no in palettes:
                out.write("%d, %d, %s, 0, 255\n" % (group, no, palettes[no]))
        out.write("\n")
#        out.write("[Option]\n")
#        out.write("sprite.usepal = %d, %d\n" % (self.defaultPalette[0], self.defaultPalette[1]))
#        out.write("\n")


    def outputSprites(self, out):
        shared = False
        out.write("[Option]\n")             # 独自パレット
        out.write("sprite.usepal = -1\n")
        out.write("[Sprite]\n")
        for sprite in self.sprites:
            #sys.stderr.write("%s, %d\n" %(sprite.group == 9000, sprite.group))
            if not shared:
                if ((sprite.group == 9000 or sprite.group == 0) and sprite.number is 0):
                    #sys.stderr.write("%d, %d\n" % (sprite.group, sprite.number))
                    shared = True
                    out.write("[Option]\n")
                    out.write("sprite.usepal = %d, %d\n" % (self.defaultPalette[0], self.defaultPalette[1]))
                    #out.write("sprite.removecolors = 0, 255\n")
                    out.write("[Sprite]\n")
            else:
                if not sprite.shared:
                    shared = False
                    out.write("[Option]\n")
                    out.write("sprite.usepal = -1\n")
                    out.write("[Sprite]\n")
            out.write("%s\n" % sprite)
        out.write("\n")

def usage():
    sys.stderr.write("USAGE: %s <sff name> [<input csv> [<output file>]]\n" % (sys.argv[0]))
    sys.stderr.write("\t<sff name>\tName of SFF File Name\n")
    sys.stderr.write("\t<input csv>\tSff Air Editor style SFF Export CSV\n")
    sys.stderr.write("\t<output file>\tOutput File Name\n")
    sys.stderr.write("\n")
    sys.stderr.write("<input csv> and <output file> can be omitted. If omitted these elements, uses <STDIN> for input and <STDOUT> for output.\n")

if __name__ == '__main__':
    input = sys.stdin
    output = sys.stdout
    name = None
    if len(sys.argv) > 1:
        name = sys.argv[1]
    else:
        usage()
        exit()

    if not name is None:
        if len(sys.argv) > 2:
            input = file(sys.argv[2], "r")
        if len(sys.argv) > 3:
            output = file(sys.argv[3], "w")

        sffv2 = SFFv2(input, name)
        palettes = listFiles(os.getcwd(), ".*act$")
        for i in range(0, len(palettes)):
            sffv2.addPalette(1, i + 1, palettes[i])
        sffv2.setDefaultPalette(1, 1)
        sffv2.output(output)
    else:
        sys.stderr.write("Error: No given sff name\n")

#    for file in listFiles(os.getcwd(), '.*\\.act$'):
#        print file

renames.awk

BEGIN {
    FS = ",";
}
{
    printf("rename %s %s\n", $1, gensub(/[[:space:]]/, "_", "g", $1));
}

この二つのスクリプトは
  1. sae2sffv2.pyでsprmake2用defファイルを作成する
  2. sae2sffv2.pyはファイル名部分を"<通し番号> <グループ>-<番号>" のファイル名を"<通し番号>_<グループ>-<番号>" に変換する
  3. renames.awkからファイル名変換用batファイルを出力する
  4. batを実行してファイル名を変換する
  5. sprmake2にdefファイルを渡してSFFv2形式のSFFを作成する
という手順で使用する。

ファイル名変換について

ファイル名を変換する理由は、単にsprmake2がスペースを含むファイル名を扱えないからである。

キャラクターの色を設定する

SFFを変換したら、キャラクター側で使う色を設定する

defファイルの内容

defに[Palette Keymap]セクションを書き、どのキーとパレット番号が対応するかを設定する。
[Palette Keymap]
a  = 1        ; 1P
b  = 2        ; 2P
c  = 3        ; 3P
x  = 4        ; 4P
y  = 5        ; 5P
z  = 6        ; 6P
a2 = 7        ; 7P
b2 = 8        ; 8P
c2 = 9        ; 9P
x2 = 10       ; 10P
y2 = 11       ; 11P
z2 = 12       ; 12P

5900番ステート

パレットを設定しただけでは実は色を変更することは出来ない。実態は5900番ステートの先頭にある次の記述である。
[State 5900:            Set Palette]
type     = RemapPal
trigger1 = 1
source   = 1, 1
dest     = 1, PalNo
つまり、初期化処理のひとつとして選択されているパレットに色を変更しているのである。
ここを調整すればパレット番号だけでなく、パレットグループも変えることが可能と思われる。

ゲージ位置の調整

MUGEN1.0では画面の高さと幅を取得する「GameWidth」「GameHeight」トリガーが追加された。これを用いて、画面下に張り付くタイプのゲージの位置を調整する。具体的にはGameHeight - 10などとして、画面下からどれだけ離れるか、を設定することになる。

その他エフェクト

その他画面下が基準となるエフェクトや、画面外の位置を基準とするようなエフェクト・動作の調整を行う。

AIスイッチ

MUGEN1.0ではゲーム難易度を取得するためのAILevelトリガーが追加されている。このトリガーは0~8の値をとる模様。0は人操作、1以上はCPU操作である。
さしあたってはAIレベルにAILevelトリガーを設定する。なお、このトリガーを使用するとAI起動用コマンドならびに起動用ヘルパーを用いることなく、最速でAI・人操作の判断を行える。
ċ
renames.awk
(0k)
Kenichi Tanaka,
2010/05/05 0:47
ċ
sae2sffv2.py
(6k)
Kenichi Tanaka,
2010/05/05 0:47