読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

ゲーム仕様について責任を持つ

開発

こんにちは、開発プランナーの岩柿です。

クラッシュフィーバーの仕様企画/作成の業務を行っています。

 

「ゲーム仕様について責任を持つ」というタイトルは、クラッシュフィーバーの

プロジェクト体制図の「開発プランナー」の概要に書かれている文言です。

この一行の文を初めて見た時は、

『サラッと書いてあるけど、とても重要な事が書かれている・・・』と、改めて責任感を感じました。

 

ワンダープラネットに入社する前までは、

ゲームのUIや仕様などについて、さほど深く考えるという事はなかったのですが、

何故こういうUI配置なのだろう?何故わざわざこんな制限を付けているのだろう?

など、ゲームの設計や仕組みについて、仕様の目的や意図を考える癖が付きました。

 

・仕様を考える

仕様を考える際に、留意する点は様々ありますが、特に自分が留意している点を紹介します。

 

1.仕様の目的を汲み取り、最短で目的を達成する

  仕様を切るにあたって、まず目的があり、目的の達成手段として仕様が存在します。

  最短で目的を達成する為には、目的がブレないように、

  仕様を切る事が重要になります。(常に目的と照らしながら仕様作成します) 

f:id:iwagaki_001:20160817170454j:plain

2.各チームとの連携

  仕様は、自分の考えや一存だけでは作れません。

  プロデューサーとの擦り合わせは元より

  ・実現可能かどうか

  ・工数はどれぐらい掛かりそうか

  ・他の仕様に影響はないか

  など、各チームとコンセンサスを取りつつ進行します。

 

  これをしっかりやっておかないと、

  実装時に想定外な問題/課題が浮上したり、考慮が足りていない個所が出て来たり、

  いわゆる「仕様変更」が発生してしまう場合があります。

  こういうケースを減らす為に、最初に仕様を切る時は、

  各所としっかり擦り合わせを行いつつ進行する必要があります。

f:id:iwagaki_001:20160817172112j:plain

 

・フラッシュアイデア

様々な作成予定の仕様リストの中に「フラッシュアイデア」というものがあります。

これは、工数や影響範囲などはあまり考慮せずに、直観や感覚で感じた

『こういう機能があると嬉しい』『こうするともっとよくなるのでは?』

といったアイデアのリストになり、Trelloで管理しています。 

 

「あると便利そう」から「是非実現したい」まで、様々な幅広いアイデアが沢山ある為、

優先順位を決め、順番に対応を行っています。

 

また、クラッシュフィーバーのスタッフからは、

KPIに関わりそうなものや、ユーザビリティ向上に影響を及ぼしそうなものなど、 

コアゲーマーならではの鋭いフラッシュアイデアを貰う事も少なくありません。

f:id:iwagaki_001:20160817175625j:plain

 

・まとめ

 ・常に目的に照らしながら、仕様を作成する。

 ・各チームとのコンセンサスを取る。

 

Trelloでデザイナータスク管理

効率化

アートディレクターの榊原です。

デザイナーっぽい内容を書きたいなと思いつつ・・・敢えてデザイナーサイドのタスク管理ツールについて取り挙げたいと思います。

今までのタスク管理ツール

私のチームのデザインタスクは主にUI,エフェクト,バナー,イラスト指示書,イラスト等がありますが、そのタスク管理は今までGoogle スプレッドシートを使っていました。

求める要件を満たすようにスプレッドシートをカスタマイズし、タスク管理ツール化したイメージです。

求める要件とは下記のような点です。

  • 進捗を管理できる
  • タスクのスケジュールを把握できる
    (開始日、終了日、納期をそれぞれ設定できガントチャートで見れる)
  • メンバーの負荷を把握できる

f:id:wp_sakakibara:20160816082546p:plain

要件は満たせていますがいまいち使いづらいです。

問題点としては主に下記のような点でしょうか。

  • 編集しづらい
  • 直近のタスクを把握しづらい

Trelloの導入

チケットベースのタスク管理ツールにシフトしたいと考えていたところ、一人のデザイナーの方からTrelloを使いたいと提案がありました。

Trelloは簡単に言うとアジャイル開発で使われるようなカンバン方式のタスク管理ツールです。(https://trello.com/)

求める要件を満たさない部分もありますが、バナーやイラスト指示書のタスク管理なら向いていそう・・・ということで導入してみました。

※Trelloの使い方は様々なサイトで既に紹介されているので割愛します

 

f:id:wp_sakakibara:20160816082627p:plain

Trelloの良い点

  • タスクの状況が一目でわかる

  • 編集が簡単

この2点がバナーやイラスト指示書のタスク管理には非常に有効でした。最もほしい情報がTrelloを開くだけで手に入り、頻繁に変わるタスクのステータスをドラッグ&ドロップで手軽に変えられるのがとても便利。

Trello導入後の効果

  • タスク管理工数が減少
    動作はサクサクですしタスク登録も手軽です。タスク管理がかなり楽になりました。

  • タスクの作業漏れが減少
    Trelloのインターフェースはタスク達の全体像を視覚的に分かり易く把握できます。これによって、このタスクこの日までだった!等の漏れがなくなりました。
  • 作業者がタスク管理をしてくれる
    編集が直感的なので作業者が積極的にTrelloを更新してくれます。

個人的には3つ目が一番良かったかなと感じます。「タスク管理ツール」はデザイナーにとってけっこう敷居が高い場合が多いんじゃないでしょうか?エンジニアさんが主にバリバリ使っているイメージです(※個人的なイメージです)。タスク管理をデザイナーメンバーそれぞれが行えるというのは、思いの外メリットが大きいと思います。

Trelloの問題点

Trelloでのタスク管理には下記のような問題点も感じました。

  • メンバーの負荷を把握しづらい※拡張機能で見積り工数をつけられるようにはできるようです。
    メンバーの負荷は把握できないので、現状は負荷管理をTrelloのタスク管理とは別に行っています。キャパシティ内に収まるような納期でTrelloにタスクを追加していき、納期内に終わるのであれば進め方は作業者におまかせするという形です。

  • 大量/長期のタスクの管理には不向き
    キャラクターイラストのように作業期間が1ヶ月以上で、同時に100件以上も登録されいる状態になるようなタスクの管理にはTrelloは不向きかなと感じます。タスクのリストが全然画面内に収まらなくなるので、「タスクの状況が一目でわかる」というTrelloの良い点が潰されてしまいます。

※Trelloの使用期間はまだ浅く、今の所で自分が感じている課題なのでまだまだ改善の余地はあると考えています

まとめ

1週間や2週間と短いスパンで同じようなタスクが繰り返し発生する運営フェーズのデザインタスク管理では、Trelloは非常に活躍してくれると感じました。同じようにタスク管理で課題を感じている方は試してみてはいかがでしょうか。

ただTrelloで管理するには向いていないタスクもありますので、適材適所に使い分けが必要だと感じます。ざっくり分けると運営フェーズはTrello、開発フェーズは他のチケットベースツールが向いている印象です。

 

 

Gatlingを使った負荷テストをsbtタスクとして実行する

Scala

サーバーエンジニアの原 @zetta1985 です。

弊社ではモバイルゲームのバックエンド Web APIの負荷テストに Gatling というScala製の負荷テストツールを使用しています。

公式ドキュメントの Quick Start ではzipファイルをダウンロード・インストールするように案内されていますが、 そのままだとコードの共有には向かないディレクトリレイアウトになっています。

Scalaプロジェクトでよく用いられている sbtGatling公式のsbt用プラグインを使用することで、標準的なScalaプロジェクトのレイアウトでGatlingを使用することができます。

今回は、Gatlingをsbtのタスクとして実行する方法をご紹介します。 なお、あらかじめsbt(バージョン0.13.9)をインストールしておく必要があります。*1

sbt設定

sbtの設定ファイルであるbuild.sbtを以下の内容で作成します。

enablePlugins(GatlingPlugin)

scalaVersion := "2.11.8"

scalacOptions := Seq(
  "-encoding", "UTF-8", "-target:jvm-1.8", "-deprecation",
  "-feature", "-unchecked", "-language:implicitConversions", "-language:postfixOps")

libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % "test"
libraryDependencies += "io.gatling"            % "gatling-test-framework"    % "2.2.2" % "test"

ここには、Gatling公式のsbtプラグインを有効にすること、このプロジェクトの依存関係にgatlingが含まれることなどが記載されています。 build.sbtの詳細にはここでは触れません。*2

sbtプラグイン設定

build.sbtにはsbtプラグインへの依存関係は定義されていないため、以下のproject/plugins.sbtファイルによって別途sbtプラグインへの依存関係を定義する必要があります。

addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.0")

シナリオの定義

Gatlingを使用した負荷テスト用のシナリオ(Scalaソースコード)を src/test/scala ディレクトリ配下に配置します。 ここではサンプルとして、以下の内容でsrc/test/scala/BasicSimulation.scala を作成します。

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._


class BasicSimulation extends Simulation {

  val httpConf = http
    .baseURL("http://127.0.0.1:8080")
    .headers(Map(
        HttpHeaderNames.ContentType    -> HttpHeaderValues.ApplicationJson,
        HttpHeaderNames.Accept         -> HttpHeaderValues.ApplicationJson,
        HttpHeaderNames.AcceptEncoding -> "gzip,deflate"
      ))

  val scn = scenario("Basic sinario")
    .exec(http("request_1")
      .get("/"))
    .pause(500 milliseconds)
    .exec(http("request_2")
      .get("/"))

  setUp(scn.inject(atOnceUsers(1)).protocols(httpConf))
}

上記は、http://127.0.0.1 に対して 一回リクエストを投げたあと 500ミリ秒待ってから、再度リクエストを投げる、というシナリオになっています。*3

詳細は割愛しますが、Web APIのテストということで、HTTPリクエストヘッダーのContentTypeやAcceptヘッダーにapplication/jsonを指定してみました。 実際に使用するAPIに対するシナリオは、認証や独自HTTPリクエストヘッダー、レスポンスボディのJSONのパースなどが含まれるため、もっと複雑です。

それぞれどのようにDSLを書くかについては、また次回以降に取り上げたいと思います。

負荷テストの実行

プロジェクトのレイアウトは以下のようになりました。

~/w/b/gatling_sample ❯❯❯ tree
.
├── build.sbt
├── project
│   └── plugins.sbt
└── src
    └── test
        └── scala
            └── BasicSimulation.scala

4 directories, 3 files

ここで sbt gatling:test コマンド *4 を実行すれば http://127.0.0.1:8080 に対して負荷テストが実行できます。

ローカルホストでHTTPサーバーが起動していないと無残にも失敗するので、HTTPサーバーを起動しておきましょう。 Pythonには簡単にHTTPサーバーを起動できるモジュールがデフォルトで含まれているので、それを用います。

~/w/b/gatling_sample ❯❯❯ python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...

HTTPサーバーが起動できたので、Gatlingの負荷テストを実行してみましょう。 初回は依存ライブラリのインストールやScalaソースコードのコンパイルなどが走るため、少し重いです。

~/w/b/gatling_sample ❯❯❯ sbt gatling:test

(略)

================================================================================
2016-08-09 18:23:27                                           1s elapsed
---- Basic sinario -------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done:1
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=2      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )
================================================================================

(略)

================================================================================
---- Global Information --------------------------------------------------------
> request count                                          2 (OK=2      KO=0     )
> min response time                                      4 (OK=4      KO=-     )
> max response time                                      9 (OK=9      KO=-     )
> mean response time                                     6 (OK=6      KO=-     )
> std deviation                                          2 (OK=2      KO=-     )
> response time 50th percentile                          6 (OK=6      KO=-     )
> response time 75th percentile                          7 (OK=7      KO=-     )
> response time 95th percentile                          8 (OK=8      KO=-     )
> response time 99th percentile                          8 (OK=8      KO=-     )
> mean requests/sec                                      2 (OK=2      KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             2 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: /Users/zetta1985/work/blog/gatling_sample/target/gatling/basicsimulation-1470734605372/index.html
[info] Simulation BasicSimulation successful.
[info] Simulation(s) execution ended.
[success] Total time: 4 s, completed 2016/08/09 18:23:27

負荷テストが起動し、全てのリクエストが成功しました。胸熱ですね、わかります。

まとめ

今回は、sbtを使用したGatlingの実行方法についてご紹介しました。

今回取り上げたトピックの他に、src/test/resources にGatlingやログの設定ファイルを配置できたり、他の3rdパーティのライブラリのインストールがsbtによって楽になったりと、sbtを使用することのメリットはまだまだ他にもあります。

実践的なDSLの記述方法などについてもこのブログで解説していきたいと思っているので、お楽しみにー

*1:http://www.scala-sbt.org/download.html

*2:build.sbtに詳細に触れるとそれだけで別の記事ができてしまいます。build.sbtは非常に柔軟なので、新規で作成する際は他のプロジェクトを参考にした方がyak率を抑えられます

*3: atOnceUsers(1) としているため、シナリオ自体が1回しか実行されません。負荷テストとしては不十分ですが、サンプルということで

*4:gatling-sbtプラグインがバージョン2.1.7のときは sbt test で実行できていましたが、2.2.0になって実行方法が変わったようです

軽量高速な「MessagePack」でデータをシリアライズしてみる

リニューアル後、3エントリー目、担当の長屋です。 日々、業務中にいいなと思ったことを書いてみます。

今回は汎用的で且つ軽量高速なバイナリベースのシリアライズ形式である「MessagePack」について書いてみようと思います。JSON-likeに汎用的に扱え、バイナリ形式で且つ余分なデータを切り詰めているためシリアライズ後のサイズはJSONよりもかなり小さくなります。

また、C++の場合は自作のデーダオブジェクトや列挙型などのそのままシリアライズ・デシリアライズできる楽ちん仕様になってます。

MessagePack
MessagePack: It's like JSON. but fast and small.

実際にMessagePack形式でシリアライズしたデータのサイズとJSONの場合のサイズを比べてみたいと思います。

#include <iostream>
#include <msgpack.hpp>

using namespace std;

void test01()
{
    //シリアライズ用テストデータ
    map<string,int> tests = {{"AAAA",100},{"BBBB",121},{"CCCC",50},{"DDDD",25}};
    
    //テストデータと同じ構造のJSON文字列
    const char * jsonStr = "{\"AAAA\":100,\"BBBB\":121,\"CCCC\":50,\"DDDD\":25}";
    
    //シリアライズ用のバッファ
    msgpack::sbuffer buff;
    
    //シリアライズ
    msgpack::pack(&buff, tests);
    
    //シリアライズされたデータ。
    //調整されたバイナリデータになっておりかなり軽量
    char * data = buff.data();
    
    //msgpackとjsonでサイズを比べてみる
    cout << "MsgPack size" << strlen(data) << endl;
    cout << "json size" << strlen(jsonStr) << endl;
    
    //シリアライズされたデータをデシリアライズしてみる
    msgpack::object_handle oh = msgpack::unpack(data, buff.size());
    msgpack::object obj = oh.get();
    
    map<string,int> deserializeDatas;
    obj.convert(deserializeDatas);
    
    //デシリアライズしたデータの出力
    for (auto data : deserializeDatas)
        cout << data.first << ":" << data.second << endl;
}

出力結果

MsgPack size25
json size43
AAAA:100
BBBB:121
CCCC:50
DDDD:25

長さベースのサイズ比較ですが、JSONに比べて約60%近くにまでサイズがカットされています。

次は自作のデータオブジェクトをそのままシリアライズ・デシリアライズしてみます。 C++の場合自作のデータオブジェクトや列挙型でも提供されているマクロを用いることで簡単にシリアライズとデシリアライズができるようになっています。

#include <iostream>
#include <msgpack.hpp>

using namespace std;

//テスト列挙型
enum class TestType : int
{
    Unknown,
    A = 100,
    B = 200,
    C = 300
};

//シリアライズしたい列挙型を設定
MSGPACK_ADD_ENUM(TestType);

//テストオブジェクトその1
struct TestData
{
    string name;
    int id;
    float value;
    TestType type;
    
    //シリアライズしたい変数を設定
    MSGPACK_DEFINE(name,id,value,type);
};

//テストオブジェクトその2
struct TestDataGroup
{
    int groupId;
    vector<TestData> testDatas;
    
    //シリアライズしたい変数を設定
    MSGPACK_DEFINE(groupId,testDatas);
};

void test02()
{
    //テストデータグループAを作成
    TestDataGroup groupA;
    groupA.groupId = 10;
    groupA.testDatas.push_back({"AAAA",1,123.456f,TestType::A});
    groupA.testDatas.push_back({"BBBB",2,180.554f,TestType::B});
    groupA.testDatas.push_back({"CrashFever!",3,10.0f,TestType::C});
    
    //テストデータグループBを作成
    TestDataGroup groupB;
    groupB.groupId = 20;
    groupB.testDatas.push_back({"CCCC",1,100.0f,TestType::A});
    groupB.testDatas.push_back({"DDDD",2,110.0f,TestType::B});
    groupB.testDatas.push_back({"EEEE",3,120.0f,TestType::C});
    
    //シリアライズ用のオブジェクトを作成
    map<string,TestDataGroup> testDataGroups;
    testDataGroups["GroupA"] = groupA;
    testDataGroups["GroupB"] = groupB;
    
    //シリアライズ
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, testDataGroups);
    
    //デシリアライズ
    msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size());
    msgpack::object data = oh.get();
    map<string,TestDataGroup> deserializeDatas;
    data.convert(deserializeDatas);
    
    //デシリアライズした物を出力してみる
    for (const auto & group : deserializeDatas)
    {
        cout << "key = " << group.first << " GroupID = "<< group.second.groupId << endl;
        for (const auto & data : group.second.testDatas)
        {
            cout << data.name << " " << data.id << " " << data.value << " " << (int)data.type << endl;
        }
        cout << endl;
    }
}

出力結果

key = GroupA GroupID = 10
AAAA 1 123.456 100
BBBB 2 180.554 200
CrashFever! 3 10 300

key = GroupB GroupID = 20
CCCC 1 100 100
DDDD 2 110 200
EEEE 3 120 300

このように自作のオブジェクトであっても簡単にシリアライズ・デシリアライズができます。また各オブジェクト用にシリアライザなどを作る必要がないのも大きな利点です。
ただ、シリアライズされたデータはバイナリ形式になるため人の目で見ることができないので使い分けが必要な部分は出てくるとおもいます。
弊社の「CrashFever」の場合はサーバーとのやり取りはJSONで行いマルチプレイ時の速度が必要な場面はMessagePackを使用しています。

【Unity】AESでデータを暗号化

Unity

リニューアル後、2エントリー目、担当の大橋です。
リニューアルに伴った特別なネタを持っているわけでもないので、いつものピンポイントなネタでお送りします。

Unityでデータを暗号化

Unityでなんらかのデータを保存するとき、PlayerPrefsを利用したり、 ファイルに書き込んだりすると思いますが、
ある程度の知識があるユーザーであれば、これらの保存したデータを 比較的簡単に見ることができてしまいます。

そのため、アプリを利用するユーザーには知られたくないデータを保存する場合、
そのデータを暗号化する必要があります。

ということで、今回はAESという暗号化方式でデータを暗号化する方法を取り上げようかと思います。

AESはアメリカ国立標準技術研究所(NIST)が新しい標準暗号として暗号方式を公募し、
Rijndaelという暗号方式がAESとして採用されました。
そういうこともあり、UnityでAESを実装するときは「RijndaelManaged」というクラスを使います。

暗号化のコード

using System.Security.Cryptography;
using System.Text;
RijndaelManaged rijndael = new RijndaelManaged ();
rijndael.KeySize = 128;
rijndael.BlockSize = 128;

// パスワードから共有キーと初期化ベクターを作成
string pw = “passward”;
string salt = “salt”;

byte[] bSalt = Encoding.UTF8.GetBytes (salt);
Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes (pw, bSalt);
deriveBytes.IterationCount = 1000;        // 反復回数

rijndael.Key = deriveBytes.GetBytes (rijndael.KeySize / 8);
rijndael.IV = deriveBytes.GetBytes (rijndael.BlockSize / 8);

// 暗号化
ICryptoTransform encryptor = rijndael.CreateEncryptor ();
byte[] encrypted = encryptor.TransformFinalBlock (src, 0, src.Length);

encryptor.Dispose ();

暗号化コードを順に説明していきます

RijndaelManaged rijndael = new RijndaelManaged ();
rijndael.KeySize = 128;
rijndael.BlockSize = 128;

AESでの暗号化は、RijndaelManagedというクラスを使います。
ですので、何はともあれRijndaelManagedのインスタンスを作成します。

そして、暗号化の鍵長とブロック長を設定します。
AESはブロック長が128ビット固定で、鍵長が128ビット、192ビット、256ビットの中から選べますが、
今回はどちらも128ビットにしておきます。

string pw = “passward”;
string salt = “salt”;

パスワードとサルトの文字列です。
この二つの文字列を元に、暗号化のキーと初期化ベクトル(IV)を作ります。

初期化ベクトルとは
AESはブロック暗号方式で、ブロック暗号方式では、暗号化対象をブロックごとに区切って暗号化します。
このとき(CBCモードのとき)、一つ前のブロックの暗号化結果を次のブロックの暗号化に利用します。
ただし、一番最初のブロックを暗号化するときは、一つ前のブロックがありません。
そこで、一つ前のブロックの暗号化結果の代わりとして使われるのが初期化ベクトルです。

byte[] bSalt = Encoding.UTF8.GetBytes (salt);
Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes (pw, bSalt);
deriveBytes.IterationCount = 1000;        // 反復回数

rijndael.Key = deriveBytes.GetBytes (rijndael.KeySize / 8);
rijndael.IV = deriveBytes.GetBytes (rijndael.BlockSize / 8);

パスワードとサルトから暗号化キーと初期化ベクトルを作成し、
それらをRijndaelManagedに指定します。

ICryptoTransform encryptor = rijndael.CreateEncryptor ();
byte[] encrypted = encryptor.TransformFinalBlock (src, 0, src.Length);

暗号化対象のバイト配列(byte[] src)を暗号化します。
暗号化されたバイト配列がencryptedに入ります。
文字や数値を暗号化する場合は、あらかじめバイト配列に変換しておく必要があります。

encryptor.Dispose ();

最後にRijndaelManagedをDisposeして、暗号化完了です。

復号化

暗号化したデータを利用するときは復号化しなければいけません。
復号化は次のようなコードになります。

RijndaelManaged rijndael = new RijndaelManaged ();
rijndael.KeySize = 128;
rijndael.BlockSize = 128;

// パスワードから共有キーと初期化ベクターを作成
string pw = “passward”;
string salt = “salt”;

byte[] bSalt = Encoding.UTF8.GetBytes (salt);
Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes (pw, bSalt);
deriveBytes.IterationCount = 1000;        // 反復回数

rijndael.Key = deriveBytes.GetBytes (rijndael.KeySize / 8);
rijndael.IV = deriveBytes.GetBytes (rijndael.BlockSize / 8);

// 復号化
ICryptoTransform decryptor = rijndael.CreateDecryptor ();
byte[] plain = decryptor.TransformFinalBlock (src, 0, src.Length);

decryptor.Dispose ();

ほとんど暗号化時と同じようなコードになります。
というより、暗号化したときと同じ鍵長、ブロック長、パスワード、サルトを指定しないと ちゃんと復号化されません。

ICryptoTransform decryptor = rijndael.CreateDecryptor ();
byte[] plain = decryptor.TransformFinalBlock (src, 0, src.Length);

暗号化と違うのはこの部分だけです。
暗号化ではCreateEncryptor ()でしたが、復号化ではCreateDecryptor ()になります。
srcは暗号化したバイト列(byte[])です。
復号化されたバイト列がplainに入ります。
文字列や数値を暗号化している場合は、復号化されたバイト列から文字列や数値に戻す必要があります。

以上が、AESでの暗号化、復号化になります。
難しいことはRijndaelManagedなどがやってくれるので、意外と簡単!

デベロッパーブログとしてリニューアル

リニューアル後、1エントリー目を担当する村田です。

今回は、ブログのリニューアルにまつわる内容を書きたいと思います。

デベロッパーブログとして生まれ変わった背景

元々は「エンジニアブログ」として、

  • エンジニアが開発中に困り調査 / 解決したこと
  • 新しい技術の調査内容

を主軸に技術情報を発信してきました。
Cocos2d-xを採用したプロダクトが多かったこともあり、Cocos2d-x関連の記事が充実しております。

ブログを始めた当初はエンジニア率が50%を超えるメンバー構成でしたが、採用サイトの「データで見るWP」のとおり、おかげさまでエンジニア以外のメンバーも多くジョインし、ワンダープラネット内の職種の幅が広がりました。

エンジニア以外の取り組みや改善内容も発信すれば、同じ悩みを持つ方の解決の糸口になれば幸いですし、ワンダープラネットにおけるモバイルゲーム開発を知っていただく良い機会かと思いました。

そのような思いで『デベロッパーブログ』としてリニューアルに至りました。

今まではエンジニアからの技術情報のみの発信でしたが、これからはデザイナーやプランナーだけでなく、QAやCSも交えてゲーム開発に関する情報を発信していきます!モバイルゲーム開発に携わっている方、モバイルゲーム開発に興味を持っている方、ぜひこれからもチェックしてください!!

はてなブログProへ移行した背景

以前は企業サイトと同じサーバー上で運用をしておりました。
しかし、サイト運用は決してゼロではなく、割り込みで運用作業が入るということもしばしば。

その煩わしい運用作業を無くすべく、はてなブログProへの移行となりました。

ワンダープラネットでは各自が開発により集中できるように、便利なサービスを積極的に活用します(もちろん費用対効果も見ます)。
あと、流行のサービスを使う時はワクワクしますよね!!

C#のLINQ to XMLでHTMLを読む

C-Sharp

今回のエンジニアブログを担当する加賀です。

個人的にXML、HTMLを扱う機会があったので、LINQ to XMLを使用してみました。

LINQ to XMLは.NET Framework 3.5以降で使用できます。
System.Xml.Linq名前空間の各クラスで構成されています。
XML文書へのアクセスを簡単に行う事が出来ます。

しかし、HTMLはそのままではLINQ to XMLでは扱えない(エスケープ文字でXMLにないものを使っているなど)ため、一度変換する必要があります。
今回はSGMLReaderというライブラリを使ってHTMLをXMLに変換することで、LINQ to XMLで使用できる形にします。

HTML to XML

Sgml.SgmlReaderは、XmlReaderを継承しているので、最初に読み込む部分を作成してしまえば、後はLINQ to XMLでそのまま使う事が出来ます。

XDocument ConvertToXML(Stream stream) {  
  StreamReader sr = new StreamReader (stream);  
  XDocument xml = null;  
  // SGMLReaderの作成  
  using(SgmlReader sgml = new SgmlReader ()) {  
    // DTDを取得しにいかないように設定  
    sgml.IgnoreDtd = true;  
    // ドキュメントタイプをHTMLに設定  
  sgml.DocType = "HTML";  
    // 読み込むストリームの設定  
  sgml.InputStream = sr;  
    // XMLに変換  
    xml = XDocument.Load (sgml);  
  }  
  sr.Close ();  
  
  return xml;  
}   

たったこれだけで、HTMLからXMLに変換できます。

LINQ to XMLでデータを検索

LINQ to XMLでのデータ検索は、上手に絞り込みをするとかなり簡潔に書けます。
HTMLの場合はソースを見ることがしやすく、ブラウザによっては指定のタグがどの範囲に描画されているかを見ながら探す事が出来るので、それらを使うことで絞り込みのヒントが得やすくなります。
また、サイトによっては特に重要な情報を含むタグやその親に、一意の名前を付けていることが多いので、簡単に目当ての情報を取得できます。

検索に使用できるメソッドはXElementを参照してください。

今回は例として、Twitter(PC版)の@CrashFever_PR(クラッシュフィーバー公式)のスクリーン名(表示ユーザー名)と自己紹介文の取得をしてみます。
今回取得するURLは、https://twitter.com/CrashFever_PRです。
例なのでエラー処理はありません。(あるとしても今回はnullチェックくらいです)

XDocument xml = ConvertToXML (事前に取得したデータのStream);  
// スクリーン名の取得  
// 場所:ProfileHeaderCard-nameクラスのh1タグ 内のaタグの内容  
// Descendants(子方向探索)で h1 タグのみに絞り込む  
// Whereで ProfileHeaderCard-name というクラス指定を持つ h1 タグに絞り込む  
// Selectで h1 タグ内の最初の a タグを取得(今回は a タグは1つしか存在しない)  
// FirstOrDefaultで1つだけ取得  
// 取得したXElementのValueがスクリーン名  
XElement screenNameElement = xml  
    .Descendants ("h1")  
    .Where (h1 =>  
        h1.Attribute ("class") != null &&  
        h1.Attribute ("class").Value == "ProfileHeaderCard-name")  
    .Select (h1 => h1.Element ("a"))  
    .FirstOrDefault ();  
string screenName = screenNameElement.Value;  
  
// 自己紹介文の取得  
// 場所:ProfileHeaderCard-bioクラスのpタグの内容  
// Descendants(子方向探索)で p タグのみに絞り込む  
// Whereで ProfileHeaderCard-bio というクラス指定を持つ p タグに絞り込む  
// FirstOrDefaultで1つだけ取得  
// 取得したXElementのValueが自己紹介文  
XElement textElement = xml  
    .Descendants ("p")  
    .Where (p =>  
        p.Attribute ("class") != null &&  
        p.Attribute ("class").Value.Contains ("ProfileHeaderCard-bio")) // クラス指定が複数されている場合はContainsで判定  
    .FirstOrDefault ();  
string text = textElement.Value;  

いちいちルートから辿って行ったり、全文検索で探したりしなくても、簡単に検索ができます。
応用すればかなり複雑な絞り込みもできます。

終わり

今回、はじめてLINQ to XMLを使用しましたが、非常に簡単にデータを取得することが出来ました。
XMLベースのAPIの値を取得するのもいいですし、今回のようにHTMLページからデータを取得することも簡単です。
RSSのないページの更新検知ソフトとか簡単に作れそうです。