Unity3D 屏幕点击特效

实现点击屏幕任意位置播放点击特效。

屏幕点击特效

需求

现有一个需求,点击屏幕任意位置,播放一个点击特效。

美术已经做好了特效,效果如图:

点击特效

特效容器

首先,画布是 Camera 模式,画布底下有一个 UIClickEffect 界面。

界面底下有一个 Effect 空节点,它带有 RectTransform 组件,后续会通过这个空节点设置特效的位置。

Effect 空节点底下则是美术制作好的特效。

画布

空节点

创建脚本

检测输入

创建 ScreenClickEffect.cs 脚本,添加特效设置相关的字段。

Start 的时候把特效预制体默认隐藏,防止初始化时会播放一次特效。

Update 中检测鼠标和触摸输入,把点击的位置 clickPosition 传入 PlayClickEffect 方法中。

using UnityEngine;public class ScreenClickEffect : MonoBehaviour
{[Header("特效设置")]public GameObject clickEffectPrefab;  // 特效预制体public float effectDuration = 1f;     // 特效持续时间void Start(){if (clickEffectPrefab != null)clickEffectPrefab.SetActive(false);}void Update(){HandleInput();}void HandleInput(){Vector3 clickPosition = Vector3.zero;bool hasInput = false;// 检测鼠标点击if (Input.GetMouseButtonDown(0)){clickPosition = Input.mousePosition;hasInput = true;}// 检测触摸输入(移动设备)if (Input.touchCount > 0){Touch touch = Input.GetTouch(0);if (touch.phase == TouchPhase.Began){clickPosition = touch.position;hasInput = true;}}if (hasInput){PlayClickEffect(clickPosition);}}void PlayClickEffect(Vector3 screenPosition){if (clickEffectPrefab == null){Debug.LogWarning("点击特效预制体未设置!");return;}}
}

实例化特效

添加节点组件字段,获得画布和界面的变换组件。

继续完善 PlayClickEffect 方法,使用 RectTransformUtility.ScreenPointToLocalPointInRectangle 把屏幕坐标转换成当前界面内的本地坐标,把转换后的 localPosition 赋值给实例化出来的特效对象。

最后,延迟 effectDuration 秒后销毁特效。

using UnityEngine;public class ScreenClickEffect : MonoBehaviour
{// ...[Header("节点组件")]public Canvas targetCanvas;           // 画布public RectTransform rectTransform;   // 界面变换组件void Start(){// ...if (targetCanvas == null)targetCanvas = FindObjectOfType<Canvas>();if (rectTransform == null)rectTransform = GetComponent<RectTransform>();}// ...void PlayClickEffect(Vector3 screenPosition){if (clickEffectPrefab == null){Debug.LogWarning("点击特效预制体未设置!");return;}if (targetCanvas == null){Debug.LogWarning("目标Canvas未设置!");return;}if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPosition, targetCanvas.worldCamera, out Vector2 localPosition)){// 实例化特效GameObject effectInstance;effectInstance = Instantiate(clickEffectPrefab, transform);effectInstance.SetActive(true);// 设置位置if (effectInstance.TryGetComponent<RectTransform>(out var effectRect)){effectRect.localPosition = localPosition;}else{effectInstance.transform.localPosition = (Vector3)localPosition;}// 延迟销毁特效Destroy(effectInstance, effectDuration);}}
}

节点组件的引用如图:

拖拽引用

运行游戏效果:

点击屏幕效果

对象池优化

因为屏幕点击特效会比较频繁地创建和销毁,可以使用 Unity 内置的对象池进行优化。

这里定义了一个 useObjectPool 字段,可以自行选择是否启用对象池,如果不用的话,就还是以创建销毁的方式播放点击特效。

关于对象池的容量设置,可参考以下建议:

  • 休闲游戏,容量4-8,启用对象池,平衡性能和内存
  • 音游/快节奏,容量10-20,启用对象池,优先保证流畅度
  • 低端设备,容量2-4,可关闭对象池,减少内存占用
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;public class ScreenClickEffect : MonoBehaviour
{// ...[Header("对象池设置")]public bool useObjectPool = true;     // 是否使用对象池public int defaultCapacity = 4;       // 对象池默认容量public int maxSize = 8;               // 对象池最大容量private ObjectPool<GameObject> effectPool;void Start(){// ...if (useObjectPool)SetupObjectPool();}// ...void SetupObjectPool(){if (clickEffectPrefab == null){Debug.LogWarning("无法设置对象池:特效预制体未设置!");return;}effectPool = new ObjectPool<GameObject>(createFunc: CreateEffect,actionOnGet: OnGetEffect,actionOnRelease: OnReleaseEffect,actionOnDestroy: OnDestroyEffect,collectionCheck: true,defaultCapacity: defaultCapacity,maxSize: maxSize);}GameObject CreateEffect(){GameObject effect = Instantiate(clickEffectPrefab, transform);effect.SetActive(false);return effect;}void OnGetEffect(GameObject effect){effect.SetActive(true);// 重置粒子系统if (effect.TryGetComponent<ParticleSystem>(out var particles)){particles.Clear();particles.Play();}}void OnReleaseEffect(GameObject effect){if (effect != null){effect.SetActive(false);// 停止粒子系统if (effect.TryGetComponent<ParticleSystem>(out var particles)){particles.Stop();particles.Clear();}}}void OnDestroyEffect(GameObject effect){if (effect != null){Destroy(effect);}}void OnDestroy(){// 清理对象池effectPool?.Dispose();}void PlayClickEffect(Vector3 screenPosition){// ...if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPosition, targetCanvas.worldCamera, out Vector2 localPosition)){GameObject effectInstance;// 使用对象池或传统方式创建特效if (useObjectPool && effectPool != null){effectInstance = effectPool.Get();}else{effectInstance = Instantiate(clickEffectPrefab, transform);effectInstance.SetActive(true);}// 设置位置if (effectInstance.TryGetComponent<RectTransform>(out var effectRect)){effectRect.localPosition = localPosition;}else{effectInstance.transform.localPosition = (Vector3)localPosition;}// 处理特效生命周期if (useObjectPool && effectPool != null){// 使用对象池,延迟归还StartCoroutine(ReleaseEffectAfterDelay(effectInstance, effectDuration));}else{// 传统方式销毁Destroy(effectInstance, effectDuration);}}}IEnumerator ReleaseEffectAfterDelay(GameObject effect, float delay){yield return new WaitForSeconds(delay);if (effect != null && effectPool != null){effectPool.Release(effect);}}
}

完整代码

ScreenClickEffect.cs

using System.Collections;
using UnityEngine;
using UnityEngine.Pool;public class ScreenClickEffect : MonoBehaviour
{[Header("特效设置")]public GameObject clickEffectPrefab;  // 特效预制体public float effectDuration = 1f;     // 特效持续时间[Header("节点组件")]public Canvas targetCanvas;           // 画布public RectTransform rectTransform;   // 界面变换组件[Header("对象池设置")]public bool useObjectPool = true;     // 是否使用对象池public int defaultCapacity = 4;       // 对象池默认容量public int maxSize = 8;               // 对象池最大容量private ObjectPool<GameObject> effectPool;void Start(){if (clickEffectPrefab != null)clickEffectPrefab.SetActive(false);if (targetCanvas == null)targetCanvas = FindObjectOfType<Canvas>();if (rectTransform == null)rectTransform = GetComponent<RectTransform>();if (useObjectPool)SetupObjectPool();}void SetupObjectPool(){if (clickEffectPrefab == null){Debug.LogWarning("无法设置对象池:特效预制体未设置!");return;}effectPool = new ObjectPool<GameObject>(createFunc: CreateEffect,actionOnGet: OnGetEffect,actionOnRelease: OnReleaseEffect,actionOnDestroy: OnDestroyEffect,collectionCheck: true,defaultCapacity: defaultCapacity,maxSize: maxSize);}GameObject CreateEffect(){GameObject effect = Instantiate(clickEffectPrefab, transform);effect.SetActive(false);return effect;}void OnGetEffect(GameObject effect){effect.SetActive(true);// 重置粒子系统if (effect.TryGetComponent<ParticleSystem>(out var particles)){particles.Clear();particles.Play();}}void OnReleaseEffect(GameObject effect){if (effect != null){effect.SetActive(false);// 停止粒子系统if (effect.TryGetComponent<ParticleSystem>(out var particles)){particles.Stop();particles.Clear();}}}void OnDestroyEffect(GameObject effect){if (effect != null){Destroy(effect);}}void Update(){HandleInput();}void HandleInput(){Vector3 clickPosition = Vector3.zero;bool hasInput = false;// 检测鼠标点击if (Input.GetMouseButtonDown(0)){clickPosition = Input.mousePosition;hasInput = true;}// 检测触摸输入(移动设备)if (Input.touchCount > 0){Touch touch = Input.GetTouch(0);if (touch.phase == TouchPhase.Began){clickPosition = touch.position;hasInput = true;}}if (hasInput){PlayClickEffect(clickPosition);}}void PlayClickEffect(Vector3 screenPosition){if (clickEffectPrefab == null){Debug.LogWarning("点击特效预制体未设置!");return;}if (targetCanvas == null){Debug.LogWarning("目标Canvas未设置!");return;}if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPosition, targetCanvas.worldCamera, out Vector2 localPosition)){GameObject effectInstance;// 使用对象池或传统方式创建特效if (useObjectPool && effectPool != null){effectInstance = effectPool.Get();}else{effectInstance = Instantiate(clickEffectPrefab, transform);effectInstance.SetActive(true);}// 设置位置if (effectInstance.TryGetComponent<RectTransform>(out var effectRect)){effectRect.localPosition = localPosition;}else{effectInstance.transform.localPosition = (Vector3)localPosition;}// 处理特效生命周期if (useObjectPool && effectPool != null){// 使用对象池,延迟归还StartCoroutine(ReleaseEffectAfterDelay(effectInstance, effectDuration));}else{// 传统方式销毁Destroy(effectInstance, effectDuration);}}}IEnumerator ReleaseEffectAfterDelay(GameObject effect, float delay){yield return new WaitForSeconds(delay);if (effect != null && effectPool != null){effectPool.Release(effect);}}void OnDestroy(){// 清理对象池effectPool?.Dispose();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/86097.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MCU编程

MCU 编程基础&#xff1a;概念、架构与实践 一、什么是 MCU 编程&#xff1f; MCU&#xff08;Microcontroller Unit&#xff0c;微控制器&#xff09; 是将 CPU、内存、外设&#xff08;如 GPIO、UART、ADC&#xff09;集成在单一芯片上的小型计算机系统。MCU 编程即针对这些…

Go语言--语法基础6--基本数据类型--数组类型(1)

Go 语言提供了数组类型的数据结构。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列&#xff0c;这种类型可以是任意的 原始类型例如整型、字符串或者自定义类型。相对于去声明number0,number1, ..., and number99 的变量&#xff0c;使用数组形式 numbers[0], …

左神算法之给定一个数组arr,返回其中的数值的差值等于k的子数组有多少个

目录 1. 题目2. 解释3. 思路4. 代码5. 总结 1. 题目 给定一个数组arr&#xff0c;返回其中的数值的差值等于k的子数组有多少个 2. 解释 略 3. 思路 直接用hashSet进行存储&#xff0c;查这个值加上k后的值是否在数组中 4. 代码 public class Problem01_SubvalueEqualk {…

自回归(AR)与掩码(MLM)的核心区别:续写还是补全?

自回归(AR)与掩码(MLM)的核心区别:用例子秒懂 一、核心机制对比:像“续写”还是“完形填空”? 维度自回归(Autoregressive)掩码语言模型(Masked LM)核心目标根据已生成的token,预测下一个token(顺序生成)预测句子中被“掩码”的token(补全缺失信息)输入输出输入…

后端开发两个月实习总结

前言 本人目前在一家小公司后端开发实习差不多两个月了&#xff0c;现在准备离职了&#xff0c;就这两个月的实习经历写下这篇文章&#xff0c;既是对自己实习的一个总结&#xff0c;也是给正在找实习的小伙伴以及未来即将进入到后端开发这个行业的同学的分享一下经验。 一、个…

Python基础(​​FAISS​和​​Chroma​)

​​1. 索引与查询性能​ ​​指标​​​​FAISS​​​​Chroma​​​​分析​​​​索引构建速度​​72.4秒&#xff08;5551个文本块&#xff09;91.59秒&#xff08;相同数据集&#xff09;FAISS的底层优化&#xff08;如PQ量化&#xff09;加速索引构建&#xff0c;适合批…

Windows下memcpy_s如何在Linux下使用

Windows下代码如下 memcpy_s(pLine->ppBuf[i], m_ColorLineByte, pIn nOffset, m_ColorLineByte); 方案 1&#xff1a;使用标准 memcpy 手动检查&#xff08;最通用&#xff09; // 检查参数有效性 if (pLine->ppBuf[i] nullptr || pIn nullptr || m_ColorLi…

2025年数学算法与自动化控制国际会议(ICMAAC 2025)

2025年数学算法与自动化控制国际会议&#xff08;ICMAAC 2025&#xff09; 2025 International Conference on Mathematical Algorithms and Automation Control 一、大会信息 会议简称&#xff1a;ICMAAC 2025 大会地点&#xff1a;中国长沙 审稿通知&#xff1a;投稿后2-3日…

C语言数组介绍 -- 一维数组和二维数组的创建、初始化、下标、遍历、存储,C99 变长数组

目录 1. 一维数组 1.1 数组的概念 1.2 一维数组的创建 1.3 一维数组的初始化 1.4 数组的类型 1.5 数组下标 1.5.1 数组元素的遍历 1.5.2 数组的输入 1.6 一维数组在内存中的存储 1.7 sizeof 计算数组元素个数 2. 二维数组 2.1 二维数组的创建 2.2 二维数组的初始…

SpringAI + DeepSeek大模型应用开发 - 进阶篇(上)

三、SpringAI 2. 哄哄模拟器 2.1 提示词工程 提示词工程&#xff08;Prompt Engineering&#xff09;&#xff1a;通过优化提示词&#xff0c;使大模型生成尽可能理想的内容&#xff0c;这一过程就叫提示词工程。 &#xff08;1&#xff09;清晰明确的指令 谈谈人工智能 …

Spring Boot实现异常处理

Spring Boot 提供了多种灵活的方式实现异常处理&#xff0c;以下是核心方案和最佳实践&#xff1a; 一、基础异常处理方案 1. ControllerAdvice ExceptionHandler&#xff08;全局处理&#xff09; ControllerAdvice public class GlobalExceptionHandler {// 处理特定异常&…

【目标检测】IOU的概念与Python实例解析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

Vue2中如何使用vue-print-nb打印功能

插件官网地址&#xff1a;vue-print-nb - npm 1.安装 npm install vue-print-nb --save 2.导入打印插件 //main.js import Print from vue-print-nb Vue.use(Print); 3.配置参数 4.页面使用 <div id"printDiv">打印内容</div><el-button v-print&…

Matplotlib快速入门

目录 基本使用 解决中文乱码 一个坐标系绘制多个图像 多个坐标系绘制 基本使用 什么是Matplotlib 是专门用于开发2D图表(包括3D图表)以渐进&#xff0c;交互式方式实现数据可视化 为什么要学习matplotlib 可视化是在整个数据挖掘的关键辅助工具&#xff0c;可以清晰的理解…

扣料不允许‘货物移动’

遇到了报错&#xff0c;不允许货物移动 以为又是和之前一样是订单已经关闭&#xff0c;看是领错料还是财务误关的原因&#xff0c;但是co03一看订单状态并没有关闭 原因就是这个CRTD 订单只是创建了&#xff0c;但是没有下达 找个正常的看看&#xff1a; 一般订单创建和下达都…

【AI】全新AI测试系列之二--------AI自动化测试,提高测试效率

目录 一、自动化测试 1、与手动测试对比 2、自动化测试流程 二、自动化测试环境搭建 三、web自动化使用AI的两种方式 1、利用DeepSeek快速生成脚本 2、pycharm集成通义灵码 四、通义灵码实战 1、使用提示词生成代码 2、使用pytest框架 前言&#xff1a;上一章节只要是…

npm包冲突install失败

--legacy-peer-deps是npm&#xff08;Node.js包管理器&#xff09;的一个命令行选项&#xff0c;主要用于解决依赖冲突问题。当安装依赖时&#xff0c;npm默认会严格检查peer dependencies&#xff08;对等依赖&#xff09;的版本兼容性&#xff0c;可能导致安装失败。启用此选…

68、数据访问-crud实验-删除用户完成

68、数据访问-crud实验-删除用户完成 以下是完成“数据访问-CRUD实验-删除用户”功能的一般步骤&#xff0c;以常见Web应用框架&#xff08;如Spring Boot MyBatis-Plus、Django、Ruby on Rails&#xff09;为例&#xff1a; #### 准备工作 - **数据库表设计**&#xff1a;确…

实现 TurtleBot3 多点轨迹跟踪导航

系统架构 move_base本身不支持一次性发送多个目标点并自动按顺序导航,使用nav_msgs/Path消息类型发布多个路径点,然后让机器人按顺序依次到达每个路径点。 发布一个包含多个路径点的Path消息(可选,用于在RVIZ中显示路径)。按顺序将每个路径点作为MoveBaseGoal发送给move_…

《人性的优点》:破解忧虑密码,构建积极人生

我强烈推荐4本可以改变命运的经典著作&#xff1a; 《寿康宝鉴》在线阅读白话文《欲海回狂》在线阅读白话文《阴律无情》在线阅读白话文《了凡四训》在线阅读白话文 一、世界观&#xff1a;忧虑的本质与生命的真相 &#xff08;一&#xff09;忧虑是精神的“虚构苦难” 卡耐基…