游戏的存档与读档

游戏的存档与读档

素材资源及源码链接:素材资源及源码链接

第一部分:游戏基本功能的实现(场景的搭建、碰撞器、协程的使用、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)运行后点击“保存游戏”,提示“保存成功”
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_46649692/article/details/116202375
今日推荐