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):
- public static Object Instantiate(Object original);
- public static Object Instantiate(Object original, Transform parent);
- public static Object Instantiate(Object original, Transform parent, bool instantiateInWorldSpace);
- public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);
- public static Object Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);
參數介紹:
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變量去。
6 槍口
在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 遊戲效果