Unity使用新输入系统InputSystem制作飞机大战Demo(对象池设计模式及应用)

Posted SYFStrive

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity使用新输入系统InputSystem制作飞机大战Demo(对象池设计模式及应用)相关的知识,希望对你有一定的参考价值。

@作者 : 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 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;
        
    

对象池制作

思路:从池中取出一个可用对象(判断有没有可用的对象、如果有就出列,没有就克隆1个) 👉 启动可用对象池(显示改对象) 👉 让完成任务的对象返回对象池(进列)

对象池步骤:克隆对象 👉 初始话对象池 👉 可用队列&&返回队列 👉预备好的对象

代码框架

代码如 👇

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//序列化Pool
[System.Serializable()]public class Pool

  //初始化对象池的个数 👉 测试初始化需要多少个预制体
  public int initializeSize => size;
  //需要的总个数 👉 测试初始化需要多少个预制体
  public int sumSize => queue.Count;

  //返回prefabeObjNmae
  public GameObject prefabeObjProperty=> prefabeObj;
  //初始化对象池的个数
  public int initializeSize => size;
  //创建队列
  private Queue<GameObject> queue;
  //对象池用的Obj
  [SerializeField] GameObject prefabeObj;
  //初始化的个数
  [SerializeField] int objCount;
  //设置父对象的位置
  private Transform parentTran;

1、克隆对象

代码如 👇

  /// <summary>
  /// 克隆对象
  /// </summary>
  private GameObject CloneObj()
  
    //创建对象
    var cloneObj = GameObject.Instantiate(prefabeObj,parentTran);
    //隐藏对象
    cloneObj.SetActive(false);
    //返回对象
    return cloneObj;
  

2、初始话对象池

代码如 👇

  /// <summary>
  /// 初始话对象池
  /// </summary>
  private void Initialize(Transform parentTran)
  
    //保存parentTran位置
    this.parentTran = parentTran;

    //初始化队列否者会是空值
    queue = new Queue<GameObject>();

    //遍历创建Obj添加进队列
    for (int i = 0; i < objCount; i++)
    
      queue.Enqueue(CloneObj());
    
  

3、可用队列

代码如 👇

  /// <summary>
  /// 可用多列
  /// </summary>
  private GameObject AvailableQueue()
  
    GameObject availableObj;
    
    //return queue.Count > 0 ? queue.Dequeue() : CloneObj();
    //出列 👉 想要出列后直接进列必须先判断第一个是否处于激活状态,否者去除第二个可能是出列的状态
    //poll:将首个元素从队列中弹出,如果队列是空的,就返回null
	//peek:查看首个元素,不会移除首个元素,如果队列是空的就返回null
	//element:查看首个元素,不会移除首个元素,如果队列是空的就抛出异常NoSuchElementException
    
    if (queue.Count > 0 && !queue.Peek().activeSelf)
      availableObj = queue.Dequeue();
    else
      availableObj = CloneObj();

    //进列
    queue.Enqueue(availableObj);

    //返回obj
    return availableObj;
  

4、预备好的对象

代码如 👇

  #region 预备的对象&&重载
  /// <summary>
  /// 预备好的对象
  /// </summary>
  private GameObject PrepareQuene(Vector3 position)
  
    GameObject prepareObj = AvailableQueue();
    prefabeObj.SetActive(true);
    prefabeObj.transform.position = position;
    return prefabeObj;
  
  private GameObject PrepareQuene(Vector3 position,Quaternion quaternion)
  
    GameObject prepareObj = AvailableQueue();
    prefabeObj.SetActive(true);
    prefabeObj.transform.position = position;
    prefabeObj.transform.rotation = quaternion;
    return prefabeObj;
  
  private GameObject PrepareQuene(Vector3 position, Quaternion quaternion, Vector3 localScale)
  
    GameObject prepareObj = AvailableQueue();
    prefabeObj.SetActive(true);
    prefabeObj.transform.position = position;
    prefabeObj.transform.rotation = quaternion;
    prefabeObj.transform.localScale = localScale;
    return prefabeObj;
  

  #endregion

5、对象池管理脚本

public class PoolManager : MonoBehaviour

  //储存不同类装备的对象池
  [SerializeField] Pool[] poolArr;

  private void OnEnable()
  
    InitializeObj(poolArr);
  

  /// <summary>
  /// 初始化子弹
  /// </summary>
  private void InitializeObj(Pool[] pools)
  
    foreach (var pool in pools)
    
      //给创建的Obj命名
      Transform poolPatent = new GameObject("对象池Poll" + pool.prefabeName.name).transform;
      //设置父位置
      poolPatent.parent = transform;
      //初始话对象池
      pool.Initialize(poolPatent);
    
  

对象池的应用

字典Dictionary:对象池的预制体(Key键) 👉 字典(获取对应Key的值) 👉从对象池Pool检索 👉 匹配对应的数据

poolManager添加

添加如 👇

using System.Collections;
using System.Collections.Generic;
using System.Security.Policy;
using UnityEngine;

public class PoolManager : MonoBehaviour

    //储存不同类装备的对象池
    [SerializeField] Pool[] poolArr;

    //使用字典来存储不同的装备
    public static Dictionary<GameObject, Pool> dictionary;

    private void Awake()
    
        //实例化字典
        dictionary = new Dictionary<GameObject, Pool>();
    

    private void OnEnable()
    
        InitializeObj(poolArr);
    
    
    #region 测试代码
		
		#if UNITY_EDITOR
		
		    //停止游戏时执行
		    private void OnDestroy()
		    
		        CheckPoolSize(poolArr);
		    
		    
		#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("字典有相同的名字!");
	                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

        #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

Player添加功能

添加如 👇

public class Player : MonoBehaviour

	  [Header("---Fire---")]
	  //子弹的预制体
	  [SerializeField] GameObject ProjectileTOP90;
	  [SerializeField] GameObject ProjectileTOP;
	  [SerializeField] GameObject ProjectileCenter;
	  [SerializeField] GameObject ProjectileDown;
	  [SerializeField] GameObject ProjectileDown90;
	
	  //子弹模式
	  [SerializeField, Range(0, 4)] int attackPattern = 0;
	
	  //发射的位置
	  [SerializeField] Transform fireTranPosTop90;
	  [SerializeField] Transform fireTranPosCenter;
	  [SerializeField] Transform fireTranPosTop;
	  [SerializeField] Transform fireTranPosDown;
	  [SerializeField] Transform fireTranPosDown90;
		
	  //发射子弹的间隔
	  [SerializeField] float fireTime = 0.3f;
	  //发射子弹间隔携程
	  WaitForSeconds fireProjectileTimeIE;
	  //使用技能下的开火时间携程
	  WaitForSeconds areSkillFireProjectileTimeIE;
	  
	  [Header("---EnergySkill---")]
	  //是否开启了爆发技能
	  private bool isEnetgySkill;

  protected override void OnEnable()
  
    base.OnEnable();

    //订阅移动事件
    playerInput.onMove += Move;
    playerInput.onStopMove += StopMove;

    //订阅发射子弹
    playerInput.onFire += Fire;
    playerInput.onStopFire += StopFire;
  
  private void OnDisable()
  
    //移除移动事件
    playerInput.onMove -= Move;
    playerInput.onStopMove -= StopMove;

    //移除发射子弹
    playerInput.onFire -= Fire;
    playerInput.onStopFire -= StopFire;
  



  private void Start()
  
    //实例化子弹协程时间间隔
    fireProjectileTimeIE = new WaitForSeconds(fireTime);
    //使用技能下的开火时间
    areSkillFireProjectileTimeIE = new WaitForSeconds(fireTime / areSkillPlayerProjectilefFactorAdd);
  

  #region 子弹
  /// <summary>
  /// 发射子弹
  /// </summary>
  private void Fire()
  
    StartCoroutine(nameof(FireProjectileIE));
  
  /// <summary>
  ///停止发射
  /// </summary>
  private void StopFire()
  
    StopCoroutine(nameof(FireProjectileIE));
  

  //发射子弹携程
  IEnumerator FireProjectileIE()
  
    //创建预制体
    while (true)
    
      switch (attackPattern)
      
        case 0:
          //普通创建子弹
          //Instantiate(projectile1, fireTranPosCenter.position, Quaternion.identity);

          //对象池方法
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileCenter, fireTranPosCenter.position);

          break;
        case 1:
          //普通创建子弹
          //Instantiate(projectile1, fireTranPosCenter.position, Quaternion.identity);
          //Instantiate(projectile2, fireTranPosTop.position, Quaternion.identity);

          //对象池方法
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileCenter, fireTranPosCenter.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP, fireTranPosTop.position);
          break;
        case 2:
          //普通创建子弹
          //Instantiate(projectile1, fireTranPosCenter.position, Quaternion.identity);
          //Instantiate(projectile2, fireTranPosTop.position, Quaternion.identity);
          //Instantiate(projectile3, fireTranPosDown.position, Quaternion.identity);

          //对象池方法
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileCenter, fireTranPosCenter.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP, fireTranPosTop.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileDown, fireTranPosDown.position);
          break;
        case 3:
          //对象池方法
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileCenter, fireTranPosCenter.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP, fireTranPosTop.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileDown, fireTranPosDown.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP90, fireTranPosTop90.position);
          break;
        case 4:
          //对象池方法
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileCenter, fireTranPosCenter.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP, fireTranPosTop.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileDown, fireTranPosDown.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileTOP90, fireTranPosTop90.position);
          PoolManager.Release(isEnetgySkill ? energyPrefabeVFX : ProjectileDown90, fireTranPosDown90.position);
          break;
      
      //播放声音
      AudioManager.Instance.PlaySFX(audioData);

      //判断是否是技能子弹 👉 条用不同的携程时间
      yield return isEnetgySkill ? areSkillFireProjectileTimeIE : fireProjectileTimeIE;
    
  
  #endregion

测试子弹对象池需要的数量

效果如 👇

子弹尾线出现残影的问题

  using UnityEngine;

  public class PlayerProjectile : Projectile
  
   private TrailRenderer trilRenderer;

    protected virtual void Awake()
    
        //获取对应的组件   
        trilRenderer=GetComponentInChildren<TrailRenderer>();
        if (moveDirection != Vector3.right)
        
            transform.GetChild(0).rotation = Quaternion.FromToRotation(Vector3.right, moveDirection);
        
    
    //被禁用调用
    private void OnDisable()
    
        //清楚拖尾 
        trilRenderer.Clear();
    

效果如 👇

敌人控制器制作

实现:完成敌人生成初始点、随机生成,发射子弹

控制敌人的飞行范围

Viewport 视图添加代码如 👇

using UnityEngine;

public class Viewport : Singleton<Viewport>

    //获取X轴的中间位置
    private float centerPosX;
    
    private void Start()
    
        //获取X轴的中间位置
        centerPosX = camera.ViewportToWorldPoint(new Vector3(0.5f, 0f, 0f)).x;
    

    /// <summary>
    /// 敌人随机的初始化位置
    /// </summary>
    public Vector3 RandomEnemyInitializePos(float paddingX,float paddingY)
    
        //初始化位置
        Vector3 intaialPosition = Vector3.zero;

        //paddingX、paddingY 限制飞机一半身体超出视口外偏移量
        intaialPosition.x = maxX + paddingX;
        intaialPosition.y = Random.Range(minY + paddingY, maxY - paddingY);

        //返回位置
        return intaialPosition;
    


    /// <summary>
    /// 限制敌人的移动范围
    /// </summary>
    /// <returns></returns>
    public Vector3 EnemyRandomMoveScope(float paddingX, float paddingY)
    
        //初始化位置
        Vector3 MoveScopePosition = Vector3.zero;
        //paddingX、paddingY 限制飞机一半身体超出视口外偏移量
        MoveScopePosition.x = Random.Range(centerPosX,maxX-paddingX);
        MoveScopePosition.y = Random.Range(minY + paddingY, maxY - paddingY);
        //返回位置
        return MoveScopePosition;
    

    /// <summary>
    /// 限制敌人的移动范围
    /// </summary>
    /// <returns></returns>
    public Vector3 EnemyRandomMove(float paddingX, float paddingY)
    
        //初始化位置
        Vector3 MoveScopePosition = Vector3.zero;
        //paddingX、paddingY 限制飞机一半身体超出视口外偏移量
        MoveScopePosition.x = Random.Range(minX + paddingX, maxX - paddingX);
        MoveScopePosition.y = Random.Range(minY + paddingY, maxY - paddingY);
        //返回位置
        return MoveScopePosition;
    

控制敌人的移动发射子弹

EnemyController 敌人管理器添加代码如 👇

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour

    [Header("---Move---")]
    [SerializeField] float paddingX;
    [SerializeField] float paddingY;
    [SerializeField] float moveSpeed = 2f;
    [SerializeField] float rositionAngle = 25f;

    [Header("---Fire---")]
    [SerializeField] float maxFireTime=2;
    [SerializeField] float minFireTime=3;
    [SerializeField] GameObject[] enemyProjectileS;
    [SerializeField] Transform enemyFirePosition;

    private void OnEnable()
    
        //敌人随机移动携程
        StartCoroutine(nameof(EnemyRandomMovePosIE));
        //开启敌人射击携程
        StartCoroutine(nameof(EnemyRandomFireIE));
    

    private void OnDisable()
    
        //关闭所有携程
        StopAllCoroutines();
    

    IEnumerator EnemyRandomMovePosIE()
    
        //敌人初始化的位置
        transform.position = Viewport.Instance.RandomEnemyInitializePos(paddingX, paddingY);
        //初始化移动目标的位置
        Vector3 targetPosition = Viewport.Instance.EnemyRandomMoveScope(paddingX, paddingY);

        while (gameObject.activeSelf)
        
            //PseudoCode伪代码(思路清晰)
            //敌人还没到达目标位置 那么继续移动
            //Mathf.Epsilon 接近与零的数
            if (Vector3.Distance(transform.position, targetPosition) > Mathf.Epsilon)
            
                //移动
                transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
                //旋转
                transform.rotation = Quaternion.AngleAxis((targetPosition - transform.position).normalized.y * rositionAngle, Vector3.right);
            
            猿创征文| Unity使用新输入系统InputSystem制作飞机大战Demo

Unity使用新输入系统InputSystem制作飞机大战Demo(后处理配置子弹的发射……)

Unity使用新输入系统InputSystem制作飞机大战Demo(对象池设计模式及应用)

Unity使用新输入系统InputSystem制作飞机大战Demo(对象池设计模式及应用)

Unity3d InputSystem使用教程-安装指南

Unity——InputSystem入门及部分问题讲解