当前位置: 首页 > 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.jsqmd.com/news/1386660/

相关文章:

  • 深入了解宁津县建设局网站获取最新住建资讯与便民办事指南
  • 终极RBTray指南:让Windows窗口管理效率提升300%的完整教程
  • 2026年金湖窗帘真空定型机生产厂家优选:江苏新浩泰科技开发有限公司(金湖销售中心) - 品牌优推
  • oa网站建设推广:如何让传统企业在数字化转型的浪潮中立于不败之地
  • 深度复盘:揭秘因脉网站建设公司怎么呀韩国市场拓展与本土化实战经验分享
  • FasterLivePortrait深度解析:实时肖像驱动的架构设计与实战应用
  • ureq库完全解析:轻量级HTTP请求处理的革命性C语言解决方案
  • skills23:重构智能代理网页访问工作流的全栈解决方案
  • 告别Windows虚拟机:用ParsePDM在Mac上5分钟看完PDM数据库设计文件
  • 全域流量覆盖:智搜GEO如何打通AI问答与传统搜索的双向获客
  • 佛山市口碑好的短视频代运营服务商怎么选?这几点判断标准值得收藏 - 官方资讯
  • 如何快速部署Garage-WebUI?3种简单方法助你5分钟上手
  • 【2026-08】演播室搭建靠谱安装厂家选哪个?一站式演播室搭建、带货专用演播室搭甄选——吉河嘉业 - 多才菠萝
  • 免费硬件监控神器:LibreHardwareMonitor终极使用指南
  • 2026年工程机械铸件专业发布,五大实力方向值得关注 - 官方资讯
  • EmotiVoice易魔声:免费开源的多音色情感语音合成引擎终极指南
  • 终极Windows 11精简指南:让老旧电脑重获新生的完整教程
  • 4步极速AI图像生成:Qwen-Image-Lightning技术解析与应用实践
  • Terraform Provider for VMware vSphere 进阶:从模板克隆到高级网络配置
  • 2026年南京庭院鱼池泡池除碱怎么做才对?顺序错了鱼白养
  • 告别杂乱:Jupyter Notebook输出显示优化与界面定制全攻略
  • 2026年金属表面处理清洗剂出炉,高口碑产品一次盘点 - 官方资讯
  • AutoSchemaKG与Neo4j集成指南:高效存储和管理知识图谱数据
  • 业余时间学影视后期,这条路走得通吗? - 生活动态圈
  • 零基础建站指南:2024年最新网站建设自学路径与电子版PDF资源获取全解析
  • Minecraft:Java想不付钱就用PCL2
  • 深耕工程质量与进度双控:揭秘上海三凯建设管理咨询有限公司网站背后的专业逻辑与客户信任构建
  • 如何使用TRL强化学习框架快速微调大语言模型:5个步骤掌握完整流程
  • PotreeConverter终极指南:5分钟掌握海量点云数据转换技巧
  • RBTray:3步魔法操作让Windows任务栏空间翻倍的效率革命