【游戏开发实战】使用Unity制作水果消消乐游戏教程(三):水果拖动与交换逻辑

一、前言

嗨,大家好,我是新发。下班坐地铁的时候,好几次看到其他人在玩消消乐,既然大家都这么喜欢玩,那我就写个Unity制作水果消消乐的教程吧。

我会根据内容点分成好几篇文章来讲,希望对想学Unity的同学有所帮助,创作不易,喜欢的同学欢迎关注、点赞、收藏,文章目录如下:
第一篇:生成冰块阵列
第二篇:随机生成水果
第三篇:水果拖动与交换逻辑
第四篇:使用DOTween插件实现水果的滑动效果
第五篇:水果的消除检测,实现消除效果
第六篇:水果下落与新水果生成
第七篇:水果消除特效
第八篇:游戏得分加分效果
第九篇:使用UGUI显示游戏UI

游戏运行效果如下:
在这里插入图片描述
最终的Demo工程已上传到GitHub,感兴趣的同学可以自行下载下来学习。
GitHub地址:https://github.com/linxinfa/UnityXiaoXiaoLeDemo
注:我使用的Unity版本为2020.1.14f1c1
在这里插入图片描述


本篇讲水果拖动与交换的实现,本篇的效果:
在这里插入图片描述

二、事件管理器

我们想要监听水果被点击的事件,那么我们先封装一个事件管理器,可以用来订阅事件和抛出事件。
创建一个EventDispatcher.cs脚本。
在这里插入图片描述
代码如下:

// EventDispatcher.cs

using UnityEngine;
using System.Collections.Generic;

public delegate void MyEventHandler(params object[] objs);

/// <summary>
/// 事件管理器,订阅事件与事件触发
/// </summary>
public class EventDispatcher
{
    /// <summary>
    /// 订阅事件
    /// </summary>
    public void Regist(string eventName, MyEventHandler handler)
    {
        if (handler == null)
            return;

        if (!listeners.ContainsKey(eventName))
        {
            listeners.Add(eventName, new Dictionary<int, MyEventHandler>());
        }
        var handlerDic = listeners[eventName];
        var handlerHash = handler.GetHashCode();
        if (handlerDic.ContainsKey(handlerHash))
        {
            handlerDic.Remove(handlerHash);
        }
        listeners[eventName].Add(handler.GetHashCode(), handler);
    }

    /// <summary>
    /// 注销事件
    /// </summary>
    public void UnRegist(string eventName, MyEventHandler handler)
    {
        if (handler == null)
            return;

        if (listeners.ContainsKey(eventName))
        {
            listeners[eventName].Remove(handler.GetHashCode());
            if (null == listeners[eventName] || 0 == listeners[eventName].Count)
            {
                listeners.Remove(eventName);
            }
        }
    }

    /// <summary>
    /// 触发事件
    /// </summary>
    public void DispatchEvent(string eventName, params object[] objs)
    {
        if (listeners.ContainsKey(eventName))
        {
            var handlerDic = listeners[eventName];
            if (handlerDic != null && 0 < handlerDic.Count)
            {
                var dic = new Dictionary<int, MyEventHandler>(handlerDic);
                foreach (var f in dic.Values)
                {
                    try
                    {
                        f(objs);
                    }
                    catch (System.Exception ex)
                    {
                        Debug.LogErrorFormat(szErrorMessage, eventName, ex.Message, ex.StackTrace);
                    }
                }
            }
        }
    }


    /// <summary>
    /// 清空事件
    /// </summary>
    /// <param name="key"></param>
    public void ClearEvents(string eventName)
    {
        if (listeners.ContainsKey(eventName))
        {
            listeners.Remove(eventName);
        }
    }

    private Dictionary<string, Dictionary<int, MyEventHandler>> listeners = new Dictionary<string, Dictionary<int, MyEventHandler>>();
    private readonly string szErrorMessage = "DispatchEvent Error, Event:{0}, Error:{1}, {2}";

    private static EventDispatcher s_instance;
    public static EventDispatcher instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new EventDispatcher();
            return s_instance;
        }
    }
}

接着,我们创建一个EventDef.cs用来定义事件。
在这里插入图片描述

三、水果点击事件

我们定义一个EVENT_FRUIT_SELECTED事件。

// EventDef.cs

public class EventDef
{
    /// <summary>
    /// 水果被点击
    /// </summary>
    public const string EVENT_FRUIT_SELECTED = "EVENT_FRUIT_SELECTED";
}

接着,我们在FruitItem.cs中添加OnMouseDown函数,抛出EVENT_FRUIT_SELECTED事件。

// FruitItem.cs

/// <summary>
/// 水果被点击
/// </summary>
private void OnMouseDown()
{
    // 抛出事件
    EventDispatcher.instance.DispatchEvent(EventDef.EVENT_FRUIT_SELECTED, this);
}

接着,我们在FruitSpawner.cs中添加事件的订阅。

// FruitSpawner.cs

/// <summary>
/// 被选中的水果
/// </summary>
private FruitItem m_curSelectFruit;

private void Awake()
{
	// 注册事件
    EventDispatcher.instance.Regist(EventDef.EVENT_FRUIT_SELECTED, OnFruitSelected);
}

private void OnDestroy()
{
	// 注销事件
    EventDispatcher.instance.UnRegist(EventDef.EVENT_FRUIT_SELECTED, OnFruitSelected);
}

/// <summary>
/// 水果被点击
/// </summary>
private void OnFruitSelected(params object[] args)
{
    // 把被点击的水果对象缓存起来,下面交换水果的逻辑需要用到
    m_curSelectFruit = args[0] as FruitItem;
}

四、水果拖动交换

我们分析一下操作过程,我们点击到水果后,就是滑动,水平滑动或者竖直滑动,如果往左滑则与左边的水果交换。
我们可以在Update函数中判断滑动。

// FruitSpawner.cs

/// <summary>
/// 手指水平滑动量
/// </summary>
private float m_fingerMoveX;
/// <summary>
/// 手指竖直滑动量
/// </summary>
private float m_fingerMoveY;

private void Update()
{
     if (null == m_curSelectFruit) return;
     if (Input.GetMouseButtonUp(0))
     {
         // 手指抬起,释放当前选中的水果对象
         m_curSelectFruit = null;
         return;
     }


#if UNITY_EDITOR || UNITY_STANDALONE
     if (Input.GetMouseButton(0))
#else
     if(1 == Input.touchCount && Input.touches[0].phase == TouchPhase.Moved)
#endif
     {
         m_fingerMoveX = Input.GetAxis("Mouse X");
         m_fingerMoveY = Input.GetAxis("Mouse Y");
     }


     // 滑动量太小,不处理
     if (Mathf.Abs(m_fingerMoveX) < 0.3f && Mathf.Abs(m_fingerMoveY) < 0.3f)
         return;

     OnFruitMove();

     m_fingerMoveX = 0;
     m_fingerMoveY = 0;
 }

 /// <summary>
 /// 水果滑动响应
 /// </summary>
 private void OnFruitMove()
 {
    // TODO
 }

OnFruitMove中处理水果的交换,交换之前,我们得先封装两个接口。

// FruitSpawner.cs

/// <summary>
/// 根据行号列号获取水果对象
/// </summary>
private FruitItem GetFruitItem(int rowIndex, int columIndex)
{
    if (rowIndex < 0 || rowIndex >= fruitList.Count) return null;
    var temp = fruitList[rowIndex] as ArrayList;
    if (columIndex < 0 || columIndex >= temp.Count) return null;
    return temp[columIndex] as FruitItem;
}

/// <summary>
/// 根据行号列号设置水果对象
/// </summary>
private void SetFruitItem(int rowIndex, int columIndex, FruitItem item)
{
    var temp = fruitList[rowIndex] as ArrayList;
    temp[columIndex] = item;
}

现在,我们可以实现OnFruitMove函数体了。

// FruitSpawner.cs

/// <summary>
/// 水果滑动响应
/// </summary>
private void OnFruitMove()
{
    if (Mathf.Abs(m_fingerMoveX) > Mathf.Abs(m_fingerMoveY))
    {
        //横向滑动
        var targetItem = GetFruitItem(m_curSelectFruit.rowIndex, m_curSelectFruit.columIndex + (m_fingerMoveX > 0 ? 1 : -1));
        if (null != targetItem)
        {
            StartCoroutine(ExchangeAndMatch(m_curSelectFruit, targetItem));
        }
        else
        {
            m_curSelectFruit = null;
        }
    }
    else if (Mathf.Abs(m_fingerMoveX) < Mathf.Abs(m_fingerMoveY))
    {
        //纵向滑动
        var targetItem = GetFruitItem(m_curSelectFruit.rowIndex + (m_fingerMoveY > 0 ? 1 : -1), m_curSelectFruit.columIndex);
        if (null != targetItem)
        {
            StartCoroutine(ExchangeAndMatch(m_curSelectFruit, targetItem));
        }
        else
        {
            m_curSelectFruit = null;
        }
    }
}

/// <summary>
/// 交换水果并检测是否可以消除
/// </summary>
IEnumerator ExchangeAndMatch(FruitItem item1, FruitItem item2)
{
    // 交换水果
	Exchange(item1, item2);
	// TODO 检测是否可消除
	
    yield return null;
}

要交换水果,我们封装一个交换水果的方法。

// FruitSpawner.cs

/// <summary>
/// 交换水果
/// </summary>
private void Exchange(FruitItem item1, FruitItem item2)
{

    SetFruitItem(item1.rowIndex, item1.columIndex, item2);
    SetFruitItem(item2.rowIndex, item2.columIndex, item1);

    int tmp = 0;
    tmp = item1.rowIndex;
    item1.rowIndex = item2.rowIndex;
    item2.rowIndex = tmp;

    tmp = item1.columIndex;
    item1.columIndex = item2.columIndex;
    item2.columIndex = tmp;

    item1.UpdatePosition(item1.rowIndex, item1.columIndex);
    item2.UpdatePosition(item2.rowIndex, item2.columIndex);

    m_curSelectFruit = null;
}

五、运行测试

运行Unity,测试效果如下:
在这里插入图片描述
我们可以看到,水果交换的时候,是瞬间换过去的,没有一个滑动的过程。
下一篇讲使用DOTween插件来实现水果的滑动效果。
[点击进入下一篇]

  • 19
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值