unity项目中使用精灵(Sprite)和其他图形来创建其场景的视觉效果。这意味着单个项目中可能包含许多纹理(texture) 文件。Unity 通常会为场景中的每个纹理发出一个绘制调用(DrawCall);但是,在具有许多纹理的项目中,过多的绘制调用会占用大量资源,并会对项目的性能产生负面影响。
为了降低性能消耗,我们可以使用精灵图集(Sprite Atlas)技术,它能够将多个纹理合并成一个大纹理,当访问图集中的多个纹理时,也只需要调用一次DrawCall。
关于Sprite Atlas 的使用可以参考–这里
在查找相关资料时由知乎文章:【Unity游戏开发】SpriteAtlas与AssetBundle最佳食用方案 得知不同的打包方案,即:
以下所有结论均在Unity 2021.3.11下得出

文件结构

两个AssetBundle包。

其中testImage使用了一个图集中的图元

A组:
勾选Include in build
打包散图文件夹,不打包SpriteAtlas
使用方式:
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
img1.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
img2.sprite = _bundle2.LoadAsset<Sprite>("gameS-num2");
需要先加载atlas包,再使用prefab,否则build后 prefab无法正常显示纹理(editor下正常)
结论:
1. 使用两张散图,DrawCall仍记为1;
2. AB包中除了所有散图外,仍会有一张合并后的大图;
下图由AssetStudio分别读取AB包中的内容
testatlas.u3d:

testimage.u3d:

3. 改变图元调用的次数后
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
img1.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
img2.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
img3.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
由MemoryProfiler检查内存可知,所有关于图元 "gameS-num1"的引用在内存中有且仅有一份,也就是说,以上方式加载的图元,以单例形式存在于内存之中,不同于C组结论3 .
- B组: 不勾选Include in build
打包散图文件夹,不打包SpriteAtlas
使用方式:
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
// img1.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
// img2.sprite = _bundle2.LoadAsset<Sprite>("gameS-num2");
结论:
无法加载图片,加载testatlas.u3d AB时调用 SpriteAtlasManager.atlasRequested ;
AB包中只有散图
加载prefab时不会调用 SpriteAtlasManager.atlasRequested (与D组差异)
prefab无法正常显示;


C组:
勾选Include in build
不打包散图文件夹,打包SpriteAtlas
使用方式:
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
atlas = _bundle2.LoadAsset<SpriteAtlas>("testAtlas");
img1.sprite = atlas.GetSprite("gameS-num1");
img2.sprite = atlas.GetSprite("gameS-num2");
结论:
1. 使用SpriteAtlas,调用多个图元,DrawCall记为1
2. 由AssetStudio分别读取AB包中的内容
testatlas.u3d:

testimage.u3d:

由此可见,AB包中除了所有散图外,会有一张合并后的大图;但是,prefab中也出现一份相同的图元,打包冗余;
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
atlas = _bundle2.LoadAsset<SpriteAtlas>("testAtlas");
img1.sprite = atlas.GetSprite("gameS-num1");
img2.sprite = atlas.GetSprite("gameS-num1");
img3.sprite = atlas.GetSprite("gameS-num1");
由MemoryProfiler检查内存可知,所有关于图元 “gameS-num1"的引用都会clone出独立的一份,相对比A组造成大量的内存开销。同时由于prefab中也有一张图元,在加载AB包时,多次载入同一份图元"gameS-num1”。

D组:
不勾选Include in build
不打包散图文件夹,打包SpriteAtlas
使用方式:
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
atlas = _bundle2.LoadAsset<SpriteAtlas>("testAtlas");
img1.sprite = atlas.GetSprite("gameS-num1");
img2.sprite = atlas.GetSprite("gameS-num2");
结论:
prefab中出现一份散图,打包冗余;
AB包中除了所有散图外,仍会有一张合并后的大图;
加载prefab时调用 SpriteAtlasManager.atlasRequested ;
prefab无法正常显示


E组
勾选Include in build
将散图文件夹,SpriteAtlas打入同一个AB包中
F组
不勾选Include in build
将散图文件夹,SpriteAtlas打入同一个AB包中
| 打包方式 | 是否勾选Include in build | 纹理合并是否成功 | 是否冗余1 | 先加载图元是否需要后绑定2 | 先加载prefab是否需要后绑定2 | 内存中图元的唯一性atlas.GetSprite(“xxx”) | 内存中图元的唯一性_bundle2.LoadAsset(“gameS-num1”); |
|---|---|---|---|---|---|---|---|
| 对文件夹打包 | 是 | 是 | 否 | 否 | 否 | 是 | 不可用 |
| 对文件夹打包(无意义) | 否 | 否 | 否 | 是 | 否 | 不可用 | 不可用 |
| 对spriteAtlas文件打包 | 是 | 是 | 是 | 否 | 否 | 不可用 | 否 |
| 对spriteAtlas文件打包 | 否 | 是 | 是 | 是 | 是 | 不可用 | 否 |
| 将文件夹和spriteAtlas打入同一个包 | 是 | 是 | 否 | 否 | 否 | 是 | 否 |
| 将文件夹和spriteAtlas打入同一个包 | 否 | 是 | 否 | 是 | 否 | 是 | 否 |
综上所诉,在使用SpriteAtlas时,根据不同的情况使用不同的打包方案。需要特备注重的是否冗余,是否需要精灵在内存唯一。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
using UnityEngine.UI;
public class AtlasTest : MonoBehaviour
{
public Image img0;
public Image img1;
public Image img2;
public Image img3;
private SpriteAtlas atlas;
private AssetBundle _bundle;
private GameObject _testImage;
private AssetBundle _bundle2;
// Start is called before the first frame update
void Start()
{
SpriteAtlasManager.atlasRequested += OnCallBack_AtlasRequested;
SpriteAtlasManager.atlasRegistered += OnCallBack_AtlasRegistered;
}
private void OnCallBack_AtlasRegistered(SpriteAtlas obj)
{
Debug.Log("atlasRegistered: " + obj.name);
//img1.sprite = atlas.GetSprite("gameS-num1");
//img2.sprite = atlas.GetSprite("gameS-num2");
}
private void OnCallBack_AtlasRequested(string arg1, Action<SpriteAtlas> arg2)
{
Debug.Log("无法在运行时找到图集资源:"+ arg1);
//_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
atlas = _bundle2.LoadAsset<SpriteAtlas>("testAtlas");
arg2.Invoke(atlas);
}
public void OnClick_Start()
{
_bundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testimage.u3d");
var testImage = _bundle.LoadAsset<GameObject>("testImage");
_testImage=GameObject.Instantiate(testImage, img1.transform.parent);
}
public void OnClick_Start2()
{
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
atlas = _bundle2.LoadAsset<SpriteAtlas>("testAtlas");
img1.sprite = atlas.GetSprite("gameS-num1");
img2.sprite = atlas.GetSprite("gameS-num1");
img3.sprite = atlas.GetSprite("gameS-num1");
}
public void OnClick_Start3()
{
_bundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + @"\testatlas.u3d");
img1.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
img2.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
img3.sprite = _bundle2.LoadAsset<Sprite>("gameS-num1");
}
public void OnClick_Unload()
{
//GameObject.Destroy(_testImage);
//img1.sprite = null;
//img2.sprite = null;
_bundle?.Unload(true);
_bundle2?.Unload(true);
}
// Update is called once per frame
void Update()
{
}
}
打包用
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class ExportAssetBundles : MonoBehaviour
{
[MenuItem("Example/Build Asset Bundles")]
static void BuildABs()
{
// Put the bundles in a folder called "ABs" within the Assets folder.
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u