首页 > 编程语言 >Godot.NET C#IOC重构(9-10):三连击,攻击框

Godot.NET C#IOC重构(9-10):三连击,攻击框

时间:2024-05-03 19:11:05浏览次数:26  
标签:10 Godot 连击 AnimationEnum private AnimationState Attack using public

目录

前言

这篇博客来深入讲解一下Godot中的AnimationPlayer

AnimationPlayer和AnimatedSprite2D

AnimatedSprite2D就是一个简单的帧动画组件,只能用于播放简单的帧动画。
AnimationPlayer是一个可以实现复杂的帧动画的组件,可以添加各种关键帧。

将导出属性添加到关键帧里面。

我们在C# 中添加导出属性

[Export]
public bool CanCombo
{
  get => Model.CanCombo;
  set { Model.CanCombo = value; }
}

我们添加这个CanCombo的用意是用来检测动画是否播放到位,有点类似于LOL里面的攻击前摇和攻击后摇的感觉。

状态机构建

核心代码

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 enum AnimationEnum { REST, Idel, Running, Jump, Fall, Land, WallSliding, Attack_1, Attack_2, Attack_3 }

        /// <summary>
        /// 可以移动的状态
        /// </summary>
        public AnimationEnum[] CanMoveAnimation = [
            AnimationEnum.Idel, AnimationEnum.Running ,AnimationEnum.Fall,AnimationEnum.WallSliding
        ];

        private AnimationEnum animationEnum = AnimationEnum.Idel;
        public AnimationEnum AnimationState
        {
            get => animationEnum;
            private set
            {
                if (value != animationEnum)
                {
                    printHelper.Debug($"{value}");
                }
                animationEnum = value;
            }
        }

        public bool IsLand { get; private set; } = true;

        public float Direction { get; private set; } = 0;




 

        private void SetAnimation()
        {
            isComboRequest = Input.IsActionPressed($"{ProjectSettingHelper.InputMapEnum.attack}") && canCombo;
            //isComboRequest = !Input.IsActionJustReleased(ProjectSettingHelper.InputMapEnum.attack.ToString());

            var isPlaying = animationPlayer.IsPlaying();
            switch (AnimationState)
            {
                case AnimationEnum.Idel:
                    if (Input.IsActionJustPressed($"{ProjectSettingHelper.InputMapEnum.attack}"))
                    {
                        AnimationState = AnimationEnum.Attack_1;
                    }
                    else if (!Mathf.IsZeroApprox(Direction))
                    {
                        AnimationState = AnimationEnum.Running;
                    }

                    break;
                case AnimationEnum.Jump:
                    if (characterBody2D.Velocity.Y < 0)
                    {
                        AnimationState = AnimationEnum.Fall;
                    }
                    else if (characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.WallSliding;

                    }

                    break;
                case AnimationEnum.Running:
                    if (Input.IsActionJustPressed($"{ProjectSettingHelper.InputMapEnum.attack}"))
                    {
                        AnimationState = AnimationEnum.Attack_1;
                    }
                    else 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;

                            }
                        });
                    }
                    else if (characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.WallSliding;

                    }
                    break;
                case AnimationEnum.Land:

                    break;

                case AnimationEnum.WallSliding:
                    if (!characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.Fall;
                    }
                    break;
                case AnimationEnum.Attack_1:

                    if (isComboRequest && !isPlaying)
                    {
                        AnimationState = AnimationEnum.Attack_2;
                        isComboRequest = false;
                    }
                    else if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;

                case AnimationEnum.Attack_2:
                    if (isComboRequest && !isPlaying)
                    {
                        AnimationState = AnimationEnum.Attack_3;
                        isComboRequest = false;

                    }
                    else if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;
                case AnimationEnum.Attack_3:
                    if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;
            }


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

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

            animationPlayer.Play(AnimationState.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
    {
        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;

        /// <summary>
        /// 蹬墙跳的速度
        /// </summary>
        public readonly Vector2 WALL_JUMP_VELOCITY = new Vector2(400, -320);

        private bool canCombo = false;

        /// <summary>
        /// 是否能够连击
        /// </summary>
        public bool CanCombo
        {
            get => canCombo; set
            {
                printHelper.Debug($"设置canComBo:{value}");
                canCombo = value;
            }
        }

        #endregion

        private Sprite2D sprite2D;

        private CharacterBody2D characterBody2D;

        private AnimationPlayer animationPlayer;

        private Camera2D camera2D;

        private bool isComboRequest = false;

        public enum AnimationEnum { REST, Idel, Running, Jump, Fall, Land, WallSliding, Attack_1, Attack_2, Attack_3 }

        /// <summary>
        /// 可以移动的状态
        /// </summary>
        public AnimationEnum[] CanMoveAnimation = [
            AnimationEnum.Idel, AnimationEnum.Running ,AnimationEnum.Fall,AnimationEnum.WallSliding
        ];

        private AnimationEnum animationEnum = AnimationEnum.Idel;
        public AnimationEnum AnimationState
        {
            get => animationEnum;
            private set
            {
                if (value != animationEnum)
                {
                    printHelper.Debug($"{value}");
                }
                animationEnum = value;
            }
        }

        public bool IsLand { get; private set; } = true;

        public float Direction { get; private set; } = 0;



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

        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 (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;

            if (CanMoveAnimation.Contains(AnimationState))
            {
                characterBody2D.MoveAndSlide();

            }

        }

        private void SetAnimation()
        {
            isComboRequest = Input.IsActionPressed($"{ProjectSettingHelper.InputMapEnum.attack}") && canCombo;
            //isComboRequest = !Input.IsActionJustReleased(ProjectSettingHelper.InputMapEnum.attack.ToString());

            var isPlaying = animationPlayer.IsPlaying();
            switch (AnimationState)
            {
                case AnimationEnum.Idel:
                    if (Input.IsActionJustPressed($"{ProjectSettingHelper.InputMapEnum.attack}"))
                    {
                        AnimationState = AnimationEnum.Attack_1;
                    }
                    else if (!Mathf.IsZeroApprox(Direction))
                    {
                        AnimationState = AnimationEnum.Running;
                    }

                    break;
                case AnimationEnum.Jump:
                    if (characterBody2D.Velocity.Y < 0)
                    {
                        AnimationState = AnimationEnum.Fall;
                    }
                    else if (characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.WallSliding;

                    }

                    break;
                case AnimationEnum.Running:
                    if (Input.IsActionJustPressed($"{ProjectSettingHelper.InputMapEnum.attack}"))
                    {
                        AnimationState = AnimationEnum.Attack_1;
                    }
                    else 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;

                            }
                        });
                    }
                    else if (characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.WallSliding;

                    }
                    break;
                case AnimationEnum.Land:

                    break;

                case AnimationEnum.WallSliding:
                    if (!characterBody2D.IsOnWall())
                    {
                        AnimationState = AnimationEnum.Fall;
                    }
                    break;
                case AnimationEnum.Attack_1:

                    if (isComboRequest && !isPlaying)
                    {
                        AnimationState = AnimationEnum.Attack_2;
                        isComboRequest = false;
                    }
                    else if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;

                case AnimationEnum.Attack_2:
                    if (isComboRequest && !isPlaying)
                    {
                        AnimationState = AnimationEnum.Attack_3;
                        isComboRequest = false;

                    }
                    else if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    break;
                case AnimationEnum.Attack_3:
                    if (!isPlaying)
                    {
                        AnimationState = AnimationEnum.Idel;
                    }
                    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();
        }

        /// <summary>
        /// 设置相机
        /// </summary>
        /// <param name="rect2"></param>
        public void SetCameraLimit(Rect2 rect2)
        {
            camera2D.LimitLeft = (int)rect2.Position.X;
            //camera2D.LimitTop = (int)rect2.Position.Y;
            camera2D.LimitRight = (int)rect2.End.X;
            camera2D.LimitBottom = (int)rect2.End.Y;
            //printHelper.Debug(JsonConvert.SerializeObject(rect2));
        }
    }
}

实现效果

碰撞框和受攻击框

全局类

Godot Engine 4.2 简体中文文档 编写脚本 C#/.NET C# 全局类

这个的优点就是组件化,扩展性比较的强。类似于C# 的扩展函数,在原本的基础上面进行功能的加强。

推荐大家把Godot的高级API了解一下

HitBox:攻击框

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GodotNet_LegendOfPaladin2.GlobalClass
{
    [GlobalClass]
    public partial class Hitbox:Area2D
    {
        /// <summary>
        /// 在实例化事件中添加委托
        /// </summary>
        public Hitbox() {
            AreaEntered += Hitbox_AreaEntered;
        }

        /// <summary>
        /// 当有Area2D进入时
        /// </summary>
        /// <param name="area"></param>
        private void Hitbox_AreaEntered(Area2D area)
        {

            //当进入的节点是继承Area2D的HurtBox的时候
            if (area is Hurtbox)
            {

                OnAreaEnterd((Hurtbox)area);
            
            }
        }

        /// <summary>
        /// 攻击判断
        /// </summary>
        /// <param name="area"></param>
        public void OnAreaEnterd(Hurtbox area)
        {
            GD.Print($"[Hit] {Owner.Name} => {area.Owner.Name}");
        }
    }


}

HurtBox:受击框

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GodotNet_LegendOfPaladin2.GlobalClass
{
    [GlobalClass]
    public partial class Hurtbox:Area2D
    {
        
    }
}

实现效果

添加Player攻击

我们给三个Attack攻击都设置好了Disable关键帧之后,测试一下

总结

我其实省略了大部分的代码,详细的代码可以去看原视频的讲解

标签:10,Godot,连击,AnimationEnum,private,AnimationState,Attack,using,public
From: https://www.cnblogs.com/gclove2000/p/18171042

相关文章

  • P1525 [NOIP2010 提高组] 关押罪犯
    原题链接题解这题我采用了带权并查集的做法,0代表两囚犯处于监狱,1代表两囚犯不同监狱。根据题意,我们想让冲突值尽可能的小,那么我们要先把仇恨值大的两罪犯放在不同监狱;即按仇恨值从大到小的去判断每条仇恨信息。(贪心思想)code #include<bits/stdc++.h>usingnamespacestd;......
  • [转帖]10 Hardware Components of Oracle Exadata
    https://docs.oracle.com/en/engineered-systems/exadata-database-machine/dbmso/hardware-components-exadata-db-machine.html#GUID-EBA9369F-A2AB-449F-A361-40F48A5B37C2 OracleExadata consistsofdatabaseservers,storageservers,andthenetworkcomponent......
  • [转帖]Introducing Exadata Cloud@Customer X10M
    https://blogs.oracle.com/database/post/introducing-exadata-cloudcustomer-x10m  ExtremeScalewithDramaticallyImprovedPricePerformanceWeareexcitedtoannounceExadataCloud@CustomerX10M,thelatestgenerationoftheworld’sbeston-premises......
  • [转帖]Introducing Exadata X10M: Extreme Scalability and Dramatically Improved Pr
    https://blogs.oracle.com/database/post/exadata-x10m  Oracleisexcitedtoannouncethenextgenerationof ExadataDatabaseMachine platform,the OracleExadataX10M.Builtonmorethanadecadeofhigh-performancedatabasehardwareandsoftware......
  • 代码随想录算法训练营第10天 | 栈和队列 232.用栈实现队列 225.用队列实现栈
    leetcode232.用栈实现队列题目232.用栈实现队列请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现MyQueue类:voidpush(intx)将元素x推到队列的末尾intpop()从队列的开头移除并返回元素intpeek()返回队列开头的......
  • Django Error: [WinError 10013] An attempt was made to access a socket in a way f
      D:\06softw-dev-202306\manage.pyrunserverWatchingforfilechangeswithStatReloaderPerformingsystemchecks...Systemcheckidentifiednoissues(0silenced).May03,2024-10:02:12Djangoversion3.2.18,usingsettings'MPDB.settings......
  • CPU利用率100%怎么处理?
    --查看内核节拍率[root@p4-oadmnewdb01~]#grep'CONFIG_HZ='/boot/config-4.19.90-23.32.v2101.ky10.x86_64CONFIG_HZ=250--查看不同场景的CPU时间cat/proc/stat|grep^cpu[root@p4-oadmnewdb01~]#cat/proc/stat|grep^cpucpu1597988157210022139005384242060......
  • 从0到10Wqps,大厂的智能客服平台,如何实现架构演进?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • Codeforces 1044F DFS
    考虑到存在方案使得以\(u\)为起点还能走出原树的条件。能发现是以\(u\)为根,在原树上新添加的边只会是返祖边而不会有横叉边。这是因为如果有横叉边就肯定会在遍历到一边的点后先通过这条边走到另一边的点,就算上了这条边就不是原树了。那么考虑\((x,y)\),合法的\(u\)需要......
  • 对于 CF1107E 中 dp 状态设计的一点想法
    不太想发到洛谷讨论区,就往这里放了。我觉得现有的题解都没说明白为什么本题的状态和转移能覆盖所有情况,并且感觉也非常不自然,没见过的话感觉挺难发现这么一个东西。然而这个dp其实是可以自然地推导出来的。首先发现这个过程非常难以描述,主要原因在于很难刻画一个局面。然而,如......