このマニュアルは Starling Framework の包括的な解説を目的とします。 Starling は ActionScript 3 で記述された、クロスプラットフォームで動作するライブラリです。主に2Dゲーム開発を対象にしていますが、他にも様々なアプリケーションを開発する事ができます。

このドキュメントの PDFバージョン がダウンロード可能です。
※訳者注:このマニュアルは、Starling マニュアルの2017年1月初回リリースを日本語訳したものです。 その後の英語版にアップデートが入っている関係で最新の内容と翻訳の内容がずれていたり、リンクが消滅してしまっているものも存在しますが、随時修正予定となります。了承ください。また、修正・翻訳に貢献いただける方は、 https://github.com/Starling-Manual/Starling-Manual-JP まで修正プルリクを送るか、直接連絡をいただければと思います。

このマニュアルでは、下記について学ぶ事ができます。

  • Starling の基盤テクノロジーや、開発理念について

  • 開発に使う IDE の選定や、プロジェクト設定のやり方について

  • ディスプレイリストやアニメーションシステムなどの基本コンセプトについて

  • フラグメントシェーダー・バーテックスシェーダーの扱いなど、上級テクニックニついて

  • フレームワークのベストパフォーマンスを引き出す方法

  • モバイルフォンやタブレットでゲームを動かすために必要な事

早速、詳細を読み進んで行きましょう!

The Starling Handbook
The Starling Handbook

このマニュアルのさらに高度な内容を含んだバージョンが必要でしょうか? Starling Handbook という本がまさにそれです。 このマニュアルで扱われた内容に加えて、さらにたくさんの情報が詰まっています。

  • ゲーム一つを最初から通して開発する具体的なやり方の紹介。

  • あらゆるヒントとなる制作のレシピ集。

  • フェザーズ(UIライブラリ) と Starling ビルダー (Stalingの画面構成をデザインするWYSIWYGエディター)について直接学ぶことができます。

本についての詳しい情報は The Starling Handbook を確認して下さい。

1. まず始めに

この章では、Starling を構成しているテクノロジーついての概要を説明します。 まず最初に FlashAIR 、そしてそこに Starling がどのようにして仲間になったのか、その歴史について簡単に確認します。 その次に、Starling と共に使う事になるであろう、便利な関連ツール・リソースについて紹介します。 章の最後まで読み終わった頃には、"Hello World" を画面に表示するサンプルが作成できるようになっているでしょう。

1.1. イントロダクション

まず最初に…、tarling コミュニティーへようこそ! 2011年の最初のリリース以来、世界中のデベロッパーがこの小さな赤い鳥とともに、素晴らしいゲームやアプリを開発してきました。 このドキュメントを読んでくれているあなたが仲間入りしてくれる事はとても素晴らしい事です。

あなたは正しいドキュメントを手にしています。 このマニュアルはあなたが知るべきことを全て教えてくれます。 AからZまで、例えばA…、Asset Management から、Z…(えーと) Zen-like coding までです。

まず、フレームワークの雰囲気を掴んでもらうために、1から小さなゲームを作っていきましょう。 その後、ディスプレイリストの構成やアニメーションシステムのコンセプトについて詳細まで見ていきます。 その後の章では、中上級 Starling ユーザの為に、(シェーダーを用いた)カスタムレンダリングの書き方などについて言及します。 そうこうしているうちに、あなたは Starling マスターとなっている事でしょう!

その為には ActionScript 3 (AS3)の基本的な理解をしていなくてはいけません。 しかし、心配はいらないかもしれません。何かしらオブジェクト指向言語の経験があればすぐに AS3 の理解は深める事ができるでしょう。 世の中にある、たくさんの本やチュートリアルも役に立つはずです。

あえて述べるならば、Colin Moock 氏の著書である "Essential ActionScript 3" をオススメします。私自身もこの本にお世話になり、たくさんの重要なことを学びました。そして今でもしばしば本棚からこの本を取り出してくるのです。 (特にAS3の奇妙な仕様のXMLの取り扱い方法、E4X、について調べる時などに。)

AS3 vs. JS

ActionScript 3 が実際には JavaScript の後継言語としてデザインされているのを知っていましたか? ご存知の通り、JavaScriptECMAScript 仕様の実装であり、ActionScript 3ECMAScript 4 に基づいています。 しかし主に政治的な理由により、ECMAScript 4 は見捨てられ、ブラウザに実装される事はありませんでした。 今日では、ActionScript 3ECMAScript 4 の唯一の標準実装となっています。 皮肉な事に、最近の JavaScript はどんどんと ActionScript 3 に似てきています。

1.2. Adobe AIR とは

たった数年前まで、Adobe の Flash Player で再生されるコンテンツは web のいたるところで見る事ができました。その頃は、web で動くインタラクティブであったりアニメーションを含むコンテンツを作るには、Flash Player を使う事が当たり前で唯一の選択でした。 ブラウザ上でビデオやサウンド、アニメーションというようなマルチメディアコンテンツを再生するのは技術的な制限が多かったのです。 そして、数少ないブラウザに実装されたマルチメディア関連機能は、ブラウザ間の非互換性という問題に悩まされていました。 端的に言えば、メチャクチャな状態でした。

Adobe Flash が持てはやされていた理由はここにあります。 脚注:[ Flash は元々 Macromedia 社により開発され, 2005年に Adobe 社の1製品に仲間入りしました。 ]

Flash(現在は Adobe Animate という名前に変わりました)によって、デザイナやデベロッパーは直感的なオーサリングツール上でマルチメディアコンテンツを作成する事ができ、出来上がったコンテンツは全てのプラットフォームで確実に同じ見た目で動作させる事ができました。 ActionScript 3 も習うのが簡単で、かつパワフルな言語として取り上げられました。

このプラットフォームが人気であるため、Adobe はある要望がある事に気づきました。 この技術をブラウザ外でも用い、単独のアプリケーションとしても動くようにしたいという要望です。 これに応じる技術が、 Adobe AIR ランタイムなのです。

AIR SDK を用いて作られたアプリケーションはデスクトップ(WindowsmacOS)及びモバイル(AndroidiOS)上で動かす事ができます。

AIR の標準ライブラリは、Flashのスーパーセットであり、Flash で可能な事は AIR 上でも全てそのまま動作可能です。 さらにアプリケーション window の制御や、システムファイルへのアクセスなど、たくさんの追加 API をも提供します。

当然デスクトップアプリケーションを作る際にも、グラフィカルなユーザーインターフェースを作る方法は必要ですね? Flash はこの要求を強くマッチする機能を持ち合わせていなかっため、さらに別の SDK が用意されました。 それが Flex(現在の Apache Flex)です。 Flex では MXML と呼ばれる XML ベースのマークアップ言語を用いて、ユーザーインターフェースのレイアウトを指定する事ができます。

Starling は Flex を必要としません。AIR SDK のみが必要です。

1.2.1. 現在の Flash と AIR

その導入の時期において、AIR は "rich Internet applications" (RIA) という流行…2000年代後半のバズワード…その一部でした。 そこには Adobe AIR と Microsoft Silverlight 間での(そして同様に Sun JavaFX との)強烈な競争がありました。

しかし、時は現在となり、世の中の流行は明らかに移り変わりました。 勝者は明白に HTML5/JavaScript であり、web 技術でアプリケーションを作成するケースでの現在最も人気なテクノロジーです。 Adobe であっても、この流れに乗っ取り、ツールの HTML5 サポートをどんどんと強めていっています。

ソフトウェアの開発という点では、一般の流行に盲目的に乗っかるという罠にはまらないようにしてください。 全ての問題には、いくつもの解法があります。ある解法は他のものよりもずっと適切です。 自身が最も快適だと感じる、作りたいと思うソフトウェア開発に集中できるツールを選びましょう。

今では "クールなやつ" と街で呼ばれないかもしれませんが、 AIR/Flash は現在でもソフトウェアを開発するのに非常に魅力的なプラットフォームです。

  • HTML5 の断片的な世界…流行のライブラリがレディーガガの衣装よりも早く移り変わる場所… それと比べて AIR/Flash は成熟していて使いやすいプラットフォームです。

  • AIR/Flash プラットフォームには、毎日の開発に必要な機能全てを網羅した大規模な標準ライブラリが付属します。

  • Flash Player プラグインは毎日のウェブサイトで見る事が確かに減ってきてはいますが、今でもブラウザ上のゲームにおいての標準です。例えば、Facebook ゲームの大多数は今でも Flash で作成されています。

  • 特に StarlingFeathers(UIライブラリ) を組み合わせる事は、デスクトップとモバイル両方をターゲットにしたシングルコードのクロスプラットフォーム開発において、最も円滑な開発手法の一つであると言えるでしょう。

さて、ここから Starling について考えます。これまでの話はどのように当てはまるでしょう?

1.3. Starling について

Starling FrameworkActionScript 3 を用いており、ハードウェアアクセラレーションの効いた(高速に動作する) アプリケーションの制作を可能にします。 Starling の主なターゲットは 2D のゲームですが、どのようなグラフィカルなアプリケーションの制作にも用いることができます。 Adobe AIR を使えば、全てのメジャーなモバイル端末とデスクトップPCに向けてアプリケーションを書き出すことができます。

The Starling logo.
Figure 1. この赤くて小さいトリが Starling Framework のロゴです。

Starling はクラシックな(昔から AIR や Flash で使われている)ディスプレイリスト構造を模倣しているものの、 それよりもはるかに高速に動作します。 全てのオブジェクトは Stage3D API を用いて、GPU によって直接描画されます。 Starling は GPU をうまく使って動作するように設計されているのです。 一般的にゲーム開発に必要となる機能はすでに Starling で実装済みです。 Starling は 難解な Stage3D の制御をゲーム開発者から隠蔽しつつ、パフォーマンスや柔軟性を損なわずに、そのパワフルな機能だけを提供します。

1.3.1. ディスプレイリスト API を新たに作る理由

上に述べた通り、Starling の API は Flash のそれと、とても近いものとなっています。flash.display パッケージの事です。 ここで、このような疑問が出てくるかもしれません: Flash 自体の内部実装を モバイルに対応できるよう作り直せばいいのではないか? 別の API を作り直す必要はないのでは?、と。

それをしない理由は、元々の flash.display (柔軟なディスプレイリストを持ち、ベクターグラフィックス・テキスト描画・フィルタ機能などの様々な機能を持った)API は、デスクトップコンピューターの時代に作られたものだからです。 それらのコンピューターはパワフルなCPUをいくつか持つものの、 グラフィックスのハードウェアは原始的で決まった処理しかできないものしか搭載していません。 しかし今日のモバイルハードウェアは、ほとんど逆の特色を持っています。 貧弱でバッテリー喰いの CPU と、とても高性能なグラフィックスチップを積んでいるのです。

問題は、CPU 描画向けに作られた API を急遽 GPU を効率的に使えるよう変更するのは、不可能ではないにしろ、とても難しいという事です。 [1] したがって、そのような試みは壮大に失敗しました。Flash プラグインはモバイル端末やタブレットのブラウザから完全にいなくなってしまったのです。

ここで名誉のために言っておくと、Adobe はこの問題については元より気づいていました。 Stage3D という ローレベルなグラフィックス API を 2011年に発表したのは、その理由からです。 Stage3D は紛れもなくローレベルで、基本的には、OpenGLDirectX のラッパーです。 開発者に GPU の生のパワーを利用する術を提供します。

さらに別の問題としてあるのは、そのようなローレベルな API では、既存のディスプレイリスト操作に慣れているユーザーの開発の助けには、少なくとも直ちにはならない、という事です。 なぜならば Stage3D API はこの上なくローレベルなので、一般的な開発者がアプリやゲームを開発する際に直接触れるようなものでも、触るべきものでもないのです。 [2] Adobe は間違いなく、Stage3D 上で動作する`flash.display` のように使いやすいハイレベルな API を欲していました。

これが、Starling が "stage" の上にたった背景(ダジャレです)なのです! Starling は Stage3D 上で動き、できる限り Flash の ディスプレイリストのように扱うことができるようデザインされています。 これによって、多くの開発者が慣れ親しんでいる開発コンセプトを用いつつ、今日のパワフルなグラフィックスハードウェアを利用するというように、 少ない労力で最大のうまみを得ることを可能としています。

もちろん、Adobe 自身がそのような API を作ることもできたかもしれません。 しかし、一枚岩な API群 が巨大な1企業によって作られる場合、肥大して柔軟性を欠いたものになってしまう傾向があります。 一方、開発者と同目線のコミュニティによって作られたオープンソースのプロジェクトは、ずっと軽快に振る舞うことができます。 これは、2011年に FlashAIR プラットフォームのプロダクトマネージャーであった Thibault Imbert が、 Starling プロジェクトを開始するに至った洞察です。

そして今日では、Starling は Adobe から資金提供を受けて支えられています。

1.3.2. Starling の哲学

Starling の主なポリシーの一つに、できる限り軽量で使いやすい状態であるようにする、というものがありました。 これは私の個人的な意見ですが、オープンソースのライブラリは単に使いやすいだけでなく、そのコードにユーザーが手を入れる事も奨励すべきだと思います。 開発者には、ライブラリが裏で何を行なっているのか、理解できる状態であって欲しいと思います。 そしてまた、もしもライブラリの機能が不十分であった場合は、開発者側でライブラリを拡張したり調整したりできるようであって欲しいと思います。

これが、Starling のソースが細かくコメントが入れられていて、ソースコード自体は驚くほど簡潔になっている理由です。 たった1万5千行のコードは、おそらくStarling を使って作られているゲーム側のコードより小さい事でしょう!

もしある日思った通りにコードが動かず行き詰まったり困ってしまったなら、 ためらわずに Starling のソースコードを参照してみることを強く推奨したく思います。 たいていの場合、すぐに何がいけないのかがわかり、内部構造についてより深い理解が得られるはずです。

もう一つの Starling の 重要なポリシーはもちろん、レガシーなディスプレイリストとよく似た構造でいる、ということです。 これは、私がディスプレイリストの背後にある考え方を好きだからという理由からだけでなく、 開発者が Starling の扱いにすぐに馴染めるようにするためでもあります。

それでも、私はディスプレイリストの完全な複製を作ろうとした事は一度もありません。 GPU をターゲットとした場合、特別なコンセプトが必要であり、そのコンセプトが随所に行き渡っていなくてはなりません。 TexturesMeshes のようなコンセプトは、オリジナルの API が自然に、そもそも GPU 向けにデザインされていたかのように 調和できるよう、狙って作られたものです。

1.4. IDE を選ぶ

Starling でのゲームやアプリの開発は Adobe AIR SDK を使って行うことをここまで述べてきました。 技術的には、テキストエディタとコマンドラインのみで AIR アプリをコンパイルすることは可能です。 しかしあまりオススメはできません。 開発にはいわゆる IDE を使うのが良いでしょう。 IDE を使えば、デバッグやリファクタリングやデプロイという作業を、ずっと簡単に行うことができます。 ありがたい事に、いくつかの選択肢があります。早速それらを見てみましょう。

1.4.1. Adobe Flash Builder

以前は Flex Builder と呼ばれていたもので、 Adobe によって作られた IDE です。 単独で(スタンダードもしくはプレミアムエディションを)購入することもできますし、Creative Cloud 契約の一部として使うこともできます。 Flex Builder は Eclipse という有名な IDE を元にして作られており、 考えうる全ての機能、例えば、モバイル機器のデバッグ機能やリファクタリング機能なども持つ、とてもパワフルなソフトウェアです。

プレミアムエディションには、とても役に立つパフォーマンスプロファイラーも含んでいます。

個人的にも長い間 Flash Builder を使ってきました。Starling のダウンロードファイルには Flash Builder のプロジェクトファイルもついてきます。 しかし、一つ注意すべき点があります。Flash Builder の開発はすでに Adobe 内で継続されていないようなのです。 最後のアップデートであるバージョン4.7は2012年にリリースされました。そしてそれはあまり安定した動作をしません。 この問題が近い未来に改善する見通しはなさそうです。

よって、私が Flex Builder をオススメするのは、あなたがすでに Creative Cloud ユーザーである場合と (それならば無料で手に入ります)、古いライセンスを手に入れている場合だけです。 誤解しないで欲しいのですが、Flash Builder は大変優れた機能を持っていますし、実際の業務にも役立つでしょう。

しかし、しばしばクラッシュに遭遇したり、AIR SDK の切り替え作業が面倒であったりはします。

  • 対応プラットフォーム: Windows, macOS

  • 値段: USD 249 (スタンダードエディション)、 USD 699 (プレミアムエディション)

Adobe Flash Builder.

1.4.2. IntelliJ IDEA

この選択候補は、"全てのIDEを支配するIDE" と呼ばれます。 なぜなら IDEA はあまりにもたくさんのプラッフォームと言語をサポートするからです。 AIR のサポートは、"Flash/Flex Support" というプラグインによって行われます。

しばらく私も IDEA を使ってみましたが、とても気に入りました。特にリファクタリング機能が強力です。 機能的にはまるで AS3 専用として開発されたかのようです。全ての重要な機能が含まれています。

Flash Builder とは異なり、この IDE はしばしばアップデートされます。まあ、特に Flash 開発プラグインのアップデートという訳ではありませんが。。長い間、修復が待たれているマイナーなプラグインの不具合が多少存在します。

しかしそれは粗探しというものです。 IDEA はとても素晴らしい IDE ですし、もしもあなたが macOS を使っているのならば、私はこの IDE をオススメします。 唯一の問題となるかもしれないのは、料金体制です。開発元の JetBrains は最近、サブスクリプション型の支払いモデルに移行しました。 人によっては不都合がある料金モデルかもしれません。

この IDE には、フリーのコミュニティバージョンも存在します。 しかし、残念ながら Flash/Flex プラグインが含まれていません。

  • 対応プラットフォーム: Windows, macOS

  • 値段: USD 499 (1年目)、 USD 399 (2年目)、 USD 299 (3年目以降)

サブスクリプション型の支払いモデルには、"perpetual fallback license" と呼ばれるライセンスも含まれています。 これは、1年間の利用の後にはそのバージョンの IDEA を永久的に使うことができる権利のことです。 その後にサブスクリプションをキャンセルしても、すでに永久利用ができる状態であるバージョンの IDEA は使い続けることができます。 個人的にはこの仕組みでサブスクリプションモデルのマイナス面を軽減できているかなと感じます。
IntelliJ IDEA

1.4.3. FlashDevelop

私は macOS で作業することが好きなのですが、ある部分では Windows ユーザが羨ましくなります。 FlashDevelop という素晴らしい IDE があるからです。無料及びオープンソースで、Starling を使った開発に使うことができます。 だいたい2005年からこの IDE は存在しますが、現在でも定期的なアップデートがなされています。 もしあなたが Haxe (プログラム言語の一種) に熱中しているのであれば、この IDE はそんなあなたもカバーしています。

私は主に macOS を利用しているため、直接 FlashDevelop を触ったことはあまりありません。 しかし、たくさんの Starling フォーラムへの投稿を見ましたが、この IDE に関しては良い噂しか聞きません。 Parallels などのソフトを使って、mac のバーチャルマシン上で このソフトを使う人もいるようです。

  • 対応プラットフォーム: Windows のみ

  • 値段: 無料 かつ オープンソース

FlashDevelop

1.4.4. PowerFlasher FDT

Flash Builder と同じように、FDTEclipse のプラグインとして作られた IDE です。 したがって、Flash Builder からの移行先としては良い選択となります。Flash BuilderFDT は、とてもよく似ています。 Flash Builder のプロジェクトファイルをインポートすることさえできます。

FDT は Flash Builder よりも、いくつかの部分ではより機能が向上しています。

例えば、Flash から AIR へと簡単にプロジェクトの種類を切り替えることができます。 この作業を Flash Builder で行うこと事は不可能です。

さらに、HTML5/JavaScriptHaxePHP など、他のいくつかの言語も追加でサポートしています。

総じて、これはとても手堅い IDE です。 もしも Eclipse が好きなのであれば、FDT を使って損はないでしょう。

FDT には無料のバージョンも存在します。最初に開発を始める際にはぴったりです。 プロダクトページに書かれている内容とは異なり、無料バージョンであってもモバイル AIR の開発が可能です。

  • 対応プラットフォーム: Windows, macOS

  • 値段: USD 25 〜 USD 55 / 月 (契約期間による) 学生及び教員は特例の適用あり

Powerflasher FDT

1.4.5. Adobe Animate

もしもあなたがデザイナーもしくはデベロッパーで Flash の経験が長いのであれば、 このリストに なぜ Adobe Flash Professional がないのだろうか、と疑問に思うかもしれません。 それは、まさに今読んでいるここの事です!

気づいていない人もいるかもしれません。 Adobe は最近 Flash の名前を Adobe Animate に変更しました。

この新しい名前は現在のソフトウェアのアップデート内容を反映しており、合点が行くものです。

Animate はまさに一般的なアニメーションを作成するツールであり、出力形式としては Flash だけではなく HTML5WebGLSVG などをサポートします。

Animate を Starling の開発に使うことは可能です。しかしオススメはできません。 Animate はデザイナーにとってとても素晴らしいツールですが、コードを書くのには向いていません。

Animate を使うのはグラフィック作成に留めておき、コードは上に述べた IDE を使って書くのが良いでしょう。

  • 対応プラットフォーム: Windows, macOS

  • 値段: Creative Cloud 契約があるならば無料

1.5. リソース一覧

必要な道具はほぼ揃いました。 最初のプロジェクトを始めるため、どのように準備をすればいいか見てみましょう。

1.5.1. AIR SDK

IDE の準備が終わったならば、次にすることは AIR SDK 最新バージョンのダウンロードです。 Adobe は SDK の安定バージョンを3ヶ月に1度定期リリースします。常に最新をキープしましょう。 新しいリリースにはたいてい重要なバグフィックスが含まれ、それは最新のモバイルOSとの互換性を保つのに重要なものです。 さらに、新機能の実験的な実装などもしばしば見ることでできるでしょう。 なるべくこの SDK の 更新ペースについていけるよう、Starling への取り込みも頑張っています。

最新のリリースバージョンはいつでもここで見つけることができます。 Download Adobe AIR SDK

Starling のバージョン2系 は AIR のバージョン19以上を必要とします。

1.5.2. Flash Player プロジェクター

もしもあなたのプロジェクトが Flash Player 上でも動くコンテンツを作ることも目的とするなら、 Flash Player の スタンドアロン版を手に入れることをオススメします。 プロジェクター と呼ばれるものです。(プロジェクターには、デバッグ版リリース版 が存在します。)

プロジェクターを使うとデバッグ開発を行うのが簡単になります。 もちろんデバッグ版の Flash Player をブラウザにインストールしてデバッグを行うこともできます。しかし個人的にはとても面倒に感じます。

プロジェクターはずっと速く起動しますし、動作させるために html ファイルを用意する必要もありません。

下記のページは開発者が利用可能な全てのバージョンの Flash Player を含んでいます。 "projector content debugger" という項目を探してください。

デバッグ版の動作は通常盤に比べて著しく低速です。コンテンツのパフォーマンス調整を行う際はこの事に気をつけてください。

あなたが使っている IDE はどのバージョンの Player を使えばいいか設定してやる必要があるかもしれません。 例えば、IDEA ではデバッグ設定スクリーンに、この Player 設定が存在します。 他の IDE では シンプルに OS のシステム設定の Player を使うかも知れません。 どの場合でも、SWF ファイルをコンパイルする AIR SDK のバージョンと同じか高いバージョンの Player を使う事が必要です。

1.5.3. Starling

さて、あと残すは Starling 自体のダウンロードだけです。 2つの方法で手に入れる事ができます。

  1. 最新のリリースを ZIP ファイルで入手:http://gamua.com/starling/download/[gamua.com/starling/download]

  2. Starling の Git リポジトリをクローンする

前者の良い点は、ライブラリをコンパイル済みの SWC ファイルが付属してくる事です。(starling/bin フォルダ以下に配置されています。) SWC ファイルは簡単にプロジェクトに取り込む事ができます。

しかし、この方法では安定リリースバージョンしか手に入れる事ができません。 最新機能やバグ修正をすぐさま取り入れることはできないのです。 この理由から、Git を使ってファイルを入手することをオススメします。

あなたがバグレポートをしたとして、数日後にすぐそれが修正されたとします。(そういうことは実際にあります。) 通常のダウンロードの方法では、次の安定バージョンのリリースを私が作るまで、かなり長い間待つ場合がありますが、 もしも Git リポジトリを使っているならば、すぐさま修正バージョンを手に入れることができるのです。

Git について詳しい解説をすると、このマニュアルの範疇を超えてしまいます。 しかし、web 上には Git の良いチュートリアルがたくさん見つかるはずです。 一度 GIT をインストールすれば、下記のコマンドにより、Starling リポジトリの完全なコピーをあなたの PC 上に手に入れることができます。

git clone https://github.com/Gamua/Starling-Framework.git

このお万度により、Starling-Framework フォルダの下に Starling のコピーが作られます。

ライブラリの実際のソースコードは starling/src というサブフォルダの中に見つかります。 先の述べた全ての IDE が source path として、このソースコードフォルダを登録できます。 これにより、Starling をあなたのプロジェクトの一部として取り込むことができます。 SWC ファイルをリンクすることより難しいなんていうことはありません。 そして素敵な副次作用として、デバッグ開発時には Starling のソースコードに直接潜っていって参照することができます。

そして、このアプローチの最も良い部分は、簡単に最新バージョンの Starling に更新することができることです。

単純に、リポジトリのディレクトリにて`pull`コマンドを実行してください。

cd Starling-Framework
git pull

この方法はブラウザを開いて最新ファイルをダウンロードするより、ずっとシンプルです。そう思いませんか?

Git ユーザ向けにいくつか追加の情報を記します。

  • 日々の Starling の開発は master ブランチで行われます。

  • 安定バージョンは タグ付け されます。 (例えば v2.0, v2.0.1, v2.1 など)

  • それぞれのタグは GitHub 上で Release として扱われ、そこにはコンパイル済みの SWC ファイルも付属されます。

1.5.4. Getting Help

誰にでも行き詰ってしまうことはあります。 解決不可能な問題に直面したり、Starling のバグのせいで先に進めなくなる事もあるでしょう。

どちらにしても、Starling コミュニティ はあなたを一人置き去りにはしません。

ヘルプが必要な時に訪れるべき情報源となるサイトがいくつか存在します。

Starling フォーラム

Starling コミュニティの中の一番のハブたる場所です。 毎日たくさんの投稿があり、あなたが持っている問題はすでにここで質問済みであることも多いでしょう。 検索 機能を活用してください。

もしも目的の情報がみつからないのなら、メンバー登録をして気軽に質問をしてください。 web上で最も親切で寛容なコミュニティをそこに見つける事ができるでしょう。
http://forum.starling-framework.org

Starling マニュアル

今あなたが読んでいるこのページの事です. できる限り Straling のリリースと共にこのマニュアルも更新していくつもりです。
http://manual.starling-framework.org

Starling Wiki

wiki ページは また他と異なった感じで Starling に関する話題のリンクや記事を含んでいます。 そしてこれは重要なコンテンツかと思うのですが、Starling の拡張機能の一覧ページを持っています。 いくつかの拡張機能については後ほどまた紹介します。
http://wiki.starling-framework.org

API リファレンス

Starling の API リファレンス ページを参照するのも忘れないでください。 クラスやメソッドについてより詳細な情報が得られます。
http://doc.starling-framework.org

Gamua Blog

Gamua blog で Starling について、最新の情報を手に入れてください。 正直なところ、ブログを書くのは怠けてしまう事もあるのですが、 Starling の最新リリースについての情報は必ずブログに投稿されています。
http://gamua.com/blog

Twitter

私は いくつかの SNS を使っていますが、Twitter で @Gamua とメンションするのが、私と連絡を取る一番良い方法です。 このアカウントをフォローして、新しい開発内容や、Starling で作られたゲームなどの最新情報を手に入れてください。
https://twitter.com/Gamua

1.6. Hello World

さて、ここまでたくさんの背景的な情報を伝えてきましたが、ついに手を動かす時が来ました! プログラミングの一般的な通例である "Hello World" 的なプログラムが最適でしょう。 それ無くして、このマニュアルは完成しないのです。

1.6.1. チェックリスト

以下は、ここまでで準備を終えているはずの項目一覧です。

  • IDE を選んでダウンロードする。

  • AIR SDK の最新バージョンをダウンロードする。

  • Flash Player Projector の最新バージョンをダウンロードする。

  • Starling の最新バージョンをダウンロードする。

  • IDE が正しい SDK と Flash Player を使うように設定する。

IDE の設定とプロジェクトのセットアップを行うやり方は それぞれの IDE で多少違います。 参考となるよう、それぞれの IDE でのセットアップ方法について、Wiki ページにチュートリアルを用意しておきました。 Starling Wiki.

この先に進む前に、適切なチュートリアルにしたがってください。

確かに、このようなセットアップの手続きはとても面倒なものです。でも我慢してください。 このような手続きはそんなには必要となりません。

1.6.2. 最初のコーディング

新しいプロジェクト、もしくはモジュールを IDE 上で作成してください。 最初は Flash Player 向けの( Web 向けの)"Hello World" プロジェクトを作ることをオススメします。 プロジェクトを作ると同時に、あなたの IDE は小さなクラスを1つ作るでしょう。 そのクラスを開いて下記の様に編集して見ましょう。 (一般的に、そのクラスはプロジェクト名と似たような名前で作られます。下の例のクラス名は実際のクラス名に適切に置き換えて考えてください。)

package
{
    import flash.display.Sprite;
    import starling.core.Starling;

    [SWF(width="400", height="300", frameRate="60", backgroundColor="#808080")]
    public class HelloWorld extends Sprite
    {
        private var _starling:Starling;

        public function HelloWorld()
        {
            _starling = new Starling(Game, stage);
            _starling.start();
        }
    }
}

このコードでは Starling クラスのインスタンスを作成し、すぐさま Start させています。 Starling のコンストラクタ引数として、"Game" クラス自身を渡していることに注目してください。 Starling は、その準備が出来次第、"Game" クラスをインスタンス化します。 (そのように自動で処理が行われるので、正しい実行順序などを気にする必要はありません。)

もちろんですが、その"Game" クラスの内容は先に記述しておかないといけません。 プロジェクトに`Game`クラスを追加して、下記のコードを記述してください。

package
{
    import starling.display.Quad;
    import starling.display.Sprite;
    import starling.utils.Color;

    public class Game extends Sprite
    {
        public function Game()
        {
            var quad:Quad = new Quad(200, 200, Color.RED);
            quad.x = 100;
            quad.y = 50;
            addChild(quad);
        }
    }
}

ここまでの作業がうまくいっていれば、このクラスは単純に赤い矩形を画面に表示します。

Game クラスは starling.display.Sprite クラスを継承するようにしてください!flash.display.Sprite ではありません。 これは重要です。なぜなら今は(Flash の ディスプレイオブジェクトの世界ではなく)Starling の世界にいるからです。 Starling の世界は、flash.display パッケージ空間と完全に切り離されています。

1.6.3. 最初のローンチ

では、プロジェクトを実行して見ましょう。 人によっては面食らうかもしれません、下記のようなエラーメッセージが表示されるからです。

Startup error message
Figure 2. このようなエラーメッセージがあなたをお出迎えしてくれるかもしれません。

このエラーを見るケースでは、おそらくスタンドアロンの Flash Player ではなく、ブラウザが起動していることでしょう。 run/debug 設定を確認して、ブラウザではなくデバッグ版の Flash Player Projector が立ち上がるように設定してください。 おそらくそれで問題が解決します。

ブラウザ上でも動くようにする

しかし、ブラウザの HTML 上で Starling で作った SWF コンテンツを動かしたくなる事もあるでしょう。

その場合、wmode というパラメータ値を direct という値に変更する事で、エラーが起こらないようにする事ができます。 このパラメータ設定は SWF を埋め込んだ HTML ファイルのコード中に記述されています。 通常、下記のような編集を行なう事になります。

// このような記述がある場所を見つけ…
var params = {};

// …直接このような記述を追加します。
params.wmode = "direct";
AIRアプリでのエラーを修正する

SWF ファイルではなく、AIR アプリケーションを書き出した場合も、同様のエラーを見る事になるでしょう。 この場合、AIR application descriptor と呼ばれるファイルを編集する必要があります。 そして今回の場合、そのファイルは HelloWorld-app.xml か、それと似たような名前ですでに作られているはずです。 その XML ファイルの中にある、renderMode という記述を探してください。(おそらくコメントアウトされた状態かと思います。) 設定値を direct に変更しましょう。

この記述を見つけます:
<!-- <renderMode></renderMode> -->

このように置き換えます:
<renderMode>direct</renderMode>
この作業によって、flash ランタイムが GPU にアクセスする事が許可されます。 初期状態のままでは、Stage3D は利用できないのです。

1.6.4. 修正後のローンチ

おめでとうございます! これで、無事に最初の Starling コンテンツをコンパイルして実行できたかと思います。

Hello World
Figure 3. 赤い Starling(ムクドリ)が 赤い箱の中にいる素晴らしい情景ですね!

冗談はさておき、作業のうち最も気が滅入る部分はすでに終わりました。 ついに、実際のプロジェクト内容の作成に取りかかる時がやってきました!

1.7. まとめ

これで、Starling と関連ツール・リソースについての理解が深まったと思います。 次は実際にゲーム開発に取り掛かってみましょう。

2. 基本コンセプト

Starling はコンパクトなフレームワークかもしれません。 それでも、内部にたくさんのパッケージやクラスを持っている事を誇りと思っています。 それらクラスやパッケージは、いくつかの基本コンセプトに乗っ取って作られており、お互いに保管しあったり、拡張しあったりしています。

同時に、どんなアプリケーションの作成にも力となるツール類が提供されます。

ディスプレイリスト

画面上に表示されている全てのオブジェクトは ディスプレイオブジェクト であり、ディスプレイリスト の中で管理されます。

テクスチャとイメージ

スクリーン上のピクセルに様々な色や形を表示するには_Texture_ と Image クラスの使い方を覚えなくてはいけません。

動的なテキストフィールド

動的な内容のテキストを表示する処理は、ほとんどのアプリケーションの基本処理となります。

イベントのハンドリング

コミュニケーションがカギです!ディスプレイオブジェクトはお互いに連携をとって動作する必要があります。 その連携は、Starling のパワフルなイベントシステムによって行われるものです。

アニメーション

画像に動きを与えましょう!ディスプレイオブジェクトをアニメさせるには、いくつかのやり方があります。

アセットの管理

どうやってテクスチャやサウンドファイルのような外部アセットをロードして管理すればいいのか、これを学びます。

スペシャルエフェクト

エフェクト機能とフィルター機能は、あなたのアプリのグラフィックスを際立たせてくれます。

ユーティリティ

あなたの人生を楽にする、たくさんのヘルパークラスです。

たくさんの領域をカバーして学ぶ必要があります。 スーパーマリオのセリフを借りつつ挑みましょう。"レッツ・ア・ゴー!"

2.1. Starling の設定

Starling を使ったアプリケーション作成の最初のステップです。 Starling クラス (starling.core パッケージ下にあります。)のインスタンスを作ります。 コンストラクタの引数は以下のようになっています。

public function Starling(
    rootClass:Class,
    stage:Stage,
    viewPort:Rectangle = null,
    stage3D:Stage3D = null,
    renderMode:String = auto,
    profile:Object = auto);
rootClass

Stage3D の初期化が終わったタイミングですぐにインスタンス化されるクラスです。 starling.display.DisplayObject のサブクラスでなくてはいけません。

stage

従来の Flash の stage です。Starling で参照が保持され利用されます。 これによって Starling と Flash の ディスプレイリストがお互いに接続されるようになります。

viewPort

Flash の stage 全体のうち、Starling が描画するエリアです。 stage と同じ大きさにされる事が多いため、値を指定しないか null を指定する事で、自動的に stage の大きさが使われます。

stage3D

描画に使われる Stage3D のインスタンスです。 全ての Flash の stage は複数の Stage3D インスタンスを持つ事ができるため、そのうちの一つを選択する事ができます。 しかし、たいていの場合、デフォルトの null パラメータを渡せば(引数を省略すれば)十分です。 自動的に利用可能な Stage3D オブジェクトの最初の一つが Starling で利用されるようになります。

renderMode

Stage3D の目的は、ハードウェアを使って高速な描画を行う事です。 しかし、renderMode の引数に Context3DRenderMode.SOFTWARE を渡すごとにより、ソフトウェアのレンダリングモードに切り替えることもできます。 というものの、デフォルトの auto 値が推奨されます。 auto の指定とすると、ハードウェアレンダリングが使えない状況では自動的にソフトウェアのレンダリングに切り替わります。

profile

Stage3D は、GPUの能力をグループに振り分けたいくつかの プロファイル を提供します。 ( 各プロファイルは、Context3DProfile クラスの定数として設定されています。) 良いハードウェア上でアプリが動いているほど、プロファイルも良いものが選択可能になります。 auto の設定では単純に一番良いプロファイルを選択します。

たいていの引数はデフォルトの引数が適切に設定されています。よって全てを指定する必要はありません。 下記のコードは、Starling を開始する単純なやり方の例です。 Flash Player 用、もしくは AIR プロジェクト用の Main クラスを記述しています。

package
{
    import flash.display.Sprite;
    import starling.core.Starling;

    [SWF(width="640", height="480",
         backgroundColor="#808080",
         frameRate="60")]
    public class Main extends Sprite
    {
        private var _starling:Starling;

        public function Main()
        {
            _starling = new Starling(Game, stage);
            _starling.start();
        }
    }
}

ここで Main クラスが flash.display.Sprite を継承しており、Starling の Sprite を継承していない事に注目してください。 AS3 で作られた全てのプロジェクトで Main クラスはこのような状態である必要があります。 しかし、Starling が初期化を終えるとすぐに、starling.display の世界を構築する Game クラスへと処理が移ります。

フレームレートの設定

いくつかのコンテンツの設定は class 定義より前にある "SWF" MetaData タグにて設定されています。 フレームレートもそこで設定できます。 Starling 自体にはフレームレートの設定がありません、stage の frameRate 値をそのまま利用します。 frameRate 値は nativeStage のプロパティ経由で動的に変更することもできます。

Starling.current.nativeStage.frameRate = 60;

Starling の初期化プロセスは非同期の処理です。 これは、Main 命令の最後の段階ではまだ Game インスタンスにアクセスできない事を意味します。 Game インスタンスが作成されたタイミングは ROOT_CREATED イベントを受け取ることで知る事ができます。

public function Main()
{
    _starling = new Starling(Game, stage);
    _starling.addEventListener(Event.ROOT_CREATED, onRootCreated);
    _starling.start();
}

private function onRootCreated(event:Event, root:Game):void
{
    root.start(); // Game クラスには 'start' メソッドが定義されている想定です。
}

2.1.1. ViewPort

Stage3D は Starling に矩形の描画エリアを提供します。 描画エリアは stage のどこでも構いません。つまり Flash Player の画面内のどこでも、AIR の場合はアプリケーション画面内のどこでも、です。

Starling では、この描画エリアを viewPort と呼びます。 大抵は画面全体を使いたい事かと思いますが、場合によってはエリアを制限した方が有効なこともあります。

4:3 のアスペクト比をもつゲームを 16:9 のそれをもつ画面上で動かすことを考えます。 4:3 比率の viewPort を画面の真ん中に配置する事で、"letterbox" されたゲーム、 つまり、画面上下に何も表示しないエリアをもつゲームが出来上がります。

Starling の stage を考えずに、viewPort についてあれこれ言う事はできません。 デフォルトでは stage の大きさと viewPort の大きさは等しくなります。

これはもちろんとても理にかなっています。 1024 × 768 ピクセルのディスプレイを持っているデバイスは同じ stage の大きさを持っているはずです。

しかし、Starling の stage の大きさは、変更する事ができます。 stage.stageWidthstage.stageHeight のプロパティ値を変更する事で可能です。

stage.stageWidth = 1024;
stage.stageHeight = 768;

しかし、これは何を意味するでしょう? 描画エリアは viewPort で定義されるのでしょうか?それとも stageの大きさ で定義されるのでしょうか?

慌てなくとも大丈夫です。描画エリアはやはり先に説明されたように viewPort によって定義されます。 stageWidthstageHeight を変更しても描画エリアは全く変化しません。 viewPort の値によってのみ、 stage は大きさを変えます。 では、何を変更したのでしょうか。それは stage の coordinate システム のサイズです。

stage の横幅が 1024 である時、x座標が 1000 であるあるオブジェクトはほぼ右端に表示されます。viewPort の値が 51210242048 などどんなピクセル数であろうと関係ありません。

これは HiDPI スクリーンに対しての開発をしている時に特に役に立ちます。 例えば、iPad にはノーマル解像度のものとレティナ解像度のものがありますが、後者は縦横のピクセル幅が倍で、画面全体で言えば4倍のピクセル数を持っています。 そのようなスクリーン上で、ユーザーインターフェースは小さくは表示されずに、よりきめ細かく表示されるべきです。

viewPortstage サイズ の違いを理解する事で、このデバイス解像度の違いは 簡単に Starling 上で再現する事ができます。 どちらのタイプの端末でも、stage サイズは 1024×768 になります。対して、viewPort 値は実際の端末のピクセル値となります。 さらに、この調整はどのようなデバイスでアプリケーションが動いているかに関わらず、画面上に表示しているオブジェクトに対しても適用する事ができます。

Points と Pixels

上記についてよく理解をしたならば、そのようなレティナデバイス上では、x座標が 1 である表示オブジェクトが 実際には 2 ピクセルの位置にある事がわかるでしょう。 つまり、計測の単位が変わったのです。 ピクセルの単位ではなく、ポイントの単位として考えます。 低い解像度のスクリーンでは 1ピクセル = 1ポイント ですが、HiDPI スクリーンでは 2ピクセル = 1ポイントです。 (端末によってはそれ以上のピクセル数となります。)

実際の幅をピクセル数で求めるには、単に viewPort.width 値を stage.stageWidth 値で割ってやればよいです。 もしくは Starling の contentScaleFactor プロパティ値を参照します。内部で同じ計算をします。

starling.viewPort.width = 2048;
starling.stage.stageWidth = 1024;
trace(starling.contentScaleFactor); // -> 2.0

このコンセプトが実際にどのように役に立つかは モバイル開発 の章で詳しく説明します。

2.1.2. Context3D プロファイル

Starling が動いているプラットフォームは、様々なグラフィックプロフェッサーを搭載しています。 もちろん、それら GPU はそれぞれで異なった性能を持っています。 ここで、どのようにしてそれらの性能差をランタイム上で見分ければいいか、という疑問が湧きます。

Context3D プロファイルrender プロファイル とも呼ばれます。)が存在するのがその答えです。

Context3D とは何か?

あなたが Stage3D を扱う際は、裏にあるレンダリングパイプラインと連携を取っています。 レンダリングパイプラインはたくさんのプロパティやセッティングを持ちます。 context は、このパイプラインをカプセル化する物です。 テクスチャの作成、シェーダーのアップロード、三角ポリゴンのレンダリング、これらは全て context を通じて行われます。

実際、Starling はプロファイルの制限事項を利用者から隠す事に力を尽くしています。 広い環境で確実に動くようにするため、一番性能の低いプロファイルであっても動くようにデザインされています。 同時に、高機能なプロファイルとともに動く場合は、自動的にその能力を引き出すように動きます。

それらプロファイルの基本機能について知っておくことは役にたつかもしれません。 下記はそれぞれのプロファイルの要約です。低機能な順に並んでいます。

BASELINE_CONSTRAINED

もしデバイスが Stage3D を完全サポートするなら、このプロファイルは最低限サポートされていなければいけません。 このプロファイルには、いくつかの強い制限があります。例えば、テクスチャのサイズは縦横とも2の倍数のドット数でなければならない、 シェーダープログラムの長さが著しく制限されている、などです。 このプロファイルは古いデスクトップPCで見つかる事が多いです。

BASELINE

モバイル端末ではこのプロファイルが一番性能の低いものとなります。 Starling はこのプロファイルを使ってうまく動作する事が可能です。 テクスチャの縦横サイズが2の倍数でなくてはいけないという制限が消え去ったのでメモリを効率的に使う事ができます。 また、シェーダープログラムの長さの制限も緩和したので十分に実用的です。

BASLINE_EXTENDED

テクスチャの最大サイズが 2048x2048 から 4096x4096 ピクセルへと増加しました。 これはハイレゾ画面を持つ端末には極めて重要な拡張です。

STANDARD_CONSTRAINED, STANDARD, STANDARD_EXTENDED

現在、Starling はこれらのプロファイルが提供する機能を必要とはしません。 これらのプロファイルは、追加のシェーダーコマンドと、その他低レベル基本機能の強化を提供します。

私のオススメは、設定を auto にして利用可能な一番高性能のプロファイルを自動選択させ、その選択でよしなに動作させる事です。

テクスチャの最大サイズ

プロファイの利用時に気をつけないといけないことが一つだけあります。 テクスチャサイズを大きくしすぎない事です。最大テクスチャサイズは Texture.maxSize 値で確認する事ができます。 なお、その値が参照できるのは Starling の初期化が終わった後からです。

2.1.3. Flash描画の重ね合わせ

Starling のメインコンセプトは Stage3D API を利用して高速な描画をする、という事です。 しかし、クラシック(元々のFlashの)ディスプレイリストには Starling では実現できない様々な機能があることは否定できません。 よって、両者を合わせて使ってやることは理にかなっています。

nativeOverlay プロパティを使うと、それを簡単に行う事ができます。 viewPort 値と contentScaleFactor 値を考慮しつつ、Starling の前面に従来の flash.display.Sprite が表示されます。

従来の Flash 描画オブジェクトを使いたい場合、この nativeOverlay にそれらを追加してください。

しかし、Flash 描画を Stage3D 上に重ねると、いくつかの(モバイルの)端末ではパフォーマンスの低下を招く事には注意してください。 この理由で、特に必要がない場合は全ての Flash 描画オブジェクトを取り除くようにしましょう。

質問される前に…Starling のディスプレイオブジェクトの背面に Flash 描画オブジェクトを配置する事はできません。 Stage3D の領域はいつでも最背面に存在します。それ以外の状態はありえません。

2.1.4. 不必要な描画処理のスキップ

アプリケーションの画面が数フレームの間、変化がなくそのままである事はとてもよくある事です。 例えば、アプリケーションは静的な画面を見せているのかもしれませんし、ユーザーの入力を待っているのかもしれません。 同じ画面を表示するために、画面描画処理をなんども行う必要はあるでしょうか?

これが、skipUnchangedFrames というプロパティがポイントとする箇所です。 このプロパティが有効に設定されると、画面更新命令がない事が認識されると、現在の描画がそのまま保たれます。 モバイル端末では、この機能による効果はかなりのものがあります。 バッテリー寿命を伸ばすのにこれ以上シンプルな良い方法はないでしょう!

この機能が効果的ならば、なぜデフォルトで有効にしないのか?という意見がすでに上がってきています。 これは、最もな意見です。

しかし、この機能は ビデオテクスチャレンダーテクスチャ を使った場合うまく機能しないのです。 それらのテクスチャに変更があった事は判定しづらいのです。 しかし、簡単なワークアラウンドがあります。それらのテクスチャを使う間は、skipUnchangedFrames を一時的に無効とするか、stage.setRequiresRedraw() 命令を実行して、テクスチャに変更があった際に強制的に描画更新をしてしまえばいいのです。

この機能についてよく理解をしたのならば、いつでも有効にする事を習慣にしてしまいましょう! そのうち、上記の問題を自動で回避できる実装を将来の Starling のバージョンで実現したいと思っています、

モバイル端末では、知っておくべき制限事項があります。nativeOverlay を使った場合など、Flash側 の stage に描画オブジェクトが存在する場合、Starling は 描画処理をスキップできません。これは Stage3D の仕組み上の制限によるものです。

2.1.5. 使用メモリやFPSの画面表示

アプリケーションの開発をしている際は、できる限り何が内部処理で起こっているか把握していいたい事かと思います。 そうであれば、事前に何かの問題に気付けたり、後々に開発が行き詰まってしまうような事態を避けることができます。 statistics(stats)の表示はそのような場合に役に立ちます。

_starling.showStats = true;
The statistics display
Figure 4. stats の表示 (デフォルトでは左上に表示されます。).

これらの数値はそれぞれ何を意味するのでしょうか。

  • framerate は説明するまでもないかもしれません。直前の数秒間の間に Starling が行った描画更新の回数です。

  • Standard memory は、AS3 のオブジェクトがどれだけ存在しているかを示します。 String 自体も SpriteBitmap も _Function_も、全てのオブジェクトは幾らかのメモリを消費します。単位は MBytes です。

  • GPU memory は、上述のメモリとは別のものです。テクスチャはグラフィックメモリに格納されます。 頂点バッファやシェーダープログラムも同様にグラフィックに格納されます。 たいていの場合、テクスチャがほとんどのグラフィックメモリを占めます。

  • draw calls 数は、毎フレームにどれだけの "draw" 命令が GPU に送られたかを示します。 一般的には、draw call が少ない方がアプリケーションは高速に動きます。 この数値については、 パフォーマンス チューニング の項目で詳しく説明します。

stats 表示の背景色が、黒と緑で時々入れ替わる事に気づいたかもしれません。 このちょっとした変化は skipUnchangedFrames が今どのように動いているかを示しています。 フレームスキップの仕組みが過去数フレームでうまく動いている場合は、背景色が緑になります。 静的な画面が表示されている場合はここが緑色のままになるはずです。 もし緑にならないのであれば、フレームスキップを阻害している実装箇所がどこかにあります。

showStatsAt 命令を使う事によって、stats の表示位置を調整することができます。

2.2. ディスプレイオブジェクトの表示

すべての準備が整ったので、画面上に何か表示してみましょう。

どんなアプリケーションを作成する際でも、メインのタスクはアプリケーションをたくさんのユニットに分割する事になるでしょう。 大抵の場合、それらのユニットは見た目を持ちます。 つまり、それぞれのユニットは ディスプレイオブジェクト になる事が多い、と言えます。

2.2.1. ディスプレイオブジェクト

画面上に表示されている構成要素は、全てが ディスプレイオブジェクト です。 starling.display パッケージには DisplayObject という抽象クラスが存在します。 このクラスはたくさんの種類のディスプレイオブジェクトに対して基本的な機能を提供します。 いくつかディスプレイオブジェクトの種類を具体的にあげるとイメージ、ムービークリップ、テキストフィールドなどがあります。

DisplayObject クラスはすべてのディスプレイオブジェクトにメソッドとプロパティを提供します。 例えば、下記プロパティ一覧は画面上のオブジェクト位置を調整するものです。

  • x, y: 現在の座標系での位置を表します。

  • width, height: オブジェクトの大きさです。 (単位はポイント)

  • scaleX, scaleY: オブジェクトの大きさを表す別の方法です。1.0 は変化なし 2.0 は倍の大きさです。

  • rotation: 元の位置からの回転角度です。(単位はラジアン).

  • skewX, skewY: 水平及び垂直方向への skew(傾き) 値です。 (単位はラジアン).

その他のプロパティは、ピクセルの表示状態を変更します。

  • blendMode: ピクセルが背面の描画内容とどのようにブレンドされるかを設定します。

  • filter: 特別な GPU プログラム(シェーダー)でオブジェクトの見た目を装飾します。例えば、"ぼけ"や"影の効果"を与えることなどが可能です。

  • mask: 指定されたエリア外の表示をカットします。

  • alpha: オブジェクトの透明度です。0 (非表示)から` 1`(完全に不透明) までの値をとります。

  • visible: 値が false の場合、完全にオブジェクト表示がなされなくなります。

これらはどのディスプレイオブジェクトでもサポートしなくてはならない基本的な機能となります。 この周辺の Starling’s API クラス継承図を見てみましょう。

class hierarchy

ツリーが主に大きく2つに枝分かれしている事に気づいたでしょうか。 片方のツリーでは、Mesh を継承した QuadImageMovieClip などが並びます。

Mesh は、Starling のレンダリングアーキテクチャーの基本的な箇所です。 実際、画面上に描画される全ての物はメッシュなのです! Stage3D は三角ポリゴン以外を画面に描画できません。そして、メッシュは三角ポリゴンを作り出すためのポイント(点)の集まり以外の何物でもありません。

一方で、DisplayObjectContainer を継承したいくつかのクラスも並んでいます。 文字通り、このクラスは他のディスプレイオブジェクトのコンテナーとして振舞います。 このクラスにより、ディスプレイオブジェクトをまとめて管理する事ができるようになる、"ディスプレイリスト" というシステムを作り出すのです。

2.2.2. ディスプレイリスト

描画されるディスプレイオブジェクトのヒエラルキー構造を ディスプレイリスト と呼びます。 Stage はディスプレイリストの一番上の親(ルート)です。

文字通りそれを "ステージ" だと考えてみましょう。ユーザー(観客)はステージに登場したオブジェクト(俳優)だけを見る事ができます。 Starling を開始すると、自動的にステージが作成されます。 ステージにはディスプレイリストに関連した全て(それが直接であれ間接であれ)が描画されます。

"関連した" と言いましたが、これは親子関係がある、という意味です。 画面上にオブジェクトを表示させるには、ステージの子とするとか、ステージに関連する DisplayObjectContainer の子とする必要があります。

Display List
Figure 5. ディスプレイオブジェクトがディスプレイリストに属しています。

最初の(そして通常唯一の)ステージの子は application root です。 これは、Starling のコンストラクタに引数として渡したクラスです。 ステージと同じように、これもまた DisplayObjectContainer となります。 _DisplayObjectContainer_は、大元の継承元であるわけです。

いくつかコンテナ要素を作ることになるでしょうが、それはまたコンテナ要素を子として持つ事ができます。そして image のようなメッシュも子として持つ事ができます。 ディスプレイリストでは、それらメッシュ要素は葉要素となります。葉要素は、子を持てない要素です。

話が抽象的すぎたかもしれません。ここで実際の例を見てみましょう。漫画の "ふきだし" を例とします。 ふきだしを作るには、背景となる画像と、内容としてテキストが必要となるでしょう。

これら2つのオブジェクトは、一体となって働きます。移動する際は、画像とテキストが一緒に移動します。 サイズの変更、拡大縮小、回転などでも同じ事が言えます。 とても軽量な DisplayObjectContainer の実装である Sprite に、その2つのオブジェクトをグルーピングさせる事でこれを実現できます。

DisplayObjectContainer と Sprite。

DisplayObjectContainerSprite は、ほぼ同じように利用されます。 2つのクラスのたった一つの違いは、片方 (DisplayObjectContainer) は抽象クラスで、(Sprite) がそうではない、という事です。 他のサブクラスは必要とせず、Sprite によってディスプレイオブジェクトをグループとしてまとめる事ができます。 SpriteDisplayObjectContainer よりも優位な別の点があります。それは、文字数が少なくタイピングするのが早いという事です。 概して、私が Sprite を好む主な理由もそれです。 他の大抵のプログラマと同じように、私も怠惰な人間なのです。

テキストとイメージをグループ化するため、Sprite を作成して、画像とテキストを子として登録します。

var sprite:Sprite = new Sprite(); (1)
var image:Image = new Image(texture);
var textField:TextField = new TextField(200, 50, "Ay caramba!");
sprite.addChild(image); (2)
sprite.addChild(textField); (3)
1 sprite を作成します。
2 Image を sprite に追加します。
3 TextField を sprite に追加します。

子を追加する順番は重要です。レイヤー構造を持つように、お互いの前面に配置されていきます。 ここでは、textFieldimage の前に配置されます。

Speech Bubble
Figure 6. ふきだしは、画像とテキストで構成されます。

オブジェクトのグループ化はできました。これで1つのオブジェクトとしてスプライトを扱う事ができます。

var numChildren:int = sprite.numChildren; (1)
var totalWidth:Number = sprite.width; (2)
sprite.x += 50; (3)
sprite.rotation = deg2rad(90); (4)
1 子の数をカウントすると、ここでは 2 が返ります。
2 widthheight は、子要素の大きさや位置を計算に入れて求められます。
3 全てを 50 ポイント右に移動します。
4 グループを 90度回転します。 (Starling では単位にラジアンを用います。)

他にも、DisplayObjectContainer には子要素を操作するためのたくさんのメソッドが存在します。

function addChild(child:DisplayObject):void;
function addChildAt(child:DisplayObject, index:int):void;
function contains(child:DisplayObject):Boolean;
function getChildAt(index:int):DisplayObject;
function getChildIndex(child:DisplayObject):int;
function removeChild(child:DisplayObject, dispose:Boolean=false):void;
function removeChildAt(index:int, dispose:Boolean=false):void;
function swapChildren(child1:DisplayObject, child2:DisplayObject):void;
function swapChildrenAt(index1:int, index2:int):void;

2.2.3. 座標系

全てのディスプレイオブジェクトは、それぞれに固有の座標系を持っています。 例えば、xy プロパティはスクリーン座標系での値ではありません。それらの値がいくつになるのかはその座標系によります。 そして座標系がどのようであるのかは、デイスプレイリストヒエラルキー上のどこが現在のポジションであるのかによります。

これをイメージとして理解するには、コルクボードにペーパーシートをピン留めする事を想像するといいでしょう。 どのシートも水平のX座標と垂直のY座標をもつ座標系を持ちます。 ピンが貫通して突き刺している場所が座標系の中心となります。

Coordinage Systems
Figure 7. 座標系はコルクボード上に留められたシートのように振る舞います。

シートを回転すると、そのシートに書かれた全ての内容(イメージやテキスト)も一緒に回転します。X座標とY座標も同様に回転します。 しかし、座標系の中心は元の場所をキープしたままです。

ピンのポジションはシートのX座標及びY座標の原点であり、親の座標系(=コルクボード)での場所を表します。

ディスプレイオブジェクトの階層構造を作成するときは、頭の中にこのコルクボードの例えを思い起こしましょう。 Starling を利用するために、理解しておかなくてはいけない大事なコンセプトです。

2.2.4. カスタムディスプレイオブジェクト

すでに述べましたが、アプリケーションを作る際に細かいロジック状のパーツに分ける事を行うかと思います。 簡単なチェスゲームを作る場合、盤とピースとポーズボタンとメッセージボックスが用意されるでしょう。 これらの構成要素は全てが画面上に表示されます。したがって、それぞれが DisplayObject から派生するクラスで表されます。 シンプルなメッセージボックスのサンプルを見てみましょう。

Message Box
Figure 8. ゲームのメッセージボックス。

メッセージボックスは、ふきだしの例の場合とほとんど同じです。背景画像とテキストに加えて、2つのボタンを持っています。 今回は、ただスプライトにオブジェクトをグループ化するだけでなく、実装を適切なクラスにカプセル化してみましょう。

ここでは、DisplayObjectContainer を継承した新しいクラスを作成しましょう。 このコンスタラクタの中で、メッセージボックスを構成する全てのパーツを生成してしまいます。

public class MessageBox extends DisplayObjectContainer
{
    [Embed(source = "background.png")]
    private static const BackgroundBmp:Class;

    [Embed(source = "button.png")]
    private static const ButtonBmp:Class;

    private var _background:Image;
    private var _textField:TextField;
    private var _yesButton:Button;
    private var _noButton:Button;

    public function MessageBox(text:String)
    {
        var bgTexture:Texture = Texture.fromEmbeddedAsset(BackgroundBmp);
        var buttonTexture:Texture = Texture.fromEmbeddedAsset(ButtonBmp);

        _background = new Image(bgTexture);
        _textField  = new TextField(100, 20, text);
        _yesButton  = new Button(buttonTexture, "yes");
        _noButton   = new Button(buttonTexture, "no");

        _yesButton.x = 10;
        _yesButton.y = 20;
        _noButton.x  = 60;
        _noButton.y  = 20;

        addChild(_background);
        addChild(_textField);
        addChild(_yesButton);
        addChild(_noButton);
    }
}

背景画像と2つのボタンとテキストを含むシンプルなクラスが出来上がりました。 これを利用するには、MessageBox のインスタンスを作成して、ディスプレイツリーに追加するだけで良いです。

var msgBox:MessageBox = new MessageBox("Really exit?");
addChild(msgBox);

追加のメソッドをクラスに追加する事もできます。( 例えば、fadeInfadeOut など。) そして、ユーザがボタンをクリックした時に動作するコードを書く事もできます。 その処理は、Starling のイベント処理の仕組みを用いて実装する事ができます。(イベントについては後のチャプターで扱います。)

2.2.5. ディスプレイオブジェクトの廃棄

ディスプレイオブジェクトを表示する必要がなくなった場合は、親からそのオブジェクトを取り除きましょう。removeFromParent() を呼び出します。 親から取り除かれても、オブジェクト自体はまだ存在するので、必要なら別のディスプレイオブジェクトの子として追加する事もできます。 ですが、実際にはそのオブジェクトはもう必要なくなっている事も多いでしょう。 そうであれば、そのオブジェクトは廃棄 (dispose) しましょう。

msgBox.removeFromParent();
msgBox.dispose();

ディスプレイオブジェクトを廃棄した場合、そのオブジェクト、またはその子孫オブジェクトが、保持していた全てのリソースを廃棄します。 これはとても重要です。なぜなら、多くの Stage3D に関連したデータは、ガベージコレクターの対象にならないからです。 もしもデータを破棄しない場合はそれがメモリに居座り続け、アプリケーションは遅かれ早かれリソース不足になりクラッシュしてしまうでしょう。

removeFromParent() は任意の Boolean型のパラメータを受け付け、取り除こうとしているデイスプレイオブジェクトを自動で破棄する指定ができるようになっています。 よって、上記のコードは1行にシンプルにまとめる事ができます。

msgBox.removeFromParent(true);

2.2.6. Pivot Point

Pivot Point は Flash のディスプレイリストには存在しない機能です。 Starling では、ディスプレイリストに2つの追加パラーメータが存在します。pivotXpivotY です。 オベジェクトの Pivot Point (originrootanchor と呼ばれる事もあります。) は、その座標系の中心を示します。

デフォルトでは、Pivot Point は (0, 0) です。画像の左上のポイントとなります。 通常、これはちょうど都合のいい値です。 しかし、他の場所に移動させたい場合もあるでしょう。例えば、画像の真ん中を基準にしてオブジェクトを回転させたい場合などです。

Pivot Point を使わないとすると、対象オブジェクトを一度スプライトで包んでやる必要があります。

var image:Image = new Image(texture);

var sprite:Sprite = new Sprite(); (1)
image.x = -image.width / 2.0;
image.y = -image.height / 2.0;
sprite.addChild(image); (2)

sprite.rotation = deg2rad(45); (3)
1 スプライトを作成します。
2 スプライトの基準座標に画像の中心点が重なるように配置します。
3 スプライトを回転すると、画像もその中心を基準として回転します。

経験のある Flashデベロッパーは、このテクニックに馴染みがあるでしょう。頻繁に必要となる方法です。 しかし、こんな簡単な事を実現するためにたくさんのコードが必要になっていると不平を言うものもいるでしょう。 Pivot Point を使えば、下記のようにコード量が減少します。

var image:Image = new Image(texture);
image.pivotX = image.width  / 2.0; (1)
image.pivotY = image.height / 2.0; (2)
image.rotation = deg2rad(45); (3)
1 pivotX を画像の水平方向の中点に移動します。
2 pivotY を画像の垂直方向の中点に移動します。
3 画像を回転します。

もはやスプライトは必要ありません! 前のチャプターで使った例えで考えてみましょう。 Pivot Point は 親に対してオブジェクトにピンで打ち付けた座標です。 上記のコードは Pivot Point をオブジェクトの中心に移動しています。

Pivot Point
Figure 9. Note Pivot Point を変更すると回転の結果も変わる。

Pivot Point をコントロールする術は学んだので、次は alignPivot() メソッドについて学びましょう。 このメソッドを使うと、たったの1行で Pivot Point をオブジェクトの真ん中に移動する事ができます。

var image:Image = new Image(texture);
image.alignPivot();
image.rotation = deg2rad(45);

便利ですよね。

もしも Pivot Point を別の場所(例えば右下など)に設定したいのなら、引数でそれを指定する事もできます。

var image:Image = new Image(texture);
image.alignPivot(Align.RIGHT, Align.BOTTOM);
image.rotation = deg2rad(45);

このコードはを画像の右下をオブジェクトの基準点として設定します。

その他

Pivot Point はいつでもそのオブジェクトのローカル座標で定義される事に注意してください。 widthheight プロパティは、その親の座標系での値となっていますが、それらとは異なります。 例えばオブジェクトが拡大縮小や回転をしている時には、予想しない結果になるかもしれません。

画像が幅 100ピクセルで、200%の大きさまで拡大 (image.scaleX = 2.0) されている事を考えてみましょう。 この時、画像は width プロパティを 200ピクセルと返します。(元の幅の2倍です。) しかし、水平の Pivot Point を中心に設定するためには、100 ではなく、それでもやはり 50 と設定します! ローカル座標での画像サイズは 100ピクセルのままで、親の座標系で大きくなっているだけなのです。

このセクションの初め、親のスプライトの真ん中に画像を配置したあたりからコードを見直すとわかりやすいかもしれません。 スプライトのスケールを変更すると何が起こるでしょう? 画像を真ん中の位置にキープするために位置を調整する必要があるでしょうか? もちろんありません。 スケールの変更はスプライトの中には何も影響を与えないのです。外から見てどう見えるかが変わるだけです。 そしてそれは Pivot Point プロパティでも同じ事です。

もしまだこの事を理解するのが難しいなら、(実際自分もそうでした)必ずオブジェクトの大きさや回転を変更する 前に Pivot Point を設定するようにしましょう。 これで問題が確実に起きなくなります。

2.3. テクスチャとイメージ

すでにイメージとテクスチャのクラスについては何回か目にしているでしょう。そしてそれらは Starling の中で最も便利なクラスです。 しかしどのように使うのでしょうか。そして両者の違いはなんでしょう?

2.3.1. テクスチャ

テクスチャは画像を表すデータでしかありません。デジタルカメラに保存されたファイルのようなものです。 他の人にファイルそのものとして見せる事はないでしょう。それは、0と1のデータの集まりでしかないのですから。 イメージビューアで閲覧する、もしくは、プリンターへファイルを送る必要があります。

テクスチャは直接 GPUのメモリに格納されます。これは、レンダリング時にとても効率的にアクセスされるます。 テクスチャは swf に埋め込んだクラスからでも、動的にロードされたファイルからでも作製する事ができます。ファイルフォーマットは以下の中から選択可能です。

PNG

もっとも多目的に使われるフォーマットです。劣化しない圧縮方式は、単色で広い領域に塗りがある画像に最適です。デフォルトのフォーマットとして推奨します。

JPG

非可逆圧縮であるため、写真、または写真のような画像では PNG よりも小さなファイルサイズとなります。しかし、 アルファチャンネルの情報を持たないため、使いにくい面もあります。大きな背景や写真を扱う場合におすすめです。

ATF

特に Stage3D 用として開発されたフォーマットです。メモリをあまり必要とせず、またロードも高速に行われます。しかし、見た目が劣化するため、全ての画像に向いているとは言えません。ATF テクスチャについては、この後のチャプターで取り扱います。( [ATF Textures] を参照。)

starling.textures.Texture クラスには、テクスチャをインスタンス化するためのたくさんのファクトリーメソッドが存在します。 いくつかを下記に羅列します。(それぞれ、引数は、わかりやすくするために省いています。)

public class Texture
{
    static function fromColor():Texture;
    static function fromBitmap():Texture;
    static function fromBitmapData():Texture;
    static function fromEmbeddedAsset():Texture;
    static function fromCamera():Texture;
    static function fromNetStream():Texture;
    static function fromTexture():Texture;
}

おそらく最も一般的な処理はビットマップからテクスチャを作成する事でしょう。 とても簡単に行う事ができます。

var bitmap:Bitmap = getBitmap();
var texture:Texture = Texture.fromBitmap(bitmap);

埋め込みビットマップからテクスチャを生成する事も同様に一般的です。 同じように対応できます。

[Embed(source="mushroom.png")] (1)
public static const Mushroom:Class;

var bitmap:Bitmap = new Mushroom(); (2)
var texture:Texture = Texture.fromBitmap(bitmap); (3)
1 ビットマップの埋め込み。
2 ビットマップをインスタンス化する。
3 ビットマップからテクスチャを生成する。

しかし、もっと簡単に処理を行う方法があります。

[Embed(source="mushroom.png")] (1)
public static const Mushroom:Class;

var texture:Texture = Texture.fromEmbeddedAsset(Mushroom); (2)
1 ビットマップの埋め込み。
2 埋め込まれた画像のクラス定義からテクスチャを生成します。
プロのテクニック

このように処理するとコード量が少ないだけでなく、メモリ消費量も少ないのです!

fromEmbeddedAsset メソッドは内部の処理がうまく働き、コンテキストのロスを防ぎます。また、fromBitmap メソッドが行うよりも、もっと効率的に働きます。 後ほどこのトピックについて触れますが、今のところは埋め込まれたビットマップからテクスチャを生成するにはこの方法が推奨される事を覚えておきましょう。

あまり目立たない Texture クラスの別の機能に、 fromTexture というメソッドがあります。 これは、テクスチャの一部の領域から別のテクスチャを生成するメソッドです。

このメソッドの素晴らしい点は、ピクセルのコピーが行われないという事実です。 代わりに、このメソッドによって作成された SubTexture は、大元のテクスチャの参照を持ちます。 これは、とても効果的に働きます。

var texture:Texture = getTexture();
var subTexture:Texture = Texture.fromTexture(
        texture, new Rectangle(10, 10, 41, 47));

この後すぐに TextureAtlas クラスについて解説します。基本的に、TextureAtlas のために上記の仕組みは存在するのです。

2.3.2. Image

テクスチャについていくつか触れましたが、画面上に表示する方法をまだ知りません。 最も簡単な方法は Image クラスか、その仲間のクラスを利用する事です。

その仲間も合わせて詳しく見てみましょう。

mesh classes
  • Mesh は、三角ポリゴンの集合です。(GPU は三角形しか描画できません。)

  • Quad は、矩形を構成する少なくとも2つの三角ポリゴンの集合です。

  • Image は、Quad に便利なコンストラクタといくつかのメソッドを追加したものです。

  • MovieClip は、時間の経過に連れて内容を切り替える Image です。

これら全てのクラスはテクスチャを扱いますが、おそらく Image クラスを最も頻繁に使う事になるでしょう。 なぜならば、矩形のテクスチャ表示は最も一般的で、それを扱うのに最も便利なのが Image クラスだからです。

QuadImage を比較してみましょう。

var texture:Texture = Texture.fromBitmap(...);

var quad:Quad = new Quad(texture.width, texture.height); (1)
quad.texture = texture;
addChild(quad);

var image:Image = new Image(texture); (2)
addChild(image);
1 quad を適切なサイズで生成し、テクスチャを紐づけます。または、
2 image を標準的なコンストラクタで生成します。

個人的には、キーを打つ回数が少ない方をいつも選択します。 内部で処理される内容は2つのクラスで全く同じです。

Texture-Mapping
Figure 10. テクスチャが quad に適用された様子。

2.3.3. 1つのテクスチャを複数のImageに適用する

重要な事なのですが、テクスチャは複数の image (mesh) に適用する事ができます。 実際、そのように対応すべきです。テクスチャは1度だけロードして、アプリケーションが動いている間はそれを再利用しましょう。

// このようにしてはいけません!
var image1:Image = new Image(Texture.fromEmbeddedAsset(Mushroom));
var image2:Image = new Image(Texture.fromEmbeddedAsset(Mushroom));
var image3:Image = new Image(Texture.fromEmbeddedAsset(Mushroom));

// 代わりに一度だけテクスチャを生成して参照を保持しましょう。
var texture:Texture = Texture.fromEmbeddedAsset(Mushroom));
var image1:Image = new Image(texture);
var image2:Image = new Image(texture);
var image3:Image = new Image(texture);

メモリ使用量のうち、テクスチャがほとんどの領域を占めるでしょう。テクスチャメモリを浪費するとすぐに全体のメモリも足りなくなってしまいます。

2.3.4. テクスチャアトラス

今までの全ての動作例では、テクスチャをそれぞれ個別にロードしました。 しかし、実際のアプリケーションでは、そのようにすべきではありません。 以下が理由です。

  • GPU のレンダリングを効率的に行うため、Staring は、メッシュをバッチ処理でまとめて処理します。しかし、バッチ処理はテクスチャが切り替わると分断してしまいます。

  • いくつかのシチュエーションでは、Stage3D はテクスチャの縦横の大きさが2の倍数のサイズである事(power-of-two)を要求します。Staring は、この制限をうまく隠蔽しますが、サイズのルールに則っていない場合、余分なメモリを消費します。

テクスチャアトラスを使えば、テクスチャの切り替えと、power-of-two の制限を共に回避する事ができます。 全てのテクスチャを1つの大きなメインテクスチャに収め、Starling が、そこから適切な領域が描画されるようにうまく取り扱います。

Texture Atlas
Figure 11. テクスチャアトラスの例。

このテクニックは、Stage3D に小さなテクスチャではなく、大きなテクスチャを扱わせ、それぞれの quad にその大きなテクスチャの一部分を割り当てて描画させる方法です。 これにより、メモリ消費量を大幅に抑える事ができ、極力無駄がない状態とする事ができます。 (他のフレームワークはこの機能を Sprite Sheets と呼びます。)

"Texture Packer" の開発チームが、スプライトシートについて良い感じの紹介動画を作成しています。 ここで視聴可能です。 What is a Sprite Sheet?
アトラスの作成

SubTexture の位置は下記のような XMLファイル で定義されます。

<TextureAtlas imagePath="atlas.png">
 <SubTexture name="moon" x="0" y="0" width="30" height="30"/>;
 <SubTexture name="jupiter" x="30" y="0" width="65" height="78"/>;
 ...
</TextureAtlas>;

このように、XML は一つの大きなテクスチャを参照し、複数の SubTextures を設定します。それらはメインテクスチャの一部領域を指定します。 ランタイムでは、SubTextures をその名前で参照する事が可能で、まるで1つの独立したテクスチャであるように扱う事ができます。

しかし、どのようにしてそれぞれのテクスチャをアトラスとしてまとめる事が可能なのでしょう? 幸い手作業で行う必要はありません。これを行うためのいくつものツールが存在します。 2つの候補を紹介しますが、Google で検索すればさらにたくさんの候補が見つかるでしょう。

  • TexturePacker は、個人的に気に入っているツールです。これほどスプライトシートを調整する事ができるツールは他に見つからないでしょう。このツールは Starling のサポートもしており、とても素晴らしい物です。( ATF テクスチャを書き出せるツールが他にあるでしょうか?)

  • Shoebox AIR で作られた無料のツールです。アトラスの生成について TexturePacker ほどのたくさんの機能を持ってはいませんが、関連機能がたくさんあります。例えば、ビットマップフォントの作成機能や、スプライトの抽出機能があります。

アトラスの利用

テクスチャアトラスファイルを手に入れる事はできました。 では、どのようにそれを使えばいいでしょう? では、テクスチャと XMLデータをソースコードに埋め込んでみましょう。

[Embed(source="atlas.xml", mimeType="application/octet-stream")] (1)
public static const AtlasXml:Class;

[Embed(source="atlas.png")] (2)
public static const AtlasTexture:Class;
1 アトラスの XML を埋め込みます。mimeType の設定を忘れないようにしましょう。
2 アトラスのテクスチャを埋め込みます。
代わりにそれぞれのファイルを URL から、または AIR としてアプリケーションが動いているのであればディスクからロードする事もできます。 この事については、AssetManager の説明の際に詳しく取り扱います。

この2つのオブジェクトがあれば、TextureAtlas インスタンスを生成する事ができ、getTexture() メソッドを通して全ての SubTexture にアクセスする事ができます。 ゲームの初期化時に一度アトラスを生成しておけば、その後はいつでも参照して利用する事ができます。

var texture:Texture = Texture.fromEmbeddedAsset(AtlasTexture); (1)
var xml:XML = XML(new AtlasXml());
var atlas:TextureAtlas = new TextureAtlas(texture, xml);

var moonTexture:Texture = atlas.getTexture("moon"); (2)
var moonImage:Image = new Image(moonTexture);
1 アトラスを生成する。
2 SubTexture を表示する。

簡単ですよね!

2.3.5. レンダーテクスチャー

RenderTexture クラスを使うと、動的なテクスチャー生成が可能となります。 どんなディスプレイオブジェクトでも描画する事ができるキャンバスを思い浮かべてみてください。

レンダーテクスチャを作成した後に、drawObject メソッドを呼び出してテクスチャにオブジェクトを描画しましょう。 テクスチャ上にそのオブジェクトがその現在の位置で描かれ、回転・拡大・アルファも、そのまま適用されます。

var renderTexture:RenderTexture = new RenderTexture(512, 512); (1)

var brush:Sprite = getBrush(); (2)
brush.x = 40;
brush.y = 120;
brush.rotation = 1.41;

renderTexture.draw(brush); (3)
1 新規に RenderTexture を、サイズ指定して作成します。(単位はポイントです。)初期値は完全に透明な状態です。
2 このサンプルでは、筆で描画したような見た目のオブジェクトを描画ソースとして使います。適切な位置と回転角度を設定します。
3 オブジェクトがテクスチャ上に現在の位置と回転角度で描画されます。

この描画処理はとても効率的に行われます。 テクスチャにオブジェクトを描画した後は、描画負荷的は通常のテクスチャと変わりません。レンダーテクスチャにどれだけのオブジェクトを描画したとしてもです。

var image:Image = new Image(renderTexture);
addChild(image); (1)
1 レンダーテクスチャも通常のテクスチャと同じように扱えます。

たくさんのオブジェクトを一度に描画するのならば、drawBundled メソッドの処理ブロック内に描画処理をまとめる事を推奨します。 このようにすると、Starling がコストが高い内部処理をスキップする事ができ、かなりのスピードアップとなります。下記はその例です。

renderTexture.drawBundled(function():void (1)
{
    for (var i:int=0; i<numDrawings; ++i)
    {
        image.rotation = (2 * Math.PI / numDrawings) * i;
        renderTexture.draw(image); (2)
    }
});
1 関数内に全ての描画命令をカプセル化して bundled drawing を有効にします。
2 関数内では、通常通り draw 命令を呼び出します。

レンダーテクスチャの一部を消去したい場合、ブレンドモードを BlendMode.ERASE に設定をする事で、ディスプレイオブジェクトを消しゴムのように使う事ができます。

brush.blendMode = BlendMode.ERASE;
renderTexture.draw(brush);

完全に全てを消し去るには、clear メソッドを使って下さい。

コンテキスト ロス

レンダーテクスチャには大きな欠点があります。それは、レンダーコンテキストが失われた際、レンダーテクスチャの内容が全てなくなってしまうという事です。 [コンテキスト ロス] は、この後のチャプターで詳しく取り扱います。一言で言えば、Stage3D はいくつかのシチュエーションにおいて、全ての描画コンテンツの内容を失う事があるという事です。 (聞こえての通り、とても嫌な感じです。)

従って、テクスチャの内容が重要であれば(例えば、それが単なる装飾目的の物でないのなら)、いくつかの努力を費やす必要があります。 このワークアラウンドについても後のチャプターで取り扱います。 この事態に急に遭遇して驚ろいてしまわないように先にお知らせしておきました。

2.4. ダイナミックテキスト

どのアプリケーションでもテキストの表示は大事な要素です。 多量の情報はイメージを通してのみ伝える事ができますが、そのうちいくらかは言葉としてランタイムで動的に描画されます。

2.4.1. テキストフィールド

Starling では、動的なテキスト(ダイナミックテキスト)を簡単に表示する事ができます。 TextField クラスについては説明するまでもないかもしれません。

var textField:TextField = new TextField(100, 20, "text"); (1)
textField.format.setTo("Arial", 12, Color.RED); (2)
textField.format.horizontalAlign = Align.RIGHT; (3)
textField.border = true; (4)
1 100 × 20 ポイントのサイズでテキストフィールドを作成し、"text" という文字を表示しています。.
2 テキストの装飾は "Arial" フォント、12 ポイント、赤、に設定されています。
3 テキストが右寄せに設定されています。
4 borderの設定は開発時に役に立ちます。テキストフィールドの周りに枠線が表示されます。
テキストの装飾は format プロパティを通して設定されます。starling.text.TextFormat のインスタンスです。

一度 TextField が生成されれば、その後はイメージや Quad と同じように扱う事ができます。

TextField Samples
Figure 12. Starling の テキスト表示機能のいくつかの例。

2.4.2. TrueType フォント

デフォルトでは、Starling はシステムフォントをテキストのレンダリングに利用します。 例えば、"Arial" フォント をテキストフィールドに適用した場合、システムにインストールされたものを使います。(もしインストールされていれば。)

しかし、そのやり方ではレンダリングクオリティがあまりよくなりません。例えば、アンチエイリアス処理が行われず。フォントがレンダリングされてしまったりします。 より良い出力を得るためには、TrueType フォントを直接 swf ファイルに埋め込みます。 下記のコードに従ってください。

[Embed(source="Arial.ttf", embedAsCFF="false", fontFamily="Arial")]
private static const Arial:Class; (1)

[Embed(source="Arial Bold.ttf", embedAsCFF="false", fontFamily="Arial", fontWeight="bold")]
private static const ArialBold:Class; (2)

[Embed(source="Arial Italic.ttf", embedAsCFF="false", fontFamily="Arial", fontStyle="italic")]
private static const ArialItalic:Class; (3)

[Embed(source="Arial.ttf", embedAsCFF="false", fontFamily="Arial", unicodeRange = "U+0020-U+007e")]
private static const ArialJustLatin:Class; (4)
1 標準的な Arial フォントを埋め込みます。embedAsCFF という項目の記述を忘れないでください。記述がないとフォントが表示されなくなります。
2 Bold と italic スタイルではそれぞれ別の埋め込み指定が必要です。fontWeight 属性に注目してください。
3 こちらには fontStyle 属性があります。
4 どの文字を含めるか指定する事もできます。この機能はフォントデータの文字の一部しか必要がない場合に便利です。 この例で指定しているのは 基本的な Latin の範囲(英数字の大文字小文字と一般的な記号と句読点)です。

フォントを埋め込んだ後は、対応したフォント名(もしくはフォントファミリー)とウェイトをテキストフィールドに設定するだけで利用する事ができます。 そのほかの設定・準備は必要ありません。

フォントのすべての文字を埋め込んだ場合、とても大きなデータ範囲指定となってしまう事に気をつけてください。 上記のようにユニコードの範囲指定をすると、その問題が軽減します。範囲の設定は、例えばこのサイトで作成する事ができます。 Unicode Range Generator.
もしもフォントの表示にかけが生じたり、正しい位置に表示されないような場合は、stage.quality 設定を確認してくみてださい。 低いクオリティ設定の場合、テキストの大きさを Flash/AIR が正しく認識できない事があります。Starling はその値を元に描画を行なっています。 (Flash のステージについての話です。そしてこの問題は TrueType フォントだけに起こります。)

2.4.3. ビットマップフォント

TrueType フォントを使うのは、あまりテキストの内容に変更がない場合に適しています。 内容が頻繁に変更される場合、もしくは TrueType フォントでは再現できないような見た目が豪華な装飾付きのフォントを表示したい場合には、ビットマップフォントを代わりに使うべきです。

ビットマップフォントは、表示したい全ての文字を含ませたテクスチャーと、 テクスチャーアトラスと同じように、テクスチャー内の文字の位置情報を保持したXML ファイルからなります。

それだけ揃えれば Starling でビットマップフォントを表示する事ができます。 それらのファイルを用意するには、いくつかの方法があります。

  • Littera, フリーで高機能なオンライン上のビットマップフォント作成ツールです。

  • Bitmap Font Generator, AngelCode から提供されている TrueType フォントからビットマップフォントを生成するツールです。Windows 専用です。

  • Glyph Designer 豪華なエフェクトをフォントに加える事もできるツールです。macOS 用。

  • bmGlyph, こちらも _macOS_専用。App Store で入手できます。

どのツールも使い方は似ています。システムフォントから使いたい文字を選択し、文字にエフェクトを施す事ができます。

フォントの書き出しにあたって、考慮すべき点があります。

  • Starling は 独自 XML 形式の ".fnt" ファイルを必要とします。

  • 必要な文字だけを正しく選択するようにしましょう。必要ないものも含めると、フォントのテクスチャーが巨大なものになります。

書き出されるのは、".fnt" ファイルと、選択した文字を含んだテクスチャーです。

Bitmap Font
Figure 13. このビットマップフォントには色がついており、シャドウの装飾も含希ています。

Starling で、フォントを表示するには、SWF にそれらのファイルを埋め込んで、テキストフィールドクラスに登録します。

[Embed(source="font.png")]
public static const FontTexture:Class;

[Embed(source="font.fnt", mimeType="application/octet-stream")]
public static const FontXml:Class;

var texture:Texture = Texture.fromEmbeddedAsset(FontTexture);
var xml:XML = XML(new FontXml());
var font:BitmapFont = new BitmapFont(texture, xml); (1)

TextField.registerCompositor(font); (2)
1 BitmapFont クラスをインスタンス化します。
2 TextField クラスにそのインスランスを登録します。

ビットマップフォントが TextField クラスに一度登録されると、その後はそのインスタンスは必要無くなります。 Starling はビットマップフォントの名前が指定された TextField を見つけると、自動的に対応したフォントを利用します。 以下のような感じです。

var textField:TextField = new TextField(100, 20, "Hello World");
textField.format.font = "fontName"; (1)
textField.format.fontSize = BitmapFont.NATIVE_SIZE; (2)
1 使いたいフォントの名前を指定をします。デフォルトでは、XML ファイル内の face 属性で、フォント名が設定された物が使われます。
2 テクスチャーを作成する時と同じフォントサイズでフォントが表示される際に、最もビットマップフォントが美しく描画されます。 手作業でそのサイズを設定する事もできますが、NATIVE_SIZE という定数指定で Starling にそれを自動で行わせた方がスマートです。
了解事項

もう一つ、知っておかなくてはいけない事があります。もしもビットマップフォントのカラー設定が単色だった場合(カラーエフェクトなしの通常の truetype フォントだった場合)、フォント形状は白一色で書き出される必要があります。 そのようになっていれば、テキストフィールドの format.color プロパティを用いてフォント表示を着色する事ができます。 (テクスチャの RGB チャンネルに乗算処理がかかるだけのシンプルな処理がなされます。)

一方で、フォントに(上の画像の例のように)カラー情報が含まれている場合、テキストフィールドの format.color プロパティは白 (Color.WHITE).に設定されるべきです。 その設定であれば、テキストフィールドのカラー設定がテクスチャカラーに影響を与える事はありません。

パフォーマンス向上のため、フォントテクスチャーもテクスチャーアトラスに含める事ができます。 1毎のテクスチャーアトラスにまとまっていれば、テキストの表示も他のイメージとともにバッチされる事があります。結果ドローコールの削減となります。
MINI フォント

Starling ライブラリには、とても軽量なフォントが最初から含まれています。 美しさで賞される事はないかもしれませんが、開発中やデバッグ用途のテキスト表示用途にはもってこいです。

BitmapFont.MINI
Figure 14. "MINI" ビットマップフォント。

"軽量" と書いた通り、それぞれの文字は 5 ピクセルの高さしか持たないのです。 実際に表示される時は、200% の大きさで拡大表示して使われます。

var textField:TextField = new TextField(100, 10, "The quick brown fox ...");
textField.format.font = BitmapFont.MINI; (1)
textField.format.fontSize = BitmapFont.NATIVE_SIZE * 2; (2)
1 MINI フォントの利用。
2 元の大きさの2倍とします。このフォントはニアレスネイバー法で拡大されるため、くっきりとした表示が保たれます!

2.5. イベントハンドリング

"イベント" というのは、プログラマが関心があるであろう出来事の事だ、と考える事ができます。

  • 例えば、モバイルアプリはデバイスが回転した時やユーザが画面をタッチした際に通知をしてくれるでしょう。

  • もっと下のレベルの話で言えば、ボタンが押された時や、ゲーム中ナイトの体力が尽きた時などの通知もあるでしょう。

このような通知の目的で、Starling のイベントシステムは存在します。

2.5.1. 詳細

イベントのシステムは Starling の構成機能のうち、とても重要な物です。 簡潔に言えば、イベントによってオブジェクトは相互にコミュニケーションを取る事ができるようになります。

ここで、同じ目的で "メソッド" も使う事ができるじゃないか、と思うかもしれません。 確かにそれも正しいです。ですが、メソッドは1つの方向に動作するものです。例えば、内部に ボタン を持つ メッセージボックス を考えてみましょう。

messagebox calls button

メッセージボックスはボタンを保持しているので、そのメソッドとプロパティを利用する事ができます。

public class MessageBox extends DisplayObjectContainer
{
    private var _yesButton:Button;

    private function disableButton():void
    {
        _yesButton.enabled = false; (1)
    }
}
1 プロパティを通してボタンとやりとりをする。

ところで ボタン インスタンスは、メッセージボックスの参照を持っていません。 ボタンはどんなコンポーネントからでも使う事ができ、メッセージボックスには全く依存していません。 これは一般的に良い事です。なぜなら、そうでない場合ではメッセージボックスの中でしかボタンを使う事ができなくなってしまうので。

ボタンはある目的、押されたならば、それを誰かに伝える、そのために存在しています。 つまり、ボタンはオーナーが誰であれ、そこに対してメッセージを送信する事が必要なのです。

button dispatches to messagebox

2.5.2. イベントとイベントディスパッチャー

一つ白状しなくてはいけません。Starling のディスプレイオブジェクトのクラスの継承図を提示した時、実際はベースクラスを省略していたのです。それは、EventDispatcher_ と言うクラスです。

class hierarchy with eventdispatcher

このクラスは、全てのディスプレイオブジェクトにイベントの発行とイベントの取り扱いをする術を与えます。 EventDispatcher を全てのディスプレイオブジェクトが継承しているのは、偶然ではありません。 Starling では、イベントのシステムがディスプレイリストと強く統合しています。 これによっていくつか有利な点があります。(後ほど取り扱います。)

実際の例を見ながらを続けます。

犬を飼っている事を考えてみます。その犬はアインシュタインと呼びましょう。 1日に何回か、アインシュタイン は、吠えることによって外へ散歩に行きたいとう表現をします。

class Dog extends Sprite
{
    function advanceTime():void
    {
        if (timeToPee)
        {
            var event:Event = new Event("bark"); (1)
            dispatchEvent(event); (2)
        }
    }
}

var einstein:Dog = new Dog();
einstein.addEventListener("bark", onBark); (3)

function onBark(event:Event):void (4)
{
    einstein.walk();
}
1 bark と言う文字列がイベントを定義します。Event インスタンス内にカプセル化されます。
2 全ての bark イベントの購読者に対して event を発行します。
3 addEventListener メソッドを使って、購読を開始しています。最初の引数はイベントのタイプで、2つ目はリスナ関数です。
4 犬が吠えた際、このメソッドがイベントパラメータとともに呼美出されます。

ここで、イベントシステムの3つの構成要素をそれぞれ確認しました。

  • イベント情報は イベント クラス(またはそのサブクラス)のインスタンス内にカプセル化されます。

  • イベントを発行するには、発行元が イベント インスタンスとともに dispatchEvent メソッドを実行します。

  • イベントを購読するには、購読者が addEventListener を呼び出します。その際、どのイベントのタイプに関心があるかと、イベント発行時に呼び出すメソッドを指定します。

時には、あなたのおばさんも犬の世話をするでしょう。 その際、あなたは犬が吠えても気にしなくて良いのです。おばさんも何のための購読をしたのか知っています。 従って、イベントのリスナ登録は解除してしまいましょう。これは犬の飼い主だけでなく、Starling の開発者にも有効な習慣です。

einstein.removeEventListener("bark", onBark); (1)
einstein.removeEventListeners("bark"); (2)
1 これにより onBark リスナが解除されます。
2 こちらの方法では、このタイプのイベントの全てのリスナを解除します。 bark イベントについての解説は上のようになります。 さて、アインシュタイン は他にもいくつかのタイプのイベントを発行する事ができます。例えば、遠吠え(howl) や 唸り(growl) などです。

このような文字列は静的な定数として定義しておくのが良いでしょう。Dogクラス内の定数として、などです。

class Dog extends Sprite
{
    public static const BARK:String = "bark";
    public static const HOWL:String = "howl";
    public static const GROWL:String = "growl";
}

einstein.addEventListener(Dog.GROWL, burglar.escape);
einstein.addEventListener(Dog.HOWL, neighbor.complain);

Starling はあらかじめ、便利ないくつかのイベントタイプを Event クラスに定義しています。 以下は、メジャーな物の一部です。

  • Event.TRIGGERED: ボタンが押された

  • Event.ADDED: コンテナにディスプレイオブジェクトが追加された

  • Event.ADDED_TO_STAGE: ステージに関連するコンテナにディスプレイオブジェクトが追加された

  • Event.REMOVED: コンテナからディスプレイオブジェクトが取り除かれた

  • Event.REMOVED_FROM_STAGE: ディスプレイオブジェクトがステージへの接続を失った

  • Event.ENTER_FRAME: 時間が経って、新しいフレームが描画された(詳細は後に取り扱います)

  • Event.COMPLETE: 何か(ムービークリップの再生など)が終了した

2.5.3. カスタムイベント

犬と言う生き物は色々な理由で吠えます。 もしかすると、アインシュタインは吠える事で、おしっこをしたい、または、お腹が減った、と伝えているのかもしれません。 猫に対して出て行けと伝えるのにも使われるでしょう。

おそらく、犬好きの人々はそれらを聞き分ける事ができます。(自分は猫好きなのでできないのですが。) これが、賢い犬が吠える意図の情報を イベント に設定する理由です。

public class BarkEvent extends Event
{
    public static const BARK:String; (1)

    private var _reason:String; (2)

    public function BarkEvent(type:String, reason:String, bubbles:Boolean=false)
    {
        super(type, bubbles); (3)
        _reason = reason;
    }

    public function get reason():Boolean { return _reason; } (4)
}
1 カスタムイベントクラスにイベントのタイプを定義するのは良い慣習です。
2 カスタムイベントを作成した理由を保持する変数。
3 コンストラクタでの superクラス呼び出し。 (bubbles の意味はこの後すぐに解説します。)
4 プロパティ経由で 理由 を取得できるようにします。

これで、犬が吠える際、このカスタムクラスを使う事ができるようになりました。

class Dog extends Sprite
{
    function advanceTime():void
    {
        var reason:String = this.hungry ? "hungry" : "pee";
        var event:BarkEvent = new BarkEvent(BarkEvent.BARK, reason);
        dispatchEvent(event);
    }
}

var einstein:Dog = new Dog();
einstein.addEventListener("bark", onBark);

function onBark(event:BarkEvent):void (1)
{
    if (event.reason == "hungry") (2)
        einstein.feed();
    else
        einstein.walk();
}
1 引数の型が BarkEvent になっています。
2 吠えた理由に応じて、何をしてやるのか決定します。

このようにして、BarkEvent の取り扱いに慣れている犬のオーナーは、彼らの犬についてよく知る事ができるのです。 まったく素晴らしい技能ですね!

2.5.4. もっと単純に

ただ reason をイベントで受け渡すだけで、追加のクラスを作る事がやや面倒である事には同意します。 イベントの1つの情報にしか関心がない事はよくあります。 そのようにシンプルな状況で新たにクラスを作るのは能率が悪いように思えます。

実は、サブクラスを作る必要はそんなにないのです。 代わりに、Event クラスの data プロパティを任意の情報を持たせる用途に使う事ができます。(data の型は Object です。)

BarkEvent の実装を下記のように変更しましょう。

// イベントを作成して発行する
var event:Event = new Event(Dog.BARK);
event.data = "hungry"; (1)
dispatchEvent(event);

// イベントを購読する
einstein.addEventListener(Dog.BARK, onBark);
function onBark(event:Event):void
{
    trace("reason: " + event.data as String); (2)
}
1 吠える 理由data プロパティに設定します。
2 理由を取り出すには、dataString 型に変換します。

このアプローチの悪い点は、"型安全" ではなくなる事です。 しかし、私のとしては、新たにクラスを実装するよりも むしろ String に型を変換することを好みます。

さらに、Starling はこのコードをさらに単純化するショートカットをいくつか持っています。 下記を見てください。

// イベントを作成して発行する
dispatchEventWith(Dog.BARK, false, "hungry"); (1)

// イベントを購読する
einstein.addEventListener(Dog.BARK, onBark);
function onBark(event:Event, reason:String):void
{
    trace("reason: " + reason); (2)
}
1 Dog.BARK のイベントを作成して、data プロパティを設定して、イベントを発行する、これを全て一行で行います。
2 data プロパティは(任意の)2番目の引数として、イベントハンドラに渡されます。

このようにして一般的なコード記述をたくさん取り除く事ができました! もちろん、カスタム data を必要としない場合でも、この簡易な記述を使う事ができます。 最も単純なイベントの取り扱い例を見てみましょう。

// イベントを作成して発行する
dispatchEventWith(Dog.HOWL); (1)

// イベントを購読する
dog.addEventListener(Dog.HOWL, onHowl);
function onHowl():void (2)
{
    trace("hoooh!");
}
1 イベントのタイプのみを指定してイベントを発行します。
2 この関数は引数を持っていません。必要としないのなら、そもそも指定する必要もないのです!
単純化された dispatchEventWith 呼び出しは、メモリーをより効率的に使います。Starling は Event オブジェクトを裏で使いまわすからです。

2.5.5. バブリング

一つ前のサンプルでは、addEventListener メソッドを通じてイベントの発行者 (dispatcher) と購読者 (dispatcher) は直接繋がっていました。 しかし、時折それは望んでいる状態ではない事があります。

複雑なゲームを作り、深い構造を持ったディスプレイリストができていたとします。 そのリストのどこかで、アインシュタイン (ゲームの主人公です)が罠にはまったとします。 苦しんで遠吠えをし、息を引き取りながら GAME_OVER イベントを発行します。

この情報はディスプレイリストのはるか上層、ゲームのルートクラスで必要とされています。 一般的にこのようなイベントは、マップをリセットし、犬を最後のセーブポイントの位置まで戻します。 このイベントを犬の階層からゲームのルートまで、たくさんのディスプレイオブジェクトを経由して受け渡していくのはとても面倒です。

よくある要望です。そしてそれが バブリング をイベントがサポートする理由です。

ディスプレイリストを実際の木と想像してみましょう。180度ひっくり返せば、幹の部分が上方にきます。 幹はステージに相当し、葉は各々のディスプレイオブジェクトに相当します。 1つの葉がバブリングイベントを作成したとすると、ソーダ水の中の泡のようにイベントが上方に移動していきます。枝から枝(親から親)へ、幹にたどり着くまで移動します。

Bubbling
Figure 15. イベントのバブルがステージまで登っていく。

途中に位置するディスプレイオブジェクトはこのイベントを購読する事ができます。 イベントを受けたタイミングで、バブルを弾いて移動をストップする事もできます。そのような振る舞いをさせるには、イベントの bubbles プロパティを true に設定します。。

// 一般的なアプローチ
var event:Event = new Event("gameOver", true); (1)
dispatchEvent(event);

// 1行で記述するやり方
dispatchEventWith("gameOver", true); (2)
1 バブリングを有効とするため、イベントのコンストラクタの第二引数に `true`を渡します。
2 代わりに dispatchEventWith に同じパラメータを渡す事もできます。

イベントはその経路上であればどこでも、購読する事ができます。犬の親の階層でもステージでもそれが可能です。

dog.addEventListener("gameOver", onGameOver);
dog.parent.addEventListener("gameOver", onGameOver);
stage.addEventListener("gameOver", onGameOver);

この機能はたくさんのシチュエーションで役に立ちます。特にマウスやスクリーンのタッチなど扱ったユーザ操作で有効となるでしょう。

2.5.6. タッチイベント

一般的なデスクトップコンピューターがマウスでコントロールされるのに対し、ほとんどのモバイルデバイス(スマートフォンやタブレット)では指でコントロールされます。

Starling はそれらのインプットを統合して、TouchEvent というポインティングデバイス入力として扱います。 これにより、ゲームがどのような入力で実際はコントロールされているのかを気にする必要がなくなります。 それがマウスであろうとも、スタイラスペンでも、指であっても、です。Starling はそれらをタッチイベントとして扱います。

大事なことをまず言います。マルチタッチを扱いたい場合は、Starling のインスタンスを作成する前に間違いなくその設定をを有効にしておかなくてはいけません。

Starling.multitouchEnabled = true;

var starling:Starling = new Starling(Game, stage);
starling.simulateMultitouch = true;

simulateMultitouch プロパティに注目してください。 これを有効とすると、開発コンピュータ上のマウスを使ってマルチタッチをシミュレートする事ができるようになります。 キーボードの [Ctrl] か [Cmd] キーを(Windows か Mac かで異なります。) 押しながら、マウスを動かしてみてください。 追加で [Shift] キーを押すと、カーソルが動いている向きを反対に変更する事ができます。

Simulate Multitouch
Figure 16. マウスとキーボードでマルチタッチをシュミレートする様子。

シュミレートであれ実際のものであれ、タッチイベントを扱うには、TouchEvent.TOUCH イベントを購読する必要があります。

sprite.addEventListener(TouchEvent.TOUCH, onTouch);

Sprite インスタンスに対してリスナ登録を行った事に気づいたでしょうか? Sprite はコンテナクラスです。それ自身にはタッチ判定となる領域を持っていません。 これでタッチが可能なのでしょうか?

答えはイエスです。バブリング の機構のおかげでそれが可能となります。

これを理解するには、先ほど作成した MessageBox クラスに戻って考えてみましょう。 テキストフィールドをユーザがクリックした際、テキストフィールドに対してタッチイベントの購読をしている者には通知が飛びます。(ここまでは明白です。) しかし、メッセージボックスに対してイベントを購読している者にも同じ通知が行きます。テキストフィールドはメッセージボックスの一部なのです。 ステージに対してタッチイベントの購読を行っている者にも通知が行くはずです。 タッチが、ディスプレイリスト内のどのオブジェクトが対象とったとしても、それはステージをタッチしたと言う事にもなります!

バブリングイベントの仕組みのおかげで、Starling は簡単にこのインタラクションについて理解する事ができます。 スクリーンのタッチを検知した時、どの 葉要素 がタッチされたのかが判定されます。 タッチイベントが作成され、そのオブジェクトから発行されます。 そこから、ディスプレイリストに沿ってイベントは上層へと登って行きます。

タッチフェーズ

実際のタッチイベントのリスナ関数を確認してみましょう。

private function onTouch(event:TouchEvent):void
{
    var touch:Touch = event.getTouch(this, TouchPhase.BEGAN);
    if (touch)
    {
        var localPos:Point = touch.getLocation(this);
        trace("Touched object at position: " + localPos);
    }
}

上記は最も基本的な例です。スクリーンのタッチを検知して、その座標をログ出力します。 getTouch メソッドは、TouchEvent クラスによって提供され、これは、関心があるタッチを取り出すのに役立ちます。

Touch クラスは、シングルタッチの全ての情報をカプセル化します。どこでそれが起こって、前のフレームではその場所がどこであったか、などが情報として含まれます。

getTouch メソッドへの最初の引数として、ここでは this を渡しました。 this か その子要素で発生したタッチいくつかについての情報を得る事ができます。

タッチは生存期間中、その フェーズ が変化して行きます。

TouchPhase.HOVER

マウスのボタンが押されない状態でオブジェクト上をカーソルが通過している際のフェーズです。(マウスインプットのみ)

TouchPhase.BEGAN

指が画面に触れた、またはマウスボタンが押されたタイミングのフェーズです。

TouchPhase.MOVED

指が画面上で移動した際、または、マウスがボタンが押されながら移動している際のフェーズです。

TouchPhase.STATIONARY

指、またはマウスが押されたまま、前のフレームから移動がない場合のフェーズです。 TouchPhase.ENDED::指が画面から離れた、または、マウスボタンが離された際のフェーズです。

上のサンプル(BEGAN フェーズを取り扱っている)では、指が画面に触れたタイミングでログ出力を行います。 しかし、指が葉要素に沿って、またはスクリーン上を移動する際は何もログに表示しません。

マルチタッチ

上記のサンプルではシングルタッチ(指一本だけのタッチ)のみを購読していましたが、マルチタッチの場合もほぼ同じように取り扱う事ができます。 たった一つの違いは、代わりに touchEvent.getTouches 命令を呼び出すと言う事だけです。("Touch" が複数形になっています。)

var touches:Vector.<Touch> = event.getTouches(this, TouchPhase.MOVED);

if (touches.length == 1)
{
    // one finger touching (or mouse input)
    var touch:Touch = touches[0];
    var movement:Point = touch.getMovement(this);
}
else if (touches.length >= 2)
{
    // two or more fingers touching
    var touch1:Touch = touches[0];
    var touch2:Touch = touches[1];
    // ...
}

getTouches メソッドは、タッチのベクターを返します。 そのベクターの長さと中身を元に処理を行う事が出来るのです。

  • 最初のif節では、指一本だけがスクリーンに触れています。getMovement メソッドを通して、例えばドラッグジェスチャーのような機能を実装可能です。

  • else節では、2本(以上)の指がスクリーンに触れています。2つのタッチオブジェクトにアクセスして、ピンチジェスチャーのような機能を実装可能です。

マルチタッチのデモアプリが、Starling のダウンロード内容の一部として含まれています。 TouchSheet クラスが マルチタッチ シーンで利用されており、 タッチハンドラによってスプライトをドラッグ、回転、スケーリングする事が出来るサンプルの実装となっています。
マウスアウトとエンドホバー

マウスが(マウスボタンが押されていない状態で)オブジェクトから離れる時の特殊なケースについて考えます。 (これはタッチは関係なく、マウス入力だけに当てはまる考察です。)

ホバーしているオブジェクトが別のものに切り替わった時、TouchEvent が前のホバー対象に対して発行され、もうホバーがされていない事を通知します。 そして、この際には、getTouch メソッドが null を返します。 どのような出来事がマウスアウトイベントと呼ばれるの理解できたでしょうか。

var touch:Touch = event.getTouch(this);
if (touch == null)
    resetButton();

2.6. アニメーション

アニメーションはゲームの重要な要素であるというだけでなく、最近ではビジネス向けのアプリであってもスムーズなエフェクトがある事を期待されています。 適切に利用されたアニメーションは、直感的で相互的なインターフェースを提供するために大いに役立ちます。 そのような需要を満たせるように、Starling は柔軟なアニメーションエンジンを提供します。

アニメーションには2つのタイプがあります。

  • 一方のタイプのアニメーションはとても動的なので、前もって何が起こるかを知る事は出来ないとわかります。 自キャラに向かってやって来る敵キャラについて考えてみてください。毎フレーム移動の方向とスピードを状況に合わせて更新しなくてはいけません。 物理演算もそうです。力の追加やあたり判定の存在が、全てを変えてしまいます。

  • 他方で、詳細な計画通りに動作左折事ができるアニメーションもあります。 最初の時点で、この後何がどう起こるのかを知っておく事ができるのです。 消えていくメッセージボックスや、シーンのトランジションアニメーションなどを考えてみて下さい。

以下、その2つのタイプのアニメーションを詳しく見ていきます。

2.6.1. EnterFrame イベント

いくつかのゲームエンジンでは、run-loop というものが実装されています。 これは終わりのない無限ループ処理で、定期的にシーン上の全ての物体に対して更新処理を行います。

Starling には、ディスプレイリストの構造的な理由から、run loop 処理が適していません。 ゲームをたくさんのディスプレイオブジェクトで構成しているかと思いますが、それぞれのディスプレイオブジェクト自身が時間の経過とともに何をすればいいのか把握しているべきです。

そこが、EnterFrameEvent がポイントとなる部分です。EnterFrameEvent により、時間経過とともにディスプレイオブジェクトが自分自身を更新する事が出来るようになります。 ディスプレイリストを構成する全てのディスプレイオブジェクトに 毎フレーム EnterFrame イベントが送信されます。 下記はそのイベントの利用例です。

public function CustomObject()
{
    addEventListener(Event.ENTER_FRAME, onEnterFrame); (1)
}

private function onEnterFrame(event:Event, passedTime:Number):void (2)
{
    trace("Time passed since last frame: " + passedTime);
    bird.advanceTime(passedTime);
}
1 どこでも EnterFrame に対してりすな登録を行うことできます。コンストラクタに書くのも良い選択です。
2 イベントリスナーでは、このようにイベントを受け取ります。

onEnterFrame メソッドは1フレームに1度だけ呼び出されます。 その際、前のフレームから経過した時間を引数として受け取る事ができます。 その情報を元にして、敵キャラや太陽の位置や、その他必要な処理を行う事ができるのです。

このイベントには、隠れた強力な機能があります。それは、異なる処理をその時々で行えるという事です。 現在のゲームの状況に合わせて柔軟な対応する事ができます。 例えば、シンプルな敵AIとして、敵キャラをプレイヤーの方向に移動させる事もできるのです。

2.6.2. トゥイーン

次は、事前に手続きして再生するアニメーションについてです。

とても一般的ですが、移動、拡大縮小、フェード、といった類いのアニメーションがあります。 Starling では、これらのアニメーションの取り組み方はシンプルです。そして同時にとても柔軟でもあります。 基本的にどのオブジェクトのどのプロパティであっても、それが数値 (Numberintuint) で表されるものであれば、アニメーションさせる事ができます。 それらのアニメーションは Tween と呼ばれるオブジェクトにより表されます。

"Tween" の語源は手書きのアニメーションから来ています。 チーフイラストレーターが動画の大事なコマだけを描き、残りのつなぎ部分 (between) はチームの別の担当者が描くのです。
Soccer Tween
Figure 17. tween 中のそれぞれのフレーム。

理屈はこのくらいにして、実際の例を見てみましょう。

var tween:Tween = new Tween(ball, 0.5);

tween.animate("x", 20);
tween.animate("scale", 2.0);
tween.animate("alpha", 0.0);

この tween では 'ボール' の位置が X=20 、サイズは倍、透明度は 0 に変化するように設定されています。 これらのアニメーションは、0.5 秒をかけて、一斉に進行します。 パラメータそれぞれのトゥイーン開始時の初期値は、単純に現在のプロパティ値となります。

この例では、以下のような事がわかります。

  • オブジェクトの任意の値をアニメーションさせる事が可能です。

  • いくつかのアニメーションを1つのオブジェクトにまとめて実行させる事ができます。

ところで、拡大縮小とフェードと移動のアニメーションは、しばしば実行されるので、Tween クラスはそれらに専用のメソッドを用意しています。 同じ処理は下記のようにも書けます。

tween.moveTo(20, 0); // "x" and "y" のアニメーション
tween.scaleTo(2);    // "scale" のアニメーション
tween.fadeTo(0);     // "alpha" のアニメーション

tween 処理の面白いところは、アニメーションの進み具合をカスタマイズできると言う事です。 例えば、初めはゆっくり動き、だんだん早くなる、と言うような設定にする事ができます。 トランジションのタイプを指定する事で、それが可能です。

Transitions
Figure 18. 指定可能なトランジションタイプ。 デフォルトの linear のグラフは省略されています。

下記は、トランジション指定をどのように行うかと、tween が提供するその他いくつかの機能についての解説です。

var tween:Tween = new Tween(ball, 0.5, Transitions.EASE_IN); (1)
tween.onStart    = function():void { /* ... */ };
tween.onUpdate   = function():void { /* ... */ }; (2)
tween.onComplete = function():void { /* ... */ };
tween.delay = 2; (3)
tween.repeatCount = 3; (4)
tween.reverse = true;
tween.nextTween = explode; (5)
1 3番目の引数でトランジションのタイプを指定します。
2 これらのコールバックはそれぞれ、トゥイーンが始まった時、トゥイーン中の毎フレーム、トゥイーン終了後に呼び出されます。
3 アニメーションの開始前に2秒間待ちます。
4 トゥイーンを 3回繰り返します。オプション指定で、行ったり来たりを繰り返すヨーヨースタイルに設定しています。 もしも repeatCount をゼロに設定した場合は、トゥイーンが永久に繰り返されます。
5 最初のトゥイーンが終了した後に、別のトゥイーン処理を開始します。

さて、これでトゥイーンの設定を終えたのですが、まだ何も起きません。 tween オブジェクトはアニメーションを設定しますが、実行はしないのです。

tween の advanceTime メソッドを手動で実行する事で、アニメーションを再生する事ができます。

ball.x = 0;
tween = new Tween(ball, 1.0);
tween.animate("x", 100);

tween.advanceTime(0.25); // -> ball.x =  25
tween.advanceTime(0.25); // -> ball.x =  50
tween.advanceTime(0.25); // -> ball.x =  75
tween.advanceTime(0.25); // -> ball.x = 100

このやり方で確かに動きますが、これではちょっと面倒ですね。 ENTER_FRAME イベントハンドラ内で advanceTime を呼ぶこともできますが、 それでもやはり、アニメーションの数が増えてくると手間になってしまうでしょう。

心配しないでください。それをなんとかしてくれる "ヤツ" を知っています。 そのような処理の取り扱いに慣れているヤツです。

2.6.3. Juggler

ジャグラー (Juggler)というクラスは、アニメーションするオブジェクトをいくつでも登録でき、アニメーションを再生してくれます。 実際の曲芸師(ジャグラー)のように、情熱をひたすらに遂行します。つまり、登録されたオブジェクトの advanceTime メソッドを呼び出し続けてくれます。 Starling インスタンスには、デフォルトで利用できるジャグラーが1つ用意されています。 アニメーションを実行する最も簡単な方法は、下記の1行です。アニメーション(トゥイーン) をデフォルトのジャグラーに登録するだけで終わりです。

Starling.juggler.add(tween);

トゥイーンが終了すると、すぐにアニメーションの登録が自動的に破棄されます。 たいていの場合、このシンプルなやり方で十分でしょう。

しかし別のケースでは、もっと細かく挙動をコントロールしたい事もあります。 例えば、ステージ上にメイン処理を行うゲームエリアがあるとします。 ユーザーがポーズボタンを押した時には、ゲームを停止させて、メッセージボックスをアニメーションとともに表示させ、 同時にゲームに戻るメニューもユーザーに提供するかもしれません。

この場合、ゲーム部分は完全に停止するべきです。内部のアニメーションは進行させません。 しかし問題は、メッセージボックスもアニメーションを行うので、デフォルトのジャグラーを停止する事は出来ないという事です。

そのようなケースでは、ゲームエリアに固有のジャグラーを提供するのが良いでしょう。 ゲーム停止ボタンを押されるとすぐに、このジャグラーのアニメーション全てを停止しましょう。 ゲームは現在の状態のまま停止しますが、メッセージボックスのアニメーション(デフォルトのジャグラーか、また別のジャグラーを使っている)は普通に動きます。

カスタムジャグラーを使う場合、その advanceTime メソッドを毎フレーム呼んでやります。 下記のようにジャグラー制御するのが良いでしょう。

public class Game (1)
{
    private var _gameArea:GameArea;

    private function onEnterFrame(event:Event, passedTime:Number):void
    {
        if (activeMsgBox)
            trace("waiting for user input");
        else
            _gameArea.advanceTime(passedTime); (2)
    }
}

public class GameArea
{
    private var _juggler:Juggler; (3)

    public function advanceTime(passedTime:Number):void
    {
        _juggler.advanceTime(passedTime); (4)
    }
}
1 ゲームのルートクラスで、Event.ENTER_FRAME イベントをリッスンします。
2 メッセージボックスが存在する際は、gameArea のみが進行するようにします。
3 GameArea は自身に専用のジャグラーを持っています。ゲーム中のアニメーション全てをそれが制御します。
4 ジャグラーは Game により呼ばれた advanceTime メソッド内で、進行させられます。

このやり方で、適切にゲームとメッセージボックスのアニメーションを分けることができます。

しかし、ジャグラーが扱うことができるのは、トゥイーンだけではありません。 IAnimatable インターフェースを実装したクラスであれば、なんでもジャグラーに登録できるのです。 このインターフェースには1つのメソッドしか定義されていません。

function advanceTime(time:Number):void;

このメソッドを実装する事で、例えば簡単なムービークリップのようなクラスを自身で作る事もできます。 advanceTime メソッド内で、定期的に表示されているテクスチャーを切り替えればいいのです。 ムービークリップを再生するには、ただジャグラーに登録するでけで良いです。

さて、ここで疑問が残っているかもしれません。いつどのようにしてジャグラーから登録されたオブジェクトが取り除かれるのだろう?、という。

アニメーションの停止

トゥイーンが終了した際、ジャグラーから自動的に登録が取り除かれます。 もしもアニメーションを中断したい場合は、ジャグラーからトゥイーンをシンプルに取り除いてください。

ボールをアニメーションさせるトゥイーンを作成して、ジャグラーに登録したとします。

tween:Tween = new Tween(ball, 1.5);
tween.moveTo(x, y);
Starling.juggler.add(tween);

このアニメーションを中断させる方法はいくつか存在します。 状況に応じて、一番適した方法を選んでください。

var animID:uint = juggler.add(tween);

Starling.juggler.remove(tween); (1)
Starling.juggler.removeTweens(ball); (2)
Starling.juggler.removeByID(animID); (3)
Starling.juggler.purge(); (4)
1 トゥイーンを直接削除します。この方法は、どんな IAnimatable に対しても有効です。
2 ボールに影響するすべてのトゥイーンを削除します。これはトゥイーンだけに有効です。
3 ID指定でトゥイーンを削除します。Tween インスタンスへアクセスできないような場合に有効です。
4 すべてを停止したい場合は、ジャグラーを purge します。

purge メソッドに関しては若干の注意をはらってください。 デフォルトのジャグラーに対してこのメソッドを実行した場合、別の場所のアニメーションが停止してしまい、ゲーム自体が停止してしまうかもしれません。 purge メソッドを使うのは、カスタムジャグラーだけにする事を推奨します。

アニメーション登録の自動削除

アニメーションが終了した際に、どのようにして Tween クラスが、ジャグラーからトゥイーンを削除するのか、疑問に思ったかもしれません。 これは、REMOVE_FROM_JUGGLER イベントを受け取ったタイミングで行われています。

IAnimatable を実装したオブジェクトは、このイベントを投げる事ができます。 ジャグラーはそのイベントを受け取り、それに応じてそのオブジェクトを登録から削除します。

public class MyAnimation extends EventDispatcher implements IAnimatable
{
    public function stop():void
    {
        dispatchEventWith(Event.REMOVE_FROM_JUGGLER);
    }
}
複数 Tween を1コマンドで実行する

ジャグラーとトゥイーンが分離されて扱える事はとてもパワフルですが、簡単なアニメーションの登録にたくさんのコードを記述しなくてはいけない状況もあるでしょう。 そこで、ジャグラー側には1コマンドでトゥイーンを実行できる便利なメソッドが用意されています。 下記がサンプルです。

juggler.tween(msgBox, 0.5, {
   transition: Transitions.EASE_IN,
   onComplete: function():void { button.enabled = true; },
   x: 300,
   rotation: deg2rad(90)
});

このコードでは、msgBox オブジェクトにトゥイーンを設定しています。0.5秒間で x座標 及び rotation を変更するものです。 トゥイーン自体のプロパティと同じように、{} でくくっった引数で、オブジェクトにアニメーションさせたい項目が並んでいる事がわかるかと思います。 時間を節約する事ができますね。

2.6.4. 遅延実行

ここまでで、技術的な面では Starling がサポートするすべてのアニメーションをカバーし終えました。 しかし、このトピックに深く関係する、また別のコンセプトの機能があります。

Einstein (犬のキャラです) が、イベントシステムを紹介した事を覚えていますか? 彼を最後に見た際は、ライフゲージが0であり、ゲームオーバーがコールされる直前でした。 しかし待ってください。すぐにゲームオーバーメソッドを実行しないで欲しいのです。それでは唐突にゲームが終わってしまいます。 代わりに、そうですね、2秒ほど後に実行してください。(それはプレイヤーが事態が変化した事を把握するのに十分な時間です。)

この遅延処理を実装するには、ネイティブの TimersetTimeout メソッドを使うことができます。 しかし、ここでジャグラーを利用する事もできます。そしてジャグラーを使うやり方の方が、全てを制御する事ができるという大きな利点があります。

プレイヤーがその2秒が経たたないうちにポーズボタンを押すような可能性を考えると、その利点が明らかになるでしょう。 その際は、ゲームエリアのアニメーションを一時的に止めるだけでなく、この gameOver 呼び出しのタイミングも一時停止させておきたいはずです。

下記のように処理を実行しましょう。

juggler.delayCall(gameOver, 2);

gameOver 関数は今から2秒後(もしジャグラーが一時停止されていればもっと後に)に実行されます。 このメソッドには引数を渡す事も可能です。代わりにイベントを実行したい場合は下記のようになります。

juggler.delayCall(dispatchEventWith, 2, "gameOver");

delayed call を使うのとは別の便利な機能として、繰り返しの処理実行を行う、という機能があります。 3秒に1回敵キャラクターを生み出す事を考えてみてださい。

juggler.repeatCall(spawnEnemy, 3);

処理の裏側では、delayCallrepeatCallDelayedCall というタイプのオブジェクトを生成します。 juggler.tween メソッドが、トゥイーンを簡単に実行するためのメソッドだったのと同じように、それら2つのメソッドも delayed コール を作成するためのショートカットなのです。

遅延処理をキャンセルした場合は、下記のうちどちらかの命令を実行してください。

var animID:uint = juggler.delayCall(gameOver, 2);

juggler.removeByID(animID);
juggler.removeDelayedCalls(gameOver);

2.6.5. ムービークリップ

Mesh 周辺のクラス図を見た際に、すでに MovieClip クラスの存在に気づいていたかもしれません。 MovieClipImage のサブクラスで、テクスチャーを時間経過に沿って変更するだけのクラスです。 Starling 上でのアニメーションGIFだと理解しましょう。

テクスチャーの獲得

ムービークリップのすべての画像は1枚のアトラスから読み込む事が推奨されます。それらの画像は、すべて同じ大きさである必要があります。 (もし同じサイズでない場合、1コマ目のサイズに変形されて表示されます。) Adobe Animate のようなツールを使って、そのようなアニメーションを用意する事ができます。Animate からは直接 Starling 用のテクスチャーアトラスデータを書き出す事ができます。

下記は、ムービークリップ用のフレームを持つテクスチャーアトラスデータのサンプルです。 まずはXML内に記述されている、フレーム座標の設定を確認しましょう。 どのフレームも flight_ の名前で始まる事に注目してください。

<TextureAtlas imagePath="atlas.png">
    <SubTexture name="flight_00" x="0"   y="0" width="50" height="50" />
    <SubTexture name="flight_01" x="50"  y="0" width="50" height="50" />
    <SubTexture name="flight_02" x="100" y="0" width="50" height="50" />
    <SubTexture name="flight_03" x="150" y="0" width="50" height="50" />
    <!-- ... -->
</TextureAtlas>

下記が対応するテクスチャです。

Flight Animation
Figure 19. ムービークリップの各フレーム。
ムービークリップの作成

さあ、ムービークリップを作成しましょう。 下記ソース中の atlas 変数が、すべてのフレームを含む TextureAtlas の参照であるならば、話はとても簡単です。

var frames:Vector.<Texture> = atlas.getTextures("flight_"); (1)
var movie:MovieClip = new MovieClip(frames, 10); (2)
addChild(movie);

movie.play();
movie.pause(); (3)
movie.stop();

Starling.juggler.add(movie); (4)
1 getTextures メソッドは与えられた接頭語で始まるすべてのテクスチャーをABC順で返します。
2 設定はこれだけでOKです。テクスチャーの一覧をコンストラクタに渡します。 2番目のパラメータは1秒間に何回描画更新がかかるかを示します。
3 ムービークリップの再生状態をコントロールするメソッドがいくつか存在します。デフォルトでは再生している状態から始まります。
4 Important: 他の Starling 上のアニメーションと同じように、ムービークリップもジャグラーに登録される必要があります!

接頭語の flight_ を使ってどのようにテクスチャアトラスからテクスチャーの一覧を得たか、わかったでしょうか? このやり方であればアトラス画像には、ムービークリップとはまた別の画像を含ませる事も可能になります。 一つのムービークリップのフレームをグループ化する為に、それぞれで同じ接頭語を持たせるわけです。

ムービークリップには、サウンドや任意の処理を実行するため、あるフレームが再生された際に実行されるコールバックを仕込む事ができます。 APIリファレンスを確認して、何ができるのか把握しましょう!

さらに複雑なアニメーション

このアニメーションの仕組みの悪い面も伝えなくてはいけません。 もしもアニメーションの尺が長かったり、各フレームの画像が大きい場合、テクスチャーメモリーが足りなくなってしまう場合があるという事です。 アニメーションが複数の大きなテクスチャアトラスにまたがっている場合、メモリに収まらないでしょう。

そのようなタイプのアニメーションには、もっと手の込んだ作りの仕組みに切り替えましょう。それが、スケルタル(ボーン)アニメーションです。 そこでは、キャラクターはいくつかのパーツ(ボーン)に分解され、それぞれが別々にアニメーション制御されます。(そのキャラクターの骨格によります。) これはとても柔軟な方法です。

そのようなアニメーション機能は Starling に含まれていません。しかし、いくつかの外部ツール・ライブラリが役立ちます。 下記のリストにあるソリューションはそれぞれ Starling とともに、とてもうまく動きます。

2.7. アセット管理

どのアプリケーションでもテクスチャーがリソースの大きな範囲をしめる事はここまででわかったかと思います。 特にゲームではたくさんのギラフィックを必要とします。具体的には、ユーザインタフェース、キャラクター、アイテム、背景画像などです。 しかし、それで全てではありません。サウンドファイルや設定ファイルもおそらく必要となるでしょう。

これらのアセットにアクセスして読み込むには、いくつかの方法があります。

  • アプリケーションに [Embed] メタデータ指定で埋め込んでしまう。

  • ストレージからそれらを読み込む。 (AIR アプリケーションにでのみ可能です。)

  • 例えば Webサーバなどの URL からロードする。

それぞれのやり方で異なったコーディングが必要なため(アセットのタイプとロードの仕組みによります。)、統一した方法でアセットにアクセスするのは難しいのです。 幸運にも、Starling にはそれをうまく扱ってくれる AssetManager というクラスを含まれています。

AssetManager は以下のタイプのアセット読み込みをサポートします。

  • テクスチャ (ビットマップでも ATF 形式でも)

  • テクスチャーアトラス

  • ビットマップフォント

  • サウンド

  • XML データ

  • JSON データ

  • その他バイナリファイル

アセットの読み込みを完了するまで、 AssetManager は3つのステップを取ります。

  1. 読み込みたいアセットの "ポインタ" をロード対象キューに積みます。"ポインタ" とは、File オブジェクトや URL などです。

  2. キューの処理実行を AssetManager に伝えます。

  3. キューの読み込みが完了次第、get で始まるいくつかのメソッドでアセットにアクセスが可能になります。

AssetManager には verbose プロパティが存在します。 これが 有効(true)に設定されると、キューイングとローディングの全てのステップがコンソールにログ出力されるようになります。 あるアセットがロードされない理由がわからない場合など、デバッグ作業にとても有効です。 この理由から、最新の Starling ではこのプロパティがデフォルトで有効とされています。

2.7.1. アセット読み込みのキュー管理

最初のステップは扱いたいファイルを全てキューに入れる事です。 どのように処理されるかは、アセットのタイプとそれぞれをどこから読み込むかによります。

ディスクまたはネットワークからのアセット読み込み

キューにファイルを積む祭の読み込み元が、ディスクもしくはリモートのサーバーである場合はわかりやすいです。

// リモートURLからキューに積む
assets.enqueue("http://gamua.com/img/starling.jpg");

// ディスクからキューに積む (AIR のみ)
var appDir:File = File.applicationDirectory;
assets.enqueue(appDir.resolvePath("sounds/music.mp3"));

// 全てのファイルを再帰的にディレクトリから読み込む (AIR のみ).
assets.enqueue(appDir.resolvePath("textures"));

テクスチャーアトラスを読み込む場合、XMLと対応したテクスチャをそのままキューに積めば良いです。 ただし、XML 内に記述されている imagePath 属性値に正しいファイル名が設定されている状態として下さい。 AssetManager が実際にアトラスを実体化する際にその属性値を参照するからです。

assets.enqueue(appDir.resolvePath("textures/atlas.xml"));
assets.enqueue(appDir.resolvePath("textures/atlas.png"));

ビットマップフォント も同じように動きます。 その場合、XML の file 属性値が正しい設定であるようにして下さい。

assets.enqueue(appDir.resolvePath("fonts/desyrel.fnt"));
assets.enqueue(appDir.resolvePath("fonts/desyrel.png"));
埋め込みアセットの読み込み

埋め込みアセットに対しては、全ての埋め込み処理を1つのクラスにまとめる事をおすすめします。 public static const としてそれらを宣言して、下記の命名規則に従って下さい。

  • 埋め込まれた画像ファイルは、そのファイル名と完全に同じ名前を持つようにして下さい。その際、拡張子は含まなくて良いです。 これは、XML(アトラスまたはフォント設定)からのファイル参照を正しく行うために必要です。

  • アトラスとビットマップフォントの XML ファイルは任意のファイル名を取ることができます。なぜならばファイルネームで参照されることはないからです。

サンプルのクラスのコードは下記のようになります。

public class EmbeddedAssets
{
    /* PNG texture */
    [Embed(source = "/textures/bird.png")]
    public static const bird:Class;

    /* ATF texture */
    [Embed(source   = "textures/1x/atlas.atf",
           mimeType = "application/octet-stream")]
    public static const atlas:Class;

    /* XML file */
    [Embed(source   = "textures/1x/atlas.xml",
           mimeType = "application/octet-stream")]
    public static const atlas_xml:Class;

    /* MP3 sound */
    [Embed(source = "/audio/explosion.mp3")]
    public static const explosion:Class;
}

このクラス自体をキューに積んだ場合、AssetManager は埋め込み設定されたアセットを全てインスタンス化します。

var assets:AssetManager = new AssetManager();
assets.enqueue(EmbeddedAssets); (1)
1 bird テクスチャーと、 explosion サウンドと、テクスチャーアトラスがキューに積まれている。
アセットごとの設定

テクスチャを(Texture.from…​() メソッドを通して)マニュアルで作成する場合、細かい設定を行うチャンスです。 例えば、テクスチャのフォーマットとスケールファクター値をどのようにするか決めることができます。

それらの設定の際の注意点は、一度テクスチャーが作られると、設定が変更できないという事です。 よってテクスチャーを作成する際は、確実に正しい設定をするようにしなくてはいけません。 AssetManager はこの設定作業をサポートします。

var assets:AssetManager = new AssetManager();
assets.textureFormat = Context3DTextureFormat.BGRA_PACKED;
assets.scaleFactor = 2;
assets.enqueue(EmbeddedAssets);

AssetManager は、これらの設定を作成する全てのテクスチャに適用します。 さて、このやり方では読み込んだ全てのテクスチャーに同じ設定をする事しかできないようにも見えますが、実際にはそんな事はありません。 正しい手続き順序を踏んで対象ファイルをロードのキューに積む事で実現できます。 各々の設定をそれぞれの enqueue 実行前に行ってやればいいのです。

assets.scaleFactor = 1;
assets.enqueue(appDir.resolvePath("textures/1x"));

assets.scaleFactor = 2;
assets.enqueue(appDir.resolvePath("textures/2x"));

上記のやり方で、1x2x のフォルダから読み込んだテクスチャーに、正しいスケールファクター値である1と2をそれぞれ設定する事ができます。

2.7.2. アセットのロード

アセットがキューに積まれた後は、まとめていっぺんにロード処理を行う事ができますが、アセットの数とサイズによっては、しばらく時間がかかります。 そのため、ユーザーにはプログレスバーかローディングインジケーターを表示して見せるのが良いでしょう。

assets.loadQueue(function(ratio:Number):void
{
    trace("Loading assets, progress:", ratio);

    // when the ratio equals '1', we are finished.
    if (ratio == 1.0)
        startGame();
});

この startGame メソッドは、自身で実装して下さい。そこはローディング画面を消して、実際のゲームを開始する処理の箇所です。 また、verbose プロパティが有効になっていると、アクセスされたアセットの名前をログ画面で確認する事ができます。

[AssetManager] Adding sound 'explosion'
[AssetManager] Adding texture 'bird'
[AssetManager] Adding texture 'atlas'
[AssetManager] Adding texture atlas 'atlas'
[AssetManager] Removing texture 'atlas'

テクスチャを作成した後の最後の行で atlas テクスチャーが削除されている事に気づいたでしょうか? 何故でしょうか?

一度アトラスが作成されると、アトラステクスチャ自体には用がなくなります。そこに含まれているサブテクスチャだけが必要です。 したがって、実際のアトラステクスチャは削除されます。他のテクスチャのためにメモリに空きを作るのです。 これと同じ事がビッットマップフォントでも行われます。

2.7.3. アセットへのアクセス

キューの読み込みが終了した後は AssetManager の、さまざまな get…​ で始まるメソッドで読み込んだファイルにアクセスする事ができます。 それぞれのアセットは名前を引数にして参照可能です。拡張子なしのファイル名か、ソースに埋め込まれたクラス名がその名前となっています。

var texture:Texture = assets.getTexture("bird"); (1)
var textures:Vector.<Texture> = assets.getTextures("animation"); (2)
var explosion:SoundChannel = assets.playSound("explosion"); (3)
1 この記述では、まずテクスチャー、次にテクスチャーアトラス、の順序でファイルが探されます。
2 上と同じですが、引数の文字列で始まる名前を持つ全てのテクスチャ/サブテクスチャが返されます。
3 サウンドが再生され、それをコントロールする SoundChannel が戻り値で戻されます。

ビットマップフォントを読み込ませている場合は、すでに利用可能な状態となっています。

自分がゲームを作る場合は、AssetManager をルートクラスの static プロパティとして保持しています。 これによって、ゲームプログラムのどこからでも各アセットにとても簡単に参照できます。 やり方はシンプルに Game.assets.get…​() と、するだけです。(ルートクラス名が Game だとした場合)

2.8. フラグメントフィルター

ここまでは、描画してきたものは全てがメッシュであり、そこにテクスチャが設定されているものといないものがありました。

メッシュは位置を変更したり、拡大・縮小・回転を施したり、着色することもできます。 しかし、概してできることはむしろ少なく、 ゲームの見た目はほとんどテクスチャによって決まってしまうのです。

そしてそのうち、次のような問題にぶつかるのではないでしょうか。 一つの画像に色々なパターン、例えば、ブラーがついたもの、色違い、影がついたもの、などが必要となることがあるでしょう。 もし全てのエフェクトがついたたくさんの画像パターンをテクスチャアトラスに用意した場合、 すぐにメモリ不足になってしまうでしょう。

フラグメント フィルター がこれを解決します。 フィルターはどんな種類のディスプレイリストにも設定できます。スプライトなどのコンテナでも問題ありません。 そして劇的に見た目を変えることができます。

例えば、ガウスぼかしをディスプレイオブジェクトに適用したいとしましょう。

var filter:BlurFilter = new BlurFilter(); (1)
object.filter = filter; (2)
1 適切なフィルターのクラスを作成して、必要であればその設定もします。
2 ディスプレイオブジェクトの filter プロパティにそのフィルターを設定します。

フィルタが設定されたディスプレイオブジェクトの描画は下記のように処理されます。

  • 全てのフレームで、対象ディスプレイオブジェクトは一度テクスチャーに描画されます。

  • そのテクスチャは、直接 GPU のフラグメントシェーダーによって処理されます。

  • 複数のフィルター設定がある場合、複数回の処理を必要とします。1つのシェーダーの出力が次のシェーダーの入力として渡されます。

  • 最終的に、最後のシェーダー出力がバックバッファーに書き込まれます.

Filter Pipeline
Figure 20. フラグメントフィルターのレンダリングパイプライン。

この方法はとても柔軟で、 異なる種類のエフェクトを同時に複数適用することができます。

さらに、フィルターは、GPU の並列演算処理能力をうまく利用します。 高コストなピクセル単位の演算処理は、全てがグラフィックチップ上で行われます。

と言っても、フィルターはバッチ処理を分割します。1つのフィルター処理が1ドローコールを必要とします。 メモリ面でもパフォーマンス面でも厳密には低コストではないのです。

よって、注意してうまく扱うようにしてください。

2.8.1. ショーケース

Starling には、とても便利なフィルターがいくつか搭載され、すぐに使う事ができます。

ブラーフィルタ

ガウスぼかしをオブジェクトに適用します。ぼかし強度はX軸Y軸それぞれで個別に設定できます。

  • ブラーごとに少なくとも1ドローコールを必要とします。

  • 強度が1つ増えるごとに1ドローコールが必要となります。強度1は1パス、強度2は2パス、必要です。

  • 強度は増加させずに、フィルター解像度を低くするのが良い選択でしょう。効果的としては似た結果となりますが、ずっと低コストです。

BlurFilter
Figure 21. 実際に ブラーフィルター を動作させた例。
カラーマトリックスフィルター

オブジェクトのカラーを動的に置き換えます。明度、彩度、色相を置き換えたり、完全に色味を反転したりします。

このフィルターは各ピクセルごとに、カラー値とアルファ値を 4x5 マトリックスで行列演算します。 とても柔軟なコンセプトですが、その演算のための適切な行列値を手計算で得るのは難しい作業です。 この理由で、このクラスにはいくつかの便利なメソッドが用意されました。 色相や彩度を変えたいなど、適用したいエフェクトをのために必要な行列を用意してくれます。

  • 複数のカラー変更効果を1つのフィルターとしてまとめて設定することができます。 それぞれのメソッドを使えば、明度と彩度を同時に変更する事ができます。

  • このフィルター処理に必要なドローコール数は1パスです。

ColorMatrixFilter
Figure 22. 実施に カラーマトリックスフィルター を動作させた例。
ドロップシャドーフィルターとグローフィルター

これら2つのフィルターは元々のオブジェクトを全面に配置し、そのオブジェクトをぼかして色味が加えられたものを背面に配置します。

  • そのような手順のため、これらのフィルターは高コストです。ブラーフィルター効果に追加で処理を加えた状態なわけですので。

DropShadow and Glow filter
Figure 23. 実際に _ドロップシャドーフィルターグローフィルター を動作させた例。
ディスプレースメントマップフィルター

テクスチャーの色味に基づき、ターゲットのオブジェクトの形状を変化させます。

  • 扱いが難しいですが、とても強力な機能です!

  • 水面への写り込み、レンズの歪み、爆発の衝撃波、などをこのフィルターを使って実現可能です。

Other filters
Figure 24. いくつかのテクスチャを適用したディスプレースメントマップフィルターの様子。
フィルターチェイン

FilterChain クラスを用いると、いくつかのフィルターを組み合わせて1つのディスプレイオブジェクトに適用する事ができます。 フィルターは設定された順序そのままで処理されます。ドローコール数はフィルターが増えるごとに増加します。

FilterChain
Figure 25. カラーマトリックスフィルター-ドロップシャドウフィルター を組み合わせた様子。

2.8.2. パフォーマンス調整

すでに上で述べましたが、 GPU での処理部分はとても効率的であるものの、 追加のドローコールがフィルターをむしろ高コストにします。 しかし Starling はフィルターを最適化して、できる限り効率的に動作しようとします。

  • オブジェクトの位置がステージ座標で見て2つのフレーム間で変化しない場合、もしくはスケールや色味などが変化しない場合、Starling はそれを認識し、フィルター出力をキャッシュします。 フィルターが再度処理されなくて良い場合に行われる事で、オブジェクトは、ただの1枚の画像であるかのように振舞います。

  • 一方で、オブジェクトが絶えず移動している場合、最後のフィルター処理はテクスチャではなく、絶えずバックバッファーに直接描画されます。 これによりドローコールが1回節約されます。

  • オブジェクトが動いていた場合にもフィルター出力をキャッシュさせたい場合、filter.cache() 命令を呼び出してください。 (再度言いますが)これによりオブジェクトはただの1枚の画像のように振舞います。 しかし、対象オブジェクトの変化を描画に反映させるためには、cache または uncache 命令を明示的に呼び出しなおす必要があります。

  • resolution 及び textureFormat プロパティを調整する事でメモリー使用量を抑える事ができるでしょう。ただしこの方法の場合、イメージ品質の低下も伴います。

2.8.3. フィルターについてさらに

ここまでで、フィルターを自分で作ってみたいと思いましたか? その話題については、もう少し後の [Custom Filters] の項で扱います。

それまでは、他の Starling デベロッパーが作成したフィルターを試してみてはどうでしょう。 この フィルターコレクション は devon-o 氏による素晴らしいフィルターの例です。

2.9. メッシュ

Mesh は全てのディスプレイオブジェクトの基本構成要素です。

ここで言うディスプレイオブジェクトは、ディスプレイリスト内の葉要素であるオブジェクトの事を指しています。 つまり、スプライトなどのコンテナではなく、実際に画面に表示される部分となるオブジェクトです。

このメッシュクラスはとても重要です。詳細を確認してみましょう。

Mesh とは Stage3D 上で描画される三角形の集合の事です。

すでに何度か述べましたように、 MeshQuadImage のベースクラスです。 継承ツリーを再度下記に図示します。

mesh classes from display object

Mesh は抽象クラスではありません。実際にインスタンス化する事が可能です。

var vertexData:VertexData = new VertexData();
vertexData.setPoint(0, "position", 0, 0);
vertexData.setPoint(1, "position", 10, 0);
vertexData.setPoint(2, "position", 0, 10);

var indexData:IndexData = new IndexData();
indexData.addTriangle(0, 1, 2);

var mesh:Mesh = new Mesh(vertexData, indexData);
addChild(mesh);

このように、まずメッシュより先に別の2つのクラスをインスタンス化する必要があります。 VertexDataIndexData です。 それぞれ、頂点情報とindex情報を保持します。

  • VertexData は頂点のそれぞれの位置や色など、属性情報を効率的に保持します。

  • IndexData はその頂点のindex番号を保持します。3つのindex情報で三角形が作られます。

そのようにして、このコードでは最も基本的な描画オブジェクトである三角形を作りあげます。 3つの頂点情報を時計回りで参照しながら描画を行います。 結局、GPUがうまくできる事はたくさんの三角形を描画する事なのです。

Triangle
Figure 26. 上記で作成された三角形。

2.9.1. メッシュの継承

VertexDataIndexData を直接扱っていくのは、かなり面倒です。 これらの扱いは何かのクラスに処理を隠蔽してしまうのが良いやり方でしょう。

ここで、どのようにカスタムメッシュを作るのかを説明するために、 NGon という簡単なクラスを作ることにします。 N角形のポリゴンを好きな色で描画する事がこのクラスの仕事です。

Polygons
Figure 27. 下記は描画させたい形状の例です。

そのクラスには通常のディスプレイオブジェクトと同じように動作しいと思います。 インスタンス化でき、好きな位置に移動でき、ディスプレイリストに追加できるのです。

var ngon:NGon = new NGon(100, 5, Color.RED); (1)
ngon.x = 60;
ngon.y = 60;
addChild(ngon);
1 コンストラクタの引数はそれぞれ、半径、エッジの数、色、を示します。

この機能をどのように実現すればいいでしょうか。

2.9.2. 頂点の設定

他の全てのシェイプと同じように、このポリゴン形状もいくつかの三角形から構成されています。 下記は五角形をどのように三角形で構成させるかの図です。(N が 5 である NGon が五角形です。)

Pentagon
Figure 28. 五角形と頂点。

この五角形は6つの頂点から作られた、5つの三角形で構成されています。 それぞれの頂点には 0 ~ 5 の番号を与え、真ん中のものを 5 とします。

先ほどに説明したように、頂点情報は VertexData インスタンスに保持されます。 VertexData は各頂点ごとに、いくつかの属性値を持ちます。 ここでは、2つの標準的な属性値を必要とします。

  • position 属性が2次元座標の点、x, y を保持します。

  • color 属性が RGBA 形式の色情報を保持します。

VertexData クラスはそれらの属性値にアクセスするいくつかのメソッドを持っています。 そのメソッド経由でポリゴン形状の頂点を設定する事ができます。

Mesh クラスを継承して、新たに NGon クラスを作ります。 そして下記のようなメソッドを定義します。

private function createVertexData(
    radius:Number, numEdges:int, color:uint):VertexData
{
    var vertexData:VertexData = new VertexData();

    vertexData.setPoint(numEdges, "position", 0.0, 0.0); (1)
    vertexData.setColor(numEdges, "color", color);

    for (var i:int=0; i<numEdges; ++i) (2)
    {
        var edge:Point = Point.polar(radius, i*2*Math.PI / numEdges);
        vertexData.setPoint(i, "position", edge.x, edge.y);
        vertexData.setColor(i, "color", color);
    }

    return vertexData;
}
1 真ん中の頂点を設定します。(最後の index 番号。)
2 エッジ部分の頂点を設定します。

ここでのメッシュは単色で構成させるため、全ての頂点に同じ色を設定しています。 各エッジ(コーナー)の頂点座標はある半径の円周に沿って設定されます。

2.9.3. Index の設定

頂点についてはここまでとし、次は、ポリゴン形状を構成する三角形を設定します。

3つの連続した頂点の index 番号で1つの三角形を表す事ができますが、Stage3D は、その頂点の組が羅列して並んたデータを必要とします。 頂点は時計回りで順番に参照するのが良いでしょう。 そのルールで並んだ頂点で作られる三角形は、こちら側におもて面を向けます。 よって、五角形の indices データは下記のような内容となります。

5, 0, 1,   5, 1, 2,   5, 2, 3,   5, 3, 4,   5, 4, 0

Starling では、このような頂点番号のリストを設定するために IndexData クラスを使います。 下記のメソッドで、適切な index 番号を用いて IndexData インスタンスを設定します。

private function createIndexData(numEdges:int):IndexData
{
    var indexData:IndexData = new IndexData();

    for (var i:int=0; i<numEdges; ++i)
        indexData.addTriangle(numEdges, i, (i+1) % numEdges);

    return indexData;
}

2.9.4. NGon コンストラクタ

以上が、実際に NGon クラスに必要な処理の全てです。 上記メソッドをコンストラクタ内で使用してやります。

ディスプレイオブジェクトとして必要なその他の基本処理 (あたり判定、描画処理、バウンズの計算、など) は親クラスが処理してくれます。

public class NGon extends Mesh
{
    public function NGon(
        radius:Number, numEdges:int, color:uint=0xffffff)
    {
        var vertexData:VertexData = createVertexData(radius, numEdges, color);
        var indexData:IndexData = createIndexData(numEdges);

        super(vertexData, indexData);
    }

    // ...
}

簡単ですよね? このやり方は他のどんな形状のシェイプにも用いる事ができます。

カスタムメッシュを扱う際は、starling.geom パッケージに存在する Polygon クラスもチェックしてみてください。

このクラスは、いくつかの頂点で定義される任意の閉じた形状のシェイプを三角形で分割する際に参考になります。 詳細は [Masks] セクションでさらに解説します。

2.9.5. テクスチャの追加

このポリゴンにテクスチャを適用できたら良いですね。 実は、ベースクラスの Mesh は、すでに texture プロパティを持っています。 まだ テクスチャの座標設定 を行なっていないだけです。

テクスチャの座標設定を行うことによって、テクスチャのどの部分を頂点に適用するかが決定されます。 一般的に、u値、v値 で表される座標軸でテクスチャの場所を示すので、しばしば UV座標 と呼ばれます。

UVの値の範囲は、テクスチャの実際の大きさに関わらず、0 から 1 の間に限定される事に注意してください。

Pentagon Texture Coordinates
Figure 29. ポリゴンのテクスチャの座標は 0~1 の範囲で表されます。

これに従い、createVertexData メソッドを適切に調整します。

function createVertexData(
    radius:Number, numEdges:int, color:uint):VertexData
{
    var vertexData:VertexData = new VertexData(null, numEdges + 1);
    vertexData.setPoint(numEdges, "position", 0.0, 0.0);
    vertexData.setColor(numEdges, "color", color);
    vertexData.setPoint(numEdges, "texCoords", 0.5, 0.5); (1)

    for (var i:int=0; i<numEdges; ++i)
    {
        var edge:Point = Point.polar(radius, i*2*Math.PI / numEdges);
        vertexData.setPoint(i, "position", edge.x, edge.y);
        vertexData.setColor(i, "color", color);

        var u:Number = (edge.x + radius) / (2 * radius); (2)
        var v:Number = (edge.y + radius) / (2 * radius);
        vertexData.setPoint(i, "texCoords", u, v);
    }

    return vertexData;
}
1 真ん中の頂点のテクスチャ座標を 0.5, 0.5 といします。 <2>元々 n-gon は真ん中 (0,0) に位置しているのですが、テクスチャ座標系ではそれぞれの値が必ず正の値とならなくてはいけません。 そのため、頂点座標を半径の分だけ右に移動し、その値を 2 * radius で除算します。値を 0 ~ 1 の範囲に納めるためです。 テクスチャがこのように割り当てられると、レンダリング処理に際に自動的にそれらの設定が参照されます。
var ngon:NGon = new NGon(100, 5);
ngon.texture = assets.getTexture("brick-wall");
addChild(ngon);
Textured Pentagon
Figure 30. テクスチャが設定された五角形です。

2.9.6. アンチエイリアス処理

NGonのエッジをよく観察してみると、ジャギーが発生している事に気づくかもしれません。 これは、GPU がピクセルを n-gon の描画領域の内側か外側かどうかしか判定せず、その中間の状態が存在しないからです。 これを改善するために、アンチエイリアス処理を有効にする事ができます。Starling クラスのプロパティにそのままの名前でプロパティが存在します。

starling.antiAliasing = 4;

この値は Stage3D がレンダリングをする際に利用するサブサンプリングの回数に関連します。 たくさんのサブサンプリングを行うために、たくさんの計算が発生します。アンチエイリアス処理は高コストな処理なのです。 さらに言えば、Stage3D は全てのプラットフォームでアンチエイリアス処理をサポートしていません。

モバイル環境では、アンチエイリアス処理は RenderTextures 内だけで利用が可能です。

よって、上記のアンチエイリアス処理は現実的なジャギーの解決方法ではありません。 幸いなことに、スクリーンのピクセル密度は一般的にどんどんと上昇しています。 近年のハイエンドなモバイル端末では1ピクセルの大きさがとても小さいため、もはやジャギーが目立たず、あまり問題とはならないのです。

Anti-Aliasing
Figure 31. アンチエイリアス処理はピクセルのジャギーをスムースにする事ができます。

2.9.7. メッシュスタイル

ここまでで、任意の形状のシェイプにテクスチャを適用する方法はわかりました。 その方法では、Starling に搭載されたスタンダードなレンダリング方法を用いています。

さてでは、レンダリングのやり方そのものをカスタマイズしたい場合はどうしたら良いでしょう? Mesh クラスのメソッドとプロパティは手堅い基本的な機能を提供してくれますが、遅かれ早かれその機能だけでは物足りなくなってくる事でしょう。 Starling の メッシュスタイル がこの要望の解決を手助けします。

スタイル は Starling2.0 で導入された新機能で、カスタムレンダリング処理を作成するための推奨される方法です。スタイルの処理は高速な描画が可能です。 実際、Starling 内の全ての描画処理は、メッシュスタイルを通して実行されています。

  • どの Mesh (及び _Mesh_のサブクラスのインスタンス)にもスタイルを1つ設定する事ができます。

  • デフォルトでは、それぞれのメッシュに紐づいているのは、 MeshStyle 基底クラスのインスタンスです。

  • このスタイルは Starling の標準的な描画能力を提供します。三角ポリゴンに色味とテクスチャを加えて描画します。

メッシュに新しい見たの効果を加えるには、MeshStyle を継承する方法が良いです。 カスタムのシェーダープログラムを作れば、様々なエフェクトを実現することができます。 例えば、複数のテクスチャの同時割り当てや、カラーの高速トランジションなどが実装可能です。

スタイルの印象的な例として、 Dynamic Lighting extension をあげておきます。 ノーマルマップ(ポリゴン面の法線方向をエンコードしたテクスチャ)を用いて、リアルな照明効果をダイナミック(動的)に適用することができます。 Starling Wiki で 実際にこの拡張機能が動いている様子を確認してみてください。

スタイルを実際に使うには、まずそのインスタンスを生成し、メッシュの style プロパティとして設定します。

var image:Image = new Image(texture);
var lightStyle:LightStyle = new LightStyle(normalTexture);
image.style = lightStyle;
Dynamic Lighting
Figure 32. 照明処理のスタイルが動的に動いている様子。

スタイルで実現できることは本当にたくさんあり、適用範囲にほとんど限界がありません。

さらに、同じスタイルが設定されているメッシュは、バッチ処理で同時に処理されるので、パフォーマンスを気にする必要もあまりありません。 この点に関して、同じような目的で用いられるフラグメントフィルターよりもずっと効果的にスタイルを利用する事ができます。

スタイル短所には、それがメッシュだけに適用でき、スプライトには適用できないという点があります。 また、スタイルはメッシュの描画領域内のみで有効となります。描画領域をはみ出して効果を与える’ぼかし’効果のような事はできないのです。 一つのメッシュに対して複数のスタイルを組み合わせて使う事もできません。

それでも、スタイルは Starling 開発者全員が身につけて使うべきパワフルなツールです。 この後に続く [Custom Styles] のセクションも確認してください。 スクラッチでカスタムスタイルを作成する方法や、その他シェーダーなどについて解説します。

もしまだ、 メッシュメッシュスタイル の違いについてよく理解できないのであれば、このように考えてみてください。 メッシュ は頂点の集合でしかなく、頂点でどのように三角形を描くかを示しているだけ、であると。

スタイルは各頂点に追加で情報を設定し、それをレンダリング時に使用します。 標準の MeshStyle はカラーとテクスチャの割り当てを行います。 MultiTextureStyle は追加のテクスチャ割り当て情報などを制御するかもしれません。 しかし、スタイルは決して元々のオブジェクト形状を変更しません。 スタイルは、頂点を足しも減らしもせず、位置の移動も行わないのです。

2.10. マスク処理

マスクを適用すると、ディスプレイオブジェクトの一部分を消し去って表示する事ができます。 マスクは他のディスプレイオブジェクトをのぞき見る事ができる穴だと考えましょう。

この穴は好きな形状にする事ができます。

もし、従来の Flash のディスプレイオブジェクトでマスクを利用した事があるなら、この機能にはとても親しみがわくでしょう。 下記のように、ディスプレイオブジェクトにマスクプロパティを適用してください。 どんなディスプレイオブジェクトでもマスクになる事ができます。そして、それはディスプレイリスト上に存在してもしなくても良いです。

var sprite:Sprite = createSprite();
var mask:Quad = new Quad(100, 100);
mask.x = mask.y = 50;
sprite.mask = mask; // ← use the quad as a mask

下記のような結果になります。

Rectangular Mask
Figure 33. 矩形のマスクを適用する。

マスクの仕組みはシンプルです。マスクされたオブジェクトのピクセルはマスク形状の内側にある場合のみ描画されます。 これは重要な事です。マスクの形状はそのテクスチャ情報でなく、そのポリゴン形状から決定されるのです。

マスクは対象ピクセルを表示するかしないかの完全な二択で描画されます。

マスク と AIR

AIR アプリ内でマスクを動作させるためには、アプリケーションの設定でステンシルバッファーを有効にしなくてはいけません。 initialWindow の要素として下記を追加してください。

<depthAndStencil>true</depthAndStencil>

もし設定を忘れてしまっても、安心してください、はいてますよ。(Starling はコンソールログとして警告を。)

2.10.1. キャンバスとポリゴン

"マスク機能は本当に良いですね。"…あなたはこう言うかもしれません。 "と、言ってもどうやってマスク形状を好きな形にする事ができるんですか?" はい、いい質問です!

マスクはテクスチャーで定義されるのでははなく、純粋な幾何学形状なので、マスク形状を作る方法を覚えなくてはいけません。 (面白い事に偶然の一致なのですが)この目的を果たすための2つのクラスが存在します。 CanvasPolygon です。

それらのクラスはステンシルマスクと密接に関係しています。

Canvas クラスの API は Flash のグラフィックオブジェクトと似ています。 下記は赤い円を描画する例です。

var canvas:Canvas = new Canvas();
canvas.beginFill(0xff0000);
canvas.drawCircle(0, 0, 120);
canvas.endFill();

他にも、楕円や矩形、任意の多角形を描く命令も存在します。

これらの基本的なもの以外のメソッドはまだ存在しません。Canvas クラスはまだ制限が大きいといえます。 Graphics クラスの代わりになるような本格的なクラスではありません。 しかしこれは、将来のリリースで変更されるかもしれません!

次に、Polygon クラスを見て見ましょう。

一つの Polygonstarling.geom パッケージに存在します。)は、いくつかの直線で構成された閉じた形状を表します。

Polygon は Flash の Rectangle クラスの志を継承し、さらに任意の形状をサポートします。.[3]

Canvas は polygon オブジェクトの描画を直接サポートするため、Polygon とは完全な仲間といえます。 この2つのクラスでマスクに関する全てのニーズを満たせるでしょう。

var polygon:Polygon = new Polygon(); (1)
polygon.addVertices(0,0,  100,0,  0,100);

var canvas:Canvas = new Canvas();
canvas.beginFill(0xff0000);
canvas.drawPolygon(polygon); (2)
canvas.endFill();
1 この polygon は三角の形状を表します。
2 canvas に三角形を描きます。

さらにいくつか、マスクについてのポイントがあるので見てみましょう。

直接は見えない

マスクそれ自体は見える状態になることはありません。 マスクされたオブジェクトのエフェクトの結果として間接的に形状を確認することだけができます。

マスク位置の決定ロジック

マスクがディスプレイリストの一部でない場合(つまり親を持たない場合) マスクされたオブジェクトのローカル座標系に合わせてマスク処理が演算されます。 もし、マスクがディスプレイリストの一部である場合、その位置がそのままマスク演算に使われます。

ステンシルバッファー

裏の仕組みとして、マスクは GPU のステンシルバッファーを利用し、軽量高速に処理を実現します。 1つのマスクは2回のドローコールを必要とします。 1回目はマスク形状をステンシルバッファーに書き込むのに必要で、 2回目は全てのマスク対象が描かれた後に、それを消去するのに使われます。

Scissor Rectangle (特殊矩形マスク)

もしもマスクがテクスチャを持たず、かつ、ステージ座標と平行である場合(回転していない場合)、Starling は描画処理を最適化します。 ステンシルバッファー の代わりに scissor rectangle を用いて、ドローコールを1回節約します。

テクスチャで行うマスク

シンプルなベクター形状のマスクで満足がいかないのならば、テクスチャのアルファ値をステンシルマスクに使う拡張機能を使ってはどうでしょう。 それは、Texture Mask と呼ばれるもので、 Starling Wiki で見つける事ができます。

2.11. スプライト3D

前のセクションで話題とした全てのディスプレイオブジェクトは、純粋な 2D のオブジェクトです。 Starling は 2D のフレームワークですから、当然の事です。 しかし、2D のゲームであっても、時々簡単な 3D エフェクトがあると良いなと思う事もあります。 例えば、2つのシーンを遷移する時のトランジション演出であったり、カードの裏面を見せる際などです。

このような理由で、Starling は簡単な 3D 表現を行う Sprite3D というクラスを持っています。 このクラスは、2D オブジェクトを三次元空間へと導きます。

2.11.1. 基本事項

通常の Sprite と同様に、このコンテナオブジェクトにはディスプレイオブジェクトを追加したり、削除したりする事ができます。 いくつかのディスプレイオブジェクトをグループ化する事ができるわけです。 それに加えて、Sprite3D はいくつかの興味深いプロパティを持っています。

  • z — Z軸に沿ってスプライトを移動します。(カメラからの距離を示します。)

  • rotationX — X軸を中心にスプライトを回転します。

  • rotationY — Y軸を中心にスプライトを回転します。

  • scaleZ — Z軸に沿ってスプライトを拡大します。

  • pivotZ — スプライトの中点のうちZ座標を指定します。

これらのプロパティとともに、スプライトとその子オブジェクトを 3D の世界へと誘います。

var sprite:Sprite3D = new Sprite3D(); (1)

sprite.addChild(image1); (2)
sprite.addChild(image2);

sprite.x = 50; (3)
sprite.y = 20;
sprite.z = 100;
sprite.rotationX = Math.PI / 4.0;

addChild(sprite); (4)
1 Sprite3D インスタンスを生成します。
2 そのスプライトに、いくつか 2D オブジェクトを追加します。
3 3D空間上での位置や回転角度を設定します。
4 (いつもの通り)ディスプレイリストにスプライトを追加します。

このように、 Sprite3D を扱うのは簡単です。いくつかの新しいプロパティを扱うだで良いのです。 当たり判定、アニメーション、カスタムレンダリング、どれも他のディスプレイオブジェクトと同じように利用する事ができます。

2.11.2. カメラの設定

3D オブジェクトを表示する際、パースのかかり具合を調整したいと思うこともあるでしょう。 カメラの設定で、それが可能です。Starling ではカメラ関連の設定を stage のプロパティとして保持しています。

下記の stage プロパティが、カメラの設定を行うものです。

  • fieldOfView — field of view (FOV) 値。視野の角度をラジアンの単位で設定します。値は 0 から π の範囲となります。

  • focalLength — stage とカメラの距離です。

  • projectionOffset — カメラのデフォルトの位置からの移動量。xy方向のベクトル値です。 カメラのデフォルト位置は、stage の真ん中、真正面となります。

Camera Diagram
Figure 34. カメラを設定するそれぞれのプロパティ。

Starling は、いつでも stage が viewport 全体を占めるように表示しようとします。 ユーザが field of view 値を変更した際、focal length 値は、その制限を保つように自動で調整されます。逆もしかりです。 つまり、fieldOfViewfocalLength は実質的に同じプロパティであり、見え方が違うだけなのです。

下記は、fieldOfView 値によって、立方体の見た目がどのように変化するかを示した例です。

Field-of-View
Figure 35. fieldOfView 値を変化させた際の見た目の変化。 (単位:degrees).

デフォルトでは、カメラの位置はいつでも カメラが stage の中心を向くように調整されます。 しかし、projectionOffset 値を変更すると、パースのつき方を変更することができます。 上面や下面など、違った方向からオブジェクトを眺めたい場合にこのプロパティを変更します。 下記は再び立方体の例です。今回は、projectionOffset.y 値を変化させています。

Projection Offset
Figure 36. projectionOffset.y 値を変化させた際の見た目の変化。

2.11.3. 制限事項

Starling は本来 2D のライブラリです。それゆえの注意点がいくつかあります。

  • Starling はZ軸の深度管理をしません。前後の重ね合わせは、単純にディスプレイリストのオーダー順に処理されます。

  • パフォーマンス低下に気をつけましょう。全ての Sprite3D はバッチ処理を分断します。

しかし、後者は多くのケースで和らげる事ができます。 オブジェクトが実際には 3D 変換されていない場合、つまり、3D スプライトでしか実現できない機能を何も使っていない場合、 Starling は Sprite3D を普通の 2D オブジェクトとして扱います。 パフォーマンスに関してもバッチ処理に関しても、 2D オブジェクトを扱っている場合と同じ状態となります。

これはつまり、Sprite3D インスタンスをたくさん作る事をためらう必要がない事を意味します。 同時に 3D 効果をたくさん使位すぎなければいいだけなのです。

2.11.4. サンプルプロジェクト

この機能の実際の使い方を紹介したデモ動画を作成しました。 どのように 2D ゲームを 3D 化するのかを解説しています。

  • Vimeo サイト上で動画をご覧ください。

  • 完全なソースコードは、 GitHub で入手できます。

2.12. ユーティリティ

starling.utils パッケージにはいくつか便利なクラスや命令が存在します。見逃さないようにしましょう。

2.12.1. Colors

従来の Flash も Starling も、色の情報は16進数の値で表されます。

// format:         0xRRGGBB
var red:Number   = 0xff0000;
var green:Number = 0x00ff00; // もしくは 0xff00
var blue:Number  = 0x0000ff; // もしくは 0xff
var white:Number = 0xffffff;
var black:Number = 0x000000; // もしくは 0

Color クラスはいくつかの色味に名前をつけて保持しています。 また、その色の各チャンネル(RGB成分)には簡単にアクセスする事ができます。

var purple:uint = Color.PURPLE; (1)
var lime:uint   = Color.LIME;
var yellow:uint = Color.YELLOW;

var color:uint = Color.rgb(64, 128, 192); (2)

var red:int   = Color.getRed(color);   // ->  64 (3)
var green:int = Color.getGreen(color); // -> 128
var blue:int  = Color.getBlue(color);  // -> 192
1 いくつかの色はすでに定義済みです。
2 他の色はこのメソッドで作成する事ができます。RGB 値を引数としていください。(範囲は0〜255。)
3 それぞれのチャンネルの値を整数値として得ることも可能です。

2.12.2. Angles

Starling 角度を’ラジアン’の単位で統一して扱います。(Flash では’度’と’ラジアン’の単位が場所によって混在して使われています。) '度’と’ラジアン’を変換するには下記のようなコードで行えます。

var degrees:Number = rad2deg(Math.PI); // -> 180
var radians:Number = deg2rad(180);     // -> PI

2.12.3. StringUtil

.NetやC# と同じように、文字列の装飾に format メソッドが利用可能です。

StringUtil.format("{0} plus {1} equals {2}", 4, 3, "seven");
  // -> "4 plus 3 equals seven"

このクラスには、文字列の最初と最後に存在する空白文字をトリミングするメソッドも存在します。 これはユーザが入力した文字列を扱う際によく必要となる処理です。

StringUtil.trim("  hello world\n"); // -> "hello world"

2.12.4. SystemUtil

アプリまたはゲームが実行されている環境の情報を得ることは役に立ちます。 SystemUtil クラスはそのためのメソッドやプロパティをいくつか持っています。

SystemUtil.isAIR; // AIR で動いているか、Flash で動いているか。
SystemUtil.isDesktop; // デスクトップで動いているか、モバイルで動いているか。
SystemUtil.isApplicationActive; // 最小化されていないか。
SystemUtil.platform; // 動作プラットフォームの種類。WIN(Windows)、MAC(Mac)、LNX(Linux)、IOS(iOS)、AND(Android)。

2.12.5. MathUtil

このクラスは主に幾何学的な問題を扱う際に役に立つようデザインされていますが、 下記のように便利なヘルパーメソッドも持っています。

var min:Number = MathUtil.min(1, 10); (1)
var max:Number = MathUtil.max(1, 10); (2)
var inside:Number = MathUtil.clamp(-5, 1, 10); (3)
1 2つの数字のうち小さいものを返します。ここでの結果は、1
2 2つの数字のうち大きいものを返します。ここでの結果は、10
3 一つ目の引数の数値を、残りの引数で指定された範囲に収めます。ここでの結果は、1

もしあなたが AS3 でプログラムを組んだ事があるならば、 なぜすでに Math クラスで提供されているのと同じような機能をわざわざ新しく作ったのかと疑問に思うでしょう。

残念なことに、Math クラスのそれらには好ましくない副作用があるのです。 Math.min メソッドを実行するたびに、一時的なオブジェクトが(処理の裏で)作られてしまうのです。 (少なくとも iOS 向きにアプリケーションをコンパイルした際は。) MathUtil のそれはそのような副作用がありません。よって、どんなときも MathUtil のメソッドを使うのが良いでしょう。

2.12.6. オブジェクトプーリング

さて、上記で一時的なオブジェクトについて触れたこともあり 今が Pool オブジェクトについて紹介するのにちょうどいいタイミングだと思われます。

経験ある AS3 開発者なら知っている事ですが、どんなオブジェクトの生成にもコストが発生します。 また、オブジェクトが使ったメモリ空間は利用後に回収される必要があります。(いわゆるガベージコレクション) この処理は自動的に行われ、たいていの場合は、いつ行われたのか気づくこともできません。

しかし、このメモリ回収処理に時間がかかりすぎると、アプリケーションは一瞬フリーズをしてしまいます。 このフリーズが頻繁に起こると、ユーザー体験はすぐさま悪くなってしまいます。

この問題を避ける一つの方法は、作成したオブジェクトをリサイクルして何度も使い回す事です。 例えば、Point クラスや、Rectangle クラスはたいてい短期間しか必要とされず、 生成・利用後、すぐに廃棄されてしまいます。

今日からは、Starling の Pool クラスにこれらのオブジェクトを管理させましょう。

var point:Point = Pool.getPoint(); (1)
doSomethingWithPoint(point);
Pool.putPoint(point); (2)

var rect:Rectangle = Pool.getRectangle(); (1)
doSomethingWithRectangle(rect);
Pool.putRectangle(rect); (2)
1 オブジェクトを pool から受け取ります。インスタンスの生成を置き換える処理です。
2 オブジェクトの利用後、pool にオブジェクトを預けます。

このクラスは、Vector3DMatrix _および、_Matrix3D を同じようにサポートします。

受け取りと預ける回数はそれぞれでバランスが取れているようにして下さい。 もしもあまりにもたくさんのオブジェクトを pool に預けて、そのまま取り出さない場合、 どんどんとそこに溜まっていき、メモリを浪費してしまいます。

2.12.7. その他

starling.utilities パッケージには、ここで紹介されても良いぐらいのヘルパークラスがまだまだあります。 完全なメソッドとプロパティの一覧を得るには、 API Reference を確認してください。一見の価値あり、です!

2.13. まとめ

これで、Starling フレームワークの 基本コンセプトについて、詳しくなったかと思います。 それは、あなたが作りはじめようと思っているゲームやアプリのために必要な全ての知識です。

一方、まだ私たちの小さな鳥が袖をまくるような(鳥に袖なんてあったかな?)いくつかのテクニックがあります。 上級テクニックの話題へ乗り込んでいく準備はできていますか?私と一緒にさらに先へと進んで行きましょう。

3. 上級トピックス

前の章で学んだ内容により、あなたは実際に Starling プロジェクトに手をつける準備が十分にできています。 しかし、プロジェクトを開始すると、ちょっと困った事に出くわすかもしれません。 例えば、

  • テクスチャが利用可能なメモリ全てをすぐに消費してしまう

  • 時々、コンテキストロス というエラーに遭遇する。何なんだ一体!WTF!? [4]

  • 出来上がったアプリの動作スピードに満足がいかない。もっとサクサク動いて欲しい!

  • あなたは頂点シェーダーと断片シェーダーを自ら書きたいというマゾヒストかもしれない。しかしやり方がわからない。

面白いことに、これらの例は、この章の内容をうまく要約しています。 "ドロシー、シートベルトを閉めるんだ!上級トピックスに突入するぞ!"

3.1. ATF テクスチャ

従来の Flash 開発では、ほとんどの開発者が画像として PNG フォーマットを用いるか、透明度が必要ない場合は JPG フォーマットを用います。 それらは Starling でも同様で、とても一般的なやり方です。 しかし、Stage3D は、いくつかの優れた特徴を持つ別の画像フォーマットにも対応しています。Adobe Texture Format という圧縮テクスチャ情報を保持できるフォーマットです。

  • 圧縮テクスチャは従来のテクスチャと比べて、わずかなサイズとなります。

  • 解凍作業は GPU によって直接行われます。

  • グラフィックメモリーへのアップロードがより高速です。

  • アップロードは非同期に行うことができます。ゲーム体験を損なわずにテクスチャをロードすることができます。.[5]

3.1.1. グラフィックメモリー

ATF テクスチャの話を続ける前に、どのくらいのメモリーがテクスチャによって必要となるか知っておくと良いでしょう。

1枚の PNG 画像は、1ドットごとに4つのチャンネル情報を保持します。透明度 で、それぞれが8ビットの(つまり256段階の)情報を持ちます。 512 x 512 ピクセルのテクスチャーがどのくらいのメモリ領域を必要とするのか計算するのは難しくありません。

512 × 512 サイズの RGBA テクスチャのメモリ容量:
512 × 512 ピクセル × 4 bytes = 1,048,576 bytes ≈ 1 MB

JPG イメージを使っている場合はもっと小さくなります。アルファチャンネルが必要なくなるためです。

512 × 512 サイズの RGB テクスチャのメモリ容量:
512 × 512 ピクセル × 3 bytes = 786,432 bytes ≈ 768 kB

小さなテクスチャのために。とても大きなメモリ容量を必要としていますね? PNGJPG フォーマットで圧縮された画像は Stage3D が扱うためには、展開されざるをえないわけです。

言い換えれば、ファイルサイズは問題ではないのです。メモリ使用量はいつでも上記の計算式によって決定されます。 それでもうまくメモリ容量に収まるのであれば、そのまま(PNG や JPG を)使って良いでしょう。 特にデスクトップPCをターゲットとしたアプリケーションを作成しているのなら、たいていの場合それでとてもうまくワークします。

しかし、開発が進んで行くと、デバイスで利用可能な容量以上にメモリが必要となってしまう時もやってくるでしょう。 その時こそが、ATF フォーマットを試してみるべき時です。

3.1.2. 圧縮テクスチャ

上記で、通常のテクスチャのファイルサイズが、グラフィックメモリーが使われる量になんら影響しないことを知りました。 小さいサイズに圧縮された JPG ファイルは、メモリ上に BMP フォーマットと全く同じだけのスペースを使ってしまうのです。

これでは本当の意味での圧縮テクスチャとは呼べません。本当の圧縮テクスチャは GPU で直接処理されるのです。 圧縮の設定によって通常の10倍の数のテクスチャーをアップロードする事もできてしまいます。 とても感動的てはないですか。

残念ながら、どの GPU ベンダーも他社よりも自分たちの方がうまくできると考えていたのか、圧縮テクスチャにはいくつかの種類が存在します。 どのような環境でゲームが動いているかによって、要求されるテクスチャの種類が変わってしまうのです。 前もってゲームに含めるファイル種別を知ることはどうすればできるのでしょう?

ここで ATF テクスチャの出番です。 ATF は Stage3D のために Adobe が作成したフォーマットです。 実際には、1つのテクスチャを4つの異なったフォーマットで同梱できるコンテナファイルとなっています。

  • PVRTC (PowerVR Texture Compression) フォーマットは、PowerVR GPU 向けに使われます。 どの世代の iPhone、iPod Touch、iPadもこのフォーマットをサポートします。

  • DXT1/5 (S3 Texture Compression) フォーマットは、S3 Graphics によって開発されたものです。 現在では Nvidia と AMD の両方の GPU でサポートされているため、大抵のデスクトップと一部の Android で利用が可能です。

  • ETC (Ericsson Texture Compression) フォーマットは、たいていのモバイルフォン(特に Android)で利用されています。

  • ETC2 フォーマットは、RGB 及び RGBA フォーマットの画像を高品質に圧縮します。 OpenGL ES 3 をサポートしている Android や iOS 端末であればどの端末でも扱う事ができます。

ATF は コンテナーのフォーマットだと言いました。 上記のフォーマットをどのようにでも組み合わせても含める事ができるという事です。

ATF container
Figure 37. ATF ファイルは、実際には他のフォーマットのコンテナファイルです。

もしも全てのフォーマットを含めるのなら(それがデフォルトの設定です。)、 そのテクスチャは Stage3D をサポートするどのデバイスでも読み込む事ができます。 デスクトップで動いていようが、iOSで動いていようが、Android で動いていようが、です。内部の挙動を気にする必要はありません。

しかし、もしもゲームが、例えば、iOS上だけでしか動かさないと言う場合、PVRTC 形式以外のフォーマッットは省いてしまう事ができます。 もしくは、ハイエンドなモバイルデバイス(少なくとも OpenGL ES 3 以上の)のみしかターゲットとしていないのであれば ETC2 形式だけを含めておけば良いのです。iOS でも Android でもうまく動作します。 このようにして、ゲームのダウンロードサイズを最適化する事もできます。

DXT1 と DXT5 の違いは、ただアルファチャンネルを後者のみがサポートすると言う点だけです。 この点については考慮する必要はありません。後述の ATF ツールは自動的に適切なフォーマットを選択してくれます。

ETC1 はルファチャンネルをサポートしませんが、Stage3Dは内部的に2枚のテクスチャを使うことにとってこの点をうまくカバーします。 再度言いますが、この処理は内部的に自動で行われます。

3.1.3. ATF テクスチャの作成

Adobe から ATF ファイルをコンバートして作成するツールと、ATF をプレビューするツールが提供されています。 AIR SDK の一部として配布されており、atftools フォルダ以下に存在します。

おそらく最も重要なツールは png2atf です。 下記は、基本的な使い方の例です。標準的な設定では、全てのタイプのフォーマットを内包したテクスチャを生成します。

png2atf -c -i texture.png -o texture.atf

上記コマンドを実行すると、下記のようなエラーメッセージが表示されるかもしれません。

Dimensions not a power of 2!

まだ説明していませんでしたが、これは ATF テクスチャの制限事項の一つです。 ATF テクスチャは、縦横のドット数が2の階乗のである必要があります。 多少めんどくさいですが、ほとんど問題にはなりません。大抵はテクスチャアトラスを変換するでしょうから。

大抵のアトラス画像の作成ソフトは、縦横のドット数が2の階乗であるテクスチャを書き出せるようになっています。

コマンドの実行が成功して書き出されたファイルは、 ATFViewer で確認する事ができます。

ATFViewer
Figure 38. ATFViewer ツール。

左のリストで ATF テクスチャが内部のどのフォーマットをプレビューするか選択する事ができます。 デフォルトでは、ミップマップ画像も作成されているのがわかります。

ミップマップ画像については、[Memory Management] チャプターにて取り上げます。

おそらく圧縮後の画像クオリティが多少低下しているのに気がつくかと思います。 これら全ての圧縮フォーマットは非可逆圧縮だからです。小さなメモリ消費を得るのと引き換えに品質が低下します。 どのくらい品質が問題になるかは、画像の種別によります。 自然を写した写真などは品質の低下があまり問題になりませんが、コミックイラストのような画像はエッジの品質低下が問題になります。

このツールでは様々なオプション設定が可能です。 例えば、iOS デバイスに適切な PVRTC フォーマットのみを含ませるなどです。

png2atf -c p -i texture.png -o texture.atf

ミップマップ画像を省いて、メモリを節約することもできます。

png2atf -c -n 0,0 -i texture.png -o texture.atf

もう一つの便利なユーティリティは、atfinfo です。 このツールは、ATF ファイルの詳細情報を表示します。 例えば、含まれるフォーマットの一覧、ミップマップの数、などです。

> atfinfo -i texture.atf

File Name          : texture.atf
ATF Version        : 2
ATF File Type      : RAW Compressed With Alpha (DXT5+ETC1/ETC1+PVRTV4bpp)
Size               : 256x256
Cube Map           : no
Empty Mipmaps      : no
Actual Mipmaps     : 1
Embedded Levels    : X........ (256x256)
AS3 Texture Class  : Texture (flash.display3D.Texture)
AS3 Texture Format : Context3DTextureFormat.COMPRESSED_ALPHA

3.1.4. ATF テクスチャを使う

圧縮されたテクスチャを Starling で扱うのは、他のテクスチャと同じようにシンプルです。 ATF ファイルのバイトアレイを Texture.fromAtfData() ファクトリーメソッドへ渡します。

var atfData:ByteArray = getATFBytes(); (1)
var texture:Texture = Texture.fromATFData(atfData); (2)
var image:Image = new Image(texture); (3)
1 生のデータをファイルなどから得ます。
2 ATF テクスチャを作成します。
3 他のテクスチャと同様に利用します。

これだけです。Starling では他のテクスチャと全く同じように使う事ができます。 アトラステクスチャの完全な代替候補であるとも言えます。

しかし、上記のコードは同期的に処理されてしまうため、終了まで AS3 コードの実行が止まってしまいます。 非同期処理でテクスチャをロードするには、引数にコールバックを指定します。

Texture.fromATFData(atfData, 1, true,
    function(texture:Texture):void
    {
        var image:Image = new Image(texture);
    });

2番目と3番目の引数はそれぞれ、スケールファクターをコントロールするものとミップマップ利用を指定するものです。 4番目の引数としてコールバックが渡されると、非同期ロード処理の指定となります。 Starling はロード処理の間も描画処理を邪魔されずに続ける事ができます。

コールバックが実行されるとすぐに、テクスチャは利用可能になります。 もちろん、ATF は AS3 ソースコードに直接埋め込む事も可能です。

[Embed(source="texture.atf", mimeType="application/octet-stream")]
public static const CompressedData:Class;

var texture:Texture = Texture.fromEmbeddedAsset(CompressedData);

しかしソース埋め込みの場合、非同期アップロードは出来ない仕様となっているので注意してください。

3.1.5. その他の情報

この話題について、さらなる情報は下記で得る事ができます。

3.2. コンテキストロス

Stage3D によるレンダリングは、全て "レンダーコンテキスト" と呼ばれる Context3D クラスのインスタンスを通して行われます。 レンダーコンテキストは、アクティブなテクスチャの一覧や頂点データなど、GPU の現在の設定を保持しています。 レンダーコンテキスト は GPU とのコネクションです。これがないと、Stage3D のレンダリングを行う事ができません。

ここで問題があります。このコンテキストはしばしば失われてしまうのです。 つまり、グラフィックスメモリ上にあった全てのデータへの参照を失うと言う事です。例として、テクスチャも失われます。

そのようなコンテキストの紛失 (コンテキストロス) は全てのシステムで同じ頻度で起こるわけではありません。 iOSmacOS ではほとんど再現せず、Windows は時々再現し、Android では頻繁に再現します。(画面を回転するだけで再現します!) これが起きてしまう事自体はどうしようもないので、最悪のケースが起きる事を想定し、コンテキストロスにあらかじめ準備しておく事が必要です。

3.2.1. デフォルトの振る舞い

現在のコンテキストを紛失した事を Starling が認識すると、下記のような一連の処理を実行します。

  • Starling は自動で新しいコンテキストを作成し、それまでと同じ設定で初期化します、

  • 全ての頂点及び頂点インデックスバッファを復元します。

  • 全ての頂点シェーダーとピクセルシェーダーを再コンパイルします。

  • テクスチャを可能な限り再ロードします。(メモリ/ディスク/その他から)

バッファの内容とプログラムを元に戻すことは問題ではありません。Starling は必要な全てのデータを保持しており、すぐに元に戻す事ができます。 しかし、テクスチャに関しては頭の痛い問題です。 最悪のケースの例を見てみましょう。テクスチャの生成に埋め込みビットマップを使っている場合の例です。

[Embed(source="hero.png")]
public static const Hero:Class;

var bitmap:Bitmap = new Hero();
var texture:Texture = Texture.fromBitmap(bitmap);

Texture.fromBitmap メソッドを呼び出した時は、ビットマップが GPU メモリにアップロードされます。 つまりビットマップがコンテキストの一部になったと言うことです。 コンテキストが永久に存在すると信頼できるのなら、これ以外にやる事はありません。

しかし、実際にはそのような信頼はありまません。テクスチャのデータはいつでもなくなりうるのです。 そのため、Starling はオリジナルのビットマップのコピーを作って保持しておきます。 最悪の事態となった時、テクスチャを復元するためにそのコピーが使われます。この処理は自動で行われます。

驚くべきことに、1つのテクスチャがメモリ上3箇所に存在することになります!

  • "Hero" クラス内(通常のメモリ)

  • コピーされているビットマップ(通常のメモリ)

  • テクスチャ(グラフィックスメモリ)

モバイル端末など、メモリ量がかなり制限されている場合、これは大問題であると言えます。 このような状態は避けたいものです!

下記のようにコードを多少変更することで、問題を若干ですが緩和する事が可能です。

// 代わりに 'fromEmbeddedAsset' メソッドを使う。
var texture:Texture = Texture.fromEmbeddedAsset(Hero);

このようにすると、Starling は埋め込みクラスから直接テクスチャを再生成します。(new Hero() を呼び出します。) つまりテクスチャはメモリ上に2箇所しか存在しない事になります。 埋め込みクラスに対しては、これがベストの対応です。

しかし、理想としてはメモリ上1箇所だけにテクスチャを持ちたい所です。 そのためには、アセットを埋め込んではいけません。代わりに、ローカルもしくはリモートからアセットを読み込むようにします。 その場合 URL のみを保持すればよく、実際のデータは元の場所から再読み込みができるのです。

2つの方法があります。

  • AssetManager を用いてテクスチャのロードを行う。

  • 手動でテクスチャを読み込み直す。

推奨する方法はいつでも AssetManager を使う事です。 メモリを浪費する事なくコンテキストロスに対応する事ができます。 テクスチャを復元するための特別処理を全く記述する必要がなくなります。

しかし、内部で何が行われているか知るのは良い事です。 ひょっとすると、手動でテクスチャを復元せざるを得ない状況がありえるかもしれません。

3.2.2. テクスチャの手動復元

Texture.fromEmbeddedAsset() は実際どのように動いているのでしょうか。 このメソッドの考えられる実装をみてみましょう。

public static function fromEmbeddedAsset(assetClass:Class):Texture
{
    var texture:Texture = Texture.fromBitmap(new assetClass());
    texture.root.onRestore = function():void
    {
        texture.root.uploadFromBitmap(new assetClass());
    };
    return texture;
}

root.onRestore コールバック内で重要な処理が行われている事がわかります。 しかし、root と書いているのは何でしょう?

実は、テクスチャ のインスタンスを生成した際、それはしばしば実際の完全なテクスチャではないのです。 他のテクスチャの一部の領域への参照かもしれません。(それを SubTexture と言います。) fromBitmap メソッドを呼び出しでさえも、その参照を返します。 (しかし、その理由を説明するのは、このチャプターの範疇を超えてしまいます。)

いずれにしろ、texture.root は いつでも、ConcreteTexture オブジェクトを返します。そして、そのオブジェクトには onRestore コールバックが存在します。 このコールバックはコンテキストロスの後に直接呼び出され、テクスチャを復元する機会を与えてくれます。

今回のコールバック例では、もう一度同じビットマップをインスタンス化して、ルートテクスチャとしてアップロードしてやります。 さあ、テクスチャが復元されました!

しかし、細部には悪魔が住んでいるものです。 onRestore コールバックが、知らぬ間に別のビットマップのコピーを保持してしまわないよう、厳重にコードを記述しましょう。 ここに一見無害ではあるものの、実はとてもよくないコールバックのコード例があります。

public static function fromEmbeddedAsset(assetClass:Class):Texture
{
    // このコードを実際に利用してはいけません!悪いコードの例です。

    var bitmap:Bitmap = new assetClass();
    var texture:Texture = Texture.fromBitmap(bitmap);
    texture.root.onRestore = function():void
    {
        texture.root.uploadFromBitmap(bitmap);
    };
    return texture;
}

好ましくない部分がどこだかわかりましたか?

問題は、このメソッドが Bitmap オブジェクトを生成し、コールバック内でそれを利用しているという事です。 このコールバックは、クロージャー と呼ばれるもので、周辺の変数とともに保持されるインライン(関数内)の関数です。 言い換えれば、メモリ上に関数オブジェクトが保持して、コンテキストロス発生時に呼び出される事が出来るようにします。 そして、はっきりとそのように設定をしていないにも関わらず、ビットマップのインスタンスがその内部に保持されます。 (実際は、bitmap をコールバック内で利用する事によってメモリに保持する行為しているのです。)

元のコードでは、ビットマップは参照されず、コールバック内で 作成 されています。 従ってクロージャーに bitmap インスランスが保持される事はないのです。 assetClass オブジェクトだけがコールバック内で参照されます。どちらにしろメモリ内にですが。

このテクニックはどんなシナリオでもうまく働きます。

  • テクスチャが URL からロードされているのなら、その URL を使ってコールバック内でビットマップを読み込み直します。

  • それが ATF テクスチャであってもやり方は同じです。異なるのは root.uploadATFData メソッドを代わりに使うだけです。

  • 従来の Flash のディスプレイオブジェクトのレンダリングをビットマップとして使っている場合、 コールバック内では、そのディスプレイオブジェクトを新しいビットマップにレンダリングし直してください。 (Starling の TextField クラスがちょうどそのような処理をしています。)

強調しますが、AssetManager はこの処理を自動でよしなに行なってくれます。 ここでは、ただそれがどのように実装されているのかを説明しておきたかったのです。

3.2.3. レンダーテクスチャー

レンダーテクスチャに関しても、コンテキストロスが悩ましい所です。 他のテクスチャと同じように、レンダーテクスチャも内容を失う事がありえます。しかし、簡単にそれを復元する方法が存在しないのです。 結局、レンダーテクスチャの内容というのは、動的なたくさんの描画処理の結果なのですから。

もしも、レンダーテクスチャー が見た目を賑やかす用途だけで使われているのなら(例えば雪面の足跡など)、 全てを消し去った状態そのままとしてしまうのでも良いかもしれません。 しかし、その内容がとても重要であるのなら、この問題はなんとか解決しなくてはいけません。

実際には、特にこれといった良い対応方法はありません。手動でテクスチャを描画し直して、内容を復元する必要があります。 onRestore コールバックがここでも役にたつかもしれません。

renderTexture.root.onRestore = function():void
{
    var contents:Sprite = getContents();
    renderTexture.clear(); // テクスチャの復元に必要
    renderTexture.draw(contents);
});

言いたい事はわかります。おそらくは、オブジェクト一つだけでなく、長い時間をかけてたくさんのオブジェクトを何度も描画していたのだと。 例えば、お絵かきソフトでは RenderTexture をキャンバスとして使い、たくさんのブラシストロークの重ね合わせで絵が描かれている事でしょう。

そのような場合、最初から同じ手順で実行し直せるように、必要な情報(操作手順)を保持しておく事が必要です。

さらにお絵かきアプリの場合について考えるなら、どちらにしろそのアプリはアンドゥ・リドゥをサポートしたい所です。 そのようなアンドゥ・リドゥのシステムを実装する場合、個々の処理内容を表すオブジェクトをリストの形式で保持しておくのが一般的です。 コンテキストロスの場合でも、その仕組みを再利用して、全ての描画操作を復元する事ができるでしょう。

さて、システムを実装する前に、意識しなくてはいけない潜在的な問題があります。 root.onRestore コールバックが呼ばれる際、たいていの場合で全てのテクスチャがまだ利用できる状態ではないという事です。 それらも同様に復元されなくてはならず、それにはしばらく時間がかかるのです!

もしもテクスチャを AssetManager を用いてロードしているのなら、そのあたりは自動でうまく処理してくれます。 その際は代わりに、TEXTURES_RESTORED イベントを購読しましょう。 また、パフォーマンスを考慮して、drawBundled メソッドを忘れずに使うようにしましょう。

assetManager.addEventListener(Event.TEXTURES_RESTORED, function():void
{
    renderTexture.drawBundled(function():void
    {
        for each (var command:DrawCommand in listOfCommands)
            command.redraw(); // `renderTexture.draw()` を実行します。
    });
});
この場合は、クリア命令を実行する必要はありません。なぜなら、それが onRestore コールバックでのデフォルトの振る舞いであって、それを変更しているわけではないので。 上記処理は別のコールバック (Event.TEXTURES_RESTORED) であり、onRestore コールバックはデフォルトの振る舞いから変更されていない事を覚えておきましょう。

3.3. メモリ管理

たくさんの Starling デベロッパーがアプリやゲームをモバイル向けに作るためにこのフレームワークを利用しています。 そして遅かれ早かれそのほとんど全員が、開発に苦労して、モバイル端末は嫌になるほどメモリが少ないと言う事を知ります。 なぜでしょう?

  • ほとんどのモバイル端末はとても高い解像度のスクリーンを持ちます。

  • そのような端末で2Dゲームを動かす場合、同様に高い解像度のテクスチャが必要になります。

  • 利用可能な RAM 容量は、テクスチャデータ全てを保持するには少なすぎるのです。

つまり、ハードウェア的に見てひどい組み合わせだ、と言えるでしょう。

では、メモリが足りなくなった場合は どのような事が起きるでしょう? ほとんどの場合、エラー番号 3691 (このリソース種類に対応するリソースを使い果たした) と言う、悪名高いエラーが起こり、アプリはクラッシュするでしょう。 下記に続くのは、このたちの悪いエラーを避けるためのヒントです。

3.3.1. 無駄なメモリ浪費をなくす

もしも、あるオブジェクトがその後必要とされないのなら、忘れずに dispose メソッドを呼んでやりましょう。 Flash のオブジェクトと異なり、ガベージコレクターは Stage3D リソースのメモリを掃除してくれません! 開発者自身がメモリ管理に責任を持つ必要があるのです。

テクスチャ

テクスチャは扱う様々なオブジェクトの中で最も重要なものです。 いつでもメモリ領域全体で一番大きな割合を占めるでしょう。

もちろん、Starling はこれの問題うまくカバーしようと働きます。 例えば、アトラステクスチャを扱う際は、アトラス側を廃棄するだけでよく、実際のサブテクスチャは廃棄する必要がありません。 アトラスは GPU メモリを必要とし、"部分部分" の各テクスチャはそのアトラスをただ参照するだけとなっています。

var atlas:TextureAtlas = ...;
var hero:Texture = atlas.getTexture("hero");

atlas.dispose(); // この処理は "hero" テクスチャも同時に廃棄します。
ディスプレイオブジェクト

ディスプレイオブジェクト自体は多くのグラフィックメモリを必要としない(いくつかは全く必要としない)のですが、それでもきちんと廃棄するのは良い習慣と言えます。 特にテキストフィールドは大きなオブジェクトなので、慎重に扱いましょう。

ディスプレイオブジェクトコンテナは期待通りに、子孫要素全ての面倒も見てくれます。 コンテナを破棄すると、子孫のオブジェクトも同時に自動的に破棄されます。

var parent:Sprite = new Sprite();
var child1:Quad = new Quad(100, 100, Color.RED);
var child2:Quad = new Quad(100, 100, Color.GREEN);

parent.addChild(child1);
parent.addChild(child2);

parent.dispose(); // この処理で子ディスプレイオブジェクトも破棄されます。

概して、最近のバージョンの Starling では、ディスプレイオブジェクトの破棄に関して、寛容になりました。 ほとんどのディスプレイオブジェクトはもはや Stage3D のリソースを保持していないので、ディスプレイオブジェクトを破棄するのを忘れたとしても大きな問題にはなりません。

イメージ

ここにまず最初の落とし穴が存在します。イメージを破棄してもテクスチャは破棄されません。

var texture:Texture = Texture.fromBitmap(/* ... */);
var image:Image = new Image(texture);

image.dispose(); // ここでテクスチャは破棄されません!

なぜなら Starling は、このテクスチャを今後も利用する気があるのか、知りようがないからです。 実際、同じテクスチャを共有する別のイメージをすでに利用しているかもしれません。

逆に、そのテクスチャが他の場所で使われていない事を知っているのであれば、破棄してしまいましょう。

image.texture.dispose();
image.dispose();
フィルター

フラグメントフィルターも多少デリケートです。 オブジェクトを破棄する時、同様にフィルターも破棄されます。

var object:Sprite = createCoolSprite();
object.filter = new BlurFilter();
object.dispose(); // ここでフィルターも破棄されます。

しかし、下記のような似たコードではフィルターが破棄されないのです。

var object:Sprite = createCoolSprite();
object.filter = new BlurFilter();
object.filter = null; // ここでフィルターは破棄 *されません。*

もう一度言いますが、 Starling は、このフィルターを今後も利用する気があるのか、知りようがないのです。

しかし、実際にはこれは問題になりません。 フィルターが破棄されてなくても、Starling が全てのリソースを消去するからです。 よって、この操作でメモリリークを引き起こすことはありません。

以前の Starling のバージョン(2.0未満) では、この場合にメモリリークを起こします。

3.3.2. テクスチャを埋め込まない

Embed メタデータ設定を行い、ActionScript の開発者はビットマップを直接 swf ファイル内に埋め込んできたものです。 web コンテンツ作成においてこれはとても有効です。全てのデータを1つのファイルにまとめる事ができるのですから。

先の [コンテキストロス] セクションにて、Starling または一般的な Stage3D コンテンツでは、このアプローチがいくつか深刻な問題をはらんでいる事を確認しました。 1つのテクスチャデータがメモリ内に少なくとも2箇所、1箇所は通常の(グラフィックス用ではない)メモリに、もう1箇所はグラフィックスメモリに、存在してしまうのです。

[Embed(source="assets/textures/hero.png")]
private static var Hero:Class; (1)

var texture:Texture = Texture.fromEmbeddedAsset(Hero); (2)
1 このクラスは通常メモリ内に保持されます。
2 このテクスチャはグラフィックメモリ内に保持されます。

このサンプルでは Texture.fromEmbeddedAsset メソッドを使ってテクスチャをロードしている事に注目してください。 [コンテキストロス] セクションでその理由は述べました。他の方法(Texture.fromBitmap)ではさらに余分なメモリを使ってしまうのです。

テクスチャを確実に1つしかメモリに保持しないように保障するたった一つの方法は、URL からそれをロードする事です。 画像の読み込みに AssetManager を使っているなら、大した手間にはなりません。

var appDir:File = File.applicationDirectory;
var assets:AssetManager = new AssetManager();

assets.enqueue(appDir.resolvePath("assets/textures"));
assets.loadQueue(...);

var texture:Texture = assets.getTexture("hero");

3.3.3. RectangleTexture を使う

Starling の Texture クラスは、実際は2つの Stage3D クラスの単なるラッパーです。

flash.display3D.textures.Texture

どのプロファイルでも利用可能です。ミップマップとラッピングをサポートしますが、縦横のサイズが2の倍数である事が要求されます。

flash.display3D.textures.RectangleTexture

BASELINE プロファイル以降で利用できます。ミップマップトラッピングをサポートしませんが、任意のサイズを取る事ができます。

あまり知られていないのですが、前者はちょっと変わった短所があります。必要であろうともなかろうと、いつでもミップマップ用のメモリを確保してしまうのです。 つまり、1/3 の分だけ余計にメモリを浪費している事になります!

それにより、後者の RectangleTexture を使う事が好まれます。 Starling は利用可能な場合、後者のタイプのテクスチャを優先して使います。

しかし、少なくとも BASELINE プロファイルで動作できる環境でないとそのような選択はできません。またミップマップが必要な場合も使えません。 1つ目の条件は 利用可能な Context3D プロファイルのうち、最も高度な物を選択する事で満たす事ができます。 これは、Starling のデフォルトのコンストラクタの振る舞いです。

// このように Starling を初期化した場合、
... = new Starling(Game, stage);

// 下記の設定をしたのと同じ事になります。
... = new Starling(Game, stage, null, null, "auto", "auto");

最後の引数の auto は、Starling に 選択可能なプロファイルのうち、一番上位のものを使うように指定しています。 つまり、デバイスが RectangleTexture をサポートするなら、Starling はそれを優先して使うという事です。

ミップマップに関しては、要求された場合のみ作成されます。 いくつかの Texture.from…​ で始まるテクスチャ生成系のメソッドは、その指定パラメータを持っています。 そして、AssetManager には useMipMaps プロパティが存在します。 デフォルトでは、ミップマップが無効になる設定になっています。

3.3.4. ATF テクスチャを使う

ATF テクスチャ に関しては、すでに考察しました。 しかし、このセクションでも、もう一度取り上げる意義があります。 GPU が JPG や PNG 圧縮のデータをそのまま使う事ができない事を思い出してください。 それらのファイルはテクスチャは一度展開され、非圧縮状態でグラフィックメモリにアップロードされます。

ATF テクスチャの場合は異なります。圧縮された状態のまま描画され、たくさんのメモリを節約します。 もしも ATF テクスチャのセクションを読み飛ばしてきたのなら、目を通しておく事をオススメします!

ATF テクスチャの悪い点は、もちろんですが、イメージのクオリティが劣化する事です。 しかし、全てのゲームに適用できるわけではありませんが、下記のようなテクニックを適用する事が可能です。

  1. 実際に必要な大きさより若干大きくテクスチャを生成する。

  2. ATF ツールで圧縮。

  3. ランタイムでは、実際に必要なサイズに縮小してテクスチャを利用する。

このやり方でも、かなりたくさんのメモリの節約が可能です。そして画質の劣化ははっきりとは確認できません。

3.3.5. 16ビットテクスチャを使う

ATF テクスチャが適切でない場合、おそらくアプリケーションでは色数が制限されたアニメ調の絵柄を用いているのかと思われます。 良いニュースがあります。そのようなタイプのテクスチャでは、また別の解決方法が適用できます!

  • デフォルトのテクスチャフォーマット (Context3DTextureFormat.BGRA) はピクセルごとに32ビット(1つのチャンネルに8ビット)の容量を使います。

  • それとは別のフォーマット (Context3DTextureFormat.BGRA_PACKED) が存在し、こちらは半分のサイズ、ピクセルごとに16ビット(1つのチャンネルに4ビット)の容量を使います。

Starling ではこのフォーマットを Texture.from…​ 系のメソッドの format 引数で指定するか、AssetManager の textureFormat プロパティで指定する事ができます。 これにより 50% ものメモリを節約する事ができます!

当然、この処理は画像のクオリティの低下も招きます。 もしもグラーデーションを利用しているのなら、16ビットテクスチャはむしろ汚くなります。 しかし、ディザリングを使う事でその問題を解決する事ができます。

Dithering
Figure 39. ディザリングで色深度が減った事をカバーする事ができます。

わかりやすくするため、上記のサンプルのグラデーションはたったの16色(4ビット)まで減色されています。 このような少ない色数でも、ディザリングで画像のクオリティが受け入れられるレベルに保つ事ができます。

大抵の画像編集ソフトでは、色深度を減らす際に自動でディザリングを適用してくれます。 TexturePacker でも、それを行う事が可能です。

AssetManager は読み込みファイルごとに、適切な色深度を選択するように設定する事ができます。

var assets:AssetManager = new AssetManager();

// 16bitテクスチャをキューに積む
assets.textureFormat = Context3DTextureFormat.BGRA_PACKED;
assets.enqueue(/* ... */);

// 32bitテクスチャをキューに積む
assets.textureFormat = Context3DTextureFormat.BGRA;
assets.enqueue(/* ... */);

// 読み込みの開始
assets.loadQueue(/* ... */);

3.3.6. ミップマップを避ける

ミップマップは元となるテクスチャを縮小したバージョンの事です。レンダリングのスピードを上げつつ、エイリアシングを軽減する目的で使われます。

Mipmap
Figure 40. ミップマップを持つテクスチャの例。

Starling のバージョン2からは、デフォルトでミップマップを生成しない仕様となりました。 デフォルト設定として好ましい事がわかったからです。なぜなら、

  • テクスチャの読み込みが速い。

  • テクスチャが使うメモリ量が少ない。(オリジナルのピクセル分だけで済みます。)

  • ぼやけた描画になる事を避ける事ができる。(ミップマップはしばしば曖昧な見た目を生じます。)

一方で、ミップマップを有効とするとオブジェクトがかなり縮小されていた場合は、描画速度が著しく上がります。 そして表示にエイリアシングがかかる事を避ける事ができます。(エイリアスとぼやけは正反対のものです。) ミップマップを有効にするには、Texture.from…​ 系のメソッドで対応するパラメータを設定してください。

3.3.7. ビットマップフォントを使う

すでに議論されましたが、テキストフィールドでは2種類のフォントをサポートします。TrueType フォントと、ビットマップフォントです。

TrueType フォントはとても簡単に使えますが、いくつかの短所も持っています。

  • テキストを変更するたびに、新しくテクスチャが生成されグラフィックメモリーにアップロードされます。これは時間がかかる処理です。

  • たくさんのテキストフィールドを生成したり、大きなものを生成した場合、たくさんのテクスチャメモリが必要となります。

一方で、ビットマップフォントは、

  • 更新が高速です。

  • 必要なメモリ量は一定です。(あらかじめ用意した glyph テクスチャのみとなります。)

これらの理由で、Starling 内にテキストを表示する方法として好ましいのです。 個人的には、いつでもビットマップフォントを使う事をオススメします!

ビットマップフォントは16ビットテクスチャを適用する候補として適切です。なぜなら大抵は白一色データが、テキストフィールド設定色にランタイムで着色されるからです。

3.3.8. テクスチャアトラスを最適化する

できる限りコンパクトにテクスチャアトラスをパックする事はとても重要な事でしょう。 TexturePacker のようなツールでは下記のようなオプション指定が可能です。

  • 周辺の透明部分をトリムする。

  • より効率よくパックする場合、テクスチャを90度回転して扱う。

  • カラーの深度を減らす。(上の考察を参照。)

  • 重複する画像を削除する。

  • その他。

これらの機能をぜひ利用しましょう! たくさんのテクスチャを1枚のアトラスに収める事で、メモリ全体の浪費を抑えるだけでなく、ドローコールの回数も抑える事ができます。 (詳しくは次のチャプターで取り扱います。)

3.3.9. Adobe Scout を使う

Adobe Scout は、軽量かつ包括的な ActionScript と Stage3D のためのプロファイリングツールです。 どんな Flash または AIR アプリケーションに対して、モバイル端末上で動いていようともブラウザで動いていようとも、コードの変更なしに素早くプロファイリングを行う事ができます。 そして、Adobe Scout は素早く効率的にパフォーマンスに影響を与えうる問題を検出します。

Scout を使うと、パフォーマンスのボトルネックになる ActionScript のコードを見つけられるだけでなく、 時間とともに散り積もって行く通常メモリ及びグラフィックメモリの浪費についての詳細を知る事もできます。 非常に素晴らしい事です!

Adobe Scout は、Adobe Creative Cloud メンバーシップの 無料 契約があれば利用する事ができます。ツールを入手するために有料の契約を結ぶ必要はありません。 次のリンクは Thibault Imbert 氏による、Adobe Scout をどのように使うかの詳細を説明する素晴らしい内容のチュートリアルビデオです。 Getting started with Adobe Scout
Adobe Scout
Figure 41. Adobe Scout

3.3.10. 常にStat表示を確認する

Stat表示 (starling.showStats メソッドを実行すると表示されます。) には、通常メモリとグラフィックスメモリの情報がともに含まれています。 開発中はそれらの値に注目し続ける価値があります。

そうですね、しばしば通常メモリの値は誤解される事があります。ガベージコレクションがいつ起こるかは、わからないのです。 一方、グラフィックメモリに関してはとても正確な値が表示されます。 テクスチャを作ると、メモリ使用量が上がります。テクスチャを破棄すると使用量が下がります。これはすぐさま確認できます。

実際、この機能を Starling に追加した時、Starling のデモアプリにメモリリークを発見するのに5分ほどしかかかりませんでした。 その際は、下記のようなアプローチで利用しました。

  • メインメニューにて、GPUメモリの利用率を測定します。

  • デモシーンに1つづつ移動します。

  • メインメニューに戻るたびに、GPUメモリが元の値に戻っているかを確認します。

  • あるシーンから戻ってきた際、メモリ量が元に戻りませんでした。実際、コードレビューをしてみると、一つテクスチャを破棄し忘れているのがわかりました。

The statistics display
Figure 42. Stat表示で現在のメモリ利用量が確認できます。

言うまでもなく、Adobe Scout では、さらに詳細なメモリ利用量を測定できます。 しかし、Stat表示はいつでも利用する事ができるので、それがなければ簡単に見過ごしてしまうような事をも過ごさずに済むのです。

3.4. パフォーマンス チューニング

Starling は Flash のディスプレイリストの構成を模倣していますが、内部実装の仕組みは全く異なっています。 最も良いパフォーマンスを得るためには、どのような仕組みで動いているのか、そのコンセプトのキーを理解する必要があります。 下記に記す内容は、ゲームをできるだけ早く動かすために必要なベストプラクティスです。

3.4.1. 一般的な AS3 のチップス

いつでもリリースビルドを使う

最初に考えなくてはならない最も大事なルールは、パフォーマンステストを行う際は、必ずリリースビルドを使うという事です。 通常の Flash プロジェクトと異なり、Stage3D を使ったフレームワークを扱う場合、リリースビルドとデバッグビルドには大きな違いがあります。 スピードの差は膨大です。開発対象のプラットフォームによっては、デバッグビルドのフレームレートと何倍もの差が出る事があるでしょう。

  • Flash Builder では、Project ▸ Export Release Build をクリックする事でリリースビルドを作成できます。

  • Flash Develop では、"Release" 設定を選択して、プロジェクトをビルドしましょう。 その後、"ipa-ad-hoc" もしくは "ipa-app-store" オプションを選択して、"PackageApp.bat" スクリプトを実行します。

  • IntelliJ IDEA では、Build ▸ Package AIR Application を選択し、アンドロイドでは "release"、 iOS では "ad hoc distribution" を選択します。 AIR プロジェクトでない場合は、モジュールのコンパイラオプション設定にて、"Generate debuggable SWF" の選択を外します。

  • Starling プロジェクトをコマンドラインでビルドしている場合は、必ず、-optimize を true にして -debug が false になるようにします。

Flash Builder Dialog
Figure 43. Flash Builder のダイアログに惑わされないようにしましょう。
ハードウェアをチェックする

Starling が本当に GPU レンダリングで動いているのかを確かめましょう。 やり方は簡単です。もしも、Starling.current.context.driverInfo の値が Software という文字列を含んでいたら、それは Stage3D がフォールバックモードになっている事を示します。 そうでなければ GPU を使ったレンダリングが行われています。

また、いくつかのモバイルデバイスでは、 バッテリーセーブモード で動いている事があります。 パフォーマンステストを行う場合は、それが間違いなく off となっている事を確認しましょう。

フレームレートの設定

どのように頑張っても、何故かフレームレートが 24FPS で頭打ちになった場合、おそらくまだ目的とするフレームレートを設定しておらず、Flash Player のデフォルト設定になっているのかと思われます。

変更するには、起動クラスの適切な metadata を使うか、stage の framerate 設定を行います。

[SWF(frameRate="60", backgroundColor="#000000")]
public class Startup extends Sprite
{ /* ... */ }

// もしくはソース中のどこかで
Starling.current.nativeStage.frameRate = 60;
Adobe Scout を用いる

Adobe Scoutメモリー利用状況の分析 だけに役に立つのではありません。パフォーマンスのプロファイリングという事であれば、とてもパワフルに役に立ちます。

Scout を使うと、Starling 内外の ActionScript 各々のコードが、どのくらい実行に時間がかかっているのか確認する事ができます。 これはとても有用です。なぜなら、最適化処理を施すべき箇所を示してくれるからです。

そのような場所の特定をしない場合、実際には全くフレームレートの改善に役に立たないコードを最適化してしまうかもしれません!

早い段階でのパフォーマンスチューニングは、諸悪の根源! である事を知っておいて下さい。

古典的なプロファイラと比べて良いと言える部分は、Scout はリリースビルドの(デバッグモードではなく、全ての最適化がなされた)swfやAIRを対象にしても動くという所です。 Scout で計測される内容がとても正確な事が保証されます。

ロードされたイメージを非同期でデコードする

デフォルトでは、Loader クラスを PNG や JPEG をロードするために使った場合、イメージデータはすぐさまデコードされません。実際に最初に使うタイミングでデコードされるのです。これはメインスレッド上で処理され、テクスチャの作成時に青アプリケーションが突っかかるような状態になる事があります。そのような事態を避けるには、デコードのポリシー設定を ON_LOAD にしましょう。そうすれば、デコード処理はローダーのバックグラウンドスレッドで直接行われるようになります。

loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD;
loader.load(url, loaderContext);

ところで、イメージのロードには Starling の AssetManager を使っているでしょうか? そうであれば、上記のテクニックはすでに使われているので、気にしなくても大丈夫です。

"for each" の利用を控える

頻繁に繰り返されるループや深くネストされたループを扱う場合、for each を避けた方が良いです。 原始的な for i という記述をする方が、 より良い パフォーマンスをもたらします。 さらに、ループ終了条件がループごとに1度しか評価されないように気をつけましょう。別の変数にそれを保持するのがさらに速いという事です。

// やや遅い:
for each (var item:Object in array) { ... }

// 取り良い:
for (var i:int=0; i<array.length; ++i) { ... }

// 最も速い:
var length:int = array.length;
for (var i:int=0; i<length; ++i) { ... }
アロケーションを避ける

たくさんの一時的なオブジェクトを作成して利用するのは避けましょう。利用したメモリを後にガベージコレクターによって回収される必要が生じます。その間小さなしゃっくり(プチフリーズ)を伴うかもしれません。

// 悪い例:
for (var i:int=0; i<10; ++i)
{
    var point:Point = new Point(i, 2*i);
    doSomethingWith(point);
}

// 良い例:
var point:Point = new Point();
for (var i:int=0; i<10; ++i)
{
    point.setTo(i, 2*i);
    doSomethingWith(point);
}

実際には、Starling は上記の最適化を手助けするクラスが存在します。Pool というクラスです。 このクラスは頻繁に利用されるオブジェクトを再利用のために保持する"プール"を提供してくれます。 保持されるクラスは_Point_、 RectangleMatrix などです。 プールからこれらのオブジェクトを "借りて"、使い終わった後はそこへ返しましょう。

// 最も良い例:
var point:Point = Pool.getPoint();
for (var i:int=0; i<10; ++i)
{
    point.setTo(i, 2*i);
    doSomethingWith(point);
}
Pool.putPoint(point); // この処理を忘れない事!

3.4.2. Starling 固有のチップス

ステージの変化を最小にする

知っての通り、Starling は Stage3D をディスプレイリストの描画に利用します。これは全ての描画処理が GPU で行われる事を意味します。

ここで、Starling はQuad (矩形) の1つ1つを個別に GPU に送って描画させる事もできます。実際、これは Starling の最初の頃のリリースで行なっていた方法です。しかし、最高のパフォーマンスを出すために、GPU にはいっぺんにたくさんのでデーターを渡して1回で描画してもらうのが良いのです。

これが、その後の Starling のバージョンでは、できるだけたくさんの Quad をバッチして GPU に送っている理由です。しかし、バッチ処理でまとめられるのは似たようなプロパティを持っている Quad だけなのです。Quad の状態が切り替わるたび、"ステートの変更" が生じて、そこまでのバッチが一度描画されます。

このセクションでは QuadImage を同義的に扱っています。ImageQuad のサブクラスで、いくつかのメソッドが追加されただけなのです。さらに、QuadMesh を継承しており、以下に書く事は Mesh に対しても同様に当てはまります。

以下は、ステートを決定する重要なプロパティです。

  • テクスチャー の設定。 (しかし、同じアトラスのサブテクスチャーはステートを変えません。)

  • ディスプレイオブジェクトの ブレンドモード 設定。

  • メッシュ/Quad/イメージ の textureSmoothing 設定。

  • メッシュ/Quad/イメージ の textureRepeat 設定。

できる限りステート変化を抑えるようにシーンの設定をする事で、レンダリングのパーフォーマンスは著しく向上するでしょう。

再度言います、Starling の statics 表示は有益な情報を与えてくれます。1フレームでどれだけのドローコールが発生したかの回数を表示します。ステートの変化が多いほど、ドローコール回数も多くなります。

Statistics Display
Figure 44. statistics 表示には 現在のドローコール数も表示されています。

statistics 表示自体もドローコールを消費します。 しかし、Starling はその増加分を差し引いたドローコール数を表示してくれます。

取り組むべき事は、できる限りステートの変化を抑える事、です。具体的な方法については下に続けます。

ペインターアルゴリズム

どのようにしてステートの変化を抑えるかを知るためには、まず Starling がどのようにディスプレイオブジェクトを処理するのかを知らなくてはいけません。

Flash のように、Starling もディスプレイリストを処理するために ペインターアルゴリズム を利用します。これは、実際の画家のようにシーンを描画するアルゴリズムです。最背面のオブジェクト(例えば背景画像など)から処理を初めて、だんだんと前面に進んでいきます。前に描画したオブジェクトの上に次のものを重ねて描画していきます。

Painter's algorithm
Figure 45. ペインターアルゴリズムで描画されたシーン。

Starling でシーンを生成した際、3つのスプライトを配置したとします。一つは山の領域、1つは地面、一つは植物を描画という具合です。山は一番下層にあり (index 0)、植物は最前面 (index 2) にあります。各々のスプライトはそれぞれを構成する画像をいくつか含むとします。

Landscape Scene Graph
Figure 46. 上記の設定によるシーン構造。

レンダリングのタイミングでは、Starling は図の左の "Mountain 1" から右に向かい、一番右の "Tree 2" に到達するまで処理を進めます。もしも、それら全てのオブジェクトが異なったステートである場合、6回のドローコールが必要となってしまいます。この事は、それぞれのテクスチャーを異なるビットマップ画像から読み込んでいる場合に実際に起こりえる事です。

テクスチャーアトラス

これが、テクスチャーアトラスがとても重要である理由の1つなのです。1つのアトラスから全てのテクスチャーを読み取る場合、Starling は一度に全てのオブジェクトを描画する事ができます。(上の一覧にあるプロパティが変化しない場合に置いては。)

Landscape Scene Graph 2
Figure 47. 1つのアトラスを利用した、先ほどと同じシーンの構造。

ここから導かれる結果は、いつでも テクスチャは1枚のアトラスにまとめられるべきだという事です。ここでは、各々の画像が同じアトラスを利用しています。(描画された全てのノードは同じカラーの設定です。)

時には1枚のアトラスに全てのテクスチャーが収まらない場合もあります。テクスチャーのサイズには限度があり、遅かれ早かれ空きスペースは足りなくなってしまうでしょう。しかし、大丈夫です。うまい方法でテクスチャーを調整する事ができます。

Landscape Scene Graph 3
Figure 48. オブジェクトの順番が違いを生む。

この例の両者では2つのアトラスを利用しています。(カラー設定はそれぞれに対して1つだけです。)左のディスプレイリストでは各々のオブジェクトごとにステートの変更がなされてしまうのに対して、右のディスプレイリストでは全てのオブジェクトを描画するのに2回だけのバッチ処理で抑えられています。

MeshBatch クラスを用いる

たくさんの Quad やメッシュをまとめて描画する最も速い方法は、MeshBatch クラスを利用する事です。これは Starling の全てのレンダリングの内部処理で使われているのと同じクラスで、とてもよく最適化がなされています。[6] 下記のように使います。

var meshBatch:MeshBatch = new MeshBatch();
var image:Image = new Image(texture);

for (var i:int=0; i<100; ++i)
{
    meshBatch.addMesh(image);
    image.x += 10;
}

addChild(meshBatch);

気づいたでしょうか?ここでは1つのイメージを何度でも好きなだけバッチ処理対象に追加しています。さらに、この追加に伴う処理はとても速いのです。例えば、なんのイベントも発火しません。(スプライトなどのコンテナにオブジェクトを追加した場合はイベントが発火します。)

しかし、下記のようなマイナスな面もあります。

  • バッチ対象に追加する全てのオブジェクトは、同じステートでなくてはいけません。(例えば、テクスチャーが同じアトラスから構成されている、など。)一番最初に MeshBatch に追加するイメージがそのステートを決定する事になります。完全にリセットすること以外に、後からステートを変更することはできません。

  • バッチ対象として追加できるのは、メッシュクラスかそのサブクラスのインスタンスだけです。(Quadイメージ、別の MeshBatch などのクラスもそれに含まれます。)

  • 追加したオブジェクトを後から削除するのはとても特殊な対応を取る事になります。全体の大きなメッシュから頂点やインデックスを直接削除する事しかできないのです。しかし、ある場所のメッシュを上書きするようなこともできます。

これらの理由から、特別な状況のみにこの方法は適している事になります。(例えば、BitmapFont クラスは内部でメッシュのバッチ処理を行なっています。)しかし、そのような状況であるならば、MeshBatch が間違いなく最も速い描画方法となります。Starling において、たくさんのオブジェクトをこのクラスより効率的に描画する方法は見つけられないでしょう。

テキストフィールドをバッチする

デフォルトでは、1つのテキストフィールドは1回のドローコールを必要とします。フォントのテクスチャが他のメインのテクスチャと同じだったとしてもです。何故ならば、長いテキストはバッチするために、たくさんの CPU 時間を必要とするからです。

MeshBatch にメッシュをコピーせず、そのままシンプルにドローした方が速いのです。

しかし、テキストフィールドのテキスト数が少ない場合(経験的には16文字より少ない場合)、TextField の batchable プロパティを有効にする事ができます。これが有効の場合、テキストフィールドは他のディスプレイオブジェクトと同様にバッチ処置されます。

BlendMode 設定を NONE にする

もしも矩形テクスチャが完全に不透明であるのなら、ブレンド処理を無効にする事で、GPU の負荷を減らしてあげましょう。 大きな背景画像などで役に立つと思います。

backgroundImage.blendMode = BlendMode.NONE;

通常は、このテクニックによって描画ステートの変化ももたらす(ドローコールが増える)ため、多用してはいけません。小さな画像に対しては、おそらく適用する価値がありません。(なんにしろ、何か別の理由で描画ステートが変化するのなら避けましょう。)

ステージカラー設定を活用する

ゲーム中にはしばしば、ステージに設定されたカラーのエリアが見えない状態である事があります。なぜならステージ上にあるイメージやメッシュによって隠されてしまっているからです。その場合、ステージ色の設定は完全な黒(0x0)か完全な白(0xffffff)に設定するようにしましょう。いくつかのモバイルプラットフォームでは、context.clear が '1' または '0' で呼ばれた場合に、ハードウェアの最適化処理が走る事があります。何人かの開発者が1フレームごとにミリ秒単位でのレンダリング時間をレポートしてくれましたが、そのような簡単な変更で大きな速度改善があったとの事です!

[SWF(backgroundColor="#0")]
public class Startup extends Sprite
{
    // ...
}

一方で、バックグラウンドカラーがフラットな色である場合、それをステージをうまく使う事ができます。イメージや着色された Quad を配置するのでなく、ステージ色を設定しましょう。どちらにしろ Starling は1フレームに1回画面をクリアし直すのです。したがってステージカラーを変更したとしても描画コストは何も変わりません。

[SWF(backgroundColor="#ff2255")]
public class Startup extends Sprite
{
    // ...
}
width 及び height の参照を控える

widthheight プロパティへの参照処理は予想するよりもずっと高コストな物となっています。特にスプライトにおいてそれが顕著です。行列演算が必要であり、各々の子孫ディスプレイオブジェクトの各々の頂点を計算に入れて処理させる必要があります。

そのような理由で、例えばループの中など、繰り返しそれらのプロパティにアクセスする事は避けるようにしましょう。いくつのかのクラスでは、代わりに定数値で判断した方が効率的です。

// 悪い例:
for (var i:int=0; i<numChildren; ++i)
{
    var child:DisplayObject = getChildAt(i);
    if (child.x > wall.width)
        child.removeFromParent();
}

// 良い例:
var wallWidth:Number = wall.width;
for (var i:int=0; i<numChildren; ++i)
{
    var child:DisplayObject = getChildAt(i);
    if (child.x > wallWidth)
        child.removeFromParent();
}
コンテナのタッチ判定を無効にする

指やマウスを画面上でスライドして動かす際、Starling はどのオブジェクトがその下に存在するのか探索しなければいけません。これは高負荷な処理です。最悪の場合、全てのディスプレイオブジェクトとの当たり判定を行わなくてはいけないのです。

よってタッチ判定される必要のないオブジェクトに関しては、タッチ無効の設定にしてしまうと状況が改善します。コンテナであるディスプレイオブジェクトのタッチを完全に無効としてしまうのが最善です。そうすれば Starling が子孫ディスプレイオブジェクト全ての当たり判定を行う必要がなくなります。

// 良い例:
for (var i:int=0; i<container.numChildren; ++i)
    container.getChildAt(i).touchable = false;

// さらに良い例:
container.touchable = false;
ステージ領域から外れたオブジェクトを隠す

Starling はディスプレイリスト上のどのオブジェクトも GPU へと送信します。これは、オブジェクトがステージの領域外にあった場合でも同様です。

こう思うかもしれません。Starling は目に見えないそれらのオブジェクトを無効にしてくれないのだろうと。その理由は、表示されているかどうかを普遍的にチェックする事は、とても計算が高くつくからです。実際にそれはとても割高な処理で、全てのオブジェクトを GPU に送信して、そちらでクリッピングして表示した方が早いのです。GPU はそのような処理に置いて とても効率的に働き、オブジェクトが画面外である場合、レンダリング処理の早い段階で、描画対象から取り除かれます。

しかし、やはりデータのアップロードには時間がかかります。そしてそれを避ける事は可能です。ハイレベルなゲームロジックでは、オブジェクトが実際に表示されているかのチェックはしばしば簡単なものとなります。(例えば、ただ定数に対して、x及びy座標をチェックするだけで済む事があります。)もしもたくさんのオブジェクトがその表示領域から外れている場合、チェックする努力を払う価値はあります。それらをステージから取りさるか、visible 値を false にしましょう。

Event オブジェクトをプールする

Starling は Flash のシステムにはない、イベントの発火に関する新しいメソッドを追加しました。

// 今までのやり方
object.dispatchEvent(new Event("type", bubbles));

// 新しいやり方
object.dispatchEventWith("type", bubbles);

新しいアプローチは、今までのものとあまり変わりがないように見えます。しかしシーンの裏側では、イベントオブジェクトの使い回しを行なっています。これによって、無駄なガベージコレクションの発動を抑えることができます。

言い換えると、コード記述量が減って、動作も速くなる、という事です。したがってこれが推奨されるイベントの発火方法となります。(もしも Event クラスのカスタムサブクラスを用いる必要がある場合、新しいやり方は取れません。)

3.5. カスタムフィルター

手を動かす準備はできていますか? ついにカスタムレンダリングのコードに取り掛かろうとしています。まずは簡単なフラグメントフィルターからです。

フィルターの作成には低レイヤーのコードを扱う必要があります。何行かのアセンブラコードを書く事にもなるでしょう。 しかし恐れる事はありません。ロケット工学を扱うわけではないのですから。 昔の数学の先生は言ったものです。訓練を積んだ猿でもそんな問題は解けてしまうぞ!と。

覚えておいて欲しいのですが、フィルターはディスプレイオブジェクトのピクセル単位で動きます。 フィルターが適用されたディスプレイオブジェクトは、テクスチャーに変換され、その後フラグメントシェダーで処理されます。(よってこれをフラグメントフィルターを呼びます。)

3.5.1. ゴール

簡単なフィルターを作ってみる事をこのチャプターのゴールとしますが、実際に役に立つものを作りたいですね。 ここでは ColorOffsetFilter を作る事にしましょう。

メッシュ各頂点にはカラー値が設定でき、メッシュに色付けをする事ができる事はおそらく知っているかと思います。 そのカラー値は、レンダリング時にテクスチャーのカラー値と乗算されます。テクスチャーの色味を調整するシンプルで高速なやり方です。

var image:Image = new Image(texture);
image.color = 0x808080; // R = G = B = 0.5

背景の数学的な演算はとてもシンプルです。GPU では赤・緑・青のどのチャンネルも 0 から 1 の間の数値で表されます。 例えば、純粋な赤は下記のような値になります。

R = 1, G = 0, B = 0

レンダリング時に、このカラー値は各ピクセルのテクスチャー色(テクセルとも呼ばれます)と乗算されます。 デフォルトのイメージのカラーは純粋な白です。全てのチャンネル値が1となります。 したがって、テクセルのカラーは変化しません。(1を掛ける事は何もしない事と同じです。)

異なったカラー値を設定すると乗算結果は新しい色となります。下記はその例です。

R = 1,   G = 0.8, B = 0.6  ×
R = 0.5, G = 0.5, B = 0.5
-------------------------
R = 0.5, G = 0.4, B = 0.3

さて、ここで問題が起きます。この処理ではオブジェクトの色を暗くはできますが、絶対に明るくはできないのです。 何故ならば掛け合わせている値が 0 と 1 の間だけだからです。0 をかける事は黒を作り出します。そして 1 をかける変化がありません。

Tinting
Figure 49. グレーのカラー設定で色味が変化した画像。

この問題をこれから作るフィルターで解決してみましょう。 カラーの計算にオフセット値を含むようにしてみます。 (Flashでは ColorTransform の設定として行なっていた事と同じです。)

  • 変化後の red 値 = (元の red 値 × red 乗算値) + red オフセット値

  • 変化後の green 値 = (元の green 値 × green 乗算値) + green オフセット値

  • 変化後の blue 値 = (元の blue 値 × blue 乗算値) + blue オフセット値

  • 変化後の alpha 値 = (元の alpha 値 × alpha 乗算値) + alpha オフセット値

乗算する部分の機能はメッシュの基底クラスがすでに持っているので、オフセット値のみがこれから作るフィルターに必要なプロパティとなります。

Offset
Figure 50. 全てのチャンネルにオフセット値を与える。

では、そろそろ始めますよ?

3.5.2. FragmentFilter を継承する

あらゆるフィルターは例外なく、starling.filters.FragmentFilter クラスを継承して作ります。

ここで、 ColorOffsetFilter クラスの完全なコードを紹介します。仮コードではなく最終のコードです。 この後のコード修正はありません。

public class ColorOffsetFilter extends FragmentFilter
{
    public function ColorOffsetFilter(
        redOffset:Number=0, greenOffset:Number=0,
        blueOffset:Number=0, alphaOffset:Number=0):void
    {
        colorOffsetEffect.redOffset = redOffset;
        colorOffsetEffect.greenOffset = greenOffset;
        colorOffsetEffect.blueOffset = blueOffset;
        colorOffsetEffect.alphaOffset = alphaOffset;
    }

    override protected function createEffect():FilterEffect
    {
        return new ColorOffsetEffect();
    }

    private function get colorOffsetEffect():ColorOffsetEffect
    {
        return effect as ColorOffsetEffect;
    }

    public function get redOffset():Number
    {
        return colorOffsetEffect.redOffset;
    }

    public function set redOffset(value:Number):void
    {
        colorOffsetEffect.redOffset = value;
        setRequiresRedraw();
    }

    // その他の オフセット プロパティも同じようにそれぞれ実装します。

    public function get/set greenOffset():Number;
    public function get/set blueOffset():Number;
    public function get/set alphaOffset():Number;
}

驚くほどコンパクトですね。 実は、これはまだこのチャプターの話題の半分ほどです。もうひとつ別のクラスを記述する必要があります。そちらで、実際の色味を演算する処理が入ります。 しかし、上記のコードも実装の詳細を確認する価値があります。

このクラスは FragmentFilter クラスを継承しており、'createEffect' メソッドをオーバーライドしています。 おそらく starling.rendering.Effect クラスを扱った事はない人がほとんどだと思いますが、それはローレベルのレンダリング用途でしか必要とならないからです。 API ドキュメントからの引用ですが、下記のような説明がされます。

Effect クラスは Stage3D の描画工程の全てをカプセル化します。 レンダーコンテキストを設定し、インデックスバッファ、バーテックスバッファ、シェーダプログラムをセットアップします。 全ての基本的でローレベルなレンダリングメカニズムを提供してくれます。

FragmentFilter クラスは、このクラスもしくはこのクラスを継承した FilterEffect というサブクラスを利用します。 今回のシンプルなフィルターの制作のためには、createEffect() をオーバライドし、カスタムエフェクトが戻り値となるように実装してやらなくてはいけません。 ソース中のその他のプロパティは、エフェクトを調整するためだけのものです。 実際のレンダリング時には、ベースクラスが、effect インスタンスを扱い、フィルターの描画処理を行なってくれます。 取り急ぎ、以上となります。

colorOffsetEffect プロパティが何をしているのか気になるでしょうか。 これは、 ColorOffsetEffect へのキャストを行わずに effect へのアクセスを行うためのただのショートカットです。 ベースクラスは effect プロパティを提供します。しかし、それは FilterEffect` 型でのオブジェクトであり、 offset プロパティにアクセスするためには、完全な ColorOffsetEffect 型が必要であるわけです。

もっと複雑なフィルターでは process メソッドの継承も必要になるかもしれません。例えばマルチパスのフィルターを作る場合などです。 しかし、今回のサンプルフィルターでは必要ありません。

最後に、setRequiresRedraw メソッドの呼び出しについて記します。 このメソッドを呼び出す事で、フィルターの設定値が変化した際に必ずエフェクトの再描画がかかるようにしています。 この呼び出しがないと、Starling はオブジェクトを再描画するべきタイミングを知る事ができないのです。

3.5.3. FilterEffect を継承する

実作業を行う時がやってきました。 この FilterEffect のサブクラスは、やる事が多く、サンプルフィルター内の働き者クラスと言えます。 それは、複雑である、という意味ではありません。しばらく我慢しておつきあいください。

先ずは仮のスタブコードから始めましょう。

public class ColorOffsetEffect extends FilterEffect
{
    private var _offsets:Vector.<Number>;

    public function ColorOffsetEffect()
    {
        _offsets = new Vector.<Number>(4, true);
    }

    override protected function createProgram():Program
    {
        // TODO
    }

    override protected function beforeDraw(context:Context3D):void
    {
        // TODO
    }

    public function get redOffset():Number { return _offsets[0]; }
    public function set redOffset(value:Number):void { _offsets[0] = value; }

    public function get greenOffset():Number { return _offsets[1]; }
    public function set greenOffset(value:Number):void { _offsets[1] = value; }

    public function get blueOffset():Number { return _offsets[2]; }
    public function set blueOffset(value:Number):void { _offsets[2] = value; }

    public function get alphaOffset():Number { return _offsets[3]; }
    public function set alphaOffset(value:Number):void { _offsets[3] = value; }
}

ここでは、offsets プロパティを Vector 型の中に格納しています。その方が GPU へのアップロード時に都合がいいからです。 offset プロパティを、ベクターの中に読み書きする。シンプルです。

2つの継承メソッドについて見ていくと、さらに面白くなってきます。

createProgram

createProgram メソッドは、実際の Stage3D シェーダーコードを作成します。

基本的な部分は解説しますが、Stage3D の全てについて対象とするのはこのマニュアルの範疇を超えます。 詳細な情報が必要であれば、下記リンクのチュートリアルを参照してください。

Stage3D のレンダリングは、全てバーテックス()シェーダーとフラグメント(断片)シェーダーを通して行われます。 それらは GPU によって直接実行される小さなプログラムで、2つの役割を持ちます。

  • バーテックスシェーダー は各頂点に対してそれぞれ1度だけ実行されます。 入力情報は、頂点の属性値から作成され、通常 VertexData クラスを通して設定されます。 出力情報は、スクリーン座標系での位置情報です。

  • フラグメントシェーダー は1ピクセル(=断片=フラグメント)ごとに1回実行されます。 入力情報は関連する3つの頂点の情報がピクセルの位置によって補完されたものです。 出力情報は、シンプルにそのピクセルのカラー情報です。

  • 2つのシェーダが合わさって1つの Program を構成します。

フィルターを記述する言語は AGAL と呼ばれます。アセンブリ言語です。 (最初に述べた通り、この言語はローレベル中のローレベルです。) 幸いな事に、大抵の AGAL プログラムはとても短く、そこまで大変ではありません。

そして、良いニュースとして、今回はフラグメントシェーダーを書くだけですみます。 バーテックスシェーダーは大抵のフラグメントフィルターで同じになるので、Starling は標準的なバーテックスシェーダーの実装をあらかじめ用意しています。 では、そのコードを見てみましょう。

override protected function createProgram():Program
{
    var vertexShader:String = STD_VERTEX_SHADER;
    var fragmentShader:String =
        "tex ft0, v0, fs0 <2d, linear> \n" +
        "add oc, ft0, fc0";

    return Program.fromSource(vertexShader, fragmentShader);
}

バーテックスシェーダーは定数( STD_VERTEX_SHADER )から取得されています。 フラグメントシェーダーはたったの2行です。 2つのシェーダーは合わせて1つの Program インスタンスとなり、createProgram メソッドの戻り値となっています。

フラグメントシェーダー部分は引き続き詳細を追ってみましょう。

AGAL コード概要

AGAL コードは、各行にシンプルな命令が記述されています。

[オペコード] [出力場所], [引数1], ([引数2])
  • 最初の3文字はオペーレーション名=命令の名前です。 (tex, add).

  • 次の引数はオペレーション結果をどこに保存するかを指定します。

  • 他の引数はオペレーションへの実際の引数となるものです。

  • 全てのデータは "レジスタ" に保持されます。 レジスタは Vector3D インスタンスだと考えてみましょう。x、y、zのプロパティを持っています。

レジスタにはいくつかの種類があります。定数、一時データ、シェーダーの出力用などです。 今回のシェーダーでは、そのうちのいくつかはあらかじめ値を持っています。フラグメントフィルターの as コードによって設定されています。 (後ほど解説します。)

  • v0 (varying レジスタ 0)は、現在のテクスチャ座標を保持しています。

  • fs0 (fragment サンプラ 0)は、入力テクスチャを示します。

  • fc0 (fragment 定数 0)は、カラーオフセット値を保持しています。今回のフィルターのメインとなる部分です。

フラグメントシェーダの出力は、いつでもカラー値です。 出力カラー値は oc レジスタに格納します。

AGAL コード詳細

フラグメントシェーダーの実際のコードを見てみましょう。 最初の行 はカラー値をテクスチャから読み取っています。

tex ft0, v0, fs0 <2d, linear>

fs0 が表すテクスチャーから、v0 が表すテクスチャ座標でカラー値を読み取ります。その際 2d, linear という言うオプション情報が付与されています。 v0 にテクスチャ座標が保持されている理由は、標準バーテックスシェーダー (STD_VERTEX_SHADER) 側でそのように設定しているからです。ここに関しては、まあそう言うものだ、と思ってください。 結果は一時レジスタの `ft0`に保持されます。( AGAL では、演算結果がいつでも命令の第一引数に格納される事を思い出してください。)

ここでちょっと考えて見ましょう。テクスチャの用意は事前に行なっていません。これはどういう事でしょう?

先に説明した通り、フラグメントフィルターはピクセル毎に実行されますが、フィルターへの入力はオブジェクトが一度内部的にテクスチャとして描画されたものなのです。 ベースクラスの FilterEffect がその工程を行なっています。プログラムが実行される時、テクスチャーサンプラー fs0 はフィルター処理の対象となるテクスチャを差し指しています。

さて、この行については多少変更したい点があります。 行の最後にオプション指定があるのは気づいているかと思います。これはどのようにテクスチャデータを解釈するか指定しているものです。 しかし、対象となるテクスチャのタイプによって、このオプション内容は変更しなくてはいけない事が判明しました。 どんなテクスチャを扱っていても同じコードで動くようにするため、ヘルパーメソッドを用意しましたので、これを使いましょう。

tex("ft0", "v0", 0, this.texture)

この 'tex' メソッドは、先のコードと同じ振る舞いをする AGAL 文字列を返します。これでオプションの内容についてはもうケアする必要がなくなりました。 今後テクスチャにアクセスする際はいつでもこのメソッドを利用してください。(夜にぐっすり眠れるようになりますよ。)

そして2行目は、今回のフィルターのキモの部分です。カラーオフセット値をテクセルカラーに加算します。 オフセット値は fc0 に保持されています。テクセルカラー値を保持する ft0 レジスタの値と加算されて、出力レジスタの oc に代入されています。

add oc, ft0, fc0

AGAL コードはまずはこのような感じとなりました。 今度は as コードの、オーバーライドされたメソッドを見てみましょう。

beforeDraw

beforeDraw メソッドはシェーダーが実行される前に呼び出されます。シェーダーに必要なデータを前もって設定しておく事ができます。

override protected function beforeDraw(context:Context3D):void
{
    context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _offsets);
    super.beforeDraw(context);
}

この部分では、フラグメントシェーダーにカラーオフセット値を定数として渡しています。 2番目のパラメータの 0 は、データが格納されるレジスタを意味しています。 シェーダーのコードを見返すと、オフセット値を fc0 から読み取っています。それがデータを格納した fragment constant 0 です。

super クラスの beforeDraw 呼び出しでは残りのセットアップを行います。テクスチャを fs0 に設定したり、テクスチャ座標を設定したりします。

尋ねられる前に、、afterDraw() メソッドも同様に存在します。通常このメソッドは利用したリソースを開放するのに使われます。 しかし、定数に対して解放処理は必要ありません。よって今回はこの部分の実装を省きます。

3.5.4. 動作確認

さあ、フィルターの実装ができました!(完全なコードは ここ から手に入ります。) 早速テストしてみましょう。

var image:Image = new Image(texture);
var filter:ColorOffsetFilter = new ColorOffsetFilter();
filter.redOffset = 0.5;
image.filter = filter;
addChild(image);
Custom Filter PMA Issue
Figure 51. フィルターの見た目はちょっとがおかしな状態のようです。

なんて事でしょう! たしかに設定どおり赤味は強まっています。しかしなぜ、鳥の外側のエリアまでそれが広がってしまっているのでしょう? アルファ値は全くいじっていないというのに。

…慌てないでください。 まだ最初のフィルターを作ったばかりです。それにそんなに大げさな事ではありませんよ。 これは意味のある事なのです。予想されていた事で、これから微調整して修正する部分です。

描画がおかしくなった理由ですが、"premultiplied alpha" (PMA) という仕組みについて考慮するのを忘れていました。 全てのテクスチャは通常、RGB値がアルファ値であらかじめ乗算された状態で保持されているのです。 50%アルファ値の赤は下記のようになりますが、

R = 1, G = 0, B = 0, A = 0.5

実際は下記のように保存されています。

R = 0.5, G = 0, B = 0, A = 0.5

この状態を考慮に入れていませんでした。 対応すべき事は、オフセットRGB値を足す前に現在の対象ピクセルのアルファ値を乗算してやる事です。 下記のように実装する事ができます。

tex("ft0", "v0", 0, texture)   // カラー値をテクスチャから読み取る
mov ft1, fc0                   // ft1 にカラーオフセット値をコピーする
mul ft1.xyz, fc0.xyz, ft0.www  // オフセット値のRGB要素にテクスチャのアルファ値を乗算する (pma!)
add  oc, ft0, ft1              // オフセット値をテクスチャに加えてアウトプットとする

このように、レジスタの xyzw プロパティに個別アクセスする事が可能です。それぞれ rgba チャンネルの内容を表します。

もしもテクスチャが PMA 形式で保持されていなかったらどうすればいいでしょう? tex メソッドは 必ず PMA 形式のテクスチャを返すので、その事を心配する事はありません。
改めて動作確認

今度はアルファが正しく描画される結果を得る事ができました。(フィルターの完全なコードはここにあります: ColorOffsetFilter.as

Custom Filter with solved PMA issue
Figure 52. 良い感じなりました!

おめでとうございます。完璧に動くフィルターを作り終える事ができましたね。 (実は Starling の ColorMatrixFilter を使う事でも同じ結果を得る事はできるのですが、今回作ったフィルターのほうが若干高速に動作します。よって努力の甲斐はあるのです。)

さて、フィルターが作れた事に満足しましたか?次はメッシュスタイルを作る事に挑戦してはどうでしょう。 大きく変わる点はありません。約束します!

3.6. カスタムスタイル

Stage3D の生のパワーの領域へと足を踏み入れました。このまま突き進んでいきましょう。 このセクションでは、シンプルなメッシュスタイルのコードを記述します。 Starling 2 では、全てのレンダリング処理はスタイルを通じて行われます。 スタイルを自分自身で作る事で、パフォーマンスを犠牲にせずに独自な描画エフェクトを作ることができるのです。

この先に進む前に、必ず [カスタムフィルター] のセクションも目を通しておいてください。 フィルターとスタイルでは多くのコンセプトを共通しています。ですので、より単純なフィルターから理解を始めた方が理にかなっています。 この先は、フィルターのセクションの内容は理解していると仮定して話を進めます。

3.6.1. ゴール

今回のゴールは ColorOffsetFilter として作ったものと同じです。 描画ピクセルに対してカラーのオフセット指定ができるようにします。 これを今回はスタイルとして制作するわけです。これを ColorOffsetStyle と呼びましょう。

Offset with a Style
Figure 53. スタイルでカラーオフセットを指定する。

まずは、フィルターとスタイルの違いについて理解するのが重要です。

フィルター vs スタイル

先に述べた通り、フィルターは各ピクセル1ドット1ドットに対して働きます。オブジェクトは一度テクスチャーに描画され、フィルターはそのテクスチャになんらかの処理します。 それに対してスタイルは、オブジェクトの元々の形状に対してアクセスします。もっと正確に言えば、オブジェクトの頂点に対して処理を行います。

スタイルにはある意味でそのような制限があるのですが(例えば、ブラー効果のスタイルを作ることはできません)、スタイルはフィルターよりもずっと効率的に処理を行います。 ます、フィルターでは必要だった一度テクスチャにオブジェクトを描画する最初のステップの処理が必要がありません。 さらに重要な事に、スタイルはバッチ処理の対象となる事ができます。

知っての通り、ドローコール数を低く抑えることは、高いフレームレートをキープするためにとても大事なことです。 そのためには、Starling にできる限りたくさんのオブジェクトをバッチ処置させる事が重要です。 ここで疑問となるのは、Starling がどのようにしてバッチ対象としてまとめるオブジェクトを決定しているかという事です。 ここで、スタイルが一役買っています。同じスタイルが設定され散るオブジェクトが同じバッチ処理の対象となる事ができます。

もしもステージ上に3つのイメージを配置して、それぞれに ColorOffsetFilter を適用した場合、少なくとも3回のドローコールが確認できます。 代わりに ColorOffsetStyle を適用した場合は、それがたったの1回となります。 このあたりに絡んだ処理がスタイルのコードを若干難しくしてしまってはいますが、同時にやり遂げる価値があるものとなっているのです!

3.6.2. MeshStyle を継承する

全てのスタイルのベースクラスとなるのは、starling.styles.MeshStyle です。 このクラスがスタイルの作成に必要な基盤機能を全て提供してくれます。まずは構造を確認してみましょう。

public class ColorOffsetStyle extends MeshStyle
{
    public static const VERTEX_FORMAT:VertexDataFormat =
            MeshStyle.VERTEX_FORMAT.extend("offset:float4");

    private var _offsets:Vector.<Number>;

    public function ColorOffsetStyle(
        redOffset:Number=0, greenOffset:Number=0,
        blueOffset:Number=0, alphaOffset:Number=0):void
    {
        _offsets = new Vector.<Number>(4, true);
        setTo(redOffset, greenOffset, blueOffset, alphaOffset);
    }

    public function setTo(
        redOffset:Number=0, greenOffset:Number=0,
        blueOffset:Number=0, alphaOffset:Number=0):void
    {
        _offsets[0] = redOffset;
        _offsets[1] = greenOffset;
        _offsets[2] = blueOffset;
        _offsets[3] = alphaOffset;

        updateVertices();
    }

    override public function copyFrom(meshStyle:MeshStyle):void
    {
        // TODO
    }

    override public function createEffect():MeshEffect
    {
        return new ColorOffsetEffect();
    }

    override protected function onTargetAssigned(target:Mesh):void
    {
        updateVertices();
    }

    override public function get vertexFormat():VertexDataFormat
    {
        return VERTEX_FORMAT;
    }

    private function updateVertices():void
    {
        // TODO
    }

    public function get redOffset():Number { return _offsets[0]; }
    public function set redOffset(value:Number):void
    {
        _offsets[0] = value;
        updateVertices();
    }

    // 他のカラーオフセット用のプロパティも同じように実装される必要があります。

    public function get/set greenOffset():Number;
    public function get/set blueOffset():Number;
    public function get/set alphaOffset():Number;
}

これが、作業のとっかかりとなるコードです。 フィルター作成時のそれよりも若干先に進んだ状態となっているので、 個々のメソッドを確認してみましょう。

頂点フォーマット

頂点フォーマットが定数としてクラスの上部に記述されている事に注目してください。 スタイルは頂点レベルで働くと先に述べました。オブジェクトの全ての形状情報にアクセスが可能です。 VertexData クラスはその形状を保持しています。しかし、各頂点にどんなデーターが保持されているか、どうやって保持させるか、についてはまだ述べていませんでした。 まさにそれが、VertexDataFormat によって定義されているのです。

_MeshStyle_によって扱われるデフォルトのフォーマットは以下のようになっています。

position:float2, texCoords:float2, color:bytes4

この文字列の書式には馴染みがあるように見えます。これは属性情報とデータ型の羅列です。

  • position 属性は2つの float 型を保持します。 (頂点のX座標、Y座標です。)

  • texCoords 属性も2つの float 型を保持します。 (テクスチャ座標を表します。).

  • color 属性は4バイトの色情報を保持します。 (1バイトごとに1チャンネルの情報です。)

このフォーマットの VertexData インスタンスは、メッシュの各頂点にそれら属性値を保持します。属性値の格納順はフォーマット文字列と同じ順序です。 つまり、各頂点は合計20バイト(8バイト + 8バイト + 4バイト)を保持する事になります。

もしもメッシュになんのスタイルも当てはめなかった場合、標準の MeshStyle を用いて描画処理が行われます。 各頂点の属性情報には上記のフォーマットが適用されます。 それが、各頂点にテクスチャと色味を付与したメッシュを描画するために必要な情報です。

しかし、ColorOffsetStyle にはこれで属性情報が十分ではありません。カラーオフセット値も保持させる必要があります。 さらに4つのfloat値からなる offset 属性を追加した新しいフォーマットを定義する必要があるのです。

MeshStyle.VERTEX_FORMAT.extend("offset:float4");
// => position:float2, texCoords:float2, color:bytes4, offset:float4

さて、ここでまた疑問が湧くかもしれません。 "なぜこんな作業が必要なのか?フィルターでは頂点フォーマットを調整しなくてもうまくいったじゃないか" と。

これはとても良い質問です。(質問されて嬉しいです!) 答えは、Starlng のバッチ処理にあります。 1つのスタイルを次に描画するメッシュにも適用できる場合にそれらのメッシュがバッチ処理されます。これが上記のような労力を払う理由です。

しかし、バッチ処理とはそもそもなんでしょう? それは、個々のメッシュの頂点データをまとめて1つの大きなメッシュとしてレンダリングする事を意味します。 Starling のレンダリングコードの中で、下記のようなコードを見つけることができるでしょう。

var batch:Mesh = new Mesh();

batch.add(meshA);
batch.add(meshB);
batch.add(meshC);

batch.style = meshA.style; // ← !!!
batch.render();

ここで注目すべき箇所がわかるでしょうか?大きなメッシュ(=batch)には最初のメッシュのスタイルのコピーが適用されています。 しかし、3つのメッシュのスタイルはそれぞれ異なった設定になっているかもしれません。

もしもその異なった設定が style の中にだけ保持されているとしたら、最初の1つのメッシュをのぞき、その設定が失われてしまいます。 そうならないように、スタイルはデーターをターゲットとなるメッシュの頂点に格納しなくてはいけません。 そうすれば、巨大なメッシュは全てのオフセット情報を個別に受け取ることができます。

この事はとても重要なので、2回言います。 スタイルの設定はいつでもターゲットとなるメッシュの頂点データに格納される必要があります。

頂点フォーマットはいつでもスタイルクラスの静的な定数としてアクセスする事ができ、vertexFormat プロパティとしてもアクセスが可能です。 スタイルがメッシュに適用される際、メッシュの頂点は自動的に新しいデータフォーマットが適用されます。

このコンセプトを理解したのなら、このチャプターの半分はなし遂げたことになります。 残りは、フラグメントシェーダー定数ではなく、頂点データからオフセット値を読みとるようにコードをアップデートするだけです。

その件は後に説明します。まずは先に進みましょう。

メンバ変数

全てのデーターは頂点に保持されると言いましたが、同時にクラスのメンバ変数にもデーターを保持させています。

private var _offsets:Vector.<Number>;

なぜならば、実際にメッシュにスタイルが適用される前に、開発者がスタイルのプロパティを調整できるようにしたいからです。 ターゲットとなるオフジェクトがまだ紐づけられていない場合、頂点にデーターを保持させる事ができません。 それなので、一旦このベクター型のメンバ変数にデータを保持させます。 ターゲットとなるオブジェクトが紐づけられると、それらのデーターはターゲットの頂点へとコピーされます。 (onTargetAssigned メソッドを確認してみて下さい。)

copyFrom

バッチ処理の間、スタイルは時々(内部の値を)コピーされて使われる事があります。主な理由はスタイルを再利用し、苛だたしいガーベジコレクションを抑制するためです。 そのためには、copyFrom メソッドをオーバーライドする必要があります。 実際のコードは下記のようになります。

override public function copyFrom(meshStyle:MeshStyle):void
{
    var colorOffsetStyle:ColorOffsetStyle = meshStyle as ColorOffsetStyle;
    if (colorOffsetStyle)
    {
        for (var i:int=0; i<4; ++i)
            _offsets[i] = colorOffsetStyle._offsets[i];
    }

    super.copyFrom(meshStyle);
}

単純なコードです。 もしもスタイルが正しい型である場合、現在のインスタンスのオフセット値をコピーしています。 続きの処理はスーパークラスが受け持ってくれます。

createEffect

このメソッドには見覚えがありますね?

override public function createEffect():MeshEffect
{
    return new ColorOffsetEffect();
}

これはフィルターの createEffect と同じように働きます。後ほど作成する ColorOffsetEffect を戻り値とします。 ColorOffsetEffect はフィルタークラスで使ったものと同じではありません。(オフセット情報が頂点から読み取られています。) しかし、フィルターとスタイルの両方で動くエフェクトを作る事は可能です。

onTargetAssigned

オフセット値をは、ターゲットとなるメッシュの頂点に保持する必要があります。 全て の頂点に 同じ値 のオフセット値を保持させます。これは一見無駄な処理に思えるかもしれません。 しかしこの方法がスタイルがバッチ処理をサポートすようにできる唯一の方法なのです。

スタイルがターゲットにアサインされるとこの onTargetAssigned コールバックが呼び出されます。頂点データを更新するきっかけのタイミングです。 また後にもこの処理を行うことになるので、実際の処理は updateVertices メソッドに移動しました。

override protected function onTargetAssigned(target:Mesh):void
{
    updateVertices();
}

private function updateVertices():void
{
    if (target)
    {
        var numVertices:int = vertexData.numVertices;
        for (var i:int=0; i<numVertices; ++i)
            vertexData.setPoint4D(i, "offset",
                _offsets[0], _offsets[1], _offsets[2], _offsets[3]);

        setRequiresRedraw();
    }
}

vertexData 変数はどこからやってきたのだろう?と疑問に思うかもしれません。 ターゲットのオブジェクトがアサインされるとすぐに vertexData はターゲットの頂点を参照するようになります。 (スタイル自身は頂点データを保持していません。) よって、このコードではターゲットの全ての頂点をループで回し、正しいオフセット値を設定し、レンダリングのための準備を行っています。

3.6.3. MeshEffect を継承する

スタイルクラスの設定は終わりました。次は実際のレンダリングが行われているエフェクト処理へ移りましょう。 今度は MeshEffect クラスを継承します。 エフェクトではローレベルなレンダリングコードを書くだけである事を覚えていますか? 解説は、下記のようなクラスの継承関係を踏まえて進めます。

effect classes

ベースクラス( Effect )は、とても小さいクラスです。三角ポリゴンを描画するだけです。 FilterEffect は、それにテクスチャのサポートを加え、MeshEffect はwqらにそこにカラーとアルファの扱いを加えます。

これら2つのクラスは TexturedEffectColoredTexturedEffect という名前にする事もできました。しかし、実際にそのクラスが利用される場所を考慮に入れて別の名前としました。 フィルターを作るのであれば、FilterEffect を継承する必要があります。メッシュスタイルを作るのであれば、MeshEffect を継承します。

ColorOffsetEffect の構成をざっと見てみましょう。実際の実装はこの後行います。

class ColorOffsetEffect extends MeshEffect
{
    public  static const VERTEX_FORMAT:VertexDataFormat =
        ColorOffsetStyle.VERTEX_FORMAT;

    public function ColorOffsetEffect()
    { }

    override protected function createProgram():Program
    {
        // TODO
    }

    override public function get vertexFormat():VertexDataFormat
    {
        return VERTEX_FORMAT;
    }

    override protected function beforeDraw(context:Context3D):void
    {
        super.beforeDraw(context);
        vertexFormat.setVertexBufferAt(3, vertexBuffer, "offset");
    }

    override protected function afterDraw(context:Context3D):void
    {
        context.setVertexBufferAt(3, null);
        super.afterDraw(context);
    }
}

フィルターの場合と比較すると、全てのオフセット値設定が削除されているのがわかるでしょう。 その代わりに vertexFormat メソッドをオーバーライドしています。オフセット値は頂点データに格納された状態で受け取るため、スタイルに対応した頂点フォーマットを使っています。

beforeDraw と afterDraw

beforeDrawafterDraw メソッドは、シェーダーが va3 レジスタ (vertex attribute 3) から値を読み込めるようにコンテキストを設定します。 では、beforeDraw をみてみましょう。

vertexFormat.setVertexBufferAt(3, vertexBuffer, "offset");

上記のコードは下記と同等です。

context.setVertexBufferAt(3, vertexBuffer, 5, "float4");

三番目の引数 (5 → bufferOffset) は、頂点データのうち何番目がカラーオフセット値を示しているのかを表しています。 最後の引数 (float4 → format) は、属性値のフォーマットを示します、 よって、これらの値について覚えておいたり再計算したりする必要はありません。vertexFormat にそれらの属性を設定するように頼めばいいのです。 この方法なら、もし頂点フォーマットが変更された場合でも、さらに他の属性値がオフセット値の前に追加された場合でも、コードがそのまま動作するようにする事ができます。

頂点の属性値は描画が終了した後に必ずクリアーされていなければいけません。なぜなら後続のドローコールが別のフォーマットで利用する可能性があるからです。 それが afterDraw メソッドで行う処理となります。

createProgram

ついにスタイルのコードの核心に迫ります。AGAL コードで実際にレンダリング処理を行う部分です。 バーテックスシェーダをにも改変を入れていきます。今回は標準の実装のままではうまくいかないのです。 一方、フラグメントシェーダーのものとほぼ同じとなります。 確認してみましょう。

override protected function createProgram():Program
{
    var vertexShader:String = [
        "m44 op, va0, vc0", // 4x4行列で頂点座標をクリップスペースに変換します
        "mov v0, va1     ", // テクスチャ座標をフラグメントシェーダーに送ります
        "mul v1, va2, vc4", // アルファ値(vc4)とカラー値(va2)を掛け合わせてフラグメントシェーダーに送ります
        "mov v2, va3     "  // カラーオフセット値をフラグメントシェーダーに送ります
    ].join("\n");

    var fragmentShader:String = [
        tex("ft0", "v0", 0, texture) +  // テクスチャからカラーを読み取ります
        "mul ft0, ft0, v1",             // カラー値とテクスチャカラーを掛け合わせます
        "mov ft1, v2",                  // オフセット値をft1レジスタにコピーします
        "mul ft1.xyz, v2.xyz, ft0.www", // オフセットのrgb値をアルファ値を掛け合わせます(pma対応)
        "add oc, ft0, ft1"              // カラーオフセット値とカラー値を足し合わせて最終出力とします
    ].join("\n");

    return Program.fromSource(vertexShader, fragmentShader);
}

バーテックスシェーダーが何をしているのか理解するには、まず始めに入力情報について理解しなくてはいけません。

  • va レジスタ ("vertex attribute" レジスタ) は、バーテックスバッファーから得られた頂点の属性値情報を持ちます。属性値は先ほどバーテックスフォーマットで設定した順序で格納されています。それぞれ、va0 は座標、va1 はテクスチャ座標、va2 はカラー、`va3`はカラーオフセットの情報です。

  • 2つの定数は全ての頂点で共通となります。vc0-3 はmvpマトリックス、vc4 は現在のアルファ値です。

どのバーテックスシェーダーでも行う主な処理とは、頂点座標をクリップスペースと呼ばれる座標空間に変換する事です。 これは、頂点座標と`mvpマトリックス` (modelview-projection matrix) と掛け合わせる事で行う事ができます。 最初の行でその処理が行われます。これは Starling のどのバーテックスシェーダーでも見かける処理です。 頂点が画面上でどこに位置するのか求めている、という説明で十分でしょう。

それとは別に、"varying レジスタ" の v0 から v2 を通じて、いくらかのデータをフラグメントシェーダーに送っています。

フラグメントシェーダーの内容はフィルタークラスのものとほぼ同じような実装となっています。 違いがわかるでしょうか? それは、カラーオフセット値を読み取っているレジスタです。フィルターでは定数レジスタ、今回のスタイルでは v2 レジスタにそれが保持されています。

3.6.4. 動作テスト

オリジナルのスタイルの作成の完了まであと一歩です! 動作テストを行ってみましょう。 いきなりですが、2つのオブジェクトにスタイルを適用してみます。バッチ処理が正しく動いている事が確認できるはずです。

var image:Image = new Image(texture);
var style:ColorOffsetStyle = new ColorOffsetStyle();
style.redOffset = 0.5;
image.style = style;
addChild(image);

var image2:Image = new Image(texture);
image2.x = image.width;
var style2:ColorOffsetStyle = new ColorOffsetStyle();
style2.blueOffset = 0.5;
image2.style = style2;
addChild(image2);
Custom Style Sample
Figure 54. 2つのスタイルのイメージがドローコール1回で描画されています。

やりました、正しく動いています! ここで、左上のドローコール表示が1のままである事を確認してください。

…まだ少しやらなけらばならない事があります。今回のシェーダーコードはテクスチャからデータを読み出す前提になっていますが、 スタイルはテクスチャを使わないメッシュに適用される事もあり得るので、そのようなケースのために特別なコードを書かなくてはいけないでしょう。 (今ここで解説はしませんが、とてもシンプルなものです。)

その点を修正した完全なコードは ここ: ColorOffsetStyle.as で確認が可能です。

3.6.5. この先にたどり着く場所

スタイルについては以上です! 私と同じように、スタイルの作成を楽しんでくれたなら良いのですが。 上記のやり方は、あなたの頭の中にある Starling 拡張を実現するキーとなるものです。 MeshStyle クラスにはまだいくつか実装ポイントがあります。クラスドキュメントも通して確認するようにしてください。

みんなが作ったスタイルを見る事を楽しみにしています!

3.7. Distance Field レンダリング

何度か説明しましたが、ビットマップフォントは Starling の中で最も速く描画されます。 しかし、色々な大きさでテキストを表示する際、すぐにビットマップフォントが美しく拡大・縮小されないことに気づくでしょう。 拡大はフォントのぼやけを生み出し、縮小は輪郭にジャギーを生じさせます。 よって、最も美しい表示を得るためには、全てのサイズのフォントをコンテンツ内に埋め込んでしまうという方法が良いでしょう。

Distance Field レンダリング は、この問題を解決します。 Distance Field レンダリング では、フォント・または単色のシェイプを、ジャギーや拡大時の劣化なしに描画することができます。 このテクニックは Valve Software による SIGGRAPH の発表 で初めて紹介されました。 Starling は MeshStyle を通じてこの機能を提供します。

この機能がどのような仕組みで動いているのか知るために、まず1枚の画像を使った例で説明します。 例えば、コンテンツ内で利用するアイコンなどに適用する事ができます。

3.7.1. 1枚絵のレンダリング

このマニュアルにはここまでたくさんのトリに登場してもらっていますが、ここでは逆に鳥の捕食者に登場してもらいます。 うちのメス猫がちょうどよさそうです。今回のサンプルとしてぴったりの黒いベクターデータとして、彼女の肖像画を手に入れました。

Cat
Figure 55. うちの猫は "Seven of Nine" と言います。よろしく。

残念ながら Starling はベクターデータをそのまま表示することができません。 セブン(猫の名前)の画像を表示するには、それが PNG 形式のビットマップデータである必要があります。 ビットマップをそのオリジナルサイズかそれに近いサイズで表示した場合、とても綺麗に表示されます。 しかし画像を拡大すると、すぐにボヤけた状態となってしまいます。

Scaled Cat
Figure 56. 現存のテクスチャの仕組みでは拡大時にボケが生じてしまいます。

画像を distance field テクスチャに変換すると、このボケはを回避することができます。 実は Starling フレームワークは、この変換作業を行う小くて便利なツールを同梱しています。 "Field Agent" と呼ばれるもので、Starling リポジトリの util ディレクリ以下に見つけることができます。

Field Agent を使うためには RubyImageMagick のインストールが必要です。 付属の README ファイルで、インストール方法を確認してください。このツールは Windows でも macOS も動きます。

ここではまず、高解像度の猫画像を PNG 形式で用意して、Field Agent に適用しました。

ruby field_agent.rb cat.png cat-df.png --scale 0.25 --auto-size

このコマンドを実行すると、元画像の 25% の大きさで distance field 形式のテクスチャが作られます。 field agent は高い解像度の画像を小さく変換する場合に、一番うまく動作します。 distance field は画像の形状情報をエンコードするので、もとのテクスチャよりもかなり小さい状態に変換することができます。

Cat Distance Field Texture
Figure 57. distance field 形式テクスチャへの変換後。

元画像のシャープな猫のアウトライン形状が、グレデーションのかかったボケた画像に変換されています。 これが distance field というものです。各ピクセルごとに元となった形状のアウトラインに対して一番近い距離の値をエンコードします。

実際のテクスチャは透明の背景上に白い色のシェイプが乗っているものです。 上の図は、わかりやすくするために背景を黒く塗りつぶしました。

ボケの幅は スプレッド と呼ばれます。field agent はデフォルトで 8ピクセル の幅を用いますが、カスタマイズが可能です。 スプレッドが多いほど良い拡大結果が得られ、特殊な描画効果(後述)を追加しやすくなりますが、 どれだけの幅を持たすことができるかは変換元画像の形状によってきます。 もし元画像がとても細いライン部分を含んでいた場合、 大きく拡大するのに十分な情報を含ませるための領域が足りない事となります。

このテクスチャを Starling で表示するには、テクスチャをロードしてイメージに適用した後、 DistanceFieldStyle をそのイメージに適用します。 Starling はこれによりレンダリング方法を distance field 形式に切り替えます。

var texture:Texture = assets.getTexture("cat-df");
var image:Image = new Image(texture);
image.style = new DistanceFieldStyle();
image.color = 0x0; // we want a black cat
addChild(image);

このスタイルが設定されると、テクスチャをかなり大きく拡大したとしても、完璧にくっきりと表示されます。 ネコの頭の毛の部分のように細かい形状が存在する箇所においてのみ、画像の小さな荒れが確認できるかと思います。

Scaled cat using a distance field texture
Figure 58. distance field テクスチャによる拡大表示。

テクスチャ変換コマンドを実行した際の "spread" 値によっては、 見た目を丁度いいシャープさ(スムーズさ)に調整するために softness パラメータを変更する必要があるかもしれません。 softness パラメータは style クラスをインスタンス化する際の第一引数です。

目安となる計算式: softness = 1.0 / spread
レンダーモード

ここまでは distance field テクスチャーの基本的な使い方となります。 そして、distance field スタイルはいくつかのレンダーモードをサポートします。 レンダーモードとは、アウトライン、ドロップシャドウ、グロウの特殊エフェクト付き描画方法の事です。 これらのエフェクトは専用のフラグメントシェーダーで処理されるので、追加のドローコールを必要としません。 つまり、これらのエフェクトは基本的にパフォーマンスを気にせず使う事ができます!

var style:DistanceFieldStyle = new DistanceFieldStyle();
style.setupDropShadow(); // or
style.setupOutline(); // or
style.setupGlow();
Cat rendered with different modes
Figure 59. distance field スタイルの異なるレンダーモードで描画されたネコ。

ほらね、すごいでしょう?

一つだけ制限事項があります。これらのレンダーモードは複数を組み合わせて使う事ができません。 例えば、アウトラインとドロップシャドウを同時に適用する事はできないわけです。 しかし、代わりにフラグメントフィルターを合わせて用いる事で(アウトラインを distance field で描画し、 ドロップシャドウをフィルタで描画するなど)見た目としては実現する事ができます。

3.7.2. Distance Field フォント

distance field レンダリングの特徴は、テキストの表示にもってこいです。 良いお知らせとしては、distance field スタイルは、Starling のビットマップフォントに対してとてもうまく機能します。 しかし、実際にフォント用に distance field テクスチャを作成するのは、若干面倒だとは思います。

ビットマップフォントが、全ての文字(図形)を含んだテクスチャアトラス画像と、 それぞれの文字の情報を保持する XML ファイルから成り立っていることを思い出してください。

単に field agent を使うだけでは、テクスチャを(少なくとも簡単には)事前処理として変換する事はできません。 なぜなら、それぞれの文字は情報を格納する領域としてある程度の広がる余白部分を必要とするからです。

ですので、distance field テクスチャをサポートする bitmap font 用の専用ツールを用いるのが一番良い方法でしょう。 下記は、候補となるいくつかのツールです。

  • Littera — 無料のオンライン bitmap フォントジェネレータ。

  • Hiero — クロスプラットフォーム対応のフォントツール。

  • BMFont — AngelCode がリリースしているフォントツール。Windows のみ。

ユーザーインターフェースがいまいちではあるのですが、個人的には、Hiero を使った場合に一番良い出力を得ました。 (ユーザーインターフェース周辺は将来的に改善されることを期待しています。)

Hiero に対しては、 このページ がちょうど良い入門ページとなるでしょう。 残念ながら、Hiero は Starling に対応した XML 形式でのファイルを出力してくれないのですが、この小さな 変換作業を行う perl スクリプト が役に立つでしょう。

どのような手続きやツールを使った場合でも、最終的にはいつも通り、テクスチャと .fnt ファイルがそれぞれ1つ出来上がります。 念のため、ビットマップフォントを登録して利用するコードを下記に貼っておきます。

[Embed(source="font.fnt", mimeType="application/octet-stream")]
public static const FontXml:Class;

[Embed(source="font.png")]
public static const FontTexture:Class;

var texture:Texture = Texture.fromEmbeddedAsset(FontTexture);
var xml:XML = XML(new FontXml());
var font:BitmapFont = new BitmapFont(texture, xml)
TextField.registerCompositor(font);

var textField:TextField = new TextField(200, 50, "I love Starling");
textField.format.setTo(font.name, BitmapFont.NATIVE_SIZE);
addChild(textField);

ここまでのコードに特に新しい内容は何もありません。 distance field レンダリングに切り替えるには、テキストフィールドに適切なスタイル設定をしてやります。

var style:DistanceFieldStyle = new DistanceFieldStyle();
textField.style = style;

このような努力の結果、出来上がったフォントは、どんなに拡大しても普通に利用ができ、上で紹介した様々なエフェクトを適用する事もできるのです。

Scaled TextField with a Bitmap Font
Figure 60. distance field を使ったビットマップフォントは、どんな拡大率でも美しく見えます。

3.8. まとめ

お疲れ様です!たくさんの上級トピックスを学び終えました。

  • ATF テクスチャについて詳しくなりました。PNGファイル形式のテクスチャよりも低メモリで読込みが早いテクスチャ形式です。

  • コンテキストロスから復帰する方法を学びました。AssetManager の機能に任せるか、自分のコードで復帰させるか。

  • メモリを浪費せずに使う方法を学びました。合わせて、メモリーリークの避け方と見つけ方も学びました。

  • パフォーマンスの低下が問題となった時、まずはドローコールの回数を確認しましょう。確実にバッチを分断させない方法を学びました。

  • 低レベルのレンダリングコードは思っていたよりも恐るるに足らないものでした。カスタムフィルターやカスタムスタイルを作る事ができました。

  • Distance Field レンダリング はフォントや単色のシェイプを拡大する際に、役に立つテクニックでした。

今後何かのプロジェクトに取り組む際、これらの知識が、トラブルから救ってくれたり、たくさんの業務時間を節約してくれる事でしょう。 そのプロジェクウトのうちいくつかはモバイル端末上で動かさなくてはいけなくなるはず、と予測します。

4. モバイル開発

事クロスプラットフォームの開発となれば、Adobe AIR は現在最もパワフルな開発手法の一つです。 近年、"クロスプラットフォーム" と言った場合、それはたいてい iOSAndroid を意味します。

モバイル開発は、とてもチャレンジングになる事があります。 世の中にはあまりにもたくさんの種類のデバイスがありすぎるのです。 画面解像度をとってみても、目に失礼 なレベルから 極めて細かい レベルまで色々ありますし、アスペクト比の構成に明確なルールはありません。 さらに悪いことに、いくつかのデバイスは複数 CPU を積んではいるのですが、個々の CPU はポケット電卓程度のパワーしか持っていないのです。

開発者としてできることは、肩をすくめ、袖をまくり、真正面からその問題に取り組むことだけです。 あなたは、富と名声がこの旅路の向こう側に転がっている事を、少なくとも知っているでしょう! [7]

4.1. 様々な解像度への対応

固定の画面サイズでゲームを作ればよかった時代はどこへ行ってしまったのでしょう? 昔は html 上の四角い小さなエリアに、スプライトやテキストや画像を表示していました。 1つの画面解像度だけ考えて開発をすればよかったのです。いい時代でしたね。

しかしああ、時代は変わってしまったのです! モバイル端末の大きさは様々です。そしてデスクトップコンピューターやノートPCも、細かい密度のディスプレイを持つようになりました。 これはユーザーとしてはとても嬉しい事で。しかし、開発者の生活を楽にするものではありません。

希望をしてないでください。なんとかする事はできます。 事前によく考慮して、 Starling が提供するいくつかのシンプルな機能を利用すればいいだけの事です。

しかし、最初はその対応内容にちょっと圧倒されてしまいます。 ですので、まずは2007年の頃の状況に戻って考えて、少しずつ対応を進めていきます。

聞き間違いではありませんよ。デロリアンに乗り込んで、次元転送装置を起動しましょう。 時速120km以上のスピードで問題にぶつかってていくので、引き締まっていきましょう!

4.1.1. iPhone

間違いなく iPhone は近年で最も人気なゲームプラットフォームです。 2007年の頃、iPhone は 唯一 存在したデバイスで、簡単に開発ができました。 App Store の大ゴールドラッシュと呼べるような時代でした。

画面解像度が 320x480 に固定されていたので、最初の iPhone はとても開発がしやすかったのです。 もちろん、その頃に Starling は存在しませんでしたが、もしあったとすると下記のようなコードで実装を始めた事でしょう。

var screenWidth:int  = stage.fullScreenWidth;
var screenHeight:int = stage.fullScreenHeight;
var viewPort:Rectangle = new Rectangle(0, 0, screenWidth, screenHeight);

starling = new Starling(Game, stage, viewPort);

viewPort の大きさを画面サイズ 320×480 の目一杯に設定します。 デフォルトの状態では、Stage は画面解像度と全く同じ大きさを持ちます。

PenguFlip on the iPhone
Figure 61. 初代 iPhone で私達のゲームを動かした図

ここまではとても簡単です。ブラウザでゲームを作るのと変わりません。 (当時は、Internet Explorer 6 上で動くゲームを作っていたかも知れませんね。)

では次に、2010年に移動してみましょう。

4.1.2. iPhone レティナ

伝統ある Apple 学園の角にデロリアンを停めて、App Store のランニングを見てみましょう。 よかった!2007年に大成功した私達のゲームは、まだランキングトップ10に止まっているようです! しかし、時間を無駄にしてはいけません。 あと数週間でリリースされる iPhone 4 上でも私達のゲームがきちんとした見た目になるよう、ゲームを調整しなくては。

私達は未来からやってきたので、この大技術革新について知っています。Apple のマーケティングチームによって "レティナ ディスプレイ" と名付けられた、高解像度スクリーンの事です。 2007年にリリースした私達のゲームを起動して、まだリリースされていないこのデバイス上で動かしてみましょう。

PenguFlip with a wrong scale on the iPhone4
Figure 62. 全く意図しない状態となりました。

うーん、画面のたった1/4のエリアでしかゲームが動いていません! 一体どういう事なのでしょうか?

2007年の際のコードを見返してみると、viewPort の大きさをスクリーンと同じ大きさにしていたのがわかります。 iPhone 4 のスクリーンサイズは倍になっていて、640×960 ピクセルの大きさを持ちます。 しかし、ステージ上のディスプレイオブジェクトは 320×480 の大きさを持つシステムで動いている事を期待しているのです。 よって画面右端(x=320)に配置したつもりのオブジェクトは、突如として真ん中に現れるのです。

しかし、これは簡単に解決する事ができます。

Starling の viewPort の値と stageWidthとstageHeight の値は、独立して設定できる事を思い出してください。

  • viewPort の設定はスクリーン上のどのエリアに Starling が描画すべきかを設定します。これはいつでもピクセルの単位で記述されます。

  • stage サイズの値は、viewPort 内で描画されているシステムがどのくらいの大きさの解像度で動作するのを設定します。stage の幅を 320 と設定した場合、x座標を 0 から 320 の間で設定されたオブジェクトが画面内に表示されます。viewPort の大きさの設定値は関係ありません。

この知識があれば、拡大して表示をするのは簡単です。

var screenWidth:int  = stage.fullScreenWidth;
var screenHeight:int = stage.fullScreenHeight;
var viewPort:Rectangle = new Rectangle(0, 0, screenWidth, screenHeight);

starling = new Starling(Game, stage, viewPort);
starling.stage.stageWidth  = 320;
starling.stage.stageHeight = 480;

viewPort 値はここでも動的であって、ゲームを動かすデバイスによって変わります。 しかし、コードの最後の2行でステージの大きさを静的な値としてハードコードしています。

これら stage の大きさの値は、もはやピクセル値を示さないので ポイント という単位で表していると考えることにしましょう。 つまり、ステージサイズは 320 ポイント × 480 ポイントである、というように言う事ができます。

これで iPhone 4 でのゲームの見た目は下記のようになりました。

PenguFlip scaled up blurry
Figure 63. 大きさは良くなりましたが、ちょっとボケていますね。

全画面表示ができるようになったのは良いです。しかし、画像がボケて表示されてしまいました。 私たちはまだ、この大きな画面をうまく使いこなせていないのです。

すでに、良く無い内容のレビューが付いているようです。修正しなくてはいけませんね。

HD テクスチャ

この問題を解決するには、高いピクセル密度の画面には専用の高解像度テクスチャを用意すればいいのです。

ピクセル密度に合わせて、低解像度と高解像度の両方それぞれのテクスチャセットを使いわけます。 この方法の良い点は、どちらのテクスチャセットを適用するか選択するロジック以外は、コードを変更する必要がない、という事です。

しかし、ただ単純に異なる解像度のテクスチャセットを読み込むだけではダメです。 結局、固定横幅320ポイントのステージ上でも、大きなテクスチャは大きな幅と高さを返します。

  • 横幅160ピクセルの SD テクスチャはステージの半分のサイズとなります。

  • 対応する横幅320ピクセルの HD テクスチャはステージ全体を覆います。

理想の状態は、HDテクスチャを扱っても、SDテクスチャを扱ってもプログラム上では同じサイズとして扱う事ができ、しかし、HDテクスチャの方がより細かく詳細な見た目を持っている事です。

ここで、Starling の contentScaleFactor というプロパティが役に立ちます。

私達が Starling で stageviewPort のサイズを設定した際、contentScaleFactor 値は自動で設定されています。

試しに、先のコードに足す形で iPhone 4 で下記のコードを実行してください。

trace(starling.contentScaleFactor); // → 2

contentScaleFactorviewPortの横幅stageの横幅 で割った値を返します。

レティナ端末では、"2" を、非レティナ端末では、 "1" が返されます。

この値によって、どのテクスチャセットを読み込むかを決める事ができます。

contentScaleFactor 値が整数を返すのは、偶然ではありません。 Apple は、できるだけアンチエイリアス関連の問題を避けるため、デバイスの横幅・縦幅をちょうど2倍に設定しています。

texture クラスは単純に scale と呼ばれる、似たようなプロパティを持っています。 設定が正しくされると、texture は私達が先ほど期待した通りの振る舞いをしてくれます。

var scale:Number = starling.contentScaleFactor; (1)
var texturePath:String = "textures/" + scale + "x"; (2)
var appDir:File = File.applicationDirectory;

assetManager.scaleFactor = scale; (3)
assetManager.enqueue(appDir.resolvePath(texturePath));
assetManager.loadQueue(...);

var texture:Texture = assetManager.getTexture("penguin"); (4)
trace(texture.scale); // → Either '1' or '2'  (5)
1 Starling のインスタンスから contentScaleFactor の値を得ます。
2 scale factor 値に合わせて、1x または 2x という名前のテクスチャを読み込む準備をします。
3 AssetManager に先ほどの scale factor 値を設定します。読み込まれたテクスチャはこの値を利用して初期化されます。
4 実際にテクスチャを扱う際は、scale factor 値について考える必要はありません。
5 テクスチャの scale プロパティにアクセスして、scale 値を確認する事は可能です。
AssetManager を使わないでファイルを読み込む実装をしていますか? その場合でも問題ありません。 全てのテクスチャ作成メソッド(`Texture.from…​`で始まるメソッド)は scale factor を指定する任意の引数を持っています。 ただし、この値はテクスチャ作成時に設定しなくてはいけません。後から値を変える事は不可能です。

テクスチャの縦幅・横幅をプログラムで得ようとすると、内部計算で scale factor が考慮され、調整された後の値を得る事ができます。 下記は、私達のゲームで使っている背景画像の大きさを取得した例です。

ファイル名 ピクセルサイズ Scale Factor ポイントでのサイズ

textures/1x/bg.jpg

320×480

1.0

320×480

textures/2x/bg.jpg

640×960

2.0

320×480

さて、これで準備が整いました!

  • 後ろの席に座っているグラフィックデザイナー(Biffと呼んでください)は全てのテクスチャをまず高解像度で作成します。 理想としてはベクターグラフィクスであると良いです。

  • 次に実際にデバイス上で読み込むテクスチャを作ります。先ほどの高解像度テクスチャをちょうど(1x, 2x)として扱える解像度に変換します。

  • 実際の利用時は、Starling の contentScaleFactor 値を確認し、その値に対応したテクスチャをロードします。

これで終了です。くっきりとした見た目のレティナディスプレイ対応ゲームを作る事ができました! ゲームのユーザーはきっとこれを喜ぶでしょう。

PenguFlip on the iPhone
Figure 64. レティナディスプレイを使った例です。
TexturePacker のようなツールを使うと、今まで説明した手順を簡単に行う事ができます。 ツールには一番の高解像度で個々のイメージパーツを登録してください。それぞれの scale factor 値に対応したテクスチャアトラファイルを書き出す事ができます。

さあ、Redwood City のこの バー で、1〜2杯のビールでも飲んで成功を祝って、また、先に進みましょう。

4.1.3. iPhone 5

2012年、iPhone は更なる驚きを私達に与えてくれました。Apple は画面サイズの縦横比を変えてしまったのです。 横方向は640ピクセルのままでしたが、縦方向に少し伸びて1136ピクセルとなりました。 もちろん、これはレティナディスプレイでもあります。よって私達の考えるベース解像度は320×568ポイントとなります。

この問題に簡単に対応するため、単純に今までの viewPort を真ん中に表示して、 上下の空いた箇所はそのまま黒い帯のエリアとして放置してしまいましょう。

var offsetY:int = (1136 - 960) / 2;
var viewPort:Rectangle = new Rectangle(0, offsetY, 640, 960);

この対応できちんと動くようです! この対処方法は、この時間の旅にそろそろ登場しはじめる全てのAndroid スマートフォンに対しても有効です。 私達のゲームはいくつかのデバイス上では少しぼやけてしまうかもしれません。しかし、ものすごく悪い状態というわけでもありません。 画像のクオリティはぼやけたとしても依然かなり良いのです。ほとんどのユーザーはその事に気がつかないでしょう。

PenguFlip with letterbox bars
Figure 65. レターボックス法。

私は、この対応のやり方を レターボックス法 と呼んでいます。

  • ゲームは固定サイズ(例えば320×480ポイントの大きさ)で作成します。

  • いくつかの scale factor 値に対応したアセットを作成します。(1x, 2x, 3x、、)

  • 歪みなくスクリーン全体にフィットするよう、アプリのサイズを拡大します。

おそらくこれが、最も実用的な解決方法です。

この方法であれば、どのような画面解像度の端末上であっても、そこそこのクオリティの見た目であなたのゲームを動かす事ができます。 このためにあなたがすべきことは、viewPort を正しいサイズに設定することだけです。

ところで、Starling に付属する RectangleUtil を使うと、後者はとても簡単に実現できます。 viewPort を拡大するには、下記のようなコードを記述してください。

const stageWidth:int  = 320; // 単位:points
const stageHeight:int = 480;
const screenWidth:int  = stage.fullScreenWidth; // 単位:pixels
const screenHeight:int = stage.fullScreenHeight;

var viewPort:Rectangle = RectangleUtil.fit(
    new Rectangle(0, 0, stageWidth, stageHeight),
    new Rectangle(0, 0, screenWidth, screenHeight),
    ScaleMode.SHOW_ALL);

シンプルですが、効果的ですね! さて、当然タイムマシーンの旅には続きがあります。車に乗り込みましょう!

4.1.4. iPhone 6 及び Android

2014年にやってきましたが…、なんという事だ! App Store 年鑑を確認すると、私達のソフトの売り上げは最後のアップデートからずっと冷え込んでいる事がわかりました。

どうやら Apple はレターボックスのやり方が気にいらなかった様子で、 今回は私達のアプリをフューチャーしてくれませんでした。残念です。

どうやらやるしかないようです、追加の余ったスクリーンスペースもなんとか利用してましょう。

自動調整機能よ、さようなら!

これからは、全てのディスプレイオブジェクトに対して、相対的な座標を用いて扱っていきます。

私はこのやり方を、スマート オブジェクト プレースメント と、呼ぼうと思います。 最初のコーディング内容は今までのコードととても似ています。

var viewPort:Rectangle = new Rectangle(0, 0, screenWidth, screenHeight);

starling = new Starling(Game, stage, viewPort);
starling.stage.stageWidth  = 320;
starling.stage.stageHeight = isIPhone5() ? 568 : 480;

うーん、ちょっとこれは嫌な感じがしますね。 デバイスによるステージの高さの違いをハードコードしています。これはあまり賢いやり方ではありません。 これについては、すぐ後で修正する事を約束します。

しかし今の所このコードは正しく動きます。viewPortstage も正しいサイズです。 しかしどのように利用しましょう? ここで、Game クラスをみてみましょう。このクラスは Starling の root ディスプレイオブジェクトとして振る舞います。

public class Game extends Sprite
{
    public function Game()
    {
        addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); (1)
    }

    private function onAddedToStage():void
    {
        setup(stage.stageWidth, stage.stageHeight); (2)
    }

    private function setup(width:Number, height:Number):void
    {
        // ...

        var lifeBar:LifeBar = new LifeBar(width); (3)
        lifeBar.y = height - lifeBar.height;
        addChild(lifeBar);

        // ...
    }
}
1 Game クラスのコンストラクタが実行されたタイミングでは、まだ Game インスタンスは Stage 上に配置されていません。 なので、実際の初期化処理は準備ができるまで待ちます。
2 準備ができた後、setup メソッドを飛ぶ祭にステージのサイズを引数に渡します。
3 よくゲームのユーザーインターフェースであるような _ライフバー_を、画面の下部に配置します。

ここまでざっくりと見て所、特に難しくはないですよね? ポイントは、ステージサイズを計算に入れるという事です。

さて、もしもあなたの作ったゲーム実装の構成がよく整理されていて、 それぞれのユーザーインターフェースのオブジェクトにはそれぞれ専用のクラスが用意してあるなら、 このようなやり方でうまくいきます。

ユーザーインターフェースのオブジェクトのうち、意味があるもの全てに対して、 (LifeBar の例のように)ステージのサイズを受け渡して、うまく大きさや位置を調整して振る舞うようにするのです。

PenguFlip without letterbox bars
Figure 66. No more レータボックスの帯のエリアはもうありません。画面全体が利用されています。

iPhone 5 でこの方法はとてもうまく動きます。 2012年の時点でこのように対応しておけばよかった! 2014年には自体はもっと複雑になっていました。

  • Android はマーケット上でシェアをどんどんと伸ばしており、端末毎に大きさも解像度も異なります。

  • Apple も iPhone 6iPhone 6 Plus というさらに大きな画面の端末を導入しました。

  • タブレット端末については…もう説明しましたっけ?

ディスプレイオブジェクトの位置をステージの大きさに合わせて調整するというやり方で、 すでに問題に対する基本対応方針はできあがっています。 私達のゲームは様々なステージサイズの端末で動作する事でしょう。

残った問題は、ステージの大きさと scale factor 値をどのように決定するか、という事です。 扱うスクリーンの種類の多さを見ると、気持ちが折れてしまいそうです。

デバイス スクリーンサイズ ピクセル密度 解像度

iPhone 3

3,50"

163 dpi

320×480

iPhone 4

3,50"

326 dpi

640×960

iPhone 5

4,00"

326 dpi

640×1136

iPhone 6

4,70"

326 dpi

750×1334

iPhone 6 Plus

5,50"

401 dpi

1080×1920

Galaxy S1

4,00"

233 dpi

480×800

Galaxy S3

4,80"

306 dpi

720×1280

Galaxy S5

5,10"

432 dpi

1080×1920

Galaxy S7

5,10"

577 dpi

1440×2560

ピクセル密度を計算に入れる事が scale factor 値を決定するカギとなります。

  • より高いピクセル密度では、より高い scale factor 値になります。つまり、ピクセル密度から scale factor 値が推測できます。

  • scale factor 値からは適切なステージサイズを計算する事ができます。上記と逆の求め方となります。

最初の iPhone のピクセル密度はおおよそ160 dpi でした。 これを元に計算を進めてみましょう。どのデバイスもピクセル密度を160で割り、小数点以下を切り上げて整数値を得ます。 このやり方で、それぞれの端末を確認してみましょう。

デバイス スクリーンサイズ ピクセル密度 Scale Factor 値 ステージサイズ(単位:ポイント)

iPhone 3

3,50"

163 dpi

1.0

320×480

iPhone 4

3,50"

326 dpi

2.0

320×480

iPhone 5

4,00"

326 dpi

2.0

320×568

iPhone 6

4,70"

326 dpi

2.0

375×667

iPhone 6 Plus

5,50"

401 dpi

3.0

414×736

Galaxy S1

4,00"

233 dpi

1.5

320×533

Galaxy S3

4,80"

306 dpi

2.0

360×640

Galaxy S5

5,10"

432 dpi

3.0

360×640

Galaxy S7

5,10"

577 dpi

4.0

360×640

計算結果のステージサイズに注目してみましょう。それぞれが、320×480から414×736の範疇に収まっています。 これが、実際に扱うべきスクリーンサイズの範囲です。 そして、物理的に大きな画面の端末は大きなステージサイズを持っている、という事が言えます。 大事なことは、適切な scale factors 値を選択することで、合理的に利用できる座標系を得る事ができる、と言う点です。 これは、実際に開発時に扱う事となる大きさの範囲です。

Galaxy S1 の scale factor 値が整数値でないのに気づいていたでしょうか。 これは、現実的に扱うことができるステージサイズを算出するために必要となった値でした。

では、どのようにしてこのスケール値を得たのか実際に見てみましょう。 ScreenSetup という名前でクラスを作り、下記のような記述内容としてください。

public class ScreenSetup
{
    private var _stageWidth:Number;
    private var _stageHeight:Number;
    private var _viewPort:Rectangle;
    private var _scale:Number;
    private var _assetScale:Number;

    public function ScreenSetup(
        fullScreenWidth:uint, fullScreenHeight:uint,
        assetScales:Array=null, screenDPI:Number=-1)
    {
        // ...
    }

    public function get stageWidth():Number { return _stageWidth; }
    public function get stageHeight():Number { return _stageHeight; }
    public function get viewPort():Rectangle { return _viewPort; }
    public function get scale():Number { return _scale; }
    public function get assetScale():Number { return _assetScale; }
}

このクラスでは Starling 設定の基準となる viewPort 値と stage size 値を求める事ができます。 assetScale 以外のプロパティーについては特に説明はいらないでしょう。

上の表では、scale factor 値は1〜4の間で設定されています。 しかし、それぞれ全ての scale factor 値に対応して、たくさんのテクスチャセットを用意したくは無いことでしょう。 最も密度が高い画面のピクセルはとても小さいので、あなたの目でそれを認識することはどっちみちできないかと思われます。

したがって、大抵は scale factors 値に対応した全てのテクスチャセットを用意することはなく、 1〜2 または 1〜3 の範囲だけに対応したテクスチャセットを用意する事になるでしょう。

  • コンストラクタ引数の assetScales には、用意してあるテクスチャセットに対応する scale factors 値一覧を配列で指定します。

  • ScreenSetup インスタンスの assetScale プロパティは、どのテクスチャセットをロードすべきか教えてくれます。

最近は、scale factor 値 "1" をアプリケーションがサポートする必要はほとんどなくなりました。 しかし、この値は開発時には重宝するでしょう。なぜなら、広い画面のモニタがなくてもPC上でアプリの動作確認ができるからです。

さて次に、コンストラクタ部分の実装に取り掛かりましょう。

public function ScreenSetup(
    fullScreenWidth:uint, fullScreenHeight:uint,
    assetScales:Array=null, screenDPI:Number=-1)
{
    if (screenDPI <= 0) screenDPI = Capabilities.screenDPI;
    if (assetScales == null || assetScales.length == 0) assetScales = [1];

    var iPad:Boolean = Capabilities.os.indexOf("iPad") != -1; (1)
    var baseDPI:Number = iPad ? 130 : 160; (2)
    var exactScale:Number = screenDPI / baseDPI;

    if (exactScale < 1.25) _scale = 1.0; (3)
    else if (exactScale < 1.75) _scale = 1.5;
    else _scale = Math.round(exactScale);

    _stageWidth  = int(fullScreenWidth  / _scale); (4)
    _stageHeight = int(fullScreenHeight / _scale);

    assetScales.sort(Array.NUMERIC | Array.DESCENDING);
    _assetScale = assetScales[0];

    for (var i:int=0; i<assetScales.length; ++i) (5)
        if (assetScales[i] >= _scale) _assetScale = assetScales[i];

    _viewPort = new Rectangle(0, 0, _stageWidth * _scale, _stageHeight * _scale);
}
1 Apple iPad 用にちょっとした特別対応を施します。 iOS 上では iPhone であっても iPad であっても、同じ scale factor のセットを扱うようにしたいのです。
2 基準とするピクセル密度は160 dpi (iPadの場合、130 dpi)です。このような密度の端末は scale factor 値を "1" とします。
3 scale factors 値は整数値、もしくは`1.5`の値を取ります。このコードではそのうち最も近いものを選択します。
4 ここで、ロードすべきアセットのセット(1倍、2倍、3倍…)を決定します。
このコードによって計算された 上の表のデバイスそれぞれに対する assetScale 値を確認したいのならば、 Gist ページを参照してください。 assetScale 値一覧に満足したならば、調査デバイスを自ら追加して、assetScale の値を簡単に求める事ができます。

これで準備が整いました。Starling の起動コードに処理を取り込みましょう。

下記のコードは、scale factors 値 "1" 及び "2" に対応したアセットを用意した場合のサンプルとなっています。

var screen:ScreenSetup = new ScreenSetup(
    stage.fullScreenWidth, stage.fullScreenHeight, [1, 2]);

_starling = new Starling(Root, stage, screen.viewPort);
_starling.stage.stageWidth  = screen.stageWidth;
_starling.stage.stageHeight = screen.stageHeight;

アセットをロードする際は、ScreenSetup インスタンスの assetScale プロパティを利用してください。

var scale:Number = screen.assetScale;
var texturePath:String = "textures/" + scale + "x";
var appDir:File = File.applicationDirectory;

assetManager.scaleFactor = scale;
assetManager.enqueue(appDir.resolvePath(texturePath));
assetManager.loadQueue(...);

以上です! まだユーザインターフェースの位置調整を行わなくてはいけない事を忘れないように。 しかしそれは、そんなに難しくないでしょう。

Starling のリポジトリには、Mobile Scaffold と言う名前のプロジェクトが存在します。 そのプロジェクトにはここで解説したコード全てが含まれています。モバイル開発の雛形として最適です。 (ダウンロード済みのファイルに ScreenSetup クラスが見つからない場合は、GitHub 上の最新のリビジョンで探してみてください。)
もしあなたが Feathers を使っているのなら、ScreenDensityScaleFactorManager というクラスが ScreenSetup クラスと同じ処理を行ってくれます。 実際、ここに書いた処理内容は、ScreenDensityScaleFactorManager クラスに強くインスパイアされて作られた物です。

4.1.5. iPad 及び その他タブレット

ここで現在に戻って、私達のゲームがうまくタブレットに対応できるのか考えてみましょう。 実際、上のコードはタブレットでもそのまま動作します。 しかし、今までよりもずっと広いステージとよりたくさんのコンテンツを配置する空間が目につく事になります。 どのように対応すべきかは、あなたが作っているアプリケーションによりけりです。

いくつかのゲームでは拡大しているだけ

Super Mario BrosBejeweled のようなゲームは大きなスクリーンでも細かいテクスチャで綺麗に拡大表示されます。 このような場合、ピクセル密度は無視して、表示可能なピクセル数のみから scale factor 値を計算すれば良いです。

  • 初代 iPad(解像度: 768×1024)は、画面サイズが 384×512 であり、scale factor が "2" のデバイスとして扱えます。

  • レティナ iPad (解像度: 1536×2048)も、画面サイズが 384×512 であり、scale factor が "4" のデバイスとして扱えます。

よりたくさん物を表示するタイプも

Sim CityCommand & Conquer のようなゲームについて考えてみましょう。 そのようなゲームでは、大きな画面であれば街並みや風景をよりたくさん表示する事ができます。 つまり、ユーザーインターフェースはゲームのコンテンツに対してより小さなスペースをしめるようになります。

全く異なるユーザーインターフェースの考察が必要な事も

実用アプリの場合、特にこれは正しいと言えます。 例えば、モバイルフォンの小さな画面上では、Eメールアプリは、メール本文か受信箱かメールボックスのどれか1つを表示する事になるでしょう。 一方、タブレット端末上ではその3つを同時に表示する事ができるでしょう。

このような対応をするのに必要な開発の労力は甘く見ないようにしましょう。

4.2. デバイスの回転

スマートフォンとタブレットは、デバイスの空間的な回転方向を認識して、 ユーザーインターフェースを良い感じに調整してくれる、とても素晴らしい機能を持っています。

Starling で回転方向の変化を認識するには、まず最初に AIR の設定ファイルを編集する必要があります。 下記のような設定をしてください。

<aspectRatio>any</aspectRatio> (1)
<autoOrients>true</autoOrients> (2)
1 aspectRatio の設定を、portraitlandscape、`any`のいずれかにします。
2 アプリの起動時に自動回転機能有効にする指定します。

この設定が適用されると、Starling の stage オブジェクトから RESIZE イベントを受け取ることができます。 このイベントは回転方向が変化した際に必ず発行されます。 結局のところ、回転方向の変化は必ず Stage サイズの変更も引き起こすからです。高さ が入れ替わるのです。

このイベントを受け取った際は、Starling の描画エリアとStageの大きさを調整しましょう。

stage.addEventListener(Event.RESIZE, onResize);

private function onResize(event:ResizeEvent):void (1)
{
    updateViewPort(event.width, event.height);
    updatePositions(event.width, event.height);
}

private function updateViewPort(width:int, height:int):void (2)
{
    var current:Starling = Starling.current;
    var scale:Number = current.contentScaleFactor;

    stage.stageWidth  = width  / scale;
    stage.stageHeight = height / scale;

    current.viewPort.width  = stage.stageWidth  * scale;
    current.viewPort.height = stage.stageHeight * scale;
}

private function updatePositions(width:int, height:int):void (3)
{
    // Update the positions of the objects that make up your game.
}
1 デバイスが回転した際、このイベントハンドラが呼び出されます。
2 現在のスクリーンサイズの大きさに合わせて、Stage と viewPort の大きさを調節します。
3 新しい回転の向きにマッチするよう、あなたのゲームのユーザーインターフェースを調整します。

イベントハンドラ内で、viewPortstage の大きさ 両方を手動で更新しなくてはいけないことに注意してください。

デフォルトのままでは何の処理も行われません。つまり、アプリケーションの画面の一部がかけたような状態になってしまうでしょう。 上のコードはその問題を解決します。どんな scale factor 値でもうまく動作するでしょう。

最後の対応の、アプリのインターフェースを変化した画面方向に合わせてうまく調整すること、は多少難しいかもしれません。 全てのゲームでうまく調整できるわけではありませんが、うまくいく場合、ここにさらなる労力を費やすべきです。 ユーザー視点で見れば嬉しい事ですから!

Starling を利用して作られたフレームワークの中にはすでにこのような機能が実装されている場合もあるでしょう。

4.3. まとめ

モバイル開発は、間違いなく大変なものです。

たくさんの種類のハードウェアが存在する事で、実装プランを事前によく練り、コードをスマートな構造にする必要があるのです。 また、アプリのマーケットには、とてもたくさんのライバルがいます。 よってあなたのアプリのデザインレベルは、有象無象の中からずば抜けている必要もあるでしょう。

Starling はこのプロセスであなたを手助けします。 この章で学んだツールとともに、このチャレンジを成功させる事ができるでしょう。

5. 最後に

5.1. 全ては解き放たれた!

さて、これで Starling マニュアル は終了です。様々な分野をカバーできたかと思います。 勉強中に生えてしまった髭を剃り落として、自分自身を褒めてあげましょう。

読了、お疲れ様でした。

Impressive

5.2. Starling コミュニティ

まだわからない事があり、質問をしたいですか? 身につけた知識をシェアしたいですか? Starling コミュニティはあなたの手助けをしたいと思っています。 ぜひ、オフィシャルのフォーラムを訪れてください。 Starling Forum.

5.3. このマニュアルで物足りない、という方へ

もし、真の Starling マスターになりたいのなら、 Starling Handbook を忘れずに入手するようにしましょう! このマニュアルの全内容に加えて、さらにプロジェクト開発に役立つレシピや情報を知る事ができます。 そして同時に Starling の開発の継続をサポートする事にもなるのです!

The Starling Handbook

1. 'archive.org' サイトに、Starling のような Stage3D 上のライブラリに関する、Adobe のエンジニアによるとても面白い考察記事があります。 http://tinyurl.com/hkbdgfn
2. ピクセルシェーダーをアセンブリ言語でいくつも書くことを思い出したりしませんでしたか?
3. 唯一の制限は、形状を構成する各ライン同士が交わる事ができない、という点です。
4. 私が文章を書くのに使っているエディターが下品な表現だと忠告してきていますが、(1)単語として短縮系を使いましたし、(2) 実際コンテキストロスは ** なのです。
5. AIR 24 と Starling 2.2 より、同じことを通常のテクスチャでも行う事ができます。
6. もし 使っている Starling のバージョンが 1.x であるなら、代わりに ''QuadBatch'' を使ってください。
7. あまり個々について詳しい説明を求めないように。