Siv3Dで自作音ゲーを作った話

## 作ったもの
![タイトル画面](https://raw.githubusercontent.com/23tas9/ChronoBeat/refs/heads/master/ChronoBeat/App/resource/images/title.png)

https://github.com/23tas9/ChronoBeat

ダウンロード↓

https://github.com/23tas9/ChronoBeat/releases

## 工夫箇所
今回、ゲーム制作をする上で以下の箇所を工夫しました。

### 雰囲気を出す
どこか懐かしい雰囲気などを演出したいと考えたので、雰囲気を出すために、以下のようなことをしました。
```cpp
// ランダムな箇所に縦線
const Size ssize = Scene::Size();

int32 randomLineX = Random(ssize.x);
Scene::Rect().left().movedBy(randomLineX, 0).draw(Palette::Black.withAlpha(153));

// 回りが暗くなる
Circle{ Scene::Center(), ssize.x }
	.draw(ColorF{ .0, .0 }, Palette::Black.withAlpha(128));
```

また、タイトル画面のアニメーション時にレターボックスを使うことでおしゃれに演出しました
```cpp
double letterBoxHeight = (ssize.y - ssize.x / 2.35) / 2;	// 16:9 -> 2.35:1
int32 letterBoxFadeOutMS = 1000;

Stopwatch animationTimer;

void drawLetterBox() {
	const double now = animationTimer.msF();

	double letterboxRatio = (now <= letterBoxFadeOutMS) ?
		EaseInCubic(1.0 - Math::Min(1.0, now / letterBoxFadeOutMS)) :
		0.0;

	// レターボックス
	RectF{ 0, 0, ssize.x, letterBoxHeight * letterboxRatio }.draw(Palette::Black);
	RectF{
         Arg::bottomLeft = Vec2{ 0, ssize.y },
         ssize.x, LetterBoxHeight * letterboxRatio
    }.draw(Palette::Black);
}
```

### ノーツにポリモーフィズムを使う
ポリモーフィズムを利用することによって、以下のようにコードを簡潔に書けます。
```cpp
// ポリモーフィズムを使わない
Array<TapNote> m_tapNotes;
Array<HoldNote> m_holdNotes;
Array<StayNote> m_stayNotes;

void update(){
    for(auto& note : m_tapNotes) note.update();
    for(auto& note : m_holdNotes) note.update();
    for(auto& note : m_stayNotes) note.update();
}

// ポリモーフィズムを使う
Array<std::shared_ptr<Note>> m_notes;

void update(){
    for(auto& note : m_notes) note->update();
}
```

### INIファイルで設定を保持する
以下のような設定ファイルを使って、ユーザーが再設定をしなくてもよいようにしました。
```ini
[Volume]
master = 0.5
song = 0.5
effect = 1
bgm = 0.3
```

### 何かインプットがあったことを調べる
```cpp
// 何かのキーボード or マウスボタンが押されたらtrue
const bool actioned =
    Keyboard::GetAllInputs().any([](const Input& key) {
    	return key.down();
    }) || Mouse::GetAllInputs().any([](const Input& key) {
    	return key.down();
    });
```

### タイミングなどからノーツの位置を計算する
ノーツの位置を計算するには最低でも、
デフォルトのノーツの速度(`defaultNoteSpeed`)、現在時間(`now`)、ノーツタイミング(`timing`)
が必要になってきます。

`(timing - now)`で実際の判定するタイミングからどれだけの時間差があるかを調べ、それをノーツの速度でかけてあげるとノーツの位置を計算できます。
```cpp
double judgeLineY = 640.0;

double timing = 0.0;

int32 defaultNoteSpeed = 400;
double userSpeed = 1.0;
double noteSpeed = 1.0;

double timeDiff(double now){
    return timing - now;
}

double calcY(double now, double scroll = 1.0) {
	return judgeLineY - 
         (timeDiff(now) * defaultNoteSpeed) *
         (userSpeed * scroll * noteSpeed);
}
```

### メソッドチェーンで複数のシーンをまとめて登録する
```cpp
App manager;
manager
	.add<TitleScene>(SceneState::Title)
	.add<SelectScene>(SceneState::Select)
	.add<SettingScene>(SceneState::Setting)
	.add<GameScene>(SceneState::Game)
	.add<ResultScene>(SceneState::Result);
```

### シーンサイズをそのままでフルスクリーンにする
わたしは普段1728x1080(16:10)でゲームをしているため、一般的な16:9の解像度に合わせて開発する必要があったので、以下のコードで対応しました。
```cpp
Window::Resize(1920, 1080);
Scene::SetResizeMode(ResizeMode::Keep);

Window::SetFullscreen(true);
```

### エフェクトを使って、評価の表示
```cpp
struct JudgeView : IEffect {
	static constexpr double MaxLifetime = 0.3;
	static constexpr double FadeTime = 0.1;
	Transition fadeTransition{ SecondsF{ FadeTime }, SecondsF{ FadeTime } };

	static constexpr int32 FloatHeight = 60;

	Vec2 pos;

	String viewText;

	Color color;

	JudgeView(int32 x, const String& judgeName, const Color& judgeColor) :
         viewText{ judgeName }, color{ judgeColor }{
		pos = { x, judgeLineY - judgeViewOffset };
	}

	bool update(double t) override {
        // 進捗の計算
		const double progress = EaseOutExpo(t / MaxLifetime);

		fadeTransition.update(t <= MaxLifetime - FadeTime);

		const int32 alpha = 255 * fadeTransition.easeIn();

        // -FloatHeight * progressで徐々に浮いているように見せる
		font(viewText).drawAt(
			TextStyle::Outline(0.1, Palette::White),
			pos.movedBy(0, -FloatHeight * progress),
			color.withAlpha(alpha)
		);
  
		return (t < MaxLifetime);
	}
};
```

## 今後の展望

### 譜面エディタ

現在、譜面エディタは自作のものではなく、setchiさんのNoteEditorを使わせていただきました。

https://github.com/setchi/NoteEditor

ですが、ノーツの種類を選べない・ソフランに対応していないというところで、自分の作ろうとしている音ゲーにまだできていないので、自作の譜面エディタを作ろうと考えています。

### ランキング機能の実装
スコアの記録やランキング機能など実装できていないので、今後追加していきたいと考えています。

## 最後に

今回私はChronoBeatという音ゲーを制作しました。
今回の制作で「設計はしっかりしないとな...」と強く感じました。
(でもUMLクラス図とかよくわからないしめんどk🤛)

まだまだやれていないことが多すぎるので、今後も開発に注力していきますのでどうか生温かい目で見守ってください🙇‍♂️

また、せっかく作ったものなので誰かに遊んでもらって感想を聞きたい...

いや、**遊んでください!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!**

ダウンロードは以下URLから、`Assets` > `ChronoBeat-beta-0.0.1.zip`からダウンロードできます！

https://github.com/23tas9/ChronoBeat/releases