Go(Google App Engine)およびLua(Corona SDK)でのペンギン保育シミュレータの作成



1.はじめに



このプロジェクトは、モバイルアプリケーションでGoogle App Engineを使用する簡単な例です。



サーバー部分は、JSON形式のペンギンのリストを提供します。 モバイルクライアントは、HTTPまたはHTTPSを介してこのリストを要求します。

また、サーバー側はデータベースに特定のイベント、つまり特定のペンギンへの訪問数とボタンを押した回数を記録します。つまり、魚に餌を与え、おなかを掻きます。

各ペンギンには、 Name



Bio



およびcounterフィールドの説明フィールドがあります。



2.翻訳の微妙さ



ペンギン保育シミュレータをロシア語に翻訳する方法を考えましたが、「幼稚園」は「保育」や「保育」としても適していません。 したがって、翻訳せずに残っ​​た。



3.準備



Google App Engine Go SDKがインストールされていない場合は、 Google App Engineリンクをクリックし、「今すぐ試す」をクリックして、すべてのポイントに従います。 プロジェクトに名前を付け、[Go]を選択し、SDKをダウンロードしてインストールします。 環境変数( PATH



GOROOT



GOPATH



APPENGINE_DEV_APPSERVER



)がAPPENGINE_DEV_APPSERVER



いることを確認してください。これにより、 goapp



コマンドが表示されます。 今後は、単純なプロジェクトをGAEサーバーにアップロードして実行goapp deploy



は、プロジェクトディレクトリでgoapp deploy



コマンドを実行する必要があるとgoapp deploy



ます。 彼女は、プロジェクトを配置するGoogleアカウントのメールを尋ねます。 プロジェクト名がapp.yamlとサイトで一致することが重要です。 ただし、このプロジェクトでは、モジュールが使用され、ロードプロセスが多少異なります。



GoのIDEとしてLiteIDEを、LuaおよびCorona SDKのZeroBrane Studioをお勧めします。 Corona SDKはWebサイトからダウンロードできます。



4.クリントサーバー



次の図は、クライアント(左)とサーバー(右)の間の非常に複雑な通信スキームを示しています。









ご覧のとおり、クライアントはペンギンのリストのみを要求し、3つのイベントのみを送信します。 通信はHTTP経由ですが、HTTPSは無料で使用できます。 これは、GAEを使用する利点の1つに起因する可能性があります。SSL証明書に支払いをして、それで作業を構成する必要はありません。



すべてがHTTPを介して機能するため、特別なクライアントを使用せずにブラウザーでリクエストを直接実行できます。



penguin-daycare-simulator.appspot.com

モバイルクライアントでは使用されない単純な挨拶ですが、サービスが機能しているかどうかを確認できます。 httpをhttpsに置き換えて、これも機能することを確認できます。



penguin-daycare-simulator.appspot.com/penguins

これは最も重要なリクエストです。 モバイルクライアントは、その助けを借りて、現在監視下にあるすべてのペンギンのリストを受け取ります。

このデータをより便利に表示するには、ChromeのJSONview拡張機能をお勧めします。



penguin-daycare-simulator.appspot.com/stat/visit

penguin-daycare-simulator.appspot.com/stat/fish

penguin-daycare-simulator.appspot.com/stat/bellyrub

これらの3つのクエリは、ペンギンの対応するカウントをインクリメントします。 ペンギンId



POSTパラメーターとして渡されます。 サーバーは応答で何も返しませんが、必要に応じて、文字列「OK」または操作の成功の別の信号を応答に追加できます。



5.より多くのスクリーンショット、より多くのスクリーンショット!






記事の公開前に、私はこのペンギンについて思い出しました:

ポジティブを見る


6.サーバー-Google App Engine



これで、コードに直接アクセスできます。 Goプロジェクトのファイル構造を考慮してください。

 PenguinDaycareSimulatorServer/ ├── default/ │  ├── app.go │  ├── default.yaml │  └── penguins.json ├── static/ │  ├── favicon.ico │  └── static.yaml └── dispatch.yaml
      
      





default



およびstatic



はモジュールです。 GAEのプロジェクトはモジュールに分割することも、モジュールなしで機能することもできます。 この場合、 app.yaml



ファイルはapp.yaml



app/app.go



およびpenguins.json



3つだけです。 元々これは私のプロジェクトのケースでした(GitHubで最初のコミットを見ることができます)が、アプリケーションの1つのインスタンスが処理できる同時リクエストの数を担当するmax_concurrent_requests



設定を追加したかったのです。 デフォルト値は10のみです。Goは明らかにそれ以上の機能を備えています。 最大値は500です。負荷が増加してこの値を超えると、アプリケーションの追加のコピーが起動され、それらの間で負荷が分散されます。 GAEの無料のクォータのみに収めたい場合は、この設定を使用することを強くお勧めします。 アプリケーションがこのような負荷に対応していない場合は、この値を減らして有料請求に切り替えます。



したがって、この設定はモジュールでのみ使用できます。 また、GAEがモジュールと見なすには、アプリケーションに少なくとも2つのモジュールが必要です。



static



は非常にシンプルなモジュールであり(上記のGAEの制限がない場合)、そのタスクはfavicon.ico



ファイルを静的に提供することだけです。



default



-すべての作業を行うメインモジュール。



*.yaml



ファイルは設定と説明です。 各モジュールに1つと、どのモジュールがどのURLを処理するかを記述する1つのdispatch.yaml



ファイル。

dispatch.yaml
 application: penguin-daycare-simulator dispatch: - url: "*/favicon.ico" module: static - url: "*/" module: default
      
      





static.yaml
 application: penguin-daycare-simulator module: static version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /favicon.ico static_files: favicon.ico upload: favicon.ico
      
      





default.yaml
 application: penguin-daycare-simulator module: default version: 1 runtime: go api_version: go1 automatic_scaling: max_concurrent_requests: 500 handlers: - url: /.* script: _go_app
      
      





static.yaml



ランタイムでは、GoではなくPythonが指定されていることに注意してください。 これは、実際にGoファイルなしでGoにモジュールをロードしようとすると、GAEが誓うからです。 しかし、彼はこの状況でPythonとPHPを誓いません。

オフトピック
ここの注意深い読者は、「PHPは静的ファイルのレンダリングに関してPythonよりも悪い」と主張し、聖戦を解き放とうとしますが、Pythonは個人的に私に近いので、それを選んだのです。 誰でもこの目的でPHPを使用できます。 もちろん、このプロセスにはPythonもPHPも関与していないため、これはすべて無意味です。



default.yaml



handlers



は、特定のURLが処理する実行可能ファイルを示します。 この場合、app.goはすべての着信リクエストを処理します( dispatch.yaml



を考慮に入れます)。 URLの説明は非常に柔軟で、正規表現を使用します。 ただし、PythonとPHPで異なるファイルを使用して同じモジュール内の異なるURLを処理できる場合、Goでは「_go_app」として指定された単一のファイルである必要があります。 さらに、既にGoプログラム内で、必要に応じて、異なるURLのハンドラーを選択し、アプリケーション全体を複数のファイルに分割できます。



設定とyamlファイルの詳細については、 こちらをご覧ください



penguins.json



使用されているすべてのペンギンの名前と説明を含むJSONファイル。

penguins.json
 [ {"id": "1", "name": "Tux", "bio": "Beloved Linux mascot" }, {"id": "2", "name": "Skipper", "bio": "Small combat squad leader" }, {"id": "3", "name": "Lolo", "bio": "Russian adventurer" }, {"id": "4", "name": "Gunter", "bio": "The darkest character in Adventure Time" }, {"id": "5", "name": "The Penguin", "bio": "Na, na, na, na, na, na, na, na, na, na... The Penguin! " } ]
      
      





このファイルを介してペンギンの追加、編集が行われます。



アプリケーション全体のapp.go



ます。 完全なリストは、GitHub- app.goで見るのに便利です。



このファイルの単純化された構造:

 package app    . import (...)   : Id, , , . type penguin struct {...}  ()  . var penguins []penguin     . type penguinEntity struct {...} . func init() {...}  penguins.json   penguins. func loadPenguinsJson() {...}  / -   . func rootHandler(w http.ResponseWriter, r *http.Request) {...}  /penguins -        JSON. func penguinsHandler(w http.ResponseWriter, r *http.Request) {...}   /stat/visit -  . func visitHandler(w http.ResponseWriter, r *http.Request) {...}   /stat/fish -   . func fishHandler(w http.ResponseWriter, r *http.Request) {...}   /stat/bellyrub -    . func bellyrubHandler(w http.ResponseWriter, r *http.Request) {...}
      
      





アプリケーションが起動すると、最初にinit()関数が起動されます。この関数は、penguins.jsonファイルから読み取り、クライアントからのさまざまな要求に応答する関数を設定します。 記事の冒頭のリンクで既に使用できます。



penguinsHandler()



は、 json.Marshal()



関数を使用してペンギンスライスをJSON形式にjson.Marshal()



し、 json.Marshal()



を介してクライアントに提供します。



visitHandler()



fishHandler()



bellyrubHandler()



は同じロジックに従って動作します。データベースからペンギンを取り出し、対応するパラメーターを1つ増やしてデータベースに書き戻します。 データベース- データストア -はSQL互換ではありません。つまり、NoSQLソリューションです。 彼女の作品の説明は別の記事に値します。



データストアへのアクセスなど、GAEの多くの操作は個別に課金されるため、リソースの過剰な使用は避けてください。 そのため、たとえば、すべてのペンギンの統計を要求する場合、関連データを提供することは完全にオプションです。 このリクエストは、たとえば10分のキャッシュ有効期間でキャッシュできます。 これを行うために、追加の変数lastUpdateTime



を導入しました。これは、 penguins



スライスの最後の更新のタイムスタンプです。 そして、各/penguins



リクエストでupdatePenguinsStatistics()



関数を呼び出します。この関数は、キャッシュの有効期限が切れているかどうかを確認し、サイクルでpenguins



スライス内の各ペンギンのカウンターを更新します。



手動で更新を強制するために、追加の要求/更新と対応するupdateHandler()



ハンドラーを導入しました。



各要求は独自のゴルーチンで処理されるため、 penguins



スライスを記録中の同時書き込みまたは読み取りから保護する必要があります。 このために、 RWMutex



が使用されます-読み取りまたは書き込み用のミューテックス。 その使用は、単純なMutex



よりも効果的です。



リソースの支払済み消費を回避するために、データベースに遅延レコードを入力して、受信したすべてのイベントの値を蓄積することもできます。



プロジェクトをGAEサーバーにアップロードするには、プロジェクトディレクトリのターミナルで3つのコマンドを実行する必要があります。

 goapp deploy default/default.yaml goapp deploy static/static.yaml appcfg.py update_dispatch .
      
      





将来、app.goを変更するときは、 goapp deploy default/default.yaml



を実行するだけです。



結論として、無料の制限を増やすには、有料の請求書を接続することをお勧めしますが、同時に1日あたりの最大費用を1ドルに設定することをサーバー側についてお話します。 同時に、一部の無料割り当てが増加していますが、まだ何も使用していません。



7.クライアント側-Corona SDK



Corona SDKは、Android、iOS、Windows Phone(近日公開予定)、HTML5(開発中)用のモバイルアプリケーションを開発するためのクロスプラットフォームフレームワークです。 私はこの製品をかなり長い間使用しており、フリーランサーとしてのクライアントと私自身のためにゲームを書いています。 適切な速度とアプリケーションの作成速度に注意してください。



プロジェクトのファイル構造から始めましょう。 主にアイコンと写真のために、ここにさらにファイルがありますので、私はスポイラーの下でそれらをきれいにします。

ファイル構造
 PenguinDaycareSimulator/ ├── images/ │  ├── penguins/ │  │  ├── 1.png │  │  ├── 1@2x.png │  │  ├── 2.png │  │  ├── 2@2x.png │  │  ├── 3.png │  │  ├── 3@2x.png │  │  ├── 4.png │  │  ├── 4@2x.png │  │  ├── 5.png │  │  └── 5@2x.png │  ├── background.jpg │  ├── background@2x.jpg │  ├── button-over.png │  ├── button-over@2x.png │  ├── button.png │  ├── button@2x.png │  ├── dot-off.png │  ├── dot-off@2x.png │  ├── dot.png │  ├── dot@2x.png │  ├── fish.png │  ├── fish@2x.png │  ├── hand.png │  ├── hand@2x.png │  ├── popup.png │  └── popup@2x.png ├── lib/ │  ├── api.lua │  ├── app.lua │  └── utils.lua ├── scenes/ │  ├── choose.lua │  ├── menu.lua │  └── penguin.lua ├── Default-568h@2x.png ├── Icon-60.png ├── Icon-60@2x.png ├── Icon-72.png ├── Icon-72@2x.png ├── Icon-76.png ├── Icon-76@2x.png ├── Icon-Small-40.png ├── Icon-Small-40@2x.png ├── Icon-Small-50.png ├── Icon-Small-50@2x.png ├── Icon-Small.png ├── Icon-Small@2x.png ├── Icon-hdpi.png ├── Icon-ldpi.png ├── Icon-mdpi.png ├── Icon-ouya.png ├── Icon-xhdpi.png ├── Icon-xxhdpi.png ├── Icon.png ├── Icon@2x.png ├── build.settings ├── config.lua └── main.lua
      
      





今のところ、Luaファイルにのみ注意を払うことができます。



config.lua



build.settings



-Corona SDKのプロジェクト設定ファイル。 ポートレートビューまたはランドスケープビューに、アプリケーション、参照画面の解像度、ズーム方法、その他のさまざまな設定があることを示します。 Corona SDKが初めての場合は、今のところこれらのファイルに注意を払うことはできません。



また、ルートにはiOSとAndroidのアイコンがDefault-568h@2x.png



、iPhone 5で正常に動作するためにDefault-568h@2x.png



があります。画像/ディレクトリ内には、通常のファイルと2倍のHDバージョン@2x



ます。 原則として、iPhone 3GSのような画面を持つデバイスはサポートできなくなりました。その割合は非常に小さいですが、それでもゼロではありません。 iPad Retinaを完全にサポートするには、すでに@4x



ファイルとconfig.lua



行が必要ですが、ほとんどのゲームは問題なく動作します。



Corona SDKはmain.lua



ファイルからアプリケーションを起動し、必要なライブラリがそれに接続され、いくつかの変数が宣言され、シーンが「デイケアに入る」ボタンで切り替えられます。 アプリケーションのすべてのシーン(画面)は異なるファイルに保存され、 scenes/



ディレクトリに収集され、すべてのユーザーライブラリをlib/



に配置しました。 開発者はこれらのファイルを好きなように自由に配置できます。



lib/



app.lua



utils.lua



が含まれています-これは一緒にCorona SDKを操作するための便利な関数のコレクションです。 app.lua



、画像、テキスト、ボタンなどを表示するための標準Corona SDK関数の便利なラッパーを実装します。



main.lua



からscenes/menu.lua



main.lua



への移行scenes/menu.lua



、ラインを介して行われます

 storyboard.gotoScene('scenes.menu')
      
      





ここで、ペンギン要求は既にサーバー上で実行されています。 menu.lua



主要なコードをmenu.lua



ます。

 function scene:createScene (event) local group = self.view app.newText{g = group, text = 'Penguin Daycare', size = 32, x = _CX, y = _CY - 150} app.newText{g = group, text = 'Simulator', size = 32, x = _CX, y = _CY - 110} local pleaseWait = app.newText{g = group, text = 'Please Wait', size = 16, x = _CX, y = _CY} local button = app.newButton{g = group, x = _CX, y = _CY, text = 'Enter the Daycare', onRelease = function() storyboard.gotoScene('scenes.choose', {effect = 'slideLeft', time = app.duration}) end} button.isVisible = false app.api:getPenguins(function() pleaseWait.isVisible = false button.isVisible = true end) end
      
      





3行のテキストと1つのボタンが作成されます。 サーバーから応答を受け取るまで、ボタンは非表示になります。 要求自体はapp.api:getPenguins()



関数によって実行され、引数としてコールバック関数があります。



ボタンをクリックすると、ペンギンの選択シーンがchoose.lua



ますchoose.lua



ファイルのコードの主要部分のみをchoose.lua



ます。

 function scene:createScene(event) local group = self.view self.backButton = app.newButton{g = group, x = _L + 10, y = _T + 10, w = 48, h = 32, rp = 'TopLeft', text = 'Back', fontSize = 14, onRelease = function() storyboard.gotoScene('scenes.menu', {effect = 'slideRight', time = app.duration}) end} local function gotoPenguin(ind) storyboard.gotoScene('scenes.penguin', {effect = 'slideLeft', time = app.duration, params = ind}) end local slideView = newSlideView{g = group, x = 0, y = _CY, dots_y = 180, onRelease = gotoPenguin} for i = 1, #app.api.penguins do local p = app.api.penguins[i] local slide = display.newGroup() app.newImage('images/popup.png', {g = slide, w = 300, h = 335}) app.newImage('images/penguins/' .. p.id .. '.png', {g = slide, w = 200, h = 256}) app.newText{g = slide, x = 0, y = -140, text = p.name, size = 18, color = 'white'} app.newText{g = slide, x = 0, y = 140, text = p.bio, size = 14, color = 'white', w = 220, align = 'center'} slideView:addSlide(slide) end slideView:makeDots() slideView:gotoSlide(1) end
      
      





ここで、 newSlideView()



は、ペンギンのスライドをスクロールできるシンプルなウィジェットを作成する関数です。 このウィジェットのコードは、ファイルの先頭のchoose.lua



にあります。



ペンギンごとにスライドが作成されます。 ペンギンの画像はアプリケーション内に保存され、ペンギンのIDに対応します。 これは、GAEサーバーなどに画像を保存することで修正できます。 ネットワークから画像をダウンロードするために、Corona SDKにはdisplay.loadRemoteImage()



関数または下位レベルのnetwork.download()



ます。



スライドをクリックすると、 gotoPenguin()



関数がgotoPenguin()



、受信したすべてのペンギンの配列(テーブル)内のペンギンの番号( Id



ではない)を取得します。 この関数は最終的なpenguin.lua



シーンにジャンプし、この引数を同じシーンに渡します。

penguin.lua
 function scene:createScene(event) local group = self.view local background = app.newImage('images/background.jpg', {g = group, w = 384, h = 640, x = _CX, y = _CY}) self.backButton = app.newButton{g = group, x = _L + 10, y = _T + 10, w = 48, h = 32, rp = 'TopLeft', text = 'Back', fontSize = 14, onRelease = function() storyboard.gotoScene('scenes.choose', {effect = 'slideRight', time = app.duration}) end} local ind = event.params local p = app.api.penguins[ind] local visitsLabel = app.newText{g = group, x = _CX, y = _T + 20, text = 'Visits: ' .. p.visit_count, size = 18, color = 'white'} local fishLabel = app.newText{g = group, x = _CX, y = _T + 40, text = 'Fish: ' .. p.fish_count, size = 18, color = 'white'} local bellyrubsLabel = app.newText{g = group, x = _CX, y = _T + 60, text = 'Belly rubs: ' .. p.bellyrub_count, size = 18, color = 'white'} local penguin = app.newImage('images/penguins/' .. p.id .. '.png', {g = group, w = 200, h = 256, x = _CX, y = _CY - 25}) app.newButton{g = group, x = _CX - 80, y = _B - 50, w = 128, h = 48, text = 'Fish', fontSize = 14, onRelease = function() local fish = app.newImage('images/fish.png', {g = group, x = penguin.x, y = penguin.y + 200, w = 512, h = 188}) fish.alpha = 0.8 transition.to(fish, {time = 400, alpha = 1, y = penguin.y, xScale = 0.1, yScale = 0.1, transition = easing.outExpo, onComplete = function(obj) transition.to(fish, {time = 400, alpha = 0, onComplete = function(obj) display.remove(obj) end}) end}) app.api:sendFish(p.id) p.fish_count = p.fish_count + 1 fishLabel:setText('Fish: ' .. p.fish_count) end} app.newButton{g = group, x = _CX + 80, y = _B - 50, w = 128, h = 48, text = 'Belly rub', fontSize = 14, onRelease = function() local hand = app.newImage('images/hand.png', {g = group, x = penguin.x - 40, y = penguin.y + 30, w = 80, h = 80, rp = 'TopLeft'}) transition.to(hand, {time = 1200, x = penguin.x + 40, transition = easing.swing3(easing.outQuad), onComplete = function(obj) display.remove(obj) end}) app.api:sendBellyrub(p.id) p.bellyrub_count = p.bellyrub_count + 1 bellyrubsLabel:setText('Belly rubs: ' .. p.bellyrub_count) end} app.api:sendVisit(p.id) p.visit_count = p.visit_count + 1 visitsLabel:setText('Visits: ' .. p.visit_count) end
      
      





penguin.lua



では、背景画像が読み込まれ、選択したペンギンの画像が表示され、いくつかのテキストラベルと2つのアクションボタンが表示されます。 それらをクリックすると、アクションがアニメーション化され、リクエストが関数app.api:sendFish()



およびapp.api:sendBellyrub()



介してサーバーに送信されます。 また、 app.api:sendVisit()



は、シーンが作成された直後に呼び出されます。 これらの各関数を呼び出した後、インターネットがない場合でも、対応するテキストラベルが更新されます。 これは、各リクエストに対してサーバーからの応答のチェックを入力し、コールバック関数を提供することで修正できます。



最後に、すべてのサーバー作業はlib/api.lua



行われlib/api.lua





api.lua
 local _M = {} local app = require('lib.app') _M.hostname = 'http://penguin-daycare-simulator.appspot.com' function _M:getPenguins(callback) local url = '/penguins#' .. math.random(1000, 9999) network.request(self.hostname .. url , 'GET', function (event) if not event.isError then local response = json.decode(event.response) if response then self.penguins = response callback() end end end) end function _M:sendVisit(id) local url = '/stat/visit' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end function _M:sendFish(id) local url = '/stat/fish' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end function _M:sendBellyrub(id) local url = '/stat/bellyrub' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end return _M
      
      





ご想像のとおり、サーバーの操作は簡単なPOSTリクエストで行われます。 getPenguins()



場合、サーバーからの応答はjson.decode()



関数によってJSON形式から配列(テーブル)に変換され、モジュールの(プロパティ)フィールドに配置されます。



ご覧のとおり、Corona SDKでのPOSTリクエストの送信とその応答への応答は非常に簡単です。 したがって、Google App Engine自体との統合は非常に簡単になりました。 私は各行が何をするかをペイントしません。構文が直感的であることを望みます。



8.参照



ソースは私のGitHubにあります:



Android 2.3.3以降にクライアントパーツをインストールできます。これはAPKミラー )です。

または、Corona SDKをダウンロードし、GitHubからソースをダウンロードして、Corona Simulatorで実行します。



この記事の執筆を手伝ってくれたM0sTH8感謝します。



Twitter @SergeyLergでフォローしください



それだけです ご清聴ありがとうございました!



All Articles