2 using System.Collections.Generic;
3 using System.Diagnostics;
9 class スクロール譜面 : FDK.Activity
12 public Action<SSTFormat.チップ種別, ヒット判定種別> ヒット判定文字列開始 = null;
13 public Action コンボリセット = null;
14 public Action コンボ加算 = null;
15 public Action<ヒット判定種別> ヒット判定数加算 = null;
16 public Action 背景動画再生開始 = null;
17 public Action<SSTFormat.チップ> チップヒット = null;
18 public Action ステージクリア = null;
19 public Func<double> リアルタイム演奏時刻sec = null;
20 public Func<double> 譜面スクロール速度の倍率 = null;
21 public Action FPSをカウント = null;
25 this.子リスト.Add( this.チップ画像 = new 画像( @"$(Static)\images\Chips.png" ) );
27 protected override void On活性化( デバイスリソース dr )
30 this.チップ画像の矩形リスト = new FDK.メディア.矩形リスト( @"$(Static)\images\Chips Rectangle List.xml" ); // ここで読み込む → 変更があったら反映される(デバッグ用途)。
31 var ユーザ = StrokeStyleT.ユーザ管理.現在選択されているユーザ;
32 this.ヒット判定を行うチップと対応するレーン = new Dictionary<SSTFormat.チップ種別, ヒットレーン種別>() {
33 { SSTFormat.チップ種別.LeftCrash, ヒットレーン種別.LeftCrash },
34 { SSTFormat.チップ種別.Ride, ( ユーザ.Rideは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
35 { SSTFormat.チップ種別.Ride_Cup, ( ユーザ.Rideは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
36 { SSTFormat.チップ種別.China, ( ユーザ.Chinaは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
37 { SSTFormat.チップ種別.Splash, ( ユーザ.Splashは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
38 { SSTFormat.チップ種別.HiHat_Open, ヒットレーン種別.HiHat },
39 { SSTFormat.チップ種別.HiHat_HalfOpen, ヒットレーン種別.HiHat },
40 { SSTFormat.チップ種別.HiHat_Close, ヒットレーン種別.HiHat },
41 //{ SSTFormat.チップ種別.HiHat_Foot, ヒット判定レーン種別.HiHat },
42 { SSTFormat.チップ種別.Snare, ヒットレーン種別.Snare },
43 { SSTFormat.チップ種別.Snare_OpenRim, ヒットレーン種別.Snare },
44 { SSTFormat.チップ種別.Snare_ClosedRim, ヒットレーン種別.Snare },
45 //{ SSTFormat.チップ種別.Snare_Ghost, ヒット判定レーン種別.Snare },
46 { SSTFormat.チップ種別.Bass, ヒットレーン種別.Bass },
47 { SSTFormat.チップ種別.Tom1, ヒットレーン種別.Tom1 },
48 { SSTFormat.チップ種別.Tom1_Rim, ヒットレーン種別.Tom1 },
49 { SSTFormat.チップ種別.Tom2, ヒットレーン種別.Tom2 },
50 { SSTFormat.チップ種別.Tom2_Rim, ヒットレーン種別.Tom2 },
51 { SSTFormat.チップ種別.Tom3, ヒットレーン種別.Tom2 },
52 { SSTFormat.チップ種別.Tom3_Rim, ヒットレーン種別.Tom2 },
53 { SSTFormat.チップ種別.RightCrash, ヒットレーン種別.RightCrash },
55 this.活性化した直後である = true;
57 protected override void On非活性化( デバイスリソース dr )
60 this.高頻度進行.状態 = FDK.同期.TriStateEvent.状態種別.OFF;
62 protected override void Onデバイス依存リソースの作成( デバイスリソース dr )
64 this.画面の高さdpx = dr.設計画面サイズdpx.Height;
66 public void 小節線拍線を進行描画する( デバイスリソース dr, double 現在の演奏時刻sec )
68 this.画面内に収まるチップをすべて進行描画する(
73 public void チップを進行描画する( デバイスリソース dr, double 現在の演奏時刻sec )
75 this.画面内に収まるチップをすべて進行描画する(
82 /// 音声の再生と演奏用入力は、描画処理とは並行して、高頻度に行う。
84 private void 高頻度進行処理タスクエントリ()
86 FDK.Log.Info( $"{FDK.Utilities.現在のメソッド名} --> 開始" );
88 while( this.高頻度進行.状態 == FDK.同期.TriStateEvent.状態種別.ON )
94 var 演奏スコア = StrokeStyleT.演奏スコア;
96 for( int i = this.描画開始チップ番号; ( 0 <= i ) && ( 演奏スコア.チップリスト.Count > i ); i++ )
98 var チップ = 演奏スコア.チップリスト[ i ];
101 double 描画時間差sec = this.リアルタイム演奏時刻sec() - チップ.描画時刻sec;
103 // 判定バーとチップの距離[dpx;符号付き;負数ならバー未達、0でバー直上、正数でバー通過]を算出する。
104 double 描画距離dpx = 演奏スコア.指定された時間secに対応する符号付きピクセル数を返す( this.譜面スクロール速度の倍率(), 描画時間差sec );
106 // チップのY座標を算出し、ループの終了判定を行う。
107 double y = 座標.判定バーの中央Y座標dpx + 描画距離dpx;
108 if( y < -40.0 ) // y が画面上端より上に出ていればそこでチップのループ描画は終了。-40dpx はチップが隠れるであろう適当なマージン。
111 #region " チップが描画開始チップであり、かつ、そのY座標が画面下端を超えたなら、描画開始チップ番号を更新する。"
113 if( ( this.描画開始チップ番号 == i ) && ( this.画面の高さdpx + 40.0 < y ) ) // +40 dpx はチップが(以下同上
116 if( 演奏スコア.チップリスト.Count <= this.描画開始チップ番号 )
119 this.描画開始チップ番号 = -1; // 演奏終了。
124 #region " AutoPlay チップなら、自動ヒット判定を行う。"
126 if( ( チップ.ヒットされていない ) && // チップが未ヒット、かつ、
127 ( StrokeStyleT.ユーザ管理.現在選択されているユーザ.チップが自動演奏である( チップ.チップ種別 ) ) && // チップの AutoPlay が ON、かつ、
128 ( 0 <= 描画距離dpx ) ) // バーを通過した。
132 if( チップ.チップ種別 == SSTFormat.チップ種別.背景動画 )
135 this.背景動画再生開始?.Invoke();
140 this.チップヒット?.Invoke( チップ );
141 this.コンボ加算?.Invoke();
142 this.ヒット判定数加算?.Invoke( ヒット判定種別.AUTO );
143 this.ヒット判定文字列開始?.Invoke( チップ.チップ種別, ヒット判定種別.AUTO );
148 #region " Miss 判定を行う。"
150 if( ( チップ.ヒットされていない ) && // チップが未ヒット、かつ、
151 ( this.ヒット判定を行うチップと対応するレーン.ContainsKey( チップ.チップ種別 ) ) && // チップがヒット判定対象である、かつ、
152 ( 描画時間差sec > StrokeStyleT.ユーザ管理.現在選択されているユーザ.ヒット範囲sec.Poor ) ) // 描画時間差が Poor 範囲を超えている。
156 this.コンボリセット?.Invoke();
157 this.ヒット判定文字列開始?.Invoke( チップ.チップ種別, ヒット判定種別.MISS );
165 System.Threading.Thread.Sleep( 1 );
168 FDK.Log.Info( $"{FDK.Utilities.現在のメソッド名} <-- 終了" );
170 protected void 画面内に収まるチップをすべて進行描画する( デバイスリソース dr, Action<デバイスリソース, SSTFormat.チップ, float> 描画アクション, double 現在の演奏時刻sec )
174 var 演奏スコア = StrokeStyleT.演奏スコア;
175 Trace.Assert( null != 演奏スコア, "[バグあり] 演奏スコアが null です。" );
179 if( this.活性化した直後である )
181 this.活性化した直後である = false;
182 this.描画開始チップ番号 = 0; // -1 → 0; 演奏開始。
183 this.チップアニメ.開始する( 最初の値: 0, 最後の値: 48, 値をひとつ増加させるのにかける時間ms: 10 );
186 this.高頻度進行 = new FDK.同期.TriStateEvent( FDK.同期.TriStateEvent.状態種別.ON );
187 System.Threading.Tasks.Task.Run( () => {
194 for( int i = this.描画開始チップ番号; ( 0 <= i ) && ( 演奏スコア.チップリスト.Count > i ); i++ )
196 var チップ = 演奏スコア.チップリスト[ i ];
199 double 描画時間差sec = 現在の演奏時刻sec - チップ.描画時刻sec;
201 // 判定バーとチップの距離[dpx;符号付き;負数ならバー未達、0でバー直上、正数でバー通過]を算出する。
202 double 描画距離dpx = 演奏スコア.指定された時間secに対応する符号付きピクセル数を返す( this.譜面スクロール速度の倍率(), 描画時間差sec );
204 // チップのY座標を算出し、ループの終了判定を行う。
205 double y = 座標.判定バーの中央Y座標dpx + 描画距離dpx;
206 if( y < -40.0 ) // y が画面上端より上に出ていればそこでチップのループ描画は終了。-40dpx はチップが隠れるであろう適当なマージン。
211 描画アクション( dr, チップ, (float) y );
215 private void 小節線拍線を1つ描画する( デバイスリソース dr, SSTFormat.チップ chip, float Ydpx )
217 if( null == this.チップ画像 )
220 this.チップ画像.加算合成 = false;
224 case SSTFormat.チップ種別.小節線:
225 #region " 小節線を1本表示する。"
228 float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ SSTFormat.レーン種別.LeftCrash ] - 1f;
229 float 上位置dpx = Ydpx - 1f;
230 var 画像範囲orNull = this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.小節線 ) ];
231 if( null != 画像範囲orNull )
233 var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
234 this.チップ画像.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲 );
235 this.チップ画像.描画する( dr, 左位置dpx + 画像範囲.Width, 上位置dpx, 転送元矩形dpx: 画像範囲 );
242 case SSTFormat.チップ種別.拍線:
243 #region " 拍線を1本表示する。"
246 float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ SSTFormat.レーン種別.LeftCrash ] - 1f;
248 var 画像範囲orNull = this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.拍線 ) ];
249 if( null != 画像範囲orNull )
251 var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
252 this.チップ画像.描画する( dr, 左位置dpx: 左位置dpx, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
253 this.チップ画像.描画する( dr, 左位置dpx: 左位置dpx + 画像範囲.Width, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
261 private void チップを1つ描画する( デバイスリソース dr, SSTFormat.チップ chip, float Ydpx )
263 if( null == this.チップ画像 )
266 this.チップ画像.加算合成 = false;
267 float 音量0to1 = chip.音量 * 0.25f; // 1~4 → 0.25~1.00
271 case SSTFormat.チップ種別.LeftCrash:
272 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.LeftCrash ) ], Ydpx, 音量0to1 );
275 case SSTFormat.チップ種別.HiHat_Close:
276 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
279 case SSTFormat.チップ種別.HiHat_HalfOpen:
280 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
281 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_HalfOpen ) ], Ydpx, 1.0f ); // 音量は反映しない
284 case SSTFormat.チップ種別.HiHat_Open:
285 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
286 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Open ) ], Ydpx, 1.0f ); // 音量は反映しない
289 case SSTFormat.チップ種別.HiHat_Foot:
290 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Foot ) ], Ydpx, 1.0f ); // 音量は反映しない
293 case SSTFormat.チップ種別.Snare:
294 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare ) ], SSTFormat.レーン種別.Snare, Ydpx, 音量0to1 );
297 case SSTFormat.チップ種別.Snare_ClosedRim:
298 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_ClosedRim ) ], Ydpx, 1.0f ); // 音量は反映しない
301 case SSTFormat.チップ種別.Snare_OpenRim:
302 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_OpenRim ) ], Ydpx, 音量0to1 );
303 //this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare ) ], y, 音量0to1 ); → ないほうが見た目がいいかも。
306 case SSTFormat.チップ種別.Snare_Ghost:
307 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_Ghost ) ], Ydpx, 1.0f ); // 音量は反映しない
310 case SSTFormat.チップ種別.Bass:
311 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Bass ) ], SSTFormat.レーン種別.Bass, Ydpx, 音量0to1 );
314 case SSTFormat.チップ種別.Tom1:
315 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom1 ) ], SSTFormat.レーン種別.Tom1, Ydpx, 音量0to1 );
318 case SSTFormat.チップ種別.Tom1_Rim:
319 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom1, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom1_Rim ) ], Ydpx, 1.0f ); // 音量は反映しない
322 case SSTFormat.チップ種別.Tom2:
323 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom2 ) ], SSTFormat.レーン種別.Tom2, Ydpx, 音量0to1 );
326 case SSTFormat.チップ種別.Tom2_Rim:
327 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom2, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom2_Rim ) ], Ydpx, 1.0f ); // 音量は反映しない
330 case SSTFormat.チップ種別.Tom3:
331 this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom3 ) ], SSTFormat.レーン種別.Tom3, Ydpx, 音量0to1 );
334 case SSTFormat.チップ種別.Tom3_Rim:
335 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom3, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom3_Rim ) ], Ydpx, 1.0f ); // 音量は反映しない
338 case SSTFormat.チップ種別.RightCrash:
339 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.RightCrash ) ], Ydpx, 音量0to1 );
342 case SSTFormat.チップ種別.China:
343 if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Chinaは左 )
344 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftChina" ], Ydpx, 音量0to1 );
346 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightChina" ], Ydpx, 音量0to1 );
349 case SSTFormat.チップ種別.Ride:
350 if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Rideは左 )
351 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftRide" ], Ydpx, 音量0to1 );
353 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightRide" ], Ydpx, 音量0to1 );
356 case SSTFormat.チップ種別.Ride_Cup:
357 if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Rideは左 )
358 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftRide_Cup" ], Ydpx, 音量0to1 );
360 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightRide_Cup" ], Ydpx, 音量0to1 );
363 case SSTFormat.チップ種別.Splash:
364 if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Splashは左 )
365 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftSplash" ], Ydpx, 音量0to1 );
367 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightSplash" ], Ydpx, 音量0to1 );
371 private void アニメチップを1つ描画する( デバイスリソース dr, SharpDX.RectangleF? 画像範囲orNull, SSTFormat.レーン種別 elane, float Ydpx, float 音量0to1 )
373 if( null == 画像範囲orNull )
375 var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
377 float チップ1枚の高さdpx = 18f;
378 画像範囲.Offset( 0f, this.チップアニメ.現在値 * 15f ); // 下端3dpxは下のチップと共有する前提のデザインなので、18f-3f = 15f。
379 画像範囲.Height = チップ1枚の高さdpx;
380 float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ elane ] - 画像範囲.Width / 2f;
381 float 上位置dpx = Ydpx - ( チップ1枚の高さdpx / 2f ) * 音量0to1;
383 this.チップ画像.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲, Y方向拡大率: 音量0to1 );
385 private void 単画チップを1つ描画する( デバイスリソース dr, SSTFormat.レーン種別 eLane, SharpDX.RectangleF? 元矩形dpx, float 上位置dpx, float 音量0to1 )
390 var 画像範囲dpx = (SharpDX.RectangleF) 元矩形dpx;
393 左位置dpx: 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ eLane ] - ( 画像範囲dpx.Width / 2f ),
394 上位置dpx: 上位置dpx - ( ( 画像範囲dpx.Height / 2f ) * 音量0to1 ),
399 protected bool 活性化した直後である = false;
401 /// 演奏スコア.listチップ[] のうち、描画を始めるチップのインデックス番号を示す。
402 /// 演奏開始直後は 0 で始まり、対象番号のチップが描画範囲を流れ去るたびに +1 される。
405 protected int 描画開始チップ番号 = -1;
406 protected readonly FDK.メディア.画像 チップ画像;
407 protected FDK.メディア.矩形リスト チップ画像の矩形リスト = null;
408 protected Dictionary<SSTFormat.チップ種別, ヒットレーン種別> ヒット判定を行うチップと対応するレーン = null;
409 protected readonly FDK.カウンタ.単純増加後反復カウンタ チップアニメ = new FDK.カウンタ.単純増加後反復カウンタ();
410 protected readonly Dictionary<SSTFormat.レーン種別, float> レーン種別toレーンフレーム左端からの相対X位置dpx = new Dictionary<SSTFormat.レーン種別, float>() {
411 { SSTFormat.レーン種別.LeftCrash, +36f },
412 { SSTFormat.レーン種別.HiHat, +105f },
413 { SSTFormat.レーン種別.Foot, +145f },
414 { SSTFormat.レーン種別.Snare, +214f },
415 { SSTFormat.レーン種別.Tom1, +310f },
416 { SSTFormat.レーン種別.Bass, +381f },
417 { SSTFormat.レーン種別.Tom2, +448f },
418 { SSTFormat.レーン種別.Tom3, +544f },
419 { SSTFormat.レーン種別.RightCrash, +632f },
422 private float 画面の高さdpx = 0f; // 進行スレッドからはデバイスリソースを参照しないので、ここにキャッシュしておく。
423 private readonly object スレッド排他 = new object();
424 private FDK.同期.TriStateEvent 高頻度進行 = null;