developer_RYO’s blog

スマホアプリやPCゲーム、音楽やイラストを自由気ままに作っている人の技術ログです

Unityでのゲーム開発メモ 曲の演奏時間に対して進捗を表すプログレスバー

f:id:developer_RYO:20160102230346g:plain


gif画像にある通りの、プログレスバーを作成。

プログレスバーの作り方については、検索してみると他の方が記事にされていました。
Unityのリファレンスにもいくつかヒントが記載されています。
docs.unity3d.com

fillAmount Image.type が Image.Type.Filled に設定されている時に表示されている Image の数
fillMethod どの fill タイプのメソッドを使用するか
fillOrigin Fill プロセスの原点位置をコントロールします。各 fill メソッド毎に違う結果をもたらすことを意味します。

ここら辺をインスペクターでいじればプログレスバーは作成可能です。

■曲の長さについては、AudioClipの変数lengthで取得します。


画像オブジェクトにつけたスクリプト

//画面上部に表示するタイムゲージを表示します。
//曲の長さと、現在の経過時間より、パーセンテージを取得し、表示。
using UnityEngine;
using UnityEngine.UI;

public class BarTimer : MonoBehaviour
{
    //タイマーと、再生する曲を入れる。

    [SerializeField]
    private Timer timer;
    [SerializeField]
    private AudioClip music;

    private float musicLength;
    private Image image;

    void Start () {
        //曲の長さを取得
        musicLength = music.length;
        //画像を格納
        image = this.GetComponent<Image>();
    }

	// Update is called once per frame
	void Update () {
        //画像のFillAmountを更新し続ける。
        image.fillAmount = 1 - (timer.Now / musicLength);
    }
}

タイマーのスクリプト

//////////////////////////////////////////
//ゲームオブジェクトをタイマーにするスクリプトです。

using UnityEngine;

public class Timer : MonoBehaviour {

	/////////////////////////////////////////

	//タイマー
	private float timer;

	//タイマーを進めるかどうかを決める変数
	public bool timerRun = false;

	//タイマーをリセットする。
	public void ResetTimer(){timer = 0;}

	//タイマーを進める
	public void AddTimer(){timer +=  Time.deltaTime;}

	//プロパティで値を変えられるようにする。
	//変数をpublicにしないのは、インスペクター上で変えられないようにしたいから
	public float Now{get{return timer;}}

	///////////////////////////////////////
	//初期化
	void Start()
	{
		//タイマーの初期化
		timer = 0;
	}
	////////////////////////////////////////
	//アップデート
	void Update()
	{
		//タイマーを進めるかどうか
		if(timerRun == false) return;

		//タイマーを進める
		AddTimer();
	}
}

c#でcsvファイルを配列に格納して返すクラスを作ってみた

音ゲーのタイミングデータをCSVで取り扱いたかったので、下記のクラスを作って見ました。

StreamReaderの、closeとかdisposeとかがまだあまり理解してないので、後で修正したいと思います。

配列について、タイミングデータの数が曲や難易度によって変化することを見込んで、List<>を使用しました。


////////////////////////////////////////////////
//ファイルからノート配列を作成する役割

using System.Collections.Generic;
using System.IO;

public class InputCSV
{
	
	////////////////////////////////////////
	//データ構造

	private List<Note> noteArray;
	private int index;
	
	private StreamReader csv;
	
	
	
	////////////////////////////////////////
	//処理
	
	//コンストラクタ
	public InputCSV(string fileName)
	{
		this.noteArray = new List<Note>();
		index = 0;
		
		InputNote(fileName);	
	}
	//インデックスの最大値を返す。
	public int getIndex{get{return index;}}
	
	//CSVデータを入れたノート配列を返す。
	public Note[] Output()
	{
		return noteArray.ToArray();
	}
	
	//ファイルを読み込んで、配列に格納する関数
	private void InputNote(string filename)
	{
		//ファイルを読み込み、配列にstringを格納していく。
		csv = new StreamReader("Assets/Resources/" + filename);
		ReadAllLineCSV();
		
	}
	
	
	//CSVファイルを1行ずつ読み込み、配列に足していく関数
	public void ReadAllLineCSV()
	{
		//indexMaxの値が実際と違わないように、この関数内で初期化する。
		index = 0;
		
		//操作用データ
		string line = "";
		string[] lineArray;
		
		//順次格納
		while (csv.Peek()>=0)
		{
			//ファイルから1行読み込み、配列に格納する。
			line = csv.ReadLine();
			lineArray = line.Split(',');
			
			noteArray.Add(new Note(float.Parse(lineArray[0]),int.Parse(lineArray[1])));
			
			//行数の追加
			index++;		
		}
		csv.Close();
	}
}

field type 型名 is less accessible than field の避け方

▪️問題点
変数の宣言箇所にfield type 型名 is less accessible than fieldっていうエラーが出た
出たのは、noteっていう変数のところ。

//譜面クラス
//入力タイミングと、音符のタイミングデータの結果を返す。
public class Music
{
	////////////////////////////////////////
	//データ構造
	
	protected Note[] note;
	
class Note
{
	/////////////////////////////////////////
	//データ
	
	//タイミング、サウンドタイプ、
	private float timing;
	private Sound sound;
	

▪️解決方法
Microsoftのサイトになんだか書かれていたので、参考にしました。
アクセシビリティ レベルの使用に関する制限事項 (C# リファレンス) | Microsoft Docs

直接基本クラスは、少なくともその派生クラスと同程度にアクセス可能である必要があります

僕の場合は派生クラスではないんですが、publicとかprivateとかをいじって、アクセスレベルってやつを合わせてあげるのが必要っぽいという認識だ。

unityで音ゲーのモックを作りました

f:id:developer_RYO:20151111222709g:plain

音ゲーを作っているのですが、ゲームの大体のロジックをつかみたいと思い、
こちらのサイトを参考にしつつサンプルを作ってみました。
blog.bokuweb.me

方針として、0,1,2,・・・番のタイミングデータとユーザーの入力時間を比較し、
誤差が少なければOK、多ければBADの結果を出力します。

データの持ち方
  • 曲開始からの時刻を格納する変数timer
  • 曲のタイミングデータを格納する配列timing
  • ユーザーの入力に対して、どのタイミングデータを比較するかを指定する変数indexTiming
  • 曲の判定結果を格納しておく変数cool,good,bad
処理的なもの
  • ユーザーがスペースキー入力したらtrue返すIsBeat()
  • 現在のtimerとタイミングデータtimingを比較し、判定結果をcool,good,badに追加していくcheckTiming()
  • cool,good,badを+1するadd●●()
ゲームのフロー

ゲーム開始時にタイマーに0を代入し、曲を開始します。
音符(といってもブロック)については、タイミングデータの1秒前に音符を生成していく。速度については生成1秒後にヒットバーに当たるようにする。
ゲーム中はユーザーの入力があればタイミングデータとの比較を行い、判定範囲であれば結果を変数にプラスしていきます。

感想

参考にさせていただいたサイトが非常にわかりやすく、1,2時間ほどでサンプルを作ることができました。ありがとうございます。
音符を消す処理をどうするか、音符を動かすラインが複数ある時の処理、音ズレ対策などまだまだやることがたくさんありますが、
簡単なサンプル作成によって作りたいもののイメージをより強く持つことができました。

ソースコード

ヒットバーのオブジェクトにつけたファイル

using UnityEngine;
using System.Collections;

public class Beat : MonoBehaviour {

    //本番作品では、なるべくstatic変数を使わないこと。
    //ゲームのタイマーを宣言。
    public static float timer;
    //曲のタイミングデータを宣言
    public static float[] timing;
    //音符のインデックスを宣言。
    private int indexTiming;
    //曲の判定結果を格納しておく変数を宣言。
    public static int cool; public static int good; public static int bad;

    //エフェクトを格納。
    public GameObject goodEffect;
    public GameObject badEffect;

    //開始時
	void Start () {
        
        //タイミングデータはDTMを見ながら作成した。
        timing = new float[] {1.495f,2.220f,2.580f,2.942f,3.664f,4.387f,4.749f,5.110f,5.472f,5.833f,6.556f,7.279f,7.818f,8.363f,8.725f,9.267f,9.809f,10.170f,10.713f,11.255f,11.616f,11.797f,11.978f,12.158f,12,339f,1000f};
        cool = 0; good = 0;bad = 0;indexTiming = 0;timer = 0; 	
	}
	
	void Update () {

        if (IsBeat())
            checkTiming();
        //音符がヒットバーより後ろに流れたときの処理
        else if (timer - timing[indexTiming] >= 0.12f)
        {
            addBad();
            indexTiming++;
            Instantiate(badEffect, transform.position, transform.rotation);
        }

        timer += Time.deltaTime;
    }

    //スペースキーで入力。
    public bool IsBeat()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            return true;
        else
            return false;
    }

    //入力とタイミングデータの比較を行い、スコアを計上していく。
    public void checkTiming()
    {

        //タイミング誤差の範囲についてはいったんpublicにしてテストしやすくしたほうがいいかも
        if(timer - timing[indexTiming] >= -0.10f && timer - timing[indexTiming] <= 0.10f)
        {
            addCool();
            indexTiming++;
            Instantiate(goodEffect, transform.position, transform.rotation);
        }
        else if(timer - timing[indexTiming] >= -0.12f && timer - timing[indexTiming] <= 0.12f)
        {
            addGood();
            indexTiming++;
            Instantiate(goodEffect, transform.position, transform.rotation);

        }
        else if(timer - timing[indexTiming] >= -0.6f && timer - timing[indexTiming] <= 0.6f)
        {
            addBad();
            indexTiming++;
            Instantiate(badEffect, transform.position, transform.rotation);

        }
        else
        {
            //何もしません。
        }

        //デバッグログ用
        //outputResult();
    }

    //結果の追加
    public void addCool(){cool++;}
    public void addGood(){good++;}
    public void addBad(){bad++;}

    //デバッグログに結果の出力
    //public void outputResult()
    //{
    //    Debug.Log("cool=" + cool + "\t" + "good=" + good + "\t" + "bad=" + bad + "\n");
    //}
}

音符を生成して、初期速度を持たせるファイル

using UnityEngine;
using System.Collections;

public class CreateNote : MonoBehaviour {

    //音符オブジェクトを生成するため
    public GameObject note;

    //音符の生成位置指定用
    public Vector3 position = new Vector3(-10, 0, 0);

    private int index = 0;

    void Start()
    {
    }
	
    void Update () {

        //スタート時にまとめて作ったほうがカクツキが少なくなるかも
        if(Beat.timer >=  Beat.timing[index] -1)
        {
            Create();
            index++;
        }
    }

    //音符を作る関数
    public void Create()
    {
        Instantiate(note, position, this.transform.rotation);

    }
}

音符のデータを格納するクラス

ClassNote.cs

//音符のデータをクラスにする。
public class Note
{
	//曲が開始してから、いつ入力したら正なのかのタイミングデータ
	private float timingData;
	//何小節目か
	private byte barNumber;
	//音符の属性
	private byte type;
	
	//データのセットはインスタンス化する時に行う。
	Note(float noteTiming,byte noteBarNumber,byte noteType)
	{
		this.timingData = noteTiming;
		this.barNumber = noteBarNumber;
		this.type = noteType;
	}
}