一、游戏概述
这款游戏算是小时候的童年了,比较适合我这种新手练手,刚开始也是不知道用什么组件和逻辑去制作,后面也是看视频学的。
游戏结构:UI页面(加载画面和关卡选择)和游戏场景
二、加载页面(场景Loading)
加载页面很简单就两张图,而外摄像机或者Canvas里加个脚本,用来设置分辨率( Screen.SetResolution)和延迟调用Invoke()来实现跳转场景到地图选择。
三、地图选择和关卡选择(场景01_level)
地图选择
地图和关卡选择核心代码(绑定到每个地图)
public class MapSelect : MonoBehaviour {
public int starsNum = 0; //星星数
public bool isSelect = false; //是否被选中
public GameObject locks; //锁
public GameObject stars; //星星
public GameObject panel; //关卡
public GameObject map; //地图
public Text starsText; //星星数量
public int startNum = 1;
public int endNum = 3;
private void Start()
{
//PlayerPrefs.DeleteAll();
if (PlayerPrefs.GetInt("totalNum", 0) >= starsNum) //判断本地是否有存totalNum,有则返回对应数,无则返回0(以此来判断星星数)
{
Debug.Log(PlayerPrefs.GetInt("totalNum"));
isSelect = true; //设置有就说明被选中
}
if (isSelect)
{
locks.SetActive(false); //关锁
stars.SetActive(true); //打开星星
//TODo:text显示
int counts = 0; //目前得星星数
for (int i = startNum; i <= endNum; i++)
{
counts += PlayerPrefs.GetInt("level"+i.ToString(),0); //星星数求和
}
starsText.text = counts.ToString() + "/60";
}
}
/// <summary>
/// 鼠标点击
/// </summary>
public void Selected() //按钮事件
{
if (isSelect) {
panel.SetActive(true);
map.SetActive(false);
}
}
public void panelSelect() { //关卡按钮事件
panel.SetActive(false);
map.SetActive(true);
}
}
主要是运用PlayerPrefs本地持久化类判断选择地图,本人主页有详细写。关卡的选择则是根据Button激活方法判断。
关卡选择
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class LevelSelect : MonoBehaviour {
public bool isSelect = false;
public Sprite levelBG; //每一关背景
private Image image;
public GameObject[] stars;
private void Awake()
{
image = GetComponent<Image>();
}
private void Start()
{
if (transform.parent.GetChild(0).name == gameObject.name)
{
isSelect = true;
}
else {// 判断当前关卡是否可以选择
int beforeNum = int.Parse(gameObject.name) - 1;
if (PlayerPrefs.GetInt("level" + beforeNum.ToString()) > 0) {
isSelect = true;
}
}
if (isSelect) {
image.overrideSprite = levelBG; //已经被选则则加上背景
transform.Find("num").gameObject.SetActive(true);
int count = PlayerPrefs.GetInt("level" + gameObject.name);//获取现在关卡对应的名字,然后获得对应的星星个数
if (count > 0) {
for (int i = 0; i < count; i++) {
stars[i].SetActive(true);
}
}
}
}
public void Selected()
{
if (isSelect) {
//
PlayerPrefs.SetString("nowLevel","level" +gameObject.name);
SceneManager.LoadScene(2);
}
}
}
脚本都挂载在每一关的对象上,也是通过PlayerPrefs.set/get获取本地的信息判断是否选择和激活对象。
四、游戏场景(02_game)
核心组件
Spring Joint 2D————用来挂载鸟身上,模拟弹簧效果
Enable Collision—— 与这个关节连接的两个刚体是否应该相互碰撞 ?
Connected Rigid B ——连接另一个物体(这里我连得是我的右目标点)
Auchor Configure Connect——是否自动配置连接的锚以匹配世界空间中的锚?
Anchor——与本物体相连的接点的位置坐标(本物体的锚点)
Connected Anchor——连接除本物体外的另一个物体的连接点位置坐标(锚点)
Auto Configure Distance——锚点之间的距离是否自动计算?
Distance——关节点的位置, 弹簧试图保持两个物体之间的距离
Damping Ratio—— 弹簧力与移动速度成比例减少的量
Frequency—— 弹簧围绕物体之间的距离摆动的频率
Break Force—— 需要施加此关节断裂的力
Line Renderer组件————用来模拟皮带效果(一般用于3D)
Materials:线的材质。
Loop:是否要形成环。
Width:线的宽度。
Color:渐变颜色。
五、核心代码
public class Bird : MonoBehaviour {
private bool isClick = false;
public float maxDis = 3;
[HideInInspector]
public SpringJoint2D sp;
protected Rigidbody2D rg;
public LineRenderer right;
public Transform rightPos;
public LineRenderer left;
public Transform leftPos;
public GameObject boom;
protected TestMyTrail myTrail;
[HideInInspector]
public bool canMove = false;
public float smooth = 3;
public AudioClip select;
public AudioClip fly;
private bool isFly = false;
[HideInInspector]
public bool isReleased = false;//是否释放了小鸟
public Sprite hurt;
protected SpriteRenderer render;
private void Awake()
{
sp = GetComponent<SpringJoint2D>();
rg = GetComponent<Rigidbody2D>();
myTrail = GetComponent<TestMyTrail>();
render = GetComponent<SpriteRenderer>();
}
private void OnMouseDown()//鼠标按下
{
if (canMove)
{
AudioPlay(select);
isClick = true;
rg.isKinematic = true;
}
}
private void OnMouseUp() //鼠标抬起
{
if (canMove)
{
isClick = false;
rg.isKinematic = false;
Invoke("Fly", 0.1f);
//禁用划线组件
right.enabled = false;
left.enabled = false;
canMove = false;
}
}
private void Update()
{
if (EventSystem.current.IsPointerOverGameObject())//判断是否点击到了UI
return;
if (isClick) {//鼠标一直按下,进行位置的跟随
transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
//transform.position += new Vector3(0, 0, 10);
transform.position += new Vector3(0,0,-Camera.main.transform.position.z);
if (Vector3.Distance(transform.position, rightPos.position) > maxDis) { //进行位置限定
Vector3 pos = (transform.position - rightPos.position).normalized;//单位化向量
pos *= maxDis;//最大长度的向量
transform.position = pos + rightPos.position;
}
Line();
}
//相机跟随
float posX = transform.position.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);
if (isFly) {
if (Input.GetMouseButtonDown(0)) {
ShowSkill();
}
}
}
void Fly()
{
isReleased = true;
isFly = true;
AudioPlay(fly);
myTrail.StartTrails();
sp.enabled = false;
Invoke("Next", 5);
}
/// <summary>
/// 划线
/// </summary>
void Line()
{
right.enabled = true;
left.enabled = true;
right.SetPosition(0, rightPos.position);
right.SetPosition(1, transform.position);
left.SetPosition(0, leftPos.position);
left.SetPosition(1, transform.position);
}
/// <summary>
/// 下一只小鸟的飞出
/// </summary>
///
protected virtual void Next()
{
GameManager._instance.birds.Remove(this);
Destroy(gameObject);
Instantiate(boom, transform.position, Quaternion.identity);
GameManager._instance.NextBird();
}
private void OnCollisionEnter2D(Collision2D collision)
{
isFly = false;
myTrail.ClearTrails();
}
public void AudioPlay(AudioClip clip) {
AudioSource.PlayClipAtPoint(clip,transform.position);
}
/// <summary>
/// 炫技操作
/// </summary>
public virtual void ShowSkill() {
isFly = false;
}
public void Hurt() {
render.sprite = hurt;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
public List<Bird> birds;
public List<Pig> pig;
public static GameManager _instance;
private Vector3 originPos; //初始化的位置
public GameObject win;
public GameObject lose;
public GameObject[] stars;
private int starsNum = 0;
private int totalNum = 9;
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++)
{
if (i == 0) //第一只小鸟
{
birds[i].transform.position = originPos;
birds[i].enabled = true;
birds[i].sp.enabled = true;
birds[i].canMove = true;
}
else
{
birds[i].enabled = false;
birds[i].sp.enabled = false;
birds[i].canMove = false;
}
}
}
/// <summary>
/// 判定游戏逻辑
/// </summary>
public void NextBird()
{
if(pig.Count > 0)
{
if(birds.Count > 0)
{
//下一只飞吧
Initialized();
}
else
{
//输了
lose.SetActive(true);
}
}
else
{
//赢了
win.SetActive(true);
}
}
public void ShowStars() {
StartCoroutine("show");
}
IEnumerator show() {
for (; starsNum < birds.Count + 1; starsNum++)
{
if (starsNum >= stars.Length) {
break;
}
yield return new WaitForSeconds(0.2f);
stars[starsNum].SetActive(true);
}
print(starsNum);
}
public void Replay() {
SaveData();
SceneManager.LoadScene(2);
}
public void Home() {
SaveData();
SceneManager.LoadScene(1);
}
public void SaveData() {
if (starsNum > PlayerPrefs.GetInt(PlayerPrefs.GetString("nowLevel"))){
PlayerPrefs.SetInt(PlayerPrefs.GetString("nowLevel"), starsNum);
}
//存储所有的星星个数
int sum = 0;
for (int i = 1; i <= totalNum; i++) {
sum += PlayerPrefs.GetInt("level" + i.ToString());
}
PlayerPrefs.SetInt("totalNum",sum);
print(PlayerPrefs.GetInt("totalNum"));
}
}
主要由GameManager控制小鸟“排队"的问题,Bird再从中获取单例的数据,决定什么时候可以移动什么时候激活刚体,什么时候可以发射等操作。
猪和建筑物的脚本主要是受伤和死亡功能,通过刚体触发完成效果。
六、总结
1.愤怒的小鸟对逻辑结构还是有考验的,通过这个案例也认识到许多组件,比如弹簧、划线等之前了解甚少的组件,也加深了我的认知。
2.在脚本方面认识 到了PlayerPrefs和EventSystem下子类的功能和用法,脚本方面也认识到需要加强的很多,不会的很多,参考了许多视频(Siki),继续努力吧。