Unity 2D平台游戏问题:投射物无法沿玩家朝向发射
Unity 2D投射物始终朝右发射的问题解决
我正在开发Unity 2D平台游戏,需要实现投射物沿玩家朝向发射的攻击机制,但当前投射物始终仅朝右发射,即使玩家朝左也无法改变方向。作为C#新手,我尝试过修改Projectile.cs的SetDirection方法但未解决问题,相关关键脚本如下:
关键脚本代码
Projectile.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Projectile : MonoBehaviour { [SerializeField] private float speed; private float direction; private bool hit; private Animator anim; private BoxCollider2D boxCollider; private void Awake() { anim = GetComponent<Animator>(); boxCollider = GetComponent<BoxCollider2D>(); } private void Update() { if(hit) return; float movementSpeed = speed * Time.deltaTime * direction; transform.Translate(movementSpeed, 0, 0); } private void OnTriggerEnter2D(Collider2D collision) { hit = true; boxCollider.enabled = false; anim.SetTrigger("explode"); } public void SetDirection(float _direction) { direction = _direction; gameObject.SetActive(true); hit = false; boxCollider.enabled = true; float localScaleX = transform.localScale.x; if(Mathf.Sign(localScaleX) != _direction) localScaleX = -localScaleX; transform.localScale = new Vector3(localScaleX, transform.localScale.y, transform.localScale.z); } private void Deactivate() { gameObject.SetActive(false); } }
PlayerAttack.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerAttack : MonoBehaviour { [SerializeField] private float attackCooldown; [SerializeField] private Transform firePoint; [SerializeField] private GameObject[] ghostballs; private Animator anim; private PlayerMovement playerMovement; private float cooldownTimer = Mathf.Infinity; private void Awake() { anim = GetComponent<Animator>(); playerMovement = GetComponent<PlayerMovement>(); } private void Update() { if (Input.GetMouseButton(0) && cooldownTimer > attackCooldown && playerMovement.canAttack()) Attack(); cooldownTimer += Time.deltaTime; } private void Attack() { anim.SetTrigger("attack"); cooldownTimer = 0; ghostballs[FindGhostball()].transform.position = firePoint.position; ghostballs[FindGhostball()].GetComponent<Projectile>().SetDirection(Mathf.Sign(transform.localScale.x)); } private int FindGhostball() { for (int i = 0; i < ghostballs.Length; i++) { if (!ghostballs[i].activeInHierarchy) return i; } return 0; } }
PlayerMovement.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovement : MonoBehaviour { //variables private Rigidbody2D rb; private BoxCollider2D coll; private SpriteRenderer sprite; private Animator anim; [SerializeField] private LayerMask jumpableGround; private float dirX = 0f; [SerializeField] private float moveSpeed = 7f; [SerializeField] private float jumpForce = 14f; private enum MovementState { idle, running, jumping, falling } [SerializeField] private AudioSource jumpSoundEffect; // Start is called before the first frame update private void Start() { rb = GetComponent<Rigidbody2D>(); coll = GetComponent<BoxCollider2D>(); sprite = GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); } // Update is called once per frame private void Update() { dirX = Input.GetAxisRaw("Horizontal"); rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y); if (Input.GetButtonDown("Jump") && IsGrounded()) { jumpSoundEffect.Play(); rb.velocity = new Vector2(rb.velocity.x, jumpForce); } AnimState(); } private void AnimState() { MovementState state; if (dirX > 0f) { state = MovementState.running; sprite.flipX = false; } else if (dirX < 0f) { state = MovementState.running; sprite.flipX = true; } else { state = MovementState.idle; } if (rb.velocity.y > .1f) { state = MovementState.jumping; } else if (rb.velocity.y < -.1f) { state = MovementState.falling; } anim.SetInteger("state", (int)state); } private bool IsGrounded() { return Physics2D.BoxCast(coll.bounds.center, coll.bounds.size, 0f, Vector2.down, .1f, jumpableGround); } public bool canAttack() { return IsGrounded(); } }
我尝试修改的SetDirection方法:
public void SetDirection(float _direction) { lifetime = 0; direction = _direction; gameObject.SetActive(true); hit = false; boxCollider.enabled = true; float localScaleX = transform.localScale.x; if (Mathf.Sign(localScaleX) != _direction) { localScaleX = -localScaleX; speed = -speed; // Flip the sign of the speed if facing opposite direction } transform.localScale = new Vector3(localScaleX, transform.localScale.y, transform.localScale.z); }
问题根源
核心问题出在玩家朝向的检测逻辑:
PlayerMovement中使用sprite.flipX来翻转玩家精灵,但玩家对象的transform.localScale.x始终保持正值,并没有随朝向改变。- 而
PlayerAttack中用Mathf.Sign(transform.localScale.x)获取方向,这个值永远是1(朝右),导致投射物的direction始终被设为正,自然只会朝右飞。
另外,你修改SetDirection时直接修改了speed的值,这会导致投射物被复用(对象池)时,speed被反复翻转,后续发射方向会混乱,这个操作是错误的。
解决方案
方案1:修改PlayerAttack的方向获取逻辑(推荐,不改动原有移动逻辑)
直接从PlayerMovement中读取玩家的移动方向dirX,或者通过sprite.flipX判断朝向:
方法1-1:从PlayerMovement获取dirX
首先在PlayerMovement.cs中添加一个获取方向的方法:
// 在PlayerMovement.cs中添加 public float GetFacingDirection() { return dirX; }
然后修改PlayerAttack的Attack方法:
private void Attack() { anim.SetTrigger("attack"); cooldownTimer = 0; int ghostIndex = FindGhostball(); ghostballs[ghostIndex].transform.position = firePoint.position; // 改为从PlayerMovement获取方向 float facingDir = playerMovement.GetFacingDirection(); // 处理静止时的方向(默认保留上次朝向) if (facingDir == 0) { facingDir = GetComponent<SpriteRenderer>().flipX ? -1f : 1f; } ghostballs[ghostIndex].GetComponent<Projectile>().SetDirection(facingDir); }
方法1-2:通过SpriteRenderer判断朝向
如果不想修改PlayerMovement,可以直接获取玩家的SpriteRenderer状态:
private void Attack() { anim.SetTrigger("attack"); cooldownTimer = 0; int ghostIndex = FindGhostball(); ghostballs[ghostIndex].transform.position = firePoint.position; // 通过flipX判断方向:flipX为true时朝左(-1),false时朝右(1) float facingDir = GetComponent<SpriteRenderer>().flipX ? -1f : 1f; ghostballs[ghostIndex].GetComponent<Projectile>().SetDirection(facingDir); }
方案2:修改PlayerMovement的翻转方式(改为修改localScale)
把PlayerMovement中sprite.flipX的逻辑改成修改transform.localScale.x,这样原有的Mathf.Sign(transform.localScale.x)就能正确获取方向:
private void AnimState() { MovementState state; if (dirX > 0f) { state = MovementState.running; // 替换flipX为修改localScale transform.localScale = new Vector3(Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z); } else if (dirX < 0f) { state = MovementState.running; transform.localScale = new Vector3(-Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z); } else { state = MovementState.idle; } if (rb.velocity.y > .1f) { state = MovementState.jumping; } else if (rb.velocity.y < -.1f) { state = MovementState.falling; } anim.SetInteger("state", (int)state); }
修复Projectile的SetDirection方法
把你修改时添加的speed = -speed去掉,因为原代码中已经通过direction变量控制移动方向,修改speed会导致对象池复用的投射物方向混乱:
public void SetDirection(float _direction) { direction = _direction; gameObject.SetActive(true); hit = false; boxCollider.enabled = true; float localScaleX = transform.localScale.x; if(Mathf.Sign(localScaleX) != _direction) { localScaleX = -localScaleX; } transform.localScale = new Vector3(localScaleX, transform.localScale.y, transform.localScale.z); }
内容的提问来源于stack exchange,提问作者Joey Felling
相关产品推荐
相关产品推荐

