Help us understand the problem. What is going on with this article?

【Swift × Node.js】サーバーレスアーキテクチャでアプリにガチャを実装してみた

More than 3 years have passed since last update.

概要

近年のソーシャルゲームでは欠かせない要素である ガチャ
ガチャを引くのが1番の楽しみというユーザーも多く、最早アプリのメインコンテンツとも言えます。
今回はそんなガチャを引くだけのアプリを作ってみました。

ソースコードも載せてあるので、ソーシャルゲームを作ってみたい方やSwiftの勉強をしている方の参考になればと思います。

アプリ紹介

Today's Lunchというアプリを作ってみました。
「今日のご飯なにたべよう」と迷った時にガチャで決めようというアプリです。
食べ物の中には超激レアなアイテムもあるのでコンプリートを目指して楽しむこともできます。

Simulator Screen Shot 2016.11.22 0.24.07.png

ソース

github

データの用意

まずはガチャで出現するItemのデータを定義します。
jsonファイルで以下のようなデータを作成しました。

lunch.json
{
    "foods": [ 
        {
            "name": "おにぎり", 
            "rate": 50, 
            "star": "☆"
        }, 
        {
            "name": "ハンバーガー", 
            "rate": 25, 
            "star": "☆☆"
        }, 
        {
            "name": "コロッケ定食", 
            "rate": 15, 
            "star": "☆☆☆"
        }, 
        {
            "name": "から揚げ定食", 
            "rate": 7, 
            "star": "☆☆☆☆"
        },
        {
            "name": "焼肉ランチ", 
            "rate": 3, 
            "star": "☆☆☆☆☆"
        }
    ]
}

itemには名前と確率とレアリティである☆が定義してあります。
一般的なソーシャルゲームのガチャもこれくらいの確率ですね。
⭐️5が出たらラッキー!

構成

クライアント側はiOS向けに作ったのでSwift3.0です。

通常ガチャのロジックはクライアント側ではなくサーバー側で実装することが多いと思うので、サーバーサイドのアプリケーションとそれを実行するためのサーバーも必要になります。
しかし、自分でサーバーを用意するとなるとサーバー自体のインフラ料金はもちろん、サーバーの構築や運用にも膨大な工数を取られてしまいます。

そんなお悩みを解決するために、今回は2016/10/25にリリースされたばかりのニフティクラウドスクリプトというサービスを使ってみました。

ニフティクラウドスクリプトとは?

このサービスはサーバーサイドのスクリプトをクラウド上で実行できるサービスです。
すぐにNode.jsの実行環境が手に入るので、[サーバー不要] [環境構築不要] [運用不要] と大変便利です。

さらに通常のサーバーを用意するのと比べて低コストで運用できるメリットもあります。
例としてニフティクラウドのIaaSで運用した場合とコストを比較してみました。

  • ガチャが月に10,000回実行された場合
月額コスト
ニフティクラウド mediumuサーバー 24,200円
ニフティクラウドスクリプト 1,000円 + 300円

※ スクリプトの処理時間は1回0.1秒としてます。

ニフティクラウドスクリプトは完全重量課金なので、実行回数と処理時間に応じて課金されます。
つまり、通常のサーバーのようにアプリが人気でなくて売り上げがないのにサーバーの料金を取られてしまうということもないので安心です。
逆にスクリプトがたくさん実行されて金額がかかるということは、それだけアプリの売上も上がっているってことですね。
その場合も自分でサーバーのスペックをあげたりスケーリングする必要もないので便利です。

ニフティクラウドスクリプトへ登録する

クイックスタートを参考にニフティクラウドスクリプトへの登録を完了させます。
以上で準備は完了です、いよいよニフティクラウドスクリプトを使ってガチャを実行されるプログラムを書いていきます。

Node.jsでガチャをプログラミングする

こちらが完成形のソースになります。

script.js
const URL = 'https://lunch.jp-east-2.os.cloud.nifty.com/lunch.json';

let http = require('https');
module.exports = (req, res) => {
  http.get(URL, (result) => {
    let body = '';
    result.setEncoding('utf8');

    result.on('data', (chunk) => {
      body += chunk;
    });

    result.on('end', (result) => {
      data = JSON.parse(body);
      let max = 0;
      data.foods.forEach(d => {
        max += d["rate"];
      });

      let rate = Math.floor(Math.random() * max);
      for (var d of data.foods) {
        max -= d["rate"];
        if (max <= rate) {
          res.send(JSON.stringify(d));
        }
      }
    });
  });
}

ポイント

  • まずは先ほど用意したガチャで出現するItemデータのjsonを読み込む
  • 今回はデータをニフティクラウドオブジェクトストレージに置いてるのでそこから取得
  • その後jsonデータに定義されている確率の累計を求め、乱数を生成してマッチするアイテムを返す

早速このプログラムをニフティクラウドスクリプトに登録し、ためにしコントロールパネルから実行してみます。
script.png

⭐️1のカツ丼が出ました。

swiftからニフティクラウドスクリプトを呼び出す

ニフティクラウドスクリプトを使ってガチャのプログラミングが完了したので、今度はクライアントサイドとなるiOSアプリを作っていきます。

ニフティクラウドスクリプトはAPIを公開しているので単純にswiftからこれをリクエストしてあげればOKです。

一見難しそうに見えるAPIの認証なのですが、実はこれAWSの認証とまったく同じ仕様になっているようです。
ですので、aws-sdk-iosを使って簡単にリクエストできます。

aws-sdk-iosを使ってAPIをリクエストする

認証部分はAWSCoreにあるので、CocoaPodsなどでこれをダウンロードしてきます。

Podfile
latform :ios, '8.0'

target 'todaysLunch' do
  pod 'AWSCore'
end

aws-sdk-iosはObjective-Cで書かれているので、swiftから呼べるようにBridgeHeaderファイルを作成しておきます。

BridgeHeader.h
#import "AWSCore.h"

準備が整ったので、実際にニフティクラウドスクリプトのAPIをリクエストしてみます。

Script.swift
class Script: NSObject, XMLParserDelegate {

    var endpoint: URL
    var version: String
    var credentialsProvider: AWSStaticCredentialsProvider

    var currentElementName = ""
    var responseData       = ""

    init(endpoint: String, version: String, accessKey: String, secretKey: String){
        self.endpoint = URL(string: String(format: "%@/%@",endpoint,version))!
        self.version = version
        self.credentialsProvider = AWSStaticCredentialsProvider(accessKey: accessKey,secretKey: secretKey)
    }

    func executeScript(scriptIdentifier: String, method: String, query: String = "null", body: String  = "null", header: String = "null", callback: @escaping (String, String?) -> Void) {
        let action   = "ExecuteScript"
        let httpBody = String(format: "ScriptIdentifier=%@&Method=%@&Query=%@&Header=%@&Body=%@",scriptIdentifier,method,query,header,body)
        let req = makeReqest(body: httpBody, action: action)
        let task = URLSession.shared.dataTask(with: req as URLRequest, completionHandler: {data, response, error in
            if error == nil {
                let parser = XMLParser(data: data! as Data)
                parser.delegate = self
                parser.parse()
                callback(self.responseData as String, nil)
            } else {
                callback("", error!.localizedDescription)
            }
        })
        task.resume()
    }

    func makeReqest(body: String, action: String) -> NSMutableURLRequest {
        let req: NSMutableURLRequest = NSMutableURLRequest(url: self.endpoint)
        let date = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = AWSDateISO8601DateFormat2

        req.httpMethod = "POST"
        req.httpBody = body.data(using: String.Encoding.utf8)
        req.setValue(formatter.string(from: date), forHTTPHeaderField:"X-Amz-Date")
        req.addValue(String(format: "%@.%@",self.version,action), forHTTPHeaderField: "X-Amz-Target")
        req.addValue("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", forHTTPHeaderField: "Accept")
        req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

        let signer: AWSSignatureV4Signer = AWSSignatureV4Signer(
            credentialsProvider: self.credentialsProvider,
            endpoint: AWSEndpoint.init(region: AWSRegionType.usEast1, service: AWSServiceType.lambda, url: self.endpoint))
        signer.interceptRequest(req)
        return req
    }

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        currentElementName = elementName
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        if currentElementName == "ResponseData" {
            responseData = string
        }
    }
}

以下のように呼び出します。

let script = Script(endpoint:  "https://script.api.cloud.nifty.com",
                    version:   "2015-09-01",
                    accessKey: MY_ACCESS_KEY,
                    secretKey: MY_SECRET_KEY)

script.executeScript(scriptIdentifier: "hoge.js", method: "GET") { (response, error) -> Void in

  if error != nil {
      print(error)
  }

  let json = try! JSONSerialization.jsonObject(with: response.data(using: String.Encoding.utf8)!) as! [String: AnyObject]
  print(json)
}

ポイント

  • ACCESS_KEYとSECRET_KEYはニフティクラウドのコントロールパネルから取得した自分のキーを使う
  • aws-sdk-iosのAWSSignatureV4Signerクラスを使ってリクエストを実行するだけ
  • APIのレスポンスとしてXMLが返されるので「ResponseData」を取得すれば完了

まとめ

swiftからスクリプトを呼び出すことができれば、その結果を画面に表示させてあげるだけでアプリは完成です!

Simulator Screen Shot 2016.11.22 0.45.17.png

ソースの全体はgithubにもあげてありますのでこちらも参照ください。

いかがでしたでしょうか?
普通に作ったら結構大変なiOSとサーバーサイドの連携アプリがこんな簡単に作れてしまいました。
やっぱり自分でサーバーを用意しなくていいのは楽でいいですね。

ニフティクラウドスクリプトはとても便利なサービスだと思うので、皆さんもぜひ使ってみてください。

以上、「NIFTY Advent Calendar 2016」の1日目の記事として、ニフティクラウドスクリプトを実際のアプリで使ってみた話を記載してみました。

fuku2014
お気に入り swift 使える javascript php 書いたことはある pascal vb objective-c golang 興味ある python 苦手 java
fjct
クラウド・IoT 関連サービスを開発・提供している企業です。(こちらは、富士通クラウドテクノロジーズの有志にて運営しております。)
https://fjct.fujitsu.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away