最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【Unity】UGUI系列教程——OSU!Battle!

2017-09-08 16:37 作者:皮皮關(guān)做游戲  | 我要投稿

前言

有些認(rèn)真的讀者可能會(huì)注意到,OSU!和本教程的表現(xiàn)方式是有差異的。因?yàn)槲易龅挠螒騺?lái)講解功能的主要目的是讓給多想學(xué)習(xí)Unity制作游戲的讀者更有興趣來(lái)學(xué)習(xí)教程,而不是枯燥的背組件的使用方式和參數(shù)作用。我更想讓UGUI偏向?qū)嵱梅较蛑v解,因此每次寫(xiě)教程之前我都是需要自己花時(shí)間想下怎么用最少的知識(shí)點(diǎn)完成我們想要效果。本期根據(jù)上期的預(yù)告,將會(huì)對(duì)OSU!的Battle部分進(jìn)行簡(jiǎn)單實(shí)現(xiàn)和講解。能想學(xué)習(xí)Unity的UGUI功能的讀者也能夠有所受用,這是我的初衷,而若是能讓想制作音樂(lè)游戲的讀者能有所啟發(fā),那真是再好不過(guò)了。


預(yù)覽效果

這里只實(shí)現(xiàn)點(diǎn)擊和拖拽,轉(zhuǎn)動(dòng)圓盤(pán)的效果會(huì)在之后的教程中介紹實(shí)現(xiàn)方法。這里由于時(shí)間原因沒(méi)有擱置未實(shí)現(xiàn)。


游戲需要的知識(shí)

序列幀動(dòng)畫(huà):

序列幀動(dòng)畫(huà)原理是和我們看電視上動(dòng)畫(huà)的原理一樣,當(dāng)圖片不停按順序切換,利用視覺(jué)殘留效果,會(huì)顯示出運(yùn)動(dòng)的感覺(jué)。因此我們只用對(duì)Image圖片做固定時(shí)間切換就可以實(shí)現(xiàn),腳本方法不闡述了,這里說(shuō)一個(gè)Unity的簡(jiǎn)單實(shí)現(xiàn)方法


創(chuàng)建一個(gè)Animator掛載到需要顯示序列幀圖片的地方

直接將序列幀圖片拖到Animation窗口的動(dòng)畫(huà)Clip中

記得動(dòng)畫(huà)Clip資源設(shè)置成循環(huán)播放。



九宮格圖片:

九宮格圖片在UI中廣泛運(yùn)用,為了優(yōu)化資源大小,我們做中間過(guò)渡簡(jiǎn)單的背景圖和長(zhǎng)條UI的時(shí)候并不會(huì)實(shí)際畫(huà)游戲中需要的圖片大小。而是利用設(shè)置九宮格圖片拉伸得到。

選擇你要設(shè)置的九宮格圖片

點(diǎn)擊Spite Editor按鈕,出現(xiàn)九宮格編輯界面

我們?nèi)绻@樣設(shè)置九宮格,那么在中間正方形區(qū)域?qū)?huì)被拉伸,而外圍區(qū)域的圖片將會(huì)保持形狀處理。

這里我們需要將一個(gè)圓形拉伸成膠囊形狀的UI,于是這樣設(shè)置,只給中間留2像素就夠了

對(duì)Image組件的Image Type選擇Sliced就后,調(diào)節(jié)Width就好了

小知識(shí)點(diǎn):

對(duì)你想統(tǒng)一修改某掛點(diǎn)下所有UI物體的透明度,掛載Canvas Group組件就好了。

搭建Note界面

點(diǎn)擊圈的界面很簡(jiǎn)單,只需要一個(gè)可以點(diǎn)擊按鈕Btn_Judge,一個(gè)提示作用的白色圈Img_AimCircle。

點(diǎn)擊后根據(jù)點(diǎn)擊準(zhǔn)確度打開(kāi)得分提示GoodState、PerfectState、FailState就好了。

滑動(dòng)條需要增加跟隨小球移動(dòng)的操作,需要增加移動(dòng)位置開(kāi)始點(diǎn)Tran_StartPoint,移動(dòng)結(jié)束位置點(diǎn)Tran_EndPoint

因?yàn)榈梅痔崾綰I和黃色的范圍提示UI要跟著小球一起移動(dòng),我們便創(chuàng)建一個(gè)移動(dòng)物體掛點(diǎn)Tran_MovePos,將這幾個(gè)需要一起移動(dòng)的UI放在下面。


邏輯功能的實(shí)現(xiàn):

針對(duì)很多新手程序員來(lái)說(shuō),寫(xiě)腳本最難的在于實(shí)現(xiàn)功能的模塊化處理。腳本與腳本之間的重復(fù)代碼過(guò)多,耦合過(guò)多,這樣很不利于維護(hù)和處理業(yè)務(wù)邏輯。

OSU!的點(diǎn)擊圈和滑動(dòng)條效果其實(shí)有很多相似的地方,比如他們都需要開(kāi)始的延遲時(shí)間,這個(gè)時(shí)候其實(shí)是讓玩家做好下一步的準(zhǔn)備,他們都有一個(gè)判定時(shí)間,在這個(gè)判定時(shí)間內(nèi)點(diǎn)擊到判定區(qū)域開(kāi)始計(jì)算得分,而滑動(dòng)條只多了一步滑動(dòng)操作。都有結(jié)算顯示,根據(jù)操作來(lái)打開(kāi)不同的得分提示,最后統(tǒng)一的刪除清理。


我們先將統(tǒng)一部分的功能實(shí)現(xiàn),創(chuàng)建一個(gè)NoteLogic腳本來(lái)做為公共的邏輯腳本處理。

NoteLogic的主要函數(shù):

時(shí)間變化,在特定的狀態(tài)計(jì)時(shí),達(dá)到目標(biāo)值后進(jìn)行狀態(tài)切換


private void Update()
    {
        switch (curState)
        {
            case eState.Delay:
                {
                    curTime += Time.deltaTime;
                    if (curTime > delayTime)
                    {
                        curTime = 0;
                        SetCurState(eState.Wait);
                    }
                }
                break;
            case eState.Operation:
            case eState.Wait:
                {
                    curTime += Time.deltaTime;
                    if (curTime > startTime+judgeTime+0.3f)
                    {
                        curTime = 0;
                        SetCurState(eState.Over);
                    }
                }
                break;
            case eState.Over:
                {
                    curTime += Time.deltaTime;
                    if (curTime > desTime)
                    {
                        SetCurState(eState.None);
                        Destroy(gameObject);
                    }
                }
                break;
        }
    }


設(shè)置當(dāng)前狀態(tài)函數(shù),通關(guān)枚舉類型變化來(lái)實(shí)現(xiàn)狀態(tài)切換


public void SetCurState(eState rState)
    {
        curState = rState;

        switch (curState)
        {
            //界面在延遲等待的階段處理漸入效果
            case eState.Delay:
                curTime = 0;
                var canvasGroup = gameObject.GetComponent<CanvasGroup>();
                canvasGroup.alpha = 0;
                gameObject.GetComponent<CanvasGroup>().DOFade(1, delayTime);
                break;
            //等待判定階段,將圓圈圖片做縮放動(dòng)畫(huà)
            case eState.Wait:
                if (curType != LevelNoteData.eNoteType.Disk)
                {
                    circleTipObj.gameObject.SetActive(true);
                    circleTipObj.transform.DOScale(1, startTime).OnComplete(() => { circleTipObj.gameObject.SetActive(false); });
                }
                break;
            //操作階段調(diào)用虛方法,讓繼承的類來(lái)自定義該狀態(tài)功能
            case eState.Operation:
                if (curType != LevelNoteData.eNoteType.Disk)
                {
                    circleTipObj.gameObject.SetActive(false);
                }
                OnJudgetOperation();
                break;
            //結(jié)束狀態(tài)打開(kāi)得分提示
            case eState.Over:
                curTime = 0;
                ShowScore();
                break;
        }
    }


虛函數(shù),繼承的子類來(lái)實(shí)現(xiàn)這里的功能


    /// <summary>
    /// 做判定操作使用的虛函數(shù)
    /// </summary>
    public virtual void OnJudgetOperation()
    {

    }


打開(kāi)的得分提示UI,這里設(shè)置角度的原因是部分Note會(huì)旋轉(zhuǎn)位置,而打開(kāi)的提示UI不能隨著父物體旋轉(zhuǎn)而旋轉(zhuǎn)


    public void ShowScore()
    {
        if (mainShowObj != null)
        {
            mainShowObj.gameObject.SetActive(false);
        }

        switch (curScore)
        {
            case eScore.Good:
                statePointArr[0].gameObject.SetActive(true);
                statePointArr[0].transform.eulerAngles = Vector3.zero;
                break;
            case eScore.Perfect:
                statePointArr[1].gameObject.SetActive(true);
                statePointArr[1].transform.eulerAngles = Vector3.zero;
                break;
            case eScore.Fail:
                statePointArr[2].gameObject.SetActive(true);
                statePointArr[2].transform.eulerAngles = Vector3.zero;
                break;
        }
    }


HitCircle和Slider實(shí)現(xiàn)

創(chuàng)建HitCircle和Slider腳本并繼承NoteLogic類,通過(guò)重載OnJudgetOperation函數(shù)來(lái)做各自獨(dú)立的功能處理。

HitCircle腳本只用點(diǎn)擊后判斷出得分,改變當(dāng)前的狀態(tài)為Over就結(jié)束了。


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

public class HitCircle : NoteLogic
{

    public override void OnJudgetOperation()
    {
        //這里簡(jiǎn)單的通過(guò)時(shí)間的差值來(lái)判斷得分
        float dValue = Mathf.Abs(curTime  - startTime);

        if (dValue < startTime * 0.35f)
        {
            curScore = eScore.Perfect;
        }
        else if (dValue < startTime * 0.7f)
        {
            curScore = eScore.Good;
        }
        else
        {
            curScore = eScore.Fail;
        }

        SetCurState(eState.Over);
    }

}


而Slider腳本需要額外擴(kuò)展掛點(diǎn),因此可以直接在子類聲明變量。

[Header("移動(dòng)掛點(diǎn)")]Unity的一個(gè)屬性,它能再Inspector界面的變量上顯示你添加的字符串。

我們直接在功能處理中讓移動(dòng)點(diǎn)移動(dòng)到目標(biāo)位置就好了,當(dāng)移動(dòng)到目標(biāo)點(diǎn)后判定得分,改變狀態(tài)為結(jié)束。


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

public class Slider : NoteLogic
{
    [Header("移動(dòng)掛點(diǎn)")]
    public GameObject movePoint;
    [Header("移動(dòng)開(kāi)始掛點(diǎn)")]
    public GameObject moveStartPoint;
    [Header("移動(dòng)目標(biāo)掛點(diǎn)")]
    public GameObject moveTargetPoint;
    [Header("滾動(dòng)球物體")]
    public GameObject rollBar;
    [Header("移動(dòng)提示圈")]
    public RectTransform moveTipObj;

    public override void OnJudgetOperation()
    {
        movePoint.transform.position = moveStartPoint.transform.position;
        rollBar.gameObject.SetActive(true);

        Tweener tween = null;
        //Dotween可以很方便各種鏈接Update功能和Complete功能
        tween = movePoint.transform.DOMove(moveTargetPoint.transform.position, judgeTime).OnComplete(() =>
        {
            //當(dāng)球移動(dòng)到目標(biāo)位置后調(diào)用

            float dValue = Mathf.Abs(startTime+judgeTime -curTime);

            //和點(diǎn)擊圓圈的判定一樣,其實(shí)也可以做不同的處理
            if (dValue < startTime * 0.35f)
            {
                curScore = eScore.Perfect;
            }
            else if (dValue < startTime * 0.7f)
            {
                curScore = eScore.Good;
            }
            else
            {
                curScore = eScore.Fail;
            }

            rollBar.gameObject.SetActive(false);
            SetCurState(eState.Over);

        }).OnUpdate(() =>
        {
            //在每一幀移動(dòng)中判斷鼠標(biāo)是否超出跟隨小球移動(dòng)的黃色圓圈
            if (Vector3.Distance(Input.mousePosition, moveTipObj.position) > moveTipObj.sizeDelta.x * 0.5f)
            {
                if (tween != null)
                {
                    tween.Kill();
                }
                curScore = eScore.Fail;
                rollBar.gameObject.SetActive(false);
                SetCurState(eState.Over);
            }
        });
    }

}


可以繼續(xù)鏈加Update方法來(lái)每幀判斷處理鼠標(biāo)是否移出判定范圍了

將腳本掛在顯示的界面物體上,看下效果

HitCircle:

Slider:


關(guān)卡配置

通用UI組件

之前我們說(shuō)過(guò)了UI做成預(yù)提體的方便之處,這里我們要將這兩個(gè)非界面類型的HitCircle和Slider物體也做成預(yù)制體,這樣通用組件化可以很方便我們動(dòng)態(tài)搭建出界面效果。

將HitCircle和Slider保存成預(yù)制體

然后我們只用按照音樂(lè)播放的時(shí)間,在特定時(shí)刻動(dòng)態(tài)讀取創(chuàng)建出Com_HitCircle和Com_Slider,設(shè)置好位置和角度,再給腳本傳入延遲、等待、判定、銷毀時(shí)間就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單音樂(lè)戰(zhàn)斗了。

配置我們的關(guān)卡數(shù)據(jù)

OSU!的Battle流程其實(shí)就是一個(gè)根據(jù)時(shí)間排序好的Note列表,我們先創(chuàng)建一個(gè)LevelNoteData類來(lái)儲(chǔ)存數(shù)據(jù)。

儲(chǔ)存關(guān)卡信息就先臨時(shí)放在Page_GamePlay界面的UI腳本上吧,其實(shí)正式數(shù)據(jù)是決不能這樣做的,這里是想快速實(shí)現(xiàn)效果避免創(chuàng)建過(guò)多的類導(dǎo)致說(shuō)明混亂。

我們先規(guī)定levelNoteList存儲(chǔ)的信息都是按照時(shí)間從小到大排序的,每幀判定當(dāng)前時(shí)間是否滿足最近的快要?jiǎng)?chuàng)建的Note的創(chuàng)建時(shí)間,若滿足就判斷最近的Note類型,創(chuàng)建出對(duì)應(yīng)的UI通用組件,并對(duì)腳本賦值。

創(chuàng)建完成后就可以將這個(gè)Note移出關(guān)卡信息列表了。

簡(jiǎn)單介紹關(guān)卡編輯

創(chuàng)建一個(gè)Editor腳本添加[CustomEditor(typeof(Page_GamePlay))]屬性便可以修改所有用到掛載Page_GamePlay腳本的Inspector界面信息。

這樣寫(xiě)完之后,我們看到的腳本參數(shù)是這樣的。

這里我就不細(xì)講Editor腳本的用法了,因?yàn)檫@里做腳本數(shù)據(jù)儲(chǔ)存的方式并不常規(guī),而且這種編輯關(guān)卡的方法太過(guò)容易失誤。我這里主要是為了完成教程說(shuō)明,才臨時(shí)用這用方法存儲(chǔ)數(shù)據(jù)。


總結(jié):

OSU!的戰(zhàn)斗功能先簡(jiǎn)單講解了點(diǎn)擊圓圈和跟隨Slider兩種,下一期將會(huì)講缺失的畫(huà)圈和一個(gè)背景視頻插入的效果。這一期重點(diǎn)在于介紹UI系統(tǒng)相關(guān)的九宮格和序列幀動(dòng)畫(huà),以及游戲玩法相關(guān)的腳本功能和信息編輯存儲(chǔ)。有興趣的讀者朋友可以下載Demo了解,最后附上下載地址。

https://github.com/chs71371/OSU_Battle






對(duì)游戲開(kāi)發(fā)感興趣的同學(xué),歡迎圍觀我們:【皮皮關(guān)游戲開(kāi)發(fā)教育】 ,會(huì)定期更新各種教程干貨,更有別具一格的線下小班教育。在你學(xué)習(xí)進(jìn)步的路上,有皮皮關(guān)陪你!~

我們的官網(wǎng)地址:levelpp.com/

我們的游戲開(kāi)發(fā)技術(shù)交流群:610475807

我們的微信公眾號(hào):皮皮關(guān)


【Unity】UGUI系列教程——OSU!Battle!的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
吉林市| 闸北区| 平顶山市| 台东县| 彰化市| 东明县| 澄迈县| 锡林郭勒盟| 河北省| 即墨市| 天水市| 股票| 纳雍县| 夏津县| 海南省| 滨州市| 调兵山市| 阳谷县| 饶平县| 永春县| 虎林市| 安龙县| 沙洋县| 南岸区| 普洱| 雷州市| 太保市| 台东市| 通榆县| 金寨县| 鹤峰县| 凌海市| 乾安县| 宁阳县| 西城区| 民和| 梅河口市| 弋阳县| 叶城县| 额尔古纳市| 桓台县|