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

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

@elevateSober

1.项目实施资源

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

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

2.项目实施过程

2.1工程的建立

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

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

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

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

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

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

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

2.3spring joint 2D组件的加入

在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小鸟的拖拽

给小鸟添加一个圆型碰撞器,在小鸟的属性面板中输入点击add component,选择circle collider 2D,通过调节radius属性的值来调节圆形的大小。给小鸟添加一个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限定小鸟的最大拖拽距离

首先给right创建一个子物体rightPos,用来计算小鸟和rightPos的距离(这里如果用right来计算距离,会发现right的中心点不在spring joint 2D弹簧端点上,这样计算会有很大误差,新建一个子物体,并将其中心点移动到弹簧的端点上,这样计算就更加准确)在脚本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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

}

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猪的受伤

添加地面场景:在Image文件夹下裁剪图片THEME_01,裁剪效果为:

到场景中,同时给图片添加一个碰撞器,然后复制多个,配置成如下地面效果图:

在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();//初始化组件

}

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(这里表示更换图片)

}

}

}

在pig脚本所在的属性面板中将hurt的图片改为BIRDS_1_104

2.8弹弓划线操作

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

点击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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

}

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死亡,加分特效制作

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

在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);

}

}

在脚本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();//初始化组件

}

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文件夹中(该文件夹用来存放物体的模板,以保证物体在场景中被删除时可以被正常调用),对于不存在与场景中的物体如果直接在代码进行毁灭会导致模板被毁掉的风险,导致下次无法调用,所以需要将其赋值给一个物体,通过该物体进行显示,对该物体进行毁灭;如果该物体存在于场景中就可以直接删除

}

}

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

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

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

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List pigs;//创建猪的集合

public static gameManager _instance;

private void Awake()

{

_instance = this;

}

private void Start()

{

Initialized();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

public void NextBird()

{

if (pigs.Count > 0)

{

if (birds.Count > 0)

{

//下一只小鸟飞出

Initialized();

}

else

{

//输了

}

}

else

{

//赢了

}

}

}

在gameManager物体的属性界面中将相关的bird和pig拖入到集合到(注意顺序必须不能错)

在脚本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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

}

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);

}

///

/// 下一只小鸟的飞出

///

void Next()

{

gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)

Destroy(gameObject);

Instantiate(boom, transform.position, Quaternion.identity);//显示特效

gameManager._instance.NextBird();

}

}

在脚本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();//初始化组件

}

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解决重复划线,小鸟轮换速度突然变大的问题

解决重复划线的问题可以直接在鼠标抬起时禁用掉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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

}

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);

}

///

/// 下一只小鸟的飞出

///

void Next()

{

gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)

Destroy(gameObject);

Instantiate(boom, transform.position, Quaternion.identity);//显示特效

gameManager._instance.NextBird();

}

}

解决小鸟的轮换速度突然变大的问题就是将下一个小鸟的位置固定为第一只小鸟的的初始位置,修改gameManager.cs代码的内容如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List 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();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

public void NextBird()

{

if (pigs.Count > 0)

{

if (birds.Count > 0)

{

//下一只小鸟飞出

Initialized();

}

else

{

//输了

}

}

else

{

//赢了

}

}

}

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

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

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

https://blog.csdn.net/akof1314/article/details/37603559

为物体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();

}

}

为bird添加一个子物体命名为trail,将其位置设置在小鸟的尾巴上,为其添加一个组件名为Mesh Renderer,将材质设置为WeaponTrail,同时将属性设置为理想的效果,例如:

在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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

myTrail = GetComponent();//获取拖尾效果脚本

}

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);

}

///

/// 下一只小鸟的飞出

///

void Next()

{

gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)

Destroy(gameObject);

Instantiate(boom, transform.position, Quaternion.identity);//显示特效

gameManager._instance.NextBird();

}

///

/// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果

///

///

private void OnCollisionEnter2D(Collision2D collision)

{

myTrail.trailClear();

}

}

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

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

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

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

创建一个ui图片命名为lose(表示失败后会显示的图片),对图片进行有关操作:

在lose下在创建一个图片,对该图片进行上下进行填充,按照第一点的相关操作对其进行颜色和透明度修改,同时在视图中根据自己的需求拖动页面的大小

在图片下新建三个图片,用来构造失败后出现图片的布局(可以根据自己的喜好设置布局),以下为一个案例:

对lose按下ctrl+6后点击create添加动画片段,命名为lose,然后对该部分实现一个局部变暗的效果

对lose进行复制并重命名为win

对于lose和win两个ui来说,一般在界面上是不显示的,所需不勾选组件在工程面板中对lose进行复制,并将赋值后的对象改名为win,并进行如下操作

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

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

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class win : MonoBehaviour {

///

/// 动画播放完毕后显示星星的操作

///

public void show()

{

gameManager._instance.showStars();

}

}

在win动画的最后一帧添加一个函数show(或者在gameManager.cs中添加一个延时执行函数,前面提到过,可参考前面的内容)在gameManager.cs中添加代码,来实现lose和win组件的开启,代码如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List 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();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

public void NextBird()

{

if (pigs.Count > 0)

{

if (birds.Count > 0)

{

//下一只小鸟飞出

Initialized();

}

else

{

//输了

lose.SetActive(true);

}

}

else

{

//赢了

win.SetActive(true);

}

}

public void showStars()

{

}

}

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

2.15修改火花粒子系统

向工程中导入新的资源,资源的名字为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

新建一个照相机重命名为UICamera,进行如下修改

对Canvas进行如下设置

对root的layer设置为UI,并对sub和sub2分别进行如下设置

并将root复制三份分别放置到win下的left,middle,right中,并调整适当的位置使其效果最佳。同时移除UICamera照相机的audio listener组件删除

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

2.17让星星一颗一颗的显示

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

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List 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();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

public void NextBird()

{

if (pigs.Count > 0)

{

if (birds.Count > 0)

{

//下一只小鸟飞出

Initialized();

}

else

{

//输了

lose.SetActive(true);

}

}

else

{

//赢了

win.SetActive(true);

}

}

///

/// 通过游戏中小鸟的数量来设置星星的颗数

///

public void showStars()

{

StartCoroutine("show");//启用设定的协程函数show()

}

///

/// 协程的书写来控制星星一颗一颗的显示

///

///

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秒

}

}

}

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

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

修改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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

myTrail = GetComponent();//获取拖尾效果脚本

}

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);

}

///

/// 下一只小鸟的飞出

///

void Next()

{

gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)

Destroy(gameObject);

Instantiate(boom, transform.position, Quaternion.identity);//显示特效

gameManager._instance.NextBird();

}

///

/// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果

///

///

private void OnCollisionEnter2D(Collision2D collision)

{

myTrail.trailClear();

}

}

2.19添加暂停动画

在Canvas下新建一个image命名为pause,将暂停的图片拖入其中,并将其位置设置在左上角在Canvas下新建一个空物体命名为pausePanel,将其位置设置为整个界面,在pausePanel下新建一个UI图片命名为bg,将其位置设置为整个界面,将其颜色设置为黑色,设置透明度为适当在bg下新建一个图片image,将其颜色设置为黑色透明度设置我适当(比bg要不透明)将其位置设置为上下拉伸并拖动之左侧,在image下新建三个图片分别命名为resume,home,retry,分别插入开始,返回,重试三张图片,并调整适当位置,例如:

点击pausePanel,按下ctrl+6进行动画编辑,添加两个动画分别命名为resume和pause

对resume和pause分别进行开始动画操作和暂停动画操作,其中开始动画操作包括场景变亮(在bg操作)和按钮界面移出两个动画(在image操作),而暂停动画操作包括场景变暗(在bg操作)和按钮界面移入两个动画(在image操作),这里暂停和开始两个动画的功能效果时完全相反的,下面介绍开始动画效果(暂停动画效果类似)

1.界面变亮效果实现

2.按钮界面退出的动画

2.20添加鼠标注册事件

2.20.1输赢界面的鼠标点击事件(win和lose)

对于win和lose中的需要添加点击事件的图标添加一个按钮组件(button),在脚本gameManager中添加如下代码

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.SceneManagement;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List 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();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

public void NextBird()

{

if (pigs.Count > 0)

{

if (birds.Count > 0)

{

//下一只小鸟飞出

Initialized();

}

else

{

//输了

lose.SetActive(true);

}

}

else

{

//赢了

win.SetActive(true);

}

}

///

/// 通过游戏中小鸟的数量来设置星星的颗数

///

public void showStars()

{

StartCoroutine("show");//启用设定的协程函数show()

}

///

/// 协程的书写来控制星星一颗一颗的显示

///

///

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秒

}

}

///

/// 重玩点击事件,需要加入命名空间UnityEngine.SceneManagement,重玩点击后重新加载gameManager场景

///

public void Replay()

{

SceneManager.LoadScene(2); //重玩界面为重新加载game场景,2表示game场景的默认下标为2(在Uinty中点击file选择build setttings后,将设置的多个场景直接拖入到界面中,系统会给每一个场景设置一个下标)

}

///

/// 返回主菜单点击事件

///

public void Home()

{

SceneManager.LoadScene(1);

}

}

2.20.2暂停界面的鼠标点击事件(pause)

对场景中相应的按钮添加注册点击事件(需要对暂停按钮,开始按钮,重玩按钮,主按钮添加相应的事件,代码如上),例如:

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

新建一个脚本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();//获取面板自身的动画状态机

}

public void Retry()

{

}

public void Pause()

{

//点击播放动画

anim.SetBool("isPause", true);

button.SetActive(false);

}

public void Home()

{

}

///

/// 点击了继续按钮

///

public void Resume()

{

//点击播放动画

Time.timeScale = 1;

anim.SetBool("isPause", false);

}

///

/// pause动画播放完调用

///

public void pauseAnimEnd()

{

Time.timeScale = 0;

}

///

/// resume动画播放完调用

///

public void resumeAnimEnd()

{

button.SetActive(true);

}

}

对于pausePanel物体中的动画机进行如下操作来实现动画的转换

对于pause动画和resume动画的结尾添加事件pauseAnimEnd和resumeAnimEnd,用来实现函数代码的功能

2.21添加镜头跟随

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

修改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组件,并将其赋值给sp

rg = GetComponent();//组件赋值

myTrail = GetComponent();//获取拖尾效果脚本

}

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);

}

///

/// 下一只小鸟的飞出

///

void Next()

{

gameManager._instance.birds.Remove(this);//移除集合内的bird(第一个飞出的小鸟)

Destroy(gameObject);

Instantiate(boom, transform.position, Quaternion.identity);//显示特效

gameManager._instance.NextBird();

}

///

/// 一个碰撞器用来实现当小鸟碰撞到猪时关闭拖尾效果

///

///

private void OnCollisionEnter2D(Collision2D collision)

{

myTrail.trailClear();

}

}

修改gameManager.cs的代码如下

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.SceneManagement;

public class gameManager : MonoBehaviour

{

public List birds;//创建鸟的集合

public List 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();

}

///

/// 初始化小鸟

///

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;//禁用组件

}

}

}

///

/// 判定游戏逻辑

///

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表示小鸟不能移动

}

}

///

/// 通过游戏中小鸟的数量来设置星星的颗数

///

public void showStars()

{

StartCoroutine("show");//启用设定的协程函数show()

}

///

/// 协程的书写来控制星星一颗一颗的显示

///

///

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秒

}

}

///

/// 重玩点击事件,需要加入命名空间UnityEngine.SceneManagement,重玩点击后重新加载gameManager场景

///

public void Replay()

{

SceneManager.LoadScene(2); //重玩界面为重新加载game场景,2表示game场景的默认下标为2(在Uinty中点击file选择build setttings后,将设置的多个场景直接拖入到界面中,系统会给每一个场景设置一个下标)

}

///

/// 返回主菜单点击事件

///

public void Home()

{

SceneManager.LoadScene(1);

}

///

/// 用来实现输了比赛后相机返回到初始小鸟的位置

///

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音乐的添加

🎨 相关创意作品

Q宠企鹅和宠物社区怎么卸载永久彻底删除
365bat提现

Q宠企鹅和宠物社区怎么卸载永久彻底删除

📅 06-29 👁️ 7111
英语四六级估分器
365bat提现

英语四六级估分器

📅 06-27 👁️ 6486
2021年下半年报考指南:银行业专业人员职业资格考试(QCBP)简介