@作者 : SYFStrive
@博客首页 : HomePage
📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗
📌:觉得文章不错可以点点关注 👉:专栏连接🔗
💃:程序员每天坚持锻炼💪
👉 飞机大战专栏(🔥)
游戏单例脚本
单例模式是1种设计模式:👉(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
单例使用说明:“单例模式是指在内存中只会创建一次对象的设计模式,并且确保一个类只有实例,而且会自行实例化,并向整个系统提供这个实例。
非持久化泛型单例
using UnityEngine;
//摘要:Base class for everything attached to GameObjects.
//Component中文说明:所有能挂载到游戏对象上的类型基类
public class Singleton<T> : MonoBehaviour where T :Component
{
public static T Instance { get; private set; }
protected virtual void Awake()
{
Instance = this as T;
}
}
持久化泛型单例
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class PersistentSingleton<T> : MonoBehaviour where T : Component
{
public static T Instance;
protected virtual void Awake()
{
if(Instance == null)
Instance = this as T;
else
Destroy(this.gameObject);
DontDestroyOnLoad(this);
}
}
游戏基类
子弹基类实现子弹移动
实现:子弹生成是就开始移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Projectile : MonoBehaviour
{
//子弹的移动速度
[SerializeField] float moveSpeed;
//子弹的移动方向
[SerializeField] protected Vector3 moveDirection;
//子弹移动的Obj
protected GameObject targer;
protected virtual void OnEnable()
{
StartCoroutine(ProjectileMoveIE());
}
IEnumerator ProjectileMoveIE()
{
while (true)
{
//子弹移动
transform.position += moveSpeed * moveDirection * Time.deltaTime;
yield return null;
}
}
}
生命系统的基类
实现:储存人物的血量参数(继承这个脚本的简直爽歪歪)……
代码如 👇
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Principal;
using UnityEngine;
public class Characters : MonoBehaviour
{
[Header("---Header---")]
//最大生命值
[SerializeField] protected float maxHp;
//当前生命值
protected float currentHp;
//死亡时生成特效
[SerializeField] GameObject dieSpecialEffects;
protected virtual void OnEnable()
{
currentHp = maxHp;
}
/// <summary>
/// 玩家受伤
/// </summary>
/// <param name="injuredValue">伤害值</param>
protected virtual void Injured(float injuredValue)
{
currentHp -= injuredValue;
if (currentHp <= 0)
Die();
}
/// <summary>
/// 玩家死亡
/// </summary>
public void Die()
{
//血量归0
currentHp=0;
//调用对象池
PoolManager.Release(dieSpecialEffects,transform.position);
隐藏该对象
this.gameObject.SetActive(false);
}
/// <summary>
/// 恢复生命值
/// </summary>
protected virtual void RecoverHP(float value)
{
currentHp = Mathf.Clamp(currentHp + value, 0, maxHp);
}
/// <summary>
/// 自动恢复生命值携程
/// </summary>
/// <param name="waitForSeconds">恢复的间隔</param>
/// <param name="value">恢复值</param>
/// <returns></returns>
protected virtual IEnumerator SelfRecoverHpIE(WaitForSeconds waitForSeconds,float value)
{
while (currentHp < maxHp)
{
yield return waitForSeconds;
RecoverHP(currentHp * value);
}
}
/// <summary>
/// 持续受伤
/// </summary>
/// <param name="waitForSeconds">受伤的间隔</param>
/// <param name="value">受伤值</param>
/// <returns></returns>
protected virtual IEnumerator SelfInjuredIE(WaitForSeconds waitForSeconds, float value)
{
while (currentHp >= 0f)
{
yield return waitForSeconds;
Die(currentHp * value);
}
}
}
对象池管理器
说明:这里已经添加了这个项目所有的对象池容器
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
//储存不同类准备的对象池
[SerializeField] Pool[] playerPoolProjectile; //玩家子弹
[SerializeField] Pool[] enemyPoolProjectile; //敌人子弹
[SerializeField] Pool[] poolVFX; //特效
[SerializeField] Pool[] randomCreateEnemy; //随机敌人
[SerializeField] Pool[] createProp; 敌人掉落的道具
//使用字典来存储不同的装备
public static Dictionary<GameObject, Pool> dictionary;
private void Awake()
{
//实例化字典
dictionary = new Dictionary<GameObject, Pool>();
//初始化对象池
InitializeObj(playerPoolProjectile);
InitializeObj(enemyPoolProjectile);
InitializeObj(poolVFX);
InitializeObj(randomCreateEnemy);
InitializeObj(createProp);
}
#region 测试函数
#if UNITY_EDITOR
//停止游戏时执行
private void OnDestroy()
{
CheckPoolSize(playerPoolProjectile);
CheckPoolSize(enemyPoolProjectile);
CheckPoolSize(poolVFX);
CheckPoolSize(randomCreateEnemy);
CheckPoolSize(createProp);
}
#endif
#endregion
#region 测试需要对象池的容量
private void CheckPoolSize(Pool[] pools)
{
foreach (Pool pool in pools)
{
if (pool.sumSize > pool.initializeSize)
{
Debug.LogWarning(string.Format("Pool:{0}初始大小为{1},需要的大小为{2}",
pool.prefabeObjProperty.name,
pool.initializeSize,
pool.sumSize));
}
}
}
#endregion
/// <summary>
/// 初始化子弹
/// </summary>
private void InitializeObj(Pool[] pools)
{
foreach (var pool in pools)
{
#region //条件编译操作 只有在Unity引起运行
#if UNITY_EDITOR
if (dictionary.ContainsKey(pool.prefabeObjProperty))
{
Debug.Log("字典有相同的名字!"+pool.prefabeObjProperty.name);
continue;
}
#endif
#endregion
//添加到字典
dictionary.Add(pool.prefabeObjProperty, pool);
//给创建的Obj命名
Transform poolPatent = new GameObject("对象池Poll" + pool.prefabeObjProperty.name).transform;
//设置父位置
poolPatent.parent = transform;
//初始化对象池
pool.Initialize(poolPatent);
}
}
#region 释放子弹&&重载
/// <summary>
/// 释放子弹
/// </summary>
/// <param name="prefabe">指定游戏的预制体</param>
/// <returns></returns>
public static GameObject Release(GameObject prefabe)
{
#region 条件编译操作 只有在Unity引起运行
#if UNITY_EDITOR
if (!dictionary.ContainsKey(prefabe))
{
Debug.Log("找不到对应的Key");
return null;
}
#endif
#endregion
return dictionary[prefabe].PrepareQuene();
}
/// <summary>
/// 释放子弹
/// </summary>
/// <param name="prefabe">指定游戏的预制体</param>
/// <param name="position">指定游戏的位置</param>
/// <returns></returns>
public static GameObject Release(GameObject prefabe, Vector3 position)
{
#region 条件编译操作 只有在Unity引起运行
#if UNITY_EDITOR
if (!dictionary.ContainsKey(prefabe))
{
Debug.Log("找不到对应的Key");
return null;
}
#endif
#endregion
return dictionary[prefabe].PrepareQuene(position);
}
/// <summary>
/// 释放子弹
/// </summary>
/// <param name="prefabe">指定游戏的预制体</param>
/// <param name="position">指定游戏的位置</param>
/// <param name="quaternion">指定游戏的旋转位置</param>
/// <returns></returns>
public static GameObject Release(GameObject prefabe, Vector3 position, Quaternion quaternion)
{
#region 条件编译操作 只有在Unity引起运行
#if UNITY_EDITOR
if (!dictionary.ContainsKey(prefabe))
{
Debug.Log("找不到对应的Key");
return null;
}
#endif
#endregion
return dictionary[prefabe].PrepareQuene(position, quaternion);
}
/// <summary>
/// 释放子弹
/// </summary>
/// <param name="prefabe">指定游戏的预制体</param>
/// <param name="position">指定游戏的位置</param>
/// <param name="quaternion">指定游戏的旋转位置</param>
/// <param name="localscale">指定游戏的旋转缩放</param>
/// <returns></returns>
public static GameObject Release(GameObject prefabe, Vector3 position, Quaternion quaternion, Vector3 localscale)
{
#region 条件编译操作 只有在Unity引起运行
#if UNITY_EDITOR
if (!dictionary.ContainsKey(prefabe))
{
Debug.Log("找不到对应的Key");
return null;
}
#endif
#endregion
return dictionary[prefabe].PrepareQuene(position, quaternion, localscale);
}
#endregion
}
实现敌人生成管理器
实现共能:实现动态生成敌人、关卡递增……
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyManager : Singleton<EnemyManager>
{
//关卡
public int WaveNumber => waveNumber;
//敌人生成的间隔
public float AgainCreateEnemyTimeIE => againCreateEnemyTimeIE;
//返回导航的随机敌人
public GameObject RandomEnemy => enemyList.Count == 0 ? null : enemyList[Random.Range(0, enemyList.Count)];
//关卡UI显示关卡
[SerializeField] WaveManager waveManager;
[Header("---敌人生成相关---")]
//随机生成不同敌人类型Arr
[SerializeField] GameObject[] enemyArr;
//利用列表管理敌人是否全部死亡
[SerializeField] List<GameObject> enemyList;
//协程的间隔 与 波数生成敌人的间隔
WaitForSeconds randomCreateEnemyWaitForSeconds;
[SerializeField] float randomCreateEnemyTimeIE;
WaitForSeconds againCreateEnemyWaitForSeconds;
[SerializeField] float againCreateEnemyTimeIE;
//最小敌人数量 与 最大敌人数量
[SerializeField] int minEnemyAmout = 1;
[SerializeField] int maxEnemyAmout = 5;
//是否要生成敌人
[SerializeField] bool isCreateEnemy;
//使用WaitUntil事件来判断敌人的死亡与再次生成敌人
private WaitUntil waitUntil;
//游戏的波束默认第一波
private int waveNumber = 1;
//敌人的数量
private int enemyAmout;
protected override void Awake()
{
base.Awake();
//初始化协程时间间隔
randomCreateEnemyWaitForSeconds = new WaitForSeconds(randomCreateEnemyTimeIE);
againCreateEnemyWaitForSeconds = new WaitForSeconds(againCreateEnemyTimeIE);
//利用委托挂起再次生成敌人
waitUntil = new WaitUntil(() => enemyList.Count == 0);
}
IEnumerator Start()
{
while (isCreateEnemy && GameManager.GameState!=GameState.GameOver)
{
委托判断敌人是否全部死亡
//yield return waitUntil;
waveManager.gameObject.SetActive(true);
//生成的间隔时间
yield return againCreateEnemyWaitForSeconds;
waveManager.gameObject.SetActive(false);
//开始创建敌人
yield return StartCoroutine(nameof(RandomCreateEnemyIE));
}
}
IEnumerator RandomCreateEnemyIE()
{
enemyAmout = Mathf.Clamp(enemyAmout, minEnemyAmout + (int)Mathf.Ceil(waveNumber / 3), maxEnemyAmout);
for (int i = 0; i <= enemyAmout; i++)
{
//把生成的敌人添加进列表
enemyList.Add(PoolManager.Release(enemyArr[Random.Range(0, enemyArr.Length)]));
yield return randomCreateEnemyWaitForSeconds;
}
//委托判断敌人是否全部死亡
yield return waitUntil;
//全部敌人死亡时关卡值++
waveNumber++;
}
/// <summary>
/// 把敌人从列表中移除
/// </summary>
/// <param name="game">死亡的敌人需要移除</param>
public void RemoveListEnemy(GameObject game)
{
enemyList.Remove(game);
}
}
敌人脚本
using UnityEngine;
public class Enemy : Characters
{
[Header("---死亡获得的能量值---")]
[SerializeField] int destroyEnergyValue=3;
protected override void Die()
{
//敌人死亡时执行能量更新
PlayerEnergy.Instance.OptainEnergy(destroyEnergyValue);
//敌人死亡从列表中移除
EnemyManager.Instance.RemoveListEnemy(gameObject);
base.Die();
}
}
效果
实现波数UI
时间UI从两边出现 与 显示当前的关卡数
using System.Collections;
using UnityEngine;
public class DynamicWaveUI : MonoBehaviour
{
#region EnterTheField
[SerializeField] float animationTime = 1f;
[Header("---- LINE MOVE ----")]
[SerializeField] Vector2 lineTopStartPosition = new Vector2(-1250f, 140f);
[SerializeField] Vector2 lineTopTargetPosition = new Vector2(0f, 140f);
[SerializeField] Vector2 lineDownStartPosition = new Vector2(1250f, 0f);
[SerializeField] Vector2 lineDownTargetPosition = Vector2.zero;
[Header("---- TEXT SCALE ----")]
[SerializeField] Vector2 waveTextStartScale = new Vector2(1f, 0f);
[SerializeField] Vector2 waveTextTargetScale = Vector2.one;
//上划线
RectTransform lineTop;
//下划线
RectTransform lineDown;
//关卡Text
RectTransform waveText;
//携程的等待时间
WaitForSeconds waitStayTime;
#endregion
#region UNITY INITIALIZE FUN
void Awake()
{
//如果使用了动画Animtor那么就移除脚本
if (TryGetComponent<Animator>(out Animator animator))
{
if (animator.isActiveAndEnabled)
{
Destroy(this);
}
}
waitStayTime = new WaitForSeconds(EnemyManager.Instance.AgainCreateEnemyTimeIE - animationTime * 2f);
//获取对应的组件
lineTop = transform.Find("WAVE TOP").GetComponent<RectTransform>();
lineDown = transform.Find("WAVE DOWN").GetComponent<RectTransform>();
waveText = transform.Find("WAVE Text").GetComponent<RectTransform>();
//初始化UI的位置
lineTop.localPosition = lineTopStartPosition;
lineDown.localPosition = lineDownStartPosition;
waveText.localScale = waveTextStartScale;
}
void OnEnable()
{
StartCoroutine(LineMoveIE(lineTop, lineTopStartPosition, lineTopTargetPosition));
StartCoroutine(LineMoveIE(lineDown,lineDownStartPosition, lineDownTargetPosition));
StartCoroutine(TextScaleCoroutine(waveText, waveTextStartScale, waveTextTargetScale));
}
#endregion
#region LINE MOVE
IEnumerator LineMoveIE(RectTransform rect, Vector2 startPosition, Vector2 targetPosition)
{
//进场
yield return StartCoroutine(UIMoveIE(rect, targetPosition));
//等待事件
yield return waitStayTime;
//出场
yield return StartCoroutine(UIMoveIE(rect, startPosition));
}
IEnumerator UIMoveIE(RectTransform rect, Vector2 position)
{
float t = 0f;
Vector2 localScale = rect.localScale;
while (t < 1f)
{
t += Time.deltaTime / animationTime;
rect.localPosition = Vector2.Lerp(localScale, position, t);
yield return null;
}
}
#endregion
#region TEXT SCALE
IEnumerator TextScaleCoroutine(RectTransform rect, Vector2 startScale, Vector2 targetScale)
{
yield return StartCoroutine(UIScaleCoroutine(rect, targetScale));
yield return waitStayTime;
yield return StartCoroutine(UIScaleCoroutine(rect, startScale));
}
IEnumerator UIScaleCoroutine(RectTransform rect, Vector2 scale)
{
float t = 0f;
Vector2 localScale = rect.localScale;
while (t < 1f)
{
t += Time.deltaTime / animationTime;
rect.localScale = Vector2.Lerp(localScale, scale, t);
yield return null;
}
}
#endregion
}
效果
音频管理器
实现:继成持久单例完成声音播放、音效播放、音频的播放……
using UnityEngine;
public class AudioManager : PersistentSingleton<AudioManager>
{
//特效播放器sFXPlayer
[SerializeField] AudioSource sFXPlayer;
//pitch范围 最大范围及最小范围
const float MIN_PITCH = 0.9f;
const float MAX_PITCH = 1.1f;
/// <summary>
/// 播放声音
/// </summary>
/// <param name="audioData">播放的音频及音量</param>
public void PlaySFX(AudioData audioData)
{
sFXPlayer.PlayOneShot(audioData.clip, audioData.volume);
}
/// <summary>
/// 播放声音
/// </summary>
/// <param name="audioData">播放的音频及音量</param>
public void RandomPitchPlaySFX(AudioData audioData)
{
sFXPlayer.pitch = Random.Range(MIN_PITCH, MAX_PITCH) ;
PlaySFX(audioData);
}
/// <summary>
/// 播放声音
/// </summary>
/// <param name="audioData">播放音频的数字</param>
public void RandomPitchPlaySFX(AudioData[] audioData)
{
sFXPlayer.pitch = Random.Range(MIN_PITCH, MAX_PITCH);
PlaySFX(audioData[Random.Range(0, audioData.Length)]);
}
}
[System.Serializable] public class AudioData{
public AudioClip clip;
public float volume;
}
然后设置发射子弹、发射导弹、受伤等等设置音效就行了
场景加载
实现场景加载:异步加载与简单加载
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class SceneLoadManager : PersistentSingleton<SceneLoadManager>
{
//游戏场景名 使用常量声明
const string GAME_SCENE_NAME = "GamePage";
const string MAIN_NAME = "MainMenu";
const string SCORING_NAME = "Scoring";
//渐变的图片 与 颜色
private Image GradualImage;
private Color color;
//加载时间
float fadeTime = 3.5f;
private void Start()
{
//获取该画布
GradualImage = transform.Find("GradualChangeCanvas/GradualLmage").GetComponent<Image>();
}
#region 普通加载场景
public void LoadScoringScene()
{
SceneManager.LoadScene(SCORING_NAME);
}
#endregion
#region 异步加载场景
public void AsynLoadGamePageScene()
{
StopAllCoroutines();
StartCoroutine(LoadSceneGradientEffect(GAME_SCENE_NAME));
}
public void AsynLoadMainMenuScene()
{
StopAllCoroutines();
StartCoroutine(LoadSceneGradientEffect(MAIN_NAME));
}
public void AsynLoadScoringScene()
{
StopAllCoroutines();
StartCoroutine(LoadSceneGradientEffect(SCORING_NAME));
}
#endregion
#region 携程加载场景
//携程加载场景
IEnumerator LoadSceneGradientEffect(string sceneNmae)
{
//异步加载
var asynchronization = SceneManager.LoadSceneAsync(sceneNmae);
//设置不能跳转
asynchronization.allowSceneActivation = false;
//显示渐变画布
GradualImage.gameObject.SetActive(true);
while (color.a < 1f)
{
color.a = Mathf.Clamp01(color.a + Time.unscaledDeltaTime / fadeTime);
GradualImage.color = color;
yield return null;
}
yield return new WaitUntil(() => asynchronization.progress >= 0.9f);
asynchronization.allowSceneActivation = true;
while (color.a > 0f)
{
color.a = Mathf.Clamp01(color.a - Time.unscaledDeltaTime / fadeTime);
GradualImage.color = color;
yield return null;
}
//隐藏渐变画布
GradualImage.gameObject.SetActive(false);
}
#endregion
}
效果
最后
本文到这里就结束了,大佬们的支持是我持续更新的最大动力,希望这篇文章能帮到大家💪
下篇文章再见ヾ( ̄▽ ̄)ByeBye