最终效果

在这里插入图片描述

前言

要实现一个PC和移动设备都支持的卡牌放置、物品合成升级、装备等功能,重点就是要实现PC和移动设备都支持的2D对象拖拽功能,第一步当然就是选择合适的拖拽方法。

之前我们在【unity几种常用的拖拽方法】文章里提到过一种方法,利用Unity的EventSystem系统可以实现PC和多点触摸、移动设备上的触摸拖拽操作,对于非 GUI 元素的拖拽,也很容易实现。但是我们之前只介绍了如何在UGUI上使用它,没有介绍实现2D对象的拖拽功能。

该方法需要继承 UnityEngine.EventSystems里的IBeginDragHandlerIDragHandlerIEndDragHandler接口,并实现接口中的OnBeginDragOnDragOnEndDrag方法,从而接收该界面上的物体的拖拽操作。

注意事项

  • 确保物体上有Collider2D组件

  • 场景中需要有EventSystem对象(新建UI时会自动创建)

  • 主相机需要有Physics2D Raycaster组件

实战

1、实现拖拽功能

1.1 随便在2D场景中放置一个2D物品

这里我放一个卡牌,记得给卡牌添加合适的Collider2D碰撞器组件
在这里插入图片描述

1.2 在场景中新建EventSystem对象

在这里插入图片描述

1.3 给主相机添加Physics2D Raycaster组件

在这里插入图片描述
Physics2D Raycaster 是 Unity 中 EventSystem 系统的一个关键组件,专门用于处理 2D 场景中的 UI 和物体交互。如果没有它,UI事件监听接口仅仅对UGUI生效而已。

主要作用

  • 将输入事件(点击、拖拽等)投射到 2D 物体上:

    • 将屏幕上的触摸/鼠标位置转换为 2D 世界坐标

    • 检测这些位置下的 2D 碰撞体(Collider2D)

  • 桥接 Unity 的 UI 事件系统和 2D 物理系统:

    • 使得 OnPointerClick, OnDrag 等 UI 事件能在 2D 物体上工作

    • 支持所有实现了 IPointerXXXHandler 接口的脚本

Physics2D Raycaster 本质上就是自动帮你完成下面这种射线检测并与事件系统对接的组件。

// 手动检测2D射线点击
void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        Vector2 rayPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero);
        
        if (hit.collider != null)
        {
            Debug.Log("点击了: " + hit.collider.name);
        }
    }
}

1.4 书写代码处理拖拽和放置逻辑

using UnityEngine;
using UnityEngine.EventSystems;

// 卡牌基类,处理拖拽和放置逻辑
public class Card : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Vector3 startDragPosition; // 记录拖拽起始位置
    private Vector2 offset; // 存储偏移量

    private void Start()
    {
        mainCam = Camera.main;
    }

    // 开始拖拽时
    public void OnBeginDrag(PointerEventData eventData)
    {
        startDragPosition = transform.position; // 记录初始位置
        
		// 计算偏移:鼠标世界坐标与卡牌位置的差值
        Vector2 mouseWorldPos = mainCam.ScreenToWorldPoint(eventData.position);
        offset = (Vector2)transform.position - mouseWorldPos;
    }

    // 拖拽时
    public void OnDrag(PointerEventData eventData)
    {
        // 跟随鼠标移动 = 鼠标位置 + 偏移量(防止卡牌中心直接跳到鼠标位置)
        Vector2 mouseWorldPos = mainCam.ScreenToWorldPoint(eventData.position);
        transform.position = mouseWorldPos + offset;
    }

    // 结束拖拽时
    public void OnEndDrag(PointerEventData eventData)
    {
       // 返回起始位置
       transform.position = startDragPosition;
    }
}

1.5 挂载脚本,运行游戏

在PC中实现拖拽效果
在这里插入图片描述
在移动端手机设备实现拖拽
在这里插入图片描述

2、卡牌放置不同区域触发不同效果

2.1 新增卡牌可放置区域的接口

因为我们会有定义很多不同的区域效果,我们可以先定义一个统一的卡牌可放置区域的接口来区分它们,然后不同区域脚本都基础这个接口

// 卡牌可放置区域的接口
public interface ICardDropArea
{
    void OnCardDrop(Card card); // 卡牌放置时的回调方法
}

2.2 不同放置区域的实现

左侧放置区域实现

using UnityEngine;

// 左侧放置区域实现
public class LeftCardDropArea : MonoBehaviour, ICardDropArea
{
    // 卡牌放置时的处理
    public void OnCardDrop(Card card)
    {
        card.transform.position = transform.position; // 将卡牌对齐到该区域中心
    }
}

右侧放置区域实现

using UnityEngine;

// 右侧放置区域实现
public class RightCardDropArea : MonoBehaviour, ICardDropArea
{
    [SerializeField] private GameObject objectToSpawn; // 需要生成的物体预制体

    // 卡牌放置时的处理
    public void OnCardDrop(Card card)
    {
        Destroy(card.gameObject); // 销毁放置的卡牌
        Instantiate(objectToSpawn, transform.position, transform.rotation); // 生成新物体
    }
}

添加不同的碰撞区域,挂载脚本
在这里插入图片描述

2.3 修改Card脚本,调用不同区域的放置逻辑

using UnityEngine;
using UnityEngine.EventSystems;

// 卡牌基类,处理拖拽和放置逻辑
public class Card : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Collider2D col;          // 卡牌的碰撞体
    private Vector3 startDragPosition; // 记录拖拽起始位置
    private Vector2 offset; // 存储偏移量
    private Camera mainCam;

    private void Start()
    {
        col = GetComponent<Collider2D>();
        mainCam = Camera.main;
    }

    // 开始拖拽时
    public void OnBeginDrag(PointerEventData eventData)
    {
        startDragPosition = transform.position; // 记录初始位置

        // 计算偏移:鼠标世界坐标与卡牌位置的差值
        Vector2 mouseWorldPos = mainCam.ScreenToWorldPoint(eventData.position);
        offset = (Vector2)transform.position - mouseWorldPos;

        // 临时禁用碰撞体,避免检测自身,避免干扰
        col.enabled = false;
    }

    // 拖拽时
    public void OnDrag(PointerEventData eventData)
    {
        // 跟随鼠标移动 = 鼠标位置 + 偏移量(防止卡牌中心直接跳到鼠标位置)
        Vector2 mouseWorldPos = mainCam.ScreenToWorldPoint(eventData.position);
        transform.position = mouseWorldPos + offset;
    }

    // 结束拖拽时
    public void OnEndDrag(PointerEventData eventData)
    {
        // 使用eventData.pointerCurrentRaycast获取当前鼠标下的碰撞体
        var hitObject = eventData.pointerCurrentRaycast.gameObject;

        if (hitObject != null && hitObject.TryGetComponent(out ICardDropArea cardDropArea))
        {
            // 如果检测到可放置区域,调用其放置逻辑
            cardDropArea.OnCardDrop(this);
        }
        else
        {
            // 没有可放置区域则返回起始位置
            transform.position = startDragPosition;
        }

        col.enabled = true;
    }
}

2.4 运行效果

在这里插入图片描述


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】
【unity游戏开发——编辑器扩展】
【unity游戏开发——热更新】
【unity游戏开发——网络】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐