Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

这篇具有很好参考价值的文章主要介绍了Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 相关链接
  • 前言
  • 4:加速和减速
    • move_toward,加速移动
  • 5:角色状态机
    • 有限状态机
    • 代码改动
      • 改动前
      • 改动后
      • 如何写状态机
        • 状态初始量
        • 状态进入和退出
        • 强制状态修改
        • 熟练使用异步
  • 6:滑墙
    • 图片拼接
    • 碰撞框对应
    • 判断是否在墙上
  • 7:蹬墙跳
    • 优化跳跃手感
  • 总结

相关链接

十分钟制作横版动作游戏|Godot 4 教程《勇者传说》#0

Godot Engine 4.2 简体中文文档

GodotNet_LegendOfPaladin C# 重构项目地址

前言

这次来学习一下Godot的运动控制,Godot中内置了很多数据运算的函数,而且是使用C++集成的,使用C# 调用,性能方面肯定是没有问题的。我这个博客的序号和视频的序号是完全对应的。有时候一节课的知识点比较少,会一次多写一些

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

4:加速和减速

move_toward,加速移动

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

我们可以看到三个参数就是数学中的,起点,终点,导数。所以我们可以填入起始速度,终点速度,加速度。

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

using Godot;
using GodotNet_LegendOfPaladin2.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GodotNet_LegendOfPaladin2.SceneModels
{
    public class PlayerSceneModel : ISceneModel
    {
        private PrintHelper printHelper;
        #region 常量
        /// <summary>
        /// 速度
        /// </summary>
        public const float RUN_SPEED = 200;

        /// <summary>
        /// 加速度,为了显示明显,20秒内到达RUN_SPEED的速度
        /// </summary>
        public const float ACCELERATION = (float)(RUN_SPEED / 20);

        /// <summary>
        /// 跳跃速度
        /// </summary>
        public const float JUMP_SPEED = -350;

        #endregion


        public override void Process(double delta)
        {
            PlayerMove(delta);
        }

        private void PlayerMove(double delta)
        {
            var velocity = characterBody2D.Velocity;
            velocity.Y += ProjectSettingHelper.Gravity * (float)delta;
            var direction = Input.GetAxis(ProjectSettingHelper.InputMapEnum.move_left.ToString(),
                ProjectSettingHelper.InputMapEnum.move_right.ToString());
            //原本直接赋值
            //velocity.X = direction*RUN_SPEED;
            //现在使用加速度
            velocity.X = Mathf.MoveToward(velocity.X, direction * RUN_SPEED, ACCELERATION);
          ......

        }
    }
}

5:角色状态机

我们目前做的动画效果只是单独的跑动,跳跃,下落,站立。如果我们的动画逻辑变得复杂起来,我们的角色的状态的判断会变得异常的麻烦。会充斥着大量的if,else判断。这里就要引入有限状态机的概念。

有限状态机

简单来说就是,状态只有一个,每个状态之间的转化都是有对应的条件才会执行
Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

代码改动

改动前



namespace GodotNet_LegendOfPaladin2.SceneModels
{
    public class PlayerSceneModel : ISceneModel
    {
        ......

        public enum AnimationEnum { REST, Idel, Running, Jump, Fall, Land }


        private void PlayerMove(double delta)
        {
            var velocity = characterBody2D.Velocity;
            velocity.Y += ProjectSettingHelper.Gravity * (float)delta;
            var direction = Input.GetAxis(ProjectSettingHelper.InputMapEnum.move_left.ToString(),
                ProjectSettingHelper.InputMapEnum.move_right.ToString());
            //原本直接赋值
            //velocity.X = direction*RUN_SPEED;
            //现在使用加速度
            velocity.X = Mathf.MoveToward(velocity.X, direction * RUN_SPEED, ACCELERATION);

            if (characterBody2D.IsOnFloor())
            {
                

                if (Mathf.IsZeroApprox(direction))
                {
                    PlayAnimation(AnimationEnum.Idel);
                }
                else
                {
                    PlayAnimation(AnimationEnum.Running);
                }
                if (Input.IsActionJustPressed(ProjectSettingHelper.InputMapEnum.jump.ToString())){
                    velocity.Y = JUMP_SPEED;
                    IsLand = false;
                }
            }
            else if (characterBody2D.Velocity.Y > 0)
            {
                PlayAnimation(AnimationEnum.Fall);
            }
            else
            {
                PlayAnimation(AnimationEnum.Jump);
            }

            if (!Mathf.IsZeroApprox(direction))
            {
                sprite2D.FlipH = direction < 0;
            }



            characterBody2D.Velocity = velocity;
            characterBody2D.MoveAndSlide();

        }


        private void PlayAnimation(AnimationEnum animationEnum)
        {
            animationPlayer.Play(animationEnum.ToString());
        }

        ......
    }
}

改动后

using Godot;
using GodotNet_LegendOfPaladin2.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Godot.TextServer;

namespace GodotNet_LegendOfPaladin2.SceneModels
{
    public class PlayerSceneModel : ISceneModel
    {
        ......

        public PlayerSceneModel(PrintHelper printHelper)
        {

            this.printHelper = printHelper;
            this.printHelper.SetTitle(nameof(PlayerSceneModel));
        }


        public override void Process(double delta)
        {
            PlayerMove(delta);

            SetAnimation();
        }

        /// <summary>
        /// 角色移动
        /// </summary>
        /// <param name="delta"></param>
        private void PlayerMove(double delta)
        {
            var velocity = characterBody2D.Velocity;
            velocity.Y += ProjectSettingHelper.Gravity * (float)delta;
            Direction = Input.GetAxis(ProjectSettingHelper.InputMapEnum.move_left.ToString(),
                ProjectSettingHelper.InputMapEnum.move_right.ToString());
            //原本直接赋值
            //velocity.X = direction*RUN_SPEED;
            //现在使用加速度
            velocity.X = Mathf.MoveToward(velocity.X, Direction * RUN_SPEED, ACCELERATION);

            if(characterBody2D.IsOnFloor() && Input.IsActionJustPressed(ProjectSettingHelper.InputMapEnum.jump.ToString()))
            {
                velocity.Y = JUMP_SPEED;
                AnimationState = AnimationEnum.Jump;
            }
            characterBody2D.Velocity = velocity;
            characterBody2D.MoveAndSlide();

        }

        private void SetAnimation()
        {
            switch (AnimationState)
            {
                case AnimationEnum.Idel:
                    if (!Mathf.IsZeroApprox(Direction))
                    {
                        AnimationState = AnimationEnum.Running;
                    }
                    break;
                case AnimationEnum.Jump:
                    if (characterBody2D.Velocity.Y < 0)
                    {
                        AnimationState = AnimationEnum.Fall;
                        
                    }
                    break;
                case AnimationEnum.Running:
                    if (Mathf.IsZeroApprox(Direction))
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;
                case AnimationEnum.Fall:
                    if (Mathf.IsZeroApprox(characterBody2D.Velocity.Y))
                    {
                        AnimationState = AnimationEnum.Land;
                        //开启异步任务,如果过了400毫秒,仍然是Land,则转为Idel
                        Task.Run(async () =>
                        {
                            await Task.Delay(400);
                            if(AnimationState == AnimationEnum.Land)
                            {
                                AnimationState = AnimationEnum.Idel;

                            }
                        });
                    }
                    break;
                case AnimationEnum.Land:
                    
                    break;
            }

            if (!Mathf.IsZeroApprox(Direction))
            {
                sprite2D.FlipH = Direction < 0;
            }
            PlayAnimation();
        }

        /// <summary>
        /// 播放动画
        /// </summary>
        private void PlayAnimation()
        {
            //printHelper.Debug(AnimationState.ToString());

            animationPlayer.Play(AnimationState.ToString());
        }

        /// <summary>
        /// 是否准备好了
        /// </summary>
        public override void Ready()
        {
            characterBody2D = Scene.GetNode<CharacterBody2D>("CharacterBody2D");
            camera2D = characterBody2D.GetNode<Camera2D>("Camera2D");
            sprite2D = characterBody2D.GetNode<Sprite2D>("Sprite2D");
            animationPlayer = characterBody2D.GetNode<AnimationPlayer>("AnimationPlayer");
            printHelper.Debug("加载完成");
            AnimationState = AnimationEnum.Idel;
            PlayAnimation();
        }
    }
}

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

如何写状态机

个人不建议用图形状态机,状态一多就容易成蜘蛛网,而且后期维护困难

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

状态初始量

状态机应该最先想状态的初始状态,一般来说是Idel战力状态

状态进入和退出

你进入了一个状态之后,一定写个如何退出这个状态。至少有一个出口

强制状态修改

有些时候我们需要将状态强制修改,比如跳跃,无论你当时是什么状态,一但按下跳跃就要播放跳跃动画。

熟练使用异步

异步我之前的博客讲解过,Godot出于UI线程的安全,不允许在新线程里面对Godot节点进行修改。

Godot UI线程,Task异步和消息弹窗通知

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

6:滑墙

图片拼接

由于我们拿到的图片是同一个角色,但是分成了两个图片,这里推荐一个图片拼接网站。

在线图片拼接

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳
Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

打开之后确认格式是正确的

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

碰撞框对应

Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳
Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳

判断是否在墙上

characterBody2D也有一个是否在墙上的判断
characterBody2D.IsOnWall()

7:蹬墙跳

先个一个蹬墙跳的速度

        /// <summary>
        /// 蹬墙跳的速度
        /// </summary>
        public readonly Vector2 WALL_JUMP_VELOCITY = new Vector2(400, -320);
            //如果按下跳跃键
           if (Input.IsActionJustPressed(ProjectSettingHelper.InputMapEnum.jump.ToString()))
           {
               
               if (characterBody2D.IsOnFloor())
               {

                   velocity.Y = JUMP_SPEED;
                   AnimationState = AnimationEnum.Jump;
               }
               else if (AnimationState == AnimationEnum.WallSliding)
               {
  
                   velocity = WALL_JUMP_VELOCITY;
                   //获取墙面的法线的方向
                   velocity.X *= characterBody2D.GetWallNormal().X;
                   AnimationState = AnimationEnum.Jump;

               }
           }

优化跳跃手感

我们之前学过一个Mathf.MoveToward,这个其实特别适合做定时器的计算。我们这里将跳跃的按键判断变成时间计时判断

      /// <summary>
      /// 跳跃重置时间
      /// </summary>
      public const float JudgeIsJumpTime = 0.5f;
      private float isJumpTime = 0;

      ......
        /// <summary>
        /// 角色移动
        /// </summary>
        /// <param name="delta"></param>
        private void PlayerMove(double delta)
        {
            var velocity = characterBody2D.Velocity;
            velocity.Y += ProjectSettingHelper.Gravity * (float)delta;
            Direction = Input.GetAxis(ProjectSettingHelper.InputMapEnum.move_left.ToString(),
                ProjectSettingHelper.InputMapEnum.move_right.ToString());
            //原本直接赋值
            //velocity.X = direction*RUN_SPEED;
            //现在使用加速度
            velocity.X = Mathf.MoveToward(velocity.X, Direction * RUN_SPEED, ACCELERATION);
            //按下跳跃键,就将跳跃时间设置为判断区间
            if (Input.IsActionJustPressed(ProjectSettingHelper.InputMapEnum.jump.ToString()))
            {
                isJumpTime = JudgeIsJumpTime;
            }
            //慢慢变成0
            isJumpTime = (float)Mathf.MoveToward(isJumpTime,0,delta);

            //如果在跳跃时间的判断内
            if (isJumpTime != 0)
            {
                
                if (characterBody2D.IsOnFloor())
                {
                    //进行跳跃之后,跳跃时间结束
                    isJumpTime = 0;
                    velocity.Y = JUMP_SPEED;
                    AnimationState = AnimationEnum.Jump;
                }
                else if (AnimationState == AnimationEnum.WallSliding)
                {
                    //进行跳跃之后,跳跃时间结束
                    isJumpTime = 0;
                    velocity = WALL_JUMP_VELOCITY;
                    //获取墙面的法线的方向
                    velocity.X *= characterBody2D.GetWallNormal().X;
                    AnimationState = AnimationEnum.Jump;

                }
            }

            characterBody2D.Velocity = velocity;
            characterBody2D.MoveAndSlide();

        }

总结

我之后写的游戏是回合制战斗游戏,这个只是为了简单的过一下Godot的基本使用,所以很多的设置我都跳过了。文章来源地址https://www.toymoban.com/news/detail-852745.html

到了这里,关于Godot.NET C#IOC重构(4-7):丝滑运动控制,角色状态机,滑墙,蹬墙跳的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity | 渡鸦避难所-6 | 有限状态机控制角色行为逻辑

    有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型 在游戏开发中应用有限状态机,能够将复杂的行为逻辑分解为一组简单的状态和转换规则,每个状态都可以独立地处理其逻辑,使代码

    2024年01月16日
    浏览(44)
  • 面对根据角色和单子状态如何有效的进行按钮权限的控制

    当阁下看到这个按钮权限控制时,该如何应对 问题1: 举个🌰,对于【待分配】这个工单状态的申请撤回按钮进行解析 问题2:举2个🌰对于【科室待接受】这个节点的申请撤回按钮进行解析 问题3:有些按钮 在多个节点要显示,比如【申请撤回、追加信息、催办】,像这种

    2024年01月19日
    浏览(42)
  • Unity 新建你的第一个游戏,以及如何按WASD控制角色运动 (Unity Demo2D)

    当你打开 Unity Hub,初始化一个 2D 项目,进入了 Unity 编辑器,你会发现在 左侧 : 一个叫 SampleScene (或者其他) 的场景 场景下有一个 Main Camera,主相机 这就是一个新建的 2D 项目自带的内容。 在 Main Camera 同级目录新建: 2D Object - Sprites - Capsule ,这里 Capsule 是精灵的种类,我们

    2024年02月02日
    浏览(46)
  • Godot 官方2D C#重构(4):TileMap进阶使用

    Godot 官方 教程 Godot 2d 官方案例C#重构 专栏 Godot 2d 重构 github地址 我们有时候需要翻转图片,比如这个门,我们想要左右对称的一组 如何绘制自行摸索 Y Sort Enable是干什么的? 因为这两个物体有前后关系,所以不能通过简单的判断Z轴来设置遮挡关系(因为Z轴上下关系唯一,

    2024年02月08日
    浏览(49)
  • SMART PLC绝对值定位往复运动控制(脉冲绝对定位+状态机编程)

    三菱FX3GA系列绝对定位指令DDRVA实现往复运动控制详细内容介绍请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/135570157 https://rxxw-control.blog.csdn.net/article/details/135570157 这篇博客我们介绍SMART PLC里如何开启绝对值定位指令和简单状态机组合实现伺服系统往复运动控制

    2024年01月20日
    浏览(50)
  • FactoryBean和BeanFactory:Spring IOC容器的两个重要角色简介

    目录 一、简介 二、BeanFactory 三、FactoryBean 四、区别 五、使用场景 总结 在Spring框架中,IOC(Inversion of Control)容器是一个核心组件,它负责管理和配置Java对象及其依赖关系,实现了控制反转(Inversion of Control)和依赖注入(Dependency Injection)两个核心概念。 控制反转是一种设

    2024年02月11日
    浏览(35)
  • 【UE4 C++】07-角色运动设置

    可以看到我们可以通过WASD控制角色前后左右移动,通过鼠标控制摄像机旋转朝向。   1.在虚幻商城中搜索“Gideon”,将该免费资源添加到工程中(大概2.6G) 2. 打开之前创建的“PlayerCharacter”  选中网格体组件,骨架网格体选择“Gideon”  调整变换 动画类选择“Giden_AnimBlue

    2024年02月04日
    浏览(43)
  • .net core di ioc

    (Dependency Injection,DI)依赖注入,又称依赖关系注入,是一种软件设计模式,也是依赖倒置原则的一种体现。 依赖倒置原则的含义如下 上层模块不依赖下层模块。二者都依赖抽象 抽象不依赖细节 细节依赖抽象 依赖注入原则有别于传统的通过new直接依赖下层模块的形式,

    2024年02月22日
    浏览(35)
  • .NET项目重构之日志服务(Serilog)

    1. 目录 2. 前言 2.1. 日志控件的选择 3. 日志配置 3.1. 控制台打印 3.2. 文件打印 4. 结语 定时任务中比较重要的一个环节就是日志记录,有了日志可以记录系统的操作过程,也可以在系统异常时方便排查错误原因。 比如定时任务经常要做的一个事情,同步其它异构系统数据到本

    2024年02月16日
    浏览(38)
  • .NET 项目重构之DDD分层搭建

    1. 目录 2. 背景 3. 环境 4. 项目搭建 5. 结语 好久没有做国内项目了,也好久没有重构项目了。正好手里有一个定时服务需要重构, 就想着正好趁这个机会学一点点新的知识。比如DDD。 Windows 11 、Visual Studio 2022 、.NET 7 、SqlServer2022 在项目文件夹中依次执行这些命令,或者放在P

    2024年02月16日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包