////////////////////////////////////////////////////////////////////// //-------------------------------------------------------------------- // 第1回 東方弾幕風コンテスト // 霊夢のスペルカード: 孤空「エースズハイ」 //-------------------------------------------------------------------- // // 射手:霊夢 // 弾種:札弾 // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // ヘッダ部 ////////////////////////////////////////////////////////////////////// #東方弾幕風 #Title[孤空「エースズハイ」] #Text[第1回 東方弾幕風コンテスト作品: 孤空「エースズハイ」 霊夢のスペカをイメージ、難易度は 本家EX〜PHボスの中盤戦を想定して います。] #Image[] #BGM[] #Player[FREE] #ScriptVersion[2] ////////////////////////////////////////////////////////////////////// // スクリプト部 ////////////////////////////////////////////////////////////////////// script_enemy_main { ////////////////////////////////////////////////////////////////////// // 設定定義 ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // 定数 ////////////////////////////////////////////////////////////////////// //ifdef _DEBUG let _DEBUG = false; // スペカスコア let BOSS_SCORE = 10000000; // スペカ名 let BOSS_SPELLNAME = "孤空「エースズハイ」"; // ボス画像 let BOSS_IMAGE = "script\img\ExRumia.png"; // 使い魔画像 let FAMILIAR_IMAGE = "script\ExRumia\img\Enemy.png"; // 被弾判定半径 let BOSS_COLLISION_A = 32; // 対当たり判定半径 let BOSS_COLLISION_B = 16; // ボスライフ let BOSS_LIFE = 500; // 撃破時点アイテム数 let BOSS_ITEM_NUM = 21; // スペカ時間 let BOSS_TIME = 65; // 登場時無敵時間 let BOSS_INVINTIME = 60; // 登場時移動時間 let BOSS_HOMETIME = 60; // 攻撃開始までの間隔 let BOSS_WAITTIME = 120; // 初期X座標 let BOSS_HOME_X = GetCenterX(); // 初期Y座標 let BOSS_HOME_Y = GetClipMinY() + 96; // サイドの移動限界マージン let BOSS_MOVE_MARGIN_SIDE = 64; // 上端の移動限界マージン let BOSS_MOVE_MARGIN_TOP = 64; // 下端の移動限界マージン let BOSS_MOVE_MARGIN_BOTTOM = BOSS_HOME_Y + 32; // ボスダメージレート推移 let BOSS_DMGRATE = [ 1, 2.5, 5, 7.5, 10 ]; let BOSS_BOMBDMGRATE = [ 0.75, 1.875, 3.75, 5.625, 7.5 ]; // ボスダメージレート推移時間 let BOSS_DMGRATE_TIME = [ 0, BOSS_HOMETIME, BOSS_HOMETIME + BOSS_WAITTIME, BOSS_HOMETIME + BOSS_WAITTIME + 120, BOSS_HOMETIME + BOSS_WAITTIME + 300 ]; // ボス描画状態定数 let BOSS_ACT_STAND = 0; // 立ち(アニメなし) let BOSS_ACT_MOVE = -1; // 移動(自動で左右判定) let BOSS_ACT_MOVE_L = 2; // 左移動 let BOSS_ACT_MOVE_R = 3; // 右移動 let BOSS_ACT_SHOT = 1; // 弾幕 // ボス描画の基準点 let BOSS_RECT_LENGTH = 64; // カット正方形の一辺の長さ let BOSS_RECT_STAND = [ 0, 0 ]; // 立ち(アニメなし) let BOSS_RECT_SHOT = [ 1, 0 ]; // 弾幕 let BOSS_RECT_MOVE_L = [ 2, 0 ]; // 左移動 let BOSS_RECT_MOVE_R = [ 3, 0 ]; // 右移動 let BOSS_RECT = [ BOSS_RECT_STAND, BOSS_RECT_SHOT, BOSS_RECT_MOVE_L, BOSS_RECT_MOVE_R ]; // 読み込みリソース let IMAGE_LIST = [ FAMILIAR_IMAGE, BOSS_IMAGE ]; let SOUND_LIST = []; ////////////////////////////////////////////////////////////////////// // メンバ変数 ////////////////////////////////////////////////////////////////////// //発狂変数( 0 -> 1 ) let m_dRank = 0; //メインループカウンタ let m_nCount = 0; // 現在のアクション let m_nAction = BOSS_ACT_STAND; ////////////////////////////////////////////////////////////////////// // メイン動作 ////////////////////////////////////////////////////////////////////// // メインタスク /////////////////////////////////////////////////////////// // @param nArgv 任意の値 // @param nLevel 難易度 task MainTask( let nArgv, let nLevel ) { __Wait( BOSS_HOMETIME ); setAction( BOSS_ACT_STAND ); SetScore( BOSS_SCORE ); SetTimer( BOSS_TIME ); CutIn( YOUMU, BOSS_SPELLNAME, "", 0, 0, 0, 0); __Wait( BOSS_WAITTIME ); Concentration01( 90 ); __Wait( 120 ); //発狂モニタ TRank( BOSS_LIFE, BOSS_TIME ); //耐久力調整 TAdjustArmor( BOSS_LIFE ); //メイン攻撃 TAttack(); //メイン移動 TMove( 50, 70, -40, 40, 60, 120 ); yield; } // 攻撃 /////////////////////////////////////////////////////////// task TAttack { TMakeFalimiar01(); } task TMakeFalimiar01() { let v = 6; let script = "Familiar"; let colorIndex = 1; let wMin = 75; let wBase = 105; let wideMin = 20; let wideBase = 20; loop { if ( GetLife() > 0 ) { let wideLimit = wideMin + wideBase * m_dRank; CreateEnemyFromScript( script, GetX(), GetY(), v, 270 + rand( -wideLimit, wideLimit ), m_dRank * colorIndex ); colorIndex *= -1; } __Wait( wMin + wBase * ( 1 - m_dRank ) ); } } // 移動 /////////////////////////////////////////////////////////// // @param xmin x方向の最小絶対移動量(正値固定) // @param xmax x方向の最大絶対移動量(正値固定) // @param ymin y方向の最小相対移動量(負値で上向き移動幅となる) // @param ymax y方向の最大相対移動量(正値で下向き移動幅となる) // @param wmove 移動時間 // @param wwait 待機時間 task TMove( let xmin, let xmax, let ymin, let ymax, let wmove, let wwait ) { loop { moveToPlayer( prand_int( xmin, xmax ), prand_int( ymin, ymax ), wmove, GetClipMinX() + BOSS_MOVE_MARGIN_SIDE, BOSS_MOVE_MARGIN_TOP, GetClipMaxX() - BOSS_MOVE_MARGIN_SIDE, BOSS_MOVE_MARGIN_BOTTOM ); loop ( wmove ) { setAction( BOSS_ACT_MOVE ); yield; } SetSpeed( 0 ); setAction( BOSS_ACT_STAND ); __Wait( wwait ); } } // 耐久力調整 /////////////////////////////////////////////////////////// task TAdjustArmor( let maxLife ) { let startAdjustRate = 0.25; let minArmor = 4; let dmgRate = BOSS_DMGRATE[ length( BOSS_DMGRATE ) - 1 ]; if ( minArmor < dmgRate ) { loop { let lifeRate = GetEnemyLife() / maxLife; let maxArmor = dmgRate - minArmor; if ( lifeRate < startAdjustRate ) { SetDamageRate( dmgRate - maxArmor * ( startAdjustRate - lifeRate ) / startAdjustRate, BOSS_BOMBDMGRATE[ length( BOSS_BOMBDMGRATE ) - 1 ] ); } yield; } } } ////////////////////////////////////////////////////////////////////// // その他動作 ////////////////////////////////////////////////////////////////////// // 初期化 /////////////////////////////////////////////////////////// @Initialize { if ( _DEBUG ) { CreateDebugWindow(); } ascent ( let i in 0 .. length( IMAGE_LIST ) ) { LoadGraphic( IMAGE_LIST[ i ] ); } ascent ( let i in 0 .. length( SOUND_LIST ) ) { LoadSE( SOUND_LIST[ i ] ); } SetLife( BOSS_LIFE ); SetInvincibility( BOSS_INVINTIME ); SetColor( 255, 255, 255 ); ascent ( let i in 0 .. length( BOSS_DMGRATE ) ) { SetDamageRateDelay( BOSS_DMGRATE[ i ], BOSS_BOMBDMGRATE[ i ], BOSS_DMGRATE_TIME[ i ] ); } setAction( BOSS_ACT_MOVE ); SetMovePosition02( BOSS_HOME_X, BOSS_HOME_Y, BOSS_HOMETIME ); MainTask( 0, 0 ); } // メインループ /////////////////////////////////////////////////////////// @MainLoop { m_nCount++; SetCollisionA( GetX(), GetY(), BOSS_COLLISION_A ); SetCollisionB( GetX(), GetY(), BOSS_COLLISION_B ); if ( _DEBUG ) { OutputDebugString( 0, "m_nCount", m_nCount ); OutputDebugString( 1, "m_nAction", m_nAction ); OutputDebugString( 2, "m_dRank", m_dRank ); } yield; } // 描画ループ /////////////////////////////////////////////////////////// @DrawLoop { let xBase = BOSS_RECT[ m_nAction ][ 0 ] * BOSS_RECT_LENGTH; let yBase = BOSS_RECT[ m_nAction ][ 1 ] * BOSS_RECT_LENGTH; SetTexture( BOSS_IMAGE ); SetGraphicRect( xBase, yBase, xBase + BOSS_RECT_LENGTH ,yBase + BOSS_RECT_LENGTH ); DrawGraphic( GetX(), GetY() + 6 * sin( 3 * m_nCount ) ); } // 終了 /////////////////////////////////////////////////////////// @Finalize { let randBase = 96; let itemNumBase = 21; let itemWidth = min( randBase * BOSS_ITEM_NUM / itemNumBase, 2 * randBase ); loop( BOSS_ITEM_NUM ) { CreateItem( ITEM_SCORE, GetX() + rand( -itemWidth, itemWidth ), GetX() + rand( -itemWidth, itemWidth ) ); } ascent ( let i in 0 .. length( IMAGE_LIST ) ) { DeleteGraphic( IMAGE_LIST[ i ] ); } ascent ( let i in 0 .. length( SOUND_LIST ) ) { DeleteSE( SOUND_LIST[ i ] ); } } ////////////////////////////////////////////////////////////////////// // 汎用メソッド(自作) ////////////////////////////////////////////////////////////////////// // delayフレーム後ダメージレートを設定する task SetDamageRateDelay( let shotdmg, let bombdmg, let delay ) { loop ( delay ) { yield; } SetDamageRate( shotdmg, bombdmg ); } // wフレーム間タスクを待機させる // @param w 任意の値 function __Wait( let w ) { loop ( w ) { yield; } } // 最小値を返す // @param a 任意の値 // @param b 任意の値 function min( let a, let b ) { if ( a > b ) { return b; } return a; } // 最大値を返す // @param a 任意の値 // @param b 任意の値 function max( let a, let b ) { if ( a < b ) { return b; } return a; } // 発狂を監視する // @param maxLife ボスライフ最大値 // @param maxTime タイマー最大値 task TRank( let maxLife, let maxTime ) { loop { m_dRank = 1 - min( GetEnemyLife() / maxLife, GetTimer() / maxTime ); yield; } } // ボスの描画状態を設定する // @param act ボスの描画状態 function setAction( let act ) { alternative( act ) case ( BOSS_ACT_STAND, BOSS_ACT_SHOT ) { if ( GetSpeed() <= 0.1 ) { m_nAction = act; } } case ( BOSS_ACT_MOVE ) { if ( cos( GetAngle() ) > 0 && GetSpeed() > 0.1 ){ m_nAction = BOSS_ACT_MOVE_R; } else if ( GetSpeed() > 0.1 ) { m_nAction = BOSS_ACT_MOVE_L; } } case ( BOSS_ACT_MOVE_L, BOSS_ACT_MOVE_R ) { m_nAction = act; } others { m_nAction = BOSS_ACT_STAND; } } // なるべくプレイヤーの方向に移動 // @param xMove x方向の移動量(正の数) // @param yAdd y方向の移動量 // @param frame 移動に要するフレーム数 // @param left 以下、可動範囲 // @param top // @param right // @param bottom function moveToPlayer( xMove, yAdd, frame, left, top, right, bottom ) { let x; let y; if( GetPlayerX() < GetX() ) { // プレイヤーより右に敵がいれば、敵は左に動きます。 x = GetX() - xMove; // 但し、敵が可動領域の左端よりも左にいくようなら、右に動きます。 if( x < left ) { x = GetX() + xMove; if ( x > right ) { x = right; } } // 但し、現在可動限界にいるようなら、左に動きます。( 2006/11/15 追加 ) if ( ceil( GetX() ) == right ) { x = right - xMove; if ( x < left ) { x = left; } } } else { // さもなくば、敵は右に動きます。 x = GetX() + xMove; // 但し、敵が可動領域の右端よりも右にいくようなら、左に動きます。 if( right < x ) { x = GetX() - xMove; if ( x < left ) { x = left; } } // 但し、現在可動限界にいるようなら、右に動きます。( 2006/11/15 追加 ) if ( ceil( GetX() ) == left ) { x = left + xMove; if ( x > right ) { x = right; } } } // 可動領域の外に行く場合は、端っこで止めます。 y = GetY() + yAdd; if( y < top ) { y = top; } else if( bottom < y ) { y = bottom; } // 但し、現在可動限界にいるようなら、反対に動きます。( 2006/11/15 追加 ) if ( ceil( GetY() ) == top ) { y = top + absolute( yAdd ); } else if ( ceil( GetY() ) == bottom ) { y = bottom - absolute( yAdd ); } SetMovePosition02( x, y, frame ); } } ////////////////////////////////////////////////////////////////////// // 使い魔 ////////////////////////////////////////////////////////////////////// script_enemy Familiar { ////////////////////////////////////////////////////////////////////// // 設定定義 ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // 定数 ////////////////////////////////////////////////////////////////////// //ifdef _DEBUG let _DEBUG = true; // 使い魔画像 let FAMILIAR_IMAGE = "script\ExRumia\img\Enemy.png"; // 被弾判定半径 let FAMILIAR_COLLISION_A = 32; // 対当たり判定半径 let FAMILIAR_COLLISION_B = 16; // ライフ let FAMILIAR_LIFE = 100; ////////////////////////////////////////////////////////////////////// // メンバ変数 ////////////////////////////////////////////////////////////////////// // 使い魔ループカウンタ let m_nFamiliarCount = 0; // 発狂カウンタ( 0 -> 1 ) let m_dRank = absolute( GetArgument() ); // 弾種 let m_nColorIndex = [ 0, 1 ][ GetArgument() < 0 ]; // ワープしたかフラグ let m_bWarped = false; // 弾の分割数 let m_nBulletNum = 1; ////////////////////////////////////////////////////////////////////// // メイン動作 ////////////////////////////////////////////////////////////////////// // メインタスク /////////////////////////////////////////////////////////// // @param nArgv 任意の値 // @param nLevel 難易度 task MainTask( let nArgv, let nLevel ) { //メイン攻撃 TAttack(); //メイン移動 TMove(); yield; } // 攻撃 /////////////////////////////////////////////////////////// task TAttack { TShot01(); } // 射撃01 task TShot01() { let w0 = 60; let w1 = 5; let v = [ 1.5, 2.5 ]; let angle = [ 0, 0 ]; let way = [ 12, 13 ]; let type = [ WHITE21, RED21 ]; let delayMax = 30; let delay = delayMax; let da = 0; let limit = 64; let limit2 = ( GetClipMaxX() - GetClipMinX() ) / 2; let rIncMax = 3; let rInc = rIncMax * m_dRank; let r = 0; loop { if ( GetGapLength( GetX(), GetY(), GetPlayerX(), GetPlayerY() ) > limit && GetGapLength( GetX(), GetY(), GetCenterX(), GetClipMaxY() ) > limit2 && m_bWarped ) { RoundShot01( GetX(), GetY(), v[ m_nColorIndex ], angle[ m_nColorIndex ] + da, way[ m_nColorIndex ], type[ m_nColorIndex ], delay ); if ( r >= 1 ) { ascent ( let nBulletNum in 0 .. m_nBulletNum ) { ascent( let i in 0 .. 2 ) { RoundShot01( GetX() + r * ( nBulletNum + 1 ) / m_nBulletNum * cos( GetAngle() + 90 * [ -1, 1 ][ i ] ), GetY() + r * ( nBulletNum + 1 ) / m_nBulletNum * sin( GetAngle() + 90 * [ -1, 1 ][ i ] ), v[ m_nColorIndex ], angle[ m_nColorIndex ] + da, way[ m_nColorIndex ], type[ m_nColorIndex ], delay ); } } } delay -= w1 / 2; r += rInc; da += 360 / way[ m_nColorIndex ] / 2; } loop ( w1 ) { yield; } } } // 移動 /////////////////////////////////////////////////////////// task TMove() { TWarpMove( 1, true, false, true, true ); } // ワープ移動 // @param warp ワープ回数 // @param U 画面上端で反射 true/false // @param D 画面下端で反射 true/false // @param L 画面左端で反射 true/false // @param R 画面右端で反射 true/false task TWarpMove ( let warp, let U, let D, let L, let R ) { let EdgeTypeL = 0; let EdgeTypeR = 1; let EdgeTypeU = 2; let EdgeTypeD = 3; while( warp > 0 ) { let speed = GetSpeed(); let angle = GetAngle(); //左端ワープ if ( GetX() < GetClipMinX() && L ) { if ( GetY() > GetCenterY() ) { SetX( GetClipMaxX() ); SetY( GetCenterY() - ( GetY() - GetCenterY() ) ); } else { SetX( GetClipMaxX() ); SetY( GetCenterY() + ( GetCenterY() - GetY() ) ); } m_bWarped = true; warp--; } //右端ワープ if ( GetX() > GetClipMaxX() && R ) { if ( GetY() > GetCenterY() ) { SetX( GetClipMinX() ); SetY( GetCenterY() - ( GetY() - GetCenterY() ) ); } else { SetX( GetClipMinX() ); SetY( GetCenterY() + ( GetCenterY() - GetY() ) ); } m_bWarped = true; warp--; } //上端ワープ if ( GetY() < GetClipMinY() && U ) { if ( GetX() > GetCenterX() ) { SetX( GetCenterX() - ( GetX() - GetCenterX() ) ); SetY( GetClipMaxY() ); } else { SetX( GetCenterX() + ( GetCenterX() - GetX() ) ); SetY( GetClipMaxY() ); } m_bWarped = true; warp--; } //下端ワープ if ( GetY() > GetClipMaxY() && D ) { if ( GetX() > GetCenterX() ) { SetX( GetCenterX() - ( GetX() - GetCenterX() ) ); SetY( GetClipMinY() ); } else { SetX( GetCenterX() + ( GetCenterX() - GetX() ) ); SetY( GetClipMinY() ); } m_bWarped = true; warp--; } SetSpeed( speed ); SetAngle( angle ); yield; } } ////////////////////////////////////////////////////////////////////// // その他動作 ////////////////////////////////////////////////////////////////////// // 初期化 /////////////////////////////////////////////////////////// @Initialize { SetLife( FAMILIAR_LIFE ); SetColor( 255, 255, 255 ); SetDamageRate( 100, 0 ); MainTask( 0, 0 ); } // メインループ /////////////////////////////////////////////////////////// @MainLoop { m_nFamiliarCount++; yield; } // 描画ループ /////////////////////////////////////////////////////////// @DrawLoop { SetTexture( FAMILIAR_IMAGE ); SetGraphicRect( 0, 0, 32, 32 ); SetGraphicAngle( 0, 0, m_nFamiliarCount * 3 * [ -1, 1 ][ m_nColorIndex ] ); SetGraphicScale( 1.5, 1.5 ); SetAlpha( 160 ); DrawGraphic( GetX(), GetY() ); SetGraphicScale( 1, 1 ); SetGraphicAngle( 0, 0, 0 ); SetAlpha( 255 ); } // 終了 /////////////////////////////////////////////////////////// @Finalize { } ////////////////////////////////////////////////////////////////////// // 汎用メソッド ////////////////////////////////////////////////////////////////////// // 画面端から一定範囲内側にいるか否か // @param limit 画面端からの距離 function isInArea( let limit ) { if ( GetX() > GetClipMinX() + limit && GetX() < GetClipMaxX() - limit && GetY() > GetClipMinY() + limit && GetY() < GetClipMinY() - limit ) { return true; } return false; } ////////////////////////////////////////////////////////////////////// // 汎用メソッド(function_END) ////////////////////////////////////////////////////////////////////// // ============================================================================= // 異なる位置の点(x1,y1)(x2,y2)の距離を求める // ----------------------------------------------------------------------------- function GetGapLength( let x1, let y1, let x2, let y2 ){ return ((x2-x1)^2+(y2-y1)^2)^0.5; } // ----------------------------------------------------------------------------- // 全方向に弾を発射する // ※ 速度の変化がない場合(簡易版) // ----------------------------------------------------------------------------- function RoundShot01( let x, // 発射点x座標 let y, // 発射点y座標 let speed, // 速度 let angle, // 角度 let way, // 弾の数 let graphic, // 画像 let delay // 遅延フレーム数 ){ let wayAngle=360/way; ascent(let i in 0 .. way){ let shotAngle=angle+wayAngle*i; CreateShot01(x,y,speed,shotAngle,graphic,delay); } } }