マップ読み込みクラス(その2)
RPGにおけるマップ格納アルゴリズム
http://blog.livedoor.jp/abars/archives/50454661.html
なるほど。そういえば、そうですねー。
スクロールタイプのアクションやシューティングなどでのフィールドマップは、
すんごい長いマップになるので、これはとてもいいですねー。
あと、Pythonのタプルは、書き込み不可なので、
壊れるブロックとかどうしようかな、、
listにしようかな、、、
だけどインデックスのアクセスが遅いし、、、
などと考えてたところだったので、ちょうどいい情報でしたー。
ただ、フィールドマップは基本的にReadOnlyな領域なので、
書き込みは注意してやらないと、設計が破綻してしまいそう。
注意せねば>自分。
ということで、Pythonの実装例。
#!/usr/bin/env python # -*- coding: utf-8 -*- import struct class Layer2D: """ 2次元レイヤー """ def __init__(self, width, height, data): """ コンストラクタ @param width 幅 @param height 高さ @param data データ """ self.width = width self.height = height self.data = data def _getIndex(self, args): if "idx" in args: return args["idx"] else: x = args["x"] y = args["y"] if 0 <= x < self.width: if 0 <= y < self.height: return self.toIndex(x, y) return None def get(self, **args): """ 座標を指定して値を取得 @param x, y, idx 座標 or インデックス @return 値 """ idx = self._getIndex(args) if idx is None: return None return self.data[idx] def coordinate(self, idx): """ 直交座標に変換 """ return (idx%self.width, idx/self.width) def toIndex(self, x, y): """ インデックスに変換 """ return y*self.width + x def size(self): """ 縦×横 """ return self.width*self.height def __str__(self): strData = " " for i, data in enumerate(self.data): strData += "%03d,"%data if (i+1) % self.width == 0: strData += "\n " return "(Width,Height)=(%d,%d)\n%s"%( self.width, self.height, strData) class Layer2DEx(Layer2D): """ マップによるLayer2D """ def __init__(self, width, height, data): newdata = {} for i, d in enumerate(data): if d != 0: # 0を除去 newdata[i] = d Layer2D.__init__(self, width, height, newdata) def get(self, **args): """ 座標を指定して値を取得 @param x, y, idx 座標 or インデックス @return 値 """ return self.data.get(self._getIndex(args), None) def set(self, value, **args): """ 座標を指定して値を設定 @param x, y, idx 座標 or インデックス @param value 値 """ idx = self._getIndex(args) if idx is not None: self.data[idx] = value def find(self, value): """ valueを検索 @param value 検索する値 @return 最初に見つかったインデックス """ for k, v in self.data.items(): if v == value: return k return None def pop(self, **args): """ 指定のインデックスをpop @return 値(存在しない場合、None) """ return self.data.pop(self._getIndex(args), None) def items(self): """ キーと値のリストを返す @return (キー, 値)のリスト """ return self.data.items() def __str__(self): strData = " " for i in range(self.width*self.height): strData += "%03d,"%self.data.get(i, 0) if (i+1) % self.width == 0: strData += "\n " return "(width,height)=(%d,%d)\n%s"%(self.width, self.height, strData) class FMFLoader: """ FMFファイル読込クラス """ # データのbit数に対応するバイナリコード変換関数テーブル CONVERT_BIN_FUNC_TBL = { 8: lambda cnt, b: struct.unpack("%dB"%cnt, b), 16: lambda cnt, b: struct.unpack("%dH"%cnt, b), } def __init__(self, filepath, layer2d=Layer2D): """ コンストラクタ @param filepath FMFファイルパス @param layer レイヤーとして使用するクラス(Layer2Dの派生クラス) """ self.filepath = filepath self.file = open(filepath, "rb") # ヘッダ self._readHeader() # データ( レイヤーリスト) self.layerList = map(self._createLayer, [layer2d]*self.layerCount) def _readHeader(self): """ ヘッダ読込[20byte] """ b = self.file.read(4) assert b == "FMF_", "FileformatError: Expected 'FMF'file. (file:'%s')"%self.filepath self.identifier = b # ファイル識別子 [4byte] b = self.file.read(16) (self.dataSize, # ヘッダを除いたサイズ [4byte] self.width, # マップの幅 [4byte] self.height, # マップの高さ [4byte] self.chipWidth, # チップの幅 [1byte] self.chipHeight, # チップの高さ [1byte] self.layerCount, # レイヤー数 [1byte] self.bitCount # データのbit数(8/16) [1byte] ) = struct.unpack("3L4B", b) def _createLayer(self, layer2d): """ レイヤーの生成 """ return apply(layer2d, (self.width, self.height, self._createLayerData(self.width*self.height))) def _createLayerData(self, size): """ レイヤーデータを生成 @param size レイヤーデータのサイズ """ return self.CONVERT_BIN_FUNC_TBL[self.bitCount]( size, self.file.read(size*self.bitCount/8)) def getLayer(self, index): """ レイヤーオブジェクト取得 @parma index レイヤー番号 """ if 0 <= index < self.layerCount: return self.layerList[index] return None def toStringHeader(self): """ ヘッダを文字列に変換 """ return "%s%s%s%s%s%s%s%s"%( " Identifier :%s\n"%self.identifier, " Size(except Header):%i\n"%self.dataSize, " Width :%i\n"%self.width, " Height :%i\n"%self.height, " ChipWidth :%i\n"%self.chipWidth, " ChipHeight :%i\n"%self.chipHeight, " LayerCount :%i\n"%self.layerCount, " BitCount :%i\n"%self.bitCount, ) def toStringData(self): """ データを文字列に変換 """ result = " " for idx, layer in enumerate(self.layerList): result += "[Layer:%d]\n "%idx for i, data in enumerate(layer.data): result += "%03d,"%data if (i+1) % layer.width == 0: result += "\n " return result def __str__(self): return "Header ... \n%s\nData ... \n%s"%( self.toStringHeader(), self.toStringData()) def main(): fmf = FMFLoader("area.fmf", Layer2DEx) print fmf.getLayer(0) if __name__ == "__main__": main()
インデックスと直交座標のどちらでもアクセスできるように、
ディクショナリ引数に変更しまちた。
インデックスアクセスの場合は、
layer.get(idx=14) # 14番目の要素にアクセス
などとやり、
直交座標アクセスの場合は、
layer.get(x=3, y=5) # X座標3、Y座標5にアクセス
などとやります。