目录
前言
这次来深刻了解一下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
}
}