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 游戏效果