— boreal-kiss.com

Archive
Tag "ActionScript"

剣士装備・ガンナー装備

モンスターハンターの武器は大きくわけて二種類に分類できる。それは大剣やハンマーといった近接武器(剣士専用)と弓やボウガンなどの遠距離武器(ガンナー専用)である。面白いのは防具にも剣士専用のものとガンナー専用のものがあるところで、武器と防具の関係に以下のような制約が付けられているのが他のRPG(例えばドラクエ)と大きく異なる点であろう。

  • 剣士専用武器を装備している時は剣士専用防具しか装備できない。
  • ガンナー専用武器を装備している時はガンナー専用防具しか装備できない。

剣士・ガンナー武器の違いは予想できるとして、剣士・ガンナー防具の違いは何だろう。答えは防御力だ。ガンナー専用防具の防御力は剣士専用防具のそれに比べて著しく低い。これは妥当な設定だ。崖の上でスコープを構えて銃撃するハンターよりも、モンスターの足下すれすれで剣を振り回しているハンターの方がモンスターからダメージを受ける可能性が高いからであろう。ダメージを受ける頻度が異なる者同士をより平等に扱うためには防御力による差別化をはかったという風に考えれば、上記のような剣士・ガンナー防具の制約はとてもリーズナブルだ。

裸のハンター

さて剣士・ガンナー防具の制約、面白い仕組みなのだが、少しだけストレスになることがある。着替えをする時だ。モンハンをやったことがある人なら経験があると思うが、今例えば剣士専用装備(近接武器+近接武器用防具)をしているとしよう。この状態で武器をガンナー専用であるボウガンに替えたとするとどうなるか。答えは防具が全部外れて裸になる、である。なぜならボウガンを装備した時点でガンナー武器を装備していることになり、上記の剣士・ガンナー防具の制約から剣士防具が装備できなくなるからである。ボウガンだけを装備した裸のハンターはガンナー専用防具をイチから装備し直さないといけないのだ。そしてこの問題は剣士・ガンナー間の装備変更にはいつもつきまとうことになる。

便利な機能

そこでゲーム内に用意されているのが着替えを一括で行うマクロ機能だ。自分のお気に入りの武器と防具の組み合わせをあらかじめ登録しておくことで、次回からボタン一つで全身の装備コーディネートができてしまう。もちろん登録時の装備は自分が今現在装備しているものから選ばれているため、剣士・ガンナー防具の制約も必ず満たすことになる。登録スロットも20もあり(MHP2Gの場合)、好きなときに好きなスロットの装備にボタン一つで変身できるようになる。

本題

前置きが長くなったが、このモンハンの装備一括変更のマクロ機能、デザインパターンで言うところの何に相当するのか、ということで少し考えた。いくつか候補があったものの結局これに落ち着いた。Abstract Factoryパターンである。全く同じ操作でユーザーを煩わせることなく機能する、というところに重点を置いた結果である。Abstract Factoryパターンの解説は以下リンク先へどうぞ。簡単なActionScriptコード付き。

» Abstract Factory

Read More

Eric Gamma et al. – Desingn Patternsを全部Action Scriptで解説しようと思ったらなかなかハードで途中で力尽きてしまった。一応全体三部のうちのStructural Patterns(以下参照)については完成はしていたので公開することに。ただし注意。そもそもの動機が自分のための勉強だったため、単なる書物の和訳にならないように自分で本質だと感じた部分をメインに書いている。したがって内容に少しでも疑問を持った場合は原著をあたってください。

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy

» Design Patterns for ActionScript

Read More

コーディングの趣味の話なので必ずしも重要ではないが、個人的には大発見。

Problem

インスタンスプロパティ(Objective-Cではインスタンス変数と呼ぶ)の表示名とgetter/setter(Objective-Cではアクセサメソッドと呼ぶ)の表示名を異なるものにしたい。例えば、インスタンス変数をNSStringの”_name”と定義、そのアクセサメソッドは先頭のアンダースコアを除いた”name”で扱いたい。

Recipe

プロパティの定義は普通に”_name”とする。setter/getterを作成する際に以下のような細工をする。@propertyと@synthesizeの書き方が定義されたインスタンス変数と若干異なる(特に@synthesize)。

//TestClass.h
@interface TestClass : NSObject{
	NSString *_name;
}
@property (retain) NSString *name;
 
@end
 
//TestClass.m
@implementation TestClass
@synthesize name = _name;
 
@end

クラス内部でのインスタンス変数への直接アクセス(“_name”)とアクセサメソッド経由のアクセス(“self.name”)が明確に区別できるのが利点。Xcodeの適当な色づけに頼るよりアンダースコアの有無による絶対的な判断の方がしっくりくるという理由もある。

ActionScriptで言うと

上記内容は以下のgetter/setterと同等。ほんとに趣味の問題。

protected var _name:String;
 
public function get name():String{
	return _name;
}
 
public function set name(value:String):void{
	_name = value;
}

Reference

【コラム】ダイナミックObjective-C (102) プロパティ(2) – プロパティの宣言 | エンタープライズ | マイコミジャーナル

Read More

GeSHi – Generic Syntax Highlighterはプログラムソースを修飾語などにきれいに色付けした状態でhtmlソースとして出力してくれるPHPパッケージ。対応言語が豊富で、ActionScriptやObjective-Cも対応している。GeSHiを使ったWebサービスが見当たらないのはプロジェクトライセンスがGPLのためなのかな。

使い方

  1. sourceforge.netからパッケージをダウンロード。パッケージ全体をPHPが動作する環境に配置する。
  2. パッケージで定義されているGsShiクラスにhtml化したいソースファイルのストリング内容とパースしたい言語を引数に渡してパースさせる。
  3. パース内容を出力させたものがhtml化されたソースコードになる。

ActionScript 3.0の場合

例えばTest.asの中身を表示したい場合、PHPファイルに以下のように記述して実行。

<?php
 
include('geshi.php');
 
//Language
$lang = 'actionscript3';
 
//Raw source code
$path = 'Test.as';
 
$src = file_get_contents($path);
$geshi = new GeSHi($src, $lang);
$geshi->enable_keyword_links(false);
 
echo $geshi->parse_code();
?>

上記PHPファイル実行時の出力内容は以下の通り。Flex Builderと全く同じ見た目が出力される。インデントがちょっと深いぐらい?

package {
	import flash.display.Sprite;
 
	/**
	 * Test.as
	 */ 
	public class Test extends Sprite{
		/**
		 * Constructor
		 */ 
		public function Test(){
			this.init();
		}
 
		protected function init():void{
			trace("Hello");
		}
	}
}
 

Objective-Cの場合

例えばTestAppDelegate.mの中身を表示したい場合、PHPファイルに以下のように記述して実行。

<?php
 
include('geshi.php');
 
//Language
$lang = 'objc';
 
//Raw source code
$path = 'TestAppDelegate.m';
 
$src = file_get_contents($path);
$geshi = new GeSHi($src, $lang);
$geshi->enable_keyword_links(false);
 
echo $geshi->parse_code();
?>

上記PHPファイル実行時の出力は以下の通り。まあアリ?

//
//  TestAppDelegate.m
//  Test
//
//  Created by boreal-kiss.com on 09/02/08.
//  Copyright boreal-kiss.com 2009. All rights reserved.
//
 
#import "TestAppDelegate.h"
#import "TestViewController.h"
 
@implementation TestAppDelegate
 
@synthesize window;
@synthesize viewController;
 
 
- (void)applicationDidFinishLaunching:(UIApplication *)application {    
 
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}
 
 
- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}
 
 
@end
 

その他

パッケージデフォルトだとSpriteやintなんかに変なURLのリンクがくっつくので、以下の設定でキーワードへのURLリンクを無効にしておくとよい。

$geshi->enable_keyword_links(false);

出力された内容をそのままブログなんかにコピー&ペーストしたいのであれば、以下のように出力内容を設定してやるとよい。出力内容がhtmlタグの形で表示される。

//echo $geshi->parse_code();
 
echo '<pre>';
echo htmlspecialchars($geshi->parse_code());
echo '</pre>';
Read More

WonderFlでコンパイルできるのは1ファイルのみ(importできるのは指定されているライブラリのみ)。別ファイル化されているクラスを使いたくても使えないので、1ファイルに無理矢理押し込めてやる必要がある。» Fork me!!

一般仕様

//main.as
package {
    import flash.display.Sprite;
    import com.borealkiss.Model;

    public class main extends Sprite{
        public function main(){
        }
    }
}
//Model.as
package com.borealkiss{
    public class Model{
        public function Main(){
        }
    }
}

WonderFl仕様

//All in one file
package {
    import flash.display.Sprite;

    public class main extends Sprite{
        public function main(){
        }
    }
}

//No access modifier!!(i.e., public)
class Model{
    public function Model(){
    }
}

追記 2008/12/22

Flexアプリケーションが動いてる!!

色確認用 | Wonderfl Build Flash Online

Read More

注意:IEventDispatcherなどすでにインターフェイスが用意されているものについてはもっとスマートな実装方法がある(最下部の追記参照)

***************

インターフェイスはクラスではないのでクラスは継承できない。インターフェイスに他クラスの機能を扱えるようにして、あたかもクラスを継承しているようにする方法。

イントロダクション

親をインターフェイスで定義しておいて、その子クラスを使う場合、プログラム上では直接子クラスへの参照を作るのではなく親インターフェイスへの参照で扱う方が「後々」都合がよい場合が多い。例えばIDoSomethingインターフェイスを継承したDoSomethingクラスを作る場合は以下のように記述してやる。

package {
    import flash.display.Sprite;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            //Creates a DoSomething class.
            _doSomething = new DoSomething();
            _doSomething.do();
        }
    }
}

package{
    public interface IDoSomething{
        function do():void;
    }
}

package{
    public class DoSomething implements IDoSomething{  
        public function do():void{
            //Something is done.
        }
    }
}

さてDoSomethingクラスは”クラス”なので、その他のクラスを継承することができる。例としてEventDispatcherクラスを継承させる。

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{  
        public function do():void{
            //Do something
        }
    }
}

本題

これでDoSomethingクラスはイベントの送受信ができるようになった。ここでようやく本題(の問題)に突入。先ほどmainクラスではDoSomethingクラスを使う際、DoSomethingクラスそのものではなく親インターフェイス(IDoSomething)への参照を用いて使うことにしていた。IDoSomethingへの参照変数に参照されたDoSomethingクラスがイベントを送信したらどうなるか。IDoSomething.dispatchEvent()メソッドはインターフェイスには定義されていないのでコンパイルエラーになる。

package {
    import flash.display.Sprite;
    import flash.events.Event;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            _doSomething = new DoSomething();
            
            //Compile error as no such method is defined in the Interface.
            _doSomething.dispatchEvent(new Event("doSomething"));
        }
    }
}

package{
    public interface IDoSomething{
        function do():void;
    }
}

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{  
        public function do():void{
            //Do something
        }
    }
}

解決策

DoSomethingクラスではたしかに実装されているメソッドなのに、IDoSomethingインターフェイスでは定義されていない。それにもかかわらずmainクラスではIDoSomethingインターフェイスとして扱いたい場合どうするか。「実装したいメソッドを扱えるクラスを戻り値とする」メソッドを新たにインターフェイスに定義してやればよい。上記のEventDispathcerクラスのメソッドを扱いたい場合は、インターフェイスを以下のように再定義してやる。

package{
    import flash.events.EventDispatcher;
    
    public interface IDoSomething{
        function do():void;
        
        //New!!
        function get eventDispatcher():EventDispatcher;
    }
}

そして、実際のメソッド内容を子クラスに追加、戻り値を子クラス自身とする。子クラスはEventDispatherクラスを継承しているところがポイント。

package{
    import flash.events.EventDispatcher;
    
    public class DoSomething extends EventDispatcher implements IDoSomething{
        
        //New!!
        public function get eventDispatcher():EventDispatcher{
            return this;
        }
        
        public function do():void{
            //Do something
        }
    }
}

mainクラスは以下の通り。IDoSomething.eventDispatcher()メソッド経由でEventDispathcer.dispatchEvent()メソッドにアクセス。無事コンパイルが通る。

package {
    import flash.display.Sprite;
    import flash.events.Event;
    
    //main
    public class main extends Sprite{
        //Defined as Interface
        private var _doSomething:IDoSomething;
        
        public function main(){
            _doSomething = new DoSomething();
            
            //Event successfully dispatched!!
            _doSomething.eventDispacther.dispatchEvent(new Event("do"));
        }
    }
}

応用

同じようにして、例えば子クラスにDisplayObjectContainerクラスを継承させて、DisplayObjectContainerクラスを戻り値とするメソッドを定義してやれば、インターフェイスからDisplayObjectContainer.addChild()などを使うことができる。

package {
    import flash.display.Sprite;
    
    //main
    public class main extends Sprite{
        public function main(){
            var example:IExample = new Example();
            var testSprite:Sprite = new Sprite();
            
            //The interface acts as Sprite!!
            example.displayObjectContainer.addChild(testSprite);
        }
    }
}

package{
    import flash.display.DisplayObjectContainer;
    
    public interface IExample{
        function doExample():void;
        function get displayObjectContainer():DisplayObjectContainer;
    }
}

package{
    import flash.display.Sprite;
    import flash.display.DisplayObjectContainer;
    
    public class Example extends Sprite implements IExample{
        public function doExample():void{
        }
        
        public function get displayObjectContainer():DisplayObjectContainer{
            return this;
        }
    }
}

***********************

追記 2008/12/14

やべえ。インターフェイスにはIEventDispatcherインターフェイスを継承させて、子クラスにはEventDispatcherクラスを継承させるだけで無茶苦茶スマートな構造になった。mutaさん thanks!!

package {
    import flash.display.Sprite;
    import flash.events.Event;

    public class main extends Sprite{  
        public function main(){         
            var doSomething:IDoSomething = new DoSomething();
            doSomething.addEventListener(Event.COMPLETE,trace);
            doSomething.dispatchEvent(new Event(Event.COMPLETE));
        }
    }
}

package{
    import flash.events.IEventDispatcher;

    public interface IDoSomething extends IEventDispatcher{
    }
}

package{
    import flash.events.EventDispatcher;

    public class DoSomething extends EventDispatcher implements IDoSomething{
    }
}
Read More

ActionScript 25行以内で何ができるかに焦点を絞ったコンテストの最終選考が一般投票形式でスタート。優勝賞品がCS4一式というのが関係あるのかしらんけど作品がどれも熱い!

The 25-Line ActionScript Contest » November/December Finalists

自分は最終選考には残らんかった。というか最終選考に残ってるやつどれも25行以内のActionScriptで動いてるとは到底思えんので自分的には納得してる。パッと見た限りでは木がランダムに生えるやつがキレイでツボ。メタボールのやつがソースコードがスッカスカでびびる。アスキーアートのゲームもネタ的に面白い。いやーしかしこれは良い刺激になったなあ。次回も是非参加したい。

ちなみに僕が応募したやつは[Flash] Unknown Pleasures – boreal-kiss.comを改良したやつ。ファイナリストに日本人見かけなかったけど他に日本人で応募した人とかいたのかしら。なんとなくドイツ人が多かった感じがした。

Read More

» Demo (Spotlightのとこ。要Flash Player 10)

Spark project – /as3/Astro/SpotlightFilterに一式をアップしてあるのでご利用ください(MITライセンス)。spotlight.pbkファイル作成者のRyan Phelanに確認したところ、pbkファイルを改変する場合はその内容もアップデートに含めたいので是非教えてくれとのこと。SpotlightFilter.as作成で参考にしたのはSpark project – /as3/Astro/RadialBlurmutaさんのソースは勉強になるのでちょくちょく参考にさせてもらっている。とりあえずSpark project参加当初の目標(コミットする)は達成できた。

Read More

プログラミングテクニックのまとめ – プログラミング日記の「ifをポリモーフィズムによりなくせ」に相当するものをActionScriptで実践した。以下の例では分岐が高々2箇所なのでif文をなくす労力の方が無駄に思えるが、if文やswitch文の分岐箇所が増えるような場合ほど威力を発揮するはず(拡張が容易・メンテナンスが楽などの理由)。

a. if文が必要な場合

例えば線を描画したいとき、描画始点はGraphics.moveTo()、その他はGraphics.lineTo()を使う。そのため以下の例のようにforループでArrayデータの内容を読み込んで折れ線グラフを描画させるルーチン内ではif文を使う必要がある。

package {
    import flash.display.Sprite;
    import flash.display.Shape;

    public class Main extends Sprite{
        private var _drawer:Shape;
        private var _data:Array = [0,10,0,10,0,10,0,10,0,10];//Test data
        
        public function Main(){
            _drawer = new Shape();
            addChild(_drawer);
            draw();
        }
        
        private function draw():void{
            _drawer.graphics.lineStyle(1,0x0);
            for (var i:int=0; i<_data.length; i++){
                var x:Number = 20*i;
                var y:Number = 20*_data[i];
                
                if (i==0){
                    _drawer.graphics.moveTo(x,y);
                }
                else {
                    _drawer.graphics.lineTo(x,y);
                }
            }
        }
    }
}

この分岐処理はGraphics.moveTo()とGraphics.lineTo()という全く別のメソッドを「直接」使うから生じている。つまり、これらメソッドをコントロールする管理クラスを新たに作って、管理クラス経由で「間接」的に扱えば(delegateする、と言う)回避することが可能になるわけだ。具体的にはforループ内ではその管理クラスの単一メソッドのみを呼び出すようにする。

b. if文が不要な場合

以下の例では管理クラスLineDrawer.asを導入して描画させている。線の始点であろうがなかろうが常にLineDrawer.draw()というメソッドを使うので結果if文がなくなっている。

package {
    import flash.display.Sprite;
    import com.borealkiss.controls.LineDrawer;

    public class Main extends Sprite{
        private var _drawer:LineDrawer;
        private var _data:Array = [0,10,0,10,0,10,0,10,0,10];//Test data
        
        public function Main(){
            _drawer = new LineDrawer();
            addChild(_drawer);
            draw();
        }
        
        private function draw():void{
            _drawer.graphics.lineStyle(1,0x0);
            for (var i:int=0; i<_data.length; i++){
                var x:Number = 20*i;
                var y:Number = 20*_data[i];
                _drawer.draw(x,y);
            }
        }
    }
}

以下if文がなくなったカラクリ部分について。

b-1. LineDrawer.as (管理クラス)

Main.asから呼ばれるのはLineDrawer.draw()メソッドのみ。ポイントはLineDrawer.draw()内で実際に働く中身の入れ替えを行っているところ。最初にLineDrawer.draw()が呼ばれたときに実際に働くのがプロパティ”_moveToState”にセットされたクラス(MoveToState.as)、それ以降はプロパティ”_lineToState”にセットされた「別」クラス(LineToState.as)で、それぞれdraw()というメソッドを持っているがメソッドの内容は全く別物になっている(b-3, b-4参照)。そしてこの二つのクラスの受け皿に担当するプロパティ”_currentState”はこの両クラスを参照できるようにそれぞれの同一継承元であるクラス型(IState.as)で定義されている。これがポリモーフィズム(クラス型の多様性)。イメージとしてはクラス型を日本人型に設定しておき、東京人型クラスも大阪人型クラスも参照できるような状態。

package com.borealkiss.controls{
    import flash.display.Shape;
    
    public class LineDrawer extends Shape{
        private var _moveToState:IState;
        private var _lineToState:IState;
        private var _currentState:IState;
        
        public function LineDrawer(){
            _moveToState = new MoveToState(this);
            _lineToState = new LineToState(this);
            init();
        }

        public function draw(x:Number,y:Number):void{
            _currentState.draw(x,y);
            _currentState = _lineToState;
        }
        
        public function init():void{
            _currentState = _moveToState;
        }

    }
}

b-2. IState.as (インターフェイス)

LineDrawer.as(管理クラス)内で呼び出されて働かされるクラスMoveToState.asとLineToState.asが継承している親玉クラス。これがあるからポリモーフィズムが可能になる。重要なのはMoveToStateクラスとLineToStateクラスが共通して継承しているクラスが存在していることなので、インターフェイスを使わなくてもアブストラクト的なクラスを作成して代用してもよい(MoveToState extends XXX, LineToState extends XXX, のXXXに相当するクラス)。

package com.borealkiss.controls{
    public interface IState{
        function draw(x:Number,y:Number):void;
    }
}

b-3. MoveToState.as

折れ線グラフの始点描画時点でのみ呼ばれるクラス。MoveToState.draw()はGraphics.moveTo()を実際に行う。

package com.borealkiss.controls{
    public class MoveToState implements IState{
        protected var _drawer:LineDrawer;
        
        public function MoveToState(drawer:LineDrawer){
            _drawer = drawer;
        }

        public function draw(x:Number,y:Number):void{
            _drawer.graphics.moveTo(x,y);
        }
    }
}

b-4. LineToState.as

折れ線グラフの始点以降で呼ばれるクラス。LineToState.draw()はGraphics.lineTo()を実際に行う。MoveToStateクラスを継承しているので必然的にIStateインターフェースも継承している。

package com.borealkiss.controls{
    public class LineToState extends MoveToState{

        public function LineToState(drawer:LineDrawer){
            super(drawer);
        }
        
        override public function draw(x:Number,y:Number):void{
            _drawer.graphics.lineTo(x,y); 
        }
    }
}

追記 2008/12/17

以下の記事で引用いただいたのですが、プログラミングの思想という点で大変参考になります。extendsとかimplementsが好きな方にオススメ。

あるStateパターンの実装について

Read More

publicとinternalはクラスに修飾できるので、それ以外のprivate(自分のみアクセス可能なコンストラクタ)とprotected(継承したクラスのみアクセス可能なコンストラクタ)を作ってみる。注意する点があって、ここで言うprotectは継承クラスのアクセスは可能で自分のアクセスは不可。というのも親玉クラスをAbstractクラスとして扱いたいという理由があったからです(サブクラスのみnew可能にしたかった)。

privateクラス

privateにしたいクラス(TestPrivate)ファイル内のパッケージ外部にダミークラスを作成し、TestPrivateクラスのコンストラクタの引数にする。DummyクラスはTestPrivateクラスファイル内ではアクセス可能だが、その他からはアクセス不可能でnewしようとするとコンパイルエラーになる。実際にインスタンスを使用する場合はTestPrivateクラス内のメソッド経由(ここではgetInstance)で作成するしかない。少し改良を加えるとsingletonインスタンスを作るときに使える。

//TestPrivate.as
package com.borealkiss{
    public class TestPrivate {
        
        private _instance:TestPrivate;
        
        //Constructor
        public function TestPrivate(dummy:PrivateClass){
        }
        
        public static function getInstance():TestPrivate{
            _instance = new TestPrivate(new Dummy());
            return _instance;
        }
    }
}

//Dummy class outside the package but still in the same file.
class Dummy{
    //Constructor
    public function Dummy(){    
    }
}

protectedクラス

protectedにしたいクラス内にそのクラス名を記述したコンスタントを作成しておく。コンストラクタ内でnewされようとしているクラス名と比較し、合致したらエラーを吐かせる。TestProtectedクラスをnewするとエラーが表示されるが、TestProtectedクラスのサブクラスの場合、getQualifiedClassname(this)の内容が変化するのでnew可能。Abstractクラス(サブクラスのみnew可能)として利用できる。

//TestProtected.as
package com.borealkiss{
    import flash.errors.IllegalOperationError;
    import flash.utils.getQualifiedClassName;
    
    public class TestProtected {
        private static const THIS_CLASS_NAME:String 
                                            = "com.borealkiss:TestProtected";
        //Constructor
        public function TestProtected(){
            if (getQualifiedClassName(this) == THIS_CLASS_NAME){
                throw new IllegalOperationError("Must be subclassed to use.");
            }
        }
    }
}
Read More