2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics;
7 using FDK; // for SystemStringExtensions
13 /// StrokeStyleT.演奏スコア(ビュアーモードなら null でも可)
14 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)
15 /// StrokeStyleT.Wasapiデバイス.AudioClock
16 /// StrokeStyleT.ビュアーモードである
17 /// StrokeStyleT.最後に取得したビュアーメッセージ
21 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
22 /// this.現在のフェーズ ← クリアor失敗
25 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
26 /// this.現在のフェーズ ← キャンセル
30 public ConcurrentDictionary<ヒット判定種別, int> ヒットした回数 { get; } = new ConcurrentDictionary<ヒット判定種別, int>();
39 public FDK.同期.RWLock<フェーズ> 現在のフェーズ { get; } = new FDK.同期.RWLock<フェーズ>( フェーズ.初期状態 );
43 this.子リスト.Add( this.コンボ = new コンボ() );
44 this.子リスト.Add( this.レーンフレーム = new レーンフレーム() );
45 this.子リスト.Add( this.スクロール譜面 = new スクロール譜面() );
46 this.子リスト.Add( this.ヒット判定文字列 = new ヒット判定文字列() );
47 this.子リスト.Add( this.回転羽 = new 回転羽( 最大同時発火数: 32 ) );
48 this.子リスト.Add( this.ドラムサウンド = new ドラムサウンド() );
49 this.子リスト.Add( this.ドラムセット = new ドラムセット() );
50 this.子リスト.Add( this.判定バー = new 画像( @"$(Static)\images\判定バー.png" ) );
51 this.子リスト.Add( this.ステージ台 = new 画像( @"$(Static)\images\ステージ台.png" ) );
52 this.子リスト.Add( this.FPS画像 = new 文字列画像() );
54 // 子Activity の外部依存 Action の実体を定義する。
55 this.スクロール譜面.ヒット判定文字列開始 = ( chipType, hitType ) => {
56 this.ヒット判定文字列.表示開始( chipType, hitType );
58 this.スクロール譜面.コンボリセット = () => {
59 this.コンボ.COMBO値.Value = 0;
61 this.スクロール譜面.コンボ加算 = () => {
62 this.コンボ.COMBO値.Value++;
64 this.スクロール譜面.ヒット判定数加算 = ( hitType ) => {
65 this.ヒットした回数[ hitType ]++;
67 this.スクロール譜面.背景動画の長さsec = () => {
68 return ( null != this.BGM ) ? this.BGM.長さsec : 0.0;
70 this.スクロール譜面.背景動画再生開始 = ( 開始位置sec ) => {
71 this.背景動画.再生を開始する( 開始位置sec );
72 this.背景動画開始済み.Value = true;
73 this.BGM?.再生を開始する( 開始位置sec );
74 this.BGM再生開始済み = true;
76 this.スクロール譜面.チップヒット = ( chip ) => {
77 this.回転羽.発火する( chip.チップ種別 );
78 if( this.Autoチップのドラム音を再生する )
79 this.ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
81 this.スクロール譜面.ステージクリア = () => {
82 this.現在のフェーズ.Value = フェーズ.クリアor失敗;
84 this.スクロール譜面.リアルタイム演奏時刻sec = () => {
85 return this.現在の演奏時刻secを返す();
87 this.スクロール譜面.譜面スクロール速度の倍率 = () => {
88 return this.現在進行描画中の譜面スクロール速度の倍率.Value;
90 this.スクロール譜面.FPSをカウント = () => {
91 this.FPS.FPSをカウントする();
94 protected override void On活性化( デバイスリソース dr )
96 FDK.Log.Info( "演奏ステージを開始します。" + ( StrokeStyleT.ビュアーモードである ? "(ViewerMode)" : "" ) );
98 #region " 動画ファイルパスが有効なら、背景動画とBGMを初期化する。"
100 if( ( null != StrokeStyleT.演奏スコア ) && ( StrokeStyleT.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
103 this.子リスト.Add( this.背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
105 // 動画から BGM を作成してミキサーに追加。
106 this.BGM = new FDK.メディア.サウンド.WASAPI.Sound();
107 this.BGM.ファイルから作成する( StrokeStyleT.演奏スコア.背景動画ファイル名 );
108 StrokeStyleT.Wasapiデバイス.サウンドをミキサーに追加する( this.BGM ); // 作成に失敗した Sound を追加しても鳴らないだけなので、ノーチェックで大丈夫。
118 this.演奏開始時刻sec = 0.0;
119 this.現在進行描画中の譜面スクロール速度の倍率.Value = StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率;
120 this.BGM再生開始済み = false;
121 this.背景動画開始済み.Value = false;
122 this.レーンフレーム.左端位置dpx = 400f;
123 this.レーンフレーム.高さdpx = dr.設計画面サイズdpx.Height;
124 this.ヒットした回数.Clear();
125 foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
126 this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
129 if( StrokeStyleT.ビュアーモードである )
131 // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
132 this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
137 this.現在のフェーズ.Value = フェーズ.演奏中;
140 this.活性化した直後である = true;
142 protected override void On非活性化( デバイスリソース dr )
144 FDK.Log.Info( "演奏ステージを終了します。" );
146 //this.BGMを解放する(); → ここではまだ解放しない。結果ステージが終わるときに、外部から解放する。
148 if( null != this.背景動画 )
149 this.子リスト.Remove( this.背景動画 ); // 子リストから削除
151 public override void 進行描画する( デバイスリソース dr )
157 if( this.活性化した直後である )
159 this.活性化した直後である = false;
160 this.FPS = new FDK.カウンタ.FPS();
162 double 演奏開始位置の先頭からの時間sec = 0.0;
163 var msg = StrokeStyleT.最後に取得したビュアーメッセージ;
166 演奏開始位置の先頭からの時間sec = this.スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( msg.演奏開始小節番号 );
167 this.Autoチップのドラム音を再生する = msg.ドラムチップ発声;
170 this.サウンドタイマ.現在のデバイス位置secを取得する( StrokeStyleT.Wasapiデバイス.AudioClock ) - // '+' じゃないので注意!
176 double 演奏時刻sec = this.現在の演奏時刻secを返す();
178 #region " 譜面スクロール速度が変化している場合の追い付き進行。"
180 double 倍率 = this.現在進行描画中の譜面スクロール速度の倍率.Value;
181 if( 倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
183 // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
184 this.現在進行描画中の譜面スクロール速度の倍率.Value =
185 Math.Min( 倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
187 else if( 倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
190 this.現在進行描画中の譜面スクロール速度の倍率.Value =
191 Math.Max( 倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
195 #region " 背景動画とBGMの開始/進行描画を行う。"
197 if( this.背景動画開始済み.Value )
199 // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
200 this.背景動画?.進行描画する( dr, new SharpDX.RectangleF( 0f, 0f, dr.設計画面サイズdpx.Width, dr.設計画面サイズdpx.Height ) );
202 // 動画が重たいかもしれないので、演奏時刻をここで再度取得する。
203 演奏時刻sec = this.現在の演奏時刻secを返す();
205 // 背景動画が再生されているのにBGMがまだ再生されていないなら、再生を開始する。
206 if( false == this.BGM再生開始済み )
209 this.BGM再生開始済み = true;
215 this.ステージ台.描画する( dr, 0.0f, 0.0f );
216 this.レーンフレーム.進行描画する( dr );
217 this.コンボ.進行描画する( dr );
218 this.ヒット判定文字列.進行描画する( dr );
219 this.スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec );
220 this.判定バー.描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
221 this.ドラムセット.進行描画する( dr );
222 this.スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
223 this.回転羽.進行描画する( dr );
224 this.FPS.VPSをカウントする();
225 this.FPS画像.表示文字列 = $"VPS: {this.FPS.現在のVPS.ToString()} / FPS: {this.FPS.現在のFPS.ToString()}";
226 this.FPS画像.進行描画する( dr, 0f, 0f );
230 StrokeStyleT.すべての入力デバイスをポーリングする();
233 if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) &&
234 StrokeStyleT.ビュアーモードではない ) // ビュアーモードでは無効。
237 this.現在のフェーズ.Value = フェーズ.キャンセル;
239 // 上矢印押下 → 譜面スクロール速度の倍率を +0.5
240 else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Up ) )
242 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
243 Math.Min( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 + 0.5, 8.0 ); // 最大 8.0
245 // 下矢印押下 → 譜面スクロール速度の倍率を -0.5
246 else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Down ) )
248 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
249 Math.Max( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 - 0.5, 0.5 ); // 最小 0.5
252 public void 演奏を停止する()
254 this.スクロール譜面?.演奏を停止する();
255 this.背景動画開始済み.Value = false;
258 public void BGMを解放する()
260 if( null != this.BGM ) // 背景動画がなければ BGM も null である
262 StrokeStyleT.Wasapiデバイス.サウンドをミキサーから削除する( this.BGM );
263 FDK.Utilities.解放する( ref this.BGM );
267 protected bool 活性化した直後である = false;
268 protected FDK.同期.RWLock<bool> 背景動画開始済み = new FDK.同期.RWLock<bool>( false );
269 protected bool BGM再生開始済み = false;
270 protected FDK.同期.RWLock<double> 現在進行描画中の譜面スクロール速度の倍率 = new FDK.同期.RWLock<double>( 0.0 );
271 protected double 演奏開始時刻sec = 0.0;
272 protected bool Autoチップのドラム音を再生する = true;
273 protected readonly FDK.メディア.サウンド.WASAPI.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPI.SoundTimer();
274 protected readonly SST.ステージ.演奏.コンボ コンボ;
275 protected readonly SST.ステージ.演奏.レーンフレーム レーンフレーム;
276 protected readonly SST.ステージ.演奏.スクロール譜面 スクロール譜面;
277 protected readonly SST.ステージ.演奏.ヒット判定文字列 ヒット判定文字列;
278 protected readonly SST.ステージ.演奏.回転羽 回転羽 = new 回転羽( 32 );
279 protected readonly SST.ステージ.演奏.ドラムサウンド ドラムサウンド;
280 protected readonly SST.ステージ.ドラムセット ドラムセット;
281 protected readonly FDK.メディア.画像 判定バー;
282 protected readonly FDK.メディア.画像 ステージ台;
283 protected readonly FDK.メディア.文字列画像 FPS画像;
285 /// 解放は、演奏ステージクラスの非活性化後に、外部から行われる。
286 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを解放する"/>
288 protected FDK.メディア.サウンド.WASAPI.Sound BGM = null;
289 protected FDK.カウンタ.FPS FPS = null;
291 /// 動的子Activity。背景動画を再生しない場合は null のまま。
293 protected FDK.メディア.動画 背景動画 = null;
295 private double 現在の演奏時刻secを返す()
297 return this.サウンドタイマ.現在のデバイス位置secを取得する( StrokeStyleT.Wasapiデバイス.AudioClock ) - this.演奏開始時刻sec;