unity 音乐小游戏

设计思路

方案一
方案二
音乐游戏
频率
频率峰值
改良
频率密度
频率密度
颜色
不同时机使用不同的颜色

具体玩法:音频的高度不同,颜色不同。根据提示点击相同颜色的音频,点击范围有对应颜色加分,没有对应颜色时,减分或者断Combo。连续点对加分翻倍,要求颜色会随机变化。
效果图:在这里插入图片描述

在这里插入图片描述

简易特效

新建一个粒子系统,将Mode变为Sprites。
添加自己的UI图片(本体白色,背景透明),因为看不见所以我这里用的黑底。在这里插入图片描述

在这里插入图片描述
修改粒子的参数达到想要的效果,start color会使发出的星星随机获得颜色。
在这里插入图片描述
由于前面的全是由UI做的,粒子效果为3D的。两个无法同时显示。
需要Ugui demo中的方法,就是利用相机,创建Render Texture,实现混排。
1:首先在你的project视图中右键点击create Render Texture
2:然后在新建一个摄像机,对准。新摄像机需要删除Audio Listener。
3:给摄像机target texuure赋予Render Texture。
4:在你的canvas 画布下面新建一个Raw Image,调整好大小,再将render texture拖入到texture中。
在这里插入图片描述
如果显示出现背景,需要隐藏背景。解决方法:更改相机参数ClearFlags为Soild Color。
在这里插入图片描述

游戏视频

链接: 游戏视频.

源代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;


public class AudioDate : MonoBehaviour
{
    
    

    private AudioSource _audio;//声源
    private float[] samples = new float[512];//存放频谱数据的数组长度(64-8192)
    public Transform sc;
    public float late;
    private float time;
                            // Use this for initialization
    private void Start()
    {
    
    
        time = 0;
        _audio = GetComponent<AudioSource>();//获取声源组件
        _audio.clip = SceneLink.Instance.audioClip;
        _audio.Play();
    }
    private void Update()
    {
    
    
        if (!_audio.isPlaying)
        {
    
    
            SceneLink.Instance.scores = GameRule.Instance.GetScores();
            SceneManager.LoadScene(0, LoadSceneMode.Single);
        }
        time += Time.deltaTime;
        if (time >= late)
        {
    
    
            //获取频谱
            _audio.GetSpectrumData(samples, 0, FFTWindow.BlackmanHarris);
            /* 因为子物体排列规律为0,1,-1,2,-2。所以,需要在赋予频率密度时也需要重新排 */
            int mapIndex;         //映射对应的频率块。
            if ((sc.childCount-1) % 2 == 0)   
                mapIndex = sc.childCount - 1;
            else
                mapIndex = sc.childCount - 2;
            
            for (int i = 0; i < sc.childCount ; i++)
            {
    
    

                HeightChange hg = sc.GetChild(mapIndex).GetComponent<HeightChange>();
                hg.SetHeigth(samples[i]);
                hg.SetIndex(i);
                /*赋予规律为 6,4,2,0,1,3,5。 */
                if (mapIndex % 2 == 0 && mapIndex != 0)
                    mapIndex -= 2;
                else if (mapIndex == 0)
                    mapIndex ++;
                else if (mapIndex % 2 != 0)
                    mapIndex += 2;

            };
            time = 0;
        }
    }
}

参数Late决定刷新速度
在这里插入图片描述

GetSpectrumData解析

其中GetSpectrumData为关键API
以下为官方解释
FFTWindow
enumerationLeave feedback
Description
Spectrum analysis windowing types.

Use this to reduce leakage of signals across frequency bands.

Properties
Rectangular W[n] = 1.0.
Triangle W[n] = TRI(2n/N).
Hamming W[n] = 0.54 - (0.46 * COS(n/N) ).
Hanning W[n] = 0.5 * (1.0 - COS(n/N) ).
Blackman W[n] = 0.42 - (0.5 * COS(n/N) ) + (0.08 * COS(2.0 * n/N) ).
BlackmanHarris W[n] = 0.35875 - (0.48829 * COS(1.0 * n/N)) + (0.14128 * COS(2.0 * n/N)) - (0.01168 * COS(3.0 * n/N)).

public void GetSpectrumData(float[] samples, int channel, FFTWindow window);
Parameters
samples The array to populate with audio samples. Its length must be a power of 2.
channel The channel to sample from.
window The FFTWindow type to use when sampling.
Description
Provides a block of the currently playing audio source’s spectrum data.

The array given in the samples parameter will be filled with the requested data.

Number of values (the length of the samples array provided) must be a power of 2. (ie 128/256/512 etc). Min = 64. Max = 8192. Use window to reduce leakage between frequency bins/bands. Note, the more complex window type, the better the quality, but reduced speed.

This function will use the sampling rate specified in AudioSettings.outputSampleRate, and NOT the sampling rate specified for the audio clip.

See Also: AudioSource.GetOutputData, AudioListener.GetSpectrumData, AudioListener.GetOutputData.

对于GetSpectrumData的参数:

Channel 0在这里是一个安全的选择,因为如果我们有立体声音频,我们需要单独处理2个通道的样本数据。 Channel 0包含立体声样本的平均值,将每2个立体声样本组合成1个单声道样本。 这使我们能够更简单地决定音频中发生的事情。我们可以从众多FFT窗口中选择窗口并缩放我们的光谱数据。 我发现BlackmanHarris非常出色地向我们展示了每个频率箱的不同动作,而箱之间没有太多泄漏。

我们提供的阵列将填充到我们提供的阵列的长度,以及最近播放的音频样本的频谱数据。 这意味着我们不必在GetSpectrumData之前调用GetOutputData。 数组长度必须是2的幂。FFT分析对于2的幂的样本大小最有效,Unity使用GetSpectrumData强制执行该建议。

我发现1024的频谱数组大小给了我们很好的粒度。 同样,这意味着Unity引擎将采集2048个音频样本。 如果我们知道音频采样率,我们可以找到支持的频谱数据频率范围,然后,使用我们的数组大小,我们可以快速找出阵列的每个索引所代表的频率范围。

快速傅里叶变换(FFT)以将时域上的幅度转换到频域,仅返回从FFT返回的复数数据的相对幅度部分。虽然FFT通常在从0Hz到采样率的整个频率范围内执行(在本例中让我们使用48kHz),0Hz - 采样率的一半(24kHz)是范围后半部分的镜像(24kHz-48kHz) 。该中点称为奈奎斯特频率(Nyquist frequency)。因此,GetSpectrumData和其他一些基于FFT的助手仅返回分析前半部分的相对幅度。这意味着执行FFT所需的音频样本数量是GetSpectrumData返回的频率仓数量的两倍。因此,要分析的频率粒度越高,生成该粒度所需的时间范围就越大。如果你想要1024个频率箱(frequency bins),每箱的粒度为23.43Hz,则需要2048个音频样本。

float[] samples:float类型的数组来存放取到的频谱数据,此数组长度必须是2的n次方,最小64,最大8192。

int channel:采样的声道,一般用0。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AutoClose : MonoBehaviour
{
    
    
    public float closeTime;
    private float time;
    void Start()
    {
    
    
        time = 0;
    }

    // Update is called once per frame
    void Update()
    {
    
    
        time += Time.deltaTime;
        if(time>=closeTime)
        {
    
    
            time = 0;
            this.gameObject.SetActive(false);
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class ButtonTriggers : MonoBehaviour
{
    
    
    public string key;
    public int index;
    public ParticleSystem start;

    private Button button;


    private void Start()
    {
    
    
        button = this.GetComponent<Button>();
        button.onClick.AddListener(OnClick);
        index = this.transform.GetSiblingIndex();

    }

    public void OnClick()
    {
    
    
        if ( ColorDivider.CheckColor(GameRule.Instance.currentColor, index))
        {
    
    
            GameRule.Instance.Add(1*ScoresRatio());
            GameRule.Instance.AddCombo();
            ParticleSystem ps=Instantiate(this.start, Input.mousePosition, new Quaternion());
            ps.transform.localPosition = PositionMap(Input.mousePosition);
            ps.Play();
            Destroy(ps.gameObject,0.5f);
        }
        else
        {
    
    
            GameRule.Instance.ClearCombo();
            if(GameRule.Instance.mode==GameMode.hand)
                GameRule.Instance.Add(-1);
        }
        int ScoresRatio()
        {
    
    
            if (GameRule.Instance.Get() >= 2 && GameRule.Instance.Get() < 4 )
            {
    
    
                GameRule.Instance.SetRatio(2);
                return 2;
            }
            if (GameRule.Instance.Get() >= 4 && GameRule.Instance.Get() < 8)
            {
    
    
                GameRule.Instance.SetRatio(4);
                return 4;
            }
            if (GameRule.Instance.Get() >= 8)
            {
    
    
                GameRule.Instance.SetRatio(8);
                return 8;
            }
            GameRule.Instance.SetRatio(0);
            return 1;
        }
    }

    private Vector3 PositionMap(Vector3 vector3)
    {
    
    
        return new Vector3((vector3.x - 540f) * 10.3f / 540f, (vector3.y - 303.75f) * 5.8f / 303.75f + 0.95f, 0);
    }

    private void Update()
    {
    
    
        ColorBlock cb = button.colors;
        cb.pressedColor = UnityEngine.Color.HSVToRGB((float)GameRule.Instance.currentColor / 360, 1f, 1f);
        button.colors = cb;
        if (Input.GetKeyDown( (KeyCode)Enum.Parse(typeof(KeyCode), key)))
        {
    
    
            button.onClick.Invoke();
            cb.normalColor = UnityEngine.Color.HSVToRGB((float)GameRule.Instance.currentColor / 360, 1f, 1f);
            button.colors = cb;
        }
        if(Input.GetKeyUp((KeyCode)Enum.Parse(typeof(KeyCode), key)))
        {
    
    
            ColorBlock cb2 = button.colors;
            cb2.normalColor = new Color32(255, 255, 255, 55);
            button.colors = cb2;
        }
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum Color : int
{
    
     
    red=360,     //红色0-20,312-360
    purple=295,  //紫色271-311
    blue=195,    //蓝色156-270
    green=118,   //绿色70-154
    yellow=58,   //黄色52-69
    other=28
}

public class ColorDivider : MonoBehaviour
{
    
    
    public static ColorDivider Instance;
    public float[] colors;
    public CreateSlice createSlice;
    private static int length;
    void Start()
    {
    
    
        Instance = this;
        colors = new float[createSlice.sliceCount];
        length = createSlice.sliceCount / 8;
    }
    public static bool CheckColor(Color color,int index)
    {
    
    
        for(int i=index*length;i<length*(index+1);i++)
        {
    
    
            float colorValue= Instance.colors[i];
            switch (color)
            {
    
    
                case Color.red:if (colorValue > 312 && colorValue <= 360) return true; else break;
                case Color.purple: if (colorValue > 270 && colorValue <= 312) return true; else break;
                case Color.blue: if (colorValue > 156 && colorValue <= 270) return true; else break;
                case Color.green: if (colorValue > 70 && colorValue <= 156) return true; else break;
                case Color.yellow: if (colorValue > 52 && colorValue <= 70) return true; else break;
                default:break;
            }
        }
        return false;
    }
}

using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;

[RequireComponent(typeof(CreateSlice))]
public class CreateButtons : MonoBehaviour
{
    
    
    public Transform buttons;
    private CreateSlice createSlice;
    private float length;
    private float num;
    private void Start()
    {
    
    
        createSlice = this.GetComponent<CreateSlice>();
        num = createSlice.sliceCount / 8;
        length = 28 * num * 0.8f;

        for (int i= 0;i<buttons.childCount;i++)
        {
    
    
            buttons.GetChild(i).GetComponent<RectTransform>().sizeDelta = new Vector2(length,612.9f);
            buttons.GetChild(i).transform.localPosition = new Vector3((i-num/2)*length-56.3f,0,0);
        }
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateSlice : MonoBehaviour
{
    
    
    public GameObject slice;
    public int sliceCount;
    void Start()
    {
    
    
        int x=0;
        for (int i = 0; i < sliceCount; i++)
        {
    
    
            /* 子物体排列规律为0,1,-1,2,-2  */
            GameObject gameObject = Instantiate(slice, this.transform.parent.Find("SliceCenter"));
            gameObject.transform.localPosition = new Vector3(x*28,0,0);
            if (i == 0 || i % 2 == 0)
            {
    
    
                x *= -1;
                x++;
            }
            else
                x *= -1;
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public enum GameMode:int
{
    
    
    easy,
    medium,
    hand
}

public class GameRule : MonoBehaviour
{
    
    
    public Color currentColor;
    public Text color;
    public Transform scores;
    [Space]
    public GameMode mode;
    public float changeTime;
    [Space]
    public Transform colorChange;
    public Text Combo;
    private int combo;

    public static GameRule Instance;

    private int score;
    private Dictionary<int, Action> dic;
    private IEnumerator conroutine;

    private void Start()
    {
    
    
        mode = SceneLink.Instance.gameMode;
        conroutine = AutoChangeColor();
        currentColor = Color.blue;
        dic = new Dictionary<int, Action>()
        {
    
    
            {
    
    (int)GameMode.easy,()=>StopAllCoroutines()},
            {
    
    (int)GameMode.medium,()=>StartCoroutine(conroutine)},
            {
    
    (int)GameMode.hand,()=>StartCoroutine(conroutine)},
         };
        if (dic.TryGetValue((int)mode, out Action action))
            action.Invoke();
        Instance = this;
        score = 0;
        time = 0;
        combo = 0;
    }
    private float time;
    private IEnumerator AutoChangeColor()
    {
    
    
        while (true)
        {
    
    
            time += Time.deltaTime;
            if(changeTime<=time)
            {
    
    
                ColorSet(RandomColor());
                time = 0;
            }
            yield return null;
        }
        Color RandomColor()
        {
    
    
            int max = 5;
            int min = 1;
            switch (UnityEngine.Random.Range(min, max))
            {
    
    
                case 1:return Color.red;
                case 2: return Color.purple;
                case 3: return Color.blue;
                case 4: return Color.green;
                case 5: return Color.yellow;
            }
            return Color.other;
        }
    }

    public void Add(int score)
    {
    
    
        this.score += score;
        scores.GetComponent<Text>().text=string.Format("scores:{0}",this.score);
    }
    public void ColorSet(Color color)
    {
    
    
        currentColor = color;
        this.color.text = string.Format("color:{0}", currentColor.ToString());
        this.color.GetComponent<Outline>().effectColor = UnityEngine.Color.HSVToRGB((float)color/360, 1f, 1f);
        colorChange.gameObject.SetActive(true);
        colorChange.GetComponent<Text>().color = UnityEngine.Color.HSVToRGB((float)color / 360, 0.47f, 1f);
        colorChange.GetComponent<Outline>().effectColor = UnityEngine.Color.HSVToRGB((float)color / 360, 1f, 1f);
    }


    #region Combo
    public void AddCombo()
    {
    
    
        if(combo==1)
            this.Combo.text = string.Format("Combo:{0}", combo);
        this.combo++;
    }
    public void ClearCombo()
    {
    
    
        combo = 0;
    }
    public int Get()
    {
    
    
        return combo;
    }
    public void SetRatio(int num)
    {
    
    
        if (num <= 0)
            this.Combo.text = string.Empty;
        else
            this.Combo.text = string.Format("Combo:{1}\nx{0}", num, combo);
    }
    public int GetScores()
    {
    
    
        return score;
    }
    #endregion
}

using System.Collections;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using UnityEngine;
using UnityEngine.UI;

public class HeightChange : MonoBehaviour
{
    
    
    //public float lateTime;
    public float speed;
    public float range;
    public float O_height;

    private int index;
    private float t;
    private float height;

    private Rect rect;
    private RectTransform rectTransform;
    private void Start()
    {
    
    
        rectTransform = this.GetComponent<RectTransform>();
        height= O_height;
        t = 0f;
        index = -1;
    }
    public void SetHeigth(float height)
    {
    
    
        this.height = height;
        t = 0f;
    }
    public void SetIndex(int i)
    {
    
    
        if(index<0)
        this.index = i;
    }
    private void Update()
    {
    
    
        if (index < 0)
            return;
        rect = this.GetComponent<RectTransform>().rect;
        if (t <= 1f)
        {
    
    
            t += speed* (1+t)/2 * Time.deltaTime;    //变速 初始速度为0.5v,最大速度为1v
        }
        else
            t = 1f;
        if(rect.height> height * range)
            Move(Mathf.Lerp(height * range, rect.height, t));
        else
            Move(Mathf.Lerp(rect.height, height * range, t));//换向
    }
    private void Move(float L)
    {
    
    
        rectTransform.sizeDelta = new Vector2(rect.width, L + O_height);
        this.transform.localPosition = new Vector3(this.transform.localPosition.x, L / 2, this.transform.localPosition.z);
        float color;
        if (L > 100)
            color = 1f;
        else
            color = L / 100;
        this.GetComponent<Image>().color = UnityEngine.Color.HSVToRGB(color, 1f, 1f);
        ColorDivider.Instance.colors[index] = color * 360;
    }
}

speed小方块移动速度
range频率密度放大倍数
O_height最低(原始)高度
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneLink : MonoBehaviour
{
    
    
    public static SceneLink Instance;
    public GameMode gameMode;
    public AudioClip audioClip;
    public int scores;
    private void Awake()
    {
    
    
        if (Instance == null)
        {
    
    
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else if (Instance != this)
        {
    
    
            Destroy(this.gameObject);
            return;
        }
    }

}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class Begin : MonoBehaviour
{
    
    
    
    void Start()
    {
    
    
        this.transform.GetComponent<Button>().onClick.AddListener(OnClick);
    }
    private void  OnClick()
    {
    
    
        SceneLink.Instance.gameMode = Mode.Instance.mode;
        SceneLink.Instance.audioClip = Music.Instance.clips[Music.GetIndex()];
        SceneManager.LoadScene(1, LoadSceneMode.Single);
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Mode : MonoBehaviour
{
    
    
    public static Mode Instance;
    public Text text;
    public GameMode mode;
    void Start()
    {
    
    
        Instance = this;
        text.text = mode.ToString();
    }
    private void R()
    {
    
    
        if (mode == GameMode.hand)
        {
    
    
            mode = GameMode.easy;
        }
        else
        {
    
    
            mode++;
        }
        Show();
    }
    private void L()
    {
    
    
        if (mode == GameMode.easy)
        {
    
    
            mode = GameMode.hand;
        }
        else
        {
    
    
            mode--;
        }
        Show();
    }
    private void Show()
    {
    
    
        text.text = mode.ToString();
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Music : MonoBehaviour
{
    
    
    public static Music Instance;
    public Text text;
    public List<AudioClip> clips;
    [SerializeField]
    private int index;
    void Start()
    {
    
    
        index = 0;
        text.text = clips[0].name;
        Instance = this;
    }
    public static int  GetIndex()
    {
    
    
        return Instance.index;
    }
    private void R()
    {
    
    
        if (index==clips.Count-1)
        {
    
    
            index = 0;
        }
        else
        {
    
    
            index ++;
        }
        text.text = clips[index].name;
    }
    private void L()
    {
    
    
        if (index == 0)
        {
    
    
            index = clips.Count - 1;
        }
        else
        {
    
    
            index--;
        }
        text.text = clips[index].name;
    }

}

猜你喜欢

转载自blog.csdn.net/qq_41596891/article/details/108537636