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 { QueueTransparent IgnoreProjectorTrue RenderTypeTransparent } 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 GetComponentMeshRenderer().sharedMaterial; meshRenderer GetComponentMeshRenderer(); } /// 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 namedamage/param /// param namecolor/param /// param nameoutline/param /// param nameoutlineWidth/param /// param namefuration/param /// param namerandomOffest/param /// param namemaintainSizeTime/param /// param namescalePercent/param /// param namefadeInTime/param /// param namefadeOutTime/param /// param nameisCritical/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 GetComponentsInChildrenSingleNum(); 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 QueueDamageNumberRender damageNumQueue new QueueDamageNumberRender(); private Transform parent; private string path ; public DamageNumberManager(string path) { GameObject damgObject new GameObject(DamageNumberPool); parent damgObject.transform; this.path path; } /// summary /// 预加载 /// /summary /// param namenum/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.GetComponentDamageNumberRender(); 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); } } }

相关新闻

yuzu模拟器深度解析:如何在PC上完美运行Switch游戏的技术实现

yuzu模拟器深度解析:如何在PC上完美运行Switch游戏的技术实现

yuzu模拟器深度解析:如何在PC上完美运行Switch游戏的技术实现 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu yuzu作为目前最优秀的开源任天堂Switch模拟器,以其卓越的性能表现和持续的技术…

2026/10/21 11:02:15 阅读更多 →
Switch蓝牙控制器兼容性终极指南:如何使用MissionControl免费连接第三方手柄

Switch蓝牙控制器兼容性终极指南:如何使用MissionControl免费连接第三方手柄

Switch蓝牙控制器兼容性终极指南:如何使用MissionControl免费连接第三方手柄 【免费下载链接】MissionControl Use controllers from other consoles natively on your Nintendo Switch via Bluetooth. No dongles or other external hardware neccessary. 项目地…

2026/10/18 21:18:00 阅读更多 →
技术决策指南:如何为计算机视觉项目选择DINOv2自监督模型

技术决策指南:如何为计算机视觉项目选择DINOv2自监督模型

技术决策指南:如何为计算机视觉项目选择DINOv2自监督模型 【免费下载链接】dinov2 PyTorch code and models for the DINOv2 self-supervised learning method. 项目地址: https://gitcode.com/GitHub_Trending/di/dinov2 DINOv2作为Meta AI Research推出的自…

2026/10/22 21:30:28 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/22 21:33:42 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 22:56:44 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 22:56:12 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 19:06:46 阅读更多 →