Godot.NET C#IOC重构(8):敌人野猪

news/2024/10/14 16:22:35

目录
  • 前言
  • 场景继承
    • 在SceneModel里面添加基础的节点获取
      • EnemyScene.cs
      • EnemySceneModel.cs
      • Godot Export属性和Enum
      • Export默认值问题
        • 修改前
          • EnemySceneModel.cs
          • EnemyScene.cs
        • 修改后
          • EnemyScene.cs
          • EnemySceneModel.cs
    • 但是有个问题,有必要这么写吗?
  • 导入野猪图片
    • 图片拼接
  • 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来解决同类敌人的问题的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/26694.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

类模板的简单应用(用于存储不同类型数据的类容器)

类模板应用 explicit explicit 是一个关键字,用于指定该构造函数是显式构造函数。在C++中,当一个类的构造函数只有一个参数时,它可以被用于隐式类型转换,这可能会导致意想不到的行为和潜在的错误。为了避免这种情况,可以使用 explicit 关键字来声明该构造函数,表示禁止隐…

Unity 热更--AssetBundle学习笔记 0.7

AssetBundle AB包是什么? AssetBundle又称AB包,是Unity提供的一种用于存储资源的资源压缩包。 Unity中的AssetBundle系统是对资源管理的一种扩展,通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,可以动态地加载和卸载AB包,继而有选择地加载内容。 AB包的…

2024年4月文章一览

2024年4月编程人总共更新了5篇文章: 1.2024年3月文章一览 2.《自动机理论、语言和计算导论》阅读笔记:p139-p171 3.《自动机理论、语言和计算导论》阅读笔记:p172-p224 4.《自动机理论、语言和计算导论》阅读笔记:p225-p260 5.《自动机理论、语言和计算导论》阅读笔记:p26…

开源文档预览项目 kkFileView (9.9k star) ,快速入门

kkFileView 是一款文件文档在线预览解决方案,采用流行的 Spring Boot 框架构建,易于上手和部署。 该项目基本支持主流办公文档的在线预览,包括但不限于 doc、docx、xls、xlsx、ppt、pptx、pdf、txt、zip、rar,以及图片、视频、音频等格式。1 Docker 部署拉取镜像:# 网络环…

模拟集成电路设计系列博客——6.2.4 电流模式转换器

6.2.4 电流模式转换器 电流模式D/A转换器与电阻型转换器非常类似,但是可以用于更高速的应用。其基本的思想是将电流切换到输出或到地,如下图所示:因此输出电流通过\(R_F\)转换成电压,而每个电流源的上节点总是保持在地电压。电流型D/A转换器将在后续的温度计码D/A转换器章节…

模拟集成电路设计系列博客——6.2.3 电荷重分布开关电容转换器

6.2.3 电荷重分布开关电容转换器 电荷重分布开关电容转换器的基本思想是将开关电容增益放大器的输入电容替换为一个可编程电容阵列(PCA,Programmable Capacitor Array),如下图所示:如我们之前在开关电容增益放大器时讨论的一样,上图中的电路对于放大器输入失调电压,\(1/…

CF981F Round Marriage

传送门首先最小化最大,一眼鉴定为二分。二分这个最大值 \(k\),问题变成判断是否能让新郎新娘匹配,每一对距离 \(\le k\)。 如果把新郎新娘视作二分图,每个点只和距离 \(\le k\) 的点连边,问题就是求是否有完美匹配。 完美匹配判定,可以联想到 Halls 定理。 先把环复制一遍…

linux12-root

linux12-root为root用户设置密码 sudo passwd rootsu su, switch user, 切换用户 选项 -, 表示是否在切换用户后加载环境变量, 建议加 参数user不填写, 默认切换root用户 # 切换到root用户 su - root可以通过exit回退到上一个用户, 也可以用快捷键ctrl+dsudo super user do, 为…