unity2D學習(12)角色發射子彈

1 思路

效果的想法:按下“Fire1”就可以切換爲shoot射擊狀態,角色就會自動連續發射子彈,在shoot模式下再按下“Fire1”就可以切換回上一個狀態。

代碼的思路:用一個Empty GameObject作爲槍口,然後一個PreFab預製體作爲子彈。

2 子彈

新建一個Sprite,並命名爲Bullet(子彈)。

然後爲Bullet添加Rigidbody 2D(剛體)、Box Collider 2D(碰撞器)、Script(腳本)。其中Rigidbody 2D(剛體)注意Gravity Scale(重力)設置爲0,這樣可以直線發射子彈,然後勾選掉Freeze Rotation(自由旋轉)。Box Collider 2D(碰撞器)裏面要勾選is Trigger(扳機),後面代碼要用到OnTriggerEnter2D(和OnCollisionEnter2D的區別在上一個文章裏面提到過)。

子彈代碼:

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

public class BulletController : MonoBehaviour {

    [SerializeField] private float speed = 5f;//子彈的速度
    public Rigidbody2D rig;

    void Start () {
        rig = GetComponent<Rigidbody2D>();//獲取子彈剛體組件
        rig.velocity = transform.right * speed;//移動
        Destroy(gameObject, 2);//2秒後銷燬子彈,不然子彈會無限多
    }	

    private void OnTriggerEnter2D(Collider2D collision)//觸碰到別的碰撞器的時候
    {
        if (collision.gameObject.tag == "Enemy")//如果碰撞對象是敵人
        {
            collision.gameObject.GetComponent<CrabController>().Hurt();//調用敵人的受傷函數,新加入到敵人的裏面函數用來扣敵人血量,方便查看效果,不然太快了
        }
        Destroy(gameObject);//只要碰撞到碰撞體就摧毀子彈本身
    }
}

上面的敵人新加的受傷函數會在下面Instantiate函數介紹給出。

3 Prefab介紹

Prefab(預製件):一種資源類型,用來存儲在項目視圖中反覆使用的對象。

如何生成Prefab(以上面的Bullet子彈爲例子):只需要把GameObject拖拽到Assert裏對應的文件夾,就會在該文件夾下生成對應GameObject 的Prefab。方便後面槍口發射子彈調用創建實例。

如果Prefab拖拽回SampleScene裏面,相當於創建了一個實例。

4 Instantiate函數介紹

Instantiate函數:在unity中進行實例化的函數,返回克隆,可以用於GameObject或Component。

重載(overloaded):

參數介紹:

original 複製的現有對象
position 複製到那個位置
rotation 朝向
parent 分配新父對象
instantiateInWorldSpace 分配父對象時,傳遞爲true可將新對象直接放置在空間中,傳遞爲false以設置對象相對於其新父對象的位置

使用範例:在CrabController代碼裏添加敵人新加的受傷函數,給子彈碰撞到敵人調用

    變量
    public GameObject ExplodePrefab;//爆炸效果的預製體
    private float Hp = 100f;//敵人的hp一開始的爲100f
    函數
    public void Hurt()
    {
        Hp -= 25f;//每次受傷扣除25f
        if (Hp <= 0f)//當血量低於等於0就會被銷燬
        {
            Instantiate(ExplodePrefab, transform.position, transform.rotation);//初始化爆炸效果
            Destroy(gameObject);//銷燬
        }
    } 

爆炸效果的預製體在下面給出。

5 敵人HP小於0爆炸效果

新建一個名爲Explode(爆炸)的Sprite,然後爲它添加Animator(動畫)、Script(腳本)。

e

動畫用素材Assets->Artwork->Sprites->FX->enemy-death的動畫圖片組(動畫添加就不詳細寫了,在unity2D學習(5)爲角色添加動畫裏面有),記得保持單位像素統一爲16。

代碼:

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

public class Explode : MonoBehaviour {
    //就是設置一下銷燬時間
	void Start () {
        Destroy(gameObject, 0.2f);//0.2秒後銷燬爆炸效果,0.2秒也是我設置的爆炸動畫完整一次的時間
    }
}

然後把Explode拖到對應文件夾形成預製體,再把預製體拖拽到CrabConttroller對應的explodePrefabe變量去。

槍口

在Player下面新建一個Empty GameObject並取名爲Gun(槍)。

把Player的Shoot動畫弄出來,然後把Gun的位置調整到角色槍口的地方,因爲在Player地下,所以Gun的位置會跟隨Player移動。

然後槍口不會跟隨角色翻轉而反轉,要修改一下之前Player的控制代碼裏面的Flip函數。把原先的transform.localScale = new Vector2(face, 1)改爲transform.Rotate(0f,180f,0f),這樣槍口也會跟着反轉。

    void Flip()
    {
        face = (face == 1) ? -1 : 1;
        //transform.localScale = new Vector2(face, 1);//原來的反轉代碼
        transform.Rotate(0f,180f,0f);//這樣槍口也會一起轉向
    }

槍口代碼:

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

public class Weapon : MonoBehaviour {
    
    public GameObject bulletPrefab;
    public float fireRate = 0.5F;//0.5秒實例化一個子彈
    private float nextFire = 0.0F;
    public void Shoot()
    {
        if(Time.time > nextFire)//讓子彈發射有間隔
        {
            nextFire = Time.time + fireRate;//Time.time表示從遊戲開發到現在的時間,會隨着遊戲的暫停而停止計算。
            Instantiate(bulletPrefab, transform.position, transform.rotation);
        }
    }
}

7 動畫轉換

添加了新的參數fire(Bool)來判斷是否處於射擊狀態。

爲動畫轉移添加runShoot和Shoot,站立射擊和跑動射擊。

動畫轉移參數:

  • idle->shoot:fire爲true
  • shoot->idle:fire爲false
  • runShoot->shoot:speed小於0.01f
  • shoot->runShoot:speed大於0.01f
  • run->runShoot:fire爲true
  • runShoot->run:fire爲false

8 Player控制的總代碼

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

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D rig;//剛體
    private Animator Anim;//角色的Animator

    [Header("Layers")]
    public LayerMask groundLayer;//用來開啓layer

    [Space]
    [Header("Speed")]
    private float moveSpeed=360f;//移動速度
    private float climbSpeed=100f;//爬行速度

    [Space]
    [Header("Force")]
    private float moveForce=150f;//移動力
    private float jumpForce=575f;//跳躍力

    [Space]
    [Header("Frequency")]
    private int jumpMax=2;//跳躍次數的上線
    private int jumpNum=0;//當前跳躍的次數

    [Space]
    [Header("Booleans")]
    private bool falling = false;//用來標記是否是下落狀態
    private bool onWall;//是否在牆上
    private bool onJumping;//是否正在跳躍中
    private bool onGround;//是否正在地上
    private bool onHurt;//是否受傷
    private bool onShooting;//是否開槍
    [Space]
    [Header("Collision")]
    private float collisionRadius = 0.15f;//碰撞半徑
    private Vector2 bottomOffset, rightOffset, leftOffset;//下左右相對於角色中心的二維向量
    private Collider2D coll;//角色的碰撞器
    [Space]
    [Header("UI")]
    private Text HpNumberText;
    [Space]
    private float face = 1;//角色朝向,初始朝向向右邊
    private float HP=100f;//角色血量
    public GameObject weapon;//調用武器

    //初始化
    void Start()
    {
        rig = GetComponent<Rigidbody2D>();//獲取主角剛體組件
        Anim = GetComponent<Animator>();//獲取主角動畫組件
        coll = GetComponent<Collider2D>();//獲取角色碰撞器
        weapon= GameObject.Find("Gun");//獲取槍
        HpNumberText = GameObject.Find("HpNumber").GetComponent<Text>();//獲取ui對於的text

        groundLayer = 1 << 8;//開啓Ground的layer層,Ground在layer8

        onWall = false;//初始不在牆上
        onJumping = false;//初始不正在跳躍
        onGround = true;//初始在地上
        onShooting = false;//初始不在射擊
        onHurt = false;//初始不受傷

        bottomOffset = new Vector2(0,-1.6f);
        rightOffset = new Vector2(0.61f,-1.3f);
        leftOffset = new Vector2(-0.72f,-1.3f);  
    }
    //固定的時間間隔執行,不受遊戲幀率的影響
    void FixedUpdate()
    {
        Movement();
        changeAnimator();
    }
    //控制移動
    void Movement()
    {
        float moveMultiple = Input.GetAxis("Horizontal");
        float faceDirection = Input.GetAxisRaw("Horizontal");
        float verticalMove = Input.GetAxis("Vertical");
        if (onShooting)//在射擊狀態
        {
            shoot(moveMultiple, faceDirection);
            isShooting();
        }
        else if (onGround)//在地上
        {
            if (isShooting())
            {
                shoot(moveMultiple, faceDirection);
            }
            move(moveMultiple, faceDirection);//左右移動
            if (touchWall())//是否可爬牆,碰到牆
            {
                if (verticalMove>0)//是否有爬牆的行爲,按向上鍵
                {
                    onGround = false;
                    onWall = true;
                    Climb(verticalMove);
                }
            }
            if (Input.GetButtonDown("Jump"))
            {
                onGround = false;
                onJumping = true;
                Jump();
            }
        }
        else if (onJumping)//在空中跳躍
        {
            if (touchWall())//可以爬牆
            {
                onJumping = false;//關閉現在狀態
                onWall = true;//開啓下一個狀態
                Climb(verticalMove);//爬行
                jumpNum = 0;//跳躍次數清零
            }
            else if (Input.GetButtonDown("Jump"))//二段跳
            {
                Jump();
            }
        }
        else if (onWall)//在牆上
        {
            if(Input.GetButtonDown("Jump"))
            {
                onWall = false;
                onJumping = true;
                Flip();
                Jump();
                
            }
            else if (touchWall())//如果碰到牆,就說明還在爬
            {
                Climb(verticalMove);
            }
            else//如果說爬到了末端,就可以有跳躍行爲,不然的話難爬到地面
            {
                onWall = false;
                onJumping = true;
                Jump();
            }
        }
    }
    void Flip()//反轉
    {
        face = (face == 1) ? -1 : 1;//在牆上跳出的話,動畫要和原來相反
        transform.Rotate(0f,180f,0f);//這樣槍口也會一起轉向
    }
    bool isGround()//判斷是否碰地
    {
        return Physics2D.OverlapCircle((Vector2)transform.position + bottomOffset, collisionRadius, groundLayer);//判斷是否碰到地面
    }
    bool isShooting()//判斷是否射擊
    {
        if (Input.GetButtonDown("Fire1"))
        {
            onShooting=(onShooting == true)? false : true ;
            Debug.Log(onShooting);
        }
        return onShooting;
    }
    void shoot(float moveMultiple, float faceDirection)
    {
        weapon.GetComponent<Weapon>().Shoot();
        move(moveMultiple, faceDirection);//左右移動
    }
    bool touchWall()//判斷是否碰到牆
    {
        return Physics2D.OverlapCircle((Vector2)transform.position + rightOffset, collisionRadius, groundLayer)
            || Physics2D.OverlapCircle((Vector2)transform.position + leftOffset, collisionRadius, groundLayer); 
    }
    void move(float moveMultiple,float faceDirection)//移動代碼
    {
        //角色左右移動
        if (moveMultiple != 0)
        {
            //velocity表示速度,Vector表示向量
            rig.velocity = new Vector2(moveMultiple * moveSpeed * Time.deltaTime, rig.velocity.y);//輸入x,y向量,數值*方向
        }
        //角色朝向修改
        if (faceDirection != 0&&face!=faceDirection)
        {
            Flip();
        }
    }
    void Jump()//跳躍代碼
    {
        if(jumpNum < jumpMax)
        {
            rig.velocity = new Vector2(face*moveForce*Time.deltaTime, jumpForce * Time.deltaTime);
            jumpNum++;
        }   
    }
    void Hurt(Collision2D collision)//受傷代碼
    {
        onHurt = true;
        AccordingDirectionFlip(collision);
        rig.velocity = new Vector2(face * moveForce * Time.deltaTime, jumpForce * Time.deltaTime);
    }
    void AccordingDirectionFlip(Collision2D collision)//根據敵人方向,安排玩家轉向
    {
        if (collision != null)//如果玩家出現視野中
        {
            int direction;
            if (collision.transform.position.x < transform.position.x)
            {
                direction = -1;//玩家在敵人的左邊
            }
            else
            {
                direction = 1;//玩家在敵人的右邊
            }
            if (direction != face)//表示方向不一致
            {
                //Debug.Log(direction);
                Flip();
            }
        }
    }
    void Climb(float verticalMove)//爬牆代碼
    {
        rig.velocity = new Vector2(rig.velocity.x,climbSpeed * verticalMove* Time.deltaTime);//輸入x,y向量,數值*方向
    }
    void changeAnimator()//動畫切換
    {
        if (onGround)//如果在地上
        {
            Anim.SetFloat("speed", Mathf.Abs(rig.velocity.x));//速度是向量
            if (onHurt)
            {
                Anim.SetBool("injured", true);
                onGround = false;
            }
            if (onShooting)
            {
                Anim.SetBool("fire", true);
            }
            else
            {
                Anim.SetBool("fire", false);
            }
        }
        if (onWall)//如果在牆上
        {
            Anim.SetBool("wall", true);
            if (Anim.GetBool("ground") == false)
            {
                Anim.SetBool("ground", true);
            }
        }
        else
        {
            Anim.SetBool("wall", false);
        }
        if (onJumping)//如果在跳躍狀態
        {
            if (onHurt)
            {
                Anim.SetBool("injured", true);
                jumpNum = 0;//當前跳躍次數清零
                falling = false;
                onJumping = false;
            }
            else if (Anim.GetBool("ground"))
            {
                falling = false;
                Anim.SetBool("ground", false);
            }
            else 
            {
                if (falling&&isGround())
                {
                    Anim.SetBool("ground", true);
                    jumpNum = 0;//落地的話,當前跳躍次數清零
                    falling = false;
                    onJumping = false;
                    onGround = true;
                }
                else if(rig.velocity.y < 0)
                {
                    falling = true;
                }
            }

        }
        if (onHurt)//如果在受傷狀態
        {
            if (falling && isGround())
            {
                Anim.SetBool("injured", false);
                Anim.SetBool("ground", true);
                falling = false;
                onGround = true;
                onHurt = false;
            }
            else if (rig.velocity.y < 0)
            {
                falling = true;
            }
        }
    }
    void OnDrawGizmos()//繪製輔助線
    {
        Gizmos.color = Color.red;//輔助線顏色
        //繪製圓形輔助線
        Gizmos.DrawWireSphere((Vector2)transform.position + bottomOffset, collisionRadius);
        Gizmos.DrawWireSphere((Vector2)transform.position + rightOffset, collisionRadius);
        Gizmos.DrawWireSphere((Vector2)transform.position + leftOffset, collisionRadius);
    }
    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Enemy")
        {
            HP -= 25f;//碰到一次敵人減去25血量
            HpNumberText.text = HP.ToString();//顯示HP
            Hurt(collision);
        }
    }
}

9 遊戲效果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章