【问题标题】:Index for sprites does not seem to update精灵索引似乎没有更新
【发布时间】:2021-09-19 01:40:14
【问题描述】:

我正在尝试按照这个问题Create animations programmatically in Unity? 中的示例,基于数组和索引以编程方式更新我的英雄(GameObject)精灵(因为我不想为不同的设备创建数千个动画),然后通过使用索引对孩子做同样的事情。结构如下:

为此,我有以下几点:

更新子元素的脚本,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 SpriteRenderer heroRenderer;
  // private Sprite currentHeroSprite;
  private AnimationController animationControllerScript;
  private int currentHeroSpriteIndex;
  private HeroResources heroResourcesScript;
  private HeroMovement heroMovementScript;
  private Sprite[] spriteGroup;

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

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

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

  private void Update() {
    heroMovementScript = hero.GetComponent<HeroMovement>();

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

    objectRenderer.color = new Color(objectR, objectG, objectB, 1);
    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;
  }
}

我在这里使用这些:

animationControllerScript = hero.GetComponent<AnimationController>();
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;

获取变量currentHeroSpriteIndex 以更新索引。此更新发生在 AnimationController.cs 中:

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

public class AnimationController : MonoBehaviour {
  public int currentHeroSpriteIndex = 0;
  private bool grounded;
  private bool falling;
  private bool jumping;
  private bool running;
  private bool horizontalCollision;

  private HeroResources heroResourcesScript;
  private HeroMovement heroMovementScript;
  private GameObject hero;
  private SpriteRenderer heroRenderer;
  private Sprite[] heroSprites;

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

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

  void setHeroSprite(int index) {
    heroRenderer.sprite = heroSprites[index];
  }

  // Update is called once per frame
  void Update() {
    grounded = heroMovementScript.isGrounded;
    falling = heroMovementScript.isFalling;
    jumping = heroMovementScript.isJumping;
    running = heroMovementScript.isRunning;
    horizontalCollision = heroMovementScript.horizontalCollision;

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

目前,我只有一个特定的动作数组runningSprites,它为我提供了用于运行动画的精灵:

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

所以,在 HeroMovement.cs 中:

using UnityEngine;

public class HeroMovement : MonoBehaviour {
  [SerializeField] private float speed;
  [SerializeField] private float jumpHeight;
  private Rigidbody2D body;
  private Animator anim;
  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>();
  }

  // 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;
  // }
}

我使用以下设置布尔值来验证播放器是否正在运行:

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

然后,在动画脚本中,我得到了英雄的动作并确定它是否像这样运行:

heroMovementScript = hero.GetComponent<HeroMovement>();
.
.
.
running = heroMovementScript.isRunning;

那么如果runningtrue,动画块应该运行,并且(现在因为它是唯一的编码动作)如果不运行则停止(将精灵恢复为 0):

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

问题是,我假设currentHeroSpriteIndex 被分配了runningSprites 数组中当前索引的值,但是当我通过Debug.Log 检查SpritePosition.cs 中的值时,我总是得到一个@ 987654342@。是否与期望脚本属性中的值的变量有关?

另外,如果你想看的话,加载所有精灵的脚本在这里,在 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;

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

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

    return spriteList;
  }

  void Awake() {
    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));
  }

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

如果您知道什么是错误/缺少的,请告诉我。 HeroMovement.cs、HeroResources.cs 和 AnimationController.cs 附加到英雄游戏对象,而 SpritePosition.cs 附加到每个子游戏对象。

更新

经过更多的测试和修复,这里是更新的代码:

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 = 0;

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

  // Update is called once per frame
  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;
    }
  }
}

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;
  }

  private void SetSprite() {
    if (heroSprites.Length != heroResourcesScript.heroSprites.Length) {
      heroSprites = heroResourcesScript.heroSprites;
    }

    if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex) {
      currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
      heroRenderer.sprite = heroSprites[currentHeroSpriteIndex];
      Debug.Log(heroSprites[currentHeroSpriteIndex].name);
    }
  }

  // called on every frame of the game
  private void Update() {
    SetSprite();

    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;
  // }
}

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[]>();

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

    return spriteList;
  }

  void Awake() {
    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");
  }
}

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 SpriteRenderer heroRenderer;
  // private Sprite currentHeroSprite;
  private AnimationController animationControllerScript;
  private int currentHeroSpriteIndex;
  private HeroResources heroResourcesScript;
  private HeroMovement heroMovementScript;
  private Sprite[] spriteGroup;

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

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

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

  private void Update() {
    heroMovementScript = hero.GetComponent<HeroMovement>();

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

    objectRenderer.color = new Color(objectR, objectG, objectB, 1);
    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;
  }
}

现在唯一真正的问题是 Hero 游戏对象的渲染器似乎没有更新设置的精灵。

【问题讨论】:

  • AnimationController 的 currentHeroSpriteIndex 发生变化时,您在哪里更新了 SpritePosition 脚本的 currentHeroSpriteIndex 脚本? SpritePosition 脚本的 currentHeroSpriteIndex 的值始终为 0,因为它只在 Start 方法中分配一次。
  • 哦,你是对的,我可能应该检查该值的变化并重新分配update
  • @nIcEcOw 我更新为使用条件if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex),虽然它们现在更新了,但转换速度非常快。如何指定运行Update 函数的频率(以毫秒为单位)?
  • @gfcf14 使用Time.deltaTime。它是自最后一帧以来经过的时间(以秒为单位)。它是一个浮点数,因此您可以将秒转换为毫秒。跟踪已经过去了多少时间,如果已经过去了足够多的时间,则增加索引并重置计时器。

标签: c# unity3d


【解决方案1】:

我认为这个问题是一个逻辑错误。您提到您的精灵索引始终为 0。这就是我看到问题的方式。

AnimationController.cs 中,在Update 方法中,你有条件

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

据我了解,该声明取决于 bool running 是真还是假。每当它为 false 时,索引就会被设置回 0。

在条件的正上方,你正在做这个变量的赋值

running = heroMovementScript.isRunning;

遍历对象类型,heroMovementScriptHeroMovement 类型,因此在此相应脚本Update 函数中设置字段isRunning

void Update()
{
 ... 
 isRunning = horizontalInput != 0 && !isJumping && !isFalling;
 ...
}

在我看来,这个脚本中的布尔值isRunning 完全确定了您的动画索引是否更新,或者它是否会持续导致索引为 0。简而言之,我相信这个条件不是你所期望的,并且总是错误的,导致你的动画索引没有更新。

【讨论】:

  • 我正在关注上面@nIcEcOw 的评论,并决定将精灵更新更改为在HeroMovement 脚本中进行(这样AnimationController 将只处理动画更改),但是当我对 Hero 对象做同样的事情时,我得到了一个越​​界错误。我将资源负载更改为使用heroSprites = Resources.LoadAll&lt;Sprite&gt;("Spritesheets/hero");,但不知何故,Sprite 数组似乎返回 null dropbox.com/s/i189llfkfp1tcfe/updatedcode.jpg?dl=0 dropbox.com/s/yj2kg1b44crz5hv/filetree.jpg?dl=0
  • @gfcf14 如果该行与您所写的完全一样,那是因为您提供的精灵路径不完整。它必须阅读heroSprites = Resources.LoadAll&lt;Sprite&gt;("Spritesheets/hero/heor-body.png");。我假设您使用了 .png 扩展名,如果不替换该部分。
  • 我尝试将行更改为使用 heroSprites = Resources.LoadAll&lt;Sprite&gt;("Spritesheets/hero/hero-body.png");,但我不断收到 IndexOutOfBounds。
  • 你从哪里得到IndexOutOfBounds
  • 我在heroRenderer.sprite = heroSprites[currentHeroSpriteIndex];这一行从dropbox.com/s/i189llfkfp1tcfe/updatedcode.jpg?dl=0的更新代码中得到它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-11
  • 1970-01-01
  • 2017-02-09
  • 2012-04-23
  • 2020-06-21
  • 1970-01-01
相关资源
最近更新 更多