アクションステージのデータをGAEでセーブ&ロード
状況
アクションステージのセットデータをGAEでセーブ&ロードする見当がついたところまで。
解説
まず、セーブしようとしているデータは以下のような形式をとっている。
public static const STAGE_PARAM:Array = [ {//Limit Wall Left lx:RANGE_LX-10, rx:RANGE_LX, uy:RANGE_UY, dy:RANGE_DY, density:0.0,//fixed category_bits:0x0004, name:"Block" }, {//Limit Wall Right lx:RANGE_RX, rx:RANGE_RX+10, uy:RANGE_UY, dy:RANGE_DY, density:0.0,//fixed category_bits:0x0004, name:"Block" }, ];
一つのオブジェクトを一つの連想配列で表し、それをリストでまとめたものが一つのステージになる。
今回は、一つのオブジェクトを一つのデータとして保存する。もしかしたら、一つのステージを一つのデータにした方が良いかもしれないが、今のところはこの方法でいく。GAEで保存するために、以下の形式で表現できるように変換する。
class SetData(db.Model): #一つのセットデータが一つにまとめられる stage_id = db.StringProperty()#ステージ識別のためのID(未使用) object = db.StringListProperty()#一つのオブジェクトを文字列で表し、それをリストで持つ
つまり、AS3で連想配列になっているものを、GAEで文字列のリストと相互に変換できるようにすれば、セーブ&ロードが可能になる。基本的には、連想配列のkeyとvalを文字列にして、それをリストに入れれば良い。そのためには、「連想配列のvalを、"本来の型"と"文字列"で相互に変換できるようにする」「連想配列とリストの相互変換」の二つの処理が必要になる。
文字列のみの連想配列をAS3とGAEでやりとりすることにする。なので、AS3側では「"本来の型"と"文字列"の相互変換」を、GAE側では「"文字列のみの連想配列"と"リスト"の相互変換」を行う。
まず、「"本来の型"と"文字列"の相互変換」の方だが、あらかじめ以下のように「Number型のパラメータは何か」を用意しておいて、相互変換を可能にしておく。
static private const PARAM_NUMBER:Array = [ "lx", "rx", "uy", "dy", ];
これを使って、「"本来の型"→"文字列"」は以下のように行う。
#data = STAGE_PARAM for each (var obj:Object in data){ for each (var param:String in PARAM_NUMBER){ obj[param] = ""+obj[param];//Number -> String } }
逆に、「"文字列"→"本来の型"」は以下のように行う。
#data : GAEから返ってきたステージデータ for each (var obj:Object in data){ for each (var param:String in PARAM_NUMBER){ obj[param] = Number(obj[param]);//String -> Number } }
「"文字列のみの連想配列"と"リスト"の相互変換」の方は、連想配列を「key1, val1, key2, val2, ...」というリストで表現することで、相互変換を行う。
「"文字列のみの連想配列"→"リスト"」は以下のように行う。「"文字列のみの連想配列"のリスト:data」「"文字列のみの連想配列":obj」「"リスト":obj_data」となっている。
#連想配列のリストがdataとして入ってくる for obj in data: #連想配列(一つのオブジェクトのパラメータ)をobjとして抜き出して処理 obj_data = []#連想配列を、リストに変換して保存することにする for k, v in obj.iteritems(): #keyとvalが交互に入ることになる obj_data.append(k) obj_data.append(v) #格納するデータを作成 setdata = SetData(stage_id="stage", object=obj_data) #保存 setdata.put()
「"リスト"→"文字列のみの連想配列"」は以下のように行う。「"リスト":setdata」「"文字列のみの連想配列":param」「"文字列のみの連想配列"のリスト:res」となっている(上との対応がなってないので、あとで書き直そう)。
#セットデータの配列 res = [] for setdata in q: phase = 0#keyを処理中か、valを処理中か param_key = ''#keyの値を記憶しておく param_val = ''#なくてもいいけど、覚えておく param = {}#オブジェクトのパラメータを、文字列のリストから連想配列にしてこれに入れる for key_val in setdata.object: #keyとvalが交互に入っている if phase == 0:#key処理中 param_key = key_val else:#val処理中 param_val = key_val param[param_key] = param_val#「key->val」を追加 phase = 1 - phase#key <-> val:処理対象のスイッチ #セットデータが一つ求まったので追加 res.append(param) return res
あとは基本的に今までと同じ。AS3のファイル名を変更したので、app.yamlも変更したり、アクセスするURLがhttp://localhost:8080/Main.swfになったくらい。
コード
//Main.as package{ import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.net.NetConnection; import flash.net.Responder; public class Main extends Sprite { private var netConnection:NetConnection; private var responder_save:Responder; private var responder_load:Responder; private var output:TextField; public function Main() { output = new TextField(); output.autoSize = TextFieldAutoSize.LEFT; addChild(output); netConnection = new NetConnection(); netConnection.connect("http://localhost:8080/"); responder_save = new Responder(saveOnComplete, saveOnFail); responder_load = new Responder(loadOnComplete, loadOnFail); //save test var data:Array = [ { name:"first", x:0.0, y:0.0 }, { name:"second", x:10.0, y:20.0 }, ]; save(data); //load test load(); } // static private const PARAM_NUMBER:Array = [ "x", "y", ]; //=Save= //セーブ本体 private function save(data:Array):void{ //Python側の save を呼び出す。 //Convert for each (var obj:Object in data){ for each (var param:String in PARAM_NUMBER){ obj[param] = ""+obj[param];//Number -> String } } //Save netConnection.call("save", responder_save, data);//save(data) } //セーブ成功時 private function saveOnComplete():void { } //セーブ失敗時 private function saveOnFail(results:*):void { //原因を表示 for each (var thisResult:String in results){ output.appendText(thisResult); } } //=Load= //ロード本体 private function load():void{ //Python側の load を呼び出す。 netConnection.call("load", responder_load);//load() } //ロード成功時 private function loadOnComplete(data:Array):void { //Convert for each (var obj:Object in data){ for each (var param:String in PARAM_NUMBER){ obj[param] = Number(obj[param]);//String -> Number } } //Output for each (var val:Object in data){ output.appendText("name:" + val.name + "\n"); output.appendText("x:" + val.x + "\n"); output.appendText("y:" + val.y + "\n"); } } //ロード失敗時 private function loadOnFail(results:*):void { //原因を表示 for each (var thisResult:String in results){ output.appendText(thisResult); } } } }
#main.py import os import wsgiref.handlers from pyamf.remoting.gateway.wsgi import WSGIGateway from pyamf import ASObject from google.appengine.api import users from google.appengine.ext import db,webapp from google.appengine.ext.webapp import template #格納するデータ(db.Modelを継承する必要がある) class SetData(db.Model): #一つのセットデータが一つにまとめられる stage_id = db.StringProperty()#ステージ識別のためのID(未使用) object = db.StringListProperty()#一つのオブジェクトを文字列で表し、それをリストで持つ #Save def save(data): #連想配列のリストがdataとして入ってくる for obj in data: #連想配列(一つのオブジェクトのパラメータ)をobjとして抜き出して処理 obj_data = []#連想配列を、リストに変換して保存することにする for k, v in obj.iteritems(): #keyとvalが交互に入ることになる obj_data.append(k) obj_data.append(v) #格納するデータを作成 setdata = SetData(stage_id="stage", object=obj_data) #保存 setdata.put() #Load def load(): #全てのデータを取ってくるクエリを作成 q = SetData.all() #セットデータの配列 res = [] for setdata in q: phase = 0#keyを処理中か、valを処理中か param_key = ''#keyの値を記憶しておく param_val = ''#なくてもいいけど、覚えておく param = {}#オブジェクトのパラメータを、文字列のリストから連想配列にしてこれに入れる for key_val in setdata.object: #keyとvalが交互に入っている if phase == 0:#key処理中 param_key = key_val else:#val処理中 param_val = key_val param[param_key] = param_val#「key->val」を追加 phase = 1 - phase#key <-> val:処理対象のスイッチ #セットデータが一つ求まったので追加 res.append(param) return res #対応付け services = { 'save': save, 'load': load, } #Main def main(): application = WSGIGateway(services) wsgiref.handlers.CGIHandler().run(application) if __name__ == '__main__': main()