愤怒的小鸟游戏开发教程(持续更新)

C++最经典愤怒的小鸟游戏代码,(高仿),完美复原原游戏,好玩到停不下来 阅读详情

@elevateSober

1.项目实施资源

1.1学习视频推荐(包含项目所需资源)

http://www.sikiedu.com/course/134

2.项目实施过程

2.1工程的建立

  1. 新建一个2D的工程,命名为:angryBirds

说明:一般2D的工程和3D的工程相比,在主摄像机上有一个区别在于:3D的摄像机属性面板中Projection选项一般为perspective(透视图),2D的摄像机属性面板中Projection选项一般为orthographic。

2.2资源导入,场景简单搭建

  1. 在下载好的资源中选中Source文件夹下的Image和Music两个文件夹,将这两个文件夹复制粘贴到angryBirds工程目录下的assets文件夹下。

说明:对于一张图片中包含多个元素,且需要截出其中一个元素,可以使用uinty中的一个功能实现:选择这张包含多个元素的图片,选择属性面板中的sprite mode中的multiple(多样的),然后点击sprite editor,在弹出的面板中选择左上角的slice,在选中的面板中选择slice,这样就能将图片中的各个元素切开了,退出sprite editor面板,在工程面板中选择刚才那张包含多个元素的图片,会发现下面多了很多个图片,这些就是切下来的图片。

  1. 在uinty工程面板中新建一个文件夹命名为:Scene(用来存放场景),新建三个场景,分别命名为:00-loading,01-level,02-game。
  2. 在场景02-game中:选择image文件夹下的BIRDS_1图片并对其元素进行剪切,然后将BIRDS1_0,BIRDS1_8,BIRDS1_159三张图片拖动到场景中并重命名为:birds,right,left。
  3. 修改这三张图片的层级关系:在图片所在属性面板中选择sorting layer中的add sorting layer,点击加号新建一个层级命名为player,然后将这三张图片的层级都选择为player,将这三张图片面板属性中的order in layer属性的值分别设置为1,0,2。

说明:uinty中物体默认的层级为default,如果新建新的层级,则在显示上新的层级的物体会覆盖默认的层级;同时在一个层级中,order in layer的数值高的会覆盖数值低的,在本次层级设置中,左边的物体会覆盖小鸟,小鸟会覆盖右边的物体,从而来达到实际的效果。

2.3spring joint 2D组件的加入

  1. 在bird和right之间设置一个spring joint 2D组件:首先选中bird,添加组件spring joint 2D,然后选中right添加组件rigibody 2D,并将body type属性设置为static在bird的spring joint 2D组件属性中,将auto configure distance不勾选,将distance属性设置为0.4,将frequency属性设置为2,在connected rigid body属性中将right拖入。

说明:spring joint 2D组件(弹簧)是一个弹簧组件,弹簧的一段固定在一个物体上(该物体不会弹起),另一端固定一个物体(该物体会围绕另一个物体弹起),这里要求两个物体都为刚体;在spring joint 2D的组件属性中,auto configure distance属性表示弹簧弹起的距离是否自动计算,这里的距离是另一个物体到该弹起物体的极限距离,表示弹起的物体不会弹到这个极限距离之内;distance是极限距离,frequency表示弹动的频率,connected rigid body属性是表示弹簧被固定那端的物体;在刚体属性中,body type属性设置为static表示刚体物体没有重力不会落下。

2.4小鸟的拖拽

  1. 给小鸟添加一个圆型碰撞器,在小鸟的属性面板中输入点击add component,选择circle collider 2D,通过调节radius属性的值来调节圆形的大小。
  2. 给小鸟添加一个C#脚本,命名为bird,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        isClick = false;
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一)
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
        }
    }
}

说明:圆形碰撞器的大小用来表示鼠标拖动物体有效的范围,在范围内鼠标都能实现拖动小鸟的功能。

2.5限定小鸟的最大拖拽距离

  1. 首先给right创建一个子物体rightPos,用来计算小鸟和rightPos的距离(这里如果用right来计算距离,会发现right的中心点不在spring joint 2D弹簧端点上,这样计算会有很大误差,新建一个子物体,并将其中心点移动到弹簧的端点上,这样计算就更加准确)
  2. 在脚本bird中新增如下代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos   
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        isClick = false;
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
        }
    }
}

2.6小鸟的飞出

Dynamic、Static与IsKinematic的区别与辨析
1.有Collider和RigidBody的GameObject, Unity视之为Dynamic。
2.只含有Collider的GameObject, Unity视之为Static,适用于场景上的固定物体。
3.对于那些不会因为其他物体的影响而发生物理上的改变但是又不固定的对象,可以给它加一个Collider和RigidBody组件,并且把IsKinematic勾选上,叫做运动学刚体。IsKinematic的物体虽然不受其他物体影响,但是仍然可以与其它物体发生物理互动。
4.在本次代码书写中,isKinematic属性是确定刚体是否接受动力学模拟,不仅包括重力感应,还包括速度、阻力、质量等的物理模拟,弹簧在拉伸过程中,如果不开启,小鸟会一直受到弹簧弹力导致飞出去速度很大。

在脚本brid.cs中添加如下代码:

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

public class bird : MonoBehaviour {
   private bool isClick = false;
   public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos   
   public float maxDis = 3;//设置小鸟拖拽的最大距离
   public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
   private Rigidbody2D rg;//定义一个组件
   //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
   private void Awake()
   {
       sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
       rg = GetComponent<Rigidbody2D>();//组件赋值
   }
   private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
   {
       isClick = true;
       //
       rg.isKinematic = true;//表示开启运动学
   }
   private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
   {
       //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
       isClick = false;
       rg.isKinematic = false;//表示关闭运动学
       Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
   }
   private void Update()
   {
       if (isClick)//当鼠标一直按下时
       {
           //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
           //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
           transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
           //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
           //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
           transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
           if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
           {
               Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
               pos *= maxDis;//得到最大长度向量
               transform.position = pos + rightPos.position;//小鸟的位置限制
           }
       }
   }
   public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
   {
       sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
   }
}

2.7猪的受伤

  1. 添加地面场景:在Image文件夹下裁剪图片THEME_01,裁剪效果为:
    在这里插入图片描述

  2. 到场景中,同时给图片添加一个碰撞器,然后复制多个,配置成如下地面效果图:
    在这里插入图片描述

  3. 在Image文件夹下选择图片BIRDS_1_103,重命名为pig01拖动到场景中,为该物体添加刚体,添加碰撞器,添加脚本pig,代码如下:

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

public class pig : MonoBehaviour {
    public float maxSpeed = 10;//设置相对速度的上线
    public float minSpeed = 5;//设置相对速度的下线
    private SpriteRenderer render;//定义一个组件
    public Sprite hurt;//定义一个精灵
    private void Awake()
    {
        render = GetComponent<SpriteRenderer>();//初始化组件
    }
    public void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.relativeVelocity.magnitude > maxSpeed)//判断相对速度的大小,由于速度是个矢量,这里只要大小
        {
            Destroy(gameObject);//直接毁掉物体
        }
        else if(collision.relativeVelocity.magnitude > minSpeed && collision.relativeVelocity.magnitude < maxSpeed)
        {
            render.sprite = hurt;//改变组件中的sprite的值为hurt(这里表示更换图片)
        }
    }
}
  1. 在pig脚本所在的属性面板中将hurt的图片改为BIRDS_1_104

2.8弹弓划线操作

  1. 对right添加组件LineRenderer,将LineRenderer材质修改为Sprites-Default,颜色修改为树梢的颜色(采用提取颜色的方法),然后拖动箭头修改渐变色效果,如图:
    在这里插入图片描述

  2. 点击LineRenderer属性面板后面的设置图标,选择copy component,然后在left物体的属性面板中Sprite Renderer属性右边的设置图标中选择paste component as new。

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

public class bird : MonoBehaviour {
    private bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    private Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
        //
        rg.isKinematic = true;//表示开启运动学
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
        isClick = false;
        rg.isKinematic = false;//表示关闭运动学
        Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();
        }
    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
}

说明:当画出的线和树梢之间有一定间隔的时候,可能时rightPos物体和leftPose物体出现了问题,如果在场景面板中点击这两个物体,在窗口中不显示可移动的坐标图标,则需要重新创建这两个物体。

2.9死亡,加分特效制作

  1. 设置死亡烟雾动画特效的制定:动画可以理解为多个图片连续播放形成的结果。

在BIRDS_1中裁剪出需要连续播放的图片(当图片太零散是可以手动选择多个零散图片组合成一个所需的图片),按住ctrl键选择多个图片后拖动到场景内,会显示一个窗口(在该窗口中在Assets文件夹中创建名为animation,用来存放动画,将所选择的多个图片保存在该文件夹中重命名为boom(当同时选择多个图片拖拽到场景中时,uinty会自动识别为动画)),回到场景中将动画的名称修改为boom。

说明:如何修改动画中图片的播放顺序?
选择场景中动画的物体,选择ctrl+6就可以打开播放设置,在这个界面可以进行图片的顺序调换,图片的删除等操作,同时还可以改变图片之间的间隔时间。这里由于只需要这个动画在实际操作中播放一次,而动画系统默认时循环播放,点击工程窗口中刚刚新建的animation,选择其中的boom并将loop time选项不勾选即可,在物体boom中添加代码用来实现毁灭物体的操作,同时在动画的设置界面中在动画的最后一帧中添加该事件。
在这里插入图片描述
在这里插入图片描述
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class boom : MonoBehaviour {
public void destroyBoom()
{
Destroy(gameObject);
}
}

  1. 在脚本pig.cs中添加代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class pig : MonoBehaviour
{
    public float maxSpeed = 10;//设置相对速度的上线
    public float minSpeed = 5;//设置相对速度的下线
    private SpriteRenderer render;//定义一个组件
    public Sprite hurt;//定义一个精灵
    public GameObject boom;//定义一个物体,用来表示动画
    public GameObject pigScore;//定义一个物体,用来表示分数显示
    private void Awake()
    {
        render = GetComponent<SpriteRenderer>();//初始化组件
    }
    public void OnCollisionEnter2D(Collision2D collision)//碰撞器方法使用
    {
        if (collision.relativeVelocity.magnitude > maxSpeed)//判断相对速度的大小,由于速度是个矢量,这里只要大小
        {
            destroyPig();
        }
        else if (collision.relativeVelocity.magnitude > minSpeed && collision.relativeVelocity.magnitude < maxSpeed)
        {
            render.sprite = hurt;//改变组件中的sprite的值为hurt(这里表示更换图片)
        }
    }
    public void destroyPig()//定义一个函数用来实现猪死亡后的操作
    {
        Destroy(gameObject);//直接毁掉猪
        Instantiate(boom, transform.position, Quaternion.identity);//instantiate函数可以用来实例化物体(可以理解为显示物体),后面两个参数分别表示物体显示的位置和是否旋转,Quaternion.identity表示不旋转
        GameObject go = Instantiate(pigScore, transform.position + new Vector3(0, 0.6f, 0), Quaternion.identity);//这里表示将分数的显示位置的y轴向上偏移了
        Destroy(go, 1.0f);//在1秒后毁坏物体,前面动画播放完毕后动画消失的功能除了在结束时间的帧上添加一个毁坏物体的事件外,也可以采用这种方法
                          //这里存在一个问题:就是这里毁坏物体不能够直接输入物体的名称,而是必须将其赋值给一个物体,这是因为在本案例中,该物体不会存在与场景中,而是需要通过代码进行调用,这里必须将这种既不能存在于场景中又要被调用的物体添加到工程目录下的prefabs文件夹中(该文件夹用来存放物体的模板,以保证物体在场景中被删除时可以被正常调用),对于不存在与场景中的物体如果直接在代码进行毁灭会导致模板被毁掉的风险,导致下次无法调用,所以需要将其赋值给一个物体,通过该物体进行显示,对该物体进行毁灭;如果该物体存在于场景中就可以直接删除
    }

}
  1. 显示分数:通过裁剪相关的分数,采取同上面相同的方法进行实现,代码如上。上面时显示动画,这里为显示图片,同理。

2.10游戏逻辑的判定,实现多只小鸟的飞出

  1. 在场景视图中添加三只小鸟,两只猪。
  2. 在场景视图中添加一个空物体,命名为gameManager,同时为该物体添加一个脚本名为gameManager,写入如下内容(用来判断游戏输赢的依据):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;
    private void Awake()
    {
        _instance = this;
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count > 0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
            }
        }
        else
        {
            //赢了
        }
    }
}
  1. 在gameManager物体的属性界面中将相关的bird和pig拖入到集合到(注意顺序必须不能错)
    在这里插入图片描述
  2. 在脚本bird.cs中添加代码用来实现一个小鸟飞出后过几秒小鸟消失的特效(之前设计了猪被撞击后相对速度大于某一个值时小猪消失),同时实现一个小鸟飞出后另一个小鸟准备的功能。脚本的具体内容如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    [HideInInspector]//在属性面板中隐藏组件(如果组件的形式为Private则在属性面板中不显示,如果组件的形式为public则在属性面板中显示)
    public Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    public GameObject boom;
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
        //
        rg.isKinematic = true;//表示开启运动学
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
        isClick = false;
        rg.isKinematic = false;//表示关闭运动学
        Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();
        }
    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
        Invoke("Next", 5);//实现5秒后实现Next函数
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
    /// <summary>
    /// 下一只小鸟的飞出
    /// </summary>
    void Next()
    {
        gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)
        Destroy(gameObject);
        Instantiate(boom, transform.position, Quaternion.identity);//显示特效
        gameManager._instance.NextBird(); 
    }
}
  1. 在脚本pig.cs中实现猪死亡后在gameManager脚本中去除相关的集合物体,实现代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class pig : MonoBehaviour
{
    public float maxSpeed = 10;//设置相对速度的上线
    public float minSpeed = 5;//设置相对速度的下线
    private SpriteRenderer render;//定义一个组件
    public Sprite hurt;//定义一个精灵
    public GameObject boom;//定义一个物体,用来表示动画
    public GameObject pigScore;//定义一个物体,用来表示分数显示
    public bool isPig = false;//判断是不是猪,用来指定消失的物体是猪,可以在属性面板中指定是不是猪
    private void Awake()
    {
        render = GetComponent<SpriteRenderer>();//初始化组件
    }
    public void OnCollisionEnter2D(Collision2D collision)//碰撞器方法使用
    {
        if (collision.relativeVelocity.magnitude > maxSpeed)//判断相对速度的大小,由于速度是个矢量,这里只要大小
        {
            destroyPig();
        }
        else if (collision.relativeVelocity.magnitude > minSpeed && collision.relativeVelocity.magnitude < maxSpeed)
        {
            render.sprite = hurt;//改变组件中的sprite的值为hurt(这里表示更换图片)
        }
    }
    public void destroyPig()//定义一个函数用来实现猪死亡后的操作
    {
        if (isPig)
        {
            gameManager._instance.pigs.Remove(this);//移除集合中的猪,在执行是可以在属性面板中看到集合中相关物体的消失
        }
        Destroy(gameObject);//直接毁掉猪
        Instantiate(boom, transform.position, Quaternion.identity);//instantiate函数可以用来实例化物体(可以理解为显示物体),后面两个参数分别表示物体显示的位置和是否旋转,Quaternion.identity表示不旋转
        GameObject go = Instantiate(pigScore, transform.position + new Vector3(0, 0.6f, 0), Quaternion.identity);//这里表示将分数的显示位置的y轴向上偏移了
        Destroy(go, 1.0f);//在1秒后毁坏物体,前面动画播放完毕后动画消失的功能除了在结束时间的帧上添加一个毁坏物体的事件外,也可以采用这种方法
                          //这里存在一个问题:就是这里毁坏物体不能够直接输入物体的名称,而是必须将其赋值给一个物体,这是因为在本案例中,该物体不会存在与场景中,而是需要通过代码进行调用,这里必须将这种既不能存在于场景中又要被调用的物体添加到工程目录下的prefabs文件夹中(该文件夹用来存放物体的模板,以保证物体在场景中被删除时可以被正常调用),对于不存在与场景中的物体如果直接在代码进行毁灭会导致模板被毁掉的风险,导致下次无法调用,所以需要将其赋值给一个物体,通过该物体进行显示,对该物体进行毁灭;如果该物体存在于场景中就可以直接删除
    }

}

2.11解决重复划线,小鸟轮换速度突然变大的问题

  1. 解决重复划线的问题可以直接在鼠标抬起时禁用掉right和left两个物体的组件,就可以实现划线消失,修改bird.cs的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    [HideInInspector]//在属性面板中隐藏组件(如果组件的形式为Private则在属性面板中不显示,如果组件的形式为public则在属性面板中显示)
    public Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    public GameObject boom;
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
        //
        rg.isKinematic = true;//表示开启运动学
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        right.enabled = false;
        left.enabled = false;//鼠标抬起时禁用划线的功能
        //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
        isClick = false;
        rg.isKinematic = false;//表示关闭运动学
        Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时,使用相关的运动学进行有关的使用及输出
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();
        }
    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
        Invoke("Next", 5);//实现5秒后实现Next函数
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.enabled = true;
        left.enabled = true;//开启组件实现划线功能
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
    /// <summary>
    /// 下一只小鸟的飞出
    /// </summary>
    void Next()
    {
        gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)
        Destroy(gameObject);
        Instantiate(boom, transform.position, Quaternion.identity);//显示特效
        gameManager._instance.NextBird(); 
    }
}
  1. 解决小鸟的轮换速度突然变大的问题就是将下一个小鸟的位置固定为第一只小鸟的的初始位置,修改gameManager.cs代码的内容如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;//用来为其他脚本访问该脚本提供接口
    private Vector3 originPos;//初始化的位置
    private void Awake()
    {
        _instance = this;
        if(birds.Count > 0) {
            originPos = birds[0].transform.position;
        }
        
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)//由于每一只小鸟释放后在集合中都被移除了故下一只小鸟的下标依然为0
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].transform.position = originPos;
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count > 0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
            }
        }
        else
        {
            //赢了
        }
    }
}

2.12添加小鸟飞出的拖尾效果

  1. 向工程中导入资源Pocket RPG Weapon Trails.unitypackage(在之前下载过的资源包中)

  2. 该资源包实现小鸟飞出的拖尾效果的详细教程:

https://blog.youkuaiyun.com/akof1314/article/details/37603559

  1. 为物体bird创建一个脚本命名为TestMyTrail,具体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMyTrail : MonoBehaviour
{

    public WeaponTrail myTrail;

    private float t = 0.033f;
    private float tempT = 0;
    private float animationIncrement = 0.003f;


    void LateUpdate()
    {
        t = Mathf.Clamp(Time.deltaTime, 0, 0.066f);

        if (t > 0)
        {
            while (tempT < t)
            {
                tempT += animationIncrement;

                if (myTrail.time > 0)
                {
                    myTrail.Itterate(Time.time - t + tempT);
                }
                else
                {
                    myTrail.ClearTrail();
                }
            }

            tempT -= t;

            if (myTrail.time > 0)
            {
                myTrail.UpdateTrail(Time.time, t);
            }
        }
    }
    void Start()
    {
        // 默认没有拖尾效果
        myTrail.SetTime(0.0f, 0.0f, 1.0f);
    }

    public void trailStart()
    {
        //设置拖尾时长
        myTrail.SetTime(2.0f, 0.0f, 1.0f);
        //开始进行拖尾
        myTrail.StartTrail(0.5f, 0.4f);
    }

    public void trailClear()
    {
        //清除拖尾
        myTrail.ClearTrail();
    }

}
  1. 为bird添加一个子物体命名为trail,将其位置设置在小鸟的尾巴上,为其添加一个组件名为Mesh Renderer,将材质设置为WeaponTrail,同时将属性设置为理想的效果,例如:
    在这里插入图片描述
  2. 在bird.cs中输入代码,实现小鸟的拖尾效果的实现及相关修改,包括拖尾时间的修改,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    [HideInInspector]//在属性面板中隐藏组件(如果组件的形式为Private则在属性面板中不显示,如果组件的形式为public则在属性面板中显示)
    public Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    public GameObject boom;
    private TestMyTrail myTrail;
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
        myTrail = GetComponent<TestMyTrail>();//获取拖尾效果脚本
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        isClick = true;
        //
        rg.isKinematic = true;//表示开启运动学
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        right.enabled = false;
        left.enabled = false;//鼠标抬起时禁用划线的功能
        //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
        isClick = false;
        rg.isKinematic = false;//表示关闭运动学
        Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时,使用相关的运动学进行有关的使用及输出
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();
        }
    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        myTrail.trailStart();
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
        Invoke("Next", 5);//实现5秒后实现Next函数
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.enabled = true;
        left.enabled = true;//开启组件实现划线功能
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
    /// <summary>
    /// 下一只小鸟的飞出
    /// </summary>
    void Next()
    {
        gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)
        Destroy(gameObject);
        Instantiate(boom, transform.position, Quaternion.identity);//显示特效
        gameManager._instance.NextBird(); 
    }
    /// <summary>
    /// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果
    /// </summary>
    /// <param name="collision"></param>
    private void OnCollisionEnter2D(Collision2D collision)
    {
        myTrail.trailClear();
    }
}

6.删除掉其余几个未加入组件的小鸟,将这只实现了拖尾效果的小鸟赋值几份,并将其按序拖动到集合birds中

2.13整合场景解决无法显示划线弹弓问题

根据前面所学的知识,对场景进行增加(注意将物体的图层标签都设置为player,同时这些物体都要添加刚体组件,碰撞器,还有pig.cs脚本),效果如图所示,同时如果出现划线弹弓不显示可以改变图层的显示数字来显示划线弹弓。
在这里插入图片描述

2.14添加失败,胜利的游戏界面ui

  1. 创建一个ui图片命名为lose(表示失败后会显示的图片),对图片进行有关操作:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 在lose下在创建一个图片,对该图片进行上下进行填充,按照第一点的相关操作对其进行颜色和透明度修改,同时在视图中根据自己的需求拖动页面的大小
    在这里插入图片描述
  3. 在图片下新建三个图片,用来构造失败后出现图片的布局(可以根据自己的喜好设置布局),以下为一个案例:
    在这里插入图片描述
  4. 对lose按下ctrl+6后点击create添加动画片段,命名为lose,然后对该部分实现一个局部变暗的效果
    在这里插入图片描述
    在这里插入图片描述
  5. 对lose进行复制并重命名为win
    在这里插入图片描述
  6. 对于lose和win两个ui来说,一般在界面上是不显示的,所需不勾选组件
  7. 在工程面板中对lose进行复制,并将赋值后的对象改名为win,并进行如下操作
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

说明:对于win而言,进行与lose同等的操作,保证其值为win而不是lose

  1. 对于win而言,添加一个脚本名为win,用于在胜利播放动画完毕后显示星星操作,内容如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class win : MonoBehaviour {
    /// <summary>
    /// 动画播放完毕后显示星星的操作
    /// </summary>
	public void show()
    {
        gameManager._instance.showStars();
    }
}
  1. 在win动画的最后一帧添加一个函数show(或者在gameManager.cs中添加一个延时执行函数,前面提到过,可参考前面的内容)
  2. 在gameManager.cs中添加代码,来实现lose和win组件的开启,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;//用来为其他脚本访问该脚本提供接口  
    private Vector3 originPos;//初始化的位置
    public GameObject win;
    public GameObject lose;
    private void Awake()
    {
        _instance = this;
        if(birds.Count > 0) {
            originPos = birds[0].transform.position;
        }
        
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)//由于每一只小鸟释放后在集合中都被移除了故下一只小鸟的下标依然为0
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].transform.position = originPos;//将第一支小鸟的位置赋值给后面每一只小鸟的位置
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count > 0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
                lose.SetActive(true);
            }
        }
        else
        {
            //赢了
            win.SetActive(true);
        }
    }
    public void showStars()
    {

    }
}
  1. 在win胜利的界面中镶嵌需要的星星,按照之前的方法设计为以下界面即可:,同时将三个星星重命名为left,middle,right然后将其默认设置为关闭状态(将属性面板不勾选)
    在这里插入图片描述

2.15修改火花粒子系统

  1. 向工程中导入新的资源,资源的名字为yanhua.unitypackage,在场景面板中新建一个空物体命名为root,将导入的粒子系统中root的sub,sub2两项拖动到root中,对其进行下列修改使其效果达到最好:
    在这里插入图片描述
    在这里插入图片描述

说明:对于粒子特效root而言,将其设置为一个模板放入prefabs中,同时将sub和sub2设置为不循环播放,最后在场景视图中删除root

2.16渲染层级关系并将粒子系统显示在UI之前

2.16.1uinty渲染的先后顺序

1.Camera(对于多个Camera而言,渲染是根据depth由低到高的顺序进行的,因此会出现depth高的会覆盖depth低的情况)
2.同一个camera(根据sorting layer进行渲染,一般情况而言是sorting layer级别高的遮盖级别低的,一般新建的sorting layer层的级别都要高于default层)
3.同一个sorting layer(根据order in layre的数值来确定覆盖关系,一般而言都是数值大的覆盖数值小的)

2.16.2如何将粒子系统显示在UI层面上

说明:通常情况下而言,UI的层级关系是最大的,UI会覆盖在所有的物体之上,一般会新建一个新的照相机用来拍摄UI

  1. 新建一个照相机重命名为UICamera,进行如下修改
    在这里插入图片描述
  2. 对Canvas进行如下设置
    在这里插入图片描述
  3. 对root的layer设置为UI,并对sub和sub2分别进行如下设置
    在这里插入图片描述
  4. 并将root复制三份分别放置到win下的left,middle,right中,并调整适当的位置使其效果最佳。同时移除UICamera照相机的audio listener组件删除

说明:将sub和sub2设置为player是为了保证粒子特效能够显示在win界面之上而不会被win的界面所遮挡(同一个摄像机下通过sorting layer来判断渲染顺序)

2.17让星星一颗一颗的显示

  1. 通过协程来实现星星一颗一颗的显示,在脚本gameManager.cs中加入如下代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;//用来为其他脚本访问该脚本提供接口  
    private Vector3 originPos;//初始化的位置
    public GameObject win;
    public GameObject lose;
    public GameObject[] stars;//设定一个数组用来存放星星的数量(将组件中的星星进行赋值)
    private void Awake()
    {
        _instance = this;
        if(birds.Count > 0) {
            originPos = birds[0].transform.position;
        }
        
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)//由于每一只小鸟释放后在集合中都被移除了故下一只小鸟的下标依然为0
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].transform.position = originPos;//将第一支小鸟的位置赋值给后面每一只小鸟的位置
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count > 0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
                lose.SetActive(true);
            }
        }
        else
        {
            //赢了
            win.SetActive(true);
        }
    }
    /// <summary>
    /// 通过游戏中小鸟的数量来设置星星的颗数
    /// </summary>
    public void showStars()
    {
        StartCoroutine("show");//启用设定的协程函数show()
    }
    /// <summary>
    /// 协程的书写来控制星星一颗一颗的显示
    /// </summary>
    /// <returns></returns>
    IEnumerator show()//设置一个协程函数show用来实现等待功能
    {
        for (int i = 0; i < birds.Count + 1; i++)//零个小鸟显示一颗星星,加一的原因在于如果当前的游戏场景中小鸟的数量就是零会出现一个bug,导致无法显示一颗星星
        {
            
            stars[i].SetActive(true);
            yield return new WaitForSeconds(0.4f);//表示等待0.4秒

        }
    }
}
  1. 在属性界面中将星星按顺序依次拖入到stars数组中

2.18解决小鸟飞出后点击小鸟会实现划线效果的bug

  1. 修改bird.cs的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    private bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    [HideInInspector]//在属性面板中隐藏组件(如果组件的形式为Private则在属性面板中不显示,如果组件的形式为public则在属性面板中显示)
    public Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    public GameObject boom;
    private TestMyTrail myTrail;
    private bool canMove = true;//用来判断小鸟能否移动(解决在小鸟飞出到消失的时间内点击小鸟仍出现划线的效果的bug)
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
        myTrail = GetComponent<TestMyTrail>();//获取拖尾效果脚本
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        if (canMove) {
            isClick = true;
            rg.isKinematic = true;//表示开启运动学
        }
        
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        if (canMove)
        {
            right.enabled = false;
            left.enabled = false;//鼠标抬起时禁用划线的功能
                                 //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
            isClick = false;
            rg.isKinematic = false;//表示关闭运动学
            Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
                                //禁用划线操作
            canMove = false;//当鼠标抬起时到下一个小鸟飞起时禁用小鸟的划线功能和小鸟位置随鼠标移动的功能
        }
        
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时,使用相关的运动学进行有关的使用及输出
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();//这里存在一个bug,由于每一只小鸟飞出5秒后才被毁灭,由于小鸟在飞出的那一刻就禁用了sp弹簧组件故5秒内弹簧功能就失效了,但是如果5秒内点击小鸟该部分代码仍然可以被执行且划线功能仍存在,不能达到理想的效果(解决方案:)
        }
    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        myTrail.trailStart();
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
        Invoke("Next", 5);//实现5秒后实现Next函数
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.enabled = true;
        left.enabled = true;//开启组件实现划线功能
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
    /// <summary>
    /// 下一只小鸟的飞出
    /// </summary>
    void Next()
    {
        gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)
        Destroy(gameObject);
        Instantiate(boom, transform.position, Quaternion.identity);//显示特效
        gameManager._instance.NextBird(); 
    }
    /// <summary>
    /// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果
    /// </summary>
    /// <param name="collision"></param>
    private void OnCollisionEnter2D(Collision2D collision)
    {
        myTrail.trailClear();
    }
}

2.19添加暂停动画

  1. 在Canvas下新建一个image命名为pause,将暂停的图片拖入其中,并将其位置设置在左上角
  2. 在Canvas下新建一个空物体命名为pausePanel,将其位置设置为整个界面,在pausePanel下新建一个UI图片命名为bg,将其位置设置为整个界面,将其颜色设置为黑色,设置透明度为适当
  3. 在bg下新建一个图片image,将其颜色设置为黑色透明度设置我适当(比bg要不透明)将其位置设置为上下拉伸并拖动之左侧,在image下新建三个图片分别命名为resume,home,retry,分别插入开始,返回,重试三张图片,并调整适当位置,例如:
    在这里插入图片描述
  4. 点击pausePanel,按下ctrl+6进行动画编辑,添加两个动画分别命名为resume和pause
    在这里插入图片描述
  5. 对resume和pause分别进行开始动画操作和暂停动画操作,其中开始动画操作包括场景变亮(在bg操作)和按钮界面移出两个动画(在image操作),而暂停动画操作包括场景变暗(在bg操作)和按钮界面移入两个动画(在image操作),这里暂停和开始两个动画的功能效果时完全相反的,下面介绍开始动画效果(暂停动画效果类似)

1.界面变亮效果实现
在这里插入图片描述
在这里插入图片描述
2.按钮界面退出的动画
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.20添加鼠标注册事件

2.20.1输赢界面的鼠标点击事件(win和lose)
  1. 对于win和lose中的需要添加点击事件的图标添加一个按钮组件(button),
  2. 在脚本gameManager中添加如下代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; 

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;//用来为其他脚本访问该脚本提供接口  
    private Vector3 originPos;//初始化的位置
    public GameObject win;
    public GameObject lose;
    public GameObject[] stars;//设定一个数组用来存放星星的数量(将组件中的星星进行赋值)
    private void Awake()
    {
        _instance = this;
        if(birds.Count > 0) {
            originPos = birds[0].transform.position;
        }
        
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)//由于每一只小鸟释放后在集合中都被移除了故下一只小鸟的下标依然为0
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].transform.position = originPos;//将第一支小鸟的位置赋值给后面每一只小鸟的位置
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count > 0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
                lose.SetActive(true);
            }
        }
        else
        {
            //赢了
            win.SetActive(true);
        }
    }
    /// <summary>
    /// 通过游戏中小鸟的数量来设置星星的颗数
    /// </summary>
    public void showStars()
    {
        StartCoroutine("show");//启用设定的协程函数show()
    }
    /// <summary>
    /// 协程的书写来控制星星一颗一颗的显示
    /// </summary>
    /// <returns></returns>
    IEnumerator show()//设置一个协程函数show用来实现等待功能(用于实现星星一颗一颗出现且间隔有一个时间差)
    {
        for (int i = 0; i < birds.Count + 1; i++)//零个小鸟显示一颗星星,加一的原因在于如果当前的游戏场景中小鸟的数量就是零会出现一个bug,导致无法显示一颗星星
        {
            
            stars[i].SetActive(true);
            yield return new WaitForSeconds(0.4f);//表示等待0.4秒

        }
    }
    /// <summary>
    /// 重玩点击事件,需要加入命名空间UnityEngine.SceneManagement,重玩点击后重新加载gameManager场景
    /// </summary>
    public void Replay()
    {
        SceneManager.LoadScene(2); //重玩界面为重新加载game场景,2表示game场景的默认下标为2(在Uinty中点击file选择build setttings后,将设置的多个场景直接拖入到界面中,系统会给每一个场景设置一个下标)
    }
    /// <summary>
    /// 返回主菜单点击事件
    /// </summary>
    public void Home()
    {
        SceneManager.LoadScene(1); 
    }
}
    
2.20.2暂停界面的鼠标点击事件(pause)
  1. 对场景中相应的按钮添加注册点击事件(需要对暂停按钮,开始按钮,重玩按钮,主按钮添加相应的事件,代码如上),例如:
    在这里插入图片描述

说明:在Canvas面板中,顺序靠后的物体会覆盖顺序靠前的物体,所以如果出现按钮点击没反应的情况可能为被其他物体覆盖,需要拖动物体的相对顺序来保证每一个物体都可以合理的点击

  1. 新建一个脚本pausePanel来负责暂停后弹开面板的鼠标点击操作,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//用于暂停后弹开面板的点击事件操作
public class pausePanel : MonoBehaviour {
    private Animator anim;
    public GameObject button;
    private void Awake()
    {
        anim = GetComponent<Animator>();//获取面板自身的动画状态机
    }
    public void Retry()
    {

    }
public void Pause()
    {
        //点击播放动画
        anim.SetBool("isPause", true);
        button.SetActive(false);
        
    }
public void Home()
    {

    }
    /// <summary>
    /// 点击了继续按钮
    /// </summary>
public void Resume()
    {
        //点击播放动画
        Time.timeScale = 1;
        anim.SetBool("isPause", false);
    }
    /// <summary>
    /// pause动画播放完调用
    /// </summary>
public void pauseAnimEnd()
    {
        Time.timeScale = 0;
    }
    /// <summary>
    /// resume动画播放完调用
    /// </summary>
    public void resumeAnimEnd()
    {
        button.SetActive(true);   
    }
}

  1. 对于pausePanel物体中的动画机进行如下操作来实现动画的转换
    在这里插入图片描述
  2. 对于pause动画和resume动画的结尾添加事件pauseAnimEnd和resumeAnimEnd,用来实现函数代码的功能

2.21添加镜头跟随

思路分析:利用差值来实现相机的跟随,通过一个Mathf.Clamp函数来限制移动的范围,通过Time.deltaTime来实现速度的控制并将其运行在update函数中;当游戏赢了和输了之后实现相机回到初始小鸟的位置;当游戏赢了之后,实现剩余的小鸟仍然回到弹簧上但是禁用点击功能。

  1. 修改bird.cs的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bird : MonoBehaviour {
    [HideInInspector]
    public bool isClick = false;  
    public float maxDis = 3;//设置小鸟拖拽的最大距离
    public SpringJoint2D sp;//定义一个SpringJoint2D类型的组件
    [HideInInspector]//在属性面板中隐藏组件(如果组件的形式为Private则在属性面板中不显示,如果组件的形式为public则在属性面板中显示)
    public Rigidbody2D rg;//定义一个组件
    public LineRenderer left;//定义左边的线
    public Transform leftPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体leftPos 
    public LineRenderer right;//定义右边的线
    public Transform rightPos;//Transform包含了物体的位置,旋转等信息,这里用来申明一个物体rightPos 
    public GameObject boom;
    private TestMyTrail myTrail;
    private bool canMove = true;//用来判断小鸟能否移动(解决在小鸟飞出到消失的时间内点击小鸟仍出现划线的效果的bug)
    //Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。
    public float smooth = 3;//设置相机跟随小鸟的速率
    public float posX;
    private void Awake()
    {
        sp = GetComponent<SpringJoint2D>();//脚本将自动识别物体中的SpringJoint2D组件,并将其赋值给sp
        rg = GetComponent<Rigidbody2D>();//组件赋值
        myTrail = GetComponent<TestMyTrail>();//获取拖尾效果脚本
    }
    private void OnMouseDown()//当用户在GUIElement或者碰撞器中按下鼠标时系统会自动调用的函数
    {
        if (gameManager._instance.can == false)
            canMove = false;
        if (canMove) {
            isClick = true;
            rg.isKinematic = true;//表示开启运动学
            
        }
        
    }
    private void OnMouseUp()//当用户在GUIElement或者碰撞器中抬起鼠标时系统会自动调用的函数
    {
        if (canMove)
        {
            right.enabled = false;
            left.enabled = false;//鼠标抬起时禁用划线的功能
                                 //该部分力学分析:当不更改任何条件时,小鸟在被拖拽起来后受到重力和弹簧的弹力两个力的作用,当受力达不到理想的效果的时候,小鸟就有可能达不到理想的平抛效果,这是可以采取一种方法来实现小鸟的平抛效果,首先
            isClick = false;
            rg.isKinematic = false;//表示关闭运动学
            Invoke("Fly", 0.1f);//使用Invoke函数来实现延时执行函数,前面表示执行的函数名称,后面的表示延时的时长
                                //禁用划线操作
            canMove = false;//当鼠标抬起时到下一个小鸟飞起时禁用小鸟的划线功能和小鸟位置随鼠标移动的功能
        }
        
    }
    private void Update()
    {
        if (isClick)//当鼠标一直按下时,使用相关的运动学进行有关的使用及输出
        {
            //transform.position = Input.mousePosition;//将鼠标的位置赋值给当前物体的位置,transform理解为当前物体
            //使用上一行代码会存在一个问题,鼠标的位置和当前物体的位置不在同一个坐标系下,小鸟的位置是在世界坐标系下(例如在unity中默认的(0,0)点位置和鼠标所在(0,0)点的位置(鼠标的(0,0)点是在屏幕的左下角)不同)需要来统一坐标系
            transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);//将屏幕坐标转化为世界坐标
            //这里存在一个问题,拖拽小鸟时出现小鸟的z轴被强制赋值为-10,和相机的z轴保持一致,在3D视角下可以发现,当小鸟的z轴小于-10或者大于0则相机都无法拍摄到小鸟的位置,所以需要强制锁定小鸟拖拽后的z轴的值在-10到0之间
            //transform.position += new Vector3(0, 0, 6);//强制锁定小鸟的z轴值为-4(方法一),Vector3表示一个三维向量,包含位置,方向等信息
            transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);//将小鸟的z轴坐标加上主摄像机的z轴坐标的赋值来强制锁定小鸟的z轴为0(方法二)
            if (Vector3.Distance(transform.position, rightPos.position) > maxDis)//Vector3中有一个方法Distance用来计算两个向量的位移,这里用来计算小鸟和rightPos之间的位移
            {
                Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量,normalized属性用来实现单位化向量
                pos *= maxDis;//得到最大长度向量
                transform.position = pos + rightPos.position;//小鸟的位置限制
            }
            Line();//这里存在一个bug,由于每一只小鸟飞出5秒后才被毁灭,由于小鸟在飞出的那一刻就禁用了sp弹簧组件故5秒内弹簧功能就失效了,但是如果5秒内点击小鸟该部分代码仍然可以被执行且划线功能仍存在,不能达到理想的效果(解决方案:)
        }
        //相机跟随
        posX = transform.position.x;//获取到小鸟的x轴的位置
        //利用差值来实现相机的跟随,第一个值表示初始点,第二个值表示目标点,第三个值表示速率,即理解为物体以一个速率从初始点向目标点移动
        Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, new Vector3(Mathf.Clamp(posX, 0, 15), Camera.main.transform.position.y, Camera.main.transform.position.z), smooth*Time.deltaTime);//Mathf.Clamp(posX, 0, 15)中的0和15分别表示小鸟的坐标下限和坐标上限,其中当小鸟的坐标小于下限或者小鸟的坐标大于上限时按照临界点进行输出

    }
    public void Fly()//设定一个函数Fly()用来关闭弹簧的功能
    {
        myTrail.trailStart();
        sp.enabled = false;//当鼠标弹起时,将弹簧的功能禁用,可实现小鸟飞出的功能
        Invoke("Next", 5);//实现5秒后实现Next函数
    }
    public void Line()//划线操作,原理:两点确定一条直线
    {
        right.enabled = true;
        left.enabled = true;//开启组件实现划线功能
        right.SetPosition(0, rightPos.position);//锁定第一个点,索引为0
        right.SetPosition(1, transform.position);//锁定第二个点,索引为1
        left.SetPosition(0, leftPos.position);
        left.SetPosition(1, transform.position);
    }
    /// <summary>
    /// 下一只小鸟的飞出
    /// </summary>
    void Next()
    {
        gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)
        Destroy(gameObject);
        Instantiate(boom, transform.position, Quaternion.identity);//显示特效
        gameManager._instance.NextBird(); 
    }
    /// <summary>
    /// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果
    /// </summary>
    /// <param name="collision"></param>
    private void OnCollisionEnter2D(Collision2D collision)
    {
        myTrail.trailClear();
    }
}
  1. 修改gameManager.cs的代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; 

public class gameManager : MonoBehaviour
{
    public List<bird> birds;//创建鸟的集合
    public List<pig> pigs;//创建猪的集合
    public static gameManager _instance;//用来为其他脚本访问该脚本提供接口(一种取代通过对象访问的方法)  
    private Vector3 originPos;//初始化的位置
    public GameObject win;
    public GameObject lose;
    public GameObject[] stars;//设定一个数组用来存放星星的数量(将组件中的星星进行赋值)
    public bool can = true;//用来表示赢下比赛后小鸟不能移动的判定变量
    bird rb = new bird();//实例化bird类,用来调用bird类中的属性和方法
    private bool move = false;//用来作为照相机移动的判定
    private void Update()
    {
        if (move)
        {
            loseCameraReturn();//游戏失败后移动照相机到初始位置
        }
    }
    private void Awake()
    {
        _instance = this;
        if(birds.Count > 0) {
            originPos = birds[0].transform.position;
        }
        
    }
    private void Start()
    {
        Initialized();
    }
    /// <summary>
    /// 初始化小鸟
    /// </summary>
    private void Initialized()
    {
        //遍历鸟的集合
        for (int i = 0; i < birds.Count; i++)//由于每一只小鸟释放后在集合中都被移除了故下一只小鸟的下标依然为0
        {
            if (i == 0)//第一只小鸟
            {
                birds[i].transform.position = originPos;//将第一支小鸟的位置赋值给后面每一只小鸟的位置
                birds[i].enabled = true;
                //要想访问sp组件,必须保证bird类的sp组件的访问权限为public
                birds[i].sp.enabled = true;//启用组件
            }
            else
            {
                birds[i].enabled = false;
                birds[i].sp.enabled = false;//禁用组件
            }
        }

    }
    /// <summary>
    /// 判定游戏逻辑
    /// </summary>
    public void NextBird()
    {
        if (pigs.Count >0)
        {
            if (birds.Count > 0)
            {
                //下一只小鸟飞出
                Initialized();
            }
            else
            {
                //输了
                move = true;
                lose.SetActive(true);
            }
        }
        else
        {
            //赢了
            Initialized();//表示下一只小鸟来到弹簧上,提供给小鸟一个位置,为镜头能回到初始位置提供一个参照值
            win.SetActive(true);
            can = false;//设置为false表示小鸟不能移动
        }
    }
    /// <summary>
    /// 通过游戏中小鸟的数量来设置星星的颗数
    /// </summary>
    public void showStars()
    {
        StartCoroutine("show");//启用设定的协程函数show()
    }
    /// <summary>
    /// 协程的书写来控制星星一颗一颗的显示
    /// </summary>
    /// <returns></returns>
    IEnumerator show()//设置一个协程函数show用来实现等待功能(用于实现星星一颗一颗出现且间隔有一个时间差)
    {
        for (int i = 0; i < birds.Count + 1; i++)//零个小鸟显示一颗星星,加一的原因在于如果当前的游戏场景中小鸟的数量就是零会出现一个bug,导致无法显示一颗星星
        {
            
            stars[i].SetActive(true);
            yield return new WaitForSeconds(0.4f);//表示等待0.4秒

        }
    }
    /// <summary>
    /// 重玩点击事件,需要加入命名空间UnityEngine.SceneManagement,重玩点击后重新加载gameManager场景
    /// </summary>
    public void Replay()
    {
        SceneManager.LoadScene(2); //重玩界面为重新加载game场景,2表示game场景的默认下标为2(在Uinty中点击file选择build setttings后,将设置的多个场景直接拖入到界面中,系统会给每一个场景设置一个下标)
    }
    /// <summary>
    /// 返回主菜单点击事件
    /// </summary>
    public void Home()
    {
        SceneManager.LoadScene(1); 
    }
    /// <summary>
    /// 用来实现输了比赛后相机返回到初始小鸟的位置
    /// </summary>
    public void loseCameraReturn()
    {
        Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, new Vector3(0, Camera.main.transform.position.y, Camera.main.transform.position.z), rb.smooth * Time.deltaTime);//Time.deltaTime乘上一个速率表示速度为几米每秒,但是这种为帧数速度,需要在update函数下执行
    }
}

2.22音乐的添加

【项目精选】基于Java的愤怒的小鸟游戏的设计与实现(视频+论文+源码 游戏非常简单,小鸟跳上弹弓,发射角度和强度由玩家的自行控制,游戏中需要良好的整体计算,适当的调节强度和角度,从而更精确地击中猪。在今天如此复杂多样的游戏环境中,这一款游戏所带来的新鲜感让人为之疯狂,极大展现了Java语言在开发物理运动游戏方面独有的魅力,也极大促进了Java语言在游戏方面的发展。Super Mario Bros是任天堂游戏公司开发的,相信我们小时候都玩过这一款游戏,它几乎是所有红白机游戏中最畅销的一款,游戏风格是横版过关类型,这个游戏几乎移植到了所有已知的游戏平台,为游戏爱好者广泛而知。 阅读详情

相关推荐

基于ECS架构与Box2D的《愤怒的小鸟》风格2D物理游戏开发实战

在游戏开发领域,ECS(实体-组件-系统)架构因其卓越的数据局部性和关注点分离特性,已成为构建高性能、可维护游戏系统的关键技术范式。其核心原理是将游戏对象解耦为纯粹数据的组件、处理逻辑的系统以及作为标识符的实体,这种数据驱动模式尤其适合管理大量动态交互的实体。结合Box2D这一成熟的2D物理引擎,开发者能够高效模拟刚体动力学、碰撞检测与复杂的物理交互,为游戏提供真实的物理反馈。在诸如《愤怒的小鸟》这类2D物理游戏中,ECS架构的价值得以充分体现,它能清晰组织小鸟、障碍物等实体的状态与行为,而Box2D则负责

weixin_29007243的博客 256

迷你愤怒的小鸟游戏

游戏功能 控制弹弓和不同的小鸟,使用每个小鸟独有的技能,杀死小猪。 游戏代码 #include <bits/stdc++.h> #include <windows.h> #include <stdio.h> #include <conio.h> #include <time.h> using namespace std; const double pi = 3.1415926536; int toint (float a) { return ((

风斯在下 1414

Unity物理游戏开发实战:从愤怒的小鸟到FlyBird的完整项目解析

在Unity游戏开发中,物理引擎是实现真实交互与动态效果的核心技术,其原理基于牛顿力学,通过刚体、碰撞器和关节等组件模拟物体运动与碰撞。这项技术的价值在于能够高效创建富有物理真实感的游戏玩法,广泛应用于2D/3D休闲游戏、模拟器和互动应用中。以经典的弹射类游戏为例,开发者需要深入理解弹道物理、碰撞伤害计算与对象池优化等关键技术。本文以FlyBird项目为具体案例,剖析一个完整Unity项目的架构设计、资源管理及性能优化策略,其中涉及的AI辅助资源生成与数据驱动设计,为现代游戏开发工作流提供了高效实践参考。

weixin_34211761的博客 456

C++---状态压缩dp---愤怒的小鸟(每日一道算法2023.4.19)

Kiana 最近沉迷于一款神奇的游戏无法自拔。    简单来说,这款游戏是在一个平面上进行的。  有一架弹弓位于 (0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟, 小鸟们的飞行轨迹均为形如 y=ax2+bx 的曲线,其中 a,b 是 Kiana 指定的参数,且必须满足 a

SRestia的博客 771

8、游戏开发:射击游戏与愤怒小鸟类游戏制作指南

本博客详细介绍了使用 GameSalad 开发射击游戏和愤怒小鸟类游戏的关键步骤与技术要点。内容涵盖能量条的动态处理与颜色变化、小行星碰撞逻辑、场景切换管理、弹弓与 projectile 的构建及移动逻辑等核心开发技巧。同时总结了插值行为、三角函数和属性管理在游戏开发中的实际应用,为开发者提供了清晰的指导和实践参考。

yog99的博客 75

11、打造类似《愤怒的小鸟》游戏的菜单与粒子效果

本文详细介绍了如何打造类似《愤怒的小鸟》的游戏菜单与粒子效果,涵盖前期准备、颜色与粒子设置、角色属性与规则创建、表格使用与性能优化等内容。通过使用GameSalad引擎,实现角色循环利用、关卡更新逻辑以及爆炸粒子特效,并提供优化与拓展建议,帮助开发者更好地构建多关卡游戏。

yog99的博客 64

C++ [项目] 愤怒的小鸟

支持Dev-C++5.11版本(务必调为英文输入法),基本操作看游戏里的介绍,怎么做的……懒得说,能看懂就看注释,没有的自己猜,如果你很固执……私我吧。

ab603803的博客 2万+

经典休闲游戏愤怒的小鸟PC版完整解析

回顾全文,你会发现《愤怒的小鸟》的成功绝非偶然。它把每一个环节都做到了极致:架构清晰:模块分离、职责明确体验优先:手感调优、预测辅助健壮可靠:容错机制、降级策略易于维护:Lua脚本、数据驱动正是这种“表面极简,内核精深”的设计哲学,让它经受住了时间和市场的双重考验。下次当你随手一弹打出三星通关时,不妨对这只小鸟说一句:“兄弟,你背后的技术团队真牛!” 😎毕竟,在这个连Hello World都要用框架的时代,还能见到如此干净利落的工程实践,实在难得。

weixin_42471823的博客 481

愤怒的小鸟 AngryBird Unity(打弹弓的那种)

03 资源导入 场景简单搭建 切图 带很多子图的 (了解)图标大小 (了解)图层顺序 视频多了为小鸟新建Player层 04 SpringJoint组件介绍 弹弓右,静态2D刚体,挂到小鸟弹簧组件上 小鸟,SpringJoint2D(弹簧),2D圆形碰撞器 以“遛狗”为例,“已连接锚点”是“你”,“锚点”是“狗” (了解) 坐标系 屏幕坐标是左下原点 Unity世界坐标默认是中心原点 (问题)一点小鸟跑到摄像机的位置 一点,z值变得和主相机一样,两种方法: 01 限死z为0 02 减去相机的z

weixin_39538253的博客 3533

【VC6.0版】愤怒的小鸟摩擦力模拟demo

博文的配套源码 《 【Visual C++】游戏开发笔记二十一 游戏基础物理建模(三) 摩擦力系统模拟》 ,文章地址为http://blog.csdn.net/zhmxy555/article/details/7555785, 如题,此为VC6.0版,另外还有VS2010版提供下载。 我的博客地址是http://blog.csdn.net/zhmxy555,源码结合我的文章一起看效果更佳 应大家要求,从本节文章开始,以后浅墨提供的文章配套源码都将分为VC6.0和VS2010两个版本,以方便使用不同版本IDE的朋友们进行学习提高。当然,依旧是0资源分下载~~~~~~~~~~~~~ 希望能和大家一起交流,共同学习,共同进步。

愤怒的小鸟.zip

非常适合学习使用,已经是完整版本 直接可以跑。 完整版的Class res

愤怒的小鸟下载教程:PC/安卓/iOS 全平台安装与运行全解析(2025 新实测版)

《愤怒的小鸟》2025年下载安装指南:经典休闲游戏全新升级,提供PC/安卓/iOS全平台详细教程。新版游戏采用全新物理引擎和4K画质,新增季节关卡与云存档功能。Windows需Win10系统,手机需安卓8.0/iOS14以上。安装过程包含运行库检查、权限设置等注意事项,并附常见问题解决方案。推荐配置与存档备份方法助您获得最佳体验,重温这款融合简单操作与深度策略的经典之作。

2401_89926894的博客 4842

STM32 ADC精准采集12V电池电压:从分压电路到软件校准全解析

本文详细解析了STM32 ADC精准采集12V电池电压的全过程,涵盖分压电路设计、电阻选型、ADC配置优化及软件校准方法。针对电池电压监测中的噪声、温漂等挑战,提供了硬件滤波与软件过采样等实战技巧,确保测量精度达到1%以内,满足嵌入式设备对电源管理的严苛要求。

tech5的博客 1013

Unity项目 - MissionDemolition 愤怒的小鸟核心机制

目录 游戏原型 项目演示 绘图资源 代码实现 注意事项 技术探讨 参考来源 游戏原型 爆破任务 MissionDemolition 是一款核心机制类似于愤怒的小鸟的游戏,玩家将用弹弓发射炮弹,摧毁城堡,最终目标是让发射的炮弹抵达城堡中心的目标区域。我们所希望实现的有: 当玩家鼠标光标处于弹弓区域内时,弹弓高亮,表示此时可以进行射击操作。 当玩家在该区域内按下左键,会实例化弹丸。玩...

dhj66039的博客 806

告别手动改图!用Python+PyWin32驱动SolidWorks,实现参数自动化修改(保姆级避坑指南)

本文详细介绍了如何利用Python和PyWin32驱动SolidWorks实现参数自动化修改,提供从环境配置到API连接的工业级解决方案。通过实战案例和避坑指南,帮助工程师高效搭建自动化脚本工作流,显著提升设计效率。

weixin_30614109的博客 742

华为云物联网系统开发(纯云端)外包方案及项目需求说明书

提供一个详细的需求文档(SRS,Software Requirements Specification),用于指导完全零基础的非专业负责人外包兼职专业技术人员开发一款基于华为云物联网平台、Astro低代码平台和Astro Canvas大屏设计的物联网系统,纯软件实现并支持下载部署。文档涵盖详细的软硬件组成清单(聚焦软件和云服务)、技术文档要求等,确保非专业人员在移交后能够复用、扩展和操作该系统。本文档简洁、全面,适合非技术背景的管理者使用,并基于华为云生态的内部实施部署。

hy行者勇哥的博客 620

【项目精选】基于Java的愤怒的小鸟游戏的设计与实现(视频+论文(1)

游戏非常简单,小鸟跳上弹弓,发射角度和强度由玩家的自行控制,游戏中需要良好的整体计算,适当的调节强度和角度,从而更精确地击中猪。在今天如此复杂多样的游戏环境中,这一款游戏所带来的新鲜感让人为之疯狂,极大展现了Java语言在开发物理运动游戏方面独有的魅力,也极大促进了Java语言在游戏方面的发展。Super Mario Bros是任天堂游戏公司开发的,相信我们小时候都玩过这一款游戏,它几乎是所有红白机游戏中最畅销的一款,游戏风格是横版过关类型,这个游戏几乎移植到了所有已知的游戏平台,为游戏爱好者广泛而知。

2401_84164527的博客 907

【云原生技术】sonar的官方web接口,获取质量阀结果的接口

以下是一个示例思路,说明如何调用 SonarQube 的质量阀查询接口并在前端展示“哪些条件没过”。假设前端使用 AJAX 或 fetch 从后端接口拿到 SonarQube 返回的 JSON(或直接前端请求 SonarQube API),前端再把解析结果展示给用户。测试覆盖率 (Coverage) 当前仅有 60%,未满足最低 80% 的要求,需要补充测试或优化以提升覆盖率。接口),就会返回相应的质量阀状态信息;里使用它来查询当前质量阀(质量门)的状态。,你用它即可获取该次分析的质量阀状态。

weixin_46453070的博客 1279

《游戏学习》java代码实现《愤怒的小鸟》实战源码

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料。

2401_84009698的博客 803

【2024遥感应用组一等奖】基于风云四号卫星的小时级大气气溶胶和颗粒物监测

最后,比较AGRI观测反射率和查找表计算的反射率反演得到的1小时分辨率AOD数据。然后,使用广泛应用的MODIS DT和DB产品进行对比,图 5显示了它们逐像元尺度的对比,AGRI LaGA算法可以捕捉亚洲地区AOD高低值空间分布格局,比如南亚次大陆、中南半岛、塔克拉玛干沙漠和华北平原是AOD高值区域(平均AOD高于0.3),中国西北、中国南方、中国东北和韩国地区为AOD中值地区(平均AOD在0.2到0.3之间),北纬45度以上以及日本地区为AOD低值区域(平均AOD小于0.2),与全球平均AOD值接近。

JData_Engineer的博客 373

Qt实现窗口等比例缩放

在Qt中,实现窗口的等比例缩放是一个常见的需求。通过对窗口的尺寸进行调整,使得窗口的内容在缩放过程中保持相同的比例,可以提供更好的用户体验。通过以上的代码,我们实现了一个可以等比例缩放的Qt窗口。在调整窗口大小时,窗口中的标签和按钮将按比例缩放,保持内容的比例不变。首先,我们需要在Qt中创建一个窗口,并设置其内容。在该事件中,我们将根据窗口的新尺寸计算出合适的比例因子,并将其应用到窗口的内容上。现在,我们将添加功能,使得窗口可以等比例缩放。最后,我们将缩放因子应用到窗口中的标签和按钮上,调整它们的尺寸。

2301_79330511的博客 3958

Python复刻《愤怒的小鸟》:从Pygame游戏开发到物理引擎实现

游戏开发是计算机图形学与交互逻辑的经典应用领域,其核心在于游戏循环、实时渲染和物理模拟。通过事件驱动架构,游戏引擎能够高效处理用户输入、状态更新与画面绘制,实现流畅的交互体验。在技术层面,碰撞检测、抛物线运动计算等物理引擎原理是构建逼真游戏世界的基础,这些技术不仅提升了游戏的可玩性,也为AI模拟、虚拟现实等前沿领域提供了底层支持。Pygame作为轻量级的2D游戏开发库,以其简洁的API和跨平台特性,成为学习游戏开发与物理模拟的理想工具。本文以经典游戏《愤怒的小鸟》为案例,详细解析如何利用Pygame实现弹射

anchang7456的博客 454

新手必看!手把手教你如何在飞牛NAS上玩转Docker容器!(飞牛Nas(fnOS)如何创建Docke容器以及Compose部署项目)

新手必看!手把手教你如何在飞牛NAS上玩转Docker容器!(飞牛Nas(fnOS)如何创建Docke容器以及Compose部署项目)

XiaoqiangClub的博客 1万+

Python Pygame实战:从零复刻《愤怒的小鸟》游戏开发全流程

游戏开发是学习编程语言和计算机图形学的经典实践路径,其核心在于将交互逻辑、物理模拟与游戏状态管理有机结合。在2D游戏开发中,物理引擎负责模拟物体运动与碰撞,而面向对象设计则是组织游戏角色与场景的有效方法。Python凭借其简洁语法和丰富库生态,成为初学者入门游戏开发的优选。Pygame作为Python的2D游戏开发库,提供了窗口管理、事件处理、图形绘制等基础功能,能帮助开发者快速构建游戏原型。本文以复刻经典游戏《愤怒的小鸟》为例,详细拆解了如何使用Pygame实现弹弓发射、碰撞检测和关卡系统等核心玩法。通过

weixin_30882895的博客 299

科研神器: 用Gemini 3

Edge藏了个科研开挂神器!免TZ/免谷歌账号用Gemini 3,1分钟上手(附保姆级教程) 做科研查文献、理数据总卡壳?想蹭Gemini 3的AI能力,却被“谷歌账号+TZ+节点筛选”三连劝退? 现在不用折腾了!Edge浏览器里装个插件,直接把Gemini 3揣进口袋——**免翻墙、免谷歌账号、国内直接用**,分分钟让你的科研效率翻三倍,导师都得问你“偷偷开了啥挂”!

ZhangJiqun&191cm&85kg 1159

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

从愤怒的小鸟来看Unity武器拖尾的特效优化

本文介绍了PocketRPGWeaponTrails武器拖尾特效插件的使用方法。相比Unity自带组件,该插件通过实时计算MeshRenderer数据,能更精准地跟随武器运动轨迹。文章详细说明了插件的核心参数配置(如desiredTime、timeTransitionSpeed等),并提供了2D场景下的实现示例(参考《愤怒的小鸟》)。通过编写TestMyTrail.cs脚本控制拖尾显示逻辑,可实现在待机状态隐藏、发射时显示拖尾的效果。此外,还介绍了使用sortgroup组件解决拖尾遮挡问题的方法。

YF云飞的博客 774

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

上一篇: unity实战——穿越火线
下一篇: MyBatis框架系统学习(持续更新)
DedicationYu
博客等级 码龄9年 158粉丝 70原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值