当前位置: 首页 > news >正文

Unity大量伤害数字生成解决方案

伤害数字生成的需求分析

在Unity中实现大量伤害数字效果,通常需要解决性能优化、视觉效果多样性以及动态管理问题。以下是解决方案

使用GPU Instancing实现批量渲染

Unity GPU Instancing的优势

性能优化
GPU Instancing通过一次绘制调用(Draw Call)渲染多个相同或相似的网格,大幅减少CPU到GPU的通信开销。适合渲染大量重复对象(如植被、子弹、建筑群),性能提升显著,尤其在移动端和低端设备上表现突出。

内存效率
共享相同材质和网格的实例仅存储一份基础数据,仅通过变换矩阵(位置、旋转、缩放)区分不同实例。相比单独渲染每个对象,内存占用更低,尤其适用于大规模场景。

GPU Instancing伤害字制作

原理用一张包含0-9和符号位的图片来存储伤害数字的基本信息,通过一个Quad网格渲染每一位数字,同时可以利用shader制作一些顶点偏移动画来满足字体动画需求。

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Custom/DamageNumberInstanced" { Properties { _MainTex ("Font Texture", 2D) = "white" {} _VerticalHeightFactor ("VerticalHeightFactor",Range(0,1)) = 0.5 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 color : COLOR; float4 outlineColor : TEXCOORD1; float outlineWidth: TEXCOORD2; UNITY_VERTEX_INPUT_INSTANCE_ID }; sampler2D _MainTex; float4 _MainTex_ST; float4 _MainTex_TexelSize; float _VerticalHeightFactor; // 实例化数据 UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _InstanceColor) UNITY_DEFINE_INSTANCED_PROP(float4, _InstanceOutlineColor) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceCreationTime) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceDamageDuration) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceRandomOffset) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceIsCritical) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceDamageValue) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceOutlineWidth) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceMaintainSizeTime) UNITY_DEFINE_INSTANCED_PROP(float, _InstancesizeScalePercent) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceFadeInTime) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceFadeOutTime) UNITY_DEFINE_INSTANCED_PROP(float, _InstanceSign) UNITY_INSTANCING_BUFFER_END(Props) // 数字纹理中每个数字的UV布局(假设是2x10网格布局0-9和符号) float2 GetDigitUV(float digit, float sign,float2 baseUV) { // 将数字转换为整数并限制在0-9范围内 int intDigit = (int)digit; intDigit = clamp(intDigit, 0, 10); float x = (intDigit % 10) * 0.1; float y = ((int)sign % 2) * 0.5; // 调整UV到正确的数字区域 float2 uv = baseUV; uv.x = uv.x * 0.1 + x; uv.y = uv.y * 0.5 + y; return uv; } v2f vert (appdata v, uint instanceID : SV_InstanceID) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_TRANSFER_INSTANCE_ID(v, o); // 获取实例数据 float creationTime = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceCreationTime); float duration = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceDamageDuration); float randomOffset = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceRandomOffset); float isCritical = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceIsCritical); float4 instanceColor = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceColor); float damageValue = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceDamageValue); float4 outlineColor = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceOutlineColor); float outlineWidth = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceOutlineWidth); float maintainSizeTime = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceMaintainSizeTime); // 维持大小的时间 float sizeScalePercent = UNITY_ACCESS_INSTANCED_PROP(Props, _InstancesizeScalePercent); //缩放比例 float fadeInTime = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceFadeInTime); //淡入时间 float fadeOutTime = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceFadeOutTime); //淡出时间 float sign = UNITY_ACCESS_INSTANCED_PROP(Props, _InstanceSign); //符号图像位 // 计算动画进度 float elapsed = _Time.y - creationTime; float progress = saturate(elapsed / duration); // 动画效果:上浮 float verticalOffset = progress * _VerticalHeightFactor; // 淡入淡出效果 float fadeInProgress = saturate(elapsed / fadeInTime); // 淡入进度 float fadeOutProgress = saturate(duration - elapsed / fadeOutTime); // 淡出进度 float alpha = fadeInProgress * fadeOutProgress; // 结合淡入淡出 // 随机水平偏移 float horizontalOffset = sin(progress * 10.0 + randomOffset * 10.0) * 0.3; // 字体大小变化效果 float sizeProgress = saturate((elapsed - maintainSizeTime) / (duration - maintainSizeTime)); float sizeScale = lerp(1.0, sizeScalePercent, sizeProgress); // 从原始大小缩小到50% // 应用变换 float4 worldPos = mul(UNITY_MATRIX_M,v.vertex); worldPos.xyz += float3(0, verticalOffset, 0); // 应用字体大小缩放 worldPos.xyz = worldPos.xyz * sizeScale; o.vertex = mul(UNITY_MATRIX_VP, worldPos); o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 设置颜色和透明度 o.color = instanceColor; o.outlineColor = outlineColor; o.outlineWidth = outlineWidth; o.color.a *= alpha; // 根据数字值计算UV偏移 o.uv = GetDigitUV(damageValue, sign,o.uv); return o; } fixed4 frag (v2f i) : SV_Target { // 如果轮廓宽度为0,直接返回文字颜色 if (i.outlineWidth < 0.001) { fixed4 center = tex2D(_MainTex, i.uv); fixed4 col = i.color; col.a *= center.a; return col; } // 文字轮廓效果 fixed4 outline = i.outlineColor; fixed4 center = tex2D(_MainTex, i.uv); // 简单的轮廓检测 if (center.a < 0.5) { // 检查周围像素 float2 uvOffset = _MainTex_TexelSize.xy * i.outlineWidth; fixed a1 = tex2D(_MainTex, i.uv + float2(uvOffset.x, 0)).a; fixed a2 = tex2D(_MainTex, i.uv + float2(-uvOffset.x, 0)).a; fixed a3 = tex2D(_MainTex, i.uv + float2(0, uvOffset.y)).a; fixed a4 = tex2D(_MainTex, i.uv + float2(0, -uvOffset.y)).a; if (a1 + a2 + a3 + a4 > 0) { outline.a = i.color.a; return outline; } } fixed4 col = i.color; col.a *= center.a; return col; } ENDCG } } }

贴图如下

预制体制作

每位数字SingleNum上需要挂载控制shader显示的脚本

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; public class SingleNum : MonoBehaviour { private MaterialPropertyBlock propertyBlock; private MeshRenderer meshRenderer; private Material numberMaterial; public enum SignType { Crit = 0, Sub = 1, Add = 2 } private void Awake() { propertyBlock = new MaterialPropertyBlock(); numberMaterial = GetComponent<MeshRenderer>().sharedMaterial; meshRenderer = GetComponent<MeshRenderer>(); } /// <summary> /// 设置符号 /// </summary> public void SetSign( Color color, Color outline, float outlineWidth, float furation, float randomOffest, float maintainSizeTime, float scalePercent, float fadeInTime, float fadeOutTime, SignType signType) { // 获取Shader全局的_Time值 Vector4 globalTime = Shader.GetGlobalVector("_Time"); float timeY = globalTime.y; propertyBlock.SetVector("_InstanceColor", color); propertyBlock.SetVector("_InstanceOutlineColor", outline); propertyBlock.SetFloat("_InstanceCreationTime", timeY); propertyBlock.SetFloat("_InstanceDamageDuration", furation); propertyBlock.SetFloat("_InstanceRandomOffset", randomOffest); propertyBlock.SetFloat("_InstanceDamageValue", (float)((int)signType)); propertyBlock.SetFloat("_InstanceOutlineWidth", outlineWidth); propertyBlock.SetFloat("_InstanceMaintainSizeTime", maintainSizeTime); propertyBlock.SetFloat("_InstancesizeScalePercent", scalePercent); propertyBlock.SetFloat("_InstanceFadeInTime", fadeInTime); propertyBlock.SetFloat("_InstanceFadeOutTime", fadeOutTime); propertyBlock.SetFloat("_InstanceSign", 0f); meshRenderer.SetPropertyBlock(propertyBlock); } /// <summary> /// 设置数字 /// </summary> /// <param name="damage"></param> /// <param name="color"></param> /// <param name="outline"></param> /// <param name="outlineWidth"></param> /// <param name="furation"></param> /// <param name="randomOffest"></param> /// <param name="maintainSizeTime"></param> /// <param name="scalePercent"></param> /// <param name="fadeInTime"></param> /// <param name="fadeOutTime"></param> /// <param name="isCritical"></param> public void SetDamageNumber( float damage, Color color, Color outline, float outlineWidth, float furation, float randomOffest, float maintainSizeTime, float scalePercent, float fadeInTime, float fadeOutTime ) { // 获取Shader全局的_Time值 Vector4 globalTime = Shader.GetGlobalVector("_Time"); float timeY = globalTime.y; propertyBlock.SetVector("_InstanceColor", color); propertyBlock.SetVector("_InstanceOutlineColor", outline); propertyBlock.SetFloat("_InstanceCreationTime", timeY); propertyBlock.SetFloat("_InstanceDamageDuration", furation); propertyBlock.SetFloat("_InstanceRandomOffset", randomOffest); propertyBlock.SetFloat("_InstanceDamageValue", damage); propertyBlock.SetFloat("_InstanceOutlineWidth", outlineWidth); propertyBlock.SetFloat("_InstanceMaintainSizeTime", maintainSizeTime); propertyBlock.SetFloat("_InstancesizeScalePercent", scalePercent); propertyBlock.SetFloat("_InstanceFadeInTime", fadeInTime); propertyBlock.SetFloat("_InstanceFadeOutTime", fadeOutTime); propertyBlock.SetFloat("_InstanceSign", 1f); meshRenderer.SetPropertyBlock(propertyBlock); } }

在DamageNums上需要挂载一个管理类控制数字的所有位的显示

using UnityEngine; using System.Collections.Generic; using UnityEngine.Rendering; using System; using UnityEngine.Events; public class DamageNumberRender : MonoBehaviour { public Color color; public float duration; public float randomOffset; public int damage; [Header("轮廓设置")] public Color OutlineColor; public float outlineWidth; [Header("缩放设置")] [Tooltip("维持大小时间")] public float maintainSizeTime; [Tooltip("缩放大小"),Range(0,1f)] public float scalePercent; [Header("淡入淡出")] public float fadeInTime; public float fadeOutTime; public UnityEvent completeEvent = new UnityEvent(); private SingleNum[] singleNums; private float durationElaspe = 0; private bool isOver = false; private int[] damageNum; private void Awake() { singleNums = GetComponentsInChildren<SingleNum>(); damageNum = SplitDigits(damage); } private void Update() { if (Input.GetKeyDown(KeyCode.V)) SetDamageNumber(damage); if (durationElaspe >= 0) durationElaspe -= Time.deltaTime; else { if (!isOver) { completeEvent?.Invoke(); isOver = true; } } } public int[] SplitDigits(int number) { if (number == 0) return new int[1] { 0 }; // 处理负数并转换为正数,使用long避免Math.Abs(int.MinValue)溢出 long absoluteValue = Math.Abs((long)number); // 计算数字的位数 int digitCount = CountDigits(absoluteValue); int[] digits = new int[digitCount]; // 从最低位到最高位依次提取 for (int i = digitCount - 1; i >= 0; i--) { digits[i] = (int)(absoluteValue % 10); // 取最后一位 absoluteValue /= 10; // 移除最后一位 } return digits; } private int CountDigits(long number) { if (number == 0) return 1; int count = 0; while (number > 0) { count++; number /= 10; } return count; } public void SetDamageNumber(int damage, bool isCritical = false) { isOver = false; durationElaspe = duration; if (damage != this.damage) { this.damage = damage; damageNum = SplitDigits(damage); } int index = 0; if (isCritical) { singleNums[index].SetSign( color, OutlineColor, outlineWidth, duration, randomOffset, maintainSizeTime, scalePercent, fadeInTime, fadeOutTime, SingleNum.SignType.Crit ); index++; } if (damage < 0) { singleNums[index].SetSign( color, OutlineColor, outlineWidth, duration, randomOffset, maintainSizeTime, scalePercent, fadeInTime, fadeOutTime, SingleNum.SignType.Sub ); index++; } if (damageNum.Length + index > singleNums.Length) Debug.LogError($"数据{damage}长度>显示长度,请扩充数字长度"); for (int i = 0; i < damageNum.Length; i++) { singleNums[i + index].SetDamageNumber( damageNum[i], color, OutlineColor, outlineWidth, duration, randomOffset, maintainSizeTime, scalePercent, fadeInTime, fadeOutTime ); } } }

对象池技术(Object Pooling)

有了上面GPUInstance渲染方案后,再加入对象池技术就可以提升大量性能。

using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; namespace DayZ { public class DamageNumberManager { private Queue<DamageNumberRender> damageNumQueue = new Queue<DamageNumberRender>(); private Transform parent; private string path = ""; public DamageNumberManager(string path) { GameObject damgObject = new GameObject("DamageNumberPool"); parent = damgObject.transform; this.path = path; } /// <summary> /// 预加载 /// </summary> /// <param name="num"></param> /// <returns></returns> public void PreLoadDamageNums(int num) { for (int i = 0; i < num; i++) { DamageNumberRender numRender = CreateDamageNumberRender(); damageNumQueue.Enqueue(numRender); } } public DamageNumberRender GetDamageNumber() { DamageNumberRender numRender; if (damageNumQueue.Count > 0) { numRender = damageNumQueue.Dequeue(); } else { numRender = CreateDamageNumberRender(); } return numRender; } private DamageNumberRender CreateDamageNumberRender() { //"Game/LastWar/Particles/DamageNums" GameObject gameObject = CCNodeCache.Instance.GetGameObject(this.path); gameObject.transform.SetParent(parent); DamageNumberRender numRender = gameObject.GetComponent<DamageNumberRender>(); numRender.completeEvent.AddListener(() => { damageNumQueue.Enqueue(numRender); }); return numRender; } public void Clear() { while(damageNumQueue.Count > 0) { DamageNumberRender numRender = damageNumQueue.Dequeue(); CCNodeCache.Instance.RecycleGameObject(numRender.gameObject, path); } GameObject.Destroy(parent.gameObject); } } }
http://www.cnnetsun.cn/news/4006118.html

相关文章:

  • 终极RDPWrap配置文件指南:5分钟解决Windows远程桌面多用户限制
  • 常熟市虞山北麓的清香之旅:常熟市维摩剑门绿茶网站建设目标深度解析与未来展望
  • 兰州网站建设搜王道下拉:揭秘本地企业如何打破流量僵局,低成本获取精准客户
  • 还在到处问那里有网站建设公司?别急,先看懂这五个避坑指南让你少走弯路
  • APP开发网站建设哪家好?深度解析避坑指南与优质服务商选择策略
  • 大型企业门户网站能力建设探索与实践:从技术重构到生态融合的进阶之路
  • 关于拟与行业头部合作伙伴开展深度协同合作建设官方网站并请予审批立项以赋能业务增长的战略请示与实施计划
  • 卧龙区网站建设页面搭建实战指南,从零基础到专业级落地全流程解析
  • 数学可视化革命:用3Blue1Brown源码突破教学与科研的表达瓶颈
  • 如何利用AiPy项目快速掌握Python机器学习的10个核心技能
  • 2024年寻找高明专业网站建设哪家好?避坑指南与深度解析助您轻松选型
  • 深扒宗学华 网站建设背后的逻辑与真相以及普通人如何借势突围
  • Python-docx安装全攻略:解决lxml依赖与Windows环境配置
  • 2024年新乡网站建设哪家专业:深耕本土市场,揭秘高性价比建站底层逻辑与避坑指南
  • 胶州网站建设电话多少钱一个电话就能搞定吗?胶州网站建设电话背后的那些真相与陷阱胶州网站建设电话怎么选
  • 惠安县规划建设局网站:深入解读惠安县最新城市规划与建设动态权威信息发布平台
  • 标题:关注江苏建设厅网站 获取最新行业资讯与政策动态全指南 2024年新版
  • ToastFish:终极Windows通知栏背单词解决方案,如何在工作间隙高效提升英语能力
  • 零基础建站指南:2024年最新网站建设自学路径与电子版PDF资源获取全解析
  • Chili3D完整指南:浏览器上的免费3D CAD建模解决方案
  • 终极指南:如何用SystemInformer快速诊断Windows系统崩溃问题
  • 深度复盘:揭秘因脉网站建设公司怎么呀韩国市场拓展与本土化实战经验分享
  • 选择最专业的网站建设SEO优化服务公司,让你的流量实现指数级爆发
  • 深度复盘中国旅游网站建设现状及发展趋势分析:从流量焦虑到体验重构的真相
  • 揭秘郑田生网站建设及维护背后的真相:如何让企业官网成为真正的业绩增长引擎
  • 古云网站建设怎么做好?揭秘从0到1打造高转化企业官网的避坑指南与实战心得
  • 专业定制还是模板套用?深度解析梧州做网站建设背后的真相与避坑指南
  • 揭秘网站建设外包合同核心陷阱与避坑指南:如何签订一份靠谱的网站建设外包合同
  • 做企业官网不是摆样子,而是找伙伴:衍艺 网站建设如何帮你落地真流量
  • 深入了解宁津县建设局网站获取最新住建资讯与便民办事指南