このドキュメントについて

このドキュメントは2008年 1/15、 Colin Moockさんを講師として迎えて行われた、「今から始める ActionScript 3.0 - WORLD WIDE TOUR 」用のスライドを小野田 智(emotional-box.com)が邦訳したもので、Colinさん、及びAdobeからの了承を得て、公開しています。
また、元のスライドは、http://moock.org/lectures/groundUpAS3/で公開されています。
本来は、セミナー参加者のための確認用なのですが、セミナーに参加できなかった方でも参考にできるように訳注を各所に追加しておきました。
誤訳や分かりづらい部分がありましたら、訳者までご連絡ください。できる限り対応させていただきます。
原文の著作権はColin Moockさんに帰属し、翻訳によって発生した二次著作権は小野田 智に帰属します。コードの再利用などは、こちらのページを参考にしてください。

はじめに

プログラムを習得するには非常に多くの時間がかかります。

狙い通りに動いてくれれば、それが「正しい」ことなのです。

初心者の方へ:

  • 細かいところはあまり気にしないでください。
  • 作ったものは人に見せましょう。
  • 師匠を探してください。
  • Adobe と fitcに感謝。

    講義メモは以下のアドレスにあります。 www.moock.org/lectures/groundUpAS3

    休憩時間は短いです。また、講義中はお静かに。

    ActionScript プログラミングツール

    メモ帳

    Flash オーサリングツール

    flex builder ("IDE")

  • ActionScript + MXML
  • 今回は基本的にflex builder を使用していきます。
  • Flashクライアント ランタイム

    ActionScriptで書かれたプログラムは "ランタイム" と呼ばれるアプリケーションの中で実行されます。

  • Flash Player
  • Adobe AIR
  • Flash Lite
  • ActionScript バーチャルマシーン (AVM)

  • ActionScriptのコードを内部的に実行する仕組み
  • AVM1: ActionScript 1.0 & 2.0
  • AVM2: ActionScript 3.0
  • ではActionScript プログラムって何?

    コードの中には多くの情報が埋め込まれています。

  • コード + .fla 情報 (スクリプト)
  • コードのみ, "純粋な ActionScript" (プログラム)
  • このコードが.swf ファイルに「コンパイル(変換)」されます。

    今日の目的: 純粋なオブジェクト指向プログラム

    ただし、タイムラインベースのスクリプトにも役立つことでしょう。

    飛行機を作り上げるということ

    今から飛行機を作るところを想像してください。

    始めの一歩: 設計図

    設計図はひとつひとつのパーツ用に用意されています(タイヤ、翼、座席などなど)

    それぞれの設計図は対象のパーツの物理的な特徴について説明しています。

  • ある設計図はひとつだけしか存在しないものについて説明しているかもしれません(例えば、フロントガラスなど)
  • 他の設計図は複数存在しているものについてひとつの設計図でまとめられています(座席とか)
  • 全体の設計図では、最終的にそれぞれのパーツがどのように組み合わせるのか説明しています。

    組み合ったパーツ同士の相互作用が飛行機を動かすのです。

    クラス と オブジェクト という概念

    飛行機が飛ぶと

  • それぞれの設計図に基づいて、パーツ同士が相互に働く
  • プログラムが実行されると

  • それぞれのクラスに基づいて、オブジェクト同士が相互に働く
  • オブジェクト

    オブジェクトとは、あるプログラムの中にある "モノゴト"のことを指します。

  • 計算中に使われる、数字
  • インターフェースの中にある、ボタン
  • 時間軸の中にある、ある瞬間
  • 画像に使われる、ブラーエフェクト
  • クラス

    クラスとは、オブジェクトの元になる設計図のことです。

    オブジェクトは、設計図であるクラスを具現化したもの(インスタンス)です。

    クラスは、オブジェクトの特徴動作を定義しています。

    ビルトインクラス

    いくつかのクラスはゼロから書かれます("カスタムクラス")

  • ゲームの中で使用する、車のためのクラス
  • 買い物カゴアプリの中の、注文フォームクラス
  • ActionScriptとFlashランタイムから直接提供されるクラスもあります("ビルトインクラス")

  • 文字、サウンド、画像、ネットワーク周りなど
  • オブジェクト指向によるプログラムの作成

  • カスタムクラスを作る。
  • ビルトインクラスやカスタムクラスからオブジェクトを作る。
  • 作ったオブジェクトに、何をすべきかを伝える。
  • オブジェクト同士の動きがプログラム自体全体の動きになります。

    メインクラス

    全てのプログラムは、ひとつのメインクラスを持ちます。

    プログラムはメインクラスから実行されます。

    プログラムを開始する時は

  • Flashランタイムが対象の.swf を読み込みます。
  • Flashランタイムがメインクラスのインスタンスを自動的に作ります。
  • the virtual zoo (仮想動物園)

    架空の動物園をシミュレートするゲームです。

    「たまごっち」のようにプレーヤーはペットに餌付けをしなくてはいけません。

    フォルダ構造

    以下のようににフォルダを作りましょう。/virtualzoo/

    ActionScriptのソースコードはここに保存します。 /virtualzoo/src/

    メインクラス: VirtualZoo

    プログラムのメインクラスは VirtualZoo です。

    テキストファイルを作り VirtualZoo.as として保存しましょう。

    クラス名とファイル名は完全に一致しなくてはいけません。

    パッケージ

    潜在的な問題として:

  • "VirtualZoo"という名前はもしかしたら、Flashが用意しているビルトインクラスと競合してしまうかもしれません。
  • 名前の競合を回避するためにはパッケージを使います。

    パッケージ名は、クラス名を延長するものです

    人のフルネーム: ファーストネーム + ラストネーム

    クラスのフルネーム: パッケージ名 + クラス名

    ※訳注:名前の順番という概念においては、家の名前(氏) + 個人の名前(名)となる日本式のほうがオブジェクト指向的と言えるかもしれません。(英語では、ファーストネーム(個人名) + ラストネーム(苗字)なので)

    パッケージ名

    パッケージ名は小文字でなければいけません:

  • game
  • physics
  • networking
  • パッケージはネスト(階層化)できます:

  • game.vehicles
  • physics.2d
  • パッケージは通常、自分が所属しているドメインを逆から記述します:

  • com.yourdomain.game
  • org.moock.utils
  • パッケージ内のクラス

    パッケージ内のクラスは、自分の所属するパッケージ名を利用します。

    例えば、Playerというクラスがgameパッケージ内にあるとすると

  • game.Player
  • クラス名とパッケージの名前はドット(.)で分けます。

    パッケージの定義

    パッケージの定義は以下のように行います:

    package パッケージ名 { }

    定義はpackageキーワードで始まります。

    キーワードとは もともとActionScript内で定義されている予約語のことです。

    名前の後ろに続く部分がパッケージの内容になります。

    大括弧 "{", "}" が内容の最初と最後につきます。

    大括弧の中の部分を "パッケージボディ" とか "パッケージブロック"と呼びます。

    zoo パッケージ

    アプリケーションに利用するクラスを格納する、zoo というパッケージを作りましょう。

    package zoo { }

    VirtualZoo.as を /zoo/ という新しいフォルダに移動させます。

    パッケージ名とフォルダ構造は必ず一致させます。

    例えば org/moock/zoo/VirtualZoo.as

    クラスの定義

    クラスを定義してみましょう:

    class 識別子 { }

    クラス定義は class キーワードで開始します。

    "識別子" とは名前のことです。

    お約束: クラス名の最初の一文字は大文字を使用しましょう。

    大括弧 "{", "}" が最初と最後につきます。

    大括弧の中の部分を "クラスボディ" とか "クラスブロック"と呼びます。

    VirtualZoo クラスの定義

    package zoo { class VirtualZoo { } }

    VirtualPet クラス

    動物園の中にいるペットはVirtualPetクラスのインスタンスとして表現します。

    新しいソースファイル: VirtualPet.as

    package zoo { class VirtualPet { } }

    以下の場所に保存します。 /virtualzoo/src/zoo/

    zooパッケージの中には複数のファイルが含まれます。

    お約束: ファイルはクラスごとに用意します。

    public 属性

    デフォルトでは、クラスは自身が格納されているパッケージ内でしか使えません。

    パッケージ外でも対象のクラスを使えるようにするにはpublic属性を設定しましょう。

    package zoo { public class VirtualZoo { } }

    プログラムのメインクラスは必ずpublic属性にしてください。

    internal 属性

    パッケージ内でのみ使えるクラスはinternal属性を設定しましょう。

    package zoo { internal class VirtualPet { } }

    以下の内容でも同じです:

    package zoo { class VirtualPet { } }

    ※訳注:クラス修飾子(後述)を省略すると対象のクラスは internal として認識されます。

    publicinternalはクラス修飾子と呼ばれます。

    コンストラクタメソッド

    コンストラクタメソッドはクラスのインスタンスを初期化するときに使用します。

    function 定義を使用してコンストラクタメソッドを作りましょう。

    package zoo { public class VirtualZoo { function VirtualZoo () { } } }

    コンストラクタメソッドはfunctionキーワードで始まります。

    そして、メソッド名が続きます。このメソッド名はクラス名と完全に一致していなくてはいけません。

    さらにコンストラクタパラメータが続きます(詳細後述)

    大括弧 "{", "}" が最初と最後につきます。

    大括弧の中の部分を "コンストラクタ ボディ"と呼びます。

    コンストラクタは public

    ActionScript 3.0 では全てのコンストラクタがパブリック属性になります

    package zoo { public class VirtualZoo { public function VirtualZoo () { } } }

    インスタンスの初期化

    オブジェクトが作られると、そのコンストラクタが実行されます。

    メインクラスのコンストラクタは「プログラムを開始させる」という特別な役割を持っています:

    VirtualPet オブジェクトの作成

    動物園にペットを加えるために、VirtualPet オブジェクトを作りましょう。

    一般的な方法:

    new クラス名

    VirtualPet オブジェクトを作るときは:

    new VirtualPet

    ふたつの VirtualPet オブジェクトを作るなら:

    new VirtualPet new VirtualPet

    オブジェクト作成時の文法いろいろ

    ビルトインクラスのいくつかは特別な記述法を使用します。

    数値情報を表す Number オブジェクトを作る場合:

    25.4

    文字情報を表す String オブジェクトを作る場合:

    "hello"

    論理情報を表す Boolean オブジェクトで"正"を作る場合:

    true

    論理情報を表す Boolean オブジェクトで"偽"を作る場合:

    false

    ペットをプログラムの中に追加する

    VirtualPet オブジェクトをプログラムに追加しましょう。:

    package zoo { public class VirtualZoo { public function VirtualZoo () { new VirtualPet } } }

    VirtualPet ではパッケージ名は使わず、そのままクラス名のみで記述します。

    zooただし、パッケージ外のクラスを使用する場合は、事前に対象のクラスをインポートしておく必要があります。

    package zoo { import flash.media.Sound public class VirtualZoo { public function VirtualZoo () { new Sound } } }

    ペット失踪事件

    問題点: 現状ではプログラムがVirtualPet オブジェクトを見つけられません。

    つまり、VirtualPet オブジェクトをコントロールできません

    変数を使って、作ったオブジェクトを参照できるようにしましょう。

    変数と値

    全てのオブジェクトは何らかのとして保存されます。

    変数はある値を参照する識別子にすぎません

  • submitBtn は Button オブジェクトを参照しています。
  • productDescription は文字列 "toothpaste" を参照しています。
  • 変数を使用すると、作ったオブジェクトの場所を保存しておけます。

    ローカル変数

    変数には四つの種類があります:

  • ローカル変数, インスタンス変数, ダイナミックインスタンス変数, スタティック変数
  • メソッドや関数内など限られた中で、情報に参照したい場合はローカル変数を使います。

    ローカル変数の作成

    変数定義を使って、ローカル変数を作りましょう。

    var 識別子 = ;

    "= 値" の部分で変数を初期化します。

    この値を決めることを、"代入" とか "設定"と呼びます。

  • もし、このパートが省略されると、クラスの持つデフォルトの値が代入されます。
  • 変数 pet

    VirtualPet オブジェクトを参照する変数を作りましょう:

    package zoo { public class VirtualZoo { public function VirtualZoo () { var pet = new VirtualPet; } } }

    これで pet という変数を通してVirtualPet オブジェクトをコントロールできるようになりました。

    でも、現状ではこのペットはただのオブジェクトで何もしてくれません。ここを何とかしていきましょう。

    オブジェクトの特徴の把握

    クラスの説明を思い出してください:

  • オブジェクトの特徴
  • オブジェクトの動作
  • 特徴とはつまり、そのオブジェクトがどんな値を持っているか、ということです。

    オブジェクトの特徴を継続して把握するにはインスタンス変数を使用します。

    インスタンス変数

    インスタンス変数は、オブジェクトに対して設定される変数です。

  • width は 150 という数値を参照しています。
  • address は "55 Main St" という文字列を参照しています。
  • インスタンス変数を作るときは、クラスレベルでの変数定義を行います。

    class クラス名 { var 識別子 = ; }

    ペットのニックネーム

    インスタンス変数を使って、ペットの名前を設定しましょう。

    package zoo { internal class VirtualPet { var petName = "名無しさん"; } }

    全てのペットの名前は最初に "名無しさん" として設定されます。

    新しい名前の設定

    petName の値を変更して新しい名前を設定しましょう。

    オブジェクト.インスタンス変数 =

    petName を "Stan" とする場合は

    package zoo { public class VirtualZoo { public function VirtualZoo () { var pet = new VirtualPet; pet.petName = "Stan"; } } }

    メンバ修飾子

    public: どこからもアクセス可能

    internal: パッケージ内からならアクセス可能

    protected: クラス内および、そのクラスが継承(後述)されたクラスからアクセス可能

    private: クラス内でのみアクセス可能

    デフォルトでは internalが設定されます。

    ※訳注:メンバ修飾子、クラス修飾子、メソッド修飾子など小難しい名前はいろいろありますが、用語を一生懸命に覚えるのではなく、それが何を意味しているのかを理解することに重点を置くことをおススメします。

    カプセル化

    オブジェクトの変数は、本来ならばそのオブジェクトだけで完結しているべきです。

    したがって、外部から変数を操作されないようにprivate か internalを使用します。

    オブジェクトの特徴はメソッドを介してのみ(discussed later)

    今のところは petName は internal としておきますが、あとで private に変更します。

    package zoo { internal class VirtualPet { internal var petName = "名無しさん"; } }

    オブジェクトの初期化

    現状では VirtualPet は以下のコードで初期化しています:

    var pet = new VirtualPet; pet.petName = "Stan";

    利便性をあげるためにコンストラクタパラメータを使いましょう。

    コンストラクタパラメータ

    コンストラクタパラメータ: コンストラクタ宣言時に定義されるローカル変数のこと

    class クラス名 { function クラス名 (識別子 = ) { } }

    複数のコンストラクタパラメータ を設定するには

    class クラス名 { function クラス名 (識別子1 = 値1, 識別子2 = 値2, 識別子3 = 値3) { } }

    コンストラクタパラメータの値の設定

    コンストラクタを呼び出す際には、引数を使って値を設定しましょう:

    new クラス名()

    デフォルトの値はを識別氏を使って設定します:

    class クラス名 { function クラス名 (識別子 = ) { } }

    もしもここでデフォルトの値が設定されていない場合は、コンストラクタ呼び出し時に引数を使って設定します。

    VirtualPet のコンストラクタパラメータ

    nameというパラメータが必要です:

    package zoo { internal class VirtualPet { internal var petName = "名無しさん"; public function VirtualPet (name) { } } }

    nameの値はコンストラクタの引数から代入されます:

    package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet("Stan"); } } }

    ある変数の値を他の変数へ代入

    VirtualPet のコンストラクタ内では name の値は"Stan"となっています。

    petName にもその値を代入してみましょう。

    オブジェクト.インスタンス変数 =

    this.petName = name

    this は生成された VirtualPet オブジェクトのことを指します。

    package zoo { internal class VirtualPet { internal var petName = "名無しさん"; public function VirtualPet (name) { this.petName = name; } } }

    petName の識別子を取り除く

    petNameVirtualPetコンストラクタから常に値を受け取っています。

    二重で設定する必要はないのでインスタンス変数上での petName の初期化はやめてしまいましょう。

    package zoo { internal class VirtualPet { internal var petName; public function VirtualPet (name) { this.petName = name; } } }

    コピーと参照

    実は、以下の代入式では2種類の結果が起こり得ます:

    this.petName = name;

    もしnameの値が String, Boolean, Number, int, uint 型の場合

  • ActionScript はその値をコピーしpetNameに代入します。
  • もしnameの値がそれ以外のオブジェクトだったら

  • ふたつの変数は同じオブジェクトを参照します。
  • Flash Content1

    ※訳注:上の図はクリックすると紙芝居的に進みます。この場合、String型として"a" の内容を "b" にコピーしました。その後、"b" の内容を変更しても、これはコピーされたデータなので、"a" には何も影響がないことに注目してください。

    ひとつのオブジェクトに対する複数の参照

    ふたつの変数があるひとつのオブジェクトを参照している場合

  • どちらか変数からでも、対象のオブジェクトを変更できます。
  • 与えられた変更は、参照時に利用した変数だけでなくもう一方にも反映されます。
  • var a = new VirtualPet("Stan"); // a.petName yields "Stan" var b = a; b.petName = "Tom" // a.petName now yields "Tom"

    変数はオブジェクトを含んでいるのではありません、むしろ参照しているだけなのです。

    ※訳注:このポイントはセミナーでコリンさんがかなり強調していた部分です。よく、変数は情報を格納する箱と例えられますが(少なくとも訳者はそう習いました)、むしろ情報への矢印(ショートカット、エイリアス)という概念に近いのかもしれません。

    ※訳注:"a"と"b"が同一のオブジェクトを参照していたときに、そのオブジェクトの中身が変更されると、どの参照元から見ても対象のオブジェクトは変更後の中身を持った状態となります。また、どちらか片方の参照が途切れたとしても、全ての参照が途切れるまでオブジェクトの情報はシステムメモリ上に残り、パフォーマンスに影響するので注意が必要です。

    ペット用 インスタンス変数

    VirtualZooコンストラクタが終了した後でもペットの値へ参照できるように、インスタンス変数が必要です。

    package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); } } }

    ペットの動作

    インスタンスメソッドはそのオブジェクトの動作を定義します。

    インスタンスメソッドとは、つまり:

  • あるオブジェクトに対する指示の集まり、と言えます。
  • 例えば:

  • Sound クラスのplayメソッド
  • TextField クラスのsetSelection メソッド
  • VirtualPet には eat メソッドを設定してあげましょう。

    インスタンスメソッドの作成

    class クラス名 { function 識別子 () { } }

    括弧の中にはパラメータが入ります (詳細後述)

    大括弧 "{","}"の中の部分を "コンストラクタ ボディ"と呼びます。

    インスタンスメソッドの実行

    コード内にあるインスタンスメソッドを実行してみましょう:

    オブジェクト.メソッド名()

    ドキュメント上では、メソッド名は括弧がついた状態で記述されます。例えば、play()

    ペットの「おなか」という概念

    新しいインスタンス変数を作りましょう: currentCalories(※訳注:現在のカロリー量)

    デフォルトの値は1000としておきます。

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } } }

    eat() メソッド

    eat()というインスタンスメソッドは呼び出されるたびにcurrentCaloriesに100を追加します。

    最終的には、ユーザーの何らかのアクションでeat()が実行されることになります。

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } function eat () { // これから内容を埋めていきます。 } } }

    eat() の実行

    package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); // ここで eat() を実行 this.pet.eat(); } } }

    ペットのカロリーを増加

    変数の値を増やすには:

    変数名 = 変数名 + 数値

    eat()の場合はcurrentCaloriesに100を追加するので、

    this.currentCalories = this.currentCalories + 100;

    thisは、メソッドが呼び出されたオブジェクトを参照しています。

    もっと簡単な方法としては:

    this.currentCalories += 100;

    VirtualPet の現状

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } function eat () { this.currentCalories += 100; } } }

    メソッド修飾子

    インスタンス変数のときと同様です。

    public: どこからも実行可能

    internal: パッケージ内でのみ実行可能

    protected: クラス内及び継承されたクラス内でのみ実行可能

    private: クラス内でのみ実行可能

    デフォルトではinternalが設定されます。

    ブラックボックスの法則

    "ブラックボックス"というものを想像してください。

    この箱は外についているノブでコントロールされます。

    ノブを回している人は箱の中の仕組みがわからなくても問題ありません。

    このノブが、public メソッドです。

    パブリックではないメソッドは内部的な仕組みを表しています。

    車とブラックボックス

    ドライバーは、エンジンの仕組みを理解する必要はありません。

    ただ、アクセルを踏むだけです。

    それだけで、何の問題もなくエンジンの動きを変えられます。

    パブリックメソッドに影響を与えることなく、プライベートメソッドを変更できるのです。

    API

    パブリックメソッドとパブリック変数はAPIとも呼ばれます。

    Application Programming Interface

    "Interface"とはそのクラスのインスタンスをコントロールするためのものです。

    一度決められた API はできる限り変更を加えないようにします。

    API を変更すると、そのクラスを使う全てのユーザーがコードを書き換えなくてはいけません。

    eat() メソッドを VirtualPet の API として追加する。

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat () { this.currentCalories += 100; } } }

    メソッドパラメータ と引数

    コンストラクタ と同様にメソッドにもパラメータを定義できます。

    ローカル変数をメソッド定義時に設定するには

    function メソッド名 (識別子1 = 値1, 識別子2 = 値2, 識別子n = 値n) { }

    値が入っていなければ、メソッド実行時にパラメータが必要になります。

    パラメータの値は引数としてメソッドに渡されます:

    メソッド名(値1, 値2, 値n)

    一定の値だけ、カロリーを増加

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } } }

    50増加

    package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { this.pet = new VirtualPet("Stan"); this.pet.eat(50); } } }

    戻り値

    メソッドは単に動作するだけでなく、値を返します。

    戻り値: メソッドによって返される値

    function メソッド名 () { return 値; }

    returnが実行された時点で、メソッドは終了します。

    function methodName ( ) { return; // 終了 }

    ペットの年齢

    ペットの年齢を返すメソッドを作りましょう。

    まずは、生まれた時間を記録します。

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; public function VirtualPet (name) { this.creationTime = new Date(); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } } }

    ※訳注:このあたりはセミナーでは時間の都合で割愛されましたが訳は入れておきます。

    getAge() メソッド

    年齢を計算して返す

    package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; public function VirtualPet (name) { this.creationTime = new Date(); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } public function getAge () { var currentTime = new Date(); var age = currentTime.time - this.creationTime.time; return age; } } }

    getAge()の使用

    ペットの年齢を取得するには:

    this.pet = new VirtualPet("Stan"); var age = this.pet.getAge();

    二つのペットの年齢の平均(averageAge)を取得するには:

    var pet1 = new VirtualPet("Sarah"); var pet2 = new VirtualPet("Lois"); var averageAge = (pet1.getAge() + pet2.getAge()) / 2;

    条件式とループ

    動物園の話とは脱線しますが…

    論理的な評価には条件式を使います。

    繰り返しの処理にはループを使います。

    条件式

    条件式: ある条件が整ったときにだけ、対象のステートメントが実行されます。

    ステートメントとは、コードのかたまりのことです。

    条件式にはifswitchがあります。

    if ステートメント

    「どちらにしようかな」

    if (testExpression) { codeBlock1 } else { codeBlock2 }

    もし、testExpression の評価が正しければ(真), codeBlock1の内容が実行されます。

    違うようであれば(偽)codeBlock2です。

    もし、testExpressionの値がブーリアン型でない場合はブーリアン型に変換されます。

    if のサンプル

    ギャンブルサイトにログインする場合を考えてみましょう。

    18歳以上かを確認し、userIsOver18 という変数に設定しましょう。

    if (userIsOver18) { // ギャンブル画面のコードを記述 } else { // "アクセスが拒否されました" という旨を表示 }

    比較式

    条件式の多くは比較になります。

    ここでは、"式" というのは値を持ったコードのことを指します。

    以下の式はfalse(偽)を返します。

    "a" == "b"

    以下の式はtrue(真)を返します。

    "a" == "a"

    以下の式はtrue(真)を返します

    6 < 7

    ==< は演算子と呼ばれます。

    演算子: 元から用意されているコマンドで、主に値を取りまとめるときに使います。

    比較を基にした条件式

    ※訳注:ここでは買い物カゴをイメージしている模様

    var basketStatus; if (numItems == 0) { basketStatus = "買い物カゴは空です。"; } else { basketStatus = "あなたの買い物カゴには: " + numItems + "個のアイテムが入っています。"; }

    if ステートメントの繰り返し

    ※訳注:対象の言語"lauguage"を判断して挨拶のセリフを設定。

    var greeting; if (language == "english") { greeting = "Hello"; } else if (language == "japanese") { greeting = "Konnichiwa"; } else if (language == "french") { greeting = "Bonjour"; } else if (language == "german") { greeting = "Guten tag"; } else { // 上記の言語ではない場合の処理を記述 }

    ループ

    ループ: 条件式が正しい間、繰り返される処理

    while, do-while, for, for-in, for-each-in

    while ステートメント

    testExpression がfalseになるまでcodeBlockの内容が繰り返し実行されます。

    while (testExpression) { codeBlock }

    while の例: xのy乗を計算

    calculate 2 * 2 * 2

    var total = 2; var counter = 0; while (counter < 2) { total = total * 2; counter = counter + 1; }

    while の例: 文字列を探す

    var address = "me@moock.org"; var isValidAddress = false; var i = 0; while (i < address.length) { if (address.charAt(i) == "@") { isValidAddress = true; break; } i++; }

    for ループ

    whileと同じようなことですが、より簡潔な記述法なら

    var total = 2; for (var i = 0; i < 2; i++) { total = total * 2; }

    var address = "me@moock.org"; var isValidAddress = false; for (var i = 0; i < address.length; i++) { if (address.charAt(i) == "@") { isValidAddress = true; break; } }

    条件式の組み合わせ

    論理演算子を使うと、複数の条件を合わせて評価できます。

    && (AND、両方), || (OR、片方)

    ユーザーが"CTRL" と "S" の両方を押したとき

    ユーザーネームかパスワードの片方が正しくないとき

    論理積(AND, 両方の意)

    お正月コンテスト

    プレイヤーは1月1日に正しいパスワードを入力しなくてはいけません

    var now = new Date( ); // 新しい Date オブジェクトを作成 var day = now.getDate( ); // 1~31の値が返される var month = now.getMonth( ); // 0~11の値が返される if ( password=="fun" && month == 0 && day == 1 ) { // 上記の全てがOKならこの内容を実行 }

    論理和(OR, 片方の意)

    パスワードがふたつの中のひとつの場合:

    if (guess == "cactus" || guess == "desert") { // パスワードが "cactus" もしくは "desert" ならこの中身を実行 }

    動物園の話に戻って

    メソッドについて、さらに掘り進めましょう

  • this キーワードの省略
  • オブジェクトの状態を取得/変更するためのメソッド
  • this の省略

    'this' を記述するのは面倒ですし、コード量も増えてしまいます。

    public function eat (numberOfCalories) { currentCalories += numberOfCalories; }

    ActionScript が識別子を見つけると、以下の順番で参照先を探します:

  • ローカル変数、パラメータ、内部の関数, インスタンス変数
  • numberOfCalories: この場合、ActionScriptはパラメータを参照します。

    currentCalories: ActionScript はインスタンス変数にある参照先を見つけます。

    識別子の解決(identifier resolution)と呼ばれています (詳細はEssential ActionScript 3.0の16章を参照)

    パラメータと変数が競合している場合

    以下のコードを見てください:

  • caloriesのときはパラメータを指します。
  • this.caloriesのときはインスタンス変数を指します。
  • package zoo { internal class VirtualPet { private var calories = 1000; public function eat (calories) { this.calories += calories; } } }

    ※訳注:this抜きの場合は、前述の順番に従いActionScriptはパラメータを参照します。対して、thisが付いている場合はインスタンス変数を参照します。

    オブジェクトの状態を直接変更するのはダメ

    ペットが長生きしすぎるかもしれません:

    somePet.currentCalories = 1000000;

    何らかの不具合を起こすかもしれません:

    somePet.currentCalories = -46;

    オブジェクトの状態はメソッドを使って変更

    currentCaloriesの最大値を2,000に制限しましょう。

    public function setCalories (newCurrentCalories) { if (newCurrentCalories > 2000) { currentCalories = 2000; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } }

    eat()メソッドも更新しましょう:

    public function eat (numberOfCalories) { setCalories(currentCalories + numberOfCalories); }

    modifier method(モディファイア メソッド)として呼ばれています。 (mutator, setterとも)

    状態を取得する動作もメソッド経由に変更

    カロリー量を取得してみましょう:

    public function getCalories () { return currentCalories; }

    "空腹度"(hunger)を取得してみましょう:

    public function getHunger () { return currentCalories / 2000; }

    リトライバー メソッド(retriever method) (accessor,getter) とも

    ※訳注:ActionScript上では、setter,getterはこの用途を実現する別の方法として定義されているので呼び名には注意が必要です。

    ペットの名前を設定、取得用のメソッド

    ペットの名前を文字数を制限しつつ設定しましょう。

    public function setName (newName) { //もし、新しい名前が20文字以上だったら、20文字目までで省略 if (newName.length > 20) { newName = newName.substr(0, 20); } else if (newName == "") { return; } // 新しい名前を適用 petName = newName; }

    名前の取得:

    public function getName () { return petName; }

    インスタンス変数 petName へのアクセスを制限:

    private var petName;

    getName() setName() の使い方

    package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); // 古い名前を oldName というローカル変数に設定 var oldName = pet.getName(); // ペットに新しい名前を設定 pet.setName("Marcos"); } } }

    package zoo { internal class VirtualPet { private var petName; private var currentCalories = 1000; public function VirtualPet (name) { setName(name); } // 以下省略 } }

    スタティック変数(static 静的変数)

    この変数は、インスタンスではなく、クラスに関連付けられます。

    したがって、クラス内のどこからでも参照できます。

  • ダイアログボックスの初期サイズ
  • 車の最高速度
  • class クラス名 { private static var 識別子 = ; }

    VirtualPetクラス におけるスタティック変数

    ペットの名前の最大の長さ

    ペットの"おなかの大きさ" (カロリーの最大値)

    package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; // 以下省略 } }

    スタティック変数を参照するには

    クラス名.値

    VirtualPet.maxCalories

    "マジック値"(後述)を回避するのに最適

    ※訳注:マジック値(magic values という用語の使用が正しいかどうかは不明…ただし、このコンセプトは非常に重要です。)

    マジック値とは

    特に説明がなく、直接記述された値のこと

    setName()中の20、eat()、getHunger()中の2000

  • 後から見直したときに、何の意味か分かりづらい。
  • 値の共有がされていないので、エラーが起こりやすい
  • ※訳注:たとえば、eat()の中の2000を1000にした際に、getHunger()の2000を変更し忘れるとマズイことになります。

    マジック値をスタティック変数に置き換え

    このような値はスタティック変数として一箇所にまとめておきましょう:

    package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; private var petName; // 最初に、カロリーの最大値の半分だけをペットに与えておきます。 private var currentCalories = VirtualPet.maxCalories/2; public function VirtualPet (name) { setName(name); } public function eat (numberOfCalories) { setCalories(currentCalories + numberOfCalories); } public function setCalories (newCurrentCalories) { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } } public function getCalories () { return currentCalories; } public function getHunger () { return currentCalories / VirtualPet.maxCalories; } public function setName (newName) { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } petName = newName; } public function getName () { return petName; } } }

    スタティックメソッド(static 静的メソッド)

    このメソッドは、インスタンスではなく、クラスに関連付けられます。

    したがって、クラス全体でひとつの機能を使用できます。

    Point.polar()

  • 極座標 (距離, 角度)を直交座標(x, y)に変換します。
  • これは Point オブジェクトではなく、ポイントという概念に関連しています。
  • スタティックメソッドだけを持ったクラス

    いくつかのクラスはスタティックメソッドだけで構成されています。

    全体に関わるクラスとしては、例えば

    Mouse.show(), Mouse.hide()

    関数(function)

    関数とはひとつの独立した命令の集合体のことです。(クラスやオブジェクトとは別次元の概念)

    メソッドと同じ構造をしています。

    function 識別子 (パラメータ1, パラメータ2, パラメータ3) { }

    パッケージレベル関数

    パッケージの中でも関数を定義できます。

    package パッケージ名 { internal (もしくは) public function 識別子 () { } }

    internal もしくは public として設定します。

    例えばflash.utils.setTimeout()を参照してみてください。

    もし、publicならファイル名と同じ名前で存在している必要があります。

    グローバル関数

    無名パッケージ中の関数

    package { public function 識別子 () { } }

    プログラムのどこからもアクセスできます。

    例えばtrace()

    関数の入れ子

    関数はある関数の内側にも定義できます。

    "サブ関数" はそれを定義した関数内でのみ使用できます。

    public function getRandomPoint (rectangle) { var randomX = getRandomInteger(rectangle.left, rectangle.right); var randomY = getRandomInteger(rectangle.top, rectangle.bottom); return new Point(randomX, randomY); function getRandomInteger (min, max) { return min + Math.floor(Math.random()*(max+1 - min)); } }

    食べ物の消化

    VirtualPetにカロリーを減らすメソッドを追加しましょう。: digest()(※訳注:digestは消化の意)

    amount to digest governed by static var: caloriesPerSecond

    private static var caloriesPerSecond = 100;

    private function digest () { trace(getName() + " digested some food."); setCalories(getCalories() - VirtualPet.caloriesPerSecond); }

    digest()を実行

    VirtualPetコンストラクタを更新しましょう:

    public function VirtualPet (name) { setName(name); // digest() を毎秒呼び出しましょう digestIntervalID = setInterval(digest, 1000); }

    digestIntervalID stores a number for canceling the interval

    private var digestIntervalID;

    ※訳注:setIntervalについてはマニュアル各種を参照してください

    ペットの死

    カロリーが尽きた場合は消化を中止します:

    public function setCalories (newCurrentCalories) { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } // 残りのカロリーを計算 var caloriePercentage = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage == 0) { clearInterval(digestIntervalID); trace(getName() + " has died."); } }

    死んだペットは食事をしない

    ペットが死んだ場合はeat()を実行しないようにしましょう。

    public function eat (numberOfCalories) { // もし、ペットが死んでいるのなら、 if (currentCalories == 0) { // currentCaloriesを編集しないでこのメソッドを終了 trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate some food."); setCalories(currentCalories + numberOfCalories); }

    継承

    あるクラスが関係する他のクラスの変数やメソッドの定義を借りる (もしくは 継承)すること。

    端的にいうならば、あるクラスの機能を他のクラスでも使えるようにすること。

    ※訳注:機能を貸す(親になる)クラスをスーパークラス、借りる(子になる)クラスをサブクラスと呼びます。

    例: スーパークラスAとサブクラスBがあるとして

    public class A { public var v = 10; public function m () { trace("メソッドm() が呼ばれました"); } } public class B extends A { // ここには特にメソッドを定義しません。 } var bInstance = new B(); bInstance.m(); // "メソッド m() が呼ばれました" と表示される。 trace(bInstance.v); // "10" と表示される。

    ※訳注:継承を行う際は class サブクラス名 extends 親クラス名と記述してあげます。

    食べ物の種類

    ペットはスシとリンゴ(Apple)を食べることにします。

    新しいクラスとして、 SushiApple を作りましょう。

    SushiApple のクラスとしての機能はほぼ同一です。

    共通する機能は Food というスーパークラスに設定してあげましょう。

    SushiAppleFood を拡張して作ります。

    Food クラス

    package zoo { public class Food { private var calories; private var name; public function Food (initialCalories) { setCalories(initialCalories); } public function getCalories ( ) { return calories; } public function setCalories (newCalories) { calories = newCalories; } public function getName ( ) { return name; } public function setName (newName) { name = newName; } } }

    Apple クラス

    Apple クラスは Food クラスの内容を継承します。

    Apple クラス内では名前と、デフォルトのカロリー量を設定します。

    package zoo { public class Apple extends Food { // Set the default number of calories for an Apple object to 100 private static var DEFAULT_CALORIES = 100; public function Apple (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } // Run Food constructor super(initialCalories); setName("Apple"); } } }

    Sushi クラス

    Sushi クラスは Food クラスの内容を継承します。

    Sushi クラス内では名前と、デフォルトのカロリー量を設定します。

    package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES = 500; public function Sushi (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } // Run Food constructor super(initialCalories); setName("Sushi"); } } }

    eat() メソッドを更新

    eat() メソッドで Food オブジェクトを使うように変更しましょう。

    public function eat (foodItem) { if (currentCalories == 0) { trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); }

    餌付けの時間

    では、新しい食べ物を与えてみましょう。

    package zoo { public class VirtualZoo { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); // StanにAppleを与えてみた。 pet.eat(new Sushi()); // StanにSushiを与えてみた。 } } }

    虫食いリンゴ

    foodタイプのクラスのどちらかに、新しい機能を付け足してみましょう。

    50%の確率でリンゴには虫(worm)が入っていることにします。

    package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES = 100; private var wormInApple; public function Apple (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random() >= .5; setName("Apple"); } public function hasWorm () { return wormInApple; } } }

    pペットは虫を食べない

    虫を受け付けないようにeat() メソッドを改良しましょう。

    public function eat (foodItem) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } if (foodItem is Apple) { if (foodItem.hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); }

    画面表示のための下準備

    アプリケーションのメインクラスは必ずSpriteMovieClipを継承して作られます。

    これによって、表示リストと呼ばれる、AS3での描画の仕組みを使うことができます。

    画面表示系についての詳細は後述します。

    正しくコンパイルするためにも、いまのところは make VirtualZoo では Sprite を継承することにしましょう。

    package zoo { import flash.display.Sprite; public class VirtualZoo extends Sprite { private var pet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); pet.eat(new Sushi()); } } }

    Flash CS3 でのコンパイル

  • ファイル > 新規作成
  • FlashFile(AS3.0) を選択
  • ファイル > 名前をつけて保存
  • /virtualzoo/src フォルダを選択
  • VirtualZoo.fla というフォルダ名で保存
  • プロパティパネルのドキュメントクラスにzoo.VirtualZooと入力
  • Flex Builderでコンパイルする際の下準備

    VirtualZoo クラスを名前が付けられていないパッケージに移します。:

    ※訳注:Flex Builderではドキュメントクラスはそのアプリケーションのパッケージの最上位に置かなくてはいけません。

  • VirtualZoo.as を /virtualzoo/src/zoo から /virtualzoo/src/ へ移動
  • VirtualZoo.as の中に以下の分を追加:
  • import zoo.*;

    package zoo { の部分を package { と変更。

    VirtualPet.as で internal class の部分を public class へと変更

    ※訳注:VirtualZooクラスがZooパッケージ外に出てしまったので、VirtualPetがパブリック属性になっていないとアクセスできない。

    Flex Builder: プロジェクトを作成

    ※訳注:2008年1月現在でFlexBuilder3 は公式には英語版βしか存在していないため、FlexBuilder3での操作は英語にて記述します。

  • File > New > ActionScript Project.
  • Project name を "virtualzoo" と入力
  • "Use default location"のチェックを外す
  • Project Contents > Folder, virtualzoo フォルダを選択
  • Next をクリック
  • Main source folder に "src" と入力
  • Main application file に VirtualZoo.as と入力
  • finish をクリック
  • Flex Builder: コンパイルと実行

  • navigator panel 内で何らかの virtualzoo プロジェクトのクラスを選択してください。
  • Run > Debug VirtualZoo で実行
  • 参照エラー

    存在していないメソッドや変数を参照しようとすると参照エラーというものが起きます。

    pet.eatt(new Sushi())

    ※訳注:"t"がふたつ、俗に言うタイポ(typo)

    pet.jump()

    ※訳注:そんなメソッドは存在していない

    ReferenceError: Error #1069: Property eatt not found on zoo.VirtualPet and there is no default value. at VirtualZoo$iinit()[C:\data\virtualzoo\src\VirtualZoo.as:8]

    ※訳注:ActionScriptはとても親切で、「どこで」、「どのような」エラーが発生したのかを丁寧に教えてくれます。エラーメッセージは怖がるものではなく、ありがたいものとして認識しましょう。(Error #1069はFlashCS3(ver9.0)日本語版でも英語で表示されますね…)

    型指定

    参照エラーはプログラムを実行する際に発覚します。

    型指定を使うとコンパイルの際に、参照エラーを見つけられます。

    型指定: 対象の変数やパラメータ、メソッドなどで、どの型の値を使用するのかをコンパイラに教えてあげること。

    var 識別子: = ;

    function 識別子 (パラメータ:): { }

    とは全ての "データタイプ"のことを指します。

    データタイプ

    データタイプ: 値の集まりのこと

    nullのデータタイプを持つものは null だけです。

    voidのデータタイプは undefinedが持っています。

    クラスはデータタイプになり、そのインスタンスやサブクラスのインスタンスは、そのデータタイプを持ちます。

    Food データタイプ

  • Food、Apple、Sushiの全てのインスタンスが該当します。
  • Apple データタイプ

  • Appleのインスタンスは該当しますが、 Sushi や Food から直接生まれたインスタンスは対象外です。
  • 型指定の例

    var meal:Food = new Food();

    var meal:Food = new Apple();

    strictモードでは、型の不一致が起こりエラーになります。:

    var meal:Food = new VirtualPet("Hoss");

    standardモードでは:

  • もし、値が基本形(String, Boolean, Number, int, uint)の場合、ランタイム上で強引に変換されます。
  • タイプが基本形でない場合は、エラーが発生します。
  • コンパイル時に参照エラーを発生させる

    var pet:VirtualPet = new VirtualPet("Stan");

    pet.eatt(new Sushi());

    コンパイルエラー:

    1061: 未定義である可能性が高いメソッド eatt を静的型 zoo:VirtualPet の参照を使用して呼び出しました。

    仮想動物園(virtual zoo)プログラムとデータタイプ

    package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo () { pet = new VirtualPet("Stan"); pet.eat(new Apple()); pet.eat(new Sushi()); } } }

    package zoo { import flash.utils.setInterval; import flash.utils.clearInterval; public class VirtualPet { private static var maxNameLength:int = 20; private static var maxCalories:int = 2000; private static var caloriesPerSecond:int = 100; private var petName:String; private var currentCalories:int = VirtualPet.maxCalories/2; private var digestIntervalID:int; public function VirtualPet (name:String):void { setName(name); digestIntervalID = setInterval(digest, 1000); } public function eat (foodItem:Food):void { if (currentCalories == 0) { trace(getName() + " is dead. You can't feed it."); return; } if (foodItem is Apple) { // キャスト(詳細後述)というテクニックがここで使用されています。 if (Apple(foodItem).hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } trace(getName() + " ate the " + foodItem.getName() + "."); setCalories(currentCalories + foodItem.getCalories()); } public function setCalories (newCurrentCalories:int):void { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } // 残りのカロリー残量%を計算 var caloriePercentage:int = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage == 0) { clearInterval(digestIntervalID); trace(getName() + " has died."); } } public function getHunger ():Number { return currentCalories / VirtualPet.maxCalories; } public function getCalories ():int { return currentCalories; } public function setName (newName:String):void { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } // 新しくて正しい名前を設定 petName = newName; } public function getName ():String { return petName; } private function digest ():void { trace(getName() + " digested some food."); setCalories(getCalories() - VirtualPet.caloriesPerSecond); } } }

    package zoo { public class Food { private var calories:int; private var name:String; public function Food (initialCalories:int) { setCalories(initialCalories); } public function getCalories ():int { return calories; } public function setCalories (newCalories:int):void { calories = newCalories; } public function getName ():String { return name; } public function setName (newName:String):void { name = newName; } } }

    package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES:int = 100; private var wormInApple:Boolean; public function Apple (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random() >= .5; setName("Apple"); } public function hasWorm ():Boolean { return wormInApple; } } }

    package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES:int = 500; public function Sushi (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); setName("Sushi"); } } }

    キャスト

    VirtualPet.eat())の部分をよく見てください:

    foodItem.hasWorm();

    strictモード使用時にはfoodItemFood のインスタンスでなくてはいけません。

    しかし、Food には hasWorm() というメソッドは定義されていないのでエラーが起こります。

    エラーを回避するためには、まずはfoodItemがAppleオブジェクトかどうかをチェックしましょう。

    if (foodItem is Apple) { ... }

    つづいて、キャストを用いてコンパイラーに、このfoodItemはAppleであると教えてあげます。:

    if (foodItem is Apple) { if (Apple(foodItem).hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } }

    描画関係をやりくりするために

    VirtualPet内に描画関係のコードを書くことも可能ではありますが…

    そうすると、コードが肥大化し柔軟な編集に対応できなくなります。

    VirtualPetView という新しいクラスに描画関係は任せてしまいましょう。

    VirtualPet の状態が変わると、VirtualPetView が描画を更新します。

    VirtualPet が自身の状態の変化を VirtualPetView へ教えてあげる方法が必要です。

  • イベント
  • イベントとイベント操作

    VirtualPetの状態として、満腹(full), 空腹(hungry), 餓死寸前(starving), 死亡(dead)の4つの状態を用意します。

    ActionScript のイベント通知システムを利用して VirtualPetView に状態が変更されたことを伝えましょう。

    イベント: なんらかの状態変化。出来事。

    イベントターゲット: VirtualPet オブジェクト (※訳注:イベントが起こったオブジェクト)

    イベントリスナー: イベントが発生したことが伝えられるメソッド

    空腹度を定義する定数

    public static const PETSTATE_FULL:int = 0; public static const PETSTATE_HUNGRY:int = 1; public static const PETSTATE_STARVING:int = 2; public static const PETSTATE_DEAD:int = 3;

    private var petState:int;

    ※訳注:ここでは、定数(コンスタント、const)という変数とは若干違う概念が使われています。詳細はマニュアル等を参考にしてください。

    空腹状態を取得/変更する関数

    private function setPetState (newState:int):void { if (newState == petState) { return; } petState = newState; }

    public function getPetState ():int { return petState; }

    カロリーが変更されたら、状態も変更

    update setCalories()

    private function setCalories (newCurrentCalories:int):void { if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else if (newCurrentCalories < 0) { currentCalories = 0; } else { currentCalories = newCurrentCalories; } var caloriePercentage:int = Math.floor(getHunger()*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); // ペットの状態を記述 if (caloriePercentage == 0) { // The pet has no food left. Set state to dead. setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); } else if (caloriePercentage < 20) { // The pet needs food badly. Set state to starving. setPetState(VirtualPet.PETSTATE_STARVING); } else if (caloriePercentage < 50) { // The pet needs food. Set state to hungry. setPetState(VirtualPet.PETSTATE_HUNGRY); } else { // The pet doesn't need food. Set state to full. setPetState(VirtualPet.PETSTATE_FULL); } }

    eat()を更新

    eat()でペットが死んでしまった場合を考慮してみましょう。

    public function eat (foodItem:Food):void { // もし、ペットが死んでしまった場合は終了 if (petState == VirtualPet.PETSTATE_DEAD) { trace(getName() + " is dead. You can't feed it."); return; } // foodItemがリンゴのときは、虫食いかどうかチェック。 // 虫が入っていたら、食べない。 if (foodItem is Apple) { if (Apple(foodItem).hasWorm()) { trace("The " + foodItem.getName() + " had a worm. " + getName() + " didn't eat it."); return; } } // Display a debugging message indicating what the pet ate trace(getName() + " ate the " + foodItem.getName() + " (" + foodItem.getCalories() + " calories)."); setCalories(getCalories() + foodItem.getCalories()); }

    死亡をひとまとめに

    setCalories()では、確かにペットの死を設定できますが、この方法はおススメできません。

    ※訳注:あくまでもsetCalories()はカロリーの変更を司るのであって、「死」というペットの状態は他の部分が担当すべき

    このコードを外部に持ち出しましょう

    if (caloriePercentage == 0) { setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); }

    新しくdie()というメソッドを作ります。

    if (caloriePercentage == 0) { die(); }

    die() メソッドを定義します:

    private function die ():void { // Moved from setCalories() setPetState(VirtualPet.PETSTATE_DEAD); clearInterval(digestIntervalID); trace(getName() + " has died."); }

    ※訳注:これで、今後、他の死亡要因(交通事故でも病気でも)があったときでも、deth()を呼ぶだけで済みます。

    一般的に殆どのクラスでは、使用終了後にシステムメモリからクラス自身を消去するために、このdie()に似たコンセプトのメソッドを持っています。

    カロリーの初期値を設定

    コンストラクタで、カロリーの初期値を設定しましょう。自動的に初期の空腹度も設定されます。

    setCalories(VirtualPet.maxCalories/2);

    状態が変わったというイベントを通知

    ※訳注:イベントの通知にはFlashでもともと用意されている EventDispatcher という仕組みを使用します。

    EventDispatcherを拡張:

    import flash.events.*; public class VirtualPet extends EventDispatcher {

    イベントの名前を設定:

    public static const STATE_CHANGE:String = "STATE_CHANGE";

    setPetState()からイベントを通知:

    private function setPetState (newState:int):void { if (newState == petState) { return; } petState = newState; dispatchEvent(new Event(VirtualPet.STATE_CHANGE)); }

    VirtualPetView クラス

    ペットを画面上で表示させるためのクラスです。

    package zoo { import flash.display.*; import flash.events.*; public class VirtualPetView extends Sprite { // 表示されるペット private var pet:VirtualPet; public function VirtualPetView (pet:VirtualPet) { // ペットを変数に格納 this.pet = pet; // ペットの状態が変わったら、イベントが通知されるようにリスナーとして登録 pet.addEventListener(VirtualPet.STATE_CHANGE, petStateChangeListener); } private function petStateChangeListener (e:Event):void { renderCurrentPetState(); } private function renderCurrentPetState ():void { var state:int = pet.getPetState(); switch (state) { case VirtualPet.PETSTATE_FULL: trace(pet.getName() + " はおなかがいっぱいです。"); break; case VirtualPet.PETSTATE_HUNGRY: trace(pet.getName() + " はおなかがすいてきました。"); break; case VirtualPet.PETSTATE_STARVING: trace(pet.getName() + " はおなかが空いて死にそうです。"); break; case VirtualPet.PETSTATE_DEAD: trace(pet.getName() + " は死んでしまいました。"); break; } } } }

    ※訳注:ここでは画面表示はあとに回して、リスナーが確実に動いているかどうかをtrace文でテストしています。

    VirtualPetView に現在のペットを設定

    VirtualZooに新しいインスタンス変数を作りましょう:

    private var petView:VirtualPetView;

    VirtualPetView を VirtualZoo コンストラクタ内で作成しましょう:

    petView = new VirtualPetView(pet);

    コードをテストして、うまく動くかどうか試してみましょう。

    名前が変更されたときのイベント

    VirtualPet に新しくイベント用の定数を作ります

    public static const NAME_CHANGE:String = "NAME_CHANGE";

    setName()からイベントを通知します:

    public function setName (newName:String):void { if (newName.length > VirtualPet.maxNameLength) { newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { return; } // 新しい名前を適用 petName = newName; dispatchEvent(new Event(VirtualPet.NAME_CHANGE)); }

    名前変更イベントの受取

    VirtualPetView内に名前の変更を受け取るイベントリスナーと、それを受けて名前を変更して表示させるメソッドを作ります

    private function petNameChangeListener (e:Event):void { renderCurrentPetName(); } private function renderCurrentPetName ():void { trace("Pet name is now: " + pet.getName()); }

    VirtualPetView のコンストラクタ内でイベントリスナーを登録:

    pet.addEventListener(VirtualPet.NAME_CHANGE, petNameChangeListener);

    VirtualPetView コンストラクタ内で名前を初期化:

    renderCurrentPetName();

    グラフィックですよ!

    仕組みの部分は整ったので、今度は描画部分を作りましょう。

    まずは、描画周りのAPIについて学んでいきます。

    描画API

    描画の機能は大きく3段階に分けられます。

  • 表示
  • インタラクティブ
  • コンテナ
  • 描画API コアクラス

    DisplayObject:全ての描画クラスの基礎になります。

    InteractiveObject: マウスやキーボードに反応するインタラクティブ機能を持ちます。

    DisplayObjectContainer:他の描画オブジェクトを内包するコンテナ機能を持ちます。

    Sprite:ドラッグできたり、ボタンのように反応したりといった機能を持ちます。

    MovieClip:Spriteにタイムラインの機能を加えたものです。

    描画オブジェクトの生成

    var t:TextField = new TextField()

    var s:Sprite = new Sprite()

    表示リスト

    画面上に表示されるオブジェクトの階層構造

    Stage インスタンスが階層構造のルートになります。

    Stage インスタンスはコンテナ機能を持っています。

    .swfファイルのメインクラスのインスタンスがStage の最初の子階層として追加されます。

    このメインクラスの子階層として追加されたグラフィックオブジェクトが、画面に表示されます。

    flash3

    コンテナと子オブジェクト

    DisplayObjectContainer クラスには様々なメソッドが定義されています:

  • addChild()
  • removeChild()
  • addChildAt()
  • removeChildAt()
  • 描画オブジェクトは、オブジェクト自体の存在を残したまま、表示リストから外すことができます。

    オブジェクトの状態(x, y, rotation) は表示リストから外れた状態でも保持されます。

    描画APIの例

    package { import flash.display.*; import flash.text.TextField; public class GreetingApp extends Sprite { public function GreetingApp() { // 矩形を作成 var rect:Shape = new Shape(); rect.graphics.lineStyle(1); rect.graphics.beginFill(0x0000FF, 1); rect.graphics.drawRect(0, 0, 75, 50); // テキストメッセージを作成 var greeting_txt:TextField = new TextField(); greeting_txt.text = "Hello world"; greeting_txt.x = 60; // 作った描画オブジェクトを表示リストに追加 addChild(greeting_txt); addChild(rect); } } }

    ペットを表示

    VirtualPetViewを更新しましょう:

  • 読み込む画像用に汎用的なコンテナを用意します。
  • ペット用の画像は外部の .gif ファイルとして読み込みます。
  • 画像ファイルが読み込まれた際には、 COMPLETE というイベントを通知します。
  • 名前の表示用にテキストフィールドも用意しましょう。
  • 読み込まれる画像用のグラフィックコンテナ

    VirtualPetView に新しくインスタンス変数を作りましょう。

    private var graphicsContainer:Sprite;

    新しいメソッドも作ります。

    private function createGraphicsContainer ():void { graphicsContainer = new Sprite(); addChild(graphicsContainer); }

    コンストラクタを更新しましょう:

    createGraphicsContainer(); renderCurrentPetName();

    ローディング用の変数

    画像を読み込むにはLoader オブジェクトを使います。

    import fash.net package:

    import flash.net.*;

    ひとつのグラフィックに対して、ひとつのオブジェクト

    ひとつのオブジェクトに対して、ひとつの変数

    private var petAlive:Loader; // ペットが生きている時の画像用のローダー private var petDead:Loader; // ペットが死んでしまった時用の画像用のローダー private var foodHungry:Loader; // 空腹状態を示すアイコン用のローダー private var foodStarving:Loader; // 餓死寸前状態を示すアイコン用のローダー

    読み込まれた画像を数える変数

    画像が何枚読み込まれたかを把握できるようにしましょう。

    全ての画像が読み込まれると COMPLETE イベントが発動します。

    static private var numGraphicsToLoad:int = 4; private var numGraphicsLoaded:int = 0;

    ローディング用のメソッド

    private function loadGraphics ():void { // ペットが生きている時に表示される画像 petAlive = new Loader(); petAlive.load(new URLRequest("pet-alive.gif")); petAlive.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); petAlive.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); // ペットが死んでしまった時に表示される画像 petDead = new Loader(); petDead.load(new URLRequest("pet-dead.gif")); petDead.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); petDead.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); // "食べ物が必要です" アイコン foodHungry = new Loader(); foodHungry.load(new URLRequest("food-hungry.gif")); foodHungry.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); foodHungry.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); foodHungry.x = 15; foodHungry.y = 100; // "ほんとに食べ物が必要です" アイコン foodStarving = new Loader(); foodStarving.load(new URLRequest("food-starving.gif")); foodStarving.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); foodStarving.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); foodStarving.x = 15; foodStarving.y = 100; }

    画像が読み込まれた時のイベント

    private function completeListener (e:Event):void { // 画像が読み込まれるたびに、1ずつ追加 numGraphicsLoaded++; // もし全ての画像が読み込まれたのなら if (numGraphicsLoaded == numGraphicsToLoad) { // ペットを表示、 COMPLETE イベントを発動 renderCurrentPetState(); dispatchEvent(new Event(Event.COMPLETE)); } }

    画像の読み込みが失敗したときのためのリスナー

    private function ioErrorListener (e:IOErrorEvent):void { trace("Load error: " + e); }

    ペットの表示部分を更新

    private function renderCurrentPetState ():void { // 全てのグラフィックオブジェクトをいったん、表示リストから排除 for (var i:int = graphicsContainer.numChildren-1; i >= 0; i--) { graphicsContainer.removeChildAt(i); } // ペットの現在の状況をチェック var state:int = pet.getPetState(); // 適切な画像を表示 switch (state) { case VirtualPet.PETSTATE_FULL: graphicsContainer.addChild(petAlive); break; case VirtualPet.PETSTATE_HUNGRY: graphicsContainer.addChild(petAlive); graphicsContainer.addChild(foodHungry); break; case VirtualPet.PETSTATE_STARVING: graphicsContainer.addChild(petAlive); graphicsContainer.addChild(foodStarving); break; case VirtualPet.PETSTATE_DEAD: graphicsContainer.addChild(petDead); break; } }

    VirtualPetView コンストラクタの更新

    VirtualPetViewが作られたときに、画像を読み込むようにします。

    createGraphicsContainer(); loadGraphics(); renderCurrentPetName();

    画像が全て読み込まれるまで、ペットは停止

    VirtualPet にstop()start() という新しいメソッドを作りましょう。

    clearInterval() の呼び出しを die() から stop() へと移動します。

    public function stop ():void { clearInterval(digestIntervalID); }

    private function die ():void { // 消化を中止 stop(); setPetState(VirtualPet.PETSTATE_DEAD); trace(getName() + " has died."); }

    setInterval() の呼び出しをコンストラクタから start()へと移動します。

    public function start ():void { stop(); digestIntervalID = setInterval(digest, 1000); }

    画像が読み込まれたら、ペットが動き出す

    イベント用のパッケージを読み込みましょう。

    import flash.events.*;

    画像が読み込まれた際のイベント通知を VirtualZoo コンストラクタに設定しましょう:

    petView.addEventListener(Event.COMPLETE, petViewCompleteListener);

    画像が読み込まれたところで、画像を表示し、ペットも動き出します。

    public function petViewCompleteListener (e:Event):void { addChild(petView); pet.start(); }

    ペットの名前を表示

    VirtualPetView で flash.text パッケージをインポートしましょう。

    import flash.text.*;

    TextField オブジェクト用に新しい変数を作りましょう。

    private var petName:TextField;

    名前用のテキストフィールドオブジェクトの作成

    TextField オブジェクトを作るための新しいメソッドを作りましょう:

    private function createNameTag ():void { petName = new TextField(); petName.defaultTextFormat = new TextFormat("_sans",14,0x006666,true); petName.autoSize = TextFieldAutoSize.CENTER; petName.selectable = false; petName.x = 250; petName.y = 20; addChild(petName); }

    VirtualPetView からcreateNameTag()メソッドを呼び出しましょう:

    createGraphicsContainer(); loadGraphics(); createNameTag(); renderCurrentPetName();

    名前を画面上に表示

    renderCurrentPetName()メソッドを更新しましょう:

    private function renderCurrentPetName ():void { petName.text = pet.getName(); }

    インタラクティビティ

    ペットに餌付けするためのボタンを作ります。

    FoodButton という新しいクラスを作りましょう。

    package zoo { import flash.display.* import flash.events.*; import flash.text.*; // FoodButton クラスはシンプルな文字ベースのボタンです。 public class FoodButton extends Sprite { // クリックされるテキスト private var text:TextField; // マウスカーソルがテキスト上に「乗っていない」時の文字フォーマット private var upFormat:TextFormat; // マウスカーソルがテキスト上に「乗っている」時の文字フォーマット private var overFormat:TextFormat; // コンストラクタ public function FoodButton (label:String) { // マウスカーソルがボタンに反応して指先カーソルになる機能を有効化 // (この buttonMode 変数は Sprite クラスから継承) buttonMode = true; // これより子階層のオブジェクトに対するマウスイベントを無効化 // (mouseChildren 変数は DisplayObjectContainer クラスから継承) mouseChildren = false; // マウスカーソルがテキスト上に「乗っていない」時の文字フォーマットを定義 upFormat = new TextFormat("_sans",12,0x006666,true); // マウスカーソルがテキスト上に「乗っている」時の文字フォーマットを定義 overFormat = new TextFormat("_sans",12,0x009999,true); // ボタンの動きをするテキストフィールドを作り、表示リストに追加 text = new TextField(); text.defaultTextFormat = upFormat; text.text = label; text.autoSize = TextFieldAutoSize.CENTER; text.selectable = false; addChild(text); // このオブジェクトにマウスカーソルが乗ったときのイベントを登録 addEventListener(MouseEvent.MOUSE_OVER, mouseOverListener); // このオブジェクトからマウスカーソルが外れたときのイベントを登録 addEventListener(MouseEvent.MOUSE_OUT, mouseOutListener); } // このオブジェクトに対するマウス関連イベントを無効化するメソッド public function disable ():void { // (The mouseEnabled variable is inherited from InteractiveObject.) mouseEnabled = false; } // このオブジェクトにマウスカーソルが乗ったときに発動するイベント public function mouseOverListener (e:MouseEvent):void { // Apply the "mouse over" text format text.setTextFormat(overFormat); } // このオブジェクトからマウスカーソルが外れたときに発動するイベント public function mouseOutListener (e:MouseEvent):void { // Apply the "mouse not over" text format text.setTextFormat(upFormat); } } }

    餌付けボタン用の変数

    VirtualPetView に "Feed Sushi"(スシをあげる)ボタンと "Feed Apple"(リンゴをあげる)ボタンを作りましょう。

    private var appleBtn:FoodButton; private var sushiBtn:FoodButton;

    餌付けボタンの作成

    ボタンを作るためのメソッド

    private function createUI ():void { // Feed Apple ボタン appleBtn = new FoodButton("Feed Apple"); appleBtn.y = 170; appleBtn.addEventListener(MouseEvent.CLICK, appleBtnClick); addChild(appleBtn); // Feed Sushi ボタン sushiBtn = new FoodButton("Feed Sushi"); sushiBtn.y = 190; sushiBtn.addEventListener(MouseEvent.CLICK, sushiBtnClick); addChild(sushiBtn); }

    VirtualPetView コンストラクタから createUI() メソッドを呼び出しましょう:

    createGraphicsContainer(); loadGraphics(); createNameTag(); renderCurrentPetName(); createUI();

    ボタンがクリックされたときの反応

    private function appleBtnClick (e:MouseEvent):void { // ペットにリンゴを与える pet.eat(new Apple()); } private function sushiBtnClick (e:MouseEvent):void { // ペットにスシを与える pet.eat(new Sushi()); }

    ペットが死んだ場合は、ボタンなどを無効化

    VirtualPetView に新しいメソッドを追加しましょう:

    private function disableUI ():void { appleBtn.disable(); sushiBtn.disable(); }

    petStateChangeListener()メソッドを更新しましょう。:

    private function petStateChangeListener (e:Event):void { if (pet.getPetState() == VirtualPet.PETSTATE_DEAD) { disableUI(); } renderCurrentPetState(); }

    これにて終了!

    audience.thank()

    追加の宿題:

  • 音を追加してみましょう
  • 複数のペットが存在する動物園を作ってみましょう。
  • .swfsを読み込んで動きのあるグラフィックにしてみましょう。
  • 食べ物の種類を追加してみましょう。