Unity使用新输入系统InputSystem制作飞机大战Demo(实现能量技能)

请添加图片描述

@作者 : SYFStrive

@博客首页 : HomePage

📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗

📌:觉得文章不错可以点点关注 👉:专栏连接🔗

💃:程序员每天坚持锻炼💪

请添加图片描述
在这里插入图片描述
相关专栏

👉 飞机大战专栏(🔥)

Input输入系统

在这里插入图片描述

实现:实现了这个案例所有的输入事件绑定

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

[CreateAssetMenu(menuName = "Player Input")]
public class PlayerInput : ScriptableObject, 
  InputActions.IGameplayActions, 
  InputActions.IUnpauseActions,
  InputActions.IGameOverSceneActions
{
  //移动:使用系统事件
  public event UnityAction<Vector2> onMove; //= delegate { }判空的1种方法(一开始初始化)

  public event UnityAction onStopMove;
  //发射子弹:使用系统事件
  public event UnityAction onFire;
  public event UnityAction onStopFire;
  //飞机闪避
  public event UnityAction onDodge;
  //能源技能
  public event UnityAction onEnergySkill;
  //菜单事件
  public event UnityAction onPause;
  public event UnityAction onUnPause;
  //发射导弹事件
  public event UnityAction onLaunchMissile;
  //游戏结束
  public event UnityAction onGameOver;

  //初始化实例InputAction
  private InputActions inputActions;


  //Play时执行
  private void OnEnable()
  {
    //实例InputAction
    inputActions = new InputActions();

    //登记动作表的回调函数 如我现在只有一个动作表就登记一个
    inputActions.Gameplay.SetCallbacks(this);
    inputActions.Unpause.SetCallbacks(this);
    inputActions.GameOverScene.SetCallbacks(this);
  }

  //推出游戏执行
  private void OnDisable()
  {
    EntranceSceneInhibitoryInput();
  }

  /// <summary>
  /// 激活控制表及限制鼠标与输入设备输入
  /// </summary>
  public void AstrictGameplayImport()
  {
    //理解:操作玩家 ?? 启动动作表
    //inputActions.Gameplay.Enable();

    //确定硬件指针是否可见。
    //Cursor.visible = false;

    //Cursor.lockState对应的参数如 ??三种
    //1、不加锁定
    //CursorLockMode.None;
    //2、锁定光标
    //CursorLockMode.Locked;
    //3、将光标限制在屏幕内
    //CursorLockMode.Confined;
    //Cursor.lockState = CursorLockMode.Locked;

    SwitchActionMap(inputActions.Gameplay, false);
  }
  public void AstrictUnpauseImport()
  {
    SwitchActionMap(inputActions.Unpause, true);
  }

  public void AstrictGameOverImport()
  {
    SwitchActionMap(inputActions.GameOverScene, true);
  }


  private void SwitchActionMap(InputActionMap inputActionMap, bool isPause)
  {
    inputActions.Disable();
    inputActionMap.Enable();
    //菜单
    if (isPause)
    {
      Cursor.visible = true;
      Cursor.lockState = CursorLockMode.None;
    }
    else
    {
      Cursor.visible = false;
      Cursor.lockState = CursorLockMode.Locked;
    }
  }

  /// <summary>
  /// 场景切换时禁止输入设备一系列操作
  /// </summary>
  public void EntranceSceneInhibitoryInput()
  {
    //禁止输入
    //inputActions.Gameplay.Disable();
    inputActions.Disable();
  }

  #region 切换package
  public void FixedDynamicUpdate() => InputSystem.settings.updateMode = InputSettings.UpdateMode.ProcessEventsInDynamicUpdate;
  public void FixedUpdate() => InputSystem.settings.updateMode = InputSettings.UpdateMode.ProcessEventsInFixedUpdate;
  #endregion

  #region 事件
  public void OnMove(InputAction.CallbackContext context)
  {
    //按下及按住时执行
    if (context.phase == InputActionPhase.Performed)
    {
      //执行事件
      //判断空的第二种方法
      //if(onMove!=null) 

      //判断空的第三种方法
      //传入输入动作读取到的二维向量的值 ?? 将其作为参数 
      //当按下按键的时候 调用onMove方法 ?? 同时将读取到的二维向量值作为参数
      onMove?.Invoke(context.ReadValue<Vector2>());
    }

    //当松开按键时执行
    if (context.phase == InputActionPhase.Canceled)
    {
      //执行事件
      onStopMove?.Invoke();
    }
  }

  public void OnFire(InputAction.CallbackContext context)
  {
    //按下及按住时执行
    if (context.phase == InputActionPhase.Performed)
    {
      onFire?.Invoke();
    }

    //当松开按键时执行
    if (context.phase == InputActionPhase.Canceled)
    {
      onStopFire?.Invoke();
    }
  }

  public void OnDodge(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onDodge?.Invoke();
    }
  }

  public void OnEnergySkill(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onEnergySkill?.Invoke();
    }
  }

  public void OnPause(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onPause?.Invoke();
    }
  }
  public void OnUnpause(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onUnPause?.Invoke();
    }
  }

  public void OnLaunchMissile(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onLaunchMissile?.Invoke();
    }
  }

  public void OnGameOver(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      onGameOver?.Invoke();
    }
  }
  #endregion

}

游戏单例脚本

单例模式是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);
        }
    }
}

对象池管理器

💡理解:对象池(Object Pool)但从字面理解就是一池子的物体,在我们需要使用的时候就拿出来一个,然后包装成我想用的样子。用完之后清空放回到池子里。

  • 为什么要有对象池呢?
  1. 对象池用于减少内存开销,其原理就是把可能用到到的对象,先存在一个地方(池),要用的时候就调出来,不用就放回去。
  2. 如果我们频繁使用 Instantiate 和 Destroy 来生成后销毁,那么会占用大量的系统内存,甚至导致系统崩溃。
  3. 我们可以利用对象池的设计模式概念,在游戏的一开始就制作若干个数量的物体,将他们设置 SetActive(false) 当我们需要它们的时候,只要把它们设置成 SetActive(true) 就好了,用完了再次设置为false。以此方法来循环使用,以达到优化系统的作用。
  4. 如果不使用对象池,我们不经浪费了很多时间在寻找合适的内存上,更让内存产生了很多的内存碎片。

说明:这里已经添加了这个项目所有的对象池容器

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
}

技能相关逻辑脚本

完成使用技能逻辑 与 UI同步显示

技能特效相关逻辑

  [Header("---EnergySkill---")]
  //是否开启了爆发技能
  private bool isEnetgySkill;
  //开启技能对应的能量发生变化
  [SerializeField] int areSkillDodgeAdd = 2;
  [SerializeField] float areSkillPlayerMoveSpeedAdd = 1.2f;
  [SerializeField] float areSkillPlayerProjectilefFactorAdd = 1.2f;
  [SerializeField] GameObject energyPrefabeVFX;
  readonly float recoverTime = 1f;

  private void Awake()
  {
    //获取刚体组件
    rigidbody = GetComponent<Rigidbody2D>();
    //获取碰撞组件
    collider2D = GetComponent<Collider2D>();
    //获取导弹的脚本
    missileSystem = GetComponent<MissileSystem>();
  }

  private void Start()
  {
    //实例化子弹协程时间间隔
    fireProjectileTimeIE = new WaitForSeconds(fireTime);
    //使用技能下的开火时间
    areSkillFireProjectileTimeIE = new WaitForSeconds(fireTime / areSkillPlayerProjectilefFactorAdd);
    //生命自动恢复的间隔
    selfRecoverTimeIE = new WaitForSeconds(selfRecoverTime);
    //无敌时间
    waitForSecondsInvincibleIE = new WaitForSeconds(invincibleTimeIE);
    //更新PlayerUI状态
    playerWorldHUD.Initialize(currentHp, maxHp);

    //缩放的事件
    scaleSpeed = maxRoll / rollSpeed;

  }

  #region 技能
  private void EnergySkill()
  {
    if (!PlayerEnergy.Instance.IsEnergyEnough(PlayerEnergy.MAX_ENERGY)) return;
    //执行委托
    PlayerEnergySkill.on?.Invoke();
  }
  private void EnergyOn()
  {
    isEnetgySkill = true;
    DodegeEnergyValue *= areSkillDodgeAdd;
    moveSpeed *= areSkillPlayerMoveSpeedAdd;
  }
  private void EnergyOff()
  {
    isEnetgySkill = false;
    DodegeEnergyValue /= areSkillDodgeAdd;
    moveSpeed /= areSkillPlayerMoveSpeedAdd;
  }
  #endregion

实现技能特效

实现:释放技能攻击时触发显示技能特效

using UnityEngine;
using UnityEngine.Events;

public class PlayerEnergySkill : MonoBehaviour
{
  public static UnityAction on ;
  public static UnityAction off;

  //正常特效
  [SerializeField] GameObject engineVFXNormal;
  //目标特效
  [SerializeField] GameObject targgerVFX;
  //能量技能特效
  [SerializeField] GameObject engineVFXEnergy;
  //开/关启技能特效
  [SerializeField] AudioData onSFX;
  [SerializeField] AudioData offSFX;

  private void Awake()
  {
    on += On;
    off += Off;
  }
  private void OnDestroy()
  {
    on -= On;
    off -= Off;
  }

  private void On()
  {
    engineVFXNormal.SetActive(false);
    targgerVFX.SetActive(true);
    engineVFXEnergy.SetActive(true);
    AudioManager.Instance.RandomPitchPlaySFX(onSFX);
  }
  private void Off()
  {
    engineVFXNormal.SetActive(true);
    engineVFXEnergy.SetActive(false);
    AudioManager.Instance.RandomPitchPlaySFX(offSFX);
  }
}

效果

在这里插入图片描述

最后

在这里插入图片描述
本文到这里就结束了,大佬们的支持是我持续更新的最大动力,希望这篇文章能帮到大家💪

 

                 相关专栏连接🔗
在这里插入图片描述

下篇文章再见ヾ( ̄▽ ̄)ByeBye

在这里插入图片描述

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SYFStrive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值