13. 历史分数与音乐制作

专栏收录该内容

Hi I'm Shendi



小Bug修复

这个bug是之前没有初始化状态导致的,找好久才找到问题

问题如下:切换武器后开始游戏,火箭筒拥有第二个武器的攻速,切换到第二个武器,第二个武器拥有火箭筒的攻速...

经过查找后,发现是因为加成没有移除,然后就开始了游戏导致的

拥有加成会保存当前武器的攻速/冷却等,当加成移除,就会将当前武器的攻速/冷却恢复为保存的就导致这样的bug了


在 Main 中开始游戏处初始化即可

// 初始化加成
buffAttackTotol = 0;
buffCoolTotol = 0;


历史分数制作

因为最终是要发布为小游戏,所以这里历史分数就不做本地记录了,直接以数组+字典的方式记录


历史分数记录的是结算部分的几个数据就可以了,如下

  • gold - 金币字符串
  • score - 得分
  • time - 时间

还可以将使用的武器记录


Main 中定义代码如下

public GameObject pageHistory;
/** 历史分数 */
public List<Dictionary<string,string>> scoreHisotry = new List<Dictionary<string, string>>();

将历史分数界面拖入摄像机的main部分


在 State 的结算中增加记录

/** 结算 */
public static void Settle()
{
    int scoreGold = Main._instance.score / 50, resultGold = (int) (scoreGold + Main._instance.buffGold);
    var goldText = resultGold + "(" + scoreGold + " + " + (int)(Main._instance.buffGold) + "(格外))";
    Main._instance.settleGold.text = "金币:" + goldText;

    gold += resultGold;

    Main._instance.settleScore.text = "得分:" + Main._instance.score;
    Main._instance.settleTime.text = "时间:" + (int)(Main._instance.time/50) + "秒";

    Dictionary<string, string> map = new Dictionary<string, string>();
    map.Add("gold", goldText);
    map.Add("score", ""+Main._instance.score);
    map.Add("time", (int)(Main._instance.time / 50) + "秒");
    map.Add("weapon", State.playerWeapon.GetComponent<Weapon>().name);
    Main._instance.scoreHisotry.Add(map);
}


界面制作

顶部同商店一样,一个标题一个关闭按钮

内容同之前使用 ScrollView,滚动视图,制作第一个将其作为预制体,在代码内动态生成就可以了

可以将商店的内容复制过来(商店需要先显示,否则复制的无法显示)

界面效果


Main 中增加点击事件

/// <summary>
/// 历史分数页面是否显示
/// </summary>
/// <param name="isShow">true则显示,false隐藏</param>
public void PageScoreHistory(bool isShow)
{
    if (isShow)
    {
        pageHistory.SetActive(true);
        pageMain.SetActive(false);
    }
    else
    {
        pageHistory.SetActive(false);
        pageMain.SetActive(true);
    }
}

给关闭按钮与历史分数按钮增加点击事件,关闭按钮不勾选,历史分数按钮勾选

滚动视图部分,最开始展示最高分与最长坚持时间,然后后面都是记录列表

给滚动视图的Viewport下的Content增加 Verticle Layout Group 组件,这样 Content内的元素是上下排序,而不会重叠

增加组件


在Content下新建一个面板,用来显示最高分数与最高坚持时间

如果面板没有显示,那么调整右边属性面板的宽度和高度

显示分数


然后新建一个Panel,作为历史分数的模板,有序号和历史分数所保存的记录,效果如下

效果

其中上面的1.字体大小20,其余文字字体大小18,将其拖入预制体文件夹



分数列表

在 State 中定义变量,拿到预制体

/** 历史分数面板 */
public static GameObject historyPanel = Resources.Load<GameObject>("Prefabs/scorePanel");

然后在显示历史面板时动态增加分数面板,在隐藏历史面板时销毁动态增加的分数面板

在Main中定义以下变量

/// <summary>
/// 最高分数
/// </summary>
[HideInInspector]
public int maxScore;
/// <summary>
/// 最高分数的ui
/// </summary>
public Text historyMaxScore;
/// <summary>
/// 最高时间
/// </summary>
[HideInInspector]
public int maxTime;
/// <summary>
/// 最高时间的ui
/// </summary>
public Text historyMaxTime;
/// <summary>
/// 历史分数的内容部分
/// </summary>
public GameObject historyContent;

将对象拖入摄像机的Main脚本中后,需要获取最高分与最长时间,在结算部分处理

State 中的 Settle 增加如下代码

// 是否为最高分,是则替换
if (Main._instance.score > Main._instance.maxScore)
{
    Main._instance.maxScore = Main._instance.score;
}

// 是否为最长时,是则替换
if ((int)(Main._instance.time / 50) > Main._instance.maxTime)
{
    Main._instance.maxTime = (int)(Main._instance.time / 50);
}

动态增加了物体后需要更改Content的高度,Main 中历史分数事件代码如下

/// <summary>
/// 历史分数页面是否显示
/// </summary>
/// <param name="isShow">true则显示,false隐藏</param>
public void PageScoreHistory(bool isShow)
{
    if (isShow)
    {
        historyMaxScore.text = "最高分:" + maxScore;
        historyMaxTime.text = "最高时间:" + maxTime;

        // 倒序
        for (var i = scoreHisotry.Count - 1; i >= 0; i--)
        {
            var map = scoreHisotry[i];
            var historyPanel = Instantiate(State.historyPanel, historyContent.transform);
            // 0序号,1得分,2金币,3时间,4武器
            historyPanel.transform.GetChild(0).GetComponent<Text>().text = (i+1) + ".";
            historyPanel.transform.GetChild(1).GetComponent<Text>().text = "得分:" + map["score"];
            historyPanel.transform.GetChild(2).GetComponent<Text>().text = "金币:" + map["gold"];
            historyPanel.transform.GetChild(3).GetComponent<Text>().text = "时间:" + map["time"];
            historyPanel.transform.GetChild(4).GetComponent<Text>().text = "武器:" + map["weapon"];
        }

        // 更新Content高度
        historyContent.GetComponent<RectTransform>().sizeDelta = new Vector2(historyContent.GetComponent<RectTransform>().sizeDelta.x,
                                                                             scoreHisotry.Count * State.historyPanel.GetComponent<RectTransform>().sizeDelta.y + scoreHisotry.Count * 20 + 100);

        pageHistory.SetActive(true);
        pageMain.SetActive(false);
    }
    else
    {
        // 销毁 content 除0外的所有子对象
        for (var i = 1; i < historyContent.transform.childCount; i++)
        {
            Destroy(historyContent.transform.GetChild(i).gameObject);
        }

        pageHistory.SetActive(false);
        pageMain.SetActive(true);
    }
}


金币部分字符串比较长,所以文本框对应的也要拉大一点,否则会导致显示不全的情况

这样,历史分数就制作完成了

历史分数效果



音乐制作

制作游戏音乐我使用的 Audition

非专业,仅仅让游戏有个声音就可以了


目前就制作以下几个声音就可以了

  • 首页背景音乐
  • 方块被销毁的声音
  • 游戏结束的声音

打开 Audtion,右键左上角空白处,新建,多轨混音项目,命名为background

新建


新建完后就可以看到右边出现了多个轨道


背景音乐制作

从无到制作音乐可以使用 效果 - 生成部分的几个选项

截图


在主轨道制作背景音乐主要的内容,背景音乐使用音调来制作,生成 - 音调

音乐会由多个音调组成,点击音调后,会要求创建一个音频文件,创建后就在音频文件内制作第一个音调

音调


凡是须先研究,才会明白,这里有基频,调制深度,调制速率三个调节器,将其都置为0,调节后点击左下角播放听听有什么不同,以下是我试出来的效果

基频过低/过高都没有声音,基频越小,声音越偏向 “嘟”,越大,声音就越尖锐

调制深度于调制速率都是配合基频使用,基频没声音调整这两个也没声音

调制深度于速率也是配合使用(自行体会)


我生产的第一个音调是基频200,深度80,速率100,右边频率为150,振幅0,时间0.5秒

第一个音调


一个轻微嘟的声音,在末尾继续插入一个音调,这次调高一点,还是嘟,基频2000,深度300,速率100,频率200,时间0.5秒,声音为赌度,这样再来一个第一个音调比较好 - 嘟度嘟,复制前0.5秒或将播放杆拉到最后照着第一个音调在生成一个

第四个音调调的更高一点,基频4000,深度400,速率150,频率240,时间依然是0.5

有了这四个音调,开启循环播放试下效果,发现还可以,除了音调过少外


接下来就靠着多轨来生成更多的声音了,靠着重叠和裁剪声音,给整体加个噪音,在多轨 - 节拍器启用节拍器...

最终声音效果如下



方块被摧毁的声音

新建一个音频文件

首先一个音调比较高的声音(长),然后一个音调低点的声音(短)

第一个音调,基频1500,深度1000,速率800,频率300,时间0.3秒

第二个音调,基频800,深度300,速率250,频率180,时间0.2秒

将第一个音频分贝减少5

效果


声音如下



游戏结束的声音

新建一个音频文件,游戏结束就一个很高的音调就可以了

就一个音调,基频18000,深度600,速率200,频率2200,持续时间0.3

再给其加个和声,效果 - 调制 - 和声,使用预设突发梦魇

效果


声音如下



给游戏加入音乐

音乐制作好,在Unity内Assets下新建一个文件夹Audio作为音乐的文件夹

将背景音乐拖到摄像机下,其余两个音乐也拖到摄像机下,并将两个音乐的物体拖到预制体内

编写一个脚本,用于定时销毁,命名为TimeDestory,代码如下

/// <summary>
/// 多少秒后销毁,50一秒
/// </summary>
public int totol = 50;

private void FixedUpdate()
{
    if (--totol < 0)
    {
        Destroy(gameObject);
    }
}

将脚本拖给两个声音预制体,然后在 State 中拿到预制体

public static GameObject audioBox = Resources.Load<GameObject>("Prefabs/方块摧毁的声音");
public static GameObject audioGameOver = Resources.Load<GameObject>("Prefabs/游戏结束的声音");

在Main中的游戏结束与销毁方块部分播放声音

Instantiate(State.audioBox, transform);

Instantiate(State.audioGameOver, transform);


静音

有了声音就需要提供静音功能,在主界面右上角制作

效果


在 State 中增加声音状态

/// <summary>
/// 声音是否开启
/// </summary>
public static bool isAudio = true;

在 Main 中增加点击事件

/// <summary>
/// 声音状态切换
/// </summary>
public void switchAudio()
{
    var obj = EventSystem.current.currentSelectedGameObject;
    if (State.isAudio)
    {
        obj.transform.GetChild(0).GetComponent<Text>().text = "开";
        AudioListener.volume = 0;
    }
    else
    {
        obj.transform.GetChild(0).GetComponent<Text>().text = "关";
        AudioListener.volume = 1;
    }
    State.isAudio = !State.isAudio;
}


本文链接:https://sdpro.top/blog/html/article/1073.html

♥ 赞助 ♥

尽管去做,或许最终的结果不尽人意,但你不付出,他不付出,那怎会进步呢?