目錄
- 前言
- 帶上傷害
- 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
}
}