快速エンジニア

駆け出しエンジニア、雑記を書く。Unity多め

【Unity】AudioMixerを使った簡単な音量調整 その2

前の記事AudioMixerの作成とセットアップ

yumeoimushi.hatenablog.com


さて、次にスクリプトからグループの音量を変更するのですがどうすれば良いでしょうか。例えば以下のように書きたいところです。

AudioMixer.音量をセットする関数(BGMグループ, スライダーの値)

では、上の「BGMグループ」のようなグループの識別子であるタグ(パラメータ)を作成します。

まず、先ほどのProjectウィンドウから作成したAudioMixerの子要素(BGM)をインスペクターに開きます。

f:id:tantakotako:20210209215310p:plain

開いたらAttenuationからVolumeと書かれている部分を右クリックし、一番上にある「Expose 'Volume (of SE)' to script」をクリックします。これでパラメータが作成されVolumeをスクリプトから変更できるようになります。

f:id:tantakotako:20210209215630p:plain

作成したパラメータはAudioMixerウィンドウの右上にあるExposed Parameterから確認することができます。MyExposedParam1,2...のように追加されますがそれぞれを右クリックしRenameを選択するとパラメータの名前を変更することができます。BGM,SEなどと変更すると良いでしょう。

f:id:tantakotako:20210209220018p:plain

それではスクリプトを書いていきます。音量をセットする関数はSetFloatというものが用意されているのでこれを使用します。第一引数には先ほど設定したグループの識別子であるパラメータを、第二引数にはスライダーのvalueを入れます。スライダーはここでは解説しないので別途作成しておいてください。

    import UnityEngine.Audio;
...
    public AudioMixer audioMixer;
    public Slider slider;

    public void SetBgmVolume()
    {
        audioMixer.SetFloat("BGM", slider.value);
    }

あとはこのSetBgmVolumeをスライダーのOnValueChangedに登録するだけなのですが、少し困ったことが。
Mixerの方の音量はdb(デシベル)という単位なのですがAudioSourceはv(ボリューム)という単位を使っているため齟齬が生じます。さらにMixerは-80~0の範囲、AudioSourceが0~1の範囲で可変なのでそこでも数値のズレが起きます。
そのため、以下のボリュームからデシベルに変換して0~1の範囲に収める関数を追記してSetFloatの第二引数をslider.valueからConvertVolumeToDb(slider.value)に変更してください。そして、SliderのインスペクターからMinValueを0、MaxValueを1に変更します(Volumeと対応づけた形にする)。

    public float ConvertVolumeToDb(float volume)
    {
        return Mathf.Clamp(Mathf.Log10(Mathf.Clamp(volume, 0f, 1f)) * 20f, -80f, 0f);
    }

    public void SetBgmVolume(float volume)
    {
        audioMixer.SetFloat("BGM",ConvertVolumeToDb( slider.value));
    } 

中身の説明を少しすると、ボリュームをデシベルに変換するにはlog10をとって20をかける必要があります。そもそもデシベルは基準となる音(ボリューム)があってそれが何倍になったかを表す単位です。何倍は比の常用対数の二十倍というように定義がされているのでそうせざるを得ないんですね。ちなみに私はこれを脳科学の講義で習いました(なぜでしょうね)。Clampは第一引数でとった値を第二引数と第三引数の間に収める関数です。0~1の範囲を無理やり-80~0に収めてるわけです。

あとはSetBgmVolumeをスライダーのOnValueChangedに登録しておけばSliderを動かすたびに関数が呼ばれて音を動的に変更出来ます。お疲れ様でした。

【Unity】AudioMixerを使った簡単な音量調整 その1

まとまった記事がなかったので備忘録として残しておきます。

どういうものを作りたいかというとこんなのです。
BGMやSEの音量をスライダーで変更できる例のヤツ

f:id:tantakotako:20210209211958p:plain

音ゲーなので効果音を消して遊びたい人とかの需要がありますね。
最初はAudio SourceのVolumeを弄ろうとしましたが曲ごとに変更しなければいけないので面倒でした。
この方法を使えばAudioSourceをBGMというグループ、SEというグループに分けてグループ単位で音量の調整をすることができます。

まずはProjectウィンドウ > 右クリック > Create > AudioMixerで新規AudioMixerを作成します。
f:id:tantakotako:20210209212538p:plain

次にUnityの上のタブからWindow > Audio > AudioMixerでAudioMixerのウィンドウを開きます。先ほど作成したAudioMixerをクリックし編集します。

f:id:tantakotako:20210209212959p:plain
f:id:tantakotako:20210209213107p:plain

左側のGroupsの横にある+からグループを作成します。このときMasterを一度選択してから+を押すようにしてください。Masterを選択しておくとMasterの子グループとして作成されるようになります。

f:id:tantakotako:20210209213333p:plain

2回ほど押すとこうなります。左側のGroupsにNew Groupが追加されますが、これを右クリックしRenameを選ぶと名前を変更することができます。BGM、SEなどと名前をつけておきましょう。

また、右側にあるグループごとのMixerの0に焦点が当てられていますが矢印を動かすことで音量(dbデシベル)を変えることができます。これをスクリプトから動かせればスライダーと連結させて音量を変更できそうです。

f:id:tantakotako:20210209213835p:plain

次にこのBGM、SEグループにAudioSourceを登録していきます。
Projectウィンドウから作成したAudioMixerにMaster,BGM,SE,Snapshotなどの子要素が追加されていると思うのでそれをAudioSourceのインスペクターにあるOutputにアタッチします。(AudioSourceをグループに追加していく、という感じですね)

f:id:tantakotako:20210209214141p:plain
f:id:tantakotako:20210209214103p:plain

次の記事ではスクリプトからグループの音量を変更する部分を書くのでそちらをご覧ください。

yumeoimushi.hatenablog.com

【Unity】Textを電光掲示板みたいにループして流す

こんな感じでTextを流すことができます(よく見かけますね)。

vimeo.com

Canvasの下にPanel、その子にTextを二つ配置します。
名前は各自変えてください。

f:id:tantakotako:20210202170952p:plain

Panelの幅を画面いっぱいに設定します。高さはテキストが収まりそうな具合に。
また、Image>Colorのalpha値を0にすればPanelを隠すことができます。

f:id:tantakotako:20210202171217p:plain 
f:id:tantakotako:20210202171153p:plain

Textは位置をPanelの中央にしてHorizontal OverflowとVertical Overflowを「Overflow」に設定します。
また、Text1と2は全て同じ値にしてください(Duplicateしても良い)。

以下のスクリプトを書いて実行すれば流れます。
Textをインスペクタに入れるのを忘れないようにしてください。
textWidthの値はループする横幅なのでお好みで設定してください。

    [SerializeField]
    private Text musicText1, musicText2;
    private Vector3 musicText1Position, musicText2Position;
    private readonly float textWidth = 150f;

    void Start()
    {
        musicText1Position = musicText1.rectTransform.localPosition;
        musicText2Position = musicText2.rectTransform.localPosition;
        musicText1Position.x = 0;
        musicText2Position.x = textWidth;
        musicText2.rectTransform.localPosition = musicText2Position;
        musicText1.rectTransform.localPosition = musicText1Position;
    }

    void Update()
    {
        musicText1Position.x--;
        musicText2Position.x--;
        if (musicText1Position.x < -textWidth)
        {
            musicText1Position.x = textWidth;
        }
        if (musicText2Position.x < -textWidth)
        {
            musicText2Position.x = textWidth;
        }
        musicText1.rectTransform.localPosition = musicText1Position;
        musicText2.rectTransform.localPosition = musicText2Position;
    }

【Unity】Tilemap 2d-extrasの導入とTileのアニメーション(Unity2019.4以降)

備忘録として残しておきます。Unity(2019.4.3f1)

UnityのプロジェクトフォルダからPackages→manifest.jsonを開いて
"dependencies"に「"com.unity.2d.tilemap.extras": "https://github.com/Unity-Technologies/2d-extras.git#v1.4.0"」の一文を加えます。v1.4.0の部分を変更するとお好みのバージョンを入れることができます。
f:id:tantakotako:20210124211202p:plain
github.com

アニメーション付きのTileをCreate出来るようになります。

f:id:tantakotako:20210125141744p:plain

spriteの数をアニメーションのフレーム分指定して貼り付けます。

f:id:tantakotako:20210125143250p:plain

Minimum Speed、Maximum Speedをいじれば速度が変わります。30にすると毎フレームspriteが切り替わります。

f:id:tantakotako:20210125143831p:plain

TileをPaletteに配置して実行すれば勝手にアニメーションが再生されるはずです。

Unityで音ゲーつくる ノーツの判定実装

こんにちは、ノーツの判定を実装しました。
基本的にタップ処理はEventTriggerに「Pointer Up」「Pointer Down」を登録してコールするメソッドをセットして呼び出します。
判定ゾーンは2つなので2つ分セットします。
f:id:tantakotako:20210123211223p:plain

用意した判定の種類の以下の6つです。Noneはノーツが来ていないときに押された場合の判定です。(無くてもいいかも)

public enum JudgementType
{
    None,
    Miss,
    Perfect,
    Great,
    Good,
    Bad
}

また、生存ノーツ用のリストを用意しておき、ノーツを生成した時に格納しておきましょう。それを時間順(beatBegin)に並べ一番先頭に来たノーツを次に来るノーツとして設定します。LinQを使うと取り出しやすいです(最近までリンキューと読んでいた)。

次のノーツが判定ゾーンと重なる時間は拍と秒数の変換により分かっている(secBegin - CurrentSec)のでそれと判定ゾーンをタップした時間との差で判定を決めます。

void JudgementType GetJudgementType(float differenceSec){
        if(differenceSec <= 0.05f)
        {
            return JudgementType.Perfect;
        }
        else if(differenceSec <= 0,10f)
        {
            return JudgementType.Great;
        }
        else if(differenceSec <= 0.20f)
        {
            return JudgementType.Good;
        }
        else if (differenceSec <= 0.30f)
        {
            return JudgementType.Bad;
        }
        else
        {
            return JudgementType.None;
        }
}

しかし、これだけだと全くタップしなかったorタップがBadよりも遅かったときのMiss判定が実装されていません。
「全くタップしなかった」と「タップがBadよりも遅かった」は同義とし、その判定をノーツごとのUpdate()で行います。

void Update(){
    if (noteProperty.secBegin - PlayerController.CurrentSec < -JudgementManager.JudgementZone[JudgementType.Bad])
    {
         //ミス処理
    }
}

ミス処理にはノーツのオブジェクトを消去する処理を書きます。生存ノーツ用のリストからも消去します。
成功処理はタップした時に呼び出すメソッドから先ほどのGetJudgementTypeを取得し、None以外の場合で同じくノーツを消去する処理を書きます。効果音や後ほど紹介するスコア計算などもここで呼び出すようにしています。

シングルノー

特筆する点はありません。タップ処理がそのままシングルノーツの判定実装になります。

ロングノーツ

始点と終点を考える必要があります。終点は指を離した時なので「Pointer Up」に登録したメソッドを呼びます。中身は同じような処理を書きます。ただ、押しっぱなしにしているかを判断するために処理中フラグを用意しておき、押した際に処理中フラグをtrueに、離した際にfalseにしています。そのため、先ほどのミス処理では処理中フラグがtrueの時(ずっと押しっぱなしだった)とfalseの時(押してすらいない、もしくは離すのが早い)の2通りの処理を書く必要があります。また、処理中フラグがtrueの時にノーツの色を変えたり始点を判定ゾーンに合わせたりするとプレイした時に納得のいく感触が得られるかと思います。

フリックノーツ

ロングノーツと同じく押した際と離した際の処理を書く必要があります。押した際にmousepositionで座標を保存しておき、離した際にその座標から一定数動いているかどうかでフリック成功かを判断します。差は0.2fくらいがちょうどいいと思います。もし向きを指定したい場合はx軸だけ、もしくはy軸だけに限定しましょう。

 bool canFlicked = flickDifference <= Mathf.Abs(flickStartPos.x - flickEndPos.x) ||
                flickDifference <= Mathf.Abs(flickStartPos.y - flickEndPos.y);

Unityで音ゲーつくる 譜面読み込み機能

音ゲーを作りはじめてから2週間が過ぎ、宣言通り最低プレイできるレベルにはなりました。
一方で記事の執筆が全く進んでいないので備忘録としても書かないとですね。

 

ところでUdemyというサイトでUnityの講座を探していたら面白そうなのを見つけました。

www.udemy.com


かなりクオリティの高い2Dアクションゲームアプリが作れるみたいです。内容が良ければ学んだことなど受講レビュー的なのも書けるかなと思います。

 

さて、それでは譜面読み込み機能について書きたいと思います。
読み込むファイルは(.sus)という変わった形式ですね。中身についてはどうやらWikiがあったようで、もっと早く見つけていればよかった…。

sus形式 - sus-analyzer

「#TITLE xxx」「#ARTIST xxx」など#の直後が英字で始まるデータはメタデータ、「#00018: 00001800 」「#000280: 18000028」など数字で始まるのは譜面データだそうです。中身は1行ずつ書かれているのでFileクラスのReadAllLinesというメソッドが使えますね!

var lines = File.ReadAllLines(filePath, Encoding.UTF8);

foreach(var line in lines)
{
      // メタデータを読み込む処理
      // 譜面データを読み込む処理
}

肝心のデータを読み込む処理ですが、まず正規表現でパターンを用意しておいてそれにマッチする行のみに処理を適用させましょう。

private static string ScoreDataRegax = @"#([0-9]{3})([0-9A-Z]{2})(.*): (.*)";
private static List<string> MetaDataRegax = new List<string>
{
     @"#(TITLE) (.*)",
     @"#(ARTIST) (.*)",
     @"#(PLAYLEVEL) (.*)",
     @"#(WAVE) (.*)",
     @"#(WAVEOFFSET) (.*)",
     @"#(BPM01):(.*)"
};

if((Regex.Match(line, ScoreDataRegax)).Success){ // ここに処理を書く }

パターンに「( )」をつけておくとその部分をmatch.Groups[x+1番目]で取得できます。
メタデータは(TITLE, ARTIST)と(xの歌, yさん)など対応できるDictionaryに格納するといいと思います。
BPM01のデータだけはすぐ使うのでtempoなどの変数に入れておきましょう。

おさらいですが譜面データは以下のようになっています。
f:id:tantakotako:20210115235154p:plain

「:」の前後で分けたほうがいいです。後ろの部分はn分音符で分割されているので2桁ごとに区切って処理をします。
あとは上の画像のようにif文を使ってシングルノーツやロングノーツを生成させますが、結局それが何拍目のノーツなのかは計算する必要があります。
今回は4分の4拍子しか扱っていないため5小節目だった場合は5 * 4 + (i * 4 / (分割数))で求まります(iは区切った中のi番目という意味)。

後はシングルノーツの場合は今まで通りNotePropertyのインスタンスを生成して配列に代入させればokです。

noteProperties.Add(
     new NoteProperty(beat, beat, lane, NoteProperty.NoteType.Single)
);
noteProperty.secBegin = ScoreManager.ToSec(beat, tempo);
noteProperty.secEnd = ScoreManager.ToSec(beat, tempo);

ロングノーツは少し特殊で2桁ごとに区切ったデータが「18」の場合は始点、「28」の場合は終点となります。

 if(objNum[0] == '1')
{
     longNoteBegin = beat;
}
else if(objNum[0] == '2')
{
     noteProperties.Add(
         new NoteProperty(longNoteBegin, beat, lane, NoteProperty.NoteType.Long)
     );
}

このように十の位が1の時はノーツを生成しないで拍の情報だけ保持しておいて2の時に生成するようにします。

Unityで音ゲーつくる ノーツの実装

いかがお過ごしでしょうか。

音ゲー制作は二日ほどお休みしていまして気づけば残り一週間を切っていました。

現状は以下のような感じでロングノーツとフリックノーツを実装したところです。判定はまだです。

f:id:tantakotako:20210107142638p:plain

ロングノーツが少々雑ですがデザインは追々学ぶとしましょう(ごめんなさい)。

そして譜面ですが有志の方が某音ゲー向けに制作したものを使おうと思います↓
github.com

譜面再生機能などが備わっていてとても使いやすいです。
こちらでは2レーンとロングとフリックしか使わないですがなんとかなるでしょう

出力されるファイルの中身は以下のようになっています(譜面データ部分)。

f:id:tantakotako:20210107144932p:plain

#00010...などとよく分からないものを無理やり解析したのが以下になります。
これをUnity側で読み込むコードを書くのですが規則性は分かっても難しそうですね…

f:id:tantakotako:20210107143727j:plain