首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Unity动态图集技术

  • 25-03-02 22:41
  • 2675
  • 12898
blog.csdn.net

.背景
首先,为什么需要动态图集?主要有两个原因:

游戏中的icon数量相当大,数量达到上千个。如果打成静态图集,需要好几张2048x2048的图集。运行时内存占用较大。同时,在这种情况下,由于icon会跨几个图集,在几个图集间穿插渲染有可能会提高drawcall。
若icon不打图集,每个icon就是一个单独的drawcall,会导致drawcall很高。
而动态图集,就是为了解决以上提到的问题。它的主要思路就是在运行时将需要使用的icon动态更新到一张共享的图集中去。由于同时渲染的icon数量往往远远小于实际icon的资源数量,因此通常一张1024或者2048大小的动态图集即可以满足大多数的游戏需求。该方法的好处在于,内存在可控范围,并且icon可以共享纹理从而省drawcall。

Unity动态图集技术

在 Unity 中,动态图集(Dynamic Atlas)技术是一种优化纹理资源管理和渲染性能的方法。动态图集通过将多个小纹理动态合并到一个大纹理中,减少了渲染过程中纹理切换的次数,从而提高了渲染效率。以下是关于 Unity 动态图集技术的详细介绍和实现方法。

动态图集的优势

  1. 减少纹理切换:在渲染过程中,频繁的纹理切换会导致性能下降。通过将多个小纹理合并到一个大纹理中,可以减少纹理切换的次数。
  2. 提高渲染效率:减少纹理切换和批处理次数,可以显著提高渲染效率,特别是在移动设备上。
  3. 简化资源管理:动态图集可以简化纹理资源的管理,使得纹理的加载和使用更加高效。

实现动态图集

在 Unity 中实现动态图集可以通过以下几种方法:

1. 使用 Unity 的 Sprite Atlas

Unity 提供了内置的 Sprite Atlas 工具,可以方便地创建和管理图集。以下是使用 Sprite Atlas 的步骤:

  1. 创建 Sprite Atlas:

    • 在 Unity 编辑器中,右键点击项目窗口,选择 Create > 2D > Sprite Atlas 创建一个新的 Sprite Atlas。
  2. 添加精灵到图集:

    • 选择创建的 Sprite Atlas,在 Inspector 窗口中,点击 Objects for Packing 下的 + 按钮,将需要合并的精灵(Sprite)拖动到列表中。
  3. 打包图集:

    • 点击 Pack Preview 按钮,Unity 会自动将精灵合并到一个图集中。
  4. 使用图集中的精灵:

    • 在代码或编辑器中使用图集中的精灵时,Unity 会自动从图集中提取相应的纹理。
2. 动态生成图集

如果需要在运行时动态生成图集,可以使用 Texture2D 和 Rect 类来实现。以下是一个简单的示例,展示了如何在运行时动态生成图集:

using UnityEngine;
using System.Collections.Generic;

public class DynamicAtlas : MonoBehaviour
{
    public List<Texture2D> textures; // 要合并的纹理列表
    public int atlasWidth = 1024; // 图集宽度
    public int atlasHeight = 1024; // 图集高度

    private Texture2D atlasTexture;
    private List<Rect> uvRects;

    void Start()
    {
        GenerateAtlas();
    }

    void GenerateAtlas()
    {
        atlasTexture = new Texture2D(atlasWidth, atlasHeight);
        uvRects = new List<Rect>();

        for (int i = 0; i < textures.Count; i++)
        {
            Texture2D texture = textures[i];
            Rect uvRect = new Rect(0, 0, texture.width, texture.height);
            uvRects.Add(uvRect);

            atlasTexture.SetPixels((int)uvRect.x, (int)uvRect.y, texture.width, texture.height, texture.GetPixels());
        }

        atlasTexture.Apply();
    }

    void OnGUI()
    {
        // 在屏幕上显示图集
        GUI.DrawTexture(new Rect(0, 0, atlasWidth, atlasHeight), atlasTexture);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
3. 使用第三方插件

除了 Unity 内置的工具和自定义实现外,还有一些第三方插件可以帮助你更方便地创建和管理动态图集。例如:

  • TexturePacker:一个流行的纹理打包工具,支持多种平台和格式,可以与 Unity 无缝集成。
  • Asset Store 插件:Unity Asset Store 上有许多插件提供了动态图集的功能,可以根据需要选择合适的插件。

动态图集的注意事项

  1. 纹理大小限制:在创建图集时,需要注意图集的大小限制。不同平台对纹理大小有不同的限制,通常建议图集的大小不超过 2048x2048 或 4096x4096。
  2. 纹理压缩:为了减少内存占用和提高加载速度,可以对图集进行纹理压缩。Unity 提供了多种纹理压缩格式,可以根据平台选择合适的压缩格式。
  3. 纹理边缘处理:在合并纹理时,需要注意纹理边缘的处理,避免出现纹理缝

动态图集pipeline

游戏应用逻辑根据资源路径向动态图集服务请求icon。
如果该icon已经在该图集中,直接返回给程序使用;否则进入第3步。
去硬盘上请求加载该icon。
加载完成以后将该icon载入内存。
动态图集服务会根据某种分块算法,在其剩余可用的区域里划分一块矩形区域,同时将第4步中的icon像素拷贝到该矩形区域。
将关联第5步中矩形区域的sprite返回给应用程序使用。
在该6个步骤中,第5步是核心步骤,因为分块算法的好坏和像素的拷贝直接决定该pipeline的性能瓶颈。

动态生成图集流程

动态生成图集(Dynamic Atlas)的流程涉及多个步骤,包括纹理的收集、图集的创建、纹理的合并以及图集的使用。以下是一个详细的流程和示例代码,展示如何在 Unity 中动态生成图集。

动态生成图集的流程

  1. 收集纹理:收集需要合并到图集中的所有纹理。
  2. 创建图集:创建一个空的图集纹理(Texture2D)。
  3. 合并纹理:将收集到的纹理逐个合并到图集中,并记录每个纹理在图集中的位置(UV 坐标)。
  4. 应用图集:将生成的图集应用到需要使用的对象上。

示例代码

以下是一个完整的示例代码,展示了如何在 Unity 中动态生成图集:

using UnityEngine;
using System.Collections.Generic;

public class DynamicAtlasGenerator : MonoBehaviour
{
    public List<Texture2D> textures; // 要合并的纹理列表
    public int atlasWidth = 1024; // 图集宽度
    public int atlasHeight = 1024; // 图集高度
    public int padding = 2; // 纹理之间的间距

    private Texture2D atlasTexture;
    private List<Rect> uvRects;

    void Start()
    {
        GenerateAtlas();
    }

    void GenerateAtlas()
    {
        // 创建一个空的图集纹理
        atlasTexture = new Texture2D(atlasWidth, atlasHeight, TextureFormat.RGBA32, false);
        uvRects = new List<Rect>();

        // 初始化图集纹理为透明
        Color[] clearPixels = new Color[atlasWidth * atlasHeight];
        for (int i = 0; i < clearPixels.Length; i++)
        {
            clearPixels[i] = Color.clear;
        }
        atlasTexture.SetPixels(clearPixels);

        // 当前图集的填充位置
        int currentX = padding;
        int currentY = padding;
        int maxRowHeight = 0;

        // 合并纹理到图集中
        foreach (Texture2D texture in textures)
        {
            if (currentX + texture.width + padding > atlasWidth)
            {
                // 换行
                currentX = padding;
                currentY += maxRowHeight + padding;
                maxRowHeight = 0;
            }

            if (currentY + texture.height + padding > atlasHeight)
            {
                Debug.LogError("图集空间不足,无法容纳所有纹理");
                break;
            }

            // 复制纹理到图集
            atlasTexture.SetPixels(currentX, currentY, texture.width, texture.height, texture.GetPixels());

            // 记录纹理在图集中的位置
            Rect uvRect = new Rect((float)currentX / atlasWidth, (float)currentY / atlasHeight, (float)texture.width / atlasWidth, (float)texture.height / atlasHeight);
            uvRects.Add(uvRect);

            // 更新当前填充位置
            currentX += texture.width + padding;
            maxRowHeight = Mathf.Max(maxRowHeight, texture.height);
        }

        // 应用图集纹理
        atlasTexture.Apply();

        // 在屏幕上显示图集
        GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
        quad.transform.localScale = new Vector3(10, 10, 1);
        quad.GetComponent<Renderer>().material.mainTexture = atlasTexture;
    }

    void OnGUI()
    {
        // 在屏幕上显示图集
        GUI.DrawTexture(new Rect(0, 0, atlasWidth, atlasHeight), atlasTexture);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

详细解释

  1. 收集纹理:

    • public List textures;:定义一个列表来存储需要合并的纹理。
  2. 创建图集:

    • atlasTexture = new Texture2D(atlasWidth, atlasHeight, TextureFormat.RGBA32, false);:创建一个空的图集纹理。
    • 初始化图集纹理为透明:使用 SetPixels 方法将图集纹理初始化为透明。
  3. 合并纹理:

    • 使用嵌套的循环遍历所有纹理,并将它们逐个复制到图集中。
    • 记录每个纹理在图集中的位置(UV 坐标),以便后续使用。
  4. 应用图集:

    • atlasTexture.Apply();:调用 Apply 方法将所有的像素更改应用到图集纹理中。
    • 在屏幕上显示图集:创建一个四边形(Quad)并将图集纹理应用到其材质上,以便在场景中可视化图集。

使用图集中的纹理

在生成图集后,你可能需要在游戏中使用图集中的纹理。为了方便管理和使用这些纹理,可以创建一个简单的管理器类来存储纹理的 UV 坐标,并提供获取纹理 UV 坐标的方法。

以下是一个示例,展示如何使用生成的图集和 UV 坐标:

using UnityEngine;
using System.Collections.Generic;

public class DynamicAtlasManager : MonoBehaviour
{
    public DynamicAtlasGenerator atlasGenerator; // 引用动态图集生成器
    private Dictionary<string, Rect> textureUVs; // 存储纹理名称和 UV 坐标的字典

    void Start()
    {
        textureUVs = new Dictionary<string, Rect>();

        // 假设纹理名称与列表中的索引对应
        for (int i = 0; i < atlasGenerator.textures.Count; i++)
        {
            string textureName = atlasGenerator.textures[i].name;
            Rect uvRect = atlasGenerator.uvRects[i];
            textureUVs.Add(textureName, uvRect);
        }
    }

    public Rect GetTextureUV(string textureName)
    {
        if (textureUVs.ContainsKey(textureName))
        {
            return textureUVs[textureName];
        }
        else
        {
            Debug.LogError("纹理名称不存在:" + textureName);
            return Rect.zero;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

使用图集中的纹理示例

假设你有一个 2D 游戏,并且需要在精灵渲染器(SpriteRenderer)中使用图集中的纹理。以下是一个示例,展示如何使用 DynamicAtlasManager 获取纹理的 UV 坐标,并应用到精灵渲染器中:

using UnityEngine;

public class SpriteFromAtlas : MonoBehaviour
{
    public DynamicAtlasManager atlasManager; // 引用动态图集管理器
    public string textureName; // 要使用的纹理名称

    void Start()
    {
        // 获取图集纹理
        Texture2D atlasTexture = atlasManager.atlasGenerator.atlasTexture;

        // 获取纹理的 UV 坐标
        Rect uvRect = atlasManager.GetTextureUV(textureName);

        // 创建一个新的精灵
        Sprite sprite = Sprite.Create(atlasTexture, new Rect(uvRect.x * atlasTexture.width, uvRect.y * atlasTexture.height, uvRect.width * atlasTexture.width, uvRect.height * atlasTexture.height), new Vector2(0.5f, 0.5f));

        // 应用精灵到精灵渲染器
        SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = sprite;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

详细解释

  1. DynamicAtlasManager:

    • textureUVs:一个字典,用于存储纹理名称和对应的 UV 坐标。
    • Start 方法:在开始时,将 DynamicAtlasGenerator 中的纹理名称和 UV 坐标存储到字典中。
    • GetTextureUV 方法:提供一个方法,根据纹理名称获取对应的 UV 坐标。
  2. SpriteFromAtlas:

    • atlasManager:引用 DynamicAtlasManager,用于获取图集和 UV 坐标。
    • textureName:要使用的纹理名称。
    • Start 方法:在开始时,获取图集纹理和对应的 UV 坐标,创建一个新的精灵,并将其应用到精灵渲染器中。

总结

动态生成图集是一种有效的优化技术,可以减少纹理切换,提高渲染效率。通过收集纹理、创建图集、合并纹理和应用图集,可以在 Unity 中实现动态图集的生成和使用。通过管理器类,可以方便地管理和使用图集中的纹理,从而在游戏中实现高效的纹理管理和渲染。

动态生成图集性能问题

在 Unity 中动态生成图集虽然可以带来很多好处,但也可能引发一些性能问题。以下是一些常见的性能问题及其解决方案:

性能问题

  1. 图集生成时间:

    • 动态生成图集需要时间,特别是当有大量纹理需要合并时,可能会导致明显的延迟。
    • 解决方案:尽量在非关键帧或加载屏幕期间生成图集,避免在游戏运行时频繁生成图集。
  2. 内存占用:

    • 动态生成图集会占用额外的内存,特别是当图集尺寸较大时。
    • 解决方案:合理规划图集的尺寸,避免生成过大的图集。可以考虑将图集分割成多个较小的图集。
  3. 纹理上传到 GPU:

    • 每次生成或更新图集后,需要将图集纹理上传到 GPU,这可能会导致性能瓶颈。
    • 解决方案:尽量减少图集的更新频率,避免频繁上传纹理到 GPU。
  4. 纹理拼接和边缘问题:

    • 在合并纹理时,可能会出现纹理拼接不当或边缘渗色的问题。
    • 解决方案:在生成图集时添加适当的边距(padding),并使用纹理边缘扩展技术。

优化策略

  1. 预生成图集:

    • 尽量在编辑器中预生成图集,而不是在运行时动态生成。可以使用 Unity 的 Sprite Packer 或第三方工具如 TexturePacker 来预生成图集。
  2. 异步生成图集:

    • 如果必须在运行时生成图集,可以考虑使用异步方法来生成图集,避免阻塞主线程。
    • 示例代码:
      using UnityEngine;
      using System.Collections;
      using System.Collections.Generic;
      
      public class AsyncDynamicAtlasGenerator : MonoBehaviour
      {
          public List<Texture2D> textures;
          public int atlasWidth = 1024;
          public int atlasHeight = 1024;
          public int padding = 2;
      
          private Texture2D atlasTexture;
          private List<Rect> uvRects;
      
          void Start()
          {
              StartCoroutine(GenerateAtlasAsync());
          }
      
          IEnumerator GenerateAtlasAsync()
          {
              atlasTexture = new Texture2D(atlasWidth, atlasHeight, TextureFormat.RGBA32, false);
              uvRects = new List<Rect>();
      
              Color[] clearPixels = new Color[atlasWidth * atlasHeight];
              for (int i = 0; i < clearPixels.Length; i++)
              {
                  clearPixels[i] = Color.clear;
              }
              atlasTexture.SetPixels(clearPixels);
      
              int currentX = padding;
              int currentY = padding;
              int maxRowHeight = 0;
      
              foreach (Texture2D texture in textures)
              {
                  if (currentX + texture.width + padding > atlasWidth)
                  {
                      currentX = padding;
                      currentY += maxRowHeight + padding;
                      maxRowHeight = 0;
                  }
      
                  if (currentY + texture.height + padding > atlasHeight)
                  {
                      Debug.LogError("图集空间不足,无法容纳所有纹理");
                      break;
                  }
      
                  atlasTexture.SetPixels(currentX, currentY, texture.width, texture.height, texture.GetPixels());
      
                  Rect uvRect = new Rect((float)currentX / atlasWidth, (float)currentY / atlasHeight, (float)texture.width / atlasWidth, (float)texture.height / atlasHeight);
                  uvRects.Add(uvRect);
      
                  currentX += texture.width + padding;
                  maxRowHeight = Mathf.Max(maxRowHeight, texture.height);
      
                  yield return null; // 每次处理一个纹理后暂停,避免阻塞主线程
              }
      
              atlasTexture.Apply();
          }
      
          void OnGUI()
          {
              GUI.DrawTexture(new Rect(0, 0, atlasWidth, atlasHeight), atlasTexture);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
  3. 分块生成图集:

分块生成图集是一种将大图集分割成多个较小图集的方法,以减少单个图集的生成和更新时间。这种方法可以有效地管理内存和提高性能。

以下是一个示例代码,展示如何实现分块生成图集:

using UnityEngine;
using System.Collections.Generic;

public class ChunkedAtlasGenerator : MonoBehaviour
{
    public List<Texture2D> textures; // 要合并的纹理列表
    public int chunkSize = 512; // 每个图集块的大小
    public int padding = 2; // 纹理之间的间距

    private List<Texture2D> atlasTextures; // 存储生成的图集块
    private List<List<Rect>> uvRectsList; // 存储每个图集块中纹理的 UV 坐标

    void Start()
    {
        GenerateChunkedAtlas();
    }

    void GenerateChunkedAtlas()
    {
        atlasTextures = new List<Texture2D>();
        uvRectsList = new List<List<Rect>>();

        int currentX = padding;
        int currentY = padding;
        int maxRowHeight = 0;

        Texture2D currentAtlas = CreateNewAtlas();
        List<Rect> currentUVRects = new List<Rect>();

        foreach (Texture2D texture in textures)
        {
            if (currentX + texture.width + padding > chunkSize)
            {
                // 换行
                currentX = padding;
                currentY += maxRowHeight + padding;
                maxRowHeight = 0;
            }

            if (currentY + texture.height + padding > chunkSize)
            {
                // 当前图集块已满,创建新的图集块
                atlasTextures.Add(currentAtlas);
                uvRectsList.Add(currentUVRects);

                currentAtlas = CreateNewAtlas();
                currentUVRects = new List<Rect>();

                currentX = padding;
                currentY = padding;
                maxRowHeight = 0;
            }

            // 复制纹理到当前图集块
            currentAtlas.SetPixels(currentX, currentY, texture.width, texture.height, texture.GetPixels());

            // 记录纹理在当前图集块中的位置
            Rect uvRect = new Rect((float)currentX / chunkSize, (float)currentY / chunkSize, (float)texture.width / chunkSize, (float)texture.height / chunkSize);
            currentUVRects.Add(uvRect);

            // 更新当前填充位置
            currentX += texture.width + padding;
            maxRowHeight = Mathf.Max(maxRowHeight, texture.height);
        }

        // 添加最后一个图集块
        atlasTextures.Add(currentAtlas);
        uvRectsList.Add(currentUVRects);

        // 应用所有图集块
        foreach (var atlas in atlasTextures)
        {
            atlas.Apply();
        }
    }

    Texture2D CreateNewAtlas()
    {
        Texture2D atlas = new Texture2D(chunkSize, chunkSize, TextureFormat.RGBA32, false);
        Color[] clearPixels = new Color[chunkSize * chunkSize];
        for (int i = 0; i < clearPixels.Length; i++)
        {
            clearPixels[i] = Color.clear;
        }
        atlas.SetPixels(clearPixels);
        return atlas;
    }

    void OnGUI()
    {
        // 在屏幕上显示所有图集块
        for (int i = 0; i < atlasTextures.Count; i++)
        {
            GUI.DrawTexture(new Rect(i * chunkSize, 0, chunkSize, chunkSize), atlasTextures[i]);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

详细解释

  1. 创建新图集块:

    • CreateNewAtlas 方法用于创建一个新的图集块,并初始化为透明。
  2. 生成分块图集:

    • GenerateChunkedAtlas 方法遍历所有纹理,将它们逐个复制到当前图集块中。
    • 如果当前图集块已满,则创建一个新的图集块,并继续复制剩余的纹理。
  3. 记录 UV 坐标:

    • 每个纹理在图集块中的位置(UV 坐标)被记录下来,以便后续使用。
  4. 应用图集块:

    • 调用 Apply 方法将所有的像素更改应用到每个图集块中。
  5. 显示图集块:

    • 在 OnGUI 方法中,使用 GUI.DrawTexture 方法在屏幕上显示所有生成的图集块。
注:本文转载自blog.csdn.net的你一身傲骨怎能输的文章"https://blog.csdn.net/qq_33060405/article/details/143378829"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

134
游戏
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top