Csharp戈多

声明

Export (PropertyHint)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//基础声明语法
[Exprot]
public int age{set;get;}

//限制最大最小
[Export(PropertyHint.Range,"0,100,1")]

//整值枚举
[Export(PropertyHint.Enum,"A,B")] //输出1,2
//字符串枚举
[Export(PropertyHint.EnumSuggestion ,"A,B")] //输出A,B

//曲线
[Export(PropertyHint.ExpEasing,"positive_only")]

//锁 Link
[Export(PropertyHint.Link)]

//标志flags
[Export(PropertyHint.Flags,"A:16,B,C")]


//注册类到godot
[GlobalClass]
//自定义图标
[Icon("icon.svg")]
  • File 路径
  • Dir dir
  • GlobalFile 觉得路径
  • ResourceType 资源路径
  • MultilineText 大文本编辑器
  • Expression 字符是Expression
枚举值 描述
None 该属性没有编辑器提示。
Range 表示一个整数或浮点数属性应该在指定的范围内,通过提示字符串 “min,max” 或 “min,max,step” 来指定。还可以包括 “or_greater” 和/或 “or_less”。
Enum 表示一个整数或字符串属性是一个枚举值,可以从提示字符串指定的列表中选择。
EnumSuggestion 类似于 Enum,但是属性仍然接受任意值并且可以为空。值列表用于建议可能的取值。
ExpEasing 表示一个单精度浮点数属性应该通过指数缓动函数进行编辑。可以包括 “attenuation” 和 “positive_only”。
Link 表示一个向量属性应该允许其组件进行链接。
Flags 表示一个整数属性是一个具有命名位标志的位掩码。
Layers2DRender 表示一个整数属性是使用命名的 2D 渲染层的位掩码。
Layers2DPhysics 表示一个整数属性是使用命名的 2D 物理层的位掩码。
Layers2DNavigation 表示一个整数属性是使用命名的 2D 导航层的位掩码。
Layers3DRender 表示一个整数属性是使用命名的 3D 渲染层的位掩码。
Layers3DPhysics 表示一个整数属性是使用命名的 3D 物理层的位掩码。
Layers3DNavigation 表示一个整数属性是使用命名的 3D 导航层的位掩码。
LayersAvoidance 表示一个整数属性是使用命名的避免层的位掩码。
File 表示一个字符串属性是一个文件路径。编辑时会弹出文件对话框。
Dir 表示一个字符串属性是一个目录路径。编辑时会弹出目录对话框。
GlobalFile 类似于 File,但用于全局路径。
GlobalDir 类似于 Dir,但用于全局路径。
SaveFile 类似于 File,但用于保存文件的对话框。
GlobalSaveFile 类似于 SaveFile,但用于全局路径。
ResourceType 表示一个属性是衍生自 Godot.Resource 的类型的实例。
MultilineText 表示一个字符串属性是带有换行的文本。
Expression 表示一个字符串属性是一个 Godot.Expression。
PlaceholderText 表示一个字符串属性的输入字段为空时应显示占位文本。
ColorNoAlpha 表示一个 Godot.Color 属性应该在不影响其透明度的情况下进行编辑。
ObjectId 表示属性的值是作为对象 ID 编码的对象,其类型在提示字符串中指定。
TypeString 表示属性是字符串时,表明该属性代表特定的类型(类)。
NodePathToEditedNode, ObjectTooBig, NodePathValidTypes, IntIsObjectid, IntIsPointer, ArrayType, LocaleId, LocalizableString, NodeType, HideQuaternionEdit, Password 这些已经废弃或将在未来删除的提示,具体功能不再使用。
Max 表示 PropertyHint 枚举的大小(通常用于内部使用,不是用于实际属性提示)。

请注意,”Max” 通常不是用于实际属性编辑提示的枚举值,而是可能用于枚举内部的计数或边界检查。在实际使用中,你不会将 “Max” 分配给任何属性的 PropertyHint。

信号

  • godot内置信号
1
2
3
4
5
6
[Signal]
public delegate void 我的信号EventHandler(); //声明信号

EmitSignal(nameof(我的信号)); //发送信号

我的信号+=函数 //链接信号
  • cs信号实现
1
2
3
4
5
6
7
8
9
public delegate 委托函数(int age);  //声明委托

public event 委托函数 信号; //声明事件

信号.Invoke(); //发送信号

信号+=函数; //链接信号

public void 函数(int age);

计时器

  • 方法计时器
1
2
3
4
5
6
7
8
public async Task SomeFunction()
{
//func();
GD.Print("开始计时");
await ToSignal(GetTree().CreateTimer(5.0f,true), SceneTreeTimer.SignalName.Timeout);
GD.Print("计时结束");
//无敌=false;
}
  • 阻断延时
1
System.Threading.Thread.Sleep(5000); // 休眠1秒  
  • 异步延时
1
2
3
4
5
6
7
8
9
10
11
12
13
static async Task<string> FetchDataFromWebAsync()
{
Console.WriteLine($"FetchDataFromWebAsync: 启动web请求...");

// 使用HttpClient发送HTTP GET请求(这是异步的)
var response = await client.GetAsync("https://example.com"); // 假设这个URL返回一个字符串
var content = await response.Content.ReadAsStringAsync(); // 读取响应内容
//await Task.Delay(5000);
Console.WriteLine($"FetchDataFromWebAsync: Web请求完成.");

// 返回结果(这里简单地返回响应内容,实际中可能是解析后的数据)
return content;
}
  • 计时器1
1
2
3
4
5
6
7
System.Timers.Timer countdownTimer;  
int countdownSeconds = 1; // 假设倒计时10秒

countdownTimer=new System.Timers.Timer(1000);
countdownTimer.Elapsed+=函数;
countdownTimer.AutoReset = true; // 设置为true以便定时器持续触发
countdownTimer.Enabled = true; // 启动定时器

读写

  • godot-API实现(这只是大概,实际需要自己需求更改)

    • 读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Variant 读取数据(string paths)
{
FileAccess dile= FileAccess.Open(paths,FileAccess.ModeFlags.Read); //通过路径打开文件,打开文件返回空
if(FileAccess.GetOpenError()==Error.Ok) //检查是否打开文件
{
GD.Print("打开成功");
}else
{
GD.Print("打开失败");
return false;
}
string 解析=dile.GetAsText(); //以String返回整个文件,文件会按照UTF-8编码解析
Variant? 转换=Json.ParseString(解析); //将解析到的字符串转换为GODOT可以识别的Variant数据
if(转换==null)
{
GD.Print("解析数据失败");
return false;
}
dile.Close();
return (Variant)转换; //将Variant数据返回
}
  • 写入

    1
    2
    3
    4
    5
    6
    7
    8
    public static bool 写入数据(string paths,Variant datas)
    {
    FileAccess dile= FileAccess.Open(paths,FileAccess.ModeFlags.Write); //通过路径打开文件
    string 转换=Json.Stringify(datas); //将数据转换为字符串
    dile.StoreString(转换); //将字符串写入文件
    dile.Close(); //关闭文件
    return true;
    }
  • CS-API实现

    • XLSX转换JSON 库:(ClosedXML),(Newtonsoft )

      1
      2
      3
      4
      5
      6
      7
      8
      using Godot;
      using System;
      using System.Collections.Generic;
      using System.Text.Json;
      using System.IO;

      using Newtonsoft.Json;
      using ClosedXML.Excel;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public void duqu()
{
using (var workbook = new XLWorkbook("./Json/xxxc.xlsx"))
{
var worksheet = workbook.Worksheet(1); // 假设要读取第一个工作表

var dataTable = new System.Data.DataTable();
//bool firstRow = true; // 用来标记是否是第一行
// 从工作表数据填充到DataTable
foreach (var row in worksheet.RowsUsed())
{
//if (firstRow) // 如果是第一行,跳过并将标记设为false
//{
// firstRow = false;
// continue;
//}

if (row.RowNumber() == 1) // 处理表头
{
foreach (var cell in row.CellsUsed())
{
dataTable.Columns.Add(cell.Value.ToString());
}
}
else // 处理数据行
{
var dataRow = dataTable.NewRow();
foreach (var cell in row.CellsUsed())
{
dataRow[cell.Address.ColumnNumber - 1] = cell.Value.ToString();
}
dataTable.Rows.Add(dataRow);
}
}

// 将DataTable转换为JSON
string json = JsonConvert.SerializeObject(dataTable, Formatting.Indented);
// string json = JsonSerializer.Serialize(dataTable, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("./Json/output.json", json);

// 打印JSON数据
//Console.WriteLine(json);
GD.Print(json);
}
}

设计模式

对象池

  • 泛型对象池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    //对象池泛型
    public partial class OBjiectPool<T> : Node where T : Node, new()
    {
    private List<T> _pool;
    private int _maxSize;
    public OBjiectPool(int maxSize = 100)
    {
    _pool = new List<T>();
    _maxSize = maxSize;
    }

    //获取对象
    public T GetObject()
    {
    //如果长度不等于0则长度-1返回临时变量
    //创建一个临时对象获取对象所有的对象
    //按所有删除对象
    //返回对象
    if (_pool.Count > 0)
    {
    int lastIndex = _pool.Count - 1;
    T obj = _pool[lastIndex];
    _pool.RemoveAt(lastIndex);
    return obj;

    //或者对象小于最大对象数量,
    //返回一个对象
    }
    else if (_pool.Count < _maxSize)
    {
    return new T();
    }

    //否则报错对象池已满
    else
    {
    throw new InvalidOperationException("Object pool is full");
    }
    }

    /// <summary>
    /// 返回对象
    /// </summary>
    /// <param name="obj"></param>
    public void RetuenObject(T obj)
    {
    _pool.Add(obj);
    }

    /// <summary>
    /// 清空对象化池
    /// </summary>
    public void ClearPool()
    {
    _pool.Clear();
    }
    }
  • 数组对象池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /// <summary>
    /// 数组对象池对象池
    /// </summary>
    public class ObjectPond
    {
    public interface Pood
    {
    void InitPond();
    }
    public Enemy[] enemies { get; set; } = new Enemy[10];

    public void Init(Godot.Collections.Dictionary EnemyValuerArray)
    {
    enemies[0] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[1] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[2] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[3] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[4] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[5] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[6] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[7] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[8] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    enemies[9] = ActivityObject.CreateObject(EnemyValuerArray) as Enemy;
    }
    //将存在的对象都返回
    //遍历数组对象,如果存在就返回,并接收当前运行
    public Enemy CreaObject()
    {
    for (int i = 0; i < enemies.Length; i++)
    {
    if (enemies[i] != null)
    {
    Enemy enemy = enemies[i];
    enemies[i] = null;
    //GD.Print(enemies);
    enemy.InitPond();
    return enemy;
    }
    }
    return null;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /// <summary>
    /// 将对象收回对象池的方法
    /// </summary>
    /// <param name="enemy"></param>
    public void Ret(Enemy enemy)
    {
    for (int i = 0; i < enemies.Length; i++)
    {
    if (enemies[i] == null && enemy is Enemy)
    {
    enemies[i] = enemy;
    //GD.Print(enemy+"回到对象池");
    return;
    }
    }
    }
    //检查数组内是否有对象,没有返回false否则为true
    public bool isObject()
    {
    foreach (Enemy i in enemies)
    {
    if (i != null)
    {
    return true;
    }
    }
    return false;
    }

方法

算法

随机

1
2
3
4
//玩家范围内生成 GD.Randf()返回随机浮点
float x = PlayerP1.Position.X + GD.Randf() * 600 - 10;
float y = PlayerP1.Position.Y + GD.Randf() * 600 - 10;
Vector2 pos = new Vector2(x, y); //这个位置就是随机位置的值,给敌人位置即可
  • 返回随机位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//返回随机位置
public Vector3 Get_Vector(Vector3 center, float range)
{
Vector3 random_offset =new Vector3(
GetRandomFloat(-range, range),
GetRandomFloat(-range, range),
GetRandomFloat(-range, range)
);

return center + random_offset;
}
//返回随机浮点
static float GetRandomFloat(float min, float max)
{
Random random = new Random();
return (float)(random.NextDouble() * (max - min) + min);
}

追踪

  • A追踪B
1
2
3
4
5
public Vector3 Get_Vleor(NodeA,NodeB)
{
Vector3 dir=(NodeA-NodeB).初始化向量();
return dir;
}

模板

怪物根节点

  • 怪物的根节点,通过怪物对象生成怪物,他只负责生成,不负责怪物逻辑,他存储了玩家对象,供怪物对象调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using Godot;
using System;

public partial class EnemyFactory : Node3D
{
/// <summary>
/// 怪物体
/// </summary>
[Export]
public CharacterBody3D EnemyBody { get; set; }
/// <summary>
/// 生成位置
/// </summary>
[Export]
public Vector3 GenerateLocation { get; set;}
/// <summary>
/// 生成范围
/// </summary>
[Export]
public float GenerateScope { get; set; }
/// <summary>
/// 怪物根节点
/// </summary>
Node3D EnemyRoot { get; set; }
/// <summary>
/// 怪物最大数量
/// </summary>
[Export]
public int MaxEnemyCount { get; set; }
/// <summary>
/// 计时器
/// </summary>
public Timer timer { get; set; }=new Timer();
/// <summary>
/// 间隔时间
/// </summary>
[Export]
public float time { get; set; }=1;

// Called when the node enters the scene tree for the first time.
public override void _EnterTree()
{



//GlobalPosition = Get_Vector(GenerateLocation,GenerateScope);
}
public override void _Ready()
{
//生成计时器并开启
timer.WaitTime = time;
timer.Autostart = true;
timer.Timeout += On_Timer_timeout;
AddChild(timer);
//添加怪物节点的根,怪物将生成在这个节点下
EnemyRoot = new Node3D();
EnemyRoot.Name="EnemyRoot";
AddChild(EnemyRoot);
}

// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}

private void On_Timer_timeout()
{
if (EnemyRoot.GetChildren().Count >= 50)
{
return;
}
if (EnemyBody != null)
{
CharacterBody3D EnemyBody1 = (CharacterBody3D)EnemyBody.Duplicate();
EnemyBody1.Position = Get_Vector(GenerateLocation, GenerateScope);
EnemyRoot.AddChild(EnemyBody1);

}
}
/// <summary>
/// 返回随机位置
/// </summary>
/// <param name="center"></param>
/// <param name="range"></param>
/// <returns></returns>
public Vector3 Get_Vector(Vector3 center, float range)
{
Vector3 random_offset =new Vector3(
GetRandomFloat(-range, range),
GetRandomFloat(-range, range),
GetRandomFloat(-range, range)
);

return center + random_offset;
}
/// <summary>
/// 返回一个随机浮点数
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
static float GetRandomFloat(float min, float max)
{
Random random = new Random();
return (float)(random.NextDouble() * (max - min) + min);
}
}

怪物 快速制作一个怪物追踪玩家的怪物对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using Godot;
[Tool]

public partial class Enemy : CharacterBody3D
{
[Export]
public PackedScene EnemyTscn { get; set; }

[Export]
public Mesh mesh { get; set; }
[Export]
public CollisionShape3D shape3D { get; set; }
[Export]
public Shape3D shape { get; set; }

[Export]
public Node3D Player { get; set; }

public const float Speed = 5.0f;
public const float JumpVelocity = 4.5f;
public override void _EnterTree()
{
shape3D = new CollisionShape3D();
if (shape3D != null)
{
shape3D.Shape=shape;
AddChild(shape3D);
}
//if(EnemyTscn != null)
//{
// MeshInstance3D meshInstance = EnemyTscn.Instantiate() as MeshInstance3D;
// return;
//}
if(mesh != null)
{
MeshInstance3D EnemyMesh =new MeshInstance3D();
EnemyMesh.Mesh = mesh;
AddChild(EnemyMesh);
}
}

public override void _PhysicsProcess(double delta)
{
Vector3 velocity = Velocity;

// Add the gravity.
if (!IsOnFloor())
{
velocity += GetGravity() * (float)delta;
}

if (Player != null)
{
Vector3 dir = (Player.GlobalPosition - GlobalPosition).Normalized();
velocity = dir * (float)delta;
}

Velocity = velocity;
MoveAndSlide();
}
}

武器

  • 自动瞄准武器,通过获取怪物对象来调整方向并向怪物发射子弹对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using Godot;
using Godot.Collections;

public partial class SelfFiringWeapon : Node3D
{
// Called when the node enters the scene tree for the first time.
[Export]
public CharacterBody3D Bullet { get; set; }
[Export]
public MeshInstance3D Weapon { get; set; }
[Export]
public Marker3D BulletLocation { get; set; }
[Export]
public Marker3D BulletDirection { get; set; }
[Export]
public EnemyFactory EnemyRoot { get; set; }

[Export]
public Timer Timer { get; set; }

public override void _Ready()
{
Timer.Timeout += ON_TimerTimeout; // 绑定超时事件
}

// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{


CharacterBody3D enemy = GetNearestEnemy(this); // 获取最近的敌人
if (enemy != null) // 如果有敌人
{
LookAt(enemy.GlobalPosition); // 面向敌人
GD.Print("正在跟踪敌人");
}
}

public void ON_TimerTimeout()
{
CharacterBody3D weapon = Bullet.Duplicate() as CharacterBody3D; // 获取武器
GetParent().AddChild(weapon);
weapon.GlobalPosition = BulletLocation.GlobalPosition; // 设置武器位置

Vector3 Dir=(BulletDirection.GlobalPosition-BulletLocation.GlobalPosition).Normalized(); // 计算武器方向
weapon.Velocity=Dir * 10f; // //Dir * 100f; // 设置武器速度
// 添加武器到场景
GD.Print("发射子弹");
}


public CharacterBody3D GetNearestEnemy(Node3D player)
{
if (EnemyRoot != null && EnemyRoot.EnemyRoot == null)
{
return null;
}
// 获取所有子节点(怪物)
Array<Node> enemiesArray = EnemyRoot.EnemyRoot.GetChildren();
Enemy nearestEnemy = null;
float nearestDistance = float.PositiveInfinity; // 使用正无穷大作为初始值

// 遍历所有怪物
foreach (Enemy enemy in enemiesArray)
{
if (enemy is Enemy) // 假设敌人是 CharacterBody3D 类型
{
// 计算与玩家的距离
float distance = player.GlobalPosition.DistanceTo(enemy.GlobalPosition);
if (distance < nearestDistance) // 找到最近的怪物
{
nearestDistance = distance;
nearestEnemy = (Enemy)enemy; // 更新最近的怪物
}
}
}
return nearestEnemy; // 返回最近的怪物
}
}