当前位置: 首页 > news >正文

[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大伙晚安醒来接着更新。 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Spring Cloud之二 微服务注册
  • JS中判断字符串中是否包含指定字符
  • 代码随想录刷题day32丨动态规划理论基础,509. 斐波那契数, 70. 爬楼梯, 746. 使用最小花费爬楼梯
  • Failed building wheel for opencv-python-headless
  • 林草湿地址、导出echart为word
  • Xcode 16 RC (16A242) 发布下载,正式版下周公布
  • Spring Boot 中关闭 Actuator 端点
  • 安宝特方案 | 医疗AR眼镜,重新定义远程会诊体验
  • 【Docker部署ELK】(7.15)
  • 云更新/网维大师 win10_22H2 无盘镜像
  • mybatisplus学习总结
  • 基于Python实现一个庆祝国庆节的小程序
  • 使用LDAP登录GitLab
  • 一、机器学习算法与实践_01基本概念与项目流程笔记
  • OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(二)
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • 【Amaple教程】5. 插件
  • 【comparator, comparable】小总结
  • Debian下无root权限使用Python访问Oracle
  • django开发-定时任务的使用
  • java8 Stream Pipelines 浅析
  • javascript面向对象之创建对象
  • JavaWeb(学习笔记二)
  • JDK9: 集成 Jshell 和 Maven 项目.
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • php ci框架整合银盛支付
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • 构建二叉树进行数值数组的去重及优化
  • 构造函数(constructor)与原型链(prototype)关系
  • 基于web的全景—— Pannellum小试
  • 记一次删除Git记录中的大文件的过程
  • 力扣(LeetCode)965
  • 前端_面试
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 新手搭建网站的主要流程
  • 阿里云移动端播放器高级功能介绍
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #HarmonyOS:Web组件的使用
  • $ git push -u origin master 推送到远程库出错
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (Java入门)抽象类,接口,内部类
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (四)Android布局类型(线性布局LinearLayout)
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (自用)网络编程