Unity 套圈捕捉 UI 实现分享

期望表现效果

《拼贴冒险传 / PatchQuest》 捕捉进度 动态UI

实现效果

实现效果

  • 目标:角色 A 套圈怪物 B,进度环显示围绕角度。
  • 技术点:Shader 绘制椭圆环,支持描边、顺/逆时针,需要对两个切口也进行描边。

技术需求 & 准备

  • Unity
  • RawImage + 自定义 Shader
  • Canvas 设置为 World Space,UI 跟随敌人
  • C# 脚本控制进度和方向

UI预制体的层级结构
Ui预制体结构


捕捉逻辑

  1. 玩家位置与敌人位置计算方向向量。
  2. 计算 DeltaAngle,累积角度。
  3. 正负值表示顺/逆时针。
  4. LassoUI GameObject 始终对齐敌人位置。

`

PlayerController.cs捕捉逻辑实现

核心变量定义


// 角度计算相关变量
float totalAngle;           // 累计角度
Vector2 lastDir;           // 上一帧的玩家->猎物方向
Vector2 startDir;          // 初始方向 玩家->猎物方向
Role prey;                 // 猎物对象

进入捕捉状态初始化

private void Catching_Enter()
{// UI跟随猎物位置lassoUI.transform.position = prey.transform.position;lassoUI.SetRequiredAngle(prey.NeedAngle);// 初始化方向向量startDir = (transform.position - prey.transform.position).normalized;lassoUI.InitStartDir(startDir); lastDir = startDir;totalAngle = 0f;// 绑定满圈事件lassoUI.OnFullRotation += HandleLassoFullRotation;lassoUI.Show();
}

核心角度计算逻辑

private void Catching_Update()
{// 让LassoUI跟随猎物位置if (lassoUI != null && prey != null){lassoUI.transform.position = prey.transform.position;}// 计算当前方向向量Vector2 currentDir = (transform.position - prey.transform.position).normalized;// 计算角度变化(相对上一次)float delta = Mathf.DeltaAngle(Mathf.Atan2(lastDir.y, lastDir.x) * Mathf.Rad2Deg,Mathf.Atan2(currentDir.y, currentDir.x) * Mathf.Rad2Deg);totalAngle += delta; // 累计总角度(正负都可以)lastDir = currentDir;lassoUI.UpdateProgress(totalAngle);// 检查是否满圈if (Mathf.Abs(totalAngle) >= prey.NeedAngle){HandleLassoFullRotation();lassoUI.ResetProgress();}
}

抓捕成功处理

void HandleLassoFullRotation()
{// 满圈了,执行抓捕成功逻辑Debug.Log("执行抓捕成功");// 调用UI弹出动画UIManager.instance.GetPanel<BattleMainPanel>().ShowImagePopUp();// 销毁猎物if (prey != null){prey.Dead();}// 退出抓捕状态,回到射击模式fsm.ChangeState(PlayerControllerStates.Shooting);
}

关键技术点说明

1. 角度计算原理

  • 使用 Mathf.Atan2() 将方向向量转换为角度
  • 使用 Mathf.DeltaAngle() 计算相对角度变化,自动处理角度跨越问题
  • 支持顺时针和逆时针旋转,正负值自动处理

2. UI跟随机制

  • 每帧更新 lassoUI.transform.position = prey.transform.position
  • 确保UI始终跟随猎物位置

3. 状态管理

  • 使用状态机管理不同游戏状态(射击、狩猎、捕捉)
  • 进入捕捉状态时初始化角度计算
  • 退出时清理事件绑定

4. 事件驱动

  • 通过 OnFullRotation 事件触发抓捕成功逻辑
  • 实现UI和游戏逻辑的解耦

UI 进度计算

LassoUI.cs

ringMaterial 为shader材质的引用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;namespace Gameplay.Battle
{public class LassoUI : MonoBehaviour{// [SerializeField] private Image progressCircle; // 圆环Imageprivate CanvasGroup canvasGroup; // 控制显示隐藏的透明度private float accumulatedAngle = 0f; // 累计角度private float requiredAngle = 360f;  // 默认1圈public Material ringMaterial;public Vector2  startDir = Vector2.up; // 初始方向 玩家->猎物方向public event Action OnFullRotation; // 触发满圈事件// Start is called before the first frame updatevoid Start(){canvasGroup = GetComponent<CanvasGroup>();Hide();}public void Show(){canvasGroup.alpha = 1;canvasGroup.blocksRaycasts = true;canvasGroup.interactable = true;}public void Hide(){canvasGroup.alpha = 0;canvasGroup.blocksRaycasts = false;canvasGroup.interactable = false;}public void InitStartDir(Vector2 dir){startDir = dir;float startAngle = Mathf.Atan2(startDir.y, startDir.x) * Mathf.Rad2Deg;// 只设置起始角度,不设置进度ringMaterial.SetFloat("_StartAngle", startAngle);ringMaterial.SetFloat("_Progress", 0f); // 进度从0开始Debug.Log($"LassoUI: 初始化角度 = {startAngle}°");}public void SetRequiredAngle(float angle){requiredAngle = angle;Debug.Log($"LassoUI: 设置所需角度 = {requiredAngle}°");}public void ResetProgress(){accumulatedAngle = 0f;}public void UpdateProgress(float angle){var Progress = Mathf.Clamp(angle / requiredAngle,-1f,1f);ringMaterial.SetFloat("_Progress", Progress);}}
}

Shader 实现

参数调整
在这里插入图片描述

Shader "Unlit/EllipseRingProgress"
{Properties{_MainColor ("Fill Color", Color) = (1,0.5,0,1)           // 内圈填充颜色_EdgeColor ("Edge Color", Color) = (0,0,0,1)             // 描边颜色_Progress ("Progress", Range(-1,1)) = 0                  // 进度,负数顺时针,正数逆时针_Thickness ("Ring Thickness", Range(0.01,2)) = 1        // 环宽_EdgeWidth ("Edge Width", Range(0.001,0.1)) = 0.02      // 内外描边宽度_CapEdgeAngle ("Cap Edge Width (Degrees)", Range(0,5)) = 1.0 // 封口两端描边角度_EllipseA ("Ellipse Semi-major Axis", Float) = 1        // 椭圆长轴_EllipseB ("Ellipse Semi-minor Axis", Float) = 1        // 椭圆短轴_StartAngle ("Start Angle Offset (Degrees)", Range(-180,180)) = 0 // 起始角度}SubShader{Tags { "Queue"="Transparent" "RenderType"="Transparent" }LOD 100Pass{Blend SrcAlpha OneMinusSrcAlphaCull OffZWrite OffCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"fixed4 _MainColor;fixed4 _EdgeColor;float _Progress;float _Thickness;float _EdgeWidth;float _CapEdgeAngle;float _EllipseA;float _EllipseB;float _StartAngle;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;};// 顶点程序v2f vert(appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);// 将 UV 从 [0,1] 映射到 [-1,1],中心在 (0,0)o.uv = v.uv * 2 - 1;return o;}fixed4 frag(v2f i) : SV_Target{float2 pos = i.uv;// 1️⃣ 计算椭圆归一化距离float ellipseDist = (pos.x * pos.x) / (_EllipseA * _EllipseA) +(pos.y * pos.y) / (_EllipseB * _EllipseB);float halfThickness = _Thickness * 0.5;float innerBoundary = 1.0 - halfThickness;float outerBoundary = 1.0 + halfThickness;// 不在环内的点直接丢弃if (ellipseDist < innerBoundary || ellipseDist > outerBoundary)discard;// 2️⃣ 计算极角 (0~360)float angleRad = atan2(pos.y / _EllipseB, pos.x / _EllipseA);float angleDeg = degrees(angleRad);if (angleDeg < 0) angleDeg += 360;float relativeAngle = fmod(angleDeg - _StartAngle + 360, 360);// 3️⃣ 处理顺/逆时针显示float absProgress = abs(_Progress); // 进度长度bool clockwise = (_Progress < 0);   // 顺时针方向float progressAngle = absProgress * 360;if (clockwise){// 顺时针:从起点往回走if (relativeAngle < (360 - progressAngle) && relativeAngle > 0)discard;}else{// 逆时针:原逻辑if (relativeAngle > progressAngle)discard;}// 4️⃣ 内外描边bool radialEdge = abs(ellipseDist - (1.0 - halfThickness)) < _EdgeWidth ||abs(ellipseDist - (1.0 + halfThickness)) < _EdgeWidth;// 5️⃣ 封口描边计算float startCap = 0;float endCap = progressAngle;if (clockwise){startCap = 360 - progressAngle;endCap = 360;}bool capEdge = (relativeAngle < _CapEdgeAngle) ||(abs(relativeAngle - startCap) < _CapEdgeAngle) ||(abs(relativeAngle - endCap) < _CapEdgeAngle);// 6️⃣ 返回颜色if (radialEdge || capEdge)return _EdgeColor; // 描边return _MainColor;    // 填充}ENDCG}}
}

说明

  • _Progress

    • 负值 → 顺时针
    • 正值 → 逆时针
  • _StartAngle

    • 控制环起点位置
  • _EdgeWidth

    • 调整环内外描边粗细
  • _CapEdgeAngle

    • 调整封口角度宽度
  • _EllipseA/B

    • 控制椭圆比例,可实现圆形或拉长效果
  • _Thickness

    • 环宽

📌 总结

  1. 通过 Shader 对椭圆环的归一化计算,实现动态进度显示。
  2. 支持顺/逆时针显示。
  3. 封口描边、内外描边,增强视觉效果。
  4. C# 控制 _Progress_StartAngle,UI 可随角色位置和方向实时更新。

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

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

相关文章

MyBatis-Plus代码生成器

MyBatis-Plus 代码生成器是一款高效、灵活的自动化工具,旨在简化 Java 后端开发中的持久层代码编写。通过配置数据库连接和模板参数,它可以一键生成实体类、Mapper 接口、XML 文件、Service 层及 Controller 层代码,大幅提升开发效率,减少重复劳动。 核心优势: 快速生成:…

06-导入Maven项目模块

文章目录1、文章介绍2、模块复制3、导入pom文件4、效果图1、文章介绍 视频定位 2、模块复制 复制资料“02.maven项目”中的两个项目模块到刚刚新建的项目文件路径中 导入后的效果图 3、导入pom文件 4、效果图

Jenkins+docker 微服务实现自动化部署安装和部署过程

Jenkins 是一款流行的开源自动化服务器&#xff0c;广泛用于持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;流程的自动化。通过 Docker 部署 Jenkins 可以简化安装和配置过程&#xff0c;同时保证在不同环境下的一致性。本篇文章将介绍如何使用 Dock…

【芯片后端设计的灵魂:Placement的作用与重要性】

在芯片设计的浩瀚宇宙中&#xff0c;后端物理设计扮演着决定成败的关键角色。其中&#xff0c;​Placement&#xff08;布局&#xff09;​​ 作为整个流程的核心环节&#xff0c;被誉为芯片性能、功耗和面积的“奠基者”。今天&#xff0c;我们就来深入探讨Placement的作用、重…

将FGUI的Shader全部预热后,WebGL平台没有加载成功

1&#xff09;将FGUI的Shader全部预热后&#xff0c;WebGL平台没有加载成功 2&#xff09;iOS如何确认内存扩展使用生效 3&#xff09;SpriteAtlasManager.atlasRequested延后一帧回调 4&#xff09;Unity如何使用Java 17打包 这是第442篇UWA技术知识分享的推送&#xff0c;精选…

Python二进制、八进制与十六进制高级操作指南:从底层处理到工程实践

引言&#xff1a;为何需要掌握进制操作&#xff1f;在现代计算领域&#xff0c;直接操作不同进制的数值是一项核心技术能力。根据2024年Stack Overflow开发者调查报告&#xff1a;73%的低级系统开发涉及位级操作65%的网络协议要求理解十六进制数据80%的硬件接口配置使用二进制控…

离线可用的网络急救方案

在使用电脑的过程中&#xff0c;经常会遇到断网的状况&#xff0c;这种情况让人十分头疼&#xff0c;很多时候我们都不知道去哪里找相关的教程来解决这样的问题。它能一键操作解决电脑的网络故障问题&#xff0c;最关键的是它是完全免费的。它只需解压就可以直接双击使用。把工…

华为云Stack环境中计算资源,存储资源,网络资源发放前的准备工作(中篇)

实验流程说明再上期文章链接如下&#xff1a; 华为云Stack环境中计算资源&#xff0c;存储资源&#xff0c;网络资源发放前的准备工作&#xff08;上篇&#xff09; 华为云Stack环境中计算资源&#xff0c;存储资源&#xff0c;网络资源发放前的准备工作&#xff08;中篇篇&am…

设置密钥连接服务器

要将本地电脑的 SSH 公钥添加到服务器登录&#xff0c;可按以下步骤操作&#xff0c;确保服务器仅允许密钥认证&#xff1a; 一、将本地公钥添加到服务器 &#xff08;前提&#xff1a;你已通过密码或现有方式能登录服务器&#xff0c;且本地已生成 SSH 密钥对&#xff09; 1. …

k8s笔记04-常用部署命令

Kubernetes&#xff08;K8s&#xff09;部署与版本管理命令笔记 一、部署核心命令分类与应用场景 K8s中用于应用部署、版本控制与实例扩缩容的核心命令主要包括三类&#xff0c;分别对应“版本回滚”“手动扩缩容”“自动扩缩容”场景&#xff0c;是CKA考试中部署类题目的核心考…

[系统架构设计师]知识产权(二十)

[系统架构设计师]知识产权&#xff08;二十&#xff09; 一.知识产权的特性 1.特性 无体性&#xff1a;抽象财富 专有性&#xff1a;权利人同意或法律规定外&#xff0c;权利人以外的任何人不得享有或使用该项权力 地域性&#xff1a;只能在该国范围内手法律保护 时间性&#x…

rk3566编译squashfs报错解决

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 编译开源的rk3566代码squashfs报错&#xff0c;tspi_linux_sdk_repo_20240131.tar.gz 下之前先读我 1.tspi_linux_sdk_20230916.tar.gz这个是之前老的没有git和repo的版本&#xff0c;后面会删除掉大家…

HTTP 协议与TCP 的其他机制

TCP 的其他机制TCP头部的标志位SYN&#xff1a;请求建立连接标志位ACK&#xff1a;响应报文标志位PSH&#xff1a;携带数据标志位&#xff0c;通知接收方该从缓冲区读数据FIN&#xff1a;请求断开连接标志位RST&#xff1a;复位标志位URG&#xff1a;紧急数据标志位安全可靠机制…

点评《JMeter核心技术、性能测试与性能分析》一书

《JMeter核心技术、性能测试与性能分析》深度评价‌该书作为清华大学出版社2025年推出的性能测试领域新作&#xff0c;展现了鲜明的技术深度与实践导向性&#xff0c;具体评价如下&#xff1a;‌1. 内容体系&#xff1a;系统性与前沿性兼备‌‌知识架构完整‌&#xff1a;覆盖J…

深入解析:为什么应该避免使用 atoi、atol 和 atof 函数

问题本质深度分析 简化源码展示&#xff1a;看清本质 atoi 的典型实现&#xff1a; // atoi 的简化实现 - 看清问题所在 int atoi(const char *str) {int sign 1;int result 0;// 跳过空白字符while (isspace(*str)) {str;}// 处理符号if (*str -) {sign -1;str;} else if …

计算机网络:HTTP、抓包、TCP和UDP报文及重要概念

一、http超文本传输协议&#xff08;应用层&#xff09;&#xff08;一&#xff09;万维网1.工作过程&#xff08;二&#xff09;统一资源定位符&#xff08;URL&#xff09;http的默认端口号是80&#xff08;三&#xff09;HTTP报文结构请求报文&#xff1a;客户端-->服务器…

three.js+WebGL踩坑经验合集(8.3):合理设置camera.near和camera.far缓解实际场景中的z-fighting叠面问题

本篇延续上篇内容&#xff1a; three.jsWebGL踩坑经验合集(8.2):z-fighting叠面问题和camera.near的坑爹关系-CSDN博客 笔者也是狠佩服自己&#xff1a;一个还没划上句号的文章都能拖了半年才继续写。这次也是运气好&#xff0c;工作上再次遇到叠面问题&#xff0c;可以借这机…

记一次生产环境Hbase填坑之路、Hbase客户端登陆、kerberos认证、端口列表、Pod上手撕代码【Hbase最佳实践】

背景 1、软件系统&#xff08;转储系统&#xff09;需要向生产环境迁移&#xff1a;迁到国产操作系统、国产资源池&#xff08;Hbase存储不变&#xff09; 2、老环境上的转储系统本身存在写入hbase的性能问题、及部分省份写入hbase失败的问题&#xff08;20%失败&#xff09;…

C++知识杂项搜集

C使用如下库优化事件的注册和发布&#xff0c;ZeroMQzmqpp 通信机制&#xff0c;请求-应带方式&#xff0c;push-pull方式&#xff0c;publisher-subcriber发布-订阅模式eventpp 事件注册和回调sockpp tcp/udp封装threadpool 线程池Jinja 一个 python 的模板实现配置是实现…

连锁零售排班难?自动排班系统来解决

零售、连锁企业门店多、员工杂、班次密&#xff0c;排班时总有绕不开的问题&#xff1a;跨门店调人成本怎么算&#xff1f;节假日高峰期人手怎么补&#xff1f;全职兼职混合排班怎么平衡&#xff1f;其实&#xff0c;这些场景化难题&#xff0c;盖雅自动排班系统早就有了针对性…