<< Flash ActionScript2.0 実践講座2:ActionScript2.0導入 | top | FlashOOP詳細(4):アプリケーションフレームワーク、XMLデータの読み込み >>
Flash ActionScript2.0 実践講座3:Flash OOP詳細
本日の予定
先週の授業に引き続き、Flashを用いてオブジェクト指向プログラミング(OOP)を行う方法について学びます。今回はColin Moock氏によるレクチャー資料を参照しながら、OOPについての概念の大枠を整理し、その後、サンプルプログラムを作成しながら具体的にOOPについて実践的に学んでいきます。
本日のスライド
サンプルファイル
目次
- OOP (Object Oriented Programiing)の用語のまとめ
- このスライドについて
- オブジェクト指向プログラミング(OOP)とは
- クラス(classes)とは
- クラスの中身
- 高レベルの集合体
- なんでOOPにするの?
- クラスからオブジェクト(objects)が生まれる
- OOP、3つのステップ
- 「たまごっち」を作ってみる
- コードの実例でさらにOOPについての理解を深めていく
- クラスファイル
- クラスの宣言
- メソッド:クラスのふるまい
- 消化:digest()メソッド
- 論理が先、表示は後で
- publicとprivate
- オブジェクトの生成
- オブジェクトの保持
- .flaファイルからクラスにアクセスする
- クラスにアクセスする別の方法
- 餌をやる:the feed() メソッド
- オブジェクトのメソッドを呼び出す
- 下記のコードを復習
- まちがったメソッドを呼び出すとどうなるか?
- データタイプと正しいメソッド
- たまごっちは歌えない
- 状態の管理
- Tamagotchiのプロパティ
- 正しい型のデータの保存storing the right type of data
- 現状のTamagotchiクラス
- privateプロパティ
- わたしのプロパティは、あなたにはわからない
- アクセサメソッド(accessor methods)
- プロパティ設定アクセサ
- 設定アクセサの使いかた
- プロパティー取得アクセサ
- カプセル化
- カプセル化の利点(設定フィルタリング)
- カプセル化の利点(取得フィルタリング)
- コンストラクタ関数
- Tamacotchiのコストラクタ
- オブジェクトの初期化
- クラスプロパティシンタックス
- マジックバリュー
- マジックバリューは良くない
- マジックバリューをクラスプロパティとして保存する
- 初期値をクラスプロパティとして保存する
- Tamagotchiの生命
- 最大カロリー
- カロリー消費レート
- 生か死か?
- 死ぬまで消化し続ける、死んだら消化が停止する
- Tamagotchiの誕生
- Tamagotchi の死
- die()メソッド全体
- カロリーの消費
- Tamagotchiの食べ物:寿司とリンゴ
- 類似点を分解する
- 継承
- Foodクラスのソース
- Sushiクラス(subclass)
- superclassのコンストラクタを呼び出す
- Appleクラス
- メソッドは型を返す
- なんでメソッドの宣言は型を返すの?
- メソッドの引数の型
- Tamagotchiになにを食べさせているのか?
- ここまでのまとめ
- ユーザからの入力を処理する
- keyboard入力に反応する
- キーボードのイベントを登録する
- 画像表示を追加する
- Tamagotchiシンボル
- TamagotchiSymbol?の中身
- ムービクリップを扱うプロパティ
- コンストラクタ関数の更新
- TamagotchiSymbol?のインスタンスをつくる
- Tamagotchiの名前を表示する
- たまごっちの死を表示する
- 完成コード
OOP (Object Oriented Programiing)の用語のまとめ
このスライドについて
- このスライドは、Colin Moock氏によるレクチャー ”Introduction to OOP with ActionScript? 2.0”をの内容を参照しています
- http://moock.org/lectures/introToOOP/
オブジェクト指向プログラミング(OOP)とは
- OOPとは全体を独立したモジュールに分解してプログラミングを行う手法のこと
- それぞれのモジュールは独立し、独自の状態を保つ
- しかしながら、モジュール同士は他のモジュールと協調しなくてはならない
- 「自立分散協調」
- モジュールはOOPでは「クラス(classes)」と呼ばれる
クラス(classes)とは
- それぞれのクラスは物理的に分けてプログラミングする
- コードのブロックを分ける
- ファイルを分ける
- クラスは、抽象的な「概念」または具体的な「物」をあらわしている
- 概念:現在の時刻、色
- 物:ボタン、プルダウンメニュー、レーシングカー
クラスの中身
- 「変数(variables)」と「関数(functions)」の集まり
- 変数:値を格納する
- 数、文字列、配列、真偽 など
- 関数:タスクを処理する
var shoppingListPrices = [100, 15, 340];
function calculateCost (priceList) {
var cost = 0;
for (var i = 0; i < items.length; i++) {
cost += priceList[i];
}
return cost;
}
高レベルの集合体
- OOPは、先頭の関数に連なる階層化されたプログラムの集合体
- 関数はそれ自身がクラスの変数として束ねられ、グループ化される
- それぞれのクラスがミニプログラムのようなもの
- OOPのプログラミングとは、全体のために協調して働く多くの小さなプログラムを書くようなもの
なんでOOPにするの?
- OOPはプログラミングを助けてくれる
- 自然な設計、自然な概念化
- プロジェクトを横断してプログラムを再利用できる
- 変更が容易
- 拡張が容易
- テストが容易
- 安定している、バグが入り込みずらい
- 複数のプログラマによる共同開発がやりやすい
クラスからオブジェクト(objects)が生まれる
- クラス自身は何もできない
- クラスは設計図でしかない、通常はオブジェクトを生成する
- 工場が車を作り出すような感じ
- それぞれのクラスが自分の所有するオブジェクトを生成する
- ホンダの工場はシビックを製造し、ポルシェの工場はボクスターを製造する
- 1つのクラスから多数のオブジェクトを生成できる
- 1つの工場が沢山の車を製造する
- 「オブジェクト」は「インスタンス(instance)」とも呼ばれる
OOP、3つのステップ
- オブジェクト指向のプログラムを作成するには:
- 一つ以上のクラスを作成する
- クラスからオブジェクトを生成する(インスタンス化する)
- オブジェクトに何をするのか命令する
- オブジェクト達の動作が、プログラム全体のふるまいを決定する
「たまごっち」を作ってみる
コードの実例でさらにOOPについての理解を深めていく
- サンプルとして「たまごっち」を作ってみる
- 餌をやらないと死んでしまうバーチャルペット
クラスファイル
- “Tamagotchi.as”というテキストファイル(Flashで作成する場合はAS2.0ファイル)を新規作成する
- ここにクラスを記述していく
クラスの宣言
- あたらしくクラスを作成するには、まずクラスの宣言をする
- クラスの名前はファイルの名称”Tamagotch.as”と同一でなくてはならない(大文字、小文字に関しても)
- 一般的にクラスの名称は大文字で始める
class Tamagotchi {
}
メソッド:クラスのふるまい
- Tamagotchiは、餌を食べ、消化し、死ぬ
- よって、Tamagotchiは、feed()、digest()、die()という関数をもたせたい
- 関数は、クラスのふるまいを決定する
- クラスのふるまいをメソッドと呼ぶ
消化:digest()メソッド
- まずは消化メソッド:digest()を実装してみよう!
class Tamagotchi {
private function digest () {
trace("たまごっち消化中...");
}
}
論理が先、表示は後で
- 現段階では、digest()メソッドは出力ウィンドウにメッセージを表示するだけ
- しかし、概念的にはTamagotchiは食べ物をすでに消化したと考える
- その後で、メッセージを表示していると捉える
- 表示は実装の詳細である
publicとprivate
- メソッド定義の前に”private”というキーワードがあることに注目!
- privateで定義されたメソッドは自身を定義したクラスからしか実行できない
- privateメソッドは、外部クラスから誤って使用されることを避けることができる
- 例えば、外部のコードからTamagotchiに消化させることはできない
- 反対に、”public”というキーワードをつけて定義されたメソッドは、プログラム中のどこからでも実行可能
オブジェクトの生成
- シンプルなクラスができたので、ここからオブジェクトを生成することが可能
- 生成される個々のTamagotchオブジェクトは、Tamagotchqクラスで提供された設計図を元にしている
- オブジェクトを生成するには以下のコードを使う
- 例えば、以下のコードはTamagotchiクラスから新たにオブジェクトを生成している
new ClassName();
new Tamagotchi();
オブジェクトの保持
- new Tamagotchi() のコードは、Tamagotchiオブジェクトを戻り値として返す
- 通常は、変数内に、この新しいオブジェクトを格納する
- 例えば、petという変数内にTamagotchiオブジェクトを格納したい場合
- オブジェクトの生成を、技術的には「インスタンス化」とも呼ぶ
var pet = new Tamagotchi();
.flaファイルからクラスにアクセスする
- Tamagotchiオブジェクトを、フラッシュファイル(.flaファイル)からインスタンス化したい
- クラスを使用するには、Flashファイル(.fla)からクラスファイル(.as)の場所がわかる必要がある
- おなじディレクトリに入れておけば自動的に.flaファイルは.asファイルをみつけだしてくれる
- よって、Tamagotchi.flaとTamagotchi.asを同じディレクトリにいれる
- その状態で、Tamagotchi.flaのタイムラインの1フレーム目に以下のコードを記入する
var pet = new Tamagotchi();
クラスにアクセスする別の方法
- コンポーネント内でincludeする
- クラスパスにクラスファイルの場所のディレクトリを登録する
- 同じディレクトリに入れるのが、いちばん簡単な方 → 採用
餌をやる:the feed() メソッド
- Tamagotchiに餌をやるメソッド:feed()を追加してみる
- 以下のコードを追加
- feed()メソッドはpublicで定義する。なぜなら、Tamagotchiの外部から餌をあげる必要があるから
public function feed (foodItem) {
trace("あなたは、たまごっちに餌をあげました");
}
オブジェクトのメソッドを呼び出す
- 現在petという変数に格納されたTamagotchiオブジェクトがある
- メソッドをTamagotchiに
- メソッドを呼び出して、Tamagotchiに何かをさせることができる
- オブジェクトのメソッドを呼び出すには、以下のコードを使う
- 例えば以下のコードは、pet変数に格納されたTamagotchiオブジェクトのfeed()メソッドを呼び出している
- Tamagotchi.flaファイルからTamagotchi.swfファイルをエクスポートした時、出力パネルに以下の表示が出るはず
- 「あなたは、たまごっちに餌をあげました」
theObject.theMethod()
pet.feed()
下記のコードを復習
- あたらしくTamagotchiオブジェクトを生成
- 変数petに格納
- petに餌をやる
var pet = new Tamagotchi(); pet.feed();
まちがったメソッドを呼び出すとどうなるか?
- たとえばTamagotchiに歌わせてみる
- ムービーをエクスポートしてもなにも起こらない
- たとえば、faido()のようにスペルミスしてもなにも起こらない
pet.sing()
データタイプと正しいメソッド
- もしFlashがどのタイプのオブジェクトが変数に格納されたのかわかれば、存在しないメソッドの呼出を検知できる
- しかし、Flash自身は変数のタイプを自動的に類推することは不可能、手作業でタイプを知らせる必要がある
- 変数の「厳密な型指定」を行う
- 「厳密な型指定」は以下のようにコード化する
var someVariable:ClassName;
たまごっちは歌えない
- Flashが存在しないメソッドを警告できるように、変数petの型を宣言する
- こうすることで、Tamagotchiに歌わせようとしても、Flashに存在しないメソッドであると検知してエラーを表示する
var pet:Tamagotchi = new Tamagotchi();
pet.sing();
**Error** Scene=Scene 1, layer=Layer 1, frame=1:Line 4:
There is no method with the name 'sing'.
pet.sing();
状態の管理
- いまのところ…、Tamagotchiは永久に死なない
- ずっと空腹にならない
- Tamagotchiの空腹度をモニターする必要あり
- 言い換えると、Tamagotchiの状態を管理する必要あり
- 変数が必要となる
- 変数はクラス内で定義され、オブジェクトの現在の状態を記述する
- クラス内で定義されるとき、変数はプロパティと呼ばれる
Tamagotchiのプロパティ
- Tamagotchiクラスは以下の状態を追跡する必要あり
- 空腹度(カロリー量)
- Tamagotchi'の名前
- これに対応するプロパティは以下で定義される
private var currentCalories:Number; private var name:String;
正しい型のデータの保存storing the right type of data
- currentCaloriesの宣言の別の部分に注目
- データの型と一緒に宣言されている部分に注目
- プロパティは宣言されたデータの型と一致した場合のみ格納される
- 例えば、
- currentCaloriesは数字のみ格納される
- nameは文字列のみ格納する
private var currentCalories:Number;
現状のTamagotchiクラス
class Tamagotchi {
private var currentCalories:Number;
private var name:String;
private function digest () {
trace("たまごっち消化中...");
}
public function feed () {
trace("あなたは、たまごっちに餌をやりました...");
}
}
- プロパティーがメソッドの前に宣言されている部分に注意!
privateプロパティ
- プロパティの宣言にさらに注目
- privateというキーワードが先頭にある
- privateプロパティはクラス内からのみ設定、修正できる
わたしのプロパティは、あなたにはわからない
- たとえば、Tamatotchiの名前をしりたいとする
- もし、直接名前を取得しようとしてもエラーになる
- privateのメンバーには外部からアクセスできない
- Tamagotchiクラスの外からは, Flashはプロパティnameを取得できない
trace(pet.name); The member is private and cannot be accessed. (terminology: "member" means method or property)
アクセサメソッド(accessor methods)
- じゃあどうやってオブジェクトの外部から、プロパティを設定したり修正したりできるのか?
- アクセサメソッドを使う
- アクセサメソッドでプロパティの設定や修正ができる
プロパティ設定アクセサ
- Tamagotchiの名前を設定するためのアクセサ
- プロパティの名前は、メソッド内で直接参照されている部分に注目
public function setName (newName) {
name = newName;
}
設定アクセサの使いかた
- Tamagotchiに新たな名前を設定するには、setName()メソッドを呼びだす
- 誤った例
pet.setName("yamada");
pet.name = "yamada";
プロパティー取得アクセサ
- 以下のアクセサはTamagotchiの名前を返す
- Tamagotchiの名前を取得するにはgetName()メソッドを呼びだす
- 例えば
- 間違った例
public function getName () {
return name;
}
trace(pet.getName());
trace(pet.name);
カプセル化
- 外部からアクセスできないプロパティーは、クラスからカプセル化されている
- 同様に、メソッドのソースコードはクラスからカプセル化されている
- 概念的には、カプセル化とはクラスの内部動作の詳細は隠蔽されているということ
- カプセル化の目標:オブジェクトの詳細の理解なしに利用できる、また、その内部構造に依存せずにオブジェクトを利用できる
- publicメソッドは、オブジェクトをコントロールする際のツール
- 例:
- 「車」クラスは、ドライバーのためにある
- しかし、ドライバーは車のエンジンの内部機構を知らなくても、アクセルを踏めば前進できる
カプセル化の利点(設定フィルタリング)
- オブジェクトを機能不全に陥いらせるようなプロパティーを設定する外部コードを予防する
- 例えば、Tamagotchiの名前がデータベースに収まらない時
- setName()メソッドで正しい範囲のプロパティーになるようフィルタリングできる
- 例:setName()でnameの長さをチェックする
public function setName (newName) {
if (newName.length > 20) {
trace("Warning: 設定された名前は長すぎます.");
newName = newName.substr(0, 20);
} else if (newName == "") {
trace("Warning: 設定された名前は短かすぎます.");
return;
}
name = newName;
}
カプセル化の利点(取得フィルタリング)
- カプセル化によって、値を返す前に正しいプロパティーの値に修正できる
- アクセサメソッドの返す値
- プロパティとは違った、もしくは、設定されていない場合
- デフォルトの値を返す
- 以下の例では、Tamagotchiの名前がまだ与えられていないときには、「名無しさん」という名前を返す
public function getName () {
if (name == null) {
return "名無しさん";
} else {
return name;
}
}
コンストラクタ関数
- Tamagotchiを新たに生成した際、同時に名前を付けておきたい
- 言い替えると、オブジェクトの生成時に、オブジェクトの状態を設定できるようにしたい
- オブジェクト生成時の初期化には、コンストラクタ関数を用いる
- コンストラクタ関数は、オブジェクトが生成された際に自動的に実行される
- コンストラクタ関数内のコードは、プロパティの設定、メソッドの実行などを通してオブジェクトを初期化することができる
Tamacotchiのコストラクタ
- コンストラクタ関数は、クラスの名前と同じでなくてはならない
- この例では、コンストラクタ関数はTamagotchiの名前を設定している
- クラスの中からでもsetName()メソッドを経由して設定している部分に注目
public function Tamagotchi (petName) {
setName(petName);
}
オブジェクトの初期化
- Tamagotchiの名前を生成時に設定するには、下記のようにする
- 文字列”山田くん”はコンストラクタ関数に渡される
- コンストラクタ関数はTamagotchiの名前の設定に文字列を使用する
var pet:Tamagotchi = new Tamagotchi("山田くん");
クラスプロパティシンタックス
- クラスプロパティは静的な属性として定義される
- 例えば、以下の例は、xという名前で数字を格納するクラスプロパティを定義する
- クラスプロパティを参照するとき、クラス名を含める
- 正:SomeClass?.someProp
- 誤:someProp
private static var x:Number;
マジックバリュー
- setName()とgetName()メソッドは具体的な値を扱っている
- setName()メソッドは20という数:
- getName()メソッドは"Unnamed Pet"という文字列:
- メソッド内で用いられる、説明されていない具体的な値を、マジックバリューという。
public function setName (newName) {
if (newName.length > 20) {
...
public function getName () {
if (name == null) {
return "Unnamed Pet";
...
マジックバリューは良くない
- マジックバリューは以下の理由から推奨されない:
- 目的が自明でない
- 分散化しているので、エラーになりがち
- 例えば、nameの最長値20を修正するには2つの場所で修正しなければならない → これは見落としがち
マジックバリューをクラスプロパティとして保存する
- クラスプロパティーは、マジックバリューの保存先として理想的
- 例えば、全てのTamaghochiオブジェクトが同じ長さの名前の最長値を持つとする
- 名前の最長値を保存するクラスプロパティを作成する
- そして、setName()メソッドをクラスプロパティを用いるように変更する
private static var MAX_NAME_LENGTH:Number = 20;
public function setName (newName) {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("Warning: specified name is too long.");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("Warning: specified name is too short.");
return;
}
name = newName;
}
初期値をクラスプロパティとして保存する
- 名前を付けていないTamagotchiは全て「名無しさん」と呼ばれる
- デフォルトの名前を格納するクラスプロパティを設ける
- そして、getName()を以下のように変更する
- 固定値のクラスプロパティーは大文字を用いる
- クラスプロパティはインスタンス化の前には変更しない
private static var DEFAULT_NAME:String = "名無しさん";
public function getName () {
if (name == null) {
return Tamagotchi.DEFAULT_NAME;
} else {
return name;
}
}
現在のTamagotchiクラスの状態
class Tamagotchi {
private var currentCalories:Number;
private var name:String;
private static var MAX_NAME_LENGTH:Number = 20;
private static var DEFAULT_NAME:String = "名無しさん";
public function Tamagotchi (petName) {
setName(petName);
}
private function digest () {
trace("たまごっちは消化中...");
}
public function feed (foodItem) {
trace("あなたはたまごっちに餌をあげました.");
}
public function setName (newName) {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("警告:名前が長すぎます");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("警告:名前が短すぎます.");
return;
}
name = newName;
}
public function getName () {
if (name == null) {
return Tamagotchi.DEFAULT_NAME;
} else {
return name;
}
}
}
Tamagotchiの生命
- Tamagotchiの生命をシミュレートする
- 必要なものは:
- Tamagotchiに与えられたカロリー総量
- 時間経過によるカロリー消費
最大カロリー
- 全てのTamagotchiオブジェクトは、同じ最大カロリーとする
- よって、クラスプロパティとして指定する
private static var MAX_CALORIES:Number = 2000;
カロリー消費レート
- 全てのTamagotchiは同じ割合でカロリーを消費する:
- 1秒に100カロリー
- カロリー消費レートをクラスプロパティーとして保持する
private static var CALORIES_PER_SECOND:Number = 100;
生か死か?
- Tamagotchiの生死をすぐに判断する手段が必要
- よって、生か死を表すブール値を生成する
private var isAlive:Boolean;
死ぬまで消化し続ける、死んだら消化が停止する
- 下記のコードは1000ミリ秒ごとにdigest()メソッドを呼びだしている
- setInterval()はあとでメソッドを停止する際に使用するID番号を返す
- digest()メソッドを呼びだしているインターバルIDをあらたなプロパティーに保存する
setInterval(this, "digest", 1000);
private var digestIntervalID:Number;
Tamagotchiの誕生
- 修正したコンストラクタ関数は以下のようになる
public function Tamagotchi (petName) {
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
}
Tamagotchi の死
- Tamagotchiが死んだ時die()メソッドが呼ばれる
- die()には3つの役割がある
- オブジェクトの状態を停止する
- クラスに使用されている全てのリソースを解放する
- たまごっちの死を伝える
clearInterval(digestIntervalID); isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
die()メソッド全体
private function die () {
clearInterval(digestIntervalID);
isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
}
カロリーの消費
- 毎秒digest()メソッドが呼び出されている
- digest()は現在のカロリーを減らす
- カロリーを使い尽くした時、死が待っている、つまりdie()
- そうでないかぎり、状態を表示する
private function digest () {
trace("The Tamagotchi is digesting...");
currentCalories -= Tamagotchi.CALORIES_PER_SECOND;
if (currentCalories <= 0) {
die();
} else {
displayHealthStatus();
}
}
現状のTamagotchiクラス
class Tamagotchi {
private var currentCalories:Number;
private var name:String;
private var isAlive:Boolean;
private var digestIntervalID:Number;
private static var MAX_NAME_LENGTH:Number= 20;
private static var DEFAULT_NAME:String= "Unnamed Pet";
private static var MAX_CALORIES:Number= 2000;
private static var CALORIES_PER_SECOND:Number = 100;
public function Tamagotchi (petName) {
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
}
private function digest () {
trace("The Tamagotchi is digesting...");
currentCalories -= Tamagotchi.CALORIES_PER_SECOND;
if (currentCalories <= 0) {
die();
} else {
displayHealthStatus();
}
}
public function feed (foodItem) {
trace("You fed the Tamagotchi.");
}
private function displayHealthStatus () {
var caloriePercentage:Number
= Math.floor((currentCalories/Tamagotchi.MAX_CALORIES)*100);
trace(getName() + " has " + currentCalories + " calories"
+ " (" + caloriePercentage + "% of its food) remaining.");
}
private function die () {
clearInterval(digestIntervalID);
isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
}
public function setName (newName) {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("Warning: specified name is too long.");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("Warning: specified name is too short.");
return;
}
name = newName;
}
public function getName () {
if (name == null) {
return Tamagotchi.DEFAULT_NAME;
} else {
return name;
}
}
}
Tamagotchiの食べ物:寿司とリンゴ
- Tamagotchiは2種類の食べ物を食べる:寿司とリンゴ
- それぞれが、SushiクラスとAppleクラスを代表している
類似点を分解する
- SushiとAppleクラスは似たような個性をもっている
- 両者ともに以下を管理している:
- 食べ物の名前
- 食べ物のカロリー数
- SushiとAppleは、両方とも食べ物(food)なので、似ている
継承
- OOP「モノの性質」を継承してモデル化する
- 継承は通常、階層的な関係性をもっている
- 継承では、あるクラス(subclass)が別のクラス(superclass)の性質を譲り受ける(相続)する
- ひとつのsuperclassから多数のサブクラスが継承される
- 例えば、
- 一般的なクラスFoodを定義する
- SushiとAppleはFoodの性質を継承する
Foodクラスのソース
class Food {
private var calories;
private var name;
public function Food (initialCalories:Number) {
setCalories(initialCalories);
}
public function getCalories () {
return calories;
}
public function setCalories (newCalories) {
calories = newCalories;
}
public function getName () {
return name;
}
public function setName (newName) {
name = newName;
}
}
Sushiクラス(subclass)
- extendsというキーワードをクラスの継承にもちいる
- これでSushiはFoodのメソッドを受け継いだことになる
- Sushiはまた自分自身のプロパティを追加できる
- 自分自身のカロリー数など
class Sushi extends Food {
}
class Sushi extends Food {
private static var DEFAULT_CALORIES = 500;
}
superclassのコンストラクタを呼び出す
- すべてのsubclassはsuperclassのコンストラクタ関数を呼びださなくてはならない
- サブクラスのコンストラクタ関数から、super()関数を使ってsuperclassのコンストラクタ関数を呼び出せる
- Shushiは特定のカロリ数(500カロリー)をFoodのコンストラクタに渡す
- Shushiコンストラクタはまた、食べ物の名前を設定する
class Sushi extends Food {
private static var DEFAULT_CALORIES = 500;
public function Sushi () {
super(Sushi.DEFAULT_CALORIES);
setName("Sushi");
}
}
Appleクラス
- AppleはSushiとほとんど一緒、ただし固有のカロリーと名前をもつ
- AppleとSushi双方はメソッドとプロパティをFoodから譲り受けている
- よって、Foodのコードを変更したら、AppleとSushiも自動的に変化する
class Apple extends Food {
private static var DEFAULT_CALORIES = 100;
public function Apple () {
super(Apple.DEFAULT_CALORIES);
setName("Apple");
}
}
メソッドは型を返す
- プロタパティと同様に、メソッドの戻り値にも型を宣言する
- たとえば、getName()メソッドは文字列型(Strings)を返すので、以下のように宣言される
- なんの値も返さないメソッドはVoid型を返すと宣言される(Vは大文字で!)
public function getName ():String {
// ...省略
}
private function displayHealthStatus ():Void {
// ...省略
}
なんでメソッドの宣言は型を返すの?
- メソッドが型を返す利点:
- コンパイラが警告を出すことができる。もどってきた型がちがうとき
- 設定された型がちがうとき
returns the wrong type of value
public function getName ():String {
//本来はNumber型でないといけない!
return calorieCount;
}
**Error** Line 89: The expression returned must match the function's return type.
var petCalories:Number = pet.getName();
**Error** Line 2: Type mismatch in assignment statement: found String where Number is required.
メソッドの引数の型
- メソッドの引数の宣言に関しても、型を指定する
- 引数に型を指定する利点:
- まちがった型の引数が渡されたときに、警告を発することができる
public function setName (newName:String):Void {
//…省略
}
// ERROR! setName() requires a String, not a Number
pet.setName(25);
// ERROR! setName() requires a String, not a Name object
pet.setName(new Name("yamada"));
Tamagotchiになにを食べさせているのか?
- ここまで定義したfeedメソッド
- これを改良して、Tamagotchiには食べ物(Food)しか与えられないように変更する
- TamagotchisはFoodかFoodのサブクラスしか食べられない
public function feed (foodItem) {
trace("たまごっちに餌をあげました");
}
public function feed (foodItem:Food) {
trace("たまごっちに餌をあげました");
}
ここまでのまとめ
- Tamagotchiクラス
class Tamagotchi {
private var currentCalories:Number;
private var name:String;
private var isAlive:Boolean;
private var digestIntervalID:Number;
private static var MAX_NAME_LENGTH:Number = 20;
private static var DEFAULT_NAME:String = "Unnamed Pet";
private static var MAX_CALORIES:Number = 2000;
private static var CALORIES_PER_SECOND:Number = 100;
public function Tamagotchi (petName:String) {
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
}
private function digest ():Void {
trace("The Tamagotchi is digesting...");
currentCalories -= Tamagotchi.CALORIES_PER_SECOND;
if (currentCalories <= 0) {
die();
} else {
displayHealthStatus();
}
}
public function feed (foodItem:Food):Void {
if (isAlive == false) {
trace(getName() + " is dead. You can't feed it.");
return;
}
trace(getName() + " ate the " + foodItem.getName()
+ " (" + foodItem.getCalories() + " calories).");
if (foodItem.getCalories() + currentCalories
> Tamagotchi.MAX_CALORIES) {
currentCalories = Tamagotchi.MAX_CALORIES;
} else {
currentCalories += foodItem.getCalories();
}
}
private function displayHealthStatus ():Void {
var caloriePercentage:Number
= Math.floor((currentCalories/Tamagotchi.MAX_CALORIES)*100);
trace(getName() + " has " + currentCalories + " calories"
+ " (" + caloriePercentage + "% of its food) remaining.");
}
private function die ():Void {
clearInterval(digestIntervalID);
isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
}
public function setName (newName:String):Void {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("Warning: specified name is too long.");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("Warning: specified name is too short.");
return;
}
name = newName;
}
public function getName ():String {
if (name == null) {
return Tamagotchi.DEFAULT_NAME;
} else {
return name;
}
}
}
- Foodクラス
class Food {
private var calories:Number;
private var name:String;
public function Food (initialCalories:Number) {
setCalories(initialCalories);
}
public function getCalories ():Number {
return calories;
}
public function setCalories (newCalories:Number):Void {
calories = newCalories;
}
public function getName ():String {
return name;
}
public function setName (newName:String):Void {
name = newName;
}
}
- Sushiクラス
class Sushi extends Food {
private static var DEFAULT_CALORIES:Number = 500;
public function Sushi () {
super(Sushi.DEFAULT_CALORIES);
setName("Sushi");
}
}
- Appleクラス
class Apple extends Food {
private static var DEFAULT_CALORIES:Number = 100;
public function Apple () {
super(Apple.DEFAULT_CALORIES);
setName("Apple");
}
}
ユーザからの入力を処理する
- Tamacotchiは作動してる、でもユーザからの入力を処理するコードが必要
- 単純化するため、Tamagotchiはキーボードでコントロールされるようにしてみる
- ’a’キーで、リンゴを食べさせる
- ‘s’キーで、寿司を食べさせる
keyboard入力に反応する
- onKeyDown()メソッドを新たに追加
- キーイベントを処理する
private function onKeyDown ():Void {
if (Key.getCode() == 65) {
feed(new Apple());
} else if (Key.getCode() == 83) {
feed(new Sushi());
}
}
キーボードのイベントを登録する
- コンストラクタ関数のなかで、キー入力を聞き取るようにTamagotchiに伝えたい
- "this"は自分自身のTamagotchiオブジェクトのこと
- ということでコンストラクタは以下のように変更される
public function Tamagotchi (petName:String) {
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
Key.addListener(this);
}
画像表示を追加する
- 現状のTamagotchiの仕組みは、あまりにも機械的。画像要素を追加したい。
- Tamagotchiに画像的な要素を加えるには:
- Tamagotchiの画像の入ったムービクリップを作成
- クラスにムービクリップに関係する2つのプロパティを追加する
- クラスのコンストラクタ関数、setName()、die()、 displayHealthStatus() それぞれのメソッドを更新
Tamagotchiシンボル
- Tamagotchiの画像を含んだムービクリップをTamagotchiシンボルと呼ぶ
- Tamagotchiシンボルを”TamagotchiSynbol?”としてエクスポートする。このことでTamagotchiクラスにから貼り付けることが可能となる(attachiMovie()を使う)
- TamagotchiSymbolはなんのコードも含んでいない! ただグラフィックを保持しているのみ。
TamagotchiSymbolの中身
- TamagotchiSymbolはTamacotchiの名前を表示するテキストフィールドを含む
- TamagotchiSymbolは「空腹アイコン」を入れ子状に配置されたクリップとして含む
- 異ったグラフィック表示をタイムライン上にラベル付けすることで提供する
- TamagotchiSymbolは「生」と「死」という2つの状態をもつ。
- 空腹アイコンは、「満腹」「空腹」「飢餓」という3つの状態を持つ
ムービクリップを扱うプロパティ
- Tamagotchiクラスにムービクリップを扱う新たなプロパティを2つ追加
- 2つの新たなプロパティはTamagotchiクラス内に入る
- TamagochiSymbolムービークリップのインスタンスを保持する”clip”
- TamagotchiSymbolをアイデンティファイする情報を保持する変数 ”SYMBOL_ID”
- SYMBOL_IDは全てのTamagotchiシンボルで同一である。よってクラスプロパティとして定義する
private var clip:MovieClip;
private static var SYMBOL_ID:String = "TamagotchiSymbol";
コンストラクタ関数の更新
- TamagotchiSymbolのインスタンスを作成し保存する
- コンストラクタのパラメータはそのインスタンスをFlashムービーのどこに貼り付けるのかを伝えている
public function Tamagotchi
(petName:String,target:MovieClip,depth:Number,x:Number,y:Number) {
clip = target.attachMovie(Tamagotchi.SYMBOL_ID, "tamagotchi" + depth, depth);
clip._x = x;
clip._y = y;
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
Key.addListener(this);
}
TamagotchiSymbolのインスタンスをつくる
- 新たにTamagotchiを生成する際に、Tamagotchiムービーの親、深度、場所を指定しなければいけないようになった
var pet:Tamagotchi = new Tamagotchi("Yamada", this, 0, 100, 100);
Tamagotchiの名前を表示する
- setName()メソッドで TamagotchiSymbolに名前を表示する
- アクセサメソッドの柔軟性に注目!
- 名前付けの操作の詳細は内部で行われ、外部のユーザからは隠されている
public function setName (newName:String):Void {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("Warning: specified name is too long.");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("Warning: specified name is too short.");
return;
}
name = newName;
clip.name_txt.text = name;
}
たまごっちの死を表示する
- die()メソッドを変更して、死を画像表示する
private function die ():Void {
clearInterval(digestIntervalID);
isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
clip.gotoAndStop("dead");
}
完成コード
class Tamagotchi {
private var currentCalories:Number;
private var name:String;
private var isAlive:Boolean;
private var digestIntervalID:Number;
private var clip:MovieClip;
private static var MAX_NAME_LENGTH:Number = 20;
private static var DEFAULT_NAME:String = "Unnamed Pet";
private static var MAX_CALORIES:Number = 2000;
private static var CALORIES_PER_SECOND:Number = 100;
private static var SYMBOL_ID:String = "TamagotchiSymbol";
public function Tamagotchi
(petName:String, target:MovieClip, depth:Number, x:Number, y:Number) {
clip = target.attachMovie(Tamagotchi.SYMBOL_ID, "tamagotchi" + depth, depth);
clip._x = x;
clip._y = y;
setName(petName);
currentCalories = Tamagotchi.MAX_CALORIES;
isAlive = true;
digestIntervalID = setInterval(this, "digest", 1000);
Key.addListener(this);
}
public function feed (foodItem:Food):Void {
if (isAlive == false) {
trace(getName() + " is dead. You can't feed it.");
return;
}
trace(getName() + " ate the " + foodItem.getName()
+ " (" + foodItem.getCalories() + " calories).");
if (foodItem.getCalories() + currentCalories > Tamagotchi.MAX_CALORIES) {
currentCalories = Tamagotchi.MAX_CALORIES;
} else {
currentCalories += foodItem.getCalories();
}
}
public function setName (newName:String):Void {
if (newName.length > Tamagotchi.MAX_NAME_LENGTH) {
trace("Warning: specified name is too long.");
newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH);
} else if (newName == "") {
trace("Warning: specified name is too short.");
return;
}
name = newName;
clip.name_txt.text = name;
}
public function getName ():String {
if (name == null) {
return Tamagotchi.DEFAULT_NAME;
} else {
return name;
}
}
private function die ():Void {
clearInterval(digestIntervalID);
isAlive = false;
Key.removeListener(this);
trace(getName() + " has died.");
clip.gotoAndStop("dead");
}
private function displayHealthStatus ():Void {
var caloriePercentage:Number
= Math.floor((currentCalories/Tamagotchi.MAX_CALORIES)*100);
trace(getName() + " has " + currentCalories + " calories"
+ " (" + caloriePercentage + "% of its food) remaining.");
if (caloriePercentage < 20) {
clip.hungerIcon.gotoAndStop("starving");
} else if (caloriePercentage < 50) {
clip.hungerIcon.gotoAndStop("hungry");
} else {
clip.hungerIcon.gotoAndStop("full");
}
}
private function digest ():Void {
trace("The Tamagotchi is digesting...");
currentCalories -= Tamagotchi.CALORIES_PER_SECOND;
if (currentCalories <= 0) {
die();
} else {
displayHealthStatus();
}
}
private function onKeyDown ():Void {
if (Key.getCode() == 65) {
feed(new Apple());
} else if (Key.getCode() == 83) {
feed(new Sushi());
}
}
}
<< Flash ActionScript2.0 実践講座2:ActionScript2.0導入 | top | FlashOOP詳細(4):アプリケーションフレームワーク、XMLデータの読み込み >>


Comments
コメントはありません
Add Comment
トップページに戻る