游戏的存档与读档
素材资源及源码链接:素材资源及源码链接
第一部分:游戏基本功能的实现(场景的搭建、碰撞器、协程的使用、UGUI)
第二部分:游戏的存档与读档功能的实现(PlayerPrefs、XML、JSON)
一、游戏基本功能的实现
(一)场景的搭建
没啥技术含量,对比着搭建即可。
(二)碰撞器
(1)添加射击目标Target(也就是一个Cube。由于我们只需要它的一个坐标位置,所以移除它的Mesh Renderer和Box Collider组件)。添加一个Bat蝙蝠进去,修改其大小、位置,并给它添加Animation组件和Box Collider组件(编辑Box Collider组件时,需要调节其大小及合适位置,要选中Edit Collider才能进行大小的改变),如下图所示。
(2)添加Ghost,Rabbit,Slime道理同上。
最终效果如下图所示:
(三)协程的使用
1、控制怪物的随机生成
通过添加TargetManager脚本控制怪物的生成。
(1)首先通过数组获得4个怪物:public GameObject[] monsters;
(2)随机激活怪物函数
记得前面要添加:
public GameObject activeMonster = null; //目前激活状态的一个怪物
private void ActivateMonster()
{
int index = Random.Range(0, monsters.Length); //0—3
activeMonster = monsters[index];
activeMonster.SetActive(true); //激活状态为真,激活游戏物体是用SetActive(true)
activeMonster.GetComponent<BoxCollider>().enabled = true; //激活组件使用enabled = true
}
(3)在Start()方法里调用
void Start()
{
foreach (GameObject monster in monsters)
{
monster.GetComponent<BoxCollider>().enabled = false; //(保险起见,防止一开始有怪物没有隐藏)
monster.SetActive(false); //最开始隐藏掉所有怪物
}
ActivateMonster();
}
(4)随机生成怪物效果图
2、使用协程控制怪物的生命周期
协程(Coroutines),是一个分部执行,遇到条件(yield return语句)时会挂起,直到条件满足才会被唤醒继续执行后面的代码。
(1)生成1—4之间的随机时长
IEnumerator AliveTimer()
{
yield return new WaitForSeconds(Random.Range(1, 5)); //1-4之间的随机时长
ActivateMonster();
}
(2)此时,需要修改Start(0方法里的调用函数
void Start()
{
foreach (GameObject monster in monsters)
{
monster.GetComponent<BoxCollider>().enabled = false; //(保险起见,防止一开始有怪物没有隐藏)
monster.SetActive(false); //最开始隐藏掉所有怪物
}
//ActivateMonster();
StartCoroutine("AliveTimer"); 调用协程,会有一个延迟的生成效果
}
3、控制怪物的死亡
1—4秒怪物生成后,为了让怪物不停地转换,设置一个生命周期,比如说过了3秒后让它消失,再等待一段时间生成新的一个怪物。
(1)使激活状态的怪物变为未激活状态
/// <summary>
/// 使激活状态的怪物变为未激活状态
/// </summary>
private void DelActivateMonster()
{
if (activeMonster != null)
{
activeMonster.GetComponent<BoxCollider>().enabled = false;
activeMonster.SetActive(false);
activeMonster = null;
}
StartCoroutine("AliveTimer");
}
(2)迭代器,设置死亡的等待时间
/// <summary>
/// 迭代器,设置死亡的等待时间
/// </summary>
/// <returns></returns>
IEnumerator DeathTimer()
{
yield return new WaitForSeconds(Random.Range(3, 8)); //等待3—7秒
DelActivateMonster();
}
(3)调用
private void ActivateMonster()
{
int index = Random.Range(0, monsters.Length);//0—3
activeMonster = monsters[index];
activeMonster.SetActive(true);//激活状态为真,激活游戏物体是用SetActive(true)
activeMonster.GetComponent<BoxCollider>().enabled = true;//激活组件使用enabled = true
StartCoroutine("DeathTimer"); //调用
}
到这,就实现了怪物的一个生命周期。
4、设置手枪的旋转
随着鼠标的移动去改变手枪的旋转。然后再去按鼠标左键发射子弹,打到我们的怪物,怪物就会消失。添加GunManager脚本。
public class GunManager : MonoBehaviour
{
// Start is called before the first frame update
private float maxYRotation = 120;
private float minYRotation = 0;
private float maxXRotation = 80;
private float minXRotation = 0;
private float shootTime = 1;
private float shootTimer = 0; //计时器
void Start()
{
}
// Update is called once per frame
void Update()
{
shootTimer += Time.deltaTime;
if (shootTimer >= shootTime)
{
//TODO:可以射击
}
float xPosPrecent = Input.mousePosition.x / Screen.width;
float yPosPrecent = Input.mousePosition.y / Screen.height;
float xAngle = -Mathf.Clamp(yPosPrecent * maxXRotation, minXRotation, maxXRotation) + 15;
float yAngle = Mathf.Clamp(xPosPrecent * maxYRotation, minYRotation, maxYRotation) - 60;
transform.eulerAngles = new Vector3(xAngle, yAngle, 0);
}
}
5、控制子弹的生成
(1)首先要得到一颗子弹,于是,创建一个3D Object | Sphere(修改其名称为Bullet)。新建一个材质Bullet,
(2)回到GunManager代码中,添加public GameObject bulletGO;和public Transform firePosition;,回到Unity3D中,拖进来。之后就可以通过代码生成子弹了。
(3)通过代码控制子弹生成
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunManager : MonoBehaviour
{
// Start is called before the first frame update
//最大和最小的x、y轴旋转角度
private float maxYRotation = 120;
private float minYRotation = 0;
private float maxXRotation = 80;
private float minXRotation = 0;
private float shootTime = 1; //射击的间隔时长
private float shootTimer = 0; //计时器
//子弹的游戏物体和子弹的生成位置
public GameObject bulletGO; //GO—GameObject
public Transform firePosition;
void Start()
{
}
// Update is called once per frame
void Update()
{
shootTimer += Time.deltaTime;
if (shootTimer >= shootTime)
{
if (Input.GetMouseButtonDown(0))
{
GameObject bulletCurrent = GameObject.Instantiate(bulletGO, firePosition.position, Quaternion.identity);
}
}
//根据鼠标在屏幕上的位置,去相对应的旋转手枪
float xPosPrecent = Input.mousePosition.x / Screen.width;
float yPosPrecent = Input.mousePosition.y / Screen.height;
float xAngle = -Mathf.Clamp(yPosPrecent * maxXRotation, minXRotation, maxXRotation) + 15;
float yAngle = Mathf.Clamp(xPosPrecent * maxYRotation, minYRotation, maxYRotation) - 60;
transform.eulerAngles = new Vector3(xAngle, yAngle, 0);
}
}
(4)还可添加刚体组件,让子弹受重力下落。注意:要把Prefabs里面的Bullet勾选上,不能隐藏。Target下面的Bullet要隐藏。
(5)子弹生成效果图
6、控制子弹的移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunManager : MonoBehaviour
{
// Start is called before the first frame update
//最大和最小的x、y轴旋转角度
private float maxYRotation = 120;
private float minYRotation = 0;
private float maxXRotation = 80;
private float minXRotation = 0;
private float shootTime = 1; //射击的间隔时长
private float shootTimer = 0; //计时器
//子弹的游戏物体和子弹的生成位置
public GameObject bulletGO; //GO—GameObject
public Transform firePosition;
void Start()
{
}
// Update is called once per frame
void Update()
{
shootTimer += Time.deltaTime;
if (shootTimer >= shootTime)
{
//点击鼠标左键进行射击
if (Input.GetMouseButtonDown(0))
{
//实例化子弹
GameObject bulletCurrent = GameObject.Instantiate(bulletGO, firePosition.position, Quaternion.identity);
//通过刚体组件给子弹添加一个正前方向的力,以达到让子弹向前运动的效果
bulletCurrent.GetComponent<Rigidbody>().AddForce(transform.forward * 2000);
//播放手枪开火的动画
gameObject.GetComponent<Animation>().Play();
shootTimer = 0;
}
}
//根据鼠标在屏幕上的位置,去相对应的旋转手枪
float xPosPrecent = Input.mousePosition.x / Screen.width;
float yPosPrecent = Input.mousePosition.y / Screen.height;
float xAngle = -Mathf.Clamp(yPosPrecent * maxXRotation, minXRotation, maxXRotation) + 15;
float yAngle = Mathf.Clamp(xPosPrecent * maxYRotation, minYRotation, maxYRotation) - 60;
transform.eulerAngles = new Vector3(xAngle, yAngle, 0);
}
}
7、设置手枪的动画与子弹的自动销毁
(1)首先取消自动播放动画勾选
(2)给Bullet添加BulletManager脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
StartCoroutine("DestroySelf");
}
IEnumerator DestroySelf()
{
//等待1秒后销毁自身
yield return new WaitForSeconds(1);
Destroy(this.gameObject);
}
// Update is called once per frame
void Update()
{
}
}
(3)Apply All
8、子弹与怪物的碰撞
(1)添加标签
(2)在Target的4个物体下,挂上MonsterManager脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterManager : MonoBehaviour
{
// Start is called before the first frame update
private Animation anim;
//定义了Idle和Die状态的动画
public AnimationClip idleClip;
public AnimationClip dieClip;
private void Awake()
{
//获得动画组件
anim = gameObject.GetComponent<Animation>();
anim.clip = idleClip;
}
//当检测到碰撞时
private void OnCollisionEnter(Collision collision)
{
//如果碰撞到的物体标签为Bullet,就销毁它
if (collision.collider.tag == "Bullet")
{
Destroy(collision.collider.gameObject);
//把默认的动画改为死亡动画,并播放
anim.clip = dieClip;
anim.Play();
}
}
//当怪物被Disable的时候,将默认的动画修改为idle动画
private void OnDisable()
{
anim.clip = idleClip;
}
}
(3)分别给4个怪物拖拽对应的动画
9、怪物的死亡与刷新
(1)在MonsterManager脚本中
private void OnDisable()
{
anim.clip = idleClip;
}
IEnumerator Delactivate()
{
yield return new WaitForSeconds(0.8f);
//使当前的怪物变为未激活状态,并使整个循环重新开始
TargetManager._instance.UpdateMonsters(); //与TargetManager脚本交互(单体模式)
}
(2)在TargetManager 脚本中
public static TargetManager _instance; //单体模式
public GameObject[] monsters; //首先通过数组获得4个怪物
public GameObject activeMonster = null;//现在目前激活状态的一个怪物
private void Awake()
{
_instance = this;
}
IEnumerator DeathTimer()
{
yield return new WaitForSeconds(Random.Range(3, 8)); //等待3—7秒
DelActivateMonster();
}
//更新生命周期。当子弹击中怪物时,停止所有的协程
//将当前处于激活状态的怪物变为未激活状态,清空activeMonster
//重新开始AliveTimer的协程(随机激活怪物)
public void UpdateMonsters()
{
if (activeMonster != null)
{
StopAllCoroutines();
activeMonster.SetActive(false);
activeMonster = null;
StartCoroutine("AliveTimer");
}
}
(四)UGUI
1、制作统计得分的UI
(1)添加一个画布
(2)设计效果图
(3)添加UIManager脚本挂载在UI上,控制UI的一些显示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public Text shootNumText;
public Text scoreText;
}
(4)紧接着,回到Unity3D中,给shootNumText,scoreText赋值。
(5)完善UIManager 代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public static UIManager _instance;
//获得2个Text组件
public Text shootNumText;
public Text scoreText;
public int shootNum = 0;
public int score = 0;
private void Awake()
{
_instance = this;
}
private void Update()
{
//更新Text组件的显示内容
shootNumText.text = shootNum.ToString();
scoreText.text = score.ToString();
}
//增加射击数(当开枪时)
public void AddShootNum()
{
shootNum += 1;
}
//增加得分(当射中怪物时)
public void AddScore()
{
score += 1;
}
}
(6)调用
(7)实现效果图
2、制作菜单UI
(1)首先在UI下面创建一个UI | Panel(将其重命名为Menu),来放置我们其他所有的Menu。可以稍微将其Color修改为淡淡的灰色。
(2)然后在Menu下面创建其他的UI,首先创建一个Image(背景的板),重命名为Background,修改其颜色,做成一个浅灰色的。然后去Scene视图中修改其大小。
(3)再去创建Button。添加第一个Button,重命名为NewGame,在其Text里写上“新游戏”,将其拖到顶部合适位置(还可修改Button的颜色以及字体大小)。其他的按钮制作同理,不再详细赘述。
(4)添加一个音效的单选框Toggle。具体制作过程也很简单,不赘述。
(5)最终菜单UI设计如下图所示。
3、暂停游戏和继续游戏
(1)首先创建一个GameObject,记得Reset。用它去控制所有游戏状态的转变,还有之后保存游戏、加载游戏也可以放在这个里面去做。
(2)在GameObject中挂载GameManager 脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager _instance;
public bool isPaused = true; //是否是暂停状态
public GameObject menuGO;
private void Awake()
{
_instance = this;
Pause(); //游戏开始时是暂停的状态
}
private void Update()
{
//判断是否按下Esc键,按下的话,调出Menu菜单,并将游戏状态更改为暂停状态
if (Input.GetKeyDown(KeyCode.Escape))
{
Pause();
}
}
//暂停状态
private void Pause()
{
isPaused = true;
menuGO.SetActive(true);
Time.timeScale = 0; //让后面的所有动作停止掉(时间的缩放比例,0就是暂停,1就是正常状态)
Cursor.visible = true; //鼠标可见
}
//非暂停状态
private void UnPause()
{
isPaused = false;
menuGO.SetActive(false);
Time.timeScale = 1;
Cursor.visible = false; //鼠标不可见
}
//从暂停状态恢复到非暂停状态
public void ContinueGame()
{
UnPause();
}
}
(3)回到Unity3D中,给按钮注册一下。
(4)更改枪的状态(刚开始枪是不可以旋转的)
将GameManager设置为单体模式:
public static GameManager _instance;
然后在GunManager里面,添加一个条件:
if (GameManager._instance.isPaused == false)
{
……
}
void Update()
{
//游戏是非暂停状态时才可以进行射击,并且随着鼠标旋转
if (GameManager._instance.isPaused == false)
{
shootTimer += Time.deltaTime;
if (shootTimer >= shootTime)
{
//点击鼠标左键进行射击
if (Input.GetMouseButtonDown(0))
{
//实例化子弹
GameObject bulletCurrent = GameObject.Instantiate(bulletGO, firePosition.position, Quaternion.identity);
//通过刚体组件给子弹添加一个正前方向的力,以达到让子弹向前运动的效果
bulletCurrent.GetComponent<Rigidbody>().AddForce(transform.forward * 2000);
//播放手枪开火的动画
gameObject.GetComponent<Animation>().Play();
shootTimer = 0;
UIManager._instance.AddShootNum(); //增加射击数
}
}
//根据鼠标在屏幕上的位置,去相对应的旋转手枪
float xPosPrecent = Input.mousePosition.x / Screen.width;
float yPosPrecent = Input.mousePosition.y / Screen.height;
float xAngle = -Mathf.Clamp(yPosPrecent * maxXRotation, minXRotation, maxXRotation) + 15;
float yAngle = Mathf.Clamp(xPosPrecent * maxYRotation, minYRotation, maxYRotation) - 60;
transform.eulerAngles = new Vector3(xAngle, yAngle, 0);
}
}
4、新游戏和退出游戏
(1)创建一个空物体GameObject(记得Reset一下),将其重命名为Targets,来存放我们的Target。将之前的Target拖进Targets,并复制8份,放在其他的空格里。
(2)在TargetManager里面,添加代码
public void UpdateMonsters()
{
StopAllCoroutines();
if (activeMonster != null)
{
activeMonster.SetActive(false);
activeMonster = null;
}
StartCoroutine("AliveTimer");
}
(3)回到GameManager,添加public GameObject[] targetGOs;。
public void ContinueGame()
{
UnPause();
}
public void NewGame()
{
foreach (GameObject targetGO in targetGOs)
{
targetGO.GetComponent<TargetManager>().UpdateMonsters();
}
UIManager._instance.shootNum = 0;
UIManager._instance.score = 0;
UnPause();
}
(4)回到Unity3D,将9个Target拖入TargetGOs中,并在Menu | NewGame中注册NewGame方法。
(5)退出游戏(在GameManager脚本中)
public void QuitGame()
{
Application.Quit(); //在游戏发布时才好使
}
(6)回到Unity3D,在Menu | QuitGame中注册QuitGame方法。
(7)很重要的一点就是,做完UI界面后要设置一下它的锚点。
(8)File | Build Settings… | Build And Run
5、添加音效
为了让游戏显得不那么单调,可以给它添加音效。
将背景音乐放在GameManager下面,添加Audio Source组件,找到素材里面的music,将它拖进来。勾选Play On Awake一开始就播放和Loop循环。
再去添加手枪和子弹的音效。
(1)手枪音效
取消Play On Awake的勾选,因为我们只有在点击鼠标左键时才开火。每次只播放一次,所以Loop也不用勾选。
(2)子弹音效
添加一个Audio Source组件在Targets下面,这个音效是子弹与怪物撞击时的音效。
(3)首先在GunManager脚本里,最前面添加private AudioSource gunAudio; //播放枪的声音
然后在Awake()中添加如下代码:
private void Awake()
{
gunAudio = gameObject.GetComponent<AudioSource>();
}
最后在Update()中添加播放手枪开火的音效:
if (Input.GetMouseButtonDown(0))
{
//实例化子弹
GameObject bulletCurrent = GameObject.Instantiate(bulletGO, firePosition.position, Quaternion.identity);
//通过刚体组件给子弹添加一个正前方向的力,以达到让子弹向前运动的效果
bulletCurrent.GetComponent<Rigidbody>().AddForce(transform.forward * 2000);
//播放手枪开火的动画
gameObject.GetComponent<Animation>().Play();
shootTimer = 0;
gunAudio.Play(); //播放手枪开火的音效
UIManager._instance.AddShootNum(); //增加射击数
}
(4)在MonsterManager脚本中,写子弹与怪物碰撞时的音效。
首先, 在最前面添加:public AudioSource kickAudio; //子弹与怪物撞击时的音效。通过拖拽的方式给它赋值。
由于之后还要保存游戏、加载游戏,所以可以先把Target(1)—Target(8)删掉。同时记得把GameManager中TargetGOs里的Size从之前的9改为现在的1。
然后,在MonsterManager脚本中,添加相应代码。
private void OnCollisionEnter(Collision collision)
{
//如果碰撞到的物体标签为Bullet,就销毁它
if (collision.collider.tag == "Bullet")
{
Destroy(collision.collider.gameObject);
//播放撞击音效
kickAudio.Play();
//把默认的动画改为死亡动画,并播放
anim.clip = dieClip;
anim.Play();
gameObject.GetComponent<BoxCollider>().enabled = false;
StartCoroutine("Delactivate");
UIManager._instance.AddScore(); //增加得分
}
}
6、控制背景音乐的开关
(1)首先在UIManager的最前面添加:
//音乐开关的单选框和播放背景音乐的AudioSource
public Toggle musicToggle;
public AudioSource musicAudio;
(2)回到Unity3D中,给musicToggle,musicAudio赋一下值。
(3)添加控制音效的代码。
public void MusicSwitch()
{
//通过判断单选框是否被勾选上,从而来决定是否播放背景音乐
if (musicToggle.isOn == false)
{
musicAudio.enabled = false;
}
else
{
musicAudio.enabled = true;
}
}
(4)回到Unity3D,将事件注册一下。
二、游戏的存档与读档功能的实现
(一)存档相关概念介绍
1、PlayerPrefs——数据持久化方案
2、二进制方法 && XML && JSON
(二)存档与读档功能的实现
1、存储背景音乐的开关状态
UIManager 脚本中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public static UIManager _instance;
//获得2个Text组件
public Text shootNumText;
public Text scoreText;
public int shootNum = 0;
public int score = 0;
//音乐开关的单选框和播放背景音乐的AudioSource
public Toggle musicToggle;
public AudioSource musicAudio;
private void Awake()
{
_instance = this;
if (PlayerPrefs.HasKey("MusicOn"))
{
if (PlayerPrefs.GetInt("MusicOn") == 1)
{
musicToggle.isOn = true;
musicAudio.enabled = true;
}
else
{
musicToggle.isOn = false;
musicAudio.enabled = false;
}
}
else
{
musicToggle.isOn = true;
musicAudio.enabled = true;
}
}
private void Update()
{
//更新Text组件的显示内容
shootNumText.text = shootNum.ToString();
scoreText.text = score.ToString();
}
public void MusicSwitch()
{
//通过判断单选框是否被勾选上,从而来决定是否播放背景音乐
if (musicToggle.isOn == false)
{
musicAudio.enabled = false;
PlayerPrefs.SetInt("MusicOn", 0);
}
else
{
musicAudio.enabled = true;
PlayerPrefs.SetInt("MusicOn", 1); //保存音乐开关的状态,0代表关闭,1代表开启
}
PlayerPrefs.Save();
}
//增加射击数(当开枪时)
public void AddShootNum()
{
shootNum += 1;
}
//增加得分(当射中怪物时)
public void AddScore()
{
score += 1;
}
}
2、设置Target位置和怪物类型
(1)在TargetManager脚本里,给它添加:
public int targetPosition; //每个target目标所在的位置(0—8)(等下可直接去层级面板给它赋值)
(2)在MonsterManager脚本里,也需要给它这样的一个值:
public int monsterType; //0—3
(3)TargetManager 脚本中代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetManager : MonoBehaviour
{
// Start is called before the first frame update
public static TargetManager _instance;
public GameObject[] monsters; //首先通过数组获得4个怪物
public GameObject activeMonster = null;//现在目前激活状态的一个怪物
public int targetPosition; //每个target目标所在的位置(0—8)(等下可直接去层级面板给它赋值)
private void Awake()
{
_instance = this;
}
void Start()
{
foreach (GameObject monster in monsters)
{
monster.GetComponent<BoxCollider>().enabled = false;//(保险起见,防止一开始有怪物没有隐藏)
monster.SetActive(false);//最开始隐藏掉所有怪物
}
//ActivateMonster();
StartCoroutine("AliveTimer"); //调用协程
}
/// <summary>
/// 随机生成怪物函数
/// </summary>
private void ActivateMonster()
{
int index = Random.Range(0, monsters.Length);//0—3
activeMonster = monsters[index];
activeMonster.SetActive(true);//激活状态为真,激活游戏物体是用SetActive(true)
activeMonster.GetComponent<BoxCollider>().enabled = true;//激活组件使用enabled = true
StartCoroutine("DeathTimer"); //调用死亡时间的协程
}
/// <summary>
/// 迭代器方法,设置生成的等待时间
/// </summary>
/// <returns></returns>
IEnumerator AliveTimer()
{
yield return new WaitForSeconds(Random.Range(1, 5));//1-4之间的随机时长
ActivateMonster();
}
/// <summary>
/// 使激活状态的怪物变为未激活状态
/// </summary>
private void DelActivateMonster()
{
if (activeMonster != null)
{
activeMonster.GetComponent<BoxCollider>().enabled = false;
activeMonster.SetActive(false);
activeMonster = null;
}
StartCoroutine("AliveTimer"); //调用激活时间的协程,达到一个反复激活和死亡的循环
}
/// <summary>
/// 迭代器,设置死亡的等待时间
/// </summary>
/// <returns></returns>
IEnumerator DeathTimer()
{
yield return new WaitForSeconds(Random.Range(3, 8)); //等待3—7秒
DelActivateMonster();
}
//更新生命周期。当子弹击中怪物时,或者当重新开始游戏时
//停止所有的协程
//将当前处于激活状态的怪物变为未激活状态,清空activeMonster
//重新开始AliveTimer的协程(随机激活怪物)
public void UpdateMonsters()
{
StopAllCoroutines();
if (activeMonster != null)
{
activeMonster.GetComponent<BoxCollider>().enabled = false;
activeMonster.SetActive(false);
activeMonster = null;
}
StartCoroutine("AliveTimer");
}
//按照给定的怪物类型激活怪物
//停止所有协程
//将当前激活状态的怪物(如果有的话),转变为未激活状态
//激活给定类型的怪物
//调用死亡时间的协程
public void ActivateMonsterByType(int type)
{
StopAllCoroutines();
if (activeMonster != null)
{
activeMonster.SetActive(false);
activeMonster = null;
}
activeMonster = monsters[type];
activeMonster.SetActive(true);
activeMonster.GetComponent<BoxCollider>().enabled = true;
StartCoroutine("DeathTimer");
}
}
3、复制Target并完善
(1)复制Target
(2)删除下图中的单体模式,并在MonsterManager脚本里重新做一些修改
(3)在MonsterManager脚本里,做一些修改。
IEnumerator Delactivate()
{
yield return new WaitForSeconds(0.8f);
//使当前的怪物变为未激活状态,并使整个循环重新开始
gameObject.GetComponentInParent<TargetManager>().UpdateMonsters();
}
(4)可以给怪物换其他的颜色,根据自己喜好来。
4、创建Save保存类
(1)首先创建Save脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Save : MonoBehaviour
{
public List<int> livingTargetPositions = new List<int>();
public List<int> livingMonsterType = new List<int>();
public int shootNum = 0;
public int score = 0;
}
(2)在GameManager 脚本中添加对应代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager _instance;
public bool isPaused = true; //是否是暂停状态
public GameObject menuGO;
public GameObject[] targetGOs;
private void Awake()
{
_instance = this;
Pause(); //游戏开始时是暂停的状态
}
private void Update()
{
//判断是否按下Esc键,按下的话,调出Menu菜单,并将游戏状态更改为暂停状态
if (Input.GetKeyDown(KeyCode.Escape))
{
Pause();
}
}
//暂停状态
private void Pause()
{
isPaused = true;
menuGO.SetActive(true);
Time.timeScale = 0; //让后面的所有动作停止掉(时间的缩放比例,0就是暂停,1就是正常状态)
Cursor.visible = true; //鼠标可见
}
//非暂停状态
private void UnPause()
{
isPaused = false;
menuGO.SetActive(false);
Time.timeScale = 1;
Cursor.visible = false; //鼠标不可见
}
//创建Save对象并存储当前游戏状态信息
private Save CreateSaveGO()
{
//新建Save对象
Save save = new Save();
//遍历所有的target
//如果其中有处于激活状态的怪物,就把该target的位置信息和激活状态的怪物类型添加到List中
foreach (GameObject targetGO in targetGOs)
{
TargetManager targetManager = targetGO.GetComponent<TargetManager>();
if (targetManager.activeMonster != null)
{
save.livingTargetPositions.Add(targetManager.targetPosition);
int type = targetManager.activeMonster.GetComponent<MonsterManager>().monsterType;
save.livingMonsterType.Add(type);
}
}
//把shootNum和score保存在Save对象中
save.shootNum = UIManager._instance.shootNum;
save.score = UIManager._instance.score;
//返回该Save对象
return save;
}
//从暂停状态恢复到非暂停状态
public void ContinueGame()
{
UnPause();
}
public void NewGame()
{
foreach (GameObject targetGO in targetGOs)
{
targetGO.GetComponent<TargetManager>().UpdateMonsters();
}
UIManager._instance.shootNum = 0;
UIManager._instance.score = 0;
UnPause();
}
public void QuitGame()
{
Application.Quit();
}
}
5、保存游戏(二进制方法)
(1)首先在GameManager脚本里写两个方法
//保存游戏
public void SaveGame()
{
}
//加载游戏
public void LoadGame()
{
}
(2)回到Unity3D,给这两个方法注册一下
(3)可以先在GameManager脚本中把存档和读档的三种方法写出来
//二进制方法:存档和读档
private void SaveByBin()
{
}
private void LoadByBin()
{
}
//XML:存档和读档
private void SaveByXml()
{
}
private void LoadByXml()
{
}
//JSON:存档和读档
private void SaveByJson()
{
}
private void LoadByJson()
{
}
(4)在GameManager脚本中,首先引入两个命名空间:
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
然后补充完整二进制保存游戏的方法代码:
private void SaveByBin()
{
//序列化过程(将Save对象转换为字节流)
//创建Save对象并保存当前游戏状态
Save save = CreateSaveGO();
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//创建一个文件流
FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/byBin.txt");
//用二进制格式化程序的序列化方法来序列化Save对象,参数:创建的文件流和需要序列化的对象
bf.Serialize(fileStream, save);
//关闭流
fileStream.Close();
}
最后在SaveGame中调用:
public void SaveGame()
{
SaveByBin();
}
(5)运行游戏后,点击“保存游戏”按钮,在StreamingFile文件夹下就会保存二进制文本内容
6、读取游戏(二进制方法)
(1)在UI下面创建一个UI | Text(重命名为Message),用来提示
(2)在UIManager脚本中,前面写上:
public Text messageText;
后面重新写一个方法:
public void ShowMessage(string str)
{
messageText.text = str;
}
(3)在GameManager脚本中,SaveByBin()后面添加条件判断:
private void SaveByBin()
{
//序列化过程(将Save对象转换为字节流)
//创建Save对象并保存当前游戏状态
Save save = CreateSaveGO();
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//创建一个文件流
FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/byBin.txt");
//用二进制格式化程序的序列化方法来序列化Save对象,参数:创建的文件流和需要序列化的对象
bf.Serialize(fileStream, save);
//关闭流
fileStream.Close();
if (File.Exists(Application.dataPath + "/StreamingFile" + "/byBin.txt"))
{
UIManager._instance.ShowMessage("保存成功");
}
}
同时在ContinueGame(),NewGame(),LoadGame()中加上:
UIManager._instance.ShowMessage("");
public void ContinueGame()
{
UnPause();
UIManager._instance.ShowMessage("");
}
//重新开始游戏
public void NewGame()
{
foreach (GameObject targetGO in targetGOs)
{
targetGO.GetComponent<TargetManager>().UpdateMonsters();
}
UIManager._instance.shootNum = 0;
UIManager._instance.score = 0;
UIManager._instance.ShowMessage("");
UnPause();
}
//加载游戏
public void LoadGame()
{
UIManager._instance.ShowMessage("");
}
(4)回到Unity3D,将Message拖入Message Text中
(5)运行后点击“保存游戏”,提示“保存成功”