In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Stage1.きらきら星

「打鍵練習 × ドットキャラ」
 ・メロディーの押すべきキーとタイミングを、ドットキャラで可視化してみた

 操作方法
 ・キャラの居る位置のキーを押すだけ
Get Adobe Flash player
by o_healer 14 Oct 2010
/**
 * Copyright o_healer ( http://wonderfl.net/user/o_healer )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/w6VQ
 */

/*
 「打鍵練習 × ドットキャラ」
 ・メロディーの押すべきキーとタイミングを、ドットキャラで可視化してみた

 操作方法
 ・キャラの居る位置のキーを押すだけ

 問題
 ・やはり「キー入力→音の再生」のラグがあるのはつらい
  ・しかし現状のFlashではこれが限界っぽい
 ・PCのキーボードをピアノの鍵盤に見立てるのも限界がある
  ・横の長さが足りないし、同時押しがサポートされているかも不確実

 やりたかったことなど
 ・複数のキャラを出すことで、和音や複数のパートを再現
 ・スラーはジャンプでなくローリングで再現
 ・背景はもうちょっとちゃんとピアノっぽく

 その他
 ・一応、MUSICの中をいじることで楽譜は変えられる
*/


package {
    import flash.display.*;
    import flash.events.*;
    import flash.filters.*;
    import flash.geom.*;
    import flash.net.*;
    import flash.system.*;
    import flash.text.*;
    import flash.ui.*;

    //SiON (SE)
    import org.si.sion.*;
    import org.si.sion.utils.SiONPresetVoice;
    import org.si.sion.sequencer.SiMMLTrack;
 
    [SWF(width="465", height="465", frameRate="60", backgroundColor="0xFFFFFF")]
    public class GameMain extends Sprite {

        //==Const==

        //画像URL
//*
        static public const URL_BLOCK:String = "http://assets.wonderfl.net/images/related_images/e/ef/ef08/ef08de22b6acdcba7aa0aadd10c9787736441dea";
        static public const URL_ONPU:String = "http://assets.wonderfl.net/images/related_images/d/d0/d027/d027688e8a475887996757d4ad0db28c0bcd54ed";
        static public const URL_PLAYER:String = "http://assets.wonderfl.net/images/related_images/5/53/531b/531b465571ac568a02661d045842c74076ae9774";
/*/
        static public const URL_BLOCK:String = "Block.png";
        static public const URL_ONPU:String = "OnpuBlock.png";
        static public const URL_PLAYER:String = "Player.png";
//*/
        //画面サイズ
        static public const STAGE_W:int = 465;
        static public const STAGE_H:int = 465;

        //パネルサイズ
        static public const PANEL_LEN:int = 32;

        //音階
        static public const C:int = 60;//ド
        static public const D:int = 62;//レ
        static public const E:int = 64;//ミ
        static public const F:int = 65;//ファ
        static public const G:int = 67;//ソ
        static public const A:int = 69;//ラ
        static public const B:int = 71;//シ
        static public const o:int = 0;//
        static public const NOTE_LIST:Array = [C, D, E, F, G, A, B];
        static public const NOTE_NUM:int = NOTE_LIST.length;

        //キー入力
        static public const KEY_A:int = 65;
        static public const KEY_S:int = 83;
        static public const KEY_D:int = 68;
        static public const KEY_F:int = 70;
        static public const KEY_G:int = 71;
        static public const KEY_H:int = 72;
        static public const KEY_J:int = 74;

        //SiON
        static public const PRESET_VOICE:SiONPresetVoice = new SiONPresetVoice();

        //キー入力 => 音階 のマッピング
        static public const KEY_to_NOTE:Object = {
            //Keycode:Note
            65 : C,//KEY_A : C,//A => ド
            83 : D,//KEY_S : D,//S => レ
            68 : E,//KEY_D : E,//D => ミ
            70 : F,//KEY_F : F,//F => ファ
            71 : G,//KEY_G : G,//G => ソ
            72 : A,//KEY_H : A,//H => ラ
            74 : B,//KEY_J : B,//J => シ

            hoge:0//dummy
        };

        //楽譜
        static public const MUSIC:Array = [
            //Stage1. きらきら星
            [C,C,G,G,A,A,G,o,F,F,E,E,D,D,C,o,G,G,F,F,E,E,D,o,G,G,F,F,E,E,D,o,C,C,G,G,A,A,G,o,F,F,E,E,D,D,C,o],
        ];

        //BPM
        static public const BPM:int = 120;//楽譜と合わせた方が良いか

        //先行入力の受付時間
        static public const ACCEPT_TIME:Number = 0.1;

        //音階→位置のマッピング
        static public const NOTE_to_POS:Object = {
            60 : STAGE_W/2 - PANEL_LEN*3,//C : ~,
            62 : STAGE_W/2 - PANEL_LEN*2,//D : ~,
            64 : STAGE_W/2 - PANEL_LEN*1,//E : ~,
            65 : STAGE_W/2 - PANEL_LEN*0,//F : ~,
            67 : STAGE_W/2 + PANEL_LEN*1,//G : ~,
            69 : STAGE_W/2 + PANEL_LEN*2,//A : ~,
            71 : STAGE_W/2 + PANEL_LEN*3,//B : ~,

            hoge:0//dummy
        };

        //地面の高さ
        static public const GROUND_Y:int = STAGE_H * 0.6;
        //重力
        static public const GRAVITY:Number = 1000.0;


        //==Var==

        //背景
        public var m_BG:BitmapData = new BitmapData(STAGE_W, STAGE_H, false, 0x000000);

        //音符ブロック
        public var m_OnpuBlocks:Array = new Array(NOTE_NUM);
        public var m_OnpuBitmapData:BitmapData = new BitmapData(PANEL_LEN, PANEL_LEN, true, 0x00000000);

        //プレイヤー
        public var m_Player:Sprite = new Sprite();
        public var m_PlayerBitmapData:BitmapData = new BitmapData(PANEL_LEN, PANEL_LEN, true, 0x00000000);

        //プレイヤーの移動管理
        public var m_SrcNote:int = MUSIC[0][0];
        public var m_DstNote:int = MUSIC[0][0];
        public var m_MoveTime:Number = 0.0;//移動にかかる時間
        public var m_Timer:Number = 0.0;//移動開始からの時間
        public var m_VY:Number = 0.0;
        public var m_DstRot:Number = 0.0;

        //楽譜管理
        public var m_MusicIndex:int = 0;//選ばれた楽譜の番号
        public var m_NoteIter:int = 0;//楽譜用イテレータ

        //SiON
        public var m_Driver:SiONDriver = new SiONDriver();
        public var m_Voice:SiONVoice = PRESET_VOICE["valsound.piano8"];//default.sin

        //Input
        public var m_InputFlags:Object = {};

        //ロード管理カウンタ
        public var m_RestUnloadNum:int = 0;


        //==Function==

        //Init
        public function GameMain():void{
            var i:int;
            var bmp:Bitmap;

            //BG
            {
                addChild(new Bitmap(m_BG));
            }

            //Onpu Block
            {
                for(i = 0; i < NOTE_NUM; i++){
                    var note:int = NOTE_LIST[i];
                    m_OnpuBlocks[i] = new Bitmap(new BitmapData(PANEL_LEN, PANEL_LEN, false, 0x000000));
                    m_OnpuBlocks[i].x = NOTE_to_POS[note] - PANEL_LEN/2;
                    m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2;

                    addChild(m_OnpuBlocks[i]);
                }
            }

            //Other OnpuBlocks
            {
                //だいぶ雑だが、他のところも音符で埋めてみる
                var x:int;
                var y:int;

                //左
                for(x = m_OnpuBlocks[0].x - PANEL_LEN; x > -PANEL_LEN; x -= PANEL_LEN){
                    bmp = new Bitmap(m_OnpuBitmapData);
                    bmp.x = x;
                    bmp.y = GROUND_Y + PANEL_LEN/2;
                    addChild(bmp);
                }

                //右
                for(x = m_OnpuBlocks[NOTE_NUM-1].x + PANEL_LEN; x < STAGE_W+PANEL_LEN; x += PANEL_LEN){
                    bmp = new Bitmap(m_OnpuBitmapData);
                    bmp.x = x;
                    bmp.y = GROUND_Y + PANEL_LEN/2;
                    addChild(bmp);
                }
            }

            //Player
            {
                //Graphic
                {
                    bmp = new Bitmap(m_PlayerBitmapData);
                    bmp.x = -PANEL_LEN/2;
                    bmp.y = -PANEL_LEN/2;
                    m_Player.addChild(bmp);
                }

                //Regist
                {
                    addChild(m_Player);
                }
            }

            //SiON
            {
                //m_Driver
                {
                    //ボリューム設定
                    m_Driver.volume = 0.3;

                    //ストリーミング開始
                    m_Driver.play();
                }
            }

            //Input
            {
                addEventListener(
                    Event.ADDED_TO_STAGE,//ステージに追加されたら
                    function registInput(e:Event):void{
                        //キー入力を見る
                        stage.addEventListener(KeyboardEvent.KEY_DOWN, OnKeyDown);
                        stage.addEventListener(KeyboardEvent.KEY_UP, OnKeyUp);

                        //Once
                        removeEventListener(Event.ADDED_TO_STAGE, registInput);
                    }
                );
            }

            //Update
            {
                addEventListener(Event.ENTER_FRAME, Update);

                //位置設定のため一回更新
                Update();
            }

            //ロード開始
            {
                const LoadFunc:Function = function(in_URL:String, in_OnLoad:Function):void{
                    m_RestUnloadNum++;

                    var loader:Loader = new Loader();
                    loader.load(new URLRequest(in_URL), new LoaderContext(true));//画像のロードを開始して
                    loader.contentLoaderInfo.addEventListener(
                        Event.COMPLETE,//ロードが完了したら
                        function(e:Event):void{
                            m_RestUnloadNum--;
                            in_OnLoad(loader.content);//初期化に入る
                        }
                    );
                }

                LoadFunc(URL_BLOCK, OnLoadEnd_Block);
                LoadFunc(URL_ONPU, OnLoadEnd_Onpu);
                LoadFunc(URL_PLAYER, OnLoadEnd_Player);
            }
        }

        //Load
        public function OnLoadEnd_Block(in_Graphic:DisplayObject):void{
            //できればもうちょっとちゃんとした背景にしたいところだが

            var bd_bg:BitmapData;
            {
                bd_bg = new BitmapData(PANEL_LEN, PANEL_LEN, false, 0x000000);

                bd_bg.draw(in_Graphic);
            }

            var pos:Point = new Point(0,0);
            for(var y:int = 0; y < STAGE_H; y += PANEL_LEN){
                pos.y = y;
                for(var x:int = 0; x < STAGE_W; x += PANEL_LEN){
                    pos.x = x;
                    m_BG.copyPixels(bd_bg, bd_bg.rect, pos);
                }
            }

            //下の方は暗くしてみる
            var r:Number = 0.5;
            m_BG.colorTransform(new Rectangle(0,GROUND_Y + PANEL_LEN/2, STAGE_W,STAGE_H), new ColorTransform(r,r,r));

            //黒鍵っぽいのも描いてみる
            {
                var w:int = PANEL_LEN*0.9;
                var rect:Rectangle = new Rectangle(0,GROUND_Y, w,PANEL_LEN);

                for(var i:int = 0; i < NOTE_NUM-1; i++){
                    var SrcNote:int = NOTE_LIST[i];
                    var DstNote:int = NOTE_LIST[i+1];

                    if(SrcNote+1 < DstNote){//1以上離れているところに黒鍵を描く
                        rect.x = NOTE_to_POS[SrcNote] + PANEL_LEN/2 - w/2;

                        m_BG.fillRect(rect, 0xFF111111);
                    }
                }
            }
        }
        public function OnLoadEnd_Onpu(in_Graphic:DisplayObject):void{
            const TEXT_LIST:Array = [
                "A",
                "S",
                "D",
                "F",
                "G",
                "H",
                "J",
            ];

            var mtx:Matrix = new Matrix();

            for(var i:int = 0; i < NOTE_NUM; i++){
                var bmd:BitmapData = m_OnpuBlocks[i].bitmapData;

                //基本ブロック描画
                {
                    bmd.draw(in_Graphic);
                }

                //キーボードの対応を明示する
                {
                    var text:TextField = new TextField();
                    {
                        text.selectable = false;
                        text.autoSize = TextFieldAutoSize.LEFT;
                        text.defaultTextFormat = new TextFormat('Verdana', PANEL_LEN*3/4, 0xFFFF88, true);
                        text.text = TEXT_LIST[i];
                        text.filters = [new GlowFilter(0x440000)];
                    }

                    //mtx
                    {
                        mtx.tx = PANEL_LEN/2 - text.textWidth/2  -3;
                        mtx.ty = PANEL_LEN/2 - text.textHeight/2 -2;
                    }

                    bmd.draw(text, mtx);
                }
            }

            {
                m_OnpuBitmapData.draw(in_Graphic);
            }
        }
        public function OnLoadEnd_Player(in_Graphic:DisplayObject):void{
            m_PlayerBitmapData.draw(in_Graphic);
        }

        //Update
        public function Update(e:Event=null):void{
            //var DeltaTime:Number = 1.0 / stage.frameRate;
            var DeltaTime:Number = 1.0 / 60.0;

            //Move
            {
                var Ratio:Number;
                {
                    m_Timer += DeltaTime;

                    if(m_Timer < m_MoveTime){
                        Ratio = m_Timer / m_MoveTime;
                    }else{
                        m_Timer = m_MoveTime;
                        Ratio = 1.0;
                    }
                }

                //X
                {
                    var SrcX:int = NOTE_to_POS[m_SrcNote];
                    var DstX:int = NOTE_to_POS[m_DstNote];

                    m_Player.x = Lerp(SrcX, DstX, Ratio);
                }

                //Y
                {
                    m_Player.y = (GROUND_Y) + (m_VY * m_Timer) + (0.5 * GRAVITY * m_Timer * m_Timer);
                }

                //Rot
                {
                    m_Player.rotation = m_DstRot * Ratio;
                }
            }
        }

        //Input
        public const len:int = 4;//再生する長さ
        public const delay:int = 0;//遅延
        public const quant:int = 0;//同期
        private function OnKeyDown(event:KeyboardEvent):void{
            PlaySE(event.keyCode, true);
        }
        private function OnKeyUp(event:KeyboardEvent):void{
            PlaySE(event.keyCode, false);
        }
        private function PlaySE(in_KeyCode:int, in_Flag:Boolean):void{
            //ロードが完了していなければ何もしない
            {
                if(m_RestUnloadNum > 0){
                    return;
                }
            }

            var Flag_Old:* = m_InputFlags[in_KeyCode];
            if(Flag_Old == null){
                Flag_Old = m_InputFlags[in_KeyCode] = false;
            }

            //変更がないなら何もしない
            if(Flag_Old == in_Flag){
                return;
            }

            m_InputFlags[in_KeyCode] = in_Flag;

            if(in_Flag){
                var note:* = KEY_to_NOTE[in_KeyCode];
                if(note != null){
                    //音を鳴らす
                    {
                        m_Driver.noteOn(note as int, m_Voice, len, delay, quant);
                    }
//*
                    //プレイヤーが居れば打ち上げる
                    {
                        var Music:Array = MUSIC[m_MusicIndex];

                        if(m_NoteIter < Music.length){//まだ次があって
                            if(Music[m_NoteIter] == note){//次に鳴らすべき音で
                                if(m_MoveTime - ACCEPT_TIME < m_Timer){//プレイヤーが居るようなら
                                    //移動開始

                                    //前回の到着位置が、今回の出発位置となる
                                    m_SrcNote = m_DstNote;

                                    //BMPから長さを決定
                                    var Time:Number = 60.0 / BPM;

                                    //そして移動時間として採用
                                    m_MoveTime = Time;

                                    //タイマーはリセット
                                    m_Timer = 0.0;

                                    //伸ばさないなら回転もしない
                                    m_DstRot = 0.0;

                                    for(;;){
                                        ++m_NoteIter;

                                        if(m_NoteIter >= Music.length){
                                            break;
                                        }

                                        var next_note:int = Music[m_NoteIter];

                                        if(next_note == o){
                                            //移動時間を伸ばす
                                            m_MoveTime += Time;
                                            //上の音を鳴らす処理はこれを反映すべきかもしれない

                                            //回転量も増やす
                                            m_DstRot += 360;
                                        }else{
                                            m_DstNote = next_note;//次に鳴らすべき音が次の到着位置となる
                                            break;
                                        }
                                    }

                                    //速度計算
                                    m_VY = -0.5 * m_MoveTime * GRAVITY;

                                    //向きによる制御
                                    var SrcX:int = NOTE_to_POS[m_SrcNote];
                                    var DstX:int = NOTE_to_POS[m_DstNote];
                                    if(SrcX < DstX){
                                        //向き変更
                                        m_Player.scaleX = 1;
                                    }
                                    if(SrcX > DstX){
                                        //向き変更
                                        m_Player.scaleX = -1;
                                    }

                                    //回転の方向も変更
                                    m_DstRot = m_Player.scaleX * m_DstRot;
                                }
                            }
                        }
                    }
//*/
                }
            }

            //音符ブロックの上下動
            {
                //これまた面倒なので、ひとまずデジタルに動かす(補間しない)
                for(var i:int = 0; i < NOTE_NUM; i++){//該当音符ブロックへのマッピングがないのでサーチ(これも配列アクセスにしたい)
                    if(NOTE_LIST[i] == KEY_to_NOTE[in_KeyCode]){
                        if(in_Flag){
                            m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2 - PANEL_LEN * 1/4;
                        }else{
                            //ここの位置は定数化しとくべきか(初期化とかぶる)
                            m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2;
                        }
                        break;
                    }
                }
            }
        }

        public function Lerp(in_Src:Number, in_Dst:Number, in_Ratio:Number):Number{
            return (in_Src * (1 - in_Ratio)) + (in_Dst * in_Ratio);
        }
    }
}