Godot.NET C#IOC重構(11):攻擊與死亡

gclove2000發表於2024-05-03

目錄
  • 前言
  • 帶上傷害
      • Hitbox
      • Hurtbox
    • 實現效果
  • 漸變淡出
  • 新增受攻擊效果
    • Hurtbox
    • 完善Enemy狀態機
    • 結果
  • 剩下的都是邏輯完善的部分了,後面的我就跳過了。

前言

這次來深刻了解一下Godot中的傷害計算

帶上傷害

我們將之前的Hitbox和HurtBox進行了一下簡單的修改

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
    {

        [Export]
        public int Damage = 1;

        /// <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($" {Owner.Name} [Hit] {area.Owner.Name}");
            area.Hurt(this);
        }
    }


}

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
    {

        public event Action<Hitbox> HurtCallback;

        public event Action DieCallback;

        [Export]
        public int Health = 100;

        public Hurtbox()
        {

            HurtCallback += Hurt;
            DieCallback += Hurtbox_DieCallback;
        }

        private void Hurtbox_DieCallback()
        {
            GD.Print($"{Owner.Name} is Die");
        }

        /// <summary>
        /// 造成傷害
        /// </summary>
        /// <param name="num"></param>
        /// <param name="owner"></param>
        public void Hurt(Hitbox hitbox)
        {
            Health -= hitbox.Damage;
            GD.Print($"{hitbox.Owner.Name} [Hit] {Owner.Name} in {hitbox.Damage} damage, Health = {Health}");
            if (Health <= 0)
            {
                Hurtbox_DieCallback();
            }
        }


    }
}

實現效果

漸變淡出

新增受攻擊效果

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
    {
        /// <summary>
        /// 註冊傷害和死亡的委託事件
        /// </summary>
        public event Action<Hitbox> HurtCallback;

        public event Action DieCallback;

        [Export]
        public int Health = 100;

        public Hurtbox()
        {

        }

        /// <summary>
        /// 造成傷害
        /// </summary>
        /// <param name="num"></param>
        /// <param name="owner"></param>
        public void Hurt(Hitbox hitbox)
        {
            Health -= hitbox.Damage;
            GD.Print($"{hitbox.Owner.Name} [Hit] {Owner.Name} in {hitbox.Damage} damage, Health = {Health}");
            HurtCallback?.Invoke(hitbox);
            if (Health <= 0)
            {
                DieCallback?.Invoke();
            }
        }


    }
}

完善Enemy狀態機

using Bogus;
using Godot;
using GodotNet_LegendOfPaladin2.GlobalClass;
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 EnemySceneModel : ISceneModel
    {
        private PrintHelper printHelper;

        private CharacterBody2D characterBody2D;

        private CollisionShape2D collisionShape2D;

        private Sprite2D sprite2D;

        private AnimationPlayer animationPlayer;

        public RayCast2D WallCheck { get; private set; }

        public RayCast2D FloorCheck { get; private set; }

        public RayCast2D PlayerCheck { get; private set; }

        public Hitbox Hitbox { get; private set; }

        public Hurtbox Hurtbox { get; private set; }

        public enum DirectionEnum
        {
            Left = -1, Right = 1
        }

        //設定正向的方向
        private DirectionEnum direction = DirectionEnum.Right;
        public DirectionEnum Direction
        {
            get => direction;
            //這個是一個生命週期的問題,屬性的設定比樹節點的載入更早
            //,所以我們會在Ready裡面使用Direction = Direction來觸發get函式
            set
            {
                if (characterBody2D != null && direction != value)
                {
                    //printHelper.Debug($"設定朝向,{value}");
                    var scale = characterBody2D.Scale;
                    //注意反轉是X=-1。比如你左反轉到右是X=-1,你右又反轉到左也是X=-1。不是X=-1就是左,X=1就是右。
                    scale.X = -1;
                    characterBody2D.Scale = scale;
                    direction = value;
                }

            }
        }



        public enum AnimationEnum
        {
            Hit, Idle, Run, Walk,Die

        }

        public AnimationEnum Animation = AnimationEnum.Idle;

        /// <summary>
        /// 動畫持續時間
        /// </summary>
        private float animationDuration = 0;

        /// <summary>
        /// 最大速度
        /// </summary>
        public int MaxSpeed { get; set; }

        /// <summary>
        /// 加速度
        /// </summary>
        public int AccelerationSpeed { get; set; }


        /// <summary>
        /// Animation型別
        /// </summary>
        public int AnimationType { get; set; }




        public EnemySceneModel(PrintHelper printHelper)
        {
            this.printHelper = printHelper;
            printHelper.SetTitle(nameof(EnemySceneModel));
        }
        public EnemySceneModel() { }
        public override void Process(double delta)
        {
            animationDuration = (float)Mathf.MoveToward(animationDuration, 99, delta);
            SetAnimation();
            Move(delta);
            Direction = Direction;
        }

        public override void Ready()
        {
            characterBody2D = Scene.GetNode<CharacterBody2D>("CharacterBody2D");
            collisionShape2D = characterBody2D.GetNode<CollisionShape2D>("CollisionShape2D");
            sprite2D = characterBody2D.GetNode<Sprite2D>("Sprite2D");
            animationPlayer = characterBody2D.GetNode<AnimationPlayer>("AnimationPlayer");
            WallCheck = Scene.GetNode<RayCast2D>("CharacterBody2D/RayCast/WallCheck");
            FloorCheck = Scene.GetNode<RayCast2D>("CharacterBody2D/RayCast/FloorCheck");
            PlayerCheck = Scene.GetNode<RayCast2D>("CharacterBody2D/RayCast/PlayerCheck");
            Hitbox = Scene.GetNode<Hitbox>("CharacterBody2D/Hitbox");
            Hurtbox = Scene.GetNode<Hurtbox>("CharacterBody2D/Hurtbox");
            Hurtbox.HurtCallback += Hurtbox_HurtCallback;
            Hurtbox.DieCallback += Hurtbox_DieCallback;
            PlayAnimation();
            printHelper.Debug("載入成功!");
            printHelper.Debug($"當前朝向是:{Direction}");
            Direction = Direction;
        }

        private void Hurtbox_DieCallback()
        {
            printHelper.Debug("Boar is die");
            Animation = AnimationEnum.Die;
        }

        private void Hurtbox_HurtCallback(Hitbox hitbox)
        {
            printHelper.Debug($"{hitbox.Owner.Name} [Hit] {Scene.Name} in {hitbox.Damage} damage, Health = {Hurtbox.Health}");
            Animation = AnimationEnum.Hit;
        }

        #region 動畫狀態機

        public void PlayAnimation()
        {
            var animationStr = string.Format("{0}_{1}", AnimationType, Animation);
            //printHelper.Debug($"播放動畫,{animationStr}");
            animationPlayer.Play(animationStr);
        }
        public void SetAnimation()
        {

            //如果檢測到玩家,就直接跑起來
            if (PlayerCheck.IsColliding())
            {
                //printHelper.Debug("檢測到玩家,開始奔跑");
                Animation = AnimationEnum.Run;
                animationDuration = 0;
            }

            switch (Animation)
            {
                //如果站立時間大於2秒,則開始散步
                case AnimationEnum.Idle:
                    if (animationDuration > 2)
                    {
                        //printHelper.Debug("站立時間過長,開始移動");

                        Animation = AnimationEnum.Walk;
                        animationDuration = 0;
                        //如果撞牆,則反轉
                        if (WallCheck.IsColliding() || !FloorCheck.IsColliding())
                        {
                            if (Direction == DirectionEnum.Left)
                            {
                                Direction = DirectionEnum.Right;
                            }
                            else
                            {
                                Direction = DirectionEnum.Left;
                            }
                        }
                        //Direction = Direction;
                    }
                    break;
                //如果檢測到牆或者沒檢測到地面或者動畫時間超過4秒,則開始walk
                case AnimationEnum.Walk:
                    if ((WallCheck.IsColliding() || !FloorCheck.IsColliding()) || animationDuration > 4)
                    {
                        Animation = AnimationEnum.Idle;
                        animationDuration = 0;
                        //printHelper.Debug("開始閒置");
                    }
                    break;
                //跑動不會立刻停下,當持續時間大於2秒後站立發呆
                case AnimationEnum.Run:
                    if (animationDuration > 2)
                    {
                        //printHelper.Debug("追逐時間到達上限,停止");

                        Animation = AnimationEnum.Idle;
                        animationDuration = 0;
                    }
                    break;
                case AnimationEnum.Die:
                    if (!animationPlayer.IsPlaying())
                    {
                        Scene.QueueFree();
                    }
                    break;

                case AnimationEnum.Hit:
                    if(!animationPlayer.IsPlaying())
                    {
                        Animation = AnimationEnum.Idle;
                    }
                    break;
            }

            PlayAnimation();

        }
        #endregion

        #region 物體移動
        public void Move(double delta)
        {
            var velocity = characterBody2D.Velocity;
            velocity.Y += ProjectSettingHelper.Gravity * (float)delta;

            switch (Animation)
            {
                case AnimationEnum.Idle:
                    velocity.X = 0;
                    break;
                case AnimationEnum.Walk:
                    velocity.X = MaxSpeed / 3;
                    break;
                case AnimationEnum.Run:
                    velocity.X = MaxSpeed;

                    break;
            }
            velocity.X = velocity.X * (int)Direction;
            characterBody2D.Velocity = velocity;
            //printHelper.Debug(JsonConvert.SerializeObject(characterBody2D.Velocity));
            characterBody2D.MoveAndSlide();

        }
        #endregion

    }
}

結果

剩下的都是邏輯完善的部分了,後面的我就跳過了。

相關文章