在Unity中创建和渲染大量2d精灵的最快方法是什么?

时间:2018-07-31 13:36:58

标签: c# unity3d optimization

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

一切都很好,但是我很难找到有关快速创建和渲染Sprite的最快方法的信息。

有关精灵的信息:

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

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

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

生成的精灵存储在名为GameObject的{​​{1}}数组中。这是我目前生成子画面的方式。

chunk

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

总结:

我需要最好的(最快的)方式来生成大量精灵,设置其帧,位置和适当的比例。

2 个答案:

答案 0 :(得分:2)

禁不住在此发布此图片,因为这确实是成千上万的单词,感谢@AidenH的“对象池”注释!

enter image description here

答案 1 :(得分:0)

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

我为此使用了对象池,因此我有一个世界管理器,它知道世界是什么样子(或者在我的情况下,它使用Perlin Noise),该世界管理器可以合并图块。

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

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

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来处理对象池,是的,我仍然在开始时实例化了许多sprite,但是由于没有必要继续生成新对象,因此我最终停止了生成。

Unity在对象池方面没有很多文档,但是他们有一个视频教程,介绍了对象池的一些基本知识:https://unity3d.com/learn/tutorials/topics/scripting/object-pooling