ゲームのアイデア
私はソーシャルネットワーク向けの本格的なゲームを開発したいという長年の願いがありましたが、興味深いアイデアはありませんでした。 アプリストアで、英語のみの単語ゲームを見たことがあります。 ロシア語辞書でも同じことをしたかった。
ゲームの意味は、六角形に触れることで言葉を作ることです。 ゲームには3つのモードがあります。
- 時間に対するゲーム(単語を検索する場合、文字ごとに1秒が追加されます);
- 割り当てられた時間内に道を見つけます(言葉を見つけ、それによってセンターを出る道を開きます);
- フリーモード(いつでも単語の検索と補完)。
外観
収益化
元のゲームでは、収益化は、ユーザーが3つのうち1つのタイプのゲームにアクセスできることでした。 残りのゲームは、50ゲームと100ゲームのプレイ後にロック解除されます。
同様の収益化を行うことにしました。 しかし、後に単語の提案機能が追加されました。 ヒントを購入できます。
追加の広告:
- VK-出会い系サービスとプリローダー
- OK-トラフィック管理システムを提供
費やした時間
開発は、メインの仕事からの空き時間にゼロから(フレームワークの研究を含む)行われ、最初の月には非常に活発です。
純開発コストのスケジュール(ドキュメントとフォーラムの時間は含まれません):
理論
まず、ゲームを開発する前に、六角形の数学の特徴を研究する必要がありました。 www.redblobgames.com/grids/hexagonsを知るために必要なすべてを網羅した素晴らしい記事
開発環境の選択
開発は2つの部分に分けられました-2つの独立したプロジェクトに:
- ゲーム自体のJSコード。
- ソーシャルネットワークと対話するためのプレーヤー統計と追加ファイルを保存/発行するためのPHPコード。
最初は、クロスプラットフォームフレームワークを使用してブラウザ用のゲームを開発し、それをiosおよびandroidに移植するというアイデアでした。 したがって、選択肢はcocos2dフレームワークバージョンhtml5にあります。
このフレームワークの主な欠点は、不十分なドキュメントであり、ソースでいくつかのことを見つける必要がありました。 しかし、このフレームワークの使用、その人気の一部、およびzyngaの開発への参加によって賄われました。 一般的に、私は彼に失望していませんでした。
このフレームワークの特徴は、画面上のすべてがレイヤーとスプライトオブジェクトに分割されることです。 デフォルトでは、60フレーム/秒で描画されます。 グラフィックのハードウェアアクセラレーションを使用するwebglモードの自動または自動選択を強制することもできます。
グラフィックス
プロトタイプの準備が整った後、フリーランスのWebサイトですべてのグラフィックを注文しました。 それとは別に、アイコンを注文する必要がありました。
いくつかの技術的側面と開発機能
問題1-スプライトキャッシング
ゲームのメイン画面を見ると、多くのオブジェクトを見ることができます-これらは六角形と文字です。 2つのゲームの合計数は約5,000で、これが最初の問題を引き起こしました-フレームワークは毎秒60回すべてを再描画し、ひどく遅くなります。
この問題を解決するために、1つの解決策が見つかりました。
- 写真として文字を使用します。
- すべての写真を1つのファイルに結合します。 Texture Packerプログラムを使用して、すべての写真を2つのpngファイルにパックしました。 1つのファイルはメインメニューの画像に使用され、2番目のファイルはゲーム自体に使用されました。 1つの一般的な画像をスプライトとともに保存し、さらに各スプライトの境界の説明を含むxmlファイルを適用します。 これはすべてフレームワークによってインポートされ、メモリに保存されます。
- 特別なSpriteBatchNodeコンテナを使用します。 ドキュメントによると、SpriteBatchNodeに含まれるすべてのスプライトはWebGLの1回の呼び出しで描画され、このコンポーネントに含まれないすべてのスプライトは個別に描画されます。 コンテナに追加するすべてのファイルが同じファイルにあることを確認してください。
SpriteBatchNodeコンテナーを使用するサンプルコードvar size = cc.director.getWinSize(); var x0 = size.width / 2 - (3 / 4 * this.game.tileWidth * this.game.size) / 2; var y0 = size.height / 2 + this.game.tileHeight * this.game.size / 2; var hex = cc.textureCache.addImage(res.Game_png); this._hexBatch = new cc.SpriteBatchNode(hex, 50); this._tableLayer.addChild(this._hexBatch); for (var i = 0; i < this.game.size; i++) { for (var j = 0; j < this.game.size; j++) { var layer = this.game.table[i][j]; if (!layer || layer == '*') { continue; } var height = Math.sqrt(3) / 2 * layer.width - 2; var width = layer.width - 2; var x = x0 + i * 3 / 4 * width; var y = y0 - j * height / 2 - (j + 1) * height / 2 - (i % 2) * height / 2; var border = new cc.Sprite('#' + layer.hexagon_border); border.x = x; border.y = y; this._hexBatch.addChild(border); layer.attr({ x: x, y: y }); this._hexBatch.addChild(layer); } } this.addChild(this._tableLayer); this.wordPreviewLayer = WordPreviewLayer.create(); this.addChild(this.wordPreviewLayer);
唯一の問題は、ユーザーがアクティブなWebGLを使用している場合にのみ意味があり、ハードウェアアクセラレーションがなければ、同じように機能しないことです。
問題2-イベント
プロジェクト全体は個別のコンポーネントに分割され、各コンポーネントは個別のクラスとファイルにあります。 互いに直接連絡することはありません。 相互作用は、グローバルイベントを使用して実装されます。 Cocos2dには、イベントを操作するためのEventManagerクラスが含まれています。
多くの独自のイベントが作成されました。
イベント初期化の例
var Timer = cc.LabelBMFont.extend({ started: false, paused: false, lastTime: null, timerLength: 0, alert: 20, listeners: [], init: function () { this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_START, function (data) { this.start(data.getUserData()); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_STOP, function () { this.stop(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_PAUSE, function () { this.pause(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_CONTINUE, function () { this.resume(); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_ADD, function (data) { var seconds = data.getUserData(); this.add(seconds); cc.eventManager.dispatchCustomEvent(EVENT_TIMER, this.timerLength); }.bind(this))); this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_END, function (data) { this.timerLength = 0; this.stop(); }.bind(this))); return true; }, cleanup: function() { this._super(); for (var i in this.listeners) { cc.eventManager.removeListener(this.listeners[i]); } },
イベント呼び出しの例:
cc.eventManager.dispatchCustomEvent(EVENT_TIMER_START, this.timer_start);
カスタムイベントを使用する場合、いくつかの機能もあります。オブジェクトが破棄された後、eventManagerはまだオブジェクトが署名されていると見なします。 この問題を解決するために、各オブジェクトに対してリスナーのローカル配列がいっぱいになり、オブジェクトが破棄されると、クリーンアップデストラクタメソッドが常に呼び出され、追加されたカスタムメソッドが削除されます。
問題3-モバイル版
ゲームのモバイル版はうまくいきませんでした。
まず、ゲームはSpiderMonkeyエミュレーターのモバイル環境で実行されますが、何らかの理由でキャッシュが機能しませんでした。 そのため、iPadのゲームはひどく遅くなりました。
第二に、私が書いた構造のいくつかはエラーを引き起こしました;互換性のためにコードを修正する必要があります。
iOSおよびAndroidへの移植のアイデアは現在延期されています。
アチブキ
ゲームの後半で50の実績が追加されました。 彼らとの仕事はそれほど難しくありませんでした。 アチーブメントを実装するには、パラメーターとinitメソッドを含む配列を作成してハンドラーを初期化し、アチーブメント条件の履行を記録します。
例
var achievements = [ { id: 'the_graduate', name: t('The Graduate'), description: t('You completed Tutorial'), game: 'Tutorial', icon: 'graduate.png', pts: 1, init: function () { var event = cc.eventManager.addCustomListener(EVENT_TUTORIAL_END, function () { sendAchieve(this, event); }.bind(this)); } }, { … }];
そして、ユーザーがまだ獲得していないすべての実績の初期化を追加します。
コード
cc.eventManager.addCustomListener(EVENT_GAME_LOADED, function () { for (var i in achievements) { if (!cc.UserData.achievements || cc.UserData.achievements.indexOf(achievements[i].id) == -1) { achievements[i].init(); } } cc.eventManager.addCustomListener(EVENT_SEND_ACHIEVEMENT, function(data) { var achieveData = data.getUserData(); if (!cc.UserData.achievements) { cc.UserData.achievements = []; } cc.UserData.achievements.push(achieveData.id); }) });
この例では、トレーニングレベルの最後に、獲得した達成度に関するポップアップメッセージがユーザーに表示され、リクエストがサーバーに送信され、達成度がユーザー情報配列に追加されます。
単語検索
サーバーからゲームをダウンロードすると、単語の辞書が読み込まれ、各辞書を検索するために使用されます。 最初は、単語を含む単純な配列が使用されましたが、辞書には約15万の単語が含まれているため、全体的なパフォーマンスに影響しました。 この問題の解決策を探してみると、TrieまたはPrefixツリーの概念に出会いました。 その結果、元の配列はトライデータ形式のファイルに変換されました。 このソリューションにより、単語の各文字を強調表示する際のブレーキを大幅に減らすことができました。
プロジェクトの組み立て
jsソースを2番目のプロジェクトに転送する前に、すべてのファイルを1つのjsファイルにアセンブルおよび縮小する必要があります。 このため、cocos2dには既に展開された既製のbuild.xmlファイルがあります。 その結果、コンソールの1つのコマンド-antによってアセンブリが実行されます。 完成したファイルは、単に2番目のプロジェクトにコピーされます。
統計の収集とソーシャルネットワークとの統合
バックエンドとして、symfony 2とmongoデータベースが選択されたのは、私にとってはバックエンド部分をすばやく簡単に実装する方法だからです。
ここでは、1つの機能のみを強調表示できます。各ソーシャルネットワークについて、個別の統合ファイルが作成され、build.xmlは各ソーシャルネットワークに対して3つの縮小ファイルを作成しました。
Webサーバーの最適化
プロジェクト全体は、10ドルの標準価格でdigitaloceanの1つのvpsサーバーでホストされます:1コアCPU、1Gb ram、30Gb ssd。 Debian OS、nginx + php-fpm Webサーバー。 ドン・モンゴッド。
トラフィックを最適化するために、gzip最適化が有効になっています。 しかし、これにより、クラスメートに多くのトラフィックがあったときにプロセッサの負荷が増加しました。 その結果、次のソリューションが実装されました。
gzip_static on;
build.xmlスクリプトは、jsファイルごとに、拡張子が.gzの圧縮バージョンを作成したため、nginxはこれらのファイルを取得し、CPU負荷なしでクライアントに提供しました。
統計
ゲームは最初にVKで発売されました。 承認の翌日、ゲームは新しいセクションに追加されました。
2週間後に別の急増がありました。新しいアイコンを注文したとき、より論理的に見えました。
グラフ
Facebookでは、状況はあまり明確ではありません。 それにもかかわらず、広告なしでそこに行くのは無意味であるように思えます-カタログには多くのモバイルゲームがあります。
統計は次のとおりです。 1月上旬には明確な急増が見られ、急激にゼロになりました。
獲得した資金を転送するには、書類を提出する必要があります。 ここでも、私は理解していませんでした-個人として登録できますが、彼らは別のPSRNを要求します。
グラフ
OKでゲームを開始するには、IPまたは法人が必要です。 しかし、すでにゲームをOKにしている人を見つけて公開し、10%の金額をアカウントに転送することに同意することができます(OKには50%以上かかります)。
OKでは、統計がより興味深いように見えます:
グラフ
リリース以来の一般的なGoogle統計:
グラフ
Googleにはイベントを保存する機能もあります。 たとえば、ゲーム内のイベントに関する統計情報があります。
グラフ
CPU負荷統計:
グラフ
合計
費用
- 開発には約200時間の自由時間がかかりました。
- デザイン-5500こする。
「収入」
VK:
- ユーザー-31,000人。
- 全期間の支払い-84票;
- 広告収入-1,500ルーブル。
OK:
- ユーザー-171,500人。
- 1月の支払い-27660 OK;
- 広告収入(2週間)-2600ルーブル。
FB:
- ユーザー-12,900人。
- 支払い-4.70ドル。
まとめ
私たちは、いくらかのお金のために、義務的な寄付を伴うゲームが必要であると結論付けることができます。 また、すべての種類の収益化と広告はすぐに接続する必要があります。 私は人気のある広告サイトと実際のトラフィック量に精通していなかったため、後でトラフィックの最初の波が通過したときにそれらを接続しませんでした。
また、ゲームがどのように進むかが事前にわからないため、負荷テストを実行する必要があります。