[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.mfbz.cn/a/879011.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【图像匹配】基于SURF算法的图像匹配,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于基于SURF算法的图像匹配&#xff0c;用matlab实现。 一、案例背景和算法介绍 前…

办了房屋抵押经营贷,空壳公司不怕被查吗?续贷不上怎么办?

很多有房的朋友&#xff0c;想必都办理过抵押经营贷款。但是&#xff0c;当办完房屋抵押经营贷款之后&#xff0c;钱到手了&#xff0c;别光顾着乐呵&#xff0c;贷后管理可是门大学问&#xff0c;稍有不慎&#xff0c;麻烦就找上门了。咱得确保资金用得对路&#xff0c;征信亮…

零宽字符应用场景及前端解决方案

零宽字符&#xff08;Zero Width Characters&#xff09;是一类在文本中不可见但具有特定功能的特殊字符。称为零宽字符&#xff0c;也叫幽灵字符。它们在显示时不占据任何空间&#xff0c;但在文本处理和显示中发挥着重要作用。这些字符主要包括零宽度空格、零宽度非连接符、零…

2024 VMpro 虚拟机中如何给Ubuntu Linux操作系统配置联网

现在这是一个联网的状态 可以在商店里面下载东西 也能ping成功 打开虚拟网络编辑器 放管理员权限 进行设置的更改 选择DNS设置 按提示修改即可 注意的是首选的DNS服务器必须是114.114.114.114 原因 这边刚刚去查了一下 114.114.114.114 是国内的IP地址 8.8.8.8 是国外的I…

鸿蒙媒体开发系列04——音频播放

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、如何选择音频播放开发方式 在HarmonyOS系统中&#xff0c;多种API都提供了音频播…

QUIC的loss detection学习

PTO backoff backoff 补偿 /ˈbkɒf/PTO backoff 是QUIC&#xff08;Quick UDP Internet Connections&#xff09;协议中的一种机制&#xff0c;用于处理探测超时&#xff08;Probe Timeout, PTO&#xff09;重传策略 它逐步增加探测超时的等待时间&#xff0c;以避免网络拥塞…

Web开发:使用C#创建、安装、调试和卸载服务

目录 一、创建服务 1.创建项目&#xff08;.NET Framework&#xff09; 2.重命名 3.编写逻辑代码 二、安装服务 1.方案一&#xff1a;利用VS2022安装文件的配置 选择添加安装程序 安装文件的介绍及配置 ​编辑​ 重新编译 工具安装 2.方案二&#xff1a;编写bat脚本安…

【嘉立创EDA】画PCB板中为什么要两面铺铜为GND,不能一面GND一面VCC吗?

在新手画板子铺铜时&#xff0c;经常会铺一面GND一面VCC。但一般情况下我们不会这样铺铜。下面将详细分析为什么要两面铺铜为GND&#xff0c;而不是一面GND一面VCC的原因&#xff1a; 提高散热能力 金属导热性&#xff1a;金属具有良好的导热性&#xff0c;铺铜可以有效分散PCB…

Oracle 19c异常恢复—ORA-01209/ORA-65088---惜分飞

由于raid卡bug故障,导致文件系统异常,从而使得数据库无法正常启动,客户找到我之前已经让多人分析,均未恢复成功,查看alert日志,发现他们恢复的时候尝试resetlogs库,然后报ORA-600 kcbzib_kcrsds_1错误 2024-09-15T17:07:32.55321508:00 alter database open resetlogs 2024-09-…

Debian11之DolphinScheduler使用

登录 默认用户名和密码 admin/dolphinscheduler123 http://192.168.111.180:12345/dolphinscheduler/ui基础配置 1、创建Worker【admin用户下】 创建项目的时候会指定Worker&#xff0c;这个配置决定了项目中的任务在哪个服务器执行 2、创建环境【admin用户下】 - 如果涉…

Linux搭建邮箱服务器(简易版)

本章是上一文档的简易版本搭建方式更为快速简洁&#xff08;只需要两条命令即可搭建&#xff09;&#xff0c;如果想了解更详细一些可以看我上一文档 Linux接发邮件mailx_linux mailx o365-CSDN博客文章浏览阅读857次&#xff0c;点赞25次&#xff0c;收藏19次。本文详细描述了…

spring security OAuth2 搭建资源服务器以及授权服务器/jdbc/jwt两种方案

一、认证服务器基于jdbc方式 如果不懂请移步上一篇文章&#xff1a;Spring security OAuth2 授权服务器搭建-CSDN博客 在上一篇文章中&#xff0c;TokenStore的默认实现为 InHenoryTokenStore 即内存存储&#xff0c;对于 CLient 信息&#xff0c;userDetaitsServce 接负责从存…

mqtt整体了解

整个系统的分布及功能 参考太极创客视频 整体分为三部分&#xff1a; 发布&#xff1a;实时发送到云平台&#xff1b;实现主体是传感器或被控对象 订阅&#xff1a;得到能够访问发布信息&#xff1b;主体是有查看和控制权限的对象 云平台&#xff1a;可以理解为有控制订阅者权…

Python 爬虫入门 - Request 静态页面数据获取

在现代 Web 开发中,HTTP 请求(Request)是与服务器进行通信的核心操作。无论是在前端还是后端开发中,数据的获取、传递以及处理都离不开请求的应用。特别是在静态页面的数据获取中,使用请求可以将页面变得更加动态和互动,从而大大提升用户体验,使得页面内容更加丰富和灵活…

MySQL_SQLYog简介、下载及安装(超详细)

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

行人动作行为识别系统源码分享

行人动作行为识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

Halo 开发者指南——项目运行、构建

准备工作 环境要求 OpenJDK 17 LTSNode.js 20 LTSpnpm 9IntelliJ IDEAGitDocker&#xff08;可选&#xff09; 名词解释 工作目录 指 Halo 所依赖的工作目录&#xff0c;在 Halo 运行的时候会在系统当前用户目录下产生一个 halo-next 的文件夹&#xff0c;绝对路径为 ~/ha…

网络高级项目( 基于webserver的工业数据采集和控制项目)

目录 一、项目要求&#xff1a; 二、演示效果&#xff1a; 设备端&#xff1a; Modbus用户控制端&#xff1a; 服务器端&#xff1a; 网页端&#xff1a; 三、 项目代码&#xff1a; Modbus用户控制端代码&#xff1a; 服务器端代码&#xff1a; 网页端代码&#xff1…

VirtualBox Install MacOS

环境搭建 git clone https://github.com/myspaghetti/macos-virtualbox 脚本配置 修改macos-guest-virtualbox.sh部分内容为 vm_name"macOS" # name of the VirtualBox virtual machine macOS_release_name"Catalina" # install &quo…

股指期货的详细玩法功能与应用解析

股指期货作为一种重要的金融衍生工具&#xff0c;为投资者提供了多样化的投资和风险管理手段。本文将详细探讨股指期货的三大主要功能&#xff1a;风险规避、价格发现和资产配置。 第一&#xff0c;风险规避功能 1.套期保值&#xff1a;股指期货的风险规避功能主要通过套期保值…