You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 14:17:29