Unity——InputSystem入门及部分问题讲解
Posted X1iaoXu666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity——InputSystem入门及部分问题讲解相关的知识,希望对你有一定的参考价值。
距离Unity发布InputSystem已经过去了一段时间了,很多人可能都有所耳闻这个新的输入系统,也有人去下载过使用过,结果发现这个新的输入系统还没老板的好用,于是就放弃使用了。说实话,这个新的输入系统我刚开始使用的时候也觉得操作十分繁琐,一点儿也不好用。但是随着我去看了油管上讲解这个视频之后我发现不是不好用,而是不太会用。
我现在虽然使用InputSystem并没很久,但是先来说一下我认为InputSystem的优势,当我们开发输入方式比较少的软件的时候,的确使用老板的InputManager会方便一点,但是一旦输入方式增多,例如需要手柄,摇杆,键盘,鼠标,触屏等等,当一个软件要同时拥有很多输入方式的时候,使用InputSystem会非常方便,因为他不需要去修改输入判定的代码,只需要挂在相关逻辑就行了。而且其实新版的InputSystem也可以实现老板的功能,并不一定要用新版方式,下面是我自己做的案例。
首先我们先下载InputSystem,这里从Window——PackManager
如果没有找请把划线处调整为Unity Registry。
导入之后会有一个弹窗,上面选启用那个就好了,这就是让你换成新的输入系统,老板的将不能使用了。如果想调整回来就在Editor——ProjectSetting——Player——OtherSetting——ActiveInputHandel中换成Both或者Old。
导入完成之后就开始我们的正题了,先反键点击一个文件夹,选择Creat创建,创建一个InputAction并打开他。
这里就会出来一个这样的界面。
ActionMap就如字面意思行动地图,里面可以创建很多东西,便于玩家分类。例如PlayerInput就是玩家输入,UI就是UI输入。
Action就是具体的行动输入了。我们先创建一个PlayerInput——Jump
众所周知,跳远应该是一个按钮,一般按下跳远键返回的是一个布尔值,所以我们ActionType中选择Button。
这里说一下ActionType中的另外几种Value他的返回值是一个很广泛的值,这个可以自己设置,当你选择了Value之后下面就会出现很多属性,后面我会再讲到。PassThrough这个其实和Value差不多,但是你不输入之后他会返回一个初始值,这个我也没有研究很透彻。大部分情况我都会使用Value。
选好了Button之后点击下面的<No Binding>,这里点击Path。上面有很多种输入
如果大家不知道需要哪种的时候,点击旁边那个Listen,然后在点击需要的那个按键,他就会出现那个按键的绑定选项。如果没有那就是你设置的情况不适合此输入。这里我们跳跃绑定一个键盘的空格,到这里就设置好了,点击右上方SaveAsset保存一下。(最好不要选择AutoSave)
这里InputAction的使用方法有两种方式,我这里只讲我觉得更好的一种,也是更合适程序员的一种方式,点击我们保存之后的InputAction。(用其他方式的小伙伴注意了,按一个按键会打印很多次的原因是因为检查一个按键的输入分为三个检测点,Start,Performed和Cancel,所以才会打印多次)
这里会有这样一个选项,勾选之后就会自动生成C#脚本,这就是为什么上面不要选择AutoSave的原因,不然你一边修改它一边保存会很令人头疼。
生成了这个脚本之后你就可以在脚本中实例化了。这里要注意的位置:
声明的脚本要new(),而不是GetComComponent,声明完之后要启用,示例:
我这里写了一个相机的移动,我认为这是输入中比较有代表性的,相机的移动和旋转均不是是一个按钮,而是要返回一个三维向量Vector3和一个二维向量Vector2。所以我选择的为Value,下面选择的是Vector3与Vector2。像这样:
这里旋转是控制鼠标的X,Y轴偏移,我试了里面的选项,Delta应该对应着Input.GetAxis。相机移动的代码如下:
private void Update()
#region 新输入系统移动
Vector2 v2 = inputs.Camera.Rotate.ReadValue<Vector2>();//Rotate绑定返回的Vector2向量
Vector3 v3 = inputs.Camera.Move.ReadValue<Vector3>();//Move绑定返回的Vector3向量
if (Mouse.current.rightButton.ReadValue() == 1)//这里相当于老板输入法Input.GetMouseButton(1),按住鼠标右键,新版输入法感觉语法上更详细了
//以下就是关于移动相机功能代码没什么要讲的
Cursor.lockState = CursorLockMode.Locked;
transform.Translate((Keyboard.current.leftShiftKey.ReadValue() == 1/* 这里是一样类似于老板的Input.GetKey("left shift")*/ ? fastSpeed : speed) * v3 * Time.deltaTime, Space.Self);
transform.RotateAround(transform.position, Vector3.up, v2.x * 0.1f);
transform.RotateAround(transform.position, transform.right, -v2.y * 0.1f);
else
Cursor.lockState = CursorLockMode.None;
#endregion
其实新版的输入系统没想象中那么难用,熟练使用之后,复杂项目你会发现他的强大。
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 Input System 新输入系统的功能及用法介绍