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

news2024/11/16 7:55:52

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

文章目录

  • 前言
  • 一、通过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.coloradmin.cn/o/2133921.html

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

相关文章

低代码开发平台:常用动态脚本Groovy

目录 引言 Groovy 的应用场景 安全问题与解决方案 SQL 注入攻击 反序列化攻击 输入验证 文件路径遍历 命令注入 结论 引言 Groovy 是一种动态语言&#xff0c;它运行在 Java 虚拟机&#xff08;JVM&#xff09;上&#xff0c;并且与 Java 有着很好的互操作性。Groovy…

MATLAB基础应用精讲-【数模应用】卡方拟合优度检验(附MATLAB、python和R语言代码实现)

目录 前言 几个高频面试题目 卡方分布与拟合优度的卡方检验是什么关系? 算法原理 什么是卡方检验 卡方是什么? 如何使用卡方表计算器? 什么是卡方拟合优度检验 使用条件 基本原理 数学模型 卡方检验自由度 理论频数计算 需要注意的事项 卡方检验分类 1、 独…

【Linux】NAT

一、NAT技术背景 之前&#xff0c;我们就讨论了在IPv4协议中&#xff1a;IP协议数量不充足的问题。NAT技术当前解决IP地址不够用的主要手段&#xff0c;是路由器的一个重要功能。对&#xff0c;现在路由器不仅可以工作到网络层&#xff0c;也可以工作到应用层。 NAT能够将私有…

【Hot100】LeetCode—45. 跳跃游戏 II

目录 1- 思路覆盖范围 2- 实现⭐45. 跳跃游戏 II——题解思路 3- ACM 实现 原题链接&#xff1a;45. 跳跃游戏 II 1- 思路 覆盖范围 int cur 0 &#xff1a;记录当前的覆盖范围int next 0&#xff1a;记录当前结点最远的跳跃距离int res 0&#xff1a;记录具体跳几步 覆盖…

tp6.0.8反序列化漏洞的一些看法

更多漏洞分析的内容&#xff0c;可前往无问社区查看http://www.wwlib.cn/index.php/artread/artid/5741.html 环境搭建 composer create-project topthink/think6.0.x-dev thinkphp-v6.0 首先构造一个反序列化点 app/controller/Index.php <?php namespace app\contro…

Claude Prompt 汉语新解

感谢刚哥&#xff01; ;; 作者: 李继刚 ;; 版本: 0.3 ;; 模型: Claude Sonnet ;; 用途: 将一个汉语词汇进行全新角度的解释 ​ ;; 设定如下内容为你的 *System Prompt* (defun 新汉语老师 () "你是年轻人,批判现实,思考深刻,语言风趣" (风格 . ("Oscar Wilde&q…

Linux shell编程学习笔记78:cpio命令——文件和目录归档工具(上)

0 前言 在Linux系统中&#xff0c;除了tar命令&#xff0c;我们还可以使用cpio命令来进行文件和目录的归档。 1 cpio命令的功能&#xff0c;帮助信息&#xff0c;格式&#xff0c;选项和参数说明 1.1 cpio命令的功能 cpio 名字来自 "copy in, copy out"&#xf…

Redis网络模型、通信协议、内存回收

Redis网络模型 一、用户空间和内核空间&#xff08;前提&#xff09;问题来了&#xff1a;为啥要区分用户空间和内核空间呢&#xff1f;我们来看看两个空间以及硬件是如何操作的 二、Linux中五种IO模型1、 阻塞IO2、非阻塞IO3、IO多路复用3.1、SELECT3.2、poll3.3、epoll 4、信…

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

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

SpringCloud-04 OpenFeign服务调用与负载均衡

OpenFeign是一个声明式、模板化的HTTP客户端&#xff0c;它简化了在Java应用程序中调用RESTful API的过程。OpenFeign是Netflix开发的一个开源项目&#xff0c;它构建在Feign的基础上&#xff0c;为开发者提供了更加简单、灵活的方式来实现HTTP请求。OpenFeign的特点包括&#…

蓝桥杯真题——约翰的牛奶

输入样例&#xff1a; 8 9 10 输出样例&#xff1a; 1 2 8 9 10 本题是宽搜的模版题&#xff0c;不论怎么倒牛奶&#xff0c;A,B,C 桶里的牛奶可以看做一个三元点集 我们只要找到A桶是空的&#xff0c;B,C桶中的状态即可 #include <iostream> #include <cstring…

四、滑动窗口-算法总结

文章目录 四、滑动窗口4.1 模板4.2 示例4.2.1 最小覆盖子串4.2.2 字符串的排列4.2.3 找到字符串中所有字母异位词4.2.4 无重复字符的最长子串 四、滑动窗口 4.1 模板 /* 滑动窗口算法框架 */ void slidingWindow(string s, string t) {unordered_map<char, int> need, …

【C++题解】1398. 奇偶统计

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1398. 奇偶统计 类型&#xff1a;二维数组 题目描述&#xff1a; 在一个n行m列的二维数组中&#xff0c;有若干奇数和偶数&#xff0c;请编程统计出这个二维数组中&#xff0c;奇数…

使用canal.deployer-1.1.7和canal.adapter-1.1.7实现mysql数据同步

1、下载地址 --查看是否开启bin_log日志&#xff0c;value on表示开启 SHOW VARIABLES LIKE log_bin; -- 查看bin_log日志文件 SHOW BINARY LOGS; --查看bin_log写入状态 SHOW MASTER STATUS; --查看bin_log存储格式 row SHOW VARIABLES LIKE binlog_format; --查看数据库服…

actuator字符绕过漏洞在Nginx上的配置

最近遇到了安全部门派发的actuator泄漏漏洞&#xff0c;领导希望不暴露到外网上&#xff0c;对于内网需要认证才可以访问。 要想不暴露到外网上&#xff0c;就需要在网络层面做拦截&#xff0c;比如nginx和apisix上做代理配置。 URI字符绕过的风险背景知识: URI字符绕过是一种安…

【笔记】第二节 熔炼、轧制、热处理和焊接工艺

文章目录 2.1 钢轨冶炼工艺2.1.1 冶炼工艺(1)铁水预处理(2)转炉合金化冶炼(3)钢包精炼工艺&#xff08;LF&#xff08;Ladle Furnace&#xff09;炉&#xff09; 2.1.2 技术要点(1) LF精炼(2) 夹杂物及有害元素控制非金属夹杂物P和S杂质气体 (3) 铸造组织控制钢轨材质的特点铸造…

雷尼绍圆光栅差分ABZ测量问题

雷尼绍圆光栅差分ABZ测量问题 文章目录 雷尼绍圆光栅差分ABZ测量问题引言一 设备1.1 雷尼绍圆光栅1.2 永磁同步电机1.3 M新动力驱动控制器 二 问题2.1 关于圆光栅2.1.1 电机静止时存在位置抖动问题2.1.2 脉冲数计算问题 引言 最近在调试FOC控制&#xff0c;位置反馈采用的是雷…

基于Ubuntu2404搭建mysql8配置远程访问

使用系统为Ubuntu2404&#xff0c;mysql8版本为8.0.36 安装mysql apt install -y mysql-server设置开机自启动 systemctl enable --now mysql修改密码&#xff0c;似乎是bug&#xff0c;修改密码第一次不成功&#xff0c;第二次可以 mysql use mysql; update user set Host…

局域网UDP通信实验

环境&#xff1a; 一个随身WIFI 一台笔记本电脑 一部手机 随身WIFI连接电脑 手机连接WIFI 此时手机和电脑在同一局域网中 手机IPV4地址&#xff1a;192.168.0.20 电脑IPV4地址&#xff1a;192.168.0.39 电脑端使用两台windows系统计算机简单TCP通信测试_两台计算机tcp通信-CSDN…

性能测试:Locust使用介绍(五)

事件钩子 Locust附带了许多事件钩子&#xff0c;可用于以不同的方式扩展Locust。 例如&#xff0c;以下是如何设置一个事件监听器&#xff0c;该监听器将在请求完成后触发&#xff1a; from locust import eventsevents.request.add_listener def my_request_handler(reques…