网店建设方案,杭州专业网站优化公司,精美旅游网站模板,网站建设流程六个步骤Unity 套圈捕捉 UI 实现分享
期望表现效果 《拼贴冒险传 / PatchQuest》 捕捉进度 动态UI实现效果 目标#xff1a;角色 A 套圈怪物 B#xff0c;进度环显示围绕角度。技术点#xff1a;Shader 绘制椭圆环#xff0c;支持描边、顺/逆时针,需要对两个切口也进行描边。 技术…Unity 套圈捕捉 UI 实现分享
期望表现效果 《拼贴冒险传 / PatchQuest》 捕捉进度 动态UI实现效果 目标角色 A 套圈怪物 B进度环显示围绕角度。技术点Shader 绘制椭圆环支持描边、顺/逆时针,需要对两个切口也进行描边。 技术需求 准备
UnityRawImage 自定义 ShaderCanvas 设置为 World SpaceUI 跟随敌人C# 脚本控制进度和方向
UI预制体的层级结构 捕捉逻辑
玩家位置与敌人位置计算方向向量。计算 DeltaAngle累积角度。正负值表示顺/逆时针。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.GetPanelBattleMainPanel().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 GetComponentCanvasGroup();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 { QueueTransparent RenderTypeTransparent }LOD 100Pass{Blend SrcAlpha OneMinusSrcAlphaCull OffZWrite OffCGPROGRAM#pragma vertex vert#pragma fragment frag#include UnityCG.cgincfixed4 _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 环宽 总结
通过 Shader 对椭圆环的归一化计算实现动态进度显示。支持顺/逆时针显示。封口描边、内外描边增强视觉效果。C# 控制 _Progress 和 _StartAngleUI 可随角色位置和方向实时更新。