- 前言
- 场景继承
- 在SceneModel里面添加基础的节点获取
- EnemyScene.cs
- EnemySceneModel.cs
- Godot Export属性和Enum
- Export默认值问题
- 修改前
- EnemySceneModel.cs
- EnemyScene.cs
- 修改后
- EnemyScene.cs
- EnemySceneModel.cs
- 修改前
- 但是有个问题,有必要这么写吗?
- 在SceneModel里面添加基础的节点获取
- 导入野猪图片
- 图片拼接
- RayCast2D 射线碰撞检测
- 碰撞检测
- 碰撞层
- 碰撞层命名
- 状态机
- 状态机的朝向问题和加载问题
- 碰撞检测
- 总结
前言
这个实在是拖了太久了,这次速战速决
场景继承
由于我们是C# ,C# 有更强的继承关系,所以我们直接继承即可
在SceneModel里面添加基础的节点获取
EnemyScene.cs
using Godot;
using GodotNet_LegendOfPaladin2.SceneModels;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GodotNet_LegendOfPaladin2.SceneScripts
{public partial class EnemyScene : Node2D{[Export]public EnemySceneModel.DirectionEnum Direction{get => Model.Direction;set => Model.Direction = value;}public EnemySceneModel Model { get; private set; }public EnemyScene(){Model = Program.Services.GetService<EnemySceneModel>();Model.Scene = this;}public override void _Ready(){Model.Ready();base._Ready();}public override void _Process(double delta){Model.Process(delta);base._Process(delta);}}
}
EnemySceneModel.cs
public class EnemySceneModel : ISceneModel
{private PrintHelper printHelper;private CharacterBody2D characterBody2D;private CollisionShape2D collisionShape2D;private Sprite2D sprite2D;private AnimationPlayer animationPlayer;public enum DirectionEnum{Left, Right}public DirectionEnum Direction { get; set; }public EnemySceneModel(PrintHelper printHelper){this.printHelper = printHelper;printHelper.SetTitle(nameof(EnemySceneModel)); }public EnemySceneModel() { }public override void Process(double delta){}public override void Ready(){characterBody2D = Scene.GetNode<CharacterBody2D>("CharacterBody2D");collisionShape2D = characterBody2D.GetNode<CollisionShape2D>("CollisionShape2D");sprite2D = characterBody2D.GetNode<Sprite2D>("Sprite2D");animationPlayer = characterBody2D.GetNode<AnimationPlayer>("AnimationPlayer");printHelper.Debug("加载成功!");printHelper.Debug($"当前朝向是:{Direction}");}
}
Godot Export属性和Enum
Godot C# 是可以导出Enum的
public enum DirectionEnum
{Left, Right
}。。。。。。[Export]
public EnemySceneModel.DirectionEnum Direction
我测试过,输入left还是right都能正确的获取到的
Export默认值问题
如果使用我的Scenes+SceneModels框架,就没有默认值了
修改前
EnemySceneModel.cs
/// <summary>
/// 最大速度
/// </summary>
public int MaxSpeed { get; set; } = 180;/// <summary>
/// 加速度
/// </summary>
public int AccelerationSpeed { get; set; } = 2000;
EnemyScene.cs
[Export]
public int MaxSpeed
{get => Model.MaxSpeed;set => Model.MaxSpeed = value;
}
[Export]
public int AccelerationSpeed
{get => Model.AccelerationSpeed;set => Model.AccelerationSpeed = value;
}
这样是不行的,没有默认值的
修改后
EnemyScene.cs
[Export]
public int MaxSpeed = 180;
[Export]
public int AccelerationSpeed = 2000;
EnemySceneModel.cs
/// <summary>
/// 最大速度
/// </summary>
public int MaxSpeed { get; set; } /// <summary>
/// 加速度
/// </summary>
public int AccelerationSpeed { get; set; }
但是有个问题,有必要这么写吗?
我测试过,如果场景有节点的话,这么写是不行的。感觉还不如每个Enemy都单独写一个好一些。
导入野猪图片
图片拼接
我们打开资源包,可以看到野猪的图片被分割了。我们肯定是喜欢尽可能的用一张图片
在线图片拼接
将图片整合在一个文件夹里面,进行数字编号
然后和我说要注册会员,我肯定是不会付的,然后我又找了个网站
做好图 图片在线拼接
这个就简单多了,直接拼接下载就行了,也不用输入宽度
导入成功!
RayCast2D 射线碰撞检测
Godot Engine 4.2 简体中文文档 所有类 RayCast2D
RayCast2D是专门用于碰撞检测的碰撞线,是只有方向和长度,没有宽度的线段。
碰撞检测
我们碰撞检测得检测三个物体,墙,地面,玩家
碰撞层
Godot 给我们提供了32个碰撞层。
碰撞分为Layer和Mask,简单来说就是类似于正极和负极。得碰撞的Layer和被碰撞的Mask是同一层才会发生碰撞。
碰撞层命名
碰撞层默认是序号1-32,我们可以给碰撞层进行命名。一般我们层的命名的顺序是,层号越低,越底层。一般我们的一楼是给环境,二楼给玩家,三楼给敌人。
一般我们先给选择Layer,然后再思考他会和哪些层发生碰撞。
- 环境:Layer1,
- 玩家:layer2,Mask1。只会和环境碰撞,但是不会和敌人碰撞。不然怪物就穿不过去了。
- 敌人:Layer3,Mask1,Mask2。会和环境,但是敌人直接是不相互碰撞的。
状态机
状态机只负责状态,不负责移动。
public enum AnimationEnum
{Hit, Idle, Run, Walk}public AnimationEnum Animation = AnimationEnum.Idle;/// <summary>
/// 动画持续时间
/// </summary>
private float animationDuration = 0;/// <summary>
/// Animation类型
/// </summary>
public int AnimationType { get; set; }public void PlayAnimation()
{var animationStr = string.Format("{0}_{1}", AnimationType, Animation);printHelper.Debug($"播放动画,{animationStr}");animationPlayer.Play(animationStr);
}public void SetAnimation()
{//如果检测到玩家,就直接跑起来if (PlayerCheck.IsColliding()){Animation = AnimationEnum.Run;animationDuration = 0;}switch (Animation){//如果站立时间大于2秒,则开始散步case AnimationEnum.Idle:if (animationDuration > 2){Animation = AnimationEnum.Walk;animationDuration = 0;}break;//如果检测到墙或者没检测到地面或者动画时间超过4秒,则开始walkcase AnimationEnum.Walk:if (WallCheck.IsColliding() || !FloorCheck.IsColliding() || animationDuration > 4){Animation = AnimationEnum.Idle;animationDuration = 0;}break;//跑动不会立刻停下,当持续时间大于2秒后站立发呆case AnimationEnum.Run:if(animationDuration > 2){Animation = AnimationEnum.Idle;animationDuration = 0;}break;}
}
建议所有的线性计算都用Mathf.MoveToward,如果直接用 time += delta容易造成溢出的Bug。
完整代码
using Godot;
using GodotNet_LegendOfPaladin2.Utils;
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 enum DirectionEnum{Left, Right}public DirectionEnum Direction { get; set; }public enum AnimationEnum{Hit, Idle, Run, Walk}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();}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");PlayAnimation();printHelper.Debug("加载成功!");printHelper.Debug($"当前朝向是:{Direction}");}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;}break;//如果检测到墙或者没检测到地面或者动画时间超过4秒,则开始walkcase 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;}PlayAnimation();}}
}
状态机的朝向问题和加载问题
using Bogus;
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 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 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}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");PlayAnimation();printHelper.Debug("加载成功!");printHelper.Debug($"当前朝向是:{Direction}");Direction = Direction;}#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秒,则开始walkcase 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;}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}
}
总结
这次解决了简单的敌人加载的问题,我暂时不了解这个场景继承有什么用。可能是我的IOC写的太麻烦了,如果真继承还是挺麻烦的。后面写多了看看怎么解决。我是打算用TypeID来解决同类敌人的问题的。