【问题标题】:What is the fastest method to create and render large number of 2d sprites in Unity?在 Unity 中创建和渲染大量 2d 精灵的最快方法是什么?
【发布时间】:2019-01-07 22:19:42
【问题描述】:

我正在创建一个无限地形生成器,我需要在玩家四处移动时不断更新地形。

一切都很好,但我无法找到有关动态创建和渲染精灵的最快方法的信息。

关于精灵的信息:

我正在使用 1 个精灵表,其中包含我的地形所需的所有帧。草、沙、水等都在一个 .png 文件中。所有帧都存储在一个数组中,我可以轻松地从中获取它们。

当前正确显示我的精灵对象需要执行的步骤:

  1. 创建新对象。
  2. 在二维空间中设置它们的位置。
  3. 添加组件。
  4. 根据需要缩放它们。

生成的精灵被存储在一个名为chunkGameObject 数组中。这是我目前生成精灵的方式。

        chunk[i] = new GameObject();
        chunk[i].gameObject.transform.position = new Vector2(spriteCoordX, spriteCoordY);
        chunk[i].AddComponent<SpriteRenderer>();
        SpriteRenderer renderer = chunk[i].GetComponent<SpriteRenderer>();
        renderer.sprite = tiles[calculatedFrameId]; //Set correct sprite frame.
        chunk[i].gameObject.transform.localScale = new Vector2(6.75f , 6.75f);

我不知道,每次我想在代码中创建新精灵时添加组件和缩放似乎是多余且不必要的,我相信有更好的方法来做到这一点。

总结一下:

我需要最好(最快)的方法来生成大量精灵,设置它们的框架、位置和适当的比例。

【问题讨论】:

  • 使用纹理图集作为图像来源。使用 prefabs 作为精灵。
  • C# 中的 new 运算符很慢。如果您重用旧对象而不是销毁它们,那会更快。见gameprogrammingpatterns.com/object-pool.html
  • 你签出Object pooling了吗?
  • @FICHEKK 向我们展示您如何确定是否需要将chunk[i] 设置为新的游戏对象?这是一个初始化方法还是检测玩家位置然后在玩家进入新区域时创建一个新的游戏对象?
  • @FICHEKK,最后您是否需要这些对象始终处于活动状态,或者“自定义”剔除方法就足够了?(对于不再可见的图块)

标签: c# unity3d optimization


【解决方案1】:

忍不住把这张图片贴在这里,因为这真的有几千字,感谢@AidenH 的“对象池”评论!

【讨论】:

  • 这不是真正的答案! xD
  • 不一定要在代码中。对我来说,这是一个答案:)
【解决方案2】:

我很抱歉这花了我一些时间...我最终创建了一个项目,在该项目中我生成了一个开放世界,我使用 Perlin Noise 根据玩家所在的位置生成地图的一部分(因为我不有一个实际的地形)

我为此使用了对象池,所以我有一个知道世界是什么样子的世界管理器(或者在我的情况下它使用 Perlin 噪声),这个 WorldManager 池化图块。

注意:这是使用对象池的一种方法。

所以基本上它看起来像这样:

public class WorldManager : MonoBehaviour {
    // Being cheap here for the time, if you use this route make sure to use a proper singleton pattern
    static public WorldManager instance; 

    [SerializeField]  // Created a prefab for all my tile objects
    private TileObject tilePrefab; 

    [SerializeField]
    private int StartingTilePool = 300;

    [SerializeField] // In my case this list stored 1 sprite, and I just changed that sprite color depending on the value of perlin noise
    List<Sprite> terrainSprites; 

    private Queue<TileObject> objectPool = new Queue<TileObject>();

    void Start() {
        instance = this; // Again use a proper singleton pattern in your project.
        GenerateTilePool();
        LoadFirstSetOfTiles();
    }

    private void LoadFirstSetOfTiles()
    {
        // my player always starts at 0,0..
        for(int x = -SpawnTileBoundry.HorizontalExtents; x <= SpawnTileBoundry.HorizontalExtents; ++x) 
        {
             for(int y = -SpawnTileBoundry.VerticalExtents; y <= SpawnTileBoundry.VerticalExtents; ++y) 
             {
                 SetActiveTile(x,y);
             }
        }
    }

    private void GenerateTilePool()
    {
        for(int i =0; i < tilesToGenerate; ++i)
        {
            TileObject tempTile = Instantiate(tilePrefab);
            EnqueTile(tempTile);
        }
    }

    public void EnqueTile(TileObject tile)
    {
        objectPool.Enqueue(tile);
        tile.gameObject.SetActive(false);
    }

    public void SetActiveTile(int x, int y)
    {
        TileObject newTile = null;
        if(objectPool.count > 0)
        {
            newTile = objectPool.Dequeue();
        }
        else
        {
            // We didn't have enough tiles store in our pool so lets make a new 1.
            newTile = Instantiate(tilePrefab);
        }         
        newTile.transform.position = new Vector3(x,y,1); // Used 1 to put it behind my player...
        newTile.gameObject.SetActive(true);
        // The sprite here would be based off of your world data, where mine is only a white box, I use the second parameters to give it a gray-scaled color.
        newTile.UpdateSprite(terrainSprites[0], Mathf.PerlinNoise(x/10.0f, y / 10.0f));
    }
}

这只是我的 WorldManager 为我处理 ObjectPooling... 这是我的 TileObject

[RequireComponent(typeof(SpriteRenderer))]
public class TileObject : MonoBehaviour {
    private SpriteRenderer myRenderer;
    private void Awake() {
        myRenderer = getComponent<SpriteRenderer>();
    }

    void Update()
    {
        if(Mathf.Abs(transform.position.x - Camera.main.transform.position.x) > SpawnTileBoundry.HorizontalExtents || Mathf.Abs(transform.position.y - Camera.main.transform.position.y) > SpawnTileBoundry.VerticalExtents) 
        {
            // With this check the tile knows it is no longer visible,
            // I could have used OnBecameInvisible to handle this
            // but I added a threshold to the extents, that prevent it so
            // players would see tiles "appearing" this caused a nice bug, where
            // if you moved just right a row/col of tiles wouldn't spawn.
            WorldManager.instance.EnqueTile(this);
        }
    }

    public void UpdateSprite(Sprite sprite)
    {
        myRenderer.sprite = sprite;
    }

    public void UpdateSprite(Sprite sprite, float grayColor)
    {
         UpdateSprite(sprite);
         myRenderer.color = new Color(grayColor,grayColor,grayColor,1f);
    }
}

这是我的 SpawnTileBoundry 脚本:

public class WorldManager : MonoBehaviour {
    private int lastX = 0;
    private int lastY = 0;
    static public int HorizontalExtents;
    static public int VerticalExtents;

    void Start() {
        VerticalExtents = (int)Camera.main.orthograpicSize + 2; // +2 is just my threshold
        HorizontalExtents = (int)(VerticalExtents * Screen.width/Screen.height) +3; // +3 is just my threshold you can change these to what you want.
        lastX = (int)transform.position.x;
        lastY = (int)transform.position.y;
    }

    void Update() {
        int newX = (int)transform.position.x;
        int newY = (int)transform.position.y;

        HandleNewTileSpawn(lastX - newX, lastY - newY);
    }

    // This will tell the WorldManager which tiles need to appear
    // We are no longer creating new tiles unless we absolutely have to.
    // We are also only making new tiles appear in the circumstance that
    // we are about to see them.
    void HandleNewTileSpawn(int x, int y)
    {
        if(x != 0) 
        {
            // This code could be refactor to a method so it was less error prone for  changes or tweaks...
            if(x < 0) 
            {
                for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
                {
                    WorldManager.instance.SetActiveTile(lastX + HorizontalExtents, i);
                }
            }
            else
            {
                for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
                {
                    WorldManager.instance.SetActiveTile(lastX - HorizontalExtents, i);
                }
            }
            lastX = (int)transform.position.x;
        }
        if(y != 0)
        {
            if(lastY < 0)
            {
                for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
                {
                    WorldManager.instance.SetActiveTile(i, lastY + VeritcalExtents);
                }
            }
            else
            {
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
                {
                    WorldManager.instance.SetActiveTile(i, lastY - VeritcalExtents);
                }
            }
            lastY = (int)transform.position.y;
        }
    }
}

我正在使用 WorldManager 来处理我的对象池,是的,一开始我仍在实例化很多精灵,但后来我最终停止生成,因为不需要继续生成新对象。

Unity 的文档中没有很多关于对象池的内容,但他们确实有一个视频教程,介绍了对象池的一些基础知识:https://unity3d.com/learn/tutorials/topics/scripting/object-pooling

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-18
    • 2014-05-21
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-02
    • 1970-01-01
    相关资源
    最近更新 更多