[Unity Demo]从零开始制作空洞骑士Hollow Knight第二集:通过InControl插件实现绑定玩家输入以及制作小骑士移动空闲动画

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、通过InControl插件实现绑定玩家输入
  • 二、制作小骑士移动和空闲动画
    • 1.制作动画
    • 2.玩家移动和翻转图像
    • 3.状态机思想实现动画切换
  • 总结


前言

好久没来CSDN看看,突然看到前两年自己写的文章从零开始制作空洞骑士只做了一篇就突然烂尾了,刚好最近开始学习做Unity,我决定重启这个项目,从零开始制作空洞骑士!第一集我们导入了素材和远程git管理项目,OK这期我们就从通过InControl插件实现绑定玩家输入以及制作小骑士移动和空闲动画。


一、通过InControl插件实现绑定玩家输入

其实这一部挺难的,因为InControl插件你在网上绝对不超过五个视频资料,但没办法空洞骑士就是用这个来控制键盘控制器输入的,为了原汁原味就只能翻一下InControl提供的Examples示例里学习。

学习完成后直接来看我写的InputHandler.cs和GameManager.cs

在GameManager.cs中我们暂且先只用实现一个单例模式:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GameManager : MonoBehaviour
{private static GameManager _instance;public static GameManager instance{get{if(_instance == null){_instance = FindObjectOfType<GameManager>();}if (_instance == null){Debug.LogError("Couldn't find a Game Manager, make sure one exists in the scene.");}else if (Application.isPlaying){DontDestroyOnLoad(_instance.gameObject);}return _instance;}}private void Awake(){if(_instance != this){_instance = this;DontDestroyOnLoad(this);return;}if(this != _instance){Destroy(gameObject);return;}}}

 在来到InputHandler.cs之前,我们还需要创建映射表HeroActions:

using System;
using InControl;public class HeroActions : PlayerActionSet
{public PlayerAction left;public PlayerAction right;public PlayerAction up;public PlayerAction down;public PlayerTwoAxisAction moveVector;public HeroActions(){left = CreatePlayerAction("Left");left.StateThreshold = 0.3f;right = CreatePlayerAction("Right");right.StateThreshold = 0.3f;up = CreatePlayerAction("Up");up.StateThreshold = 0.3f;down = CreatePlayerAction("Down");down.StateThreshold = 0.3f;moveVector = CreateTwoAxisPlayerAction(left, right, up, down);moveVector.LowerDeadZone = 0.15f;moveVector.UpperDeadZone = 0.95f;}
}

OK到了最关键的InputHandler.cs了:

using System;
using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using InControl;
using UnityEngine;public class InputHandler : MonoBehaviour
{public InputDevice gameController;public HeroActions inputActions;public void Awake(){inputActions = new HeroActions();}public void Start(){MapKeyboardLayoutFromGameSettings();if(InputManager.ActiveDevice != null && InputManager.ActiveDevice.IsAttached){}else{gameController = InputDevice.Null;}Debug.LogFormat("Input Device set to {0}.", new object[]{gameController.Name});}
//暂时没有GameSettings后续会创建的,这里是指将键盘按键绑定到HeroActions 中private void MapKeyboardLayoutFromGameSettings(){AddKeyBinding(inputActions.up, "W");AddKeyBinding(inputActions.down, "S");AddKeyBinding(inputActions.left, "A");AddKeyBinding(inputActions.right, "D");}private static void AddKeyBinding(PlayerAction action, string savedBinding){Mouse mouse = Mouse.None;Key key;if (!Enum.TryParse(savedBinding, out key) && !Enum.TryParse(savedBinding, out mouse)){return;}if (mouse != Mouse.None){action.AddBinding(new MouseBindingSource(mouse));return;}action.AddBinding(new KeyBindingSource(new Key[]{key}));}}
 给我们的小骑士创建一个HeroController.cs,检测输入最关键的是一行代码:
move_input = inputHandler.inputActions.moveVector.Vector.x;
using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;public class HeroController : MonoBehaviour
{public ActorStates hero_state;public ActorStates prev_hero_state;public bool acceptingInput = true;public float move_input;public float RUN_SPEED = 5f;private Rigidbody2D rb2d;private BoxCollider2D col2d;private GameManager gm;private InputHandler inputHandler;private void Awake(){SetupGameRefs();}private void SetupGameRefs(){rb2d = GetComponent<Rigidbody2D>();col2d = GetComponent<BoxCollider2D>();gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();}void Start(){}void Update(){orig_Update();}private void orig_Update(){if (hero_state == ActorStates.no_input){}else if(hero_state != ActorStates.no_input){LookForInput();}}private void FixedUpdate(){if (hero_state != ActorStates.no_input){Move(move_input);}}private void Move(float move_direction){if(acceptingInput){rb2d.velocity = new Vector2(move_direction * RUN_SPEED, rb2d.velocity.y);}}private void LookForInput(){if (acceptingInput){move_input = inputHandler.inputActions.moveVector.Vector.x;}}
}[Serializable]
public class HeroControllerStates
{public bool facingRight;public bool onGround;public HeroControllerStates(){facingRight = true;onGround = false;}
}

记得一句话:FixedUpdate()处理物理移动,Update()处理逻辑 

二、制作小骑士移动和空闲动画

1.制作动画

        素材找到Idle和Walk文件夹,创建两个同名animation,sprite往上面一放自己就做好了。

        可能你注意到我没有给这两个动画连线,其实是我想做个动画状态机,通过核心代码

        animator.Play()来管理动画的切换。

2.玩家移动和翻转图像

我们还需要给小骑士添加更多的功能比如翻转图像:

using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;public class HeroController : MonoBehaviour
{public ActorStates hero_state;public ActorStates prev_hero_state;public bool acceptingInput = true;public float move_input;public float RUN_SPEED = 5f;private Rigidbody2D rb2d;private BoxCollider2D col2d;private GameManager gm;private InputHandler inputHandler;public HeroControllerStates cState;private HeroAnimatorController animCtrl;private void Awake(){SetupGameRefs();}private void SetupGameRefs(){if (cState == null)cState = new HeroControllerStates();rb2d = GetComponent<Rigidbody2D>();col2d = GetComponent<BoxCollider2D>();animCtrl = GetComponent<HeroAnimatorController>();gm = GameManager.instance;inputHandler = gm.GetComponent<InputHandler>();}void Start(){}void Update(){orig_Update();}private void orig_Update(){if (hero_state == ActorStates.no_input){}else if(hero_state != ActorStates.no_input){LookForInput();}}private void FixedUpdate(){if (hero_state != ActorStates.no_input){Move(move_input);if(move_input > 0f && !cState.facingRight ){FlipSprite();}else if(move_input < 0f && cState.facingRight){FlipSprite();}}}private void Move(float move_direction){if (cState.onGround){SetState(ActorStates.grounded);}if(acceptingInput){rb2d.velocity = new Vector2(move_direction * RUN_SPEED, rb2d.velocity.y);}}public void FlipSprite(){cState.facingRight = !cState.facingRight;Vector3 localScale = transform.localScale;localScale.x *= -1f;transform.localScale = localScale;}private void LookForInput(){if (acceptingInput){move_input = inputHandler.inputActions.moveVector.Vector.x;}}/// <summary>/// 设置玩家的ActorState的新类型/// </summary>/// <param name="newState"></param>private void SetState(ActorStates newState){if(newState == ActorStates.grounded){if(Mathf.Abs(move_input) > Mathf.Epsilon){newState  = ActorStates.running;}else{newState = ActorStates.idle;}}else if(newState == ActorStates.previous){newState = prev_hero_state;}if(newState != hero_state){prev_hero_state = hero_state;hero_state = newState;animCtrl.UpdateState(newState);}}private void OnCollisionEnter2D(Collision2D collision){if(collision.gameObject.layer == LayerMask.NameToLayer("Wall")){cState.onGround = true;}}private void OnCollisionStay2D(Collision2D collision){if (collision.gameObject.layer == LayerMask.NameToLayer("Wall")){cState.onGround = true;}}private void OnCollisionExit2D(Collision2D collision){if (collision.gameObject.layer == LayerMask.NameToLayer("Wall")){cState.onGround = false;}}
}[Serializable]
public class HeroControllerStates
{public bool facingRight;public bool onGround;public HeroControllerStates(){facingRight = true;onGround = false;}
}

创建命名空间GlobalEnums,创建一个新的枚举类型: 

using System;namespace GlobalEnums
{public enum ActorStates{grounded,idle,running,airborne,wall_sliding,hard_landing,dash_landing,no_input,previous}
}

3.状态机思想实现动画切换

有了这些我们就可以有效控制动画切换,创建一个新的脚本给Player:

可以看到我们创建了两个属性记录当前的actorState和上一个actorState来实现动画切换(后面会用到的)

using System;
using GlobalEnums;
using UnityEngine;public class HeroAnimatorController : MonoBehaviour
{private Animator animator;private AnimatorClipInfo[] info;private HeroController heroCtrl;private HeroControllerStates cState;private string clipName;private float currentClipLength;public ActorStates actorStates { get; private set; }public ActorStates prevActorState { get; private set; }private void Start(){animator = GetComponent<Animator>();heroCtrl = GetComponent<HeroController>();actorStates = heroCtrl.hero_state;PlayIdle();}private void Update(){UpdateAnimation();}private void UpdateAnimation(){//info = animator.GetCurrentAnimatorClipInfo(0);//currentClipLength = info[0].clip.length;//clipName = info[0].clip.name;if(actorStates == ActorStates.no_input){//TODO:}else if(actorStates == ActorStates.idle){//TODO:PlayIdle();}else if(actorStates == ActorStates.running){PlayRun();}}private void PlayRun(){animator.Play("Run");}public void PlayIdle(){animator.Play("Idle");}public void UpdateState(ActorStates newState){if(newState != actorStates){prevActorState = actorStates;actorStates = newState;}}
}


总结

最后给大伙看看效果怎么样,可以看到运行游戏后cState,hero_state和prev_hero_state都没有问题,动画也正常播放:

累死我了我去睡个觉顺便上传到github,OK大伙晚安醒来接着更新。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1534220.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

HBase在大数据实时处理中的角色

HBase是一个分布式的、面向列的开源NoSQL数据库&#xff0c;它建立在Hadoop的HDFS之上&#xff0c;被设计用于处理大规模数据集。HBase非常适合于需要随机实时读写访问的应用程序&#xff0c;例如大数据分析、数据仓库和实时数据处理等场景。本文将探讨HBase是如何进行大数据实…

虚幻引擎 | (类恐鬼症)玩家和NPC语音聊天(中)

虚幻引擎 | &#xff08;类恐鬼症&#xff09;玩家和NPC语音聊天-CSDN博客 上篇偏重实现步骤&#xff0c;中篇偏重校准和降低延迟&#xff0c;下篇优化上下文和口音 TTS通用参数 ——————————————————————————————————————————— 以…

传统Malmquist-Luenberger指数与全局Malmquist-Luenberger指数的区别

1.全局技术前沿的构建 1.1传统ML指数 技术前沿的时间依赖性 传统的Malmquist-Luenberger&#xff08;ML&#xff09;指数在每个时期&#xff08;例如年份&#xff09;单独构建各自的技术前沿。这意味着每个时期的生产可能性集合和技术效率都是基于该时期的数据。 不可比性问…

基于SpringBoot+Vue+MySQL的IT技术交流和分享平台

系统展示 用户前台界面 管理员后台界面 系统背景 在数字化转型的浪潮中&#xff0c;构建一个基于SpringBoot、Vue.js与MySQL的IT技术交流与分享平台显得尤为重要。该平台旨在汇聚广大IT从业者、开发者及爱好者&#xff0c;提供一个高效、便捷的线上空间&#xff0c;用于分享最新…

【笔记】1.2 弹性变形

文章目录 一、弹性变形及实质二、胡克定律1. 单向拉伸2. 剪切和扭转3. E、G和v的关系 三、弹性模量弹性模量的影响因素第二相铸铁石墨形态塑性变形温度影响不明显 四、弹性比功弹性比功表示 五、滞弹性弹性体纯弹性体实际弹性体 主要特征和机制延迟反应内部结构影响因素 弹性滞…

性能测试【Locust】基本使用介绍

一.前言 Locust是一款易于使用的分布式负载测试工具&#xff0c;基于事件驱动&#xff0c;使用轻量级执行单元&#xff08;如协程&#xff09;来实现高并发。 二.基本使用 以下是Locust性能测试使用的一个基础Demo示例&#xff0c;该示例有安装Locust、编写测试脚本、启动测…

王者荣耀改重复名(java源码)

王者荣耀改重复名 项目简介 “王者荣耀改重复名”是一个基于 Spring Boot 的应用程序&#xff0c;用于生成王者荣耀游戏中的唯一名称。通过简单的接口和前端页面&#xff0c;用户可以输入旧名称并获得一个新的、不重复的名称。 功能特点 生成新名称&#xff1a;提供一个接口…

【南方科技大学】CS315 Computer Security 【Lab2 Buffer Overflow】

目录 引言软件要求启动虚拟机环境设置禁用地址空间布局随机化&#xff08;ASLR&#xff09;设置编译器标志以禁用安全功能 概述BOF.ctestShellCode.ccreateBadfile.c 开始利用漏洞在堆栈上查找返回地址 实验2的作业 之前有写过一个 博客&#xff0c;大家可以先看看栈溢出基础。…

redis的基础数据结构-list列表

文章目录 1. redis的list数据结构1.1. list结构的特性1.2. 常用命令 2. 常见业务场景2.1 消息队列案例讲解背景优势解决方案代码实现 2.2 排行榜案例讲解背景优势解决方案代码实现 3. 注意事项&#xff1a; 1. redis的list数据结构 参考链接&#xff1a;https://mp.weixin.qq.…

Java面试篇基础部分-Java创建线程详解

导语   多线程的方式能够在操作系统的多核配置上更好的利用服务器的多个CPU的资源,这样的操作可以使得程序运行起来更加高效。Java中多线程机制提供了在一个进程内并发去执行多个线程,并且每个线程都并行的去执行属于线程处理的自己的任务,这样可以提高程序的执行效率,让…

【算法】-单调队列

目录 什么是单调队列 区域内最大值 区域内最小值 什么是单调队列 说到单调队列&#xff0c;其实就是一个双端队列&#xff0c; 顾名思义&#xff0c;单调队列的重点分为「单调」和「队列」。「单调」指的是元素的「规律」——递增&#xff08;或递减&#xff09;。「队列」指…

2.5 ADC模数转换

文章目录 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器AD转换的步骤 与 时间stm32ADC的转换模式 ADC框图stm32的ADC引脚配置stm32ADC的步骤 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转…

c#引用同一命名空间下的其他类

总体结构 Class1的内容 using System.Windows.Forms; namespace demo1 {internal class Class1{public class HelperClass{public void DoSomething(){MessageBox.Show("Doing something useful..."); } }} }Class2的内容 using System.W…

【C++ Primer Plus习题】16.2

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include <string> #inc…

基于YOLO V8的学生上课行为检测系统【python源码+Pyqt5界面+数据集+训练代码】有报告

目的是利用YOLOV8这一先进的深度学习技术&#xff0c;开发一个自动化的学生上课行为检测系统。通过对上课行为数据集进行深入分析和标注&#xff0c;我们训练了YOLOV8模型&#xff0c;使其能够精确识别学生在课堂上的各种行为状态。这一系统能够实时监控并分析学生的行为&#…

Ruoyi Cloud K8s 部署

参考 https://blog.csdn.net/Equent/article/details/137779505 https://blog.csdn.net/weixin_48711696/article/details/138117392 https://zhuanlan.zhihu.com/p/470647732 https://gitee.com/y_project/RuoYi-Cloud https://blog.csdn.net/morecccc/article/details/1…

北大领衔:多智能体研究登上Nature子刊

这篇笔记可以作为接EoT那篇笔记内容中某种思想内涵的延伸和实践&#xff0c;即均是将智能体之间的关系描述为一种拓扑连接结构下的网络化关系进行研究&#xff08;贴近物理世界更加真实、自然、客观的拓扑结构&#xff09;&#xff0c;在这项研究中&#xff0c;更多的扩展到大规…

关于 vue/cli 脚手架实现项目编译运行的源码解析

1.vue项目运行命令解析 在日常开发中&#xff0c;vue 项目通过vue-cli-service脚手架包将项目运行起来&#xff0c;常用的命令例如&#xff1a; npm run serve npm run build 上述执行命令实际一般对应为项目中 package.json 文件的 scripts属性中编写的脚本命令&#xff0c;在…

解码3D数字人及AIGC产品,如何赋能医美行业全场景业务增长

9月13日&#xff0c;第六届“医美小小聚”暨医美信息与服务创新发展大会在热烈的氛围中拉开帷幕。此次盛会汇聚了医美行业的顶尖精英与前瞻者&#xff0c;他们围绕“聚焦营销&#xff0c;合规增长&#xff0c;融合共创”的主题&#xff0c;深入剖析了行业的新趋势、新机遇与新挑…

【JUC并发编程系列】深入理解Java并发机制:Synchronized机制深度剖析、HotSpot下的并发奥秘(四、synchronized 原理分析)

文章目录 【JUC并发编程系列】深入理解Java并发机制&#xff1a;Synchronized机制深度剖析、HotSpot下的并发奥秘(四、synchronized 原理分析)1. 虚拟机环境2. 基本数据类型占多少字节3. JVM对象头3.1 Klass Pointer3.2 实例属性3.3 对齐填充3.4 查看Java对象布局3.5 论证压缩效…