文章目录
- 前言
- 相关地址
- 环境配置
- 初始化环境配置
- 文件夹结构
- 代码结构
- 代码运行
- 资源文件导入
- 像素风格窗口环境设置
- 背景设置,Tileap使用
- 自动TileMap
- 人物场景
- 动画节点添加
- 站立节点添加
- 移动动画添加
- 通过依赖注入获取Godot的全局属性
- 项目声明
- 当前项目逻辑讲解
- 角色下降
- 添加代码
- 位置问题的思考
- 在Node2D上面挂载Lable节点
- 在CharacterBody2D下面挂载
- 解决方案
- 修改代码
- 动画节点的问题,需要重新绑定
- 为什么我要这么写
- 动画效果
- 初始化AnimationPlayer
- 输入映射
- 获取输入
- 简单移动
- 完善输入和添加动画
- 完善跳跃手感
前言
我之前解决了C# 的IOC的配置,现在来认真学习一个完整的Godot 项目。我看B站上面这个教程非常的好,所以打算用C# 去复刻一下,使用IOC依赖注入的想法。
相关地址
十分钟制作横版动作游戏|Godot 4 教程《勇者传说》#0
人物素材
环境素材
Gclove2000/GodotNet_LegendOfPaladin
环境配置
- Windows 10
- .net core 8.0
- Visual Studio 2022
- godot.net 4.2.1
初始化环境配置
Godot.NET C# 工程化开发(1):通用Nuget 导入+ 模板文件导出,包含随机数生成,日志管理,数据库连接等功能
文件夹结构
- Godot:Godot项目+主要游戏逻辑代码
- GodotProgram:帮助类
代码结构
- GodotNet_LegndOfPaladin:Godot主要逻辑
- SceneModels:场景IOC对象
- SceneScirpts:场景对应脚本
- Util: Godot API帮助类
- PackedSceneHelper:打包场景加载
- Program:IOC容器
- GodotProgram:C# 主要逻辑
- Assets:资产文件
- DB:数据库对象
- Interfaces:接口
- Service:服务
- Utils:帮助类
代码运行
资源文件导入
人物素材
环境素材
像素风格窗口环境设置
背景设置,Tileap使用
自动TileMap
Godot 官方2D C#重构(3):TileMap使用
大致实现效果
绘制TimeMap地形需要比较强的熟练度。多多联系即可
人物场景
长按左键选择站立动画
动画节点添加
站立节点添加
点击6次,添加6个关键帧
移动动画添加
和上面的一样
通过依赖注入获取Godot的全局属性
Godot的全局属性是通过字符串的方式获取的,这非常容易出问题。而且我们也希望这种配置信息能在项目启动的时候就获取
Godot ProjectSettings 字符串对应数据
项目声明
public class GodotProjectSettingHelper
{
private NlogHelper nlogHelper;
public readonly float Gravity = 0;
public GodotProjectSettingHelper(NlogHelper nlogHelper)
{
this.nlogHelper = nlogHelper;
Gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity");
}
}
当前项目逻辑讲解
所以我们新建一个场景的逻辑是
- 新增XXX.tscn
- 挂载XXXScene.sc脚本
- IOC注入XXXSceneModel.cs 类
- PackedSceneHelper添加对应的PackedScene
详情请看我的Github源码
Gclove2000/GodotNet_LegendOfPaladin
角色下降
添加代码
public class PlayerSceneModel : ISceneModel
{
private NlogHelper nlogHelper;
private GodotProjectSettingHelper godotProjectSettingHelper;
public PlayerSceneModel(NlogHelper nlogHelper,GodotProjectSettingHelper godotProjectSettingHelper) {
this.nlogHelper = nlogHelper;
this.godotProjectSettingHelper = godotProjectSettingHelper;
}
private CharacterBody2D characterBody2D;
public override void Process(double delta)
{
//给角色一个速度,因为重力是加速度,所以角色的速度会不断的增加。
characterBody2D.Velocity += new Vector2(0, godotProjectSettingHelper.Gravity * (float)delta);
//让物体以这个速度进行移动
characterBody2D.MoveAndSlide();
nlogHelper.Debug($"x:{characterBody2D.Velocity.X},y:{characterBody2D.Velocity.Y}");
}
public override void Ready()
{
nlogHelper.Debug($"当前重力值为:{godotProjectSettingHelper.Gravity}");
characterBody2D = this.Sence.GetNode<CharacterBody2D>("CharacterBody2D");
}
}
位置问题的思考
我们知道CharacterBody2D就是为了获取CollisionShape2D的位置。因为他的位置取决于重力,物理碰撞,加速度等多方面因素。相当于他的位置是自动变化的
在Node2D上面挂载Lable节点
在CharacterBody2D下面挂载
解决方案
我们只需要CharacterBody2D给我们的位置更改即可,而在Godot中,Position都是相对父节点的位置。所以每次Character移动的时候,我们将CharacterBody2D的位置获取,然后我们将Character的相对位置 设置为0即可
修改代码
public override void Process(double delta)
{
//给角色一个速度,因为重力是加速度,所以角色的速度会不断的增加。
characterBody2D.Velocity += new Vector2(0, godotProjectSettingHelper.Gravity * (float)delta);
//让物体以这个速度进行移动
characterBody2D.MoveAndSlide();
var postion = characterBody2D.Position;
characterBody2D.Position = new Vector2(0, 0);
this.Sence.Position += postion;
}
动画节点的问题,需要重新绑定
主要,如果修改动画节点的位置,会导致绑定出现问题
为什么我要这么写
因为我们不一定会写横版战斗游戏,横版战斗是有重力的,但是俯视角战斗又没有重力了,或者说不是垂直向下的重力,而是俯视角的效果。比如【以撒的结合】
动画效果
在Godot中,AnimationPlayer通过【Play】这个函数来播放动画。但是Godot中,Play是通过字符串的形式调用的。为了保证字符串的正确性,我们添加一个Enum枚举类型来对其进行限制
初始化AnimationPlayer
//枚举类型,防止拼写错误
public enum AnimationFlame { REST, idel,running }
......
public override void Ready()
{
nlogHelper.Debug($"当前重力值为:{godotProjectSettingHelper.Gravity}");
//初始化子节点
characterBody2D = this.Sence.GetNode<CharacterBody2D>("CharacterBody2D");
animationPlayer = this.Sence.GetNode<AnimationPlayer>("AnimationPlayer");
//播放动画
animationPlayer.Play(AnimationFlame.idel.ToString());
}
输入映射
我们输入上下左右,一般都是wasd,但是因为我们可能要做手柄,可能也要做移动端。所以最好设置一个输入映射好一些。
我的输入是,wsad是上下左右,【j】/【空格】是跳跃
获取输入
Godot 输入处理
我们在任意一个节点下面去获取按钮事件
public override void Process(double delta)
{
//获取move_left对应按下事件
if (Input.IsActionPressed("move_left"))
{
nlogHelper.Debug("move_left 按下");
}
}
简单移动
public const float RUN_SPEED = 200;
.......
public override void Process(double delta)
{
var velocity = new Vector2();
var direction = Input.GetAxis(InputMapEnum.move_left.ToString(), InputMapEnum.move_right.ToString());
var y = godotProjectSettingHelper.Gravity * (float)delta;
var x = direction * RUN_SPEED;
//在C# 中,
velocity = characterBody2D.Velocity;
//X是最终速度,所以不需要相加
velocity.X = x;
//给角色一个速度,因为重力是加速度,所以角色的速度会不断的增加。
velocity.Y += y;
characterBody2D.Velocity = velocity;
//让物体以这个速度进行移动
characterBody2D.MoveAndSlide();
//同步场景根节点位置
var postion = characterBody2D.Position;
characterBody2D.Position = new Vector2(0, 0);
this.Sence.Position += postion;
}
完善输入和添加动画
using Godot;
using GodotNet_LegendOfPaladin.Utils;
using GodotProgram.Interfaces;
using GodotProgram.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static GodotNet_LegendOfPaladin.Utils.GodotProjectSettingHelper;
namespace GodotNet_LegendOfPaladin.SceneModels
{
public class PlayerSceneModel : ISceneModel
{
public const float RUN_SPEED = 200;
public const float JUMP_VELOCITY = -300;
//枚举类型,防止拼写错误
public enum AnimationFlame { idel, running,jump }
#region IOC注入
private NlogHelper nlogHelper;
private GodotProjectSettingHelper godotProjectSettingHelper;
public PlayerSceneModel(NlogHelper nlogHelper, GodotProjectSettingHelper godotProjectSettingHelper)
{
this.nlogHelper = nlogHelper;
this.godotProjectSettingHelper = godotProjectSettingHelper;
}
#endregion
#region 子节点获取
private CharacterBody2D characterBody2D;
private AnimationPlayer animationPlayer;
private Sprite2D sprite2D;
public override void Ready()
{
nlogHelper.Debug($"当前重力值为:{godotProjectSettingHelper.Gravity}");
//初始化子节点
characterBody2D = this.Sence.GetNode<CharacterBody2D>("CharacterBody2D");
animationPlayer = this.Sence.GetNode<AnimationPlayer>("AnimationPlayer");
sprite2D = this.Sence.GetNode<Sprite2D>("Sprite2D");
//播放动画
animationPlayer.Play(AnimationFlame.idel.ToString());
}
#endregion
public override void Process(double delta)
{
//初始化速度
var velocity = new Vector2();
//初始化动画节点
var animation = AnimationFlame.idel;
var direction = Input.GetAxis(InputMapEnum.move_left.ToString(), InputMapEnum.move_right.ToString());
var y = godotProjectSettingHelper.Gravity * (float)delta;
var x = direction * RUN_SPEED;
var isOnFloor = characterBody2D.IsOnFloor();
//在C# 中,
velocity = characterBody2D.Velocity;
//X是最终速度,所以不需要相加
velocity.X = x;
//给角色一个速度,因为重力是加速度,所以角色的速度会不断的增加。
velocity.Y += y;
//如果在地上并且按下跳跃,则直接给一个y轴的速度
if(isOnFloor && Input.IsActionJustPressed(InputMapEnum.jump.ToString()))
{
velocity.Y = JUMP_VELOCITY;
}
if (isOnFloor)
{
if (Mathf.IsZeroApprox(direction))
{
animation = AnimationFlame.idel;
}
else
{
animation = AnimationFlame.running;
}
}
else
{
animation = AnimationFlame.jump;
}
//方向翻转
if (!Mathf.IsZeroApprox(direction))
{
sprite2D.FlipH = direction < 0;
}
characterBody2D.Velocity = velocity;
//让物体以这个速度进行移动
characterBody2D.MoveAndSlide();
//同步场景根节点位置
var postion = characterBody2D.Position;
characterBody2D.Position = new Vector2(0, 0);
this.Sence.Position += postion;
animationPlayer.Play(animation.ToString());
}
}
}
完善跳跃手感
如果玩过超级马里奥或者别的平台跳跃游戏,都知道有一个手感的东西。就是有个跳跃的提前量。我们现在是正好落地的时候按下跳跃才能跳起来,现在我们将跳跃的按钮进行存储,给与一定的缓冲间隔。
/// <summary>
/// 最长跳跃等待时间
/// </summary>
public const int JUMP_WAIT_TIME = 3000;
/// <summary>
/// 初始化的时候让时间往后退一点,防止时间过快
/// </summary>
private DateTime jumpLastTime = DateTime.Now.AddDays(-1);
......
public override void Process(double delta)
{
......
if (Input.IsActionJustPressed(InputMapEnum.jump.ToString()))
{
jumpLastTime = DateTime.Now;
}
if (isOnFloor)
{
//如果在地上并且按下跳跃,则直接给一个y轴的速度
//超时判断
if (jumpLastTime.AddMilliseconds(JUMP_WAIT_TIME) > DateTime.Now)
{
//如果刚好触发了跳跃,给个速度,将jumpLastTime推前
velocity.Y = JUMP_VELOCITY;
jumpLastTime = DateTime.Now.AddDays(-1);
}
......
}
......
}