“音游制作实用插件-Koreographer入门教程”,“Unity2D 音游案例-节奏大师(基于Koreographer)”

看着目录来阅读

第一个是免费视频
音游制作实用插件-Koreographer入门教程)

第二个是siki学院的收费视频
Unity2D 音游案例-节奏大师(基于Koreographer)

Demo 音游制作实用插件-Koreographer入门教程

视频

视频演示了,球的弹跳,方块的缩放,特效的显示
音游制作实用插件-Koreographer入门教程

ogg,文件,文件Track

ogg,mp3改下后缀成ogg
文件,紫图标
文件Track,黄图标

Event ID比较重要,代码通过这一变量来读取文件
在这里插入图片描述

球的脚本

有打点就复位(快歌打点密,不复位飞到天上去),然后加速度,

using SonicBloom.Koreo;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ball : MonoBehaviour {
    
    


	Rigidbody rgb;
	string eventID;
	float jumpSpeed = 3f;

	// Use this for initialization
	void Start () {
    
    

        jumpSpeed = 5f;
        eventID = "Piano";
		rgb=GetComponent<Rigidbody>();
		Koreographer.Instance.RegisterForEvents(eventID, BallJump);
	}


	void BallJump(KoreographyEvent evt)
	{
    
    
		transform.position = Vector3.zero;	//打的点密,不归零来不及
        Vector3 v = rgb.velocity;
        v.y = jumpSpeed;
        rgb.velocity = v;		
	}
}

控制节点

三个组件,音源、蓝图标,红图标。
红图标、上面创建的紫图标拖到青图标上

在这里插入图片描述

效果

这个面板就是上面的“进去打点”弹出的
Main,Piano(自己命名的,脚本读的就是EventID)填上去
点击播放,快捷键“E”就进行打点(点就是两条轨道中间的“红长矩形”,点击选中变绿)
打完,Ctrl+S,退出就运行

tips:
点击激活轨道后,Ctrl+A,可以显示点的命名
在这里插入图片描述

Koreographer的理论内容---------------------------------------------

一种音频格式ogg

mp3转mid

需要是伴奏,有人声打点很乱。
只有伴奏也不好用,打的太密了。
所以请手打
网上推荐的mp3转mid

两个插件中的类型文件

Koreography(紫图标)

在这里插入图片描述

Koreography Tracker(黄图标)

在这里插入图片描述

一个编辑面板

关系

Koreography(紫图标)的编辑面板
编辑后的输出就是Koreography Tracker(黄图标)

Zoom或者Ctrl+上下,可以缩放轨道

在这里插入图片描述

打点(需要单击激活轨道UI)

快捷键E(常用,接近Pr)
Select下双击(觉得费劲)
Draw下单击
Clone还没用过

OneOff打的点都是短的,一样长的
Span打的点,按键时间长或连着,会有的变长,长短不一

以上都注意Snap to Beat Divide beat by (1)。
打钩就是打的点会对齐轨道中的竖线。
不打就自由地多不用对齐。
我是对应人声(唱一个字打一下)打点,比较密集,所以选后者
在这里插入图片描述

有打的点的详情需要滑轮滚一下

在这里插入图片描述

CtrL+A显示点的名字

在这里插入图片描述

一个总节点

就是上面说的控制节点

三个组件,音源、青图标,红图标。
红图标和上面创建的紫图标,都拖到青图标上

在这里插入图片描述

Demo Unity2D 音游案例-节奏大师(基于Koreographer)----------------------------------

进入游戏

在这里插入图片描述

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

public class StartGameButton : MonoBehaviour {
    
    



	// Use this for initialization
	void Start () {
    
    
        GetComponent<Button>().onClick.AddListener(StartGame);
	}
	
	// Update is called once per frame
	void Update () {
    
    
		
	}

    private void StartGame()
    {
    
    
        SceneManager.LoadScene(1);
    }
}

选中是带脚本的
在这里插入图片描述

UI 元素 隐藏不影响

特效

场景中的3个特效是示例,隐藏不影响
在这里插入图片描述

键盘按键

键盘的2组TargetTop(音符生成的位置)和TargetDown(音符结束的位置)隐藏不影响
在这里插入图片描述

脚本 NoteObject(音符的生成)

代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SonicBloom.Koreo;


/// <summary>音符,绿色的音符</summary>
public class NoteObject : MonoBehaviour 
{
    
    
    #region 字属

    RhythmGameController gameController;
    LaneController laneController;

    /// <summary>音符身上挂的组件</summary>
    public SpriteRenderer visuals;

    /// <summary>不同轨道(6个)上音符的图像,和透明、蓝色、绿色三种音符(蓝色到透明=长按的开始和结束,绿色=短按)</summary>
    public Sprite[] noteSprites;

    KoreographyEvent trackedEvent;

    /// <summary>长按开始</summary>
    public bool isLongNote;
    /// <summary>长按结束</summary>
    public bool isLongNoteEnd;

    public int hitOffset;


    #endregion



    #region 生命


	// Use this for initialization
	void Start () {
    
    
		
	}
	
	// Update is called once per frame
	void Update () 
    {
    
    
        if (gameController.isPauseState)
        {
    
    
            return;
        }

        UpdatePosition();
        GetHitOffset();
        if (transform.position.z<=laneController.targetBottomTrans.position.z)
        {
    
    
            gameController.ReturnNoteObjectToPool(this);
            ResetNote();
        }
    }
    #endregion

    #region 辅助


    //初始化方法
    public void Initialize(KoreographyEvent evt,
        int noteNum,LaneController laneCont,
        RhythmGameController gameCont,
        bool isLongStart,
        bool isLongEnd)
    {
    
    
        trackedEvent = evt;
        laneController = laneCont;
        gameController = gameCont;
        isLongNote = isLongStart;
        isLongNoteEnd = isLongEnd;
        int spriteNum = noteNum;
        if (isLongNote)
        {
    
    
            spriteNum+=6;
        }
        else if (isLongNoteEnd)
        {
    
    
            spriteNum += 12;
        }
        visuals.sprite = noteSprites[spriteNum - 1];
    }

    //将Note对象重置
    private void ResetNote()
    {
    
    
    }

    /// <summary>返回对象池</summary>
    void ReturnToPool()
    {
    
    
        gameController.ReturnNoteObjectToPool(this);
        ResetNote();
    }


    /// <summary>击中音符对象</summary>
    public void OnHit()
    {
    
    
        ReturnToPool();
    }


    /// <summary>更新位置的方法</summary>
    void UpdatePosition()
    {
    
    
        Vector3 pos = laneController.TargetPosition;

        pos.z -= (gameController.DelayedSampleTime - trackedEvent.StartSample) / (float)gameController.SampleRate * gameController.noteSpeed;

        transform.position = pos;
    }


    void GetHitOffset()
    {
    
    
        int curTime = gameController.DelayedSampleTime;
        int noteTime = trackedEvent.StartSample;
        int hitWindow = gameController.HitWindowSampleWidth;
        hitOffset = hitWindow - Mathf.Abs(noteTime-curTime);
    }

    /// <summary>当前音符是否已经Miss</summary>
    public bool IsNoteMissed()
    {
    
    
        bool bMissed = true;
        if (enabled)
        {
    
    
            int curTime = gameController.DelayedSampleTime;
            int noteTime = trackedEvent.StartSample;
            int hitWindow = gameController.HitWindowSampleWidth;

            bMissed = curTime - noteTime > hitWindow;
        }
        return bMissed;
    }


    /// <summary>音符的命中等级</summary>
    public int IsNoteHittable()
    {
    
    
        int hitLevel = 0;
        if (hitOffset>=0)
        {
    
    
            if (hitOffset>=2000 && hitOffset<=9000)
            {
    
    
                hitLevel = 2;
            }
            else
            {
    
    
                hitLevel = 1;
            }
        }
        else
        {
    
    
            this.enabled = false;
        }

        return hitLevel;
    }
    #endregion

}

音符的生成

六个轨道,
0-5,绿色音符
6-11,长按的开始,蓝色音符
12-18,长按的结束,白色音符
在这里插入图片描述

音符 NoteObject

    //初始化方法
    public void Initialize(KoreographyEvent evt,
        int noteNum,LaneController laneCont,
        RhythmGameController gameCont,
        bool isLongStart,
        bool isLongEnd)
    {
    
    
        trackedEvent = evt;
        laneController = laneCont;
        gameController = gameCont;
        isLongNote = isLongStart;
        isLongNoteEnd = isLongEnd;
        int spriteNum = noteNum;
        if (isLongNote)
        {
    
    
            spriteNum+=6;
        }
        else if (isLongNoteEnd)
        {
    
    
            spriteNum += 12;
        }
        visuals.sprite = noteSprites[spriteNum - 1];
    }

轨道 LaneController

    //检测是否生成下一个新音符
    void CheckSpawnNext()
    {
    
    
        int samplesToTarget = GetSpawnSampleOffset();

        int currentTime = gameController.DelayedSampleTime;

        while (pendingEventIdx < laneEvents.Count
            && laneEvents[pendingEventIdx].StartSample < currentTime + samplesToTarget)
        {
    
    
            KoreographyEvent evt = laneEvents[pendingEventIdx];
            int noteNum = evt.GetIntValue();
            NoteObject newObj = gameController.GetFreshNoteObject();
            bool isLongNoteStart = false;
            bool isLongNoteEnd = false;
            if (noteNum > 6)
            {
    
    
                isLongNoteStart = true;
                noteNum = noteNum - 6;
                if (noteNum > 6)
                {
    
    
                    isLongNoteEnd = true;
                    isLongNoteStart = false;
                    noteNum = noteNum - 6;
                }
            }
            newObj.Initialize(evt, noteNum, this, gameController, isLongNoteStart, isLongNoteEnd);
            trackedNotes.Enqueue(newObj);
            pendingEventIdx++;
        }
    }

音符图片的索引对应着文件的卡点

在这里插入图片描述
以下代码将“紫图标”中的所有点装入rawEvents。
0-5,6-11,12-17皮队ID,相应Add入六个轨道 rawEvents[i];

RhythmGameController

    void Start()
    {
    
    
        InitializeLeadIn();
        simpleMusicPlayer = simpleMusciPlayerTrans.GetComponent<SimpleMusicPlayer>();
        simpleMusicPlayer.LoadSong(kgy, 0, false);
        // 初始化所有音轨.
        for (int i = 0; i < noteLanes.Count; ++i)
        {
    
    
            noteLanes[i].Initialize(this);
        }

        
        playingKoreo = Koreographer.Instance.GetKoreographyAtIndex(0);// 初始化事件。
        KoreographyTrackBase rhythmTrack = playingKoreo.GetTrackByID(eventID);//获取事件轨迹// 获取Koreography中的所有事件。
        List<KoreographyEvent> rawEvents = rhythmTrack.GetAllEvents();//获取所有事件
                                                                      //KoreographyEvent rawEvent = rhythmTrack.GetEventAtStartSample(2419200);
                                                                      //rawEvent.
        for (int i = 0; i < rawEvents.Count; ++i)
        {
    
    
            //KoreographyEvent  基础Koreography事件定义。 每个事件实例都可以携带一个
            //有效载荷 事件可以跨越一系列样本,也可以绑定到一个样本。 样品
            //值(开始/结束)在“采样时间”范围内,* NOT *绝对采样位置。
            //确保查询/比较在TIME而不是DATA空间中发生。
            KoreographyEvent evt = rawEvents[i];
            int noteID = evt.GetIntValue();//获取每个事件对应的字符串

            // Find the right lane.  遍历所有音轨
            for (int j = 0; j < noteLanes.Count; ++j)
            {
    
    
                LaneController lane = noteLanes[j];
                if (noteID > 6)
                {
    
    
                    noteID = noteID - 6;
                    if (noteID > 6)
                    {
    
    
                        noteID = noteID - 6;
                    }
                }
                if (lane.DoesMatch(noteID))
                {
    
    
                    //事件对应的字符串与某个音轨对应字符串匹配,则把该事件添加到该音轨
                    // Add the object for input tracking.
                    lane.AddEventToLane(evt);

                    // Break out of the lane searching loop.
                    break;
                }
            }
        }
        //SampleRate采样率,在音频资源里有。
        //命中窗口宽度,采样率*0.001*命中时长
        hitWindowRangeInSamples = (int)(0.001f * hitWindowRangeInMS * SampleRate);
    }

按键(LaneController)

脚本是挂在 Taget[1-6] 上的LaneController
按SDF JKL六个键
在这里插入图片描述

不同评价图片的判定逻辑

判定

特效

01 ClickDownSprite激活(键盘变蓝)
02 特效HitLongEffectGo实例(下图大的一直转圈圈的特效)
03 特效DownEffectGo实例(下图的绿点,音符没来前按键最明显能看到小绿点)
按下后按空,看到 01,02,03 都执行了
按下后按中,看到 01,02 都执行了

这里有DestrotEffect的脚本佐证hitEffectObjectPool对应02,downEffectObjectPool对应03。所以下图第二个特效还没见其用到

    void ReturnToPool()
    {
    
    
        if (isHitted)
        {
    
    
            gameController.ReturnEffectGoToPool(gameObject,gameController.hitEffectObjectPool);
            gameObject.SetActive(false);
        }
        else
        {
    
    
            gameController.ReturnEffectGoToPool(gameObject, gameController.downEffectObjectPool);
            gameObject.SetActive(false);
        }
    }

在这里插入图片描述

音乐的播放

谁控制

SimpleMusicPlayer会加载AudioSource,调用SimpleMusicPlayer的方法就控制
在这里插入图片描述

开局以第一个音符经过按键才开始播放Bgm

SimpleMusicPlayer的AutoPlayAwake打钩的话,AudioSource的PalyAwake不管打不打,都会播放。
但是这个Demo是第一个音符经过按键才开始播放Bgm。
。。。。。
详解Unity中Time类的用法与深入探究
。。。。。
按脚本是计时8s播放Bgm。Bgm是一上来就有音符产生
在这里插入图片描述
。。。。。
代码来自于 RhythmGameController : MonoBehaviour

    [Tooltip("开始播放音频之前提供的时间量(以秒为单位),也就是提前调用时间。相当于time计时不跑的变量")]//其他
    public float leadInTime;
    float leadInTimeLeft;//音频播放之前的剩余时间量,(盲猜是区别开局与暂停播放,Bgm)

    /// <summary>音乐开始之前的倒计时器。相当于timer计时在跑的变量</summary>
    float timeLeftToPlay;
    
   void InitializeLeadIn()
    {
    
    
        if (leadInTime > 0)
        {
    
    
            leadInTimeLeft = leadInTime;
            timeLeftToPlay = leadInTime;
        }
        else
        {
    
    
            audioCom.Play();
        }
    }

    // Update is called once per frame
    void Update () {
    
    

        if (isPauseState)
        {
    
    
            return;
        }

        if (timeLeftToPlay>0)//倒数音乐开始
        {
    
    
            timeLeftToPlay -= Time.unscaledDeltaTime;

            if (timeLeftToPlay<=0)
            {
    
    
                audioCom.Play();
                gameStart = true;
                timeLeftToPlay = 0;
            }
        }

        if (leadInTimeLeft>0)//倒数我们的引导时间
        {
    
    
            leadInTimeLeft = Mathf.Max(leadInTimeLeft - Time.unscaledDeltaTime, 0);
        }
        ......

游戏中的暂停继续会影响Bgm

RhythmGameController中一下。simpleMusicPlayer是音游插件的脚本

    //游戏的开始与暂停
    public void PauseMusic()
    {
    
    
        if (!gameStart)
        {
    
    
            return;
        }
        simpleMusicPlayer.Pause();
    }

    public void PlayMusic()
    {
    
    
        if (!gameStart)
        {
    
    
            return;
        }
        simpleMusicPlayer.Play();
    }

在这里插入图片描述

每次按键的评价图标

如图看到,Great,Perfectdt都是击中,只是音符案件的偏移量。所以连续击中下面都有分数递增显示

    #region 战斗中的三个

    //显示命中等级对应的图片
    public void ChangeHitLevelSprite(int hitLevel)//Great Perfect Miss三种
    {
    
    
        hideHitLevelImageTimeVal = 1;
        hitLevelImage.sprite = hitLevelSprites[hitLevel];
        hitLevelImage.SetNativeSize();
        hitLevelImage.gameObject.SetActive(true);
        hitLevelImageAnim.SetBool("IsNoteHittable",true);
        if (comboNum>=5)//连击5次开始显示轨道上的分数
        {
    
    
            comboText.gameObject.SetActive(true);
            comboText.text = comboNum.ToString();
            comboTextAnim.SetBool("IsNoteHittable",true);

        }
        //hitLevelImageAnim.Play("UIAnimation");
    }

    private void HideHitLevelImage()
    {
    
    
        hitLevelImage.gameObject.SetActive(false);
    }

    public void HideComboNumText()//轨道上的分数。也就是在Great Perfect Miss下的分数
    {
    
    
        comboText.gameObject.SetActive(false);
    }
    #endregion

在这里插入图片描述

扣血与分数

可以看到口5次分,就结束了
在这里插入图片描述

    #region UI上的3个


    public void UpdateScoreText(int addNum)
    {
    
    
        score += addNum;
        scoreText.text = score.ToString();
    }

    public void UpdateHP()
    {
    
    
        hp = hp - 2;
        slider.value = (float)hp / 10;
        if (hp==0)
        {
    
    
            isPauseState = true;
            gameOverUI.SetActive(true);
            PauseMusic();
        }
    }

    //游戏的开始与暂停
    public void PauseMusic()
    {
    
    
        if (!gameStart)
        {
    
    
            return;
        }
        simpleMusicPlayer.Pause();
    }

    public void PlayMusic()
    {
    
    
        if (!gameStart)
        {
    
    
            return;
        }
        simpleMusicPlayer.Play();
    }
    #endregion

通关 失败判定

结束界面

在这里插入图片描述

    #region 结束后的两个


    public void Replay()
    {
    
    
        SceneManager.LoadScene(1);
    }

    public void ReturnToMain()
    {
    
    
        SceneManager.LoadScene(0);
    }
    #endregion

猜你喜欢

转载自blog.csdn.net/weixin_39538253/article/details/129664896