文章目录
一、前言
嗨,大家好,我是新发。下班坐地铁的时候,好几次看到其他人在玩消消乐,既然大家都这么喜欢玩,那我就写个Unity
制作水果消消乐的教程吧。
我会根据内容点分成好几篇文章来讲,希望对想学Unity
的同学有所帮助,创作不易,喜欢的同学欢迎关注、点赞、收藏,文章目录如下:
第一篇:生成冰块阵列
第二篇:随机生成水果
第三篇:水果拖动与交换逻辑
第四篇:使用DOTween插件实现水果的滑动效果
第五篇:水果的消除检测,实现消除效果
第六篇:水果下落与新水果生成
第七篇:水果消除特效
第八篇:游戏得分加分效果
第九篇:使用UGUI显示游戏UI
游戏运行效果如下:
最终的Demo
工程已上传到GitHub
,感兴趣的同学可以自行下载下来学习。
GitHub
地址:https://github.com/linxinfa/UnityXiaoXiaoLeDemo
注:我使用的Unity
版本为2020.1.14f1c1
。
本篇讲水果消除特效,本篇的效果:
二、导入特效素材
导入特效图片到Unity
工程中。
三、制作序列帧动画
创建一个Animations
文件夹,用来放动画文件。
选中所有的特效图片,拖到Hierarchy
窗口中,此时会弹出一个窗口保存Animation
文件,我们保存到刚刚的Animations
文件夹中,命名为DisappearEffect.anim
。
我们刚刚拖动到场景中的图片,最终会以SpriteRenderer
的方式显示,并且自动带了一个Animator
组件。
再看回Animations
文件夹,这里有两个文件,第一个就是Animator
组件的controller
文件,第二个就是DisappearEffect.anim
动画文件,我们的序列帧动画就是在这个anim
动画文件中。
双击打开打开DisappearEffect.anim
文件即可打开动画窗口,选中场景中的SpriteRenderer
物体,再点击Animation
窗口中的播放按钮,即可在场景中预览动画效果。
四、调整序列帧帧率
生成的序列帧动画默认是12帧每秒,我们可以在Animation
窗口右上角的...
按钮处点击弹出菜单,勾选Show Sample Rate
,然后就可以调节帧率了。
我们改为30帧每秒。
五、保存特效序列帧预设
将场景中的特效节点重命名为DisappearEffect
,然后保存为预设,存放到GameRes
文件夹中。
六、特效生成器EffectSpawner
先在场景中创建一个空物体:EffectSpawner
。
创建一个EffectSpawner.cs
脚本。
写代码之前,我们先思考一下,水果消除时播放这个特效,特效播放完后是直接销毁掉特效对象吗?当然,我们可以直接销毁,但是由于这个特效在游戏过程中会频繁显示,所以这里我们最好是做成对象池:
1 想要显示特效时,先从对象池里看是否对象,如果有则直接取出来使用;
2 如果对象池中没有对象,则实例化一个对象出来;
3 特效播放完毕之后,将特效隐藏并塞回对象池中;
所以,我们先定义一个队列作为对象池的容器。
// EffectSpawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectSpawner : MonoBehaviour
{
/// <summary>
/// 水果消除特效预设
/// </summary>
public GameObject disappearEffectPrefab;
/// <summary>
/// 特效对象池
/// </summary>
private Queue<GameObject> m_disappearEffectPool = new Queue<GameObject>();
}
接着,我们就可以写一个显示特效的函数了。
// EffectSpawner.cs
/// <summary>
/// 显示水果消除特效
/// </summary>
/// <param name="pos">坐标</param>
public void ShowDisappearEffect(Vector3 pos)
{
GameObject obj;
if (m_disappearEffectPool.Count > 0)
obj = m_disappearEffectPool.Dequeue();
else
{
obj = Instantiate(disappearEffectPrefab);
// TODO ...
}
obj.SetActive(true);
obj.transform.position = pos;
}
我上面留了一个TODO
,我们还差做什么呢?
1 实例化出来的对象,需要设置一下父节点,方便管理;
2 我们还需要监听特效播放完毕的事件,当特效播放完毕后需要回收特效对象到对象池中。
先完成第一个。
// EffectSpawner.cs
private Transform m_effectRoot;
private void Awake()
{
m_effectRoot = transform;
}
/// <summary>
/// 显示水果消除特效
/// </summary>
/// <param name="pos">坐标</param>
public void ShowDisappearEffect(Vector3 pos)
{
GameObject obj;
if (m_disappearEffectPool.Count > 0)
obj = m_disappearEffectPool.Dequeue();
else
{
obj = Instantiate(disappearEffectPrefab);
// 设置父节点
obj.transform.SetParent(m_effectRoot, false);
// TODO 监听动画播放完毕,回收到对象池中
}
obj.SetActive(true);
obj.transform.position = pos;
}
七、动画帧事件,监听动画播放完毕
动画帧事件,只能调用动画所在物体的组件的public
函数,我们先创建一个AnimationEvent.cs
脚本。
在脚本中定义一个public
函数,作为帧事件的响应函数,并提供一个委托供外部使用。
using UnityEngine;
using System;
public class AnimationEvent: MonoBehaviour
{
/// <summary>
/// 委托
/// </summary>
public Action<string> aniEventCb;
/// <summary>
/// 动画帧事件响应函数
/// </summary>
public void OnAnimationEvent(string str)
{
// 调用委托
if (null != aniEventCb)
aniEventCb(str);
}
}
将脚本挂到之前做的DisappearEffect
预设上。
双击打开动画文件,在最后一帧插入帧事件,选中帧事件,填写响应函数为OnAnimationEvent
,填写String
参数为finish
。
现在,我们就可以监听动画播放完毕的事件并回收对象到对象池了。
// EffectSpawner.cs
/// <summary>
/// 显示水果消除特效
/// </summary>
/// <param name="pos">坐标</param>
public void ShowDisappearEffect(Vector3 pos)
{
GameObject obj;
if (m_disappearEffectPool.Count > 0)
obj = m_disappearEffectPool.Dequeue();
else
{
obj = Instantiate(disappearEffectPrefab);
obj.transform.SetParent(m_effectRoot, false);
// 监听动画帧事件
var bhv = obj.GetComponent<AnimationEvent>();
bhv.aniEventCb = (str) =>
{
if("finish" == str)
{
// 动画播放结束,回收对象到对象池中
obj.SetActive(false);
m_disappearEffectPool.Enqueue(obj);
}
};
}
obj.SetActive(true);
obj.transform.position = pos;
}
八、调用特效
水果消除的逻辑我们是写在FruitItem
中的。
// FruitItem.cs
/// <summary>
/// 销毁水果图片
/// </summary>
public void DestroyFruitBg()
{
Destroy(fruitSpriteObj);
fruitSpriteObj = null;
}
我们可以在这里抛出一个事件,然后在EffectSpawner
中响应这个事件,再调用特效的显示。
定义一个EVENT_FRUIT_DISAPPEAR
事件。
// EventDef.cs
public class EventDef
{
// ...
/// <summary>
/// 水果消除
/// </summary>
public const string EVENT_FRUIT_DISAPPEAR = "EVENT_FRUIT_DISAPPEAR";
}
FruitItem
抛出事件。
// FruitItem.cs
/// <summary>
/// 销毁水果图片
/// </summary>
public void DestroyFruitBg()
{
Destroy(fruitSpriteObj);
fruitSpriteObj = null;
// 抛出事件
EventDispatcher.instance.DispatchEvent(EventDef.EVENT_FRUIT_DISAPPEAR, m_selfTransform.position);
}
EffectSpawner
订阅事件,调用特效。
// EffectSpawner.cs
private void Awake()
{
m_effectRoot = transform;
EventDispatcher.instance.Regist(EventDef.EVENT_FRUIT_DISAPPEAR, OnFruitDisappear);
}
private void OnDestroy()
{
EventDispatcher.instance.UnRegist(EventDef.EVENT_FRUIT_DISAPPEAR, OnFruitDisappear);
}
private void OnFruitDisappear(params object[] args)
{
ShowDisappearEffect((Vector3)args[0]);
}
九、挂脚本EffectSpawner
场景中的EffectSpawner
物体,挂上EffectSpawner
脚本,并赋值预设对象到参数中。
十、运行测试
运行Unity
,测试效果如下,可以看到,特效物体是复用的,达到了我们的目的。
下一篇讲下得分加分效果的实现。
[点击进入下一篇]