May 2012

Volume 27 Number 05

Windows Phone - Android アプリを Windows Phone アプリに変換する

Stan Chatman | May 2012

私にとって非常にしゃくに障ることの 1 つは、クライアントが使用している iPhone アプリや Android アプリを Windows Phone プラットフォームに移植するよう依頼を受け、移植後のアプリの外観を iPhone アプリや Android アプリと同じにするように指示されることです。UI を同じに保つということは、ユーザー エクスペリエンスを向上できる Windows Phone 固有のネイティブ機能の多くを使うチャンスを失うことを意味します。Windows Phone の OS 更新プログラム (コードネーム "Mango") がリリースされたら、Android 用に作成したアプリのいくつかを Windows Phone アプリに変換することが、私の大きな目標の 1 つでした。ここではあえて "移植" ではなく "変換" という言葉を使っています。この方が、既存のスマートフォン アプリを Windows Phone プラットフォームに移行するプロセスをより正確に示しているからです。今回は、Android 用の Hollywood Empire アプリから Windows Phone アプリへの変換について説明します。

Android から Windows Phone への変換の詳細に入る前に、元のアプリ製作の背景を少し紹介して、変換のすべての手順を理解しやすくしたいと思います。

Hollywood Empire の概要

Hollywood Empire は、ドラッグ アンド ドロップを使ったカジュアル ゲームで、そのしくみは人気ゲームの Alchemy に似ています。プレーヤーはまず 4 人の俳優を手札とし、その中から任意の俳優を別の俳優にドラッグします。2 人の俳優が映画で共演していたら、プレーヤーはその映画の興行収入を獲得し、別の俳優 1 人が手札に加わります。プレーヤーは、自身のスコアをグローバルに管理されているハイスコア表に投稿したり、Facebook や Twitter を使って成果を他人と共有することもできます。また、ゲームに採用できそうな、映画競演の独自の組み合わせを提案できます。プレーヤーが打つ手に困ると、ゲームはヒントを表示します。ゲームの遊び方は簡単ですが、ゲーム ボード上の俳優の数が増えてくると、難しくなります。図 1 は、Windows Phone バージョンの Hollywood Empire の 2 枚のスクリーンショットです。

Windows Phone バージョンの Hollywood Empire のサンプル スクリーンショット
図 1 Windows Phone バージョンの Hollywood Empire のサンプル スクリーンショット

映画のデータ ソース

Hollywood Empire のオリジナルのデザインで主な懸案事項の 1 つだったのが、ゲームの進行に必要なデータの入手先でした。最初は、IMDb (インターネット ムービー データベース、http://www.imdb.com/、英語) からデータを収集できると考えました。しかし、すぐに IMDb には、データを入手するためにゲーム エンジンに必要な API がないことがわかりました。独自に開発された IMDb スクリーン スクレーパー SDK は多数提供されていますが、今回のニーズに適う信頼性を備えているものはありませんでした。次に、TMDb (The Movie Database、http://www.themoviedb.org/、英語) という同じような映画のデータベースを見つけました。TMDb は、オープンソースで、IMDb に匹敵する情報を提供しています。また、IMDb にリンクし、ユーザーが俳優と映画をキーに使用して、情報を入手できるようにもなっています。さらに TMDb には、今回必要としていた俳優と映画の情報を入手できる、非常によくできた API (http://api.themoviedb.org/2.1/、英語) もありました。TMDb 用の非常によくできたオープン ソースの .NET ラッパー API (http://themoviedbapi.codeplex.com/、英語) も見つかりました。これで、今回独自にヘルパー クラスやラッパー クラスを作成する必要がなくなり、かなりの時間を節約できました。

データ ソースを決定したら、データをリアルタイムで提供するか、ローカルのデータ ストアから提供するかを決める必要があります。リアルタイムでのデータ抽出については、予備テストをいくつか実施した結果、帯域幅の調整が生じる可能性があるため、最適なユーザー エクスペリエンスを提供できないと判断しました。また、API が必要な種類のデータを提供していないため、追加の Web サービスを作成しなければならず、さらに複雑になります。結局、データをデバイスにローカルに保存することにしました。

Hollywood Empire のテーブル デザインは、表 1 の 3 種類のテーブルから構成されます。

表 1 Hollywood Empire のテーブル

テーブル名 説明
hollywoodempire_actor 各俳優についての関連情報をすべて保持
hollywoodempire_moviecombos 2 人の俳優と、2 人が競演した映画の有効な組み合わせをすべて保持
hollywoodempire_movie 各映画についての関連情報をすべて保持

これらのテーブルは、次の 3 手順のプロセスで構築しました。

手順 1: Actor (俳優) テーブルを手動で作成する

hollywoodempire_actor テーブルには、認知度、人気、映画業界との関わりを基に選んだ約150 人の俳優の情報を保持しています。俳優の名前を themoviedb API に渡して必要な情報を収集し、それを基に Actor テーブルを派生させました。図 2 は、このファイルを Microsoft Excel 2010 で開いたようすです。id 列は、単純に、俳優を一意に識別できる連番のキーです。name 列は、TMDb データベースに登録されている俳優名です。image 列は、各俳優の jpg 形式のサムネイルのファイル名です。url 列は、追加情報が必要になった場合に備えて、IMDb システムへのリンクを保持しています。type 列は、その人物が俳優か監督かを示しています。

Excel で開いた Actor テーブル
図 2 Excel で開いた Actor テーブル

手順 2: Movie Combos (映画競演) テーブルを作成する

図 3 のコードからわかるように、Movie Combos (映画競演) テーブルの作成は、非常にシンプルなプロセスです。図 4 の GetActorsFromCSV メソッドは、手順 1 で作成したファイルを読み取るヘルパー メソッドで、TmdbPerson (前述の TMDb 用の .NET ラッパー API で定義) のディクショナリを構築します。図 5 に、TmdbPerson クラスのレイアウトを定義しています。このクラスは、themoviedbapi API から Person.getInfo (http://api.themoviedb.org/2.1/methods/Person.getInfo、英語) を呼び出して、返されるオブジェクトを定義します。このサービスから返されるキー データは、filmography フィールドで定義しています。これにより、特定の俳優が出演している映画がすべて列挙され、この情報を使用して 2 人の俳優が競演している映画を特定します。

図 6 の MoviesAppearedTogether ヘルパー メソッドは、2 つの TmdbPerson オブジェクトを受け取って、2 人の俳優が競演しているすべての映画の一覧を返します。このヘルパー アプリを実行すると、hollywoodempire_moviecombos CSV ファイルを 1 分未満で作成できます。現在、hollywoodempire_moviecombos CSV ファイルには、約 6000 件のレコードが含まれています。

図 3 Movie Combos (映画競演) テーブルを構築するメソッド

private void Create_MovieCombo_Table(object sender, RoutedEventArgs e)
{
    List<string> movieListAll = new List<string>();
    StringBuilder sb = new StringBuilder();
    string mydocpath =  
      Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
    actList = api.GetActorsFromCSV();
    foreach (String act1 in actList.Keys)
    {
        foreach (String act2 in actList.Keys)
        {
            if (act1 != act2)
            {
                TmdbPerson p1 = actList[act1];
                TmdbPerson p2 = actList[act2];
                List<string> movieList = 
                  api.MoviesAppearedTogether(p1, p2);
                foreach (string mvt in movieList)
                {
                    sb.Append('"' + mvt + '"' + "," + 
                      '"' + act1 + '"' + "," + '"' + act2 + '"');
                    sb.AppendLine();
                }
            }
        }
    }
    using (StreamWriter outfile =
    new StreamWriter(mydocpath + @"\movieListAll_vs2008_export.csv"))
    {
        outfile.Write(sb.ToString());
    }
}

図 4 俳優の csv ファイルから TmdbPerson ディクショナリを構築するメソッド

public Dictionary<String, TmdbPerson> GetActorsFromCSV()
{
    Dictionary<String, TmdbPerson> actHash = 
      new Dictionary<String, TmdbPerson>();
    using (CsvReader csv =
           new CsvReader(new 
             StreamReader("hollywoodempire_actor.csv"), true))
    {
        List<string> actList = new List<string>();
        int fieldCount = csv.FieldCount;
        string[] headers = csv.GetFieldHeaders();
        while (csv.ReadNextRecord())
        {
            for (int i = 0; i < fieldCount; i++)
                Console.Write(string.Format("{0}",
                               csv[i]));
            TmdbPerson[] actor = PersonSearch(csv[1]).ToArray();
            TmdbPerson actorCorrect = new TmdbPerson();
            foreach (TmdbPerson tp in actor)
            {
                TmdbPerson temp = GetPersonInfo(tp.Id);
                if (temp.KnownMovies > actorCorrect.KnownMovies)
                {
                    actorCorrect = temp;
                }
            }
            actHash.Add(csv[1], actorCorrect);
            Console.WriteLine();
        }
        return actHash;
    }
}

図 5 TmdbPerson クラスの定義

[DataContract]
public class TmdbPerson
{
    [DataMember(Name="score")]
    public string Score { get; set; }
    [DataMember(Name="popularity")]
    public string Popularity { get; set; }
    [DataMember(Name="name")]
    public string Name { get; set; }
    [DataMember(Name="url")]
    public string Url { get; set; }
    [DataMember(Name="id")]
    public int Id { get; set; }
    [DataMember(Name="biography")]
    public string Biography { get; set; }
    [DataMember(Name="known_movies")]
    public int KnownMovies { get; set; }
    [DataMember(Name="birthday")]
    public string BirthdayString { get; set; }
    public DateTime? Birthday
    {
        get
        {
            DateTime d;
            if (string.IsNullOrEmpty(BirthdayString) || 
              !DateTime.TryParse(BirthdayString, out d))
                return null;
            else
                return d;
        }
    }
    [DataMember(Name="birthplace")]
    public string Birthplace { get; set; }
    [DataMember(Name = "profile")]
    public List<TmdbImage> Images { get; set; }
    [DataMember(Name = "filmography")]
    public List<TmdbPersonFilm> Filmography { get; set; }
}

図 6 2 人の俳優が共演している映画を返すメソッド

public List<string> MoviesAppearedTogether(TmdbPerson act1, TmdbPerson act2)
{
    List<string> moviesTogetherList = new List<string>();
    foreach (TmdbPersonFilm film1 in act1.Filmography)
    {
        foreach (TmdbPersonFilm film2 in act2.Filmography)
        {
            if (film1.Name.Trim() == film2.Name.Trim())
            {
                moviesTogetherList.Add(film1.Name);
            }
        }
    }
    return moviesTogetherList;
}

手順 3: Movie (映画) テーブルを派生させる

hollywoodempire_moviecombos CSV ファイルを作成したら、これを使用して、Movie (映画) テーブルに必要な、一意の映画をすべて特定します。表 2 に、hollywoodempire_movie CSV ファイルに必要な情報を示します。

表 2 Movie (映画) ファイルの説明

フィールド名 説明
MovieName 映画の名前
Image 映画のサムネイル ポスターの URL
Revenue 興行収入額
Trailer YouTube の映画予告編の URL
ReleaseDate 映画の公開日

一意の映画を列挙したテキスト ファイルを最も簡単に生成するには、Excel を使って映画名のフィールドに一意の値を抽出するフィルターを適用し、他のフィールドはすべて隠します。一意の映画のファイルとして、現在、約 950 本の映画の名前が取り出されます。一意の映画名のファイルを作成したら、themoviedb データベースから各映画の名前を検索して、movie CSV ファイルに保存できます。関連映画情報のほかに、該当する映画のポスターのサムネイルもダウンロードします。図 7 は、映画情報を取得し、ポスター画像をダウンロードでするコードです。このコードの最新版には、映画の画像が既にダウンロードされているかどうかを確認するチェックを実装しています。既にダウンロードしている場合は、メディアのダウンロードを省略して、処理時間を短縮します。既に処理した映画は、ローカル ファイルに格納されているため、新しい映画のみが処理されます。GetMoviesFromCSV メソッドでは、一意の映画の名前を保持したリストをメモリ内に作成します。次に、このリストを反復処理して、必要な映画情報を取得します。

図 7 映画情報と画像サムネイルを取得するメソッド

private void Create_Movie_File(object sender, RoutedEventArgs e)
{
    movList = api.GetMoviesFromCSV();
    …
    foreach (string mov in movList)
    {
        bool proc = false;
        foreach (string movproc in movProcessed)
        {
            if (movproc.Trim() == mov.Trim())
            {
                proc = true;
                break;
            }
        }
        if (!proc)
        {
            TmdbMovie[] movie = api.MovieSearch(mov).ToArray();
            TmdbMovie movie2;
            imagename = "notfound.jpg";
            revenue = "20000000";
            string picURL = null;
            trailer = "http://www.themoviedb.org/movie";
            if (movie.Length > 0)
            {
                if (movie[0].Posters.Count > 0)
                {
                    if (movie[0].Posters.Count > 0)
                    {
                        picURL = movie[0].Posters.FirstOrDefault().ImageInfo.Url;
                    }
                    else
                    {
                        picURL = null;
                    }                          
                    if (picURL != null && picURL.Trim() != "")
                    {
                        string[] picFileName = picURL.Split('/');
                        imagename = picFileName[picFileName.Length - 1].ToLower().Replace('-', '_');
                        movie2 = api.GetMovieInfo(movie[0].Id);
                        revenue = movie2.Revenue;
                        if (revenue == "0" || revenue == "") revenue = "20000000";
                        trailer = movie2.Trailer;
                        year = String.Format("{0:MM/dd/yyyy}", movie2.Released);
                        String checkFile = mypicpath + "\\Hollywood Empire\\tmdb_images" + 
                          @"\" + picFileName[picFileName.Length - 1].ToLower().Replace('-', '_');
                        if (!File.Exists(checkFile))
                        {
                            byte[] picdata = api.GetImageData(picURL);
                            writeByteArrayToFile(picdata, imagename);
                        }
                    }
                }
            }
          …
        }
    }
    using (StreamWriter outfile =
     new StreamWriter(mydocpath + @"\hollywoodempire_movie_tmdb_vs2008.csv"))
    {
        outfile.Write(sb.ToString());
    }
}

SQL Server Compact ローカル データベースを使用する

関連する CSV ファイルをすべて作成したので、今度は、この情報をモバイル デバイス上に保存して、使用する方法を決めます。オリジナルの Android バージョンでは、最初、SQLite を使ってデータを保存し、使用できると考えました。しかし、この方法を使うと、パフォーマンスに問題が発生したため、インメモリの Java ハッシュ テーブルを用意し、このテーブルを CSV ファイルから読み取るようにしました。この方法は、Java バージョンではかなりうまくいきましたが、ハッシュ テーブルを検索して次の競演映画を見つけなければならない場合に、毎回手動で順次検索を実行する必要があったため、パフォーマンスが低下しました。

Android バージョンで直面した問題の多くは、ゲームを Windows Phone に変換すると解決されます。LINQ によって、テーブルやコレクションの検索およびフィルター処理を実行する際の検索関連の問題の多くが解決されます。また、SQL Server Compact を利用することで、SQLite に比べて堅牢で成熟したデータベース フレームワークが手に入ります。SQL Server Compact データベースを Windows Phone バージョンの Hollywood Empire で使用するには、SQL Server Compact データベースを作成し、この新しく作成したデータベースに CSV ファイルをインポートして、Windows Phone でこのデータベースを使用するためのエンティティ オブジェクト クラスを作成する必要があります。

手順 1: SQL Server Compact データベースを作成する

SQL Server Compact データベースの作成には、SQL Server 2008 R2 を使用します。管理画面で [サーバーの種類] に [SQL Server Compact] を選択し、[データベース ファイル] に [<新しいデータベース>] を選択します。次に、データベースの作成画面で、新しいデータベース ファイルの作成先を選択します (図 8 参照)。

: SQL Server Compact の便利な機能の 1 つは、データベースをパスワードで保護するようにした場合、RSA 方式で完全に暗号化されるデータベースが無料で手に入ることです。これは、アプリのセキュリティが優先事項の 1 つである場合に、SQL Server Compact データベースを利用することで得られる追加のメリットです。

新しい SQL Server Compact データベースを作成する際の管理画面
図 8 新しい SQL Server Compact データベースを作成する際の管理画面

手順 2: CSV ファイルをインポートする

実際の SQL Server Compact データベースを作成したら、テーブルを作成し、該当する CSV ファイルをインポートする必要があります。SQL Server Management Studio に、CSV ファイルを SQL Server Compact テーブルにインポートできる機能はありませんが、exportsqlce.codeplex.com (英語) からダウンロードできる ExportSqlCE アドインを使うと、SQL Server Management Studio または Visual Studio からインポートできます。このツールは、CSV ファイルを読み取り、CSV ファイルとテーブルのスキーマを比較して問題が見つからなければ、INSERT スクリプトを作成します。このスクリプトを実行すると、値を該当するテーブルに挿入できます。図 9 は、actor CSV ファイルのインポート時に生成された INSERT スクリプトで、図 10 は、INSERT スクリプト実行後の、値が設定された actor (俳優) テーブルです。

図 9 ExportSqlCE ImportFromCSV 関数から作成されたサンプルの INSERT スクリプト

INSERT INTO [hollywoodempire_actor] ([id],[name],[image],[url],[type]) VALUES ('1',N'Leonardo DiCaprio',N'leonardodicaprio.jpg',N'http://www.imdb.com/name/nm0000138/',N'Actor');
GO
INSERT INTO [hollywoodempire_actor] ([id],[name],[image],[url],[type]) VALUES ('2',N'Jennifer Connelly',N'jenniferconnelly.jpg',N'http://www.imdb.com/name/nm0000124/',N'Actor');
GO
INSERT INTO [hollywoodempire_actor] ([id],[name],[image],[url],[type]) VALUES ('3',N'Johnny Depp',N'johnnydepp.jpg',N'http://www.imdb.com/name/nm0000136/',N'Actor');
GO
INSERT INTO [hollywoodempire_actor] ([id],[name],[image],[url],[type]) VALUES ('4',N'Marion Cotillard',N'marioncotillard.jpg',N'http://www.imdb.com/name/nm0182839/',N'Actor');
GO
INSERT INTO [hollywoodempire_actor] ([id],[name],[image],[url],[type]) VALUES ('5',N'Djimon Hounsou',N'djimonhounsou.jpg',N'http://www.imdb.com/name/nm0005023/',N'Actor');
GO
…

INSERT スクリプトが実行され、actor (俳優) テーブルに値が設定された後の SQL Server Management Studio の画面
図 10 INSERT スクリプトが実行され、actor (俳優) テーブルに値が設定された後の SQL Server Management Studio の画面

手順 3: Windows Phone でデータベースを使用するためにエンティティ オブジェクト クラスを作成する

Windows Phone アプリで SQL Server Compact データベースを使用する場合のマイクロソフトの公式手法は、"コード ファースト (Code First)" です。この方法では、開発者がデータベース コンテキストとリレーションシップをコード内に作成し、そのコードの定義を基にデータベースを生成することが大前提です。Hollywood Empire Windows Phone アプリでは、別の非公式の方法を使用しました。これには、以下の手順が含まれます。

  1. Visual Studio または SQL Server Management Studio のビジュアル デザイナーを使用して、SQL Server Compact Edition 3.5 データベースを開発 PC に作成します。
  2. Visual Studio コマンド プロンプトを起動します。
  3. SQLMetal ツールを実行して、LINQ to SQL コード ファイルを生成します。
    1. cd C:\TheMovieDB_New\SQLCE
    2. Sqlmetal /code:HollyWoodEmpireEntities.cs /context:HollyWoodEmpireDataContext  /pluralize HollyWoodEmpire.sdf
  4. 生成されたコードを Windows Phone プロジェクトに組み込みます (図 11 参照)。
  5. 出力されたエンティティ ファイルを次のように変更して、コンパイルされるようにします。
    1. "System.Data.Linq" への参照を追加します。
    2. System.Data.IDbConnection を参照する 2 つのコンストラクターを削除します。

このプロセスの詳細については、windowsphonegeek.com/articles/Using-SqlMetal-to-generate-Windows-Phone-Mango-Local-Database-classes (英語) を参照してください。

SQLMetal を実行すると、図 11 に示すように、データベース エンティティを含む出力ファイルが、Models ディレクトリに保存されます (注: Hollywood Empire の SQL Server Compact データベースは、AppData フォルダに保存されます)。図 12 からわかるように、Windows Phone アプリ コンテナー内には、データを保存できる場所が 2 か所あります。ほとんどの開発者は、分離ストレージのことをよく理解しています。分離ストレージは、Windows Phone のサンドボックス化された領域で、ファイル システム API を使用してファイルやフォルダーを保存できます。もう 1 か所は、AppData で、これはインストール フォルダーです。AppData に保存されているものはすべて読み取り専用になり、アプリの更新プログラムがインストールされるときは、現在のファイルはすべて削除され、Silverlight XAP ファイルの新しいコンテンツに置き換えられます。この点が分離ストレージの領域とは異なります。分離ストレージは、アプリが更新されても維持され、既存のデータは破棄されません。

Hollywood Empire プロジェクトのレイアウト
図 11 Hollywood Empire プロジェクトのレイアウト

ローカル データ ストレージの概要
図 12 ローカル データ ストレージの概要

SQL Server Compact データベースとデータベース エンティティ クラスを作成したら、最後に、これらの成果物を Hollywood Empire Windows Phone プロジェクトで使用できるようにします。図 13 は、接続文字列をパラメーターとして受け取るデータベース コンテキストのインスタンスを作成するコードです。この接続文字列は、AppData 内のデータベースの場所を指しています。また、データベースが読み取り専用であることも指定しています。MainViewModel のコンストラクターで、データベースが既に存在するかどうかを確認し、存在しない (false) 場合はデータベースを作成します。データベースを作成したら、他の DataContext と同様に使用でき、このデータベースに対して LINQ クエリを実行できます。

図 13 SQL Server Compact データベースのデータベース コンテキストを作成するコード

public class MainViewModel : BaseViewModel
    {
        #region Fields
        public static ObservableCollection<Hollywoodempire_actor> 
          Hollywoodempire_actors_cache;
        public static ObservableCollection<Hollywoodempire_moviecombo> 
          Hollywoodempire_moviecombos_cache;
        public static ObservableCollection<Hollywoodempire_movie> 
          Hollywoodempire_movies_cache;
        public HollyWoodEmpireDataContext db = 
          new HollyWoodEmpireDataContext(@"Data Source = 
          'appdata:/AppData/HollyWoodEmpire.sdf'; 
                                  File Mode = read only;");
        private PlayerViewModel _currentPlayer;
        private HighScoreViewModel _highScore;
        private bool _isProgressBarVisible;
        private Visibility _progressBarVisibility;
        #endregion Fields
        #region Constructors
        public MainViewModel()
        {
            if (!db.DatabaseExists())
                db.CreateDatabase();
        }
…

LINQ よ、いずこに?

Java を使って Android プラットフォームでプログラミングをしているときに、最も恋しく思った .NET コンポーネントの 1 つが LINQ でした。コレクション セットに対するクエリ実行を可能にする LINQ に匹敵する機能は、Java にはありません。Hollywood Empire の主要機能の 1 つは、プレーヤーが答えに詰まったときに、ヒントを表示する機能です。このゲームの Android バージョンでこの機能を実行するメソッドは、すべての Java ハッシュ テーブルを手動の順次スキャンにより強制的に総ざらいするため、パフォーマンスに問題がありました。Windows Phone バージョンでは、少ないコードで、ミリ秒以下で実行する LINQ クエリを使用した、はるかに洗練されたソリューションをデザインできました。

ドラッグ アンド ドロップ

Hollywood Empire の主要機能の 1 つは、ドラッグ アンド ドロップ機能です。Android バージョンでは、フリースタイルのドラッグ アンド ドロップが可能なレイアウト (AbsoluteLayout) が使用されなくなり、サポートが打ち切られているため、ドラッグ アンド ドロップ機能を実現するのはやや困難を伴います。Hollywood Empire の Windows Phone バージョンにドラッグ アンド ドロップ機能を組み込むのは、比較的簡単です。MouseDragElementBehavior を actor XAML コントロールに追加するだけです (図 14 参照)。このビヘイビア-を使用すると、メインのキャンバス レイアウト内で、自由に actor オブジェクトをドラッグできます。図 15 からわかるように、ドラッグのコントロールには Manipulation_Started と Manipulation_Completed の 2 つのイベントが使われています。Manipulation_Started イベントでは、ドラッグ オブジェクトの z インデックスを現在時刻 (秒単位) に設定し、ドラッグされるオブジェクトが常に他のオブジェクトの一番上に表示されるようにしています。Manipulation_Completed イベントには、ドロップされたオブジェクトが他の俳優の四角形領域内にあるかどうかと、共演映画が見つかるかどうかを確認するチェックがあります。

図 14 ドラッグ アンド ドロップの XAML

<Border x:Name="ActorCanvas" HorizontalAlignment="Center" 
    Background="{StaticResource PhoneAccentBrush}" BorderThickness="2" 
    BorderBrush="{StaticResource PhoneContrastBackgroundBrush}">
       <i:Interaction.Behaviors>
              <el:MouseDragElementBehavior x:Name="mousedrag"/>
       </i:Interaction.Behaviors>
       <StackPanel x:Name="ActorPanel" HorizontalAlignment="Center" 
         Width="90" Canvas.ZIndex="{Binding zIndex}" VerticalAlignment="Top">
              <Image x:Name="ActImg" 
                Source="{Binding Actor.Image, StringFormat=../../Images/Actors/\{0\}}" 
                Width="70" Height="100" VerticalAlignment="Top" 
                Stretch="UniformToFill" Margin="3"/>
              <TextBlock Text="{Binding Actor.Name, Mode=TwoWay}" 
                HorizontalAlignment="Center" FontSize="14.667" TextWrapping="Wrap" 
                Style="{StaticResource PhoneTextTitle2Style}" FontWeight="Bold"/>
       </StackPanel>
</Border>
…

図 15 Manipulation_Started イベントと Manipulation_Completed イベント

private void ActorControl2_ManipulationCompleted(object sender, 
  System.Windows.Input.ManipulationCompletedEventArgs e)
{       
    var ctrl = sender as ActorControl2;
    var act = ctrl.DataContext as ActorViewModel;
    var z = DateTime.Now.TimeOfDay.TotalSeconds;
    act.zIndex = z;
    act.PosLeft_Current = ctrl.mousedrag.X;
    act.PosTop_Current = act.PosTop_Current + 
       e.TotalManipulation.Translation.Y;
    ContentPresenter cp = 
     (ContentPresenter)itemsControl.ItemContainerGenerator.ContainerFromItem(act);
    cp.SetValue(Canvas.ZIndexProperty, Convert.ToInt32(z));
    App.ViewModel.CheckForMatch(act, App.ViewModel.CurrentPlayer.ActorInventory);
}
private void ActorControl2_ManipulationStarted(object sender, 
  System.Windows.Input.ManipulationStartedEventArgs e)
{
    var act = (sender as FrameworkElement).DataContext as ActorViewModel;
    var z = DateTime.Now.TimeOfDay.TotalSeconds;
    ContentPresenter cp = 
     (ContentPresenter)itemsControl.ItemContainerGenerator.ContainerFromItem(act);
    cp.SetValue(Canvas.ZIndexProperty, Convert.ToInt32(z));
}

LongListSelector コントロールを使用する

Windows Phone のさらに洗練された機能の 1 つは、LongListSelector コントロールです。LongListSelector は、完全なデータと UI の仮想化、フラット リスト、およびグループ リストをサポートする高度な ListBox です。これは、ユーザーが長いデータ リストをスクロールするときに役立ちます。基本的に、ユーザーがグループ ヘッダーの 1 つを選択すると、クイック ジャンプ グリッドがオーバーレイされます。その後、グリッドの項目が選択されると、長いリスト内の該当箇所に自動的にリダイレクトされます。Hollywood Empire では、LongListSelector を使用して、プレーヤーが俳優を管理できるようにしています。このゲームの Windows Phone 版のデザインでは、LongListSelector 画面は People 一覧の画面に似ているので (図 16 参照)、一貫性のあるユーザー エクスペリエンスを提供することにもつながります。Android バージョンでは ListBox を使って、データと、俳優のファースト ネームでフィルター処理された TextBox を表示しています。Windows Phone バージョンと Android バージョンの両方をプレーしたユーザーは、LongListSelector (または、ジャンプ リストと呼ばれる場合もあります) の方が、より洗練された方法だとコメントしています。

俳優の LongListSelector
図 16 俳優の LongListSelector

タブ コントロールをピボット コントロールに置き換える

Hollywood Empire の Android バージョンと Windows Phone バージョンの両方をテストしたユーザーから受け取ったコメントの 1 つは、Android バージョンで使われているタブ コントロールよりも、Windows Phone のピボット コントロールの方が断然使いやすいというものでした。ピボット コントロールについて最も評価されている点は、別のページや画面へスワイプするときの遷移のアニメーションです。Windows Phone バージョンでは、できる限りタブをピボット コントロールに置き換え、場合によっては、アプリ バーを使用して、タブに配置していた機能を取り除きました。

Facebook との統合

私は、2011 年 10 月にシカゴで開催されたマイクロソフト主催の Windows Phone Boot Camp に出席しました。このクラスは、高名な「31 Days of Mango」の著者である Jeff Blankenburg (http://jeffblankenburg.com/31daysofmango/、英語) が講師でした。このクラスで何を最も学びたかったことは、Facebook と今回のゲームを統合する方法です。Jeff によると、Windows Phone なら、10 行足らずのコードで Facebook と完全に統合できるという話でした。この回答は私の予想を裏切るものでした。Hollywood Empire の Android バージョンには、Android Facebook SDK のソースを含め、800 行近くのコードがありますから。しかし、言うとおりでした。10 行足らずのコードで、Facebook とも Twitter とも、ゲームを統合できました (図 17 参照)。この合理化は、Microsoft.Phone.Tasks アセンブリに含まれる ShareLinkTask を使用することで、実現されています。

図 17 Facebook と統合し、ゲームのリンクを共有するコード

private void share_Click(object sender, System.EventArgs e)
{
    string message = " just unlocked "
      + App.ViewModel.CurrentPlayer.MovieMatch.MovieName
      + " and received "
      + App.ViewModel.CurrentPlayer.MovieMatch.MovieBoxOffice.Value.ToString("c")
      + " in Box Office dollars by playing HollyWood Empire.";
    ShareLinkTask share = new ShareLinkTask();
    share.LinkUri = new Uri("http://www.facebook.com/album.php?aid=44178&id=184237228261252&saved#!/pages/HollyWood-Empire-for-Android/184237228261252?v=wall");
    share.Message = message;
    share.Title = " They want to celebrate the occasion by sharing the link to HollyWood Empire so you too can earn Box Office dollars by unlocking movies.";
    share.Show();
}

YouTube との統合

Hollywood Empire の Android バージョンでは、ユーザーが映画のタイトルをクリックすると、再生ボタンをクリックしなくても YouTube の予告編が自動的に起動する機能を簡単に追加できました。Android には、ネイティブの YouTube アプリまたはブラウザー バージョンをユーザーが選択できる YouTube ランチャーがあります。Windows Phone には、現在この機能がありません。YouTube アプリを Windows Phone Marketplace からダウンロードしてインストールしている場合は、YouTube のビデオを再生できます。YouTube アプリがインストールされていない場合は、YouTube アプリのインストール画面にリダイレクトされます。このルートは採用したくなかったので、何日もかけて調べ、ついに、サードパーティのアドイン、YouTube.Play (mytoolkit.codeplex.com/wikipage?title=YouTube、英語) を見つけました。このアドインを使うと、Windows Phone 上で YouTube のビデオを起動できます。YouTube.Play は、目的の YouTube ID の YouTube ページをダウンロードし、適切な MP4 リンクを検索して、MediaPlayerLauncher を使ってビデオを再生します。現時点ではこの対応策を利用していますが、Windows Phone の将来のバージョンでは、YouTube ビデオをよりスムーズに再生できるようになることを願っています。

Hollywood Empire の今後の方針

以上で、Hollywood Empire を Android 版から Windows Phone 版に変換した方法を説明しました。Android プラットフォームと Windows Phone プラットフォームの両方での開発を経験することで、これら 2 つのプラットフォームについての見方がまとまりました。表 3 に、Android プラットフォームで開発を行う場合の長短を示します。

表 3 Android プラットフォームの長所と短所

長所 短所

コア フレームワーク デザインが面白い。アプリ間で API が共有される (アクティビティ / プロバイダー)

異なる解像度 (低、中、高) を簡単に管理できる

認定プロセスが速い

Eclipse は、コーディングに非常に便利なツール (リファクタリング、単体テスト、ビューなど)

対応デバイスが多岐にわたる (テストが難しい)

ユーザーが使いやすいシミュレーターがあまりまたはまったくない

UI テンプレートとコードの関連付けが十分に統合されていない

Java でクロージャを使う場合、C# のラムダと比べると、あまりに冗長

これまでのところ、Windows Phone 開発は好ましい体験になっています。いずれは、開発プラットフォームに Windows Phone を真っ先に選ぶようになるでしょう。Windows Phone には、成熟した開発ツール セット (Visual Studio と Expression Blend) と、優れた認定プロセス (基本の品質管理に準じていないアプリは除外される) があり、完全に透過的なためです。

今後の Hollywood Empire の機能強化としては、ほとんどのメジャー映画の公開日に合わせて、毎週水曜日と金曜日に、Windows Azure のワーカー ロールとして実行される自動化プロセスにより、映画データベースを作成するようにしたいと考えています。この機能があれば、更新頻度が高まるほか、手動で映画データベースを更新する必要がなくなります。

Windows Phone 用の Hollywood Empire アプリの最終バージョンは、Windows Phone Marketplace (windowsphone.com/en-US/apps/f8ad91e5-29a2-45b6-a8a9-e3e96373bfe8、英語) からダウンロードできます。

Stanley D. Chatman は、20 年以上のキャリアがある開発者で、現在は Avanade Inc でグループ マネージャーを務めています。専門は、Windows Phone、Silverlight、Windows Presentation Foundation のほか、最近では Windows 8 Metro スタイル アプリのクライアント UI 開発です。

この記事のレビューに協力してくれた技術スタッフの Moe, LarryCurly に心より感謝いたします。