このリポジトリには、書籍「Unityで作るリズムゲーム」(以下、書籍)第1章のサンプルプロジェクトを格納しています。 書籍のダウンロード版はこちら( https://ecml.booth.pm/items/1739359 )
Unity2019.2.17f1にて制作しました。
緑のボタン「Clone or download」を押したら表示される 「Download zip」を押して、zipファイルをダウンロードした後適当な場所に解凍してください。
解凍したフォルダ名は「Rhythm-Game-Tutrial-master」(もしくはそれに類するもの)になるはずなので、 Unityで「Rhythm-Game-Tutrial-master」フォルダを開いてください。
/Assets/Scripts 以下に、書籍で使っているUnity C#スクリプトを配置しています。
まず、Assets/Scenes/SelectScene (選曲画面)を開いてください。 Unityエディタの画面上部にある再生アイコンをクリックするとゲームが開始します。
選曲画面では、以下の操作方法が適用されます。
←→: 譜面の選択
↑↓: ハイスピード(演奏画面の譜面スクロール速度)の選択
Space: 譜面の決定(GameSceneに遷移)
GameScene(演奏画面)では、以下の操作方法が適用されます。
Space: 譜面の再生
Esc: 選曲画面に戻る(曲が終了したら押してください)
C: 最も左のレーンをタップ
V: 左から2番目のレーンをタップ
B: 左から3番目のレーンをタップ
N: 左から4番目のレーンをタップ
M: 左から5番目のレーンをタップ
Copyright (C) 2019 yuyu0127
このリポジトリ及びreleaseにアップロードされている全てのファイル(以下、「コンテンツ」)を、著作権法で認められている権利者の許諾を得ずに、個人的な範囲を超える使用目的で複製すること及びネットワーク等を通じて「コンテンツ」を送信できる状態にすることを禁じます。 「コンテンツ」の利用は、必ずご自身の責任と判断によって行ってください。「コンテンツ」を使用した結果生じたいかなる直接的・間接的損害も、長崎大学マルチメディア研究会、yuyu0127を含むプログラム開発者および「コンテンツ」の制作に関わったすべての個人と団体は、いっさいその責任を負いかねます。
/Beatmaps/blackSteps.ogg: ゲッポウ( https://mobile.twitter.com/guepmoo )
/Beatmaps/nightShadow.ogg: 鷹ピー( https://mobile.twitter.com/takapi130 )
/Beatmaps/nightShadow-hyper.bms, nightShadow-normal.bms, blackSteps-hyper.bms, blackSteps-normal.bms: Boltz( https://github.com/septem48 )
上記以外のスクリプトやアセット: yuyu0127( https://github.com/yuyu0127 )
12/31にC97で頒布・それ以降にboothなどで頒布した紙の書籍版の正誤表を以下に記載します。
boothにて頒布している最新の電子書籍版は、以下の項目についての修正が施されたものになります。
なお、この修正を施さなかった場合下記の不具合が起こる恐れがあります。
- 1小節の中に直接指定型のテンポ変化(BPMが整数のとき使用される)とインデックス指定型のテンポ変化(BPMが非整数のとき使用される)が混在している譜面が上手く再生されない/判定がおかしくなる
- テキストエディタ等でテンポ変化を記述し、それが時系列順に並んでいない場合、譜面が上手く再生されない/判定がおかしくなる
- ビルド後に起動したとき、UIの文字が画面外にはみ出る
- 判定の情報が上手く取得できない・値がおかしくなる
(誤) (コンストラクタのみ抜粋)
// (コンストラクタ) BMSファイルを読み込む
public BmsLoader(string filePath)
{
// BMSファイルを読み込み、各行を配列に保持
var lines = File.ReadAllLines(filePath, Encoding.UTF8);
// ヘッダー読み込み
foreach (var line in lines)
{
LoadHeaderLine(line);
}
// 基本BPMをbeat0の時のBPMとして設定
tempoChanges.Add(
new TempoChange(0, Convert.ToSingle(headerData["BPM"]))
);
// メインデータ読み込み
foreach (var line in lines)
{
LoadMainDataLine(line);
}
}
(正) (コンストラクタのみ抜粋)
// (コンストラクタ) BMSファイルを読み込む
public BmsLoader(string filePath)
{
// BMSファイルを読み込み、各行を配列に保持
var lines = File.ReadAllLines(filePath, Encoding.UTF8);
// ヘッダー読み込み
foreach (var line in lines)
{
LoadHeaderLine(line);
}
// 基本BPMをbeat0の時のBPMとして設定
tempoChanges.Add(
new TempoChange(0, Convert.ToSingle(headerData["BPM"]))
);
// メインデータ読み込み
foreach (var line in lines)
{
LoadMainDataLine(line);
}
// テンポ変化データを時系列順に並び替え この行を追加
tempoChanges = tempoChanges.OrderBy(x => x.beat).ToList(); // この行を追加
}
(誤)
public class NoteProperty
{
public float beatBegin; // 始点が判定ラインと重なるbeat
public float beatEnd; // 終点が判定ラインと重なるbeat
public float secBegin; // 始点が判定ラインと重なるsec
public float secEnd; // 終点が判定ラインと重なるsec
public int lane; // レーン
public NoteType noteType; // ノーツ種別
// コンストラクタ
public NoteProperty(
float beatBegin, float beatEnd,
float secBegin, float secEnd, // この行を追加
int lane, NoteType noteType
)
{
this.beatBegin = beatBegin;
this.beatEnd = beatEnd;
this.secBegin = secBegin; // この行を削除
this.secEnd = secEnd; // この行を削除
this.lane = lane;
this.noteType = noteType;
}
}
public enum NoteType
{
Single, // シングルノーツ
Long // ロングノーツ
}
(正)
public class NoteProperty
{
public float beatBegin; // 始点が判定ラインと重なるbeat
public float beatEnd; // 終点が判定ラインと重なるbeat
public float secBegin; // 始点が判定ラインと重なるsec
public float secEnd; // 終点が判定ラインと重なるsec
public int lane; // レーン
public NoteType noteType; // ノーツ種別
// コンストラクタ
public NoteProperty(
float beatBegin, float beatEnd,
int lane, NoteType noteType
)
{
this.beatBegin = beatBegin;
this.beatEnd = beatEnd;
this.lane = lane;
this.noteType = noteType;
}
}
public enum NoteType
{
Single, // シングルノーツ
Long // ロングノーツ
}
(誤)
NoteProperty
の変更に伴い、BMSを読み込む際の処理も変更する必要があります。
譜面を読み込んでノーツのデータを追加する際、ノーツが降ってくる秒数も計算しておき、
その値をコンストラクタに渡すようにします。
// ノーツの場合
if (dataType == DataType.SingleNote ||
dataType == DataType.LongNote)
{
// レーン番号(チャンネル番号の一の位で決まる)
int lane = LanePairs[channel[1]];
// チャンネル番号の十の位でノーツの種類を判定
……(ここまで同様につき省略)……
switch (dataType)
{
case DataType.SingleNote: // シングルノーツ
// beatを秒に変換
var sec = Beatmap.ToSec(beat, tempoChanges);
// シングルノーツとしてnotePropertiesに追加
noteProperties.Add(new NoteProperty(
beat, beat, sec, sec, lane, NoteType.Single
));
break;
case DataType.LongNote: // ロングノーツ
// このレーンのロングノーツがOFFの時
if (longNoteBeginBuffers[lane] < 0)
{
// ロングノーツがONになったことにし、
// ロングノーツの始点のbeatを保持
longNoteBeginBuffers[lane] = beat;
}
// このレーンのロングノーツがONの時
else
{
// 始点のbeat情報はバッファから読み込み
var beatBegin = longNoteBeginBuffers[lane];
var secBegin = Beatmap.ToSec(beatBegin, tempoChanges);
// 終点
var beatEnd = beat;
var secEnd = Beatmap.ToSec(beatEnd, tempoChanges);
// ロングノーツをnotePropertiesに追加
noteProperties.Add(new NoteProperty(
beatBegin, beatEnd, secBegin, secEnd, lane, NoteType.Long
));
// バッファを適当な負の値に設定
……(以下同様につき省略)……
(正)
NoteProperty
の変更に伴い、BMSを読み込む際の処理も変更する必要があります。
譜面を読み込んでノーツのデータを取得した後、ノーツが降ってくる秒数を計算し、値を設定します。
// 各レーンで最後にロングノーツがONになったbeat
// (OFFの時は負の値にしておく)
private float[] longNoteBeginBuffers = new float[] {-1, -1, -1, -1, -1 };
……(ここまで同様につき省略)……
// (コンストラクタ) BMSファイルを読み込む
public BmsLoader(string filePath)
{
// BMSファイルを読み込み、各行を配列に保持
var lines = File.ReadAllLines(filePath, Encoding.UTF8);
// ヘッダー読み込み
foreach (var line in lines)
{
LoadHeaderLine(line);
}
// 基本BPMをbeat0の時のBPMとして設定
tempoChanges.Add(
new TempoChange(0, Convert.ToSingle(headerData["BPM"]))
);
// メインデータ読み込み
foreach (var line in lines)
{
LoadMainDataLine(line);
}
// テンポ変化データを時系列順に並び替え
tempoChanges = tempoChanges.OrderBy(x => x.beat).ToList();
// 各ノーツに対して、secの設定を行う
foreach (var noteProperty in noteProperties)
{
noteProperty.secBegin = Beatmap.ToSec(noteProperty.beatBegin, tempoChanges);
noteProperty.secEnd = Beatmap.ToSec(noteProperty.beatEnd, tempoChanges);
}
}
……(以下同様につき省略)……
(誤)
- Hierarchyビューで右クリックし、UI→Textより2つのTextを作成します。
- 作成したTextの名称を、1つは「Text Label」、もう1つは「Text Value」に変更します。
- Canvasにアタッチされた
Canvas Scaler
コンポーネントのUI Scale Mode
をScale With Screen Size
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのReference Resolution
をX: 200, Y: 300
に設定します。 - Text Labelの
RectTransform
を以下のように設定します。 (以下省略)
(正)
- Hierarchyビューで右クリックし、UI→Textより2つのTextを作成します。
- 作成したTextの名称を、1つは「Text Label」、もう1つは「Text Value」に変更します。
- Canvasにアタッチされた
Canvas Scaler
コンポーネントのUI Scale Mode
をScale With Screen Size
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのReference Resolution
をX: 200, Y: 300
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのScreen Match Mode
をExpand
に設定します。 - Text Labelの
RectTransform
を以下のように設定します。 (以下省略)
(誤)
しかし、これだけでは実際の値に対応した文字が表示されません。
そこで、スクリプトを用いてTextの内容を変更できるようにしましょう。
今回は、Textにスクリプトをアタッチするのではなく、UIManager
というクラスで管理することにします。
新たにUIMangaer.csを以下の内容で作成し、先程と同様にHierarchy上のGameObject「Player」にアタッチしてください。
(正)
しかし、これだけでは実際の値に対応した文字が表示されません。
そこで、スクリプトを用いてTextの内容を変更できるようにしましょう。
今回は、Textにスクリプトをアタッチするのではなく、UIManager
というクラスで管理することにします。
新たにUIMangaer.csを以下の内容で作成してください。
(誤)
- 新たなシーンを作成し、名前をSelectSceneとします。
- SelectSceneを開きます。
- Main Cameraの
Background
を白(RGB=(255, 255, 255)
)にします。 - Hierarchyビューで右クリックし、UI→Textより2つのTextを作成します。
- 作成したTextの名称を、1つは「Text Information」、もう1つは「Text Scroll Speed」に変更します。
- Canvasにアタッチされた
Canvas Scaler
コンポーネントのUI Scale Mode
をScale With Screen Size
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのReference Resolution
をX: 200, Y: 300
に設定します。 - Text Informationの
RectTransform
を以下のように設定します。 (以下省略)
(正)
- 新たなシーンを作成し、名前をSelectSceneとします。
- SelectSceneを開きます。
- Main Cameraの
Background
を白(RGB=(255, 255, 255)
)にします。 - Hierarchyビューで右クリックし、UI→Textより2つのTextを作成します。
- 作成したTextの名称を、1つは「Text Information」、もう1つは「Text Scroll Speed」に変更します。
- Canvasにアタッチされた
Canvas Scaler
コンポーネントのUI Scale Mode
をScale With Screen Size
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのReference Resolution
をX: 200, Y: 300
に設定します。 - Canvasにアタッチされた
Canvas Scaler
コンポーネントのScreen Match Mode
をExpand
に設定します。 - Text Informationの
RectTransform
を以下のように設定します。 (以下省略)
(誤)
……(ここまで同様につき省略)……
private void Update()
{
// 譜面ID変更
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
ChangeSelectedIndex(selectedIndex - 1);
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
ChangeSelectedIndex(selectedIndex + 1);
}
// スクロール速度変更
if (Input.GetKeyDown(KeyCode.DownArrow))
{
ChangeScrollSpeed(scrollSpeed - 0.1f);
}
if (Input.GetKeyDown(KeyCode.UpArrow))
{
ChangeScrollSpeed(scrollSpeed + 0.1f);
}
// 決定処理
if (Input.GetKeyDown(KeyCode.Space))
{
// スクロール速度を設定
PlayerController.ScrollSpeed = scrollSpeed;
// 譜面を設定
PlayerController.beatmap = new Beatmap(beatmapPaths[selectedIndex]);
// シーン切り替え
SceneManager.LoadScene("GameScene");
}
}
……(以下同様につき省略)……
(正)
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement; // シーン遷移に必要
using UnityEngine.UI;
……(この間同様につき省略)……
private void Update()
{
// 譜面ID変更
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
ChangeSelectedIndex(selectedIndex - 1);
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
ChangeSelectedIndex(selectedIndex + 1);
}
// スクロール速度変更
if (Input.GetKeyDown(KeyCode.DownArrow))
{
ChangeScrollSpeed(scrollSpeed - 0.1f);
}
if (Input.GetKeyDown(KeyCode.UpArrow))
{
ChangeScrollSpeed(scrollSpeed + 0.1f);
}
// 決定処理
if (Input.GetKeyDown(KeyCode.Space))
{
// スクロール速度を設定
PlayerController.ScrollSpeed = scrollSpeed;
// 譜面を設定
PlayerController.beatmap = new Beatmap(beatmapPaths[selectedIndex]);
// シーン切り替え
SceneManager.LoadScene("GameScene");
}
}
……(以下同様につき省略)……