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

Touch Tennis

スマホにて「左手でキャラ移動」「右手で弾道指定」を行うテニスゲーム(の試作)
・PCでも「十字キーでキャラ移動」「マウスで弾道指定」で操作可能

操作方法
・タッチ:左面
 ・プレイヤーの移動
・タッチ&ムーブ:右面
 ・ボールの軌道の設定
  ・右矢印:スマッシュ
  ・左矢印:ドロップ
  ・上下矢印:カーブ
・十字キー(PC)
 ・プレイヤーの移動

How To Play
- Touch : Left Court
  - Player Move
- Touch : Right Court
  - Ball Move
    - Draw Right Arrow : Smash
    - Draw Left Arrow  : Drop
    - Draw Up or Down Arrow : Curve
- Keyboard : Arrow (PC)
  - Player Move


Android検証用
・https://play.google.com/store/apps/details?id=air.showohealer.game.airprototype
Get Adobe Flash player
by o_healer 25 May 2012
/**
 * 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/z6Lz
 */

/*
 「タッチテニス (Touch Tennis)」
 ・スマホにて「左手でキャラ移動」「右手で弾道指定」を行うテニスゲーム(の試作)
  ・PCでも「十字キーでキャラ移動」「マウスで弾道指定」で操作可能

 操作方法
 ・タッチ:左面
  ・プレイヤーの移動
 ・タッチ&ムーブ:右面
  ・ボールの軌道の設定
   ・右矢印:スマッシュ
   ・左矢印:ドロップ
   ・上下矢印:カーブ
 ・十字キー(PC)
  ・プレイヤーの移動

  How To Play
  - Touch : Left Court
    - Player Move
  - Touch : Right Court
    - Ball Move
      - Draw Right Arrow : Smash
      - Draw Left Arrow  : Drop
      - Draw Up or Down Arrow : Curve
  - Keyboard : Arrow (PC)
    - Player Move

 Android検証用
 ・https://play.google.com/store/apps/details?id=air.showohealer.game.airprototype

 メモ
 ・TouchEventは普通のPCだと動かない
  ・マルチタッチできなくてもマウス挙動として吸収してくれるかと思ったけどそんなことはなかったぜ!
*/

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.*;
 
    [SWF(width="465", height="465", frameRate="30", backgroundColor="0x000000")]
    public class TouchTennis extends Sprite {
        //==Embed (or Load)==
        //画像のリソース位置
//*
        //wonderfl用ロード型
        static public const URL_GRAPHICS:String = "http://assets.wonderfl.net/images/related_images/2/29/29bc/29bc996559de455b90e03125bc9bcb2541b25e6f";
//*/
/*
        //確認用ローカルロード型
        static public const URL_GRAPHICS:String = "TouchTennis.png";
//*/
/*
        //ブログ用埋め込み型
        [Embed(source='TouchTennis.png')]
         private static var Bitmap_Graphics: Class;
//*/

        //==Const==

        //画面の大きさ
        static public const VIEW_W:int = 465;
        static public const VIEW_H:int = 465;

        //モード
        static public var s_ModeIter:int = 0;
        static public const MODE_GAME            :int = s_ModeIter++;
        static public const MODE_DISPLAY_WIN    :int = s_ModeIter++;


        //==Var==

        //Pseudo Singleton
        static public var Instance:TouchTennis;

        //Layer
        public var m_Layer_BG:Sprite = new Sprite();
        public var m_Layer_OnBG:Sprite = new Sprite();
        public var m_Layer_Shadow:Sprite = new Sprite();
        public var m_Layer_Game:Sprite = new Sprite();

        //BG
        public var m_BaseBgBitmapData:BitmapData = new BitmapData(VIEW_W, VIEW_H, false, 0x222222);

        //Character
        public var m_Character:Vector.<GameObj_Character> = new Vector.<GameObj_Character>(2);
        //Input
        public var m_Input:Vector.<Controller> = new Vector.<Controller>(2);
        //LineDrawer
        public var m_BallLineDrawer:BallLineDrawer;

        //Ball
        public var m_Ball:GameObj_Ball;

        //テキスト
        public var m_Text:TextField = new TextField();

        //モード
        public var m_Mode:int = 0;//最初のモードから始める
        public var m_ModeTimer:Number = 0;


        //==Function==

        //Init
        public function TouchTennis():void{
            //Pseudo Singleton
            {
                Instance = this;
            }

            //stageを参照できるようになってから初期化する(イベントリスナの追加や画面の大きさの取得のため)
            if(stage != null){
                Init();
            }else{
                addEventListener(
                    Event.ADDED_TO_STAGE,//ステージに追加されたら
                    function(e:Event):void{
                        Init();
                    }
                );
            }
        }
        public function Init():void{
            var i:int;

            //Settings
            {
                Multitouch.inputMode=MultitouchInputMode.TOUCH_POINT;
            }

            //Static Init
            {
                GameObj_Character.StaticInit();
            }

            //Layer
            {
                addChild(m_Layer_BG);
                addChild(m_Layer_OnBG);
                addChild(m_Layer_Shadow);
                addChild(m_Layer_Game);
            }

            //Court
            {
                m_Layer_BG.addChild(new Bitmap(m_BaseBgBitmapData));
                m_Layer_BG.addChild(new Bitmap(Court.CreateBitmapData()));
            }

            //Character
            {
                //User
                {
                    i = 0;

                    m_Character[i] = new GameObj_Character();
                    m_Layer_Game.addChild(m_Character[i]);
                    m_Character[i].Init(Court.SIDE_LEFT);

                    var input_user:Controller_User = new Controller_User();
                    input_user.Init(m_Character[i], stage);
                    m_Input[i] = input_user;
                }

                //AI
                {
                    i = 1;

                    m_Character[i] = new GameObj_Character();
                    m_Layer_Game.addChild(m_Character[i]);
                    m_Character[i].Init(Court.SIDE_RIGHT);

                    var input_ai:Controller_AI = new Controller_AI();
                    input_ai.Init(m_Character[i]);
                    m_Input[i] = input_ai;
                }
            }

            //Ball
            {
                m_Ball = new GameObj_Ball();
                m_Layer_Game.addChild(m_Ball);
                m_Ball.SetPos(m_Character[1].m_X, m_Character[1].m_Y);
                m_Ball.Reset(m_Character[1]);
            }

            //Ball Line Drawer
            {
                m_BallLineDrawer = new BallLineDrawer();
                m_BallLineDrawer.m_Chara = m_Character[0];
                m_Layer_OnBG.addChild(m_BallLineDrawer);
            }

            //Text
            {
                m_Text.selectable = false;
                m_Text.autoSize = TextFieldAutoSize.LEFT;
                m_Text.defaultTextFormat = new TextFormat('Verdana', 60, 0xFFFFFF, true);
                m_Text.text = '';
                m_Text.filters = [new GlowFilter(0x00FFFF,1.0, 8,8)];

                addChild(m_Text);
            }

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

            //OnEnd
            {
                addEventListener(Event.REMOVED_FROM_STAGE, Finish);
            }

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

        //Reset
        public function Reset():void{
            //Character
            {
                var num:int = m_Character.length;
                for(var i:int = 0; i < num; ++i){
                    m_Character[i].Reset();
                }
            }

            //Ball
            {
                m_Ball.SetPos(m_Character[1].m_X, m_Character[1].m_Y);
                m_Ball.Reset(m_Character[1]);
            }

            //Text
            {
                m_Text.text = "";
            }
        }

        //ロード終了時の処理
        public function OnLoadEnd(in_Graphic:DisplayObject):void{
            var mtx:Matrix = new Matrix(1,0,0,1, 0,0);
            var clip_rect:Rectangle = new Rectangle(0,0, 32,32);

            //Character
            {
                clip_rect.width  = 24;
                clip_rect.height = 32;

                for(var i:int = 0; i < GameObj_Character.s_BitmapData.length; ++i){
                    mtx.ty = -32*i;
                    for(var j:int = 0; j < 3; ++j){
                        mtx.tx = -24*j;
                        GameObj_Character.s_BitmapData[i][j].draw(in_Graphic, mtx, null, null, clip_rect);
                    }
                }
            }

            //BG
            {
                mtx.tx = -32*0;
                mtx.ty = -32*2;
                clip_rect.width  = 32;
                clip_rect.height = 32;

                //Ori
                var bmd_bg_ori:BitmapData = new BitmapData(32, 32, false, 0x000000);
                bmd_bg_ori.draw(in_Graphic, mtx, null, null, clip_rect);

                //Apply
                var src_rect:Rectangle = new Rectangle(0,0,32,32);
                var dst_point:Point = new Point(0,0);
                for(var xx:int = 0; xx * 32 < VIEW_W; ++xx){
                    dst_point.x = xx * 32;
                    for(var yy:int = 0; yy * 32 < VIEW_H; ++yy){
                        dst_point.y = yy * 32;

                        m_BaseBgBitmapData.copyPixels(
                            bmd_bg_ori,
                            src_rect,
                            dst_point
                        );
                    }
                }
            }
        }

        //Finish
        public function Finish(e:Event):void{
            removeEventListener(Event.ADDED_TO_STAGE, Init);
            removeEventListener(Event.ENTER_FRAME, Update);
            removeEventListener(Event.REMOVED_FROM_STAGE, Finish);

            //Settings
            {
                Multitouch.inputMode=MultitouchInputMode.NONE;
            }
        }

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

            m_ModeTimer += DeltaTime;

            switch(m_Mode){
            case MODE_GAME:
                Update_GameObj(DeltaTime);
                break;
            case MODE_DISPLAY_WIN:
                Update_GameObj(DeltaTime);
                Check_DisplayWinEnd();
                break;
            }
        }

        //Update : GameObj
        public function Update_GameObj(in_DeltaTime:Number):void{
            //Character
            {
                var num:int = 2;

                for(var i:int = 0; i < num; ++i){
                    //Input
                    {
                        m_Input[i].Update(in_DeltaTime);
                    }

                    //GameObj
                    {
                        m_Character[i].Update(in_DeltaTime);
                    }
                }
            }

            //Ball
            {
                m_Ball.Update(in_DeltaTime);
            }

            //HitCheck
            {
                HitCheck();
            }

            //LineDrawerもここで
            {
                m_BallLineDrawer.Update();
            }
        }

        //Update : HitCheck
        public function HitCheck():void{
            //2回以上バウンドしてたらチェックしない
            if(2 <= m_Ball.m_BoundNum){
                return;
            }

            var num:int = 2;

            for(var i:int = 0; i < num; ++i){
                //打った側とは接触確認しない
                if(m_Ball.m_CourtSide == m_Character[i].m_CourtSide){
                    continue;
                }

                var GapX:Number = m_Character[i].m_X - m_Ball.m_X;
                var GapY:Number = m_Character[i].m_Y - m_Ball.m_Y;

                var Distance:Number = Math.sqrt(GapX*GapX + GapY*GapY);
                var Height:Number = Math.abs(m_Ball.m_OffsetY - (m_Character[i].m_OffsetY-32/2));

                if(Distance < 16/2 + 24/2 && Height <= 32/2){
                    m_Ball.Reset(m_Character[i]);
                    break;
                }
            }
        }

        //Check : DisplayWinEnd
        public function Check_DisplayWinEnd():void{
            const TIME:Number = 3;
            if(TIME <= m_ModeTimer){
                Reset();
                GoToMode(MODE_GAME);
            }
        }

        //OnBound
        public function OnBound(in_BoundNum:int):void{
            if(m_Mode != MODE_GAME){
                return;
            }

            switch(in_BoundNum){
            case 1://最初のバウンド
                if(! Court.IsRangeIn(m_Ball)){//コートの範囲外なら
                    //ボールを受ける側の得点とする
                    AddPoint(1 - m_Ball.m_CourtSide);
                }
                break;
            case 2://2回目のバウンド
                //ボールを打った側の得点とする
                AddPoint(m_Ball.m_CourtSide);
                break;
            }
        }

        //AddPoint
        public function AddPoint(in_CourtSide:int):void{
            m_Text.text = "Win";
            if(in_CourtSide == Court.SIDE_LEFT){
                m_Text.x = VIEW_W*1/4 - m_Text.textWidth/2;
            }else{
                m_Text.x = VIEW_W*3/4 - m_Text.textWidth/2;
            }
            m_Text.y = VIEW_H/2 - m_Text.textHeight/2;

            GoToMode(MODE_DISPLAY_WIN);
        }

        //Mode
        public function GoToMode(in_Mode:int):void{
            m_Mode = in_Mode;
            m_ModeTimer = 0;
        }
    }
}


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



//コートに関する情報
class Court
{
    //==Const==

    //Side
    static public const SIDE_LEFT :int = 0;
    static public const SIDE_RIGHT:int = 1;

    //Coord
    static public const CENTER_X:int = TouchTennis.VIEW_W/2;
    static public const CENTER_Y:int = TouchTennis.VIEW_H/2;
    static public const LINE_LX:int = TouchTennis.VIEW_W * 1/8;
    static public const LINE_RX:int = TouchTennis.VIEW_W * 7/8;
    static public const LINE_UY:int = TouchTennis.VIEW_H * 2/8;
    static public const LINE_DY:int = TouchTennis.VIEW_H * 6/8;


    //==Function==

    //初期位置の設定
    static public function SetInitPos(in_Chara:GameObj_Character):void{
        if(in_Chara.GetCourtSide() == SIDE_LEFT){
            //Left
            in_Chara.SetPos(LINE_LX, (CENTER_Y + LINE_DY) / 2);
        }else{
            //Right
            in_Chara.SetPos(LINE_RX, (CENTER_Y + LINE_UY) / 2);
        }
    }

    //移動可能範囲の制限
    static public function LimitPosition(in_Chara:GameObj_Character):void{
        if(in_Chara.GetCourtSide() == SIDE_LEFT){
            //Left
            if(CENTER_X <= in_Chara.m_X){
                in_Chara.m_X = CENTER_X-1;
            }
        }else{
            //Right
            if(in_Chara.m_X < CENTER_X){
                in_Chara.m_X = CENTER_X;
            }
        }
    }

    //指定座標(in_X)はin_Charaのコート側か
    static public function IsInOwnSide(in_Chara:GameObj_Character, in_X:Number):Boolean{
        if(in_Chara.GetCourtSide() == SIDE_LEFT){
            //Left
            return (in_X < CENTER_X);
        }else{
            //Right
            return (CENTER_X <= in_X);
        }
    }

    //そのボールはコートにInしているか
    static public function IsRangeIn(in_Ball:GameObj_Ball):Boolean{
        var x:int = in_Ball.m_X;
        var y:int = in_Ball.m_Y;

        if(in_Ball.m_CourtSide == SIDE_LEFT){//左側で打たれたもの
            //右側のコート内に入っているか
            return (CENTER_X < x && x <= LINE_RX && LINE_UY <= y && y <= LINE_DY);
        }else{//右側で打たれたもの
            //左側のコート内に入っているか
            return (LINE_LX <= x && x < CENTER_X && LINE_UY <= y && y <= LINE_DY);
        }
    }

    //コート用画像の生成
    static public function CreateBitmapData():BitmapData{
        var bmd:BitmapData = new BitmapData(TouchTennis.VIEW_W, TouchTennis.VIEW_H, true, 0x00000000);

        //Draw Line
        {
            var shape:Shape = new Shape();
            var g:Graphics = shape.graphics;

            //Outer
            g.lineStyle(4, 0xFFFFFF, 0.9);
            g.beginFill(0x44EE00, 0.9);
            g.drawRect(LINE_LX, LINE_UY, LINE_RX - LINE_LX, LINE_DY - LINE_UY);
            g.endFill();

            //Center
            g.moveTo(CENTER_X, LINE_UY);
            g.lineTo(CENTER_X, LINE_DY);

            bmd.draw(shape);
        }

        return bmd;
    }
}


//ゲームに出てくる物体全般の基底クラス
class GameObj extends Sprite
{
    //==Var==

    //Coord
    public var m_X:Number = 0;
    public var m_Y:Number = 0;
    public var m_OffsetY:Number = 0;//ボールが浮いてる時など用(ジャンプで使えなくもないが)

    //Courtside
    public var m_CourtSide:int = 0;

    //Graphic
    public var m_Layer_Obj:Sprite = new Sprite();
    public var m_Layer_Shadow:Sprite = new Sprite();


    //==Function==

    //#Init

    //Init
    public function GameObj(){
        addChild(m_Layer_Obj);
        TouchTennis.Instance.m_Layer_Shadow.addChild(m_Layer_Shadow);
    }

    //#Set

    //Set : Pos
    public function SetPos(in_X:Number, in_Y:Number):void{
        m_X = in_X;
        m_Y = in_Y;
    }

    //PosData => GraphicPos
    public function RefreshPos():void{
        m_Layer_Obj.x = m_X;
        m_Layer_Obj.y = m_Y + m_OffsetY;

        m_Layer_Shadow.x = m_X;
        m_Layer_Shadow.y = m_Y;
    }


    //#Update

    //Update
    virtual public function Update(in_DeltaTime:Number):void{
    }
}


//キャラクター
class GameObj_Character extends GameObj
{
    //==Const==

    //表示画像
    static public var s_BitmapData:Vector.<Vector.<BitmapData> > = new Vector.<Vector.<BitmapData> >(2);

    //アニメーション用パラメータ
    static public const ANIM_CYCLE:Number = 1.0;
    static public const ANIM_ITER:Array = [0,1,2,1];
    static public const ANIM_NUM:int = ANIM_ITER.length;


    //==Var==

    public var m_VX:Number = 0;
    public var m_VY:Number = 0;

    //キャラ画像は何番目を使うか
    public var m_GraphicIndex:int = 0;
    //画像表示
    public var m_Bitmap:Bitmap;
    //アニメーションの方向
    public var m_AnimDir:int = 0;
    //アニメーション用タイマー
    public var m_AnimTimer:Number = 0.0;

    //
    public var m_BallSrcX:int = 0;
    public var m_BallSrcY:int = 0;
    public var m_BallDstX:int = 0;
    public var m_BallDstY:int = 0;
    public var m_ReqBallSrcX:int = 0;
    public var m_ReqBallSrcY:int = 0;
    public var m_ReqBallDstX:int = 0;
    public var m_ReqBallDstY:int = 0;


    //==Function==

    //#Init

    //Static Init
    static public function StaticInit():void{
        var num:int = s_BitmapData.length;
        for(var i:int = 0; i < num; ++i){
            s_BitmapData[i] = new Vector.<BitmapData>(3);
            for(var j:int = 0; j < 3; ++j){
                s_BitmapData[i][j] = new BitmapData(24, 32, true, 0x00000000);
            }
        }
    }

    //Init
    public function Init(in_CourtSide:int):void{
        //Param
        {
            m_CourtSide = in_CourtSide;

            //仮でコートサイドによりキャラを決定
            m_GraphicIndex = in_CourtSide;
        }

        //Pos
        {
            Court.SetInitPos(this);
        }

        //Graphic
        {
            //Chara
            {
                m_Bitmap = new Bitmap();

                m_Bitmap.x = -24/2;
                m_Bitmap.y = -32;
                if(in_CourtSide == Court.SIDE_RIGHT){
                    m_Layer_Obj.scaleX = -1;
                }

                m_Layer_Obj.addChild(m_Bitmap);
            }

            //Shadow
            {
                const shadow_rad:int = 16;

                var shape:Shape = new Shape();
                var g:Graphics = shape.graphics;

                var bmd_shadow:BitmapData = new BitmapData(shadow_rad, shadow_rad/2, true, 0x00000000);
                g.clear();
                g.lineStyle(0,0,0);
                g.beginFill(0x000000, 0.8);
                g.drawEllipse(0, 0, shadow_rad, shadow_rad/2);
                g.endFill();
                bmd_shadow.draw(shape);

                var bmp_shadow:Bitmap = new Bitmap(bmd_shadow);
                bmp_shadow.x = -shadow_rad/2;
                bmp_shadow.y = -shadow_rad/2 / 2;
                m_Layer_Shadow.addChild(bmp_shadow);
            }
        }
    }

    //Reset
    public function Reset():void{
        //Pos
        {
            Court.SetInitPos(this);
        }
    }


    //#Set

    //Set : Vel
    public function SetVel(in_VX:Number, in_VY:Number):void{
        m_VX = in_VX;
        m_VY = in_VY;
    }

    //Request : Ball : Src
    public function ReqBallSrcPos(in_X:int, in_Y:int):void{
        m_ReqBallSrcX = in_X;
        m_ReqBallSrcY = in_Y;
    }
    //Request : Ball : Dst
    public function ReqBallDstPos(in_X:int, in_Y:int):void{
        m_ReqBallDstX = in_X;
        m_ReqBallDstY = in_Y;
    }
    //Apply : Ball
    public function ApplyBallPos():void{
        m_BallSrcX = m_ReqBallSrcX;
        m_BallSrcY = m_ReqBallSrcY;
        m_BallDstX = m_ReqBallDstX;
        m_BallDstY = m_ReqBallDstY;
    }


    //#Get

    public function GetCourtSide():int{
        return m_CourtSide;
    }

    public function GetMaxVelocity():Number{
        //一秒で何ドット進むか
        return 100;//200;
    }


    //#Update

    //Update
    override public function Update(in_DeltaTime:Number):void{
        Update_Move(in_DeltaTime);

        Update_Anim(in_DeltaTime);
    }

    //Update : Move
    public function Update_Move(in_DeltaTime:Number):void{
        //Pos
        {
            //Move
            m_X += m_VX * in_DeltaTime;
            m_Y += m_VY * in_DeltaTime;

            //Limit
            Court.LimitPosition(this);

            //Refresh
            RefreshPos();
        }
    }

    //Update : Anim
    public function Update_Anim(in_DeltaTime:Number):void{
        //m_AnimTimer
        {
            m_AnimTimer += in_DeltaTime;
            if(m_AnimTimer > ANIM_CYCLE){m_AnimTimer -= ANIM_CYCLE;}
        }

        //m_AnimTimer => iter
        var iter:int;
        {
            iter = ANIM_ITER[int(ANIM_NUM * m_AnimTimer/ANIM_CYCLE)];
        }

        m_Bitmap.bitmapData = s_BitmapData[m_GraphicIndex][iter];
    }
}


//ボール
class GameObj_Ball extends GameObj
{
    //==Const==

    //基本移動速度
    //- スマッシュなどで変更されたりはするが、単純な点指定でのショットはこの速度で移動する
    static public const BASE_VEL:Number = 100;//300;


    //==Var==

    //移動時間まわり
    public var m_Timer:Number = 0;
    public var m_MoveTime:Number = 1;

    //軌道(ベジエ)まわり
    public var m_SrcX:Number = 0;
    public var m_SrcY:Number = 0;
    public var m_CtrX:Number = 0;
    public var m_CtrY:Number = 0;
    public var m_DstX:Number = 0;
    public var m_DstY:Number = 0;
    //軌道(高度)まわり
    public var m_InitOffsetY:Number = 0;

    //バウンド数
    public var m_BoundNum:int = 0;


    //==Function==

    //#Init
    public function GameObj_Ball(){
        const ball_rad:int = 12;

        var shape:Shape = new Shape();
        var g:Graphics = shape.graphics;

        //Ball
        {
            var bmd_ball:BitmapData = new BitmapData(ball_rad, ball_rad, true, 0x00000000);
            g.clear();
            g.lineStyle(0,0,0);
            g.beginFill(0xFFFF00, 1.0);
            g.drawCircle(ball_rad/2, ball_rad/2, ball_rad/2);
            g.endFill();
            bmd_ball.draw(shape);

            var bmp_ball:Bitmap = new Bitmap(bmd_ball);
            bmp_ball.x = -ball_rad/2;
            bmp_ball.y = -ball_rad;
            m_Layer_Obj.addChild(bmp_ball);
        }

        //Shadow
        {
            var bmd_shadow:BitmapData = new BitmapData(ball_rad, ball_rad/2, true, 0x00000000);
            g.clear();
            g.lineStyle(0,0,0);
            g.beginFill(0x000000, 0.8);
            g.drawEllipse(0, 0, ball_rad, ball_rad/2);
            g.endFill();
            bmd_shadow.draw(shape);

            var bmp_shadow:Bitmap = new Bitmap(bmd_shadow);
            bmp_shadow.x = -ball_rad/2;
            bmp_shadow.y = -ball_rad/2 / 2;
            m_Layer_Shadow.addChild(bmp_shadow);
        }
    }

    //#Reset

    public function Reset(in_Chara:GameObj_Character):void{
        //Param
        var IsSmash:Boolean;
        var AbsGapX:Number;
        {
            if(in_Chara.GetCourtSide() == Court.SIDE_LEFT){
                //コートの左側なら右方向への移動がスマッシュ
                IsSmash = (in_Chara.m_BallSrcX < in_Chara.m_BallDstX);
            }else{
                //右側は逆
                IsSmash = (in_Chara.m_BallDstX < in_Chara.m_BallSrcX);
            }

            AbsGapX = Math.abs(in_Chara.m_BallDstX - in_Chara.m_BallSrcX);

            //CourtSide
            m_CourtSide = in_Chara.m_CourtSide;
            //
            m_InitOffsetY = m_OffsetY;
        }

        //基本軌道設定
        {
            //ベジエでストレートをまず作って、それを補正する形でカーブを実現

            //始点は今のボール位置
            m_SrcX = this.m_X;
            m_SrcY = this.m_Y;

            //終点は最後にタッチした位置
            m_DstX = in_Chara.m_BallDstX;
            m_DstY = in_Chara.m_BallDstY;

            //制御点はその中間(ストレートの設定)
            m_CtrX = (m_SrcX + m_DstX) / 2;
            m_CtrY = (m_SrcY + m_DstY) / 2;

            //カーブの反映
            m_CtrY -= in_Chara.m_BallDstY - in_Chara.m_BallSrcY;
        }

        //移動時間の計算
        {
            //「始点→終点」の距離と「始点→制御点→終点」の距離の半分くらいの距離を指定速度で移動するものと考える

            var GapX_SC:Number = m_CtrX - m_SrcX;
            var GapY_SC:Number = m_CtrY - m_SrcY;
            var GapX_CD:Number = m_DstX - m_CtrX;
            var GapY_CD:Number = m_DstY - m_CtrY;
            var GapX_SD:Number = m_DstX - m_SrcX;
            var GapY_SD:Number = m_DstY - m_SrcY;

            var Distance_SD:Number = Math.sqrt(GapX_SD*GapX_SD + GapY_SD*GapY_SD);
            var Distance_SCD:Number = Math.sqrt(GapX_SC*GapX_SC + GapY_SC*GapY_SC) + Math.sqrt(GapX_CD*GapX_CD + GapY_CD*GapY_CD);

            var Distance:Number = Lerp(Distance_SD, Distance_SCD, 0.1);

            m_MoveTime = Distance / BASE_VEL;
        }

        //スマッシュ・ドロップ補正
        if(IsSmash)
        {//スマッシュ
            //スマッシュは移動速度が速くなる(=移動時間が短くなる)
            //- X移動量が0なら通常の速度のまま、X移動量がHalfRatioDistanceになったら速度が倍になる
            const HalfRatioDistance_Smash:Number = 0.3 * TouchTennis.VIEW_W;
            var SmashRatio:Number = HalfRatioDistance_Smash / (HalfRatioDistance_Smash + AbsGapX);
            m_MoveTime *= SmashRatio;
        }
        else
        {//ドロップ
            //制御点を終点に寄せることで減速させる
            //- X移動量が0ならストレートのまま、X移動量がHalfRatioDistanceになったらCtrがDstに半分近づく
            const HalfRatioDistance_Drop:Number = 0.25 * TouchTennis.VIEW_W;
            var DropRatio:Number = 1 - HalfRatioDistance_Drop / (HalfRatioDistance_Drop + AbsGapX);
            m_CtrX = Lerp(m_CtrX, m_DstX, DropRatio);
            m_CtrY = Lerp(m_CtrY, m_DstY, DropRatio);
        }

        m_Timer = 0;
        m_BoundNum = 0;
    }

    //#Update

    //Update
    override public function Update(in_DeltaTime:Number):void{
//*
        //
        const OffsetRatio:Number = -20;

        //Time & Ratio
        var Ratio:Number;
        {
            m_Timer += in_DeltaTime;

            Ratio = m_Timer / m_MoveTime;
        }

        //移動
        var bound:int = 0;
        if(Ratio <= 1)
        {//ベジエで移動
            //XY
            {
                var SrcCtrX:Number = Lerp(m_SrcX, m_CtrX, Ratio);
                var SrcCtrY:Number = Lerp(m_SrcY, m_CtrY, Ratio);

                var CtrDstX:Number = Lerp(m_CtrX, m_DstX, Ratio);
                var CtrDstY:Number = Lerp(m_CtrY, m_DstY, Ratio);

                m_X = Lerp(SrcCtrX, CtrDstX, Ratio);
                m_Y = Lerp(SrcCtrY, CtrDstY, Ratio);
            }

            //OffsetY
            {
                m_OffsetY = OffsetRatio * m_Timer * (m_MoveTime - m_Timer);
                m_OffsetY += m_InitOffsetY * (1 - Ratio);
            }
        }
        else
        {//ベジエの終端速度に合わせて移動
            //バウンド後の移動時間
            var t:Number = m_Timer;
            var MoveTime:Number = m_MoveTime;
            {
                for(bound = 0; bound < 5; ++bound){
                    if(MoveTime <= t){
                        t -= MoveTime;
                        MoveTime * 0.6;
                    }else{
                        break;
                    }
                }
            }

            //XY
            {
                m_X = Lerp(m_CtrX, m_DstX, Ratio);
                m_Y = Lerp(m_CtrY, m_DstY, Ratio);
            }

            //OffsetY
            {
                m_OffsetY = OffsetRatio * t * (MoveTime - t);
            }
        }

        //移動の反映
        RefreshPos();

        //バウンド
        for(var b:int = m_BoundNum; b < bound; ++b){
            //バウンドしたらそれを伝達
            TouchTennis.Instance.OnBound(b+1);
        }
        m_BoundNum = bound;
//*/
    }


    //#Util

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



//キャラクターを動かす処理の基底クラス
class Controller
{
    //==Var==

    //操作する対象
    protected var m_Chara:GameObj_Character;


    //==Function==

    //操作する対象の設定
    public function SetChara(in_Chara:GameObj_Character):void{
        m_Chara = in_Chara;
    }

    //Update
    virtual public function Update(in_DeltaTime:Number):void{
    }
}


//キャラクターをタッチやキーボードで動かすクラス
class Controller_User extends Controller
{
    //==Var==

    //入力
    //- Keyboard
    public var m_InputL:Boolean = false;
    public var m_InputR:Boolean = false;
    public var m_InputU:Boolean = false;
    public var m_InputD:Boolean = false;
    //- Touch
    public var m_TouchID_Chara:int = -1;
    public var m_TouchID_Ball:int = -1;

    //
    public var m_CharaTrgX:int = 0;
    public var m_CharaTrgY:int = 0;


    //==Function==

    //Init
    public function Init(in_Chara:GameObj_Character, in_Parent:DisplayObject):void{
        //m_Chara
        {
            SetChara(in_Chara);
        }

        //Touch & Keyboard
        {
            in_Parent.addEventListener(KeyboardEvent.KEY_DOWN,    OnKeyDown);
            in_Parent.addEventListener(KeyboardEvent.KEY_UP,    OnKeyUp);
            if(Multitouch.supportsTouchEvents){
                in_Parent.addEventListener(TouchEvent.TOUCH_BEGIN,    OnTouchDown);
                in_Parent.addEventListener(TouchEvent.TOUCH_MOVE,    OnTouchMove);
                in_Parent.addEventListener(TouchEvent.TOUCH_END,    OnTouchUp);
            }else{
                in_Parent.addEventListener(MouseEvent.MOUSE_DOWN,    OnMouseDown);
                in_Parent.addEventListener(MouseEvent.MOUSE_MOVE,    OnMouseMove);
                in_Parent.addEventListener(MouseEvent.MOUSE_UP,        OnMouseUp);
            }
        }

        //Init
        {
            //!!Test
            m_Chara.m_BallSrcX = m_Chara.m_ReqBallSrcX = TouchTennis.VIEW_W*80/100;
            m_Chara.m_BallSrcY = m_Chara.m_ReqBallSrcY = TouchTennis.VIEW_H*30/100;
            m_Chara.m_BallDstX = m_Chara.m_ReqBallDstX = TouchTennis.VIEW_W*80/100;
            m_Chara.m_BallDstY = m_Chara.m_ReqBallDstY = TouchTennis.VIEW_H*70/100;
        }
    }

    //Keyboard
    private function OnKeyDown(event:KeyboardEvent):void{
        if(event.keyCode == Keyboard.LEFT){    m_InputL = true;}
        if(event.keyCode == Keyboard.RIGHT){m_InputR = true;}
        if(event.keyCode == Keyboard.UP){    m_InputU = true;}
        if(event.keyCode == Keyboard.DOWN){    m_InputD = true;}
    }
    private function OnKeyUp(event:KeyboardEvent):void{
        if(event.keyCode == Keyboard.LEFT){    m_InputL = false;}
        if(event.keyCode == Keyboard.RIGHT){m_InputR = false;}
        if(event.keyCode == Keyboard.UP){    m_InputU = false;}
        if(event.keyCode == Keyboard.DOWN){    m_InputD = false;}
    }

    //Touch
    //- Touch
    private function OnTouchDown(e:TouchEvent):void{
        OnDown(e.touchPointID, e.stageX, e.stageY);
    }
    private function OnTouchMove(e:TouchEvent):void{
        OnMove(e.touchPointID, e.stageX, e.stageY);
    }
    private function OnTouchUp(e:TouchEvent):void{
        OnUp(e.touchPointID, e.stageX, e.stageY);
    }
    //- Mouse
    static public const DUMMY_MOUSE_ID:int = 0;
    private function OnMouseDown(e:MouseEvent):void{
        OnDown(DUMMY_MOUSE_ID, e.stageX, e.stageY);
    }
    private function OnMouseMove(e:MouseEvent):void{
        OnMove(DUMMY_MOUSE_ID, e.stageX, e.stageY);
    }
    private function OnMouseUp(e:MouseEvent):void{
        OnUp(DUMMY_MOUSE_ID, e.stageX, e.stageY);
    }
    //- Common
    private function OnDown(in_ID:int, in_X:int, in_Y:int):void{
        if(Court.IsInOwnSide(m_Chara, in_X)){
            //プレイヤーの移動用のタッチ
            if(m_TouchID_Chara < 0){//まだ他でタッチしてなければ採用
                m_TouchID_Chara = in_ID;

                m_CharaTrgX = in_X;
                m_CharaTrgY = in_Y;
            }
        }else{
            //ボールの軌道用のタッチ
            if(m_TouchID_Ball < 0){//まだ他でタッチしてなければ採用
                m_TouchID_Ball = in_ID;

                m_Chara.ReqBallSrcPos(in_X, in_Y);
                m_Chara.ReqBallDstPos(in_X, in_Y);
            }
        }
    }
    private function OnMove(in_ID:int, in_X:int, in_Y:int):void{
        if(m_TouchID_Chara == in_ID){
            m_CharaTrgX = in_X;
            m_CharaTrgY = in_Y;
        }
        if(m_TouchID_Ball == in_ID){
            m_Chara.ReqBallDstPos(in_X, in_Y);
        }
    }
    private function OnUp(in_ID:int, in_X:int, in_Y:int):void{
        if(m_TouchID_Chara == in_ID){
            m_TouchID_Chara = -1;
        }

        if(m_TouchID_Ball == in_ID){
            m_Chara.ApplyBallPos();
            m_TouchID_Ball = -1;
        }
    }

    //Update
    override public function Update(in_DeltaTime:Number):void{
        Update_Character(in_DeltaTime);

        Update_Ball(in_DeltaTime);
    }

    //Update : Chara
    public function Update_Character(in_DeltaTime:Number):void{
        var MaxVel:Number = m_Chara.GetMaxVelocity();

        var VX:Number = 0;
        var VY:Number = 0;

        var GapX:Number;
        var GapY:Number;
        var Len:Number;

        //KeyBoard
        {
            GapX = 0;
            GapY = 0;
            if(m_InputL){GapX -= 1;}
            if(m_InputR){GapX += 1;}
            if(m_InputU){GapY -= 1;}
            if(m_InputD){GapY += 1;}

            Len = Math.sqrt(GapX*GapX + GapY*GapY);
            if(Len <= 0){
                //No Input
            }else{
                //Input
                VX = MaxVel * GapX / Len;
                VY = MaxVel * GapY / Len;
            }
        }

        //Touch
        {
            if(0 <= m_TouchID_Chara){
                GapX = m_CharaTrgX - m_Chara.m_X;
                GapY = m_CharaTrgY - m_Chara.m_Y;

                Len = Math.sqrt(GapX*GapX + GapY*GapY);
                var Move:Number = MaxVel * in_DeltaTime;

                if(Len < Move){
                    VX = MaxVel * GapX / Move;
                    VY = MaxVel * GapY / Move;
                }else{
                    VX = MaxVel * GapX / Len;
                    VY = MaxVel * GapY / Len;
                }
            }
        }

        //Apply
        {
            m_Chara.SetVel(VX, VY);
        }
    }

    //Update : Ball
    public function Update_Ball(in_DeltaTime:Number):void{
    }
}


//キャラクターをAIで動かすクラス
class Controller_AI extends Controller
{
    //==Function==

    //Init
    public function Init(in_Chara:GameObj_Character):void{
        //m_Chara
        {
            SetChara(in_Chara);
        }

        //Init
        {
            //!!Test
            in_Chara.m_BallSrcX = TouchTennis.VIEW_W*35/100;
            in_Chara.m_BallSrcY = TouchTennis.VIEW_H*55/100;
            in_Chara.m_BallDstX = TouchTennis.VIEW_W*35/100;
            in_Chara.m_BallDstY = TouchTennis.VIEW_H*55/100;
        }
    }

    //Update
    override public function Update(in_DeltaTime:Number):void{
        Update_Character(in_DeltaTime);

        Update_Ball(in_DeltaTime);
    }

    //Update : Chara
    public function Update_Character(in_DeltaTime:Number):void{
        var MaxVel:Number = m_Chara.GetMaxVelocity();

        var VX:Number = 0;
        var VY:Number = 0;
        {
            //移動目標
            var TrgX:Number;
            var TrgY:Number;
            if(TouchTennis.Instance.m_Ball.m_CourtSide == m_Chara.m_CourtSide)
            {//自分が打った球が移動中
/*
                //→コートの中央あたりの戻る
                if(m_Chara.m_CourtSide == Court.SIDE_LEFT){
                    TrgX = Lerp(Court.CENTER_X, Court.LINE_LX, 0.5);
                }else{
                    TrgX = Lerp(Court.CENTER_X, Court.LINE_RX, 0.5);
                }
                TrgY = Court.CENTER_Y;
/*/
                //ひとまず動かないでみる
                TrgX = m_Chara.m_X;
                TrgY = m_Chara.m_Y;
//*/
            }
            else
            {//相手の打った球が移動中
                //→着弾位置に移動

                //直接着弾位置を取得(卑怯!)
                TrgX = TouchTennis.Instance.m_Ball.m_DstX;
                TrgY = TouchTennis.Instance.m_Ball.m_DstY;
                //→できれば現在位置とベジエから着弾点を擬似予測したい
            }


            var GapX:Number = TrgX - m_Chara.m_X;
            var GapY:Number = TrgY - m_Chara.m_Y;

            var Len:Number = Math.sqrt(GapX*GapX + GapY*GapY);
            var Move:Number = MaxVel * in_DeltaTime;

            if(Len < Move){
                VX = MaxVel * GapX / Move;
                VY = MaxVel * GapY / Move;
            }else{
                VX = MaxVel * GapX / Len;
                VY = MaxVel * GapY / Len;
            }
        }

        //Apply
        {
            m_Chara.SetVel(VX, VY);
        }
    }

    //Update : Ball
    public function Update_Ball(in_DeltaTime:Number):void{
        //とりあえずランダムに上下に振ってみる
        if(Math.random() < 0.5){
            m_Chara.m_BallSrcX = TouchTennis.VIEW_W*35/100;
            m_Chara.m_BallSrcY = TouchTennis.VIEW_H*35/100;
            m_Chara.m_BallDstX = TouchTennis.VIEW_W*35/100;
            m_Chara.m_BallDstY = TouchTennis.VIEW_H*35/100;
        }else{
            m_Chara.m_BallSrcX = TouchTennis.VIEW_W*35/100;
            m_Chara.m_BallSrcY = TouchTennis.VIEW_H*55/100;
            m_Chara.m_BallDstX = TouchTennis.VIEW_W*35/100;
            m_Chara.m_BallDstY = TouchTennis.VIEW_H*55/100;
        }
    }


    //#Util

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


//タッチによる指定の可視化
class BallLineDrawer extends Sprite
{
    //==Var==

    //実際のグラフィック
    public var m_Shape:Shape = new Shape();
    public var m_Graphics:Graphics = m_Shape.graphics;

    //可視化する対象のキャラ
    public var m_Chara:GameObj_Character = null;

    //前回の描画パラメータ
    public var m_SrcX:int = 0;
    public var m_SrcY:int = 0;
    public var m_DstX:int = 0;
    public var m_DstY:int = 0;


    //==Function==

    //Init
    public function BallLineDrawer(){
        //Graphic
        {
            m_Shape.filters = [new GlowFilter(0xFFFFFF, 0.8, 4,4)];

            addChild(m_Shape);
        }
    }

    //Update
    public function Update():void{
        //Check
        {
            if(m_Chara == null){
                return;
            }
        }

        //
        if(m_SrcX != m_Chara.m_ReqBallSrcX || m_SrcY != m_Chara.m_ReqBallSrcY || m_DstX != m_Chara.m_ReqBallDstX || m_DstY != m_Chara.m_ReqBallDstY)
        {
            m_SrcX = m_Chara.m_ReqBallSrcX;
            m_SrcY = m_Chara.m_ReqBallSrcY;
            m_DstX = m_Chara.m_ReqBallDstX;
            m_DstY = m_Chara.m_ReqBallDstY;

            if(m_SrcX != m_DstX || m_SrcY != m_DstY)
            {//Line
                m_Graphics.clear();
                m_Graphics.lineStyle(4, 0x00FFFF, 0.8);
                m_Graphics.moveTo(m_SrcX, m_SrcY);
                m_Graphics.lineTo(m_DstX, m_DstY);

                var GapX:Number = m_DstX - m_SrcX;
                var GapY:Number = m_DstY - m_SrcY;
                var Len:Number = Math.sqrt(GapX*GapX + GapY*GapY);
                var OffsetX:Number = 16 * GapY/Len;
                var OffsetY:Number = 16 * -GapX/Len;
                var OffsetX2:Number = 16 * -GapX/Len;
                var OffsetY2:Number = 16 * -GapY/Len;
                m_Graphics.moveTo(m_DstX, m_DstY);
                m_Graphics.lineTo(m_DstX+OffsetX+OffsetX2, m_DstY+OffsetY+OffsetY2);
                m_Graphics.moveTo(m_DstX, m_DstY);
                m_Graphics.lineTo(m_DstX-OffsetX+OffsetX2, m_DstY-OffsetY+OffsetY2);
            }
            else
            {//Dot
                m_Graphics.clear();
                m_Graphics.lineStyle(0,0,0);
                m_Graphics.beginFill(0x00FFFF, 0.8);
                m_Graphics.drawCircle(m_SrcX, m_SrcY, 4);
            }
        }
    }
}