参考视频: 【SiKi学院Unity】Unity初级案例 - 愤怒的小鸟
一、项目准备
资源下载: http://www.sikiedu.com/course/134
新建工程:
选择2D ,填写项目名称并选择项目路径:
将资源中的Image 和Music 复制到项目文件夹中:
二、切片
选择猪和鸟的第一章图片,将其Sprite Mode 改为Multiple 并Apply :
如下图所示进行切片:
发现四个爆炸烟雾未切分好,手动切分一下:
Apply :
在场景中拖入弹弓和小鸟,设置层级关系:
三、实现小鸟的拖拽与飞出
1. 添加并设置Spring Joint 2D
为小鸟添加Spring Joint 2D 组件,添加后会自动添加刚体组件:
同时给弹弓右部加上刚体组件,并设置为Static (防止其受重力影响):
将弹弓右部的刚体拖到Spring Joint 2D 中,并设置Spring Joint 2D 的各项参数:
2. 实现小鸟跟随鼠标移动
添加碰撞体和Bird 脚本:
bird.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird : MonoBehaviour
{
private bool isClick = false;
private void Update()
{
MoveWithMouse();
}
private void MoveWithMouse()
{
if (isClick)
{
transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);
}
}
private void OnMouseDown()
{
isClick = true;
}
private void OnMouseUp()
{
isClick = false;
}
}
此时小鸟被鼠标拖拽时会跟随鼠标移动。
3. 限定小鸟最大拖拽距离
首先在弹弓右部设置一个点,作为小鸟绕着转的点:
在Bird.cs 中声明rightPos 和 maxDis变量:
public Transform rightPos;
public float maxDis;
修改MoveWithMouse() 如下:
private void MoveWithMouse()
{
if (isClick)
{
transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);
if (Vector2.Distance(transform.position, rightPos.position) >= maxDis)
{
transform.position = rightPos.position + (transform.position - rightPos.position).normalized * maxDis;
}
}
}
在Inspector 面板中将rightPos 拖拽到bird 脚本中,并设置maxDis ,此时小鸟被限定了拖拽距离:
4. 实现小鸟飞出
鼠标按下时,刚体状态设置为动力学。 在鼠标松开时,动力学设为false,一段时间后禁用SpringJoint2D 组件:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird : MonoBehaviour
{
private SpringJoint2D springJoint;
private Rigidbody2D rb;
public Transform rightPos;
public float maxDis;
private bool isClick = false;
private void Start()
{
springJoint = GetComponent<SpringJoint2D>();
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
MoveWithMouse();
}
private void MoveWithMouse()
{
if (isClick)
{
transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);
if (Vector2.Distance(transform.position, rightPos.position) >= maxDis)
{
transform.position = rightPos.position + (transform.position - rightPos.position).normalized * maxDis;
}
}
}
private void OnMouseDown()
{
isClick = true;
rb.isKinematic = true;
}
private void OnMouseUp()
{
isClick = false;
rb.isKinematic = false;
Invoke(nameof(Fly), 0.12f);
}
private void Fly()
{
springJoint.enabled = false;
}
}
5. 实现弹弓的划线
在弹弓上创建一个leftPos 点,此时leftPos 和rightPos 两个点用于划线:
给left 添加Line Renderer 组件:
设置材质、颜色和长度:
将该组件复制到right 上:
编写代码: bird.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird : MonoBehaviour
{
private SpringJoint2D springJoint;
private Rigidbody2D rb;
[Header("弹弓")]
public Transform rightPos;
public Transform leftPos;
public LineRenderer leftLine;
public LineRenderer rightLine;
[Space]
public float maxDis;
private bool isClick = false;
private void Start()
{
springJoint = GetComponent<SpringJoint2D>();
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
MoveWithMouse();
}
private void MoveWithMouse()
{
if (isClick)
{
transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position += new Vector3(0, 0, -Camera.main.transform.position.z);
if (Vector2.Distance(transform.position, rightPos.position) >= maxDis)
{
transform.position = rightPos.position + (transform.position - rightPos.position).normalized * maxDis;
}
DrawLine();
}
}
private void OnMouseDown()
{
isClick = true;
rb.isKinematic = true;
}
private void OnMouseUp()
{
isClick = false;
rb.isKinematic = false;
DeleteLine();
Invoke(nameof(Fly), 0.12f);
}
private void Fly()
{
springJoint.enabled = false;
}
private void DrawLine()
{
leftLine.SetPosition(0, leftPos.position);
leftLine.SetPosition(1, transform.position);
rightLine.SetPosition(0, rightPos.position);
rightLine.SetPosition(1, transform.position);
}
private void DeleteLine()
{
leftLine.SetPosition(1, leftPos.position);
rightLine.SetPosition(1, rightPos.position);
}
}
此时拖拽小鸟时可正常划线,松开则线消失。
6. 让小鸟不能重复拖拽
此时小鸟被拖拽放飞后,再次按住小鸟任然能回到弹弓上。
为了解决该bug,给小鸟添加一个bool参数:
private bool flied = false;
鼠标抬起后,设置为true:
private void OnMouseUp()
{
isClick = false;
flied = true;
rb.isKinematic = false;
DeleteLine();
Invoke(nameof(Fly), 0.12f);
}
鼠标按下后,如果flied 已经为true,则无效:
private void OnMouseDown()
{
if (flied)
return;
isClick = true;
rb.isKinematic = true;
}
此时小鸟只能被拖拽一次。
四、场景搭建
选择场景1进行切割:
将地面拖入并添加碰撞体:
创建预制体相关文件夹,并将其拖入:
设置地面:
同理,设置天空:
五、实现小鸟攻击猪
添加猪,并添加相关组件并设置图层:
将鸟和猪的角阻力都设置为2,防止其在地面滚动不停止:
1. 猪的受伤与死亡
给猪添加Pig 脚本:
设置猪受到小鸟的碰撞时,受伤和死亡应该达到的相对速度:
public float hurtSpeed = 5.0f;
public float deadSpeed = 10.0f;
设置猪受伤后的图片:
public Sprite hurtSprite;
拖入相应的烟雾的图片到场景中:
创建boom 动画:
打开Animation 进行相应的调整:
同时将该动画取消循环播放:
为Boom 添加一个脚本Boom :
编写代码: Boom.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Boom : MonoBehaviour
{
public void DestorySelf()
{
Destroy(gameObject);
}
}
在Animation 中设置,当播放完之后执行该函数,即销毁自身:
将Boom 物体制成预制体:
猪的死亡逻辑:
若相对速度大于死亡速度:则死亡; 若相对速度只是大于受伤速度:此时猪若是受伤状态,则死亡;反之则受伤
在Pig.cs 中,实现猪的受伤与死亡:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Pig : MonoBehaviour
{
private SpriteRenderer spriteRenderer;
public Sprite hurtSprite;
public GameObject boomPrefab;
[Header("相对速度")]
public float hurtSpeed = 5.0f;
public float deadSpeed = 10.0f;
private bool isHurt = false;
private void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
private void OnCollisionEnter2D(Collision2D collision)
{
float relativeV = collision.relativeVelocity.magnitude;
print(relativeV);
if (relativeV >= deadSpeed)
{
Dead();
}
else if (relativeV >= hurtSpeed)
{
if (isHurt)
Dead();
else
{
isHurt = true;
spriteRenderer.sprite = hurtSprite;
}
}
}
private void Dead()
{
Instantiate(boomPrefab, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}
2. 猪的加分
切割分数图片:
拖拽出一个分数并制为预制体:
编写pig.cs 代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Pig : MonoBehaviour
{
private SpriteRenderer spriteRenderer;
public Sprite hurtSprite;
public GameObject boomPrefab;
public GameObject scorePrefab;
[Header("相对速度")]
public float hurtSpeed = 5.0f;
public float deadSpeed = 10.0f;
private bool isHurt = false;
[Space]
public float scoreYOffset = 0.65f;
private void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
private void OnCollisionEnter2D(Collision2D collision)
{
float relativeV = collision.relativeVelocity.magnitude;
print(relativeV);
if (relativeV >= deadSpeed)
{
Dead();
}
else if (relativeV >= hurtSpeed)
{
if (isHurt)
Dead();
else
{
isHurt = true;
spriteRenderer.sprite = hurtSprite;
}
}
}
private void Dead()
{
GameManager.instance.pigs.Remove(this);
Instantiate(boomPrefab, transform.position, Quaternion.identity);
GameObject scoreObject = Instantiate(scorePrefab, transform.position + new Vector3(0, scoreYOffset, 0),
Quaternion.identity);
Destroy(scoreObject, 1.5f);
Destroy(gameObject);
}
}
六、实现多只鸟的控制
将小鸟制为预制体,并复制两只:
创建一个Game Manager 空物体,新建并挂上GameManager 脚本:
使用单例模式并用列表存储小鸟和猪:
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public List<Bird> birds;
public List<Pig> pigs;
private void Awake()
{
instance = this;
}
}
在Bird 中编写,让小鸟飞出几秒后销毁,并生成爆炸动画,并从GameManager 的列表中移除:
private bool isClick = false;
private bool flied = false;
private void Fly()
{
springJoint.enabled = false;
Invoke(nameof(DestroySelf), flyTime);
}
private void DestroySelf()
{
GameManager.instance.birds.Remove(this);
Instantiate(boomPrefab, transform.position, Quaternion.identity);
Destroy(gameObject);
}
同理,猪死亡时,在GameManger 的列表中也需要删除:
private void Dead()
{
GameManager.instance.pigs.Remove(this);
Instantiate(boomPrefab, transform.position, Quaternion.identity);
GameObject scoreObject = Instantiate(scorePrefab, transform.position + new Vector3(0, scoreYOffset, 0),
Quaternion.identity);
Destroy(scoreObject, 1.5f);
Destroy(gameObject);
}
在GameManager 中,起初启用第一只小鸟、禁用其他小鸟的脚本和SpringJoint,并在小鸟销毁后判断游戏状态以及是否启用下一只小鸟: GameManager.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public List<Bird> birds;
public List<Pig> pigs;
private Vector3 originBirdPos;
private void Awake()
{
instance = this;
if (birds.Count > 0)
originBirdPos = birds[0].transform.position;
InitBird();
}
private void InitBird()
{
for(int i = 0; i <birds.Count; i++)
{
if (i == 0)
{
birds[i].transform.position = originBirdPos;
birds[i].enabled = true;
birds[i].GetComponent<SpringJoint2D>().enabled = true;
}
else
{
birds[i].enabled = false;
birds[i].GetComponent<SpringJoint2D>().enabled = false;
}
}
}
public void Next()
{
if (pigs.Count == 0)
{
}
else
{
if (birds.Count == 0)
{
}
else
{
InitBird();
}
}
}
}
在小鸟销毁后,调用`Next()`: `bird.cs`:
private void DestroySelf()
{
GameManager.instance.birds.Remove(this);
GameManager.instance.Next();
Instantiate(boomPrefab, transform.position, Quaternion.identity);
Destroy(gameObject);
}
最后在Inspector 面板中拖拽小鸟和猪后,可正常控制多只小鸟。
七、小鸟尾迹的实现
导入素材中的Unity包:
只添加Weapon Trail :
给小鸟添加Trail Renderer 组件:
设置拖尾的材质、持续时间已经宽度:
同时加载到其他预制体上:
拖尾效果:
八、给猪造房子
切割:
以方形木头为例:
在Pig.cs 脚本中添加bool的isPig,让木块等也能使用:
[Space]
public bool isPig = false;
private void Dead()
{
if (isPig)
GameManager.instance.pigs.Remove(this);
Instantiate(boomPrefab, transform.position, Quaternion.identity);
GameObject scoreObject = Instantiate(scorePrefab, transform.position + new Vector3(0, scoreYOffset, 0),
Quaternion.identity);
Destroy(scoreObject, 1.5f);
Destroy(gameObject);
}
将之前的猪勾选isPig :
将木头挂载该脚本并不勾选isPig ,然后设置hurtSpeed等参数 :
木头需要组件: 刚体、碰撞体、Pig脚本
同理,新增其他物品。
设置简单的场景如下:
运行效果:
九、游戏胜利、失败界面
1. 显示胜利、失败基本界面
切割:
制作UI(参考第15集).
Lose UI :
Win UI :
在游戏胜利时,根据关卡中的剩余小鸟数量,判断应该获得的星星数量,GameManager.cs 部分代码如下:
[Header("UI")]
public GameObject winUI;
public GameObject loseUI;
[Header("胜利得到星星的数量需要的小鸟存活数")]
public int birdNumOf3Star;
public int birdNumOf2Star;
public void Next()
{
if (pigs.Count == 0)
{
winUI.SetActive(true);
}
else
{
if (birds.Count == 0)
{
loseUI.SetActive(true);
}
else
{
InitBird();
}
}
}
public void WinLevel()
{
if (birds.Count >= birdNumOf3Star)
{
}
else if (birds.Count >= birdNumOf2Star)
{
}
else
{
}
}
同时,为Win UI 创建一个Win.cs 的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Win : MonoBehaviour
{
public void ShowStar()
{
GameManager.instance.WinLevel();
}
}
在动画结束后调用:
2. 星星粒子效果
切割星星:
三颗星星的摆放:
根据https://www.bilibili.com/video/BV1qb411c76x?p=16第16到18集设置星星的粒子效果。
3. 星星显示
在GameManager.cs 中,利用协程,每隔0.7s,显示一颗星星:
public GameObject[] starsUI = new GameObject[3];
public void WinLevel()
{
if (birds.Count >= birdNumOf3Star)
{
StartCoroutine("ShowTheStar", 3);
}
else if (birds.Count >= birdNumOf2Star)
{
StartCoroutine("ShowTheStar", 2);
}
else
{
StartCoroutine("ShowTheStar", 1);
}
}
IEnumerator ShowTheStar(int num)
{
for(int i = 0; i < num; i++)
{
starsUI[i].SetActive(true);
yield return new WaitForSeconds(0.7f);
}
}
在Insepctor 面板中拖入星星:
此时正常显示星星:
十、暂停界面
1. 界面以及动画
P20
暂停按钮:
暂停面板:
实现暂停动画:
实现继续动画:
两个动画都取消循环播放:
2. 实现暂停和继续
在Animator 中创建两个Trigger 参数,并将两个动画相连:
Pause动画与Resume的连线:
创建PausePanle 脚本并放入Pause Panel :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PausePanel : MonoBehaviour
{
private Animator anim;
void Start()
{
anim = GetComponent<Animator>();
}
public void Resume()
{
anim.SetTrigger("resume");
}
public void AfterResume()
{
this.gameObject.SetActive(false);
anim.SetTrigger("pause");
}
}
GameManager.cs 中添加代码:
[Space]
public GameObject pausePanel;
public void PauseGame()
{
pausePanel.SetActive(true);
}
在Inspector 面板中,拖入Pause Panel :
给Pause Btn 添加点击事件,为GameManager 的暂停方法:
给Resume Btn 添加点击事件,为Pause Panel 的Pause 方法:
在Resume 动画结束时,调用AfterResume 函数:
效果:
3. 实现Restart Level
在GameManager.cs 中添加重启关卡方法:
using UnityEngine.SceneManagement;
public void RestartLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
在各个重启按钮中调用:
运行效果:
十一、相机跟随
1. 跟随第一只小鸟
打开Package Manager :
下载Cinemachine :
添加一个2D Camera :
将第一只小鸟拖入Follow ,让相机跟随小鸟:
调节部分参数,以选取合适位置:
同时创建一个名为Camera BG 的空物体,为其加上Polygon 碰撞体,勾选Is Trigger ,用作相机的范围,(即小鸟若超出该范围,相机则不跟随):
在刚刚的CM vcam1 中,添加CinemachineConfiner :
将刚刚的Camera BG 拖入:
2. 按顺序跟随多只小鸟
在GameManager 中引入:
using Cinemachine;
若报错,则导入Cinemachine 示例程序场景:
GameManager.cs 更改的代码:
using Cinemachine;
[Header("相机和弹弓")]
public CinemachineVirtualCamera virtualCamera;
public Transform slingshotLeftPos;
private void InitBird()
{
for(int i = 0; i <birds.Count; i++)
{
if (i == 0)
{
birds[i].transform.position = originBirdPos;
birds[i].enabled = true;
birds[i].GetComponent<SpringJoint2D>().enabled = true;
virtualCamera.Follow = birds[i].transform;
}
else
{
birds[i].enabled = false;
birds[i].GetComponent<SpringJoint2D>().enabled = false;
}
}
}
public void Next()
{
virtualCamera.Follow = slingshotLeftPos;
virtualCamera.transform.position = slingshotLeftPos.position;
if (pigs.Count == 0)
{
winUI.SetActive(true);
}
else
{
if (birds.Count == 0)
{
loseUI.SetActive(true);
}
else
{
InitBird();
}
}
}
最终演示效果:
参考
【SiKi学院Unity】Unity初级案例 - 愤怒的小鸟
|