简体   繁体   English

为多个孩子的游戏 object 优化精灵变化

[英]Optimizing sprite change for game object with multiple children

I have an object structure like this:我有一个这样的 object 结构:

在此处输入图像描述

Which I have to create a player with several different equipment.我必须用几种不同的设备创建一个播放器。 For this, I am using a custom animation controller which changes an index that would determine sprites out of a few spritesheets:为此,我使用自定义的 animation controller,它更改了一个索引,该索引将确定几个 spritesheet 中的 sprites:

AnimationController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimationController : MonoBehaviour {
  private float waitTime = 0.06f;
  private float timer = 0.0f;

  private bool grounded;
  private bool falling;
  private bool jumping;
  private bool running;
  private bool horizontalCollision;

  private HeroMovement heroMovementScript;
  private GameObject hero;

  private int[] runningSprites = { 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19 };
  private int animationIndex = 0;

  public int currentHeroSpriteIndex;

  // Start is called before the first frame update
  void Start() {
    hero = GameObject.Find("Hero");
    heroMovementScript = hero.GetComponent<HeroMovement>();
  }

  // Update is called based on timer and waitTime
  void Update() {
    timer += Time.deltaTime;

    if (timer > waitTime) {
      grounded = heroMovementScript.isGrounded;
      falling = heroMovementScript.isFalling;
      jumping = heroMovementScript.isJumping;
      running = heroMovementScript.isRunning;
      horizontalCollision = heroMovementScript.horizontalCollision;

      if (running) {
        currentHeroSpriteIndex = runningSprites[animationIndex % 14];
        animationIndex++;
      } else {
        currentHeroSpriteIndex = 0;
        animationIndex = 0;
      }

      timer = timer - waitTime;
    }
  }
}

So here I run the update function a set wait time, and based on variables from a HeroMovement script do I update the booleans to determine how to update the index based on declared arrays:所以在这里我运行update function 设置等待时间,并根据HeroMovement脚本中的变量更新布尔值以确定如何根据声明的 arrays 更新索引:

HeroMovement.cs

using UnityEngine;

public class HeroMovement : MonoBehaviour {
  [SerializeField] private float speed;
  [SerializeField] private float jumpHeight;
  private Rigidbody2D body;
  private Animator anim;
  private AnimationController animationControllerScript;
  private int currentHeroSpriteIndex;
  private SpriteRenderer heroRenderer;
  private HeroResources heroResourcesScript;
  private Sprite[] heroSprites;
  public bool isGrounded;
  public bool isFalling;
  public bool isJumping;
  public bool isFacingLeft;
  public bool isRunning;

  public bool horizontalCollision;

  public int collisionCounter = 0;

  // called when script is loaded
  private void Awake() {
    body = GetComponent<Rigidbody2D>();
    anim = GetComponent<Animator>();
    animationControllerScript = GetComponent<AnimationController>();
    heroRenderer = GetComponent<SpriteRenderer>();
    heroResourcesScript = GetComponent<HeroResources>();

    currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
    heroSprites = heroResourcesScript.heroSprites;
  }

  // called on every frame of the game
  private void Update() {
    float horizontalInput = Input.GetAxis("Horizontal");
    float verticalSpeed = body.velocity.y;

    // x axis movement
    if (!horizontalCollision) {
      body.velocity = new Vector2(horizontalInput * speed, body.velocity.y);

      // flip player when moving left
      if (horizontalInput > 0.01f && isGrounded) {
        transform.localScale = Vector3.one;
        isFacingLeft = false;
      }
      // flip player when moving right
      else if (horizontalInput < -0.01f && isGrounded) {
        transform.localScale = new Vector3(-1, 1, 1);
        isFacingLeft = true;
      }
    }

    // jumping
    if (Input.GetKey(KeyCode.Space) && isGrounded) {
      Jump();
    }

    isRunning = horizontalInput != 0 && !isJumping && !isFalling;

    // set animator parameters
    // anim.SetBool("isRunning", horizontalInput != 0 && !isJumping && !isFalling);
    // anim.SetBool("isGrounded", isGrounded);
    // anim.SetBool("isFalling", isFalling);
    // anim.SetBool("isJumping", isJumping);
    // anim.SetBool("horizontalCollision", horizontalCollision);

    if (!isGrounded && verticalSpeed < -1) {
      Fall();
    }
  }

  private void Fall() {
    isFalling = true;
  }

  private void Jump() {
    body.velocity = new Vector2(body.velocity.x, jumpHeight);
    isJumping = true;
    isGrounded = false;
  }

  private void OnCollisionEnter2D(Collision2D collision) {
    Collider2D collider = collision.collider;
    Collider2D otherCollider = collision.otherCollider;

    if (collision.gameObject.tag == "Ground") {
      if (otherCollider.tag == "Hero") {
        if (!isHorizontalCollision(otherCollider, collider)) {
          isGrounded = true;
          isFalling = false;
          isJumping = false;
          horizontalCollision = false;
        } else {          
          horizontalCollision = true;

          if (isBottomCollision(otherCollider, collider)) {
            horizontalCollision = false;
          }
        }
      }      
    }

    collisionCounter++;
  }

  private bool isBottomCollision(Collider2D collider1, Collider2D collider2) {
    int c1BottomEdge = (int) collider1.bounds.max.y;
    int c2TopEdge = (int) collider2.bounds.min.y;

    return c1BottomEdge == c2TopEdge;
  }

  private bool isHorizontalCollision(Collider2D collider1, Collider2D collider2) {
    int c1RightEdge = (int) collider1.bounds.max.x;
    int c1LeftEdge = (int) collider1.bounds.min.x;

    int c2RightEdge = (int) collider2.bounds.max.x;
    int c2LeftEdge = (int) collider2.bounds.min.x;

    return (c1RightEdge == c2LeftEdge) || (c1LeftEdge == c2RightEdge);
  }

  private void OnCollisionExit2D(Collision2D collision) {
    collisionCounter--;

    if (collisionCounter == 0) {
      isGrounded = false;
    }
  }

  // private bool isGrounded() {
  //   RaycastHit2D raycastHit = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0, Vector2.down, 0.1f, groundLayer);
  //   return raycastHit.collider != null;
  // }
}

Each of the child game objects also make use of the currentHeroSpriteIndex to update their sprites, their color based on serialized fields, and their position based on the parent's:每个子游戏对象还利用currentHeroSpriteIndex来更新它们的精灵、基于序列化字段的颜色以及基于父对象的 position:

SpritePosition.cs

using UnityEngine;

public class SpritePosition : MonoBehaviour {
  [SerializeField] private string objectName;
  [SerializeField] private int objectIndex;
  [SerializeField] private int objectR;
  [SerializeField] private int objectG;
  [SerializeField] private int objectB;
  private Rigidbody2D body;
  private SpriteRenderer objectRenderer;
  private GameObject hero;
  private Rigidbody2D heroRigidBody;
  private AnimationController animationControllerScript;
  private int currentHeroSpriteIndex;
  private HeroResources heroResourcesScript;
  private Sprite[] spriteGroup;

  private void Start() {
    body = GetComponent<Rigidbody2D>();
    objectRenderer = GetComponent<SpriteRenderer>();

    hero = GameObject.Find("Hero");
    heroRigidBody = hero.GetComponent<Rigidbody2D>();
    animationControllerScript = hero.GetComponent<AnimationController>();
    currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;

    heroResourcesScript = hero.GetComponent<HeroResources>();
    spriteGroup = heroResourcesScript.spriteGroup[objectName][objectIndex];

    float floatR = objectR / 255f;
    float floatG = objectG / 255f;
    float floatB = objectB / 255f;

    if (objectName != "body") {
      objectRenderer.color = new Color(floatR, floatG, floatB, 1);
    }
  }

  private void Update() {
    SetSprite();
    SetPosition();
  }
  
  private void SetSprite() {
    if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex) {
      currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
      objectRenderer.sprite = spriteGroup[currentHeroSpriteIndex];
    }

    transform.localScale = Vector3.one;
  }

  // for this to work, the game object must have a
  // RigidBody2D component with Freeze Position active
  // for X and Y axis
  private void SetPosition() {
    Vector2 currentHeroPosition = heroRigidBody.position;
    transform.position = currentHeroPosition;
  }
}

And for this, I use another script, HeroResources which loads all of the sprites on Dictionaries:为此,我使用了另一个脚本HeroResources ,它加载了词典中的所有精灵:

HeroResources.cs

using System.Collections.Generic;
using UnityEngine;

public class HeroResources : MonoBehaviour {
  const int PANTS_LIMIT = 1;
  const int BOOTS_LIMIT = 1;
  const int SHIRT_LIMIT = 1;
  const int TUNIC_LIMIT = 2;
  const int BELT_LIMIT = 1;

  public Dictionary<string, Dictionary<int, Sprite[]>> spriteGroup = new Dictionary<string, Dictionary<int, Sprite[]>>();
  public Sprite[] heroSprites = new Sprite[180];

  public Dictionary<int, Sprite[]> getAllSprites(string name, int limit) {
    Dictionary<int, Sprite[]> spriteList = new Dictionary<int, Sprite[]>();

    if (name == "hero") {
      spriteList.Add(0, Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body"));
    } else {
      for (int i = 0; i < limit; i++) {
        spriteList.Add(i, Resources.LoadAll<Sprite>("Spritesheets/" + name + "/" + (i + 1)));
      }
    }

    return spriteList;
  }

  void Awake() {
    spriteGroup.Add("body", getAllSprites("hero", 1));
    spriteGroup.Add("pants", getAllSprites("pants", PANTS_LIMIT));
    spriteGroup.Add("boots", getAllSprites("boots", BOOTS_LIMIT));
    spriteGroup.Add("shirt", getAllSprites("shirt", SHIRT_LIMIT));
    spriteGroup.Add("tunic", getAllSprites("tunic", TUNIC_LIMIT));
    spriteGroup.Add("belt", getAllSprites("belt", BELT_LIMIT));

    heroSprites = Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body");
  }
}

So, to clarify: the Hero game object has the scripts AnimationController , HeroMovement , and HeroResources attached to it, while the child game objects only have the SpritePosition script attached.因此,澄清一下: Hero游戏 object 附加了脚本AnimationControllerHeroMovementHeroResources ,而子游戏对象仅附加了SpritePosition脚本。

The idea is to load the sprites, then based on the logic in the movement script, decide on the booleans to use for the animator, check which is active (currently I only have running working), then based on which one is true determine a sprite index to use.这个想法是加载精灵,然后根据运动脚本中的逻辑,决定用于动画师的布尔值,检查哪个是活动的(目前我只有running工作),然后根据哪个是真的确定一个要使用的精灵索引。 All sprites have the same name and dimensions which is why a single index works to change all.所有精灵都具有相同的名称和尺寸,这就是为什么单个索引可以改变所有精灵的原因。 I'm basically doing it this way to avoid having hundreds of animations for each different equipment in use.我这样做基本上是为了避免为每个不同的设备使用数百个动画。

在此处输入图像描述

So, while the sprites update this way in sync, I'm not sure if I'm not optimizing the sprite changing (and redrawing in color) process, because when I press arrow keys to run, I get the following:因此,虽然精灵以这种方式同步更新,但我不确定我是否没有优化精灵更改(和重新绘制颜色)过程,因为当我按箭头键运行时,我得到以下信息:

在此处输入图像描述

Although the screen recorder I used seems to slow down the playback, there are brief moments in which the hero's body is seen through the pants (and sometimes the boots) although the sprites are supposed to not overlap.虽然我使用的屏幕录像机似乎减慢了播放速度,但仍有短暂的瞬间透过裤子(有时是靴子)看到英雄的身体,尽管精灵应该不会重叠。

I'm not sure if this is because of a computer's memory, but there are 14 sprites used for running movement, which would amount to 84 sprites in total used in the span of the 2 or so seconds which the animation lasts.我不确定这是否是因为计算机的 memory,但是有 14 个 sprite 用于运行运动,在 animation 持续的 2 秒左右的时间跨度内总共使用了 84 个 sprite。 Is it that I'm using too much memory to load the sprites needed?是不是我使用了太多 memory 来加载所需的精灵? Should I maybe try to find a way to merge sprites to only have a single child whose sprite gets updated?我是否应该尝试找到一种方法来合并 sprite,以便只有一个 sprite 得到更新的孩子? Please let me know if anyone has suggestions on how to improve my code's performance.如果有人对如何提高我的代码的性能有任何建议,请告诉我。

EDIT:编辑:

I opted to change my code to do away with the HeroResources.cs script which ultimately loads every available asset at the beginning to instead include the resource load in SpritePosition.cs so that each child game object takes care of its own spritesheet:我选择更改我的代码以取消HeroResources.cs脚本,该脚本最终在开始时加载所有可用资产,而是将资源加载包含在SpritePosition.cs中,以便每个子游戏 object 处理自己的 spritesheet:

using UnityEngine;

public class SpritePosition : MonoBehaviour {
  [SerializeField] private string objectName;
  [SerializeField] private int objectIndex;
  [SerializeField] private int objectR;
  [SerializeField] private int objectG;
  [SerializeField] private int objectB;
  private Rigidbody2D body;
  private SpriteRenderer objectRenderer;
  private GameObject hero;
  private Rigidbody2D heroRigidBody;
  private AnimationController animationControllerScript;
  private int currentHeroSpriteIndex;
  private Sprite[] spriteGroup;


  private void Start() {
    body = GetComponent<Rigidbody2D>();
    objectRenderer = GetComponent<SpriteRenderer>();

    hero = GameObject.Find("Hero");
    heroRigidBody = hero.GetComponent<Rigidbody2D>();
    animationControllerScript = hero.GetComponent<AnimationController>();
    currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;

    if (objectName != "body") {
      LoadSpriteSheet();
    } else {
      spriteGroup = Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body");
    }
    
    float floatR = objectR / 255f;
    float floatG = objectG / 255f;
    float floatB = objectB / 255f;

    if (objectName != "body") {
      objectRenderer.color = new Color(floatR, floatG, floatB, 1);
    }
  }

  private void Update() {
    SetSprite();
    SetPosition();
  }

  private void LoadSpriteSheet() {
    spriteGroup = Resources.LoadAll<Sprite>("Spritesheets/" + name + "/" + (objectIndex + 1));
  }
  
  private void SetSprite() {
    if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex) {
      currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
      objectRenderer.sprite = spriteGroup[currentHeroSpriteIndex];
    }

    transform.localScale = Vector3.one;
  }

  private void SetPosition() {
    Vector2 currentHeroPosition = heroRigidBody.position;
    transform.position = currentHeroPosition;
  }
}

This, however, doesn't seem to improve performance.然而,这似乎并没有提高性能。 I've checked the profiler using deep profiling, and though I see large spikes I don't see garbage coming from the SetSprite function nor much of a delay compared to EditorLoop or PlayerLoop :我已经使用深度分析检查了分析器,虽然我看到了大的尖峰,但我没有看到来自SetSprite function 的垃圾,与EditorLoopPlayerLoop相比也没有太多延迟:

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

I assumed there would be issues in how often the function to change sprites gets called, or how much garbage would be generated with the constant sprite changing, but it seems that this might not be the issue.我假设在 function 更改精灵的频率被调用时会出现问题,或者随着精灵的不断变化会产生多少垃圾,但似乎这可能不是问题。

If you ever want to discover where lag or high memory usage is coming from use the unity profiler, if possible use a 3rd party profiler because unity's is kinda meh, also use deep profiling (unity will profile every method call, good to see what changes are causing lag, remember that using the profiler will reduce performance and increase memory usage as long as you have it on).如果你想发现滞后或高 memory 的使用来自使用统一分析器,如果可能的话使用第 3 方分析器,因为统一的有点无聊,也使用深度分析(统一将分析每个方法调用,很高兴看到有什么变化导致滞后,请记住,使用探查器会降低性能并增加 memory 使用率,只要您打开它)。 There is a lot of stuff that you NEED to improve, but all of the issues I can think off will be obvious with deep profiling.有很多东西需要改进,但我能想到的所有问题都会通过深度分析变得显而易见。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM