【发布时间】: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;
那么如果running 是true,动画块应该运行,并且(现在因为它是唯一的编码动作)如果不运行则停止(将精灵恢复为 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。它是自最后一帧以来经过的时间(以秒为单位)。它是一个浮点数,因此您可以将秒转换为毫秒。跟踪已经过去了多少时间,如果已经过去了足够多的时间,则增加索引并重置计时器。