Gdextension

源码

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef THREEN_H
#define THREEN_H

#include <godot_cpp/classes/node.hpp>

//#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp> //输出字符串用
using namespace godot;

//#define VARIANT_ARG_MAX 8
//#define VARIANT_ARG_DECLARE const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const //Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8

class Myclass : public Node
{
GDCLASS(Myclass, Node);

private:
protected:
static void _bind_methods();
public:
GAME();
~GAME();
};
#endif // THREEN_H

_bind_methods()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Player::_bind_methods()
{
//注册方法
ClassDB::bind_method(D_METHOD("cout_property"), &Player::cout_property);

//注册属性方法
ClassDB::bind_method(D_METHOD("set_array", "GBarray"), &Player::set_array);
ClassDB::bind_method(D_METHOD("get_array"), &Player::get_array);

//注册属性
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE" -99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_speed", "get_speed");

//注册信号
ADD_SIGNAL(MethodInfo("palyer_sig",PropertyInfo(Variant::OBJECT, "key1"), PropertyInfo(Variant::OBJECT, "key2")));

//注册枚举
BIND_ENUM_CONSTANT(MON);
//注册flag
BIND_BITFIELD_FLAG(Run);
//注册常量
BIND_CONSTANT(MAX_HEALTH);
}

注册属性

1
2
3
bool br = true;
void set_br(bool key);
bool get_br();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool AbilitySystem::get_br()
{
return br;
}

void AbilitySystem::set_br(bool key)
{
br = key;
}


void AbilitySystem::_bind_methods()
{
ClassDB::bind_method(D_METHOD("set_br","br"), &AbilitySystem::set_br);
ClassDB::bind_method(D_METHOD("get_br"), &AbilitySystem::get_br);

ClassDB::add_property("AbilitySystem",
PropertyInfo(Variant::BOOL, "br"),
"set_br",
"get_br"
);
}

声明:枚举,Flag,常量

  • 枚举
1
2
3
4
5
6
7
8
enum testEnum {
MON,
TUE,
WED = 7,
};

//底下
VARIANT_ENUM_CAST(Test::testEnum);
  • flag
1
2
3
4
5
6
7
enum testFlag 
{
Run,
Idle
};
//低下
VARIANT_BITFIELD_CAST(Test::testFlag);
  • 常量
1
2
3
4
//常量声明
enum {
MAX_HEALTH = 100
};

信号

1
2
3
4
//发送
emit_signal("position_changed", this, new_position);
//接受信号
connect("visibility_changed", Callable(this, "cout_sig"));

数组

  • 普通数组
1
2
3
4
5
6
7
8
9
Array _array;
void set_array(Array p_array)
{
_array=p_array;
}
Array get_array()
{
return _array;
}
  • 特定数组
1
2
3
4
5
6
7
8
9
10
11
12
PackedVector2Array polygon;
void set_polygon(const PackedVector2Array& p_polygon) {
polygon = p_polygon;
}

PackedVector2Array get_polygon() const {
return polygon;
}
//注册
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Player::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &Player::get_polygon);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");

GDVIRTUAL_BIND(_process, “delta”);

实例对象

  • 注意:在实例化GODOT对象时应该使用memnew来实例化,不应该用new,因为GODOT没法释放new可能导致内存泄漏
1
Node node=memnew(Node);

调用单例

1
2
//Input单例     需要包含头文件
Input::get_singleton()->get_vector("ui_left", "ui_right", "ui_up", "ui_down");

重写虚函数

  • 虚函数声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//开始时调用
void _ready();
void Player::_ready()
{
};

//物理帧循环
void _physics_process(float delta);
void Player::_physics_process(float delta)
{
};

//检测输入
void _input(const Ref<InputEvent>& event); //需要头文件 #include <godot_cpp/classes/input_event.hpp>
void Player::_input(const Ref<InputEvent>& event)
{
}
  • node虚函数源码
1
2
3
4
5
6
7
8
9
10
virtual void _process(double delta);
virtual void _physics_process(double delta);
virtual void _enter_tree();
virtual void _exit_tree();
virtual void _ready();
virtual PackedStringArray _get_configuration_warnings() const;
virtual void _input(const Ref<InputEvent> &event);
virtual void _shortcut_input(const Ref<InputEvent> &event);
virtual void _unhandled_input(const Ref<InputEvent> &event);
virtual void _unhandled_key_input(const Ref<InputEvent> &event);

ADD_PROPERTY的用法

1
2
//检查器仅显示特定资源
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "_resource",PROPERTY_HINT_RESOURCE_TYPE, "ReleStats"), "set_resource", "get_resource");

enum PropertyHint

  1. PROPERTY_HINT_NONE:没有特定的提示。
  2. PROPERTY_HINT_RANGE:表示属性是一个范围值(例如,一个滑动条)。
  3. PROPERTY_HINT_ENUM:表示属性是一个枚举类型,可以从预定义的值中选择。
  4. PROPERTY_HINT_ENUM_SUGGESTION:与 PROPERTY_HINT_ENUM 类似,但可能提供建议的值。
  5. PROPERTY_HINT_EXP_EASING:可能与动画的缓动(easing)相关,特别是指数缓动。
  6. PROPERTY_HINT_LINK:表示属性可以链接到另一个属性或资源。
  7. PROPERTY_HINT_FLAGS:表示属性是一个标志位集合。
  8. PROPERTY_HINT_LAYERS_(如 PROPERTY_HINT_LAYERS_2D_RENDER):与图层相关的提示,用于 2D 或 3D 渲染、物理或导航。
  9. PROPERTY_HINT_FILE:提示用户选择文件。
  10. PROPERTY_HINT_DIR:提示用户选择目录。
  11. PROPERTY_HINT_GLOBAL_FILE/DIR:与 PROPERTY_HINT_FILE/DIR 类似,但可能指向全局路径或资源。
  12. PROPERTY_HINT_RESOURCE_TYPE:提示用户选择特定类型的资源。
  13. PROPERTY_HINT_MULTILINE_TEXT:表示属性是一个多行文本字段。
  14. PROPERTY_HINT_EXPRESSION:可能与表达式求值或编写表达式相关。
  15. PROPERTY_HINT_PLACEHOLDER_TEXT:在文本字段中显示占位符文本。
  16. PROPERTY_HINT_COLOR_NO_ALPHA:颜色选择器不显示 alpha 通道。
  17. PROPERTY_HINT_OBJECT_ID:表示属性是一个对象 ID。
  18. PROPERTY_HINT_TYPE_STRING:属性是一个表示类型的字符串。
  19. PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE:表示属性是一个节点路径,指向正在编辑的节点。
  20. PROPERTY_HINT_OBJECT_TOO_BIG:可能用于警告用户对象太大或复杂。
  21. PROPERTY_HINT_NODE_PATH_VALID_TYPES:提示用户选择特定类型的节点路径。
  22. PROPERTY_HINT_SAVE_FILE/GLOBAL_SAVE_FILE:提示用户保存文件,可能是全局的。
  23. PROPERTY_HINT_INT_IS_OBJECTID/POINTER:整数属性实际上是对象 ID 或指针。
  24. PROPERTY_HINT_ARRAY_TYPE:提示用户选择数组的元素类型。
  25. PROPERTY_HINT_LOCALE_ID:表示属性是区域设置(locale)ID。
  26. PROPERTY_HINT_LOCALIZABLE_STRING:表示属性是一个可以本地化的字符串。
  27. PROPERTY_HINT_NODE_TYPE:提示用户选择特定类型的节点。
  28. PROPERTY_HINT_HIDE_QUATERNION_EDIT:在编辑器中隐藏四元数编辑(可能是笔误,应为“quaternion”)。
  29. PROPERTY_HINT_PASSWORD:属性是一个密码字段,应该隐藏输入。
  30. PROPERTY_HINT_MAX:枚举中的最大值,通常用于范围检查。

这些提示为编辑器和开发者提供了关于如何解释和编辑属性的上下文信息,从而提高了工作效率和用户体验。

enum Type

  1. NIL:表示无或空值。
  2. 原子类型 (atomic types)
    • BOOL:布尔值(真/假)。
    • INT:整数。
    • FLOAT:浮点数。
    • STRING:字符串。
  3. 数学类型 (math types)
    • VECTOR2, VECTOR2I:二维向量,前者为浮点数,后者为整数。
    • RECT2, RECT2I:二维矩形,同样有浮点数和整数版本。
    • VECTOR3, VECTOR3I:三维向量,浮点数和整数版本。
    • TRANSFORM2D:二维变换矩阵。
    • VECTOR4, VECTOR4I:四维向量,浮点数和整数版本。
    • PLANE:平面(在数学中通常表示为点法式)。
    • QUATERNION:四元数,用于表示3D旋转。
    • AABB:Axis-Aligned Bounding Box,轴对齐包围盒,常用于碰撞检测。
    • BASIS:基础矩阵,通常用于3D变换。
    • TRANSFORM3D:三维变换矩阵。
    • PROJECTION:投影矩阵,可能用于图形渲染。
  4. 其他类型 (misc types)
    • COLOR:颜色值。
    • STRING_NAME:字符串名称,可能是优化后的字符串类型,用于快速比较和哈希。
    • NODE_PATH:节点路径,在场景图中定位节点。
    • RID:Resource ID,资源标识符,用于标识资源。
    • OBJECT:对象引用。
    • CALLABLE:可调用的函数、方法或信号。
    • SIGNAL:信号,用于在对象之间通信。
    • DICTIONARY:字典或映射,存储键值对。
    • ARRAY:数组,可以存储任何类型的元素。
  5. 类型化数组 (typed arrays)
    • 这些是固定类型的数组,存储特定类型的元素,如字节、整数、浮点数、向量等。它们可能用于优化性能或内存使用。
  6. VARIANT_MAX:枚举中的最大值,通常用于范围检查或其他需要枚举最大值的情况。

Scons

配置文件

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
#!/usr/bin/env python

libname = "libmyts"

env = SConscript("godot-cpp/SConstruct")

env.Append(CPPPATH=["src/","src/store/","src/player/"]) #指定头文件位置


sources = Glob("#src/*.cpp") # 匹配源文件位置
sources+=Glob("#src/store/*.cpp")
sources+=Glob("#src/player/*.cpp")


import editor_builders

docs_xml = [] #声明一个之
docs_xml += Glob("#src/doc_classes/*.xml") #将右边的值加到左边的值
docs_xml = sorted(docs_xml) #排序
docs_header = "#src/doc_data_{}.gen.h".format(libname) #名字
env.Command(docs_header, docs_xml, env.Action(editor_builders.make_doc_header, "Generating documentation header."))

if env['platform'] == 'linux':
pass
elif env['platform'] == 'windows':
libpath = "F:/AGodot/GDE/godotGED4.3/bin/libtest{}{}".format(env["suffix"], env["SHLIBSUFFIX"]) #文件名及生成路径
sharedlib = env.SharedLibrary(libpath,sources) #生成动态库(文件名,构建的cpp路径)
#env.Depends(sharedlib,docs_header) #依赖
Default(sharedlib) #构建文件

Gdextension

配置文件

1
2
3
4
5
6
7
8
9
10
11
[configuration]

entry_symbol = "myts_library_init"
compatibility_minimum = 4.1

[libraries]

[icons]
GDExample = "res://icons/gd_example.svg"

windows.x86_64.debug = "res://bin/libtest.windows.template_debug.x86_64.dll"

实现源码

2D

基础移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Player::move_role(float delta)
{

Vector2 direction = Input::get_singleton()->get_vector("ui_left", "ui_right", "ui_up", "ui_down");
Vector2 velovty = get_velocity();
if (direction != Vector2(0,0))
{
velovty = direction * speed;
}
else
{
velovty=velovty.move_toward(Vector2(0, 0), speed);
};
set_velocity(velovty);

}

怪物追踪玩家

  • 怪物移动向量=(玩家位置-怪物全局位置)*速度
  • 怪物位置=怪物移动向量

暴击公式

1
2
3
4
5
6
暴击随机率=100-暴击面板
var 暴击率=randi_range(暴击随机率,0)
if 暴击率==0:
//暴击伤害加成
else:
暴击伤害为1

3D

基础移动

1
//暂时没有

单例

存储系统

  • 头文件
1
2
3
4
5
6
//返回单例
static Store* Store_static();
//数据写入
bool store_write(Variant content,String path);
//数据读取
Variant store_read(String path);
  • 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Store* Store::Store_static()
{
static Store store;
return &store;
}


bool Store::store_write(Variant content, String path)
{
Ref<FileAccess> write = FileAccess::open(path,FileAccess::WRITE);
write->store_string(content);
return false;
}


Variant Store::store_read(String path)
{
Ref<FileAccess> read = FileAccess::open(path, FileAccess::READ);
Variant variant=read->get_as_text();
Variant js = JSON::parse_string(variant);

return js;
}

GUI

按钮

1
//暂时没有

插件

背包

1
//暂时没有

设计模式

简介

模式 详情 模式 详情
命令模式 通过命令控制对象行为,需要的话可以记录状态并实现撤回 更新方法模式 对每个对象单独调用更新函数
享元模式 多个对象共用同一个资源或者属性 游戏循环模式 游戏循环底层
状态模式 通过变量切换对象的不同状态 双缓冲模式 绘制相关,当A在执行是B在准备,B在执行时A在准备
单例模式 静态方法,全局变量,可以全局访问的属性和方法 字节码模式 自定义编程语言?
原型模式 复制当前对象生成新的对象,(泛型,模板)。
类似场景文件,预制体的实现
事件队列 将事件加入队列,等待执行处理,类似观察者模式,多出缓存区域
观察者模式 主题对象向所有观察者对象发送信号,在godot中可通过信号实现 服务器定位器 类似单例
子类沙箱模式 将子对象重复的方法写在基类,避免高耦合度
类型对象 对象可通过外部配置文件修改产生新的对象,如表格和json等
组件模式 对对象添加组件进行使用,挂脚本就是一种组件模式

命令模式

在游戏编程中,命令模式(Command Pattern)是一种常见的设计模式,它允许将请求封装为对象,从而实现请求的排队、记录、撤销/重做以及将请求的执行者与请求本身解耦。这对于游戏开发中的多种场景,如用户界面交互、游戏逻辑执行、AI行为模拟等,都是非常有用的。

命令模式的基本结构

  1. 命令接口(Command):定义一个命令的接口,声明执行的方法。
  2. 具体命令(ConcreteCommand):实现命令接口,并关联一个接收者对象。调用接收者对象的相应操作以执行请求。
  3. 接收者(Receiver):知道如何执行与请求相关的操作。任何类都可能作为一个接收者。
  4. 调用者(Invoker):要求命令执行一个请求。它包含一个或多个命令对象,并调用命令对象的执行方法。
  5. 客户端(Client):创建具体的命令对象,并设置它的接收者。

游戏编程中的命令模式示例

  • 将需要执行的对象具体行为通过命令对象来调用

  • 当按下1键玩家的名字改为小海,按下2键又变为小月

  1. 接收者(Receiver)
1
2
3
4
5
6
7
8
9
10
class 玩家
{
public:
玩家() {};
~玩家() {};

string name{"小月"};
private:

};
  1. 命令接口(Command)
1
2
3
4
5
6
7
8
9
//命令接口
class 抽象命令
{
public:
virtual void 执行(string string)=0;
virtual void 撤销()=0;
private:

};

2.
3. 具体命令(ConcreteCommand)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//实现命令对象
class 命令 :public 抽象命令
{
public:
玩家* 玩家指针; //指向玩家实例
string name; //记录玩家属性

命令(玩家&p_玩家):玩家指针(&p_玩家) {};

void 执行(string p_name)
{
name = 玩家指针->name; //记录玩家属性
玩家指针->name = p_name; //指向玩家行为,通过字符串更改name属性
cout << 玩家指针->name << endl;

}
void 撤销()
{
玩家指针->name = name; //撤销玩家属性,将name属性更改为上一步记录的值
cout << 玩家指针->name << endl;
}
};
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
int main() {
玩家 玩家实例;
命令 命令实例(玩家实例); //初始化,引用玩家对象

while (true)
{
int op;
cin >> op;
if (op == 1)
{
命令实例.执行("小海");
}
else if (op == 2)
{
命令实例.撤销();
}
else
{
break;
}
}



return 0;
}
  1. 调用者(Invoker)

调用者可以是游戏的主循环、UI管理器或AI系统等。在这个示例中,我们可以假设有一个UI管理器,它负责处理玩家的点击事件。

  1. 客户端(Client)

客户端通常是游戏的初始化代码或配置部分。在这里,我们创建具体的命令对象,并将其与接收者关联起来。然后,我们将命令对象传递给调用者(如UI管理器)。

命令模式的优点

  • 解耦:命令模式将请求的执行者与请求本身解耦,使得请求的发送者和接收者可以独立地变化。
  • 可扩展性:新的命令可以很容易地添加到系统中,而无需修改现有的类。
  • 可撤销/重做:通过实现undo()等方法,可以很容易地支持命令的撤销和重做功能。
  • 队列和日志:命令对象可以存储在队列中以实现命令的批量执行或延迟执行,也可以存储在日志中以记录游戏的历史状态。

注意事项

  • 命令模式可能会增加系统的复杂性和开销,因此应谨慎使用。在简单场景中,可能不需要使用命令模式。
  • 在实现命令模式时,要确保正确地管理命令对象的生命周期,以避免内存泄漏等问题。

享元模式

在游戏编程中,享元模式(Flyweight Pattern)是一种用于优化性能的设计模式,它通过共享来避免大量拥有相同内容对象的开销。在游戏开发中,这种模式特别适用于那些需要创建大量相似对象的情况,如玩家角色、怪物、道具等。

享元模式的核心思想是将对象的内部状态(即不随环境改变而改变的属性)和外部状态(即随环境改变而改变的属性)进行区分。通过共享内部状态,可以显著减少对象的数量,从而降低内存消耗和提高性能。

在游戏编程中,享元模式的应用场景可能包括:

  1. 角色创建:在大型多人在线游戏(MMO)中,可能存在大量的玩家角色。这些角色可能有很多相同的属性(如种族、职业、等级等),而装备、技能、位置等属性则各不相同。通过享元模式,可以将这些共享的属性存储在一个享元对象中,并为每个角色分配一个包含其特有属性的实例。
  2. 怪物生成:在游戏中,可能需要生成大量的怪物。这些怪物可能具有相同的外观、属性和行为,但位置、状态等属性可能各不相同。通过享元模式,可以共享怪物的内部状态,从而减少内存消耗。
  3. 道具管理:在游戏中,可能存在大量的道具,这些道具可能具有相同的属性(如名称、类型、功能等),但数量、位置等属性可能各不相同。通过享元模式,可以共享道具的内部状态,从而优化内存使用。

在实现享元模式时,通常需要定义两个主要类:

  1. 享元类(Flyweight):用于存储对象的内部状态。这个类通常具有一个静态的工厂方法或工厂类来管理享元对象的创建和共享。
  2. 上下文类(Context):用于存储对象的外部状态。这个类持有享元对象的引用,并提供操作这些对象的方法。当需要操作对象时,上下文类会将享元对象与其外部状态结合起来。

需要注意的是,享元模式虽然可以显著优化性能,但也可能增加系统的复杂性。因此,在使用享元模式时,需要权衡其优点和缺点,并根据具体需求进行选择和调整。

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
#include <iostream>
#include <list>
#include <unordered_map>
#include <memory>

using namespace std;
// 享元接口
class MonsterFlyweight {
public:
virtual ~MonsterFlyweight() = default;
virtual void attack() const = 0;
virtual void setLocation(const std::string& location) = 0;
virtual const std::string& getLocation() const = 0;
};

// 具体的享元类
class Monster : public MonsterFlyweight {
private:
std::string monsterType;
int attackPower;
std::string location; // 外部状态通常不应在享元类中直接存储,但为简单起见,这里这样做

public:
Monster(const std::string& monsterType, int attackPower)
: monsterType(monsterType), attackPower(attackPower) {}

void attack() const override {
std::cout << monsterType << " monster attacks with power " << attackPower << std::endl;
}

void setLocation(const std::string& location) override {
this->location = location;
}

const std::string& getLocation() const override {
return location;
}
};

// 享元工厂类
class MonsterFactory {
private:
std::unordered_map<std::string, std::unique_ptr<MonsterFlyweight>> monsterPool;

public:
MonsterFlyweight* getMonster(const std::string& monsterType, int attackPower) {
auto it = monsterPool.find(monsterType);
if (it == monsterPool.end()) {
it = monsterPool.emplace(std::piecewise_construct,
std::forward_as_tuple(monsterType),
std::forward_as_tuple(std::make_unique<Monster>(monsterType, attackPower))).first;
}
return it->second.get();
}
};

// 上下文类(用于存储外部状态)
class MonsterContext {
private:
MonsterFlyweight* monster;
std::string location; // 外部状态在上下文中存储

public:
MonsterContext(MonsterFlyweight* monster) : monster(monster) {}

void setLocation(const std::string& location) {
this->location = location;
monster->setLocation(location); // 通知享元更新其位置(这里只是为了演示)
}

void attack() {
monster->attack();
std::cout << "Monster is now at " << location << std::endl;
}
};

// 客户端代码
int main() {
MonsterFactory factory;

// 创建并共享享元对象
MonsterFlyweight* goblin = factory.getMonster("Goblin", 10);
MonsterFlyweight* orc = factory.getMonster("Orc", 15);

// 使用上下文来设置外部状态并执行操作
MonsterContext goblinContext(goblin);
goblinContext.setLocation("Cave Entrance");
goblinContext.attack();

MonsterContext orcContext(orc);
orcContext.setLocation("Forest Clearing");
orcContext.attack();

return 0;
}

状态模式

在游戏编程中,状态模式(State Pattern)是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。状态模式使得对象看起来好像修改了它的类,但它实际上是通过将对象的行为委托给其当前状态对象来改变其行为的。这提供了一种使行为依赖于对象内部状态变化的解决方案,而不是通过大量的条件语句和复杂的逻辑来管理这些变化。

以下是一个简单的游戏编程中状态模式的示例,假设我们有一个角色(如玩家角色)可以处于不同的状态(如空闲、移动、攻击等):

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
#include <iostream>
#include <string>

// 状态基类
class State {
public:
virtual ~State() = default;
virtual void handle(class Character* character) = 0;
};

// 空闲状态
class IdleState : public State {
public:
void handle(Character* character) override {
std::cout << "Character is idle." << std::endl;
// 切换到其他状态...
}
};

// 移动状态
class MoveState : public State {
public:
void handle(Character* character) override {
std::cout << "Character is moving." << std::endl;
// 角色移动的逻辑...
// 切换到其他状态...
}
};

// 攻击状态
class AttackState : public State {
public:
void handle(Character* character) override {
std::cout << "Character is attacking." << std::endl;
// 角色攻击的逻辑...
// 切换到其他状态...
}
};

// 角色类
class Character {
private:
State* state;

public:
Character() : state(new IdleState()) {}

void setState(State* newState) {
delete state;
state = newState;
}

void update() {
state->handle(this);
}

// 其他角色相关的方法...
};

int main() {
Character character;

character.update(); // 输出 "Character is idle."

character.setState(new MoveState());
character.update(); // 输出 "Character is moving."

character.setState(new AttackState());
character.update(); // 输出 "Character is attacking."

// 清理动态分配的状态对象(在实际应用中可能需要更复杂的内存管理)
delete character.state;

return 0;
}

在这个例子中,Character 类有一个 State 类型的成员变量 state,它指向当前的状态对象。Character 类有一个 setState 方法来更改其当前状态,以及一个 update 方法来调用当前状态对象的 handle 方法。每个状态类(如 IdleStateMoveStateAttackState)都实现了 handle 方法,该方法定义了当角色处于该状态时应该执行的行为。

通过使用状态模式,我们可以轻松地添加新的状态和行为,而无需修改 Character 类或现有的状态类。这提高了代码的可维护性和可扩展性。

子类沙箱模式

  • 子类沙箱模式(Subclass Sandbox Pattern)是一种设计模式,主要用于在面向对象编程中管理子类与基类之间的行为和操作。该模式的核心思想是将一些通用的操作或行为定义为基类的方法,而子类则通过重载或实现这些方法来实现特定的行为。通过这种方式,可以将所有与这些操作相关的耦合都聚集到基类,从而使子类与其他类更加独立。

​ 子类沙箱模式通常适用于以下情况:

  1. 有一个带有大量子类的基类。
  2. 基类能够提供所有子类可能需要执行的操作集合。
  3. 在子类之间有重叠的代码,你想让它们之间更容易地共享代码。
  4. 你想使这些继承类和程序的其他代码之间的耦合最小化。

在子类沙箱模式中,基类通常包含一些抽象方法或虚方法,这些方法定义了子类需要实现的行为。子类通过重载这些方法来实现自己的特定行为。同时,基类还可以包含一些受保护的(protected)方法,这些方法提供了一些子类在实现特定行为时需要调用的操作。这些受保护的方法可以被视为“沙箱”中的安全原语,子类可以在其中安全地执行特定的操作。

子类沙箱模式的优点包括:

  1. 减少代码冗余:通过在基类中定义通用的操作,可以避免在子类中重复编写相同的代码。
  2. 提高代码的可维护性:由于所有的耦合都聚集到基类,子类与其他类之间的依赖关系变得更加清晰和简单,从而提高了代码的可维护性。
  3. 促进代码复用:子类可以通过继承基类来重用基类中定义的操作,从而提高了代码的复用性。

然而,子类沙箱模式也存在一些缺点。例如,当基类包含大量的方法时,它可能会变得庞大且难以维护。此外,如果子类需要访问基类中的受保护方法,则可能会破坏封装性并增加类之间的耦合度。因此,在使用子类沙箱模式时,需要权衡其优缺点并根据具体的应用场景来选择是否使用该模式。

  • 在C++中,子类沙箱模式的一个示例可以通过创建一个基类,该类包含一些虚函数(表示子类可能需要实现的行为),以及一个或多个受保护的方法(表示子类可以安全调用的操作)。子类将继承这个基类,并根据需要重载虚函数来实现特定的行为。

    以下是一个简单的C++示例,展示了子类沙箱模式的概念:

    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
    #include <iostream>
    #include <string>

    // 基类,包含虚函数和受保护的方法
    class Base {
    public:
    // 虚函数,子类需要实现
    virtual void executeAction() = 0;

    protected:
    // 受保护的方法,子类可以调用
    void protectedOperation(const std::string& message) {
    std::cout << "Protected operation called with message: " << message << std::endl;
    }
    };

    // 子类,继承基类并重载虚函数
    class Derived : public Base {
    public:
    // 重载基类中的虚函数
    void executeAction() override {
    std::cout << "Derived class action starting..." << std::endl;
    // 调用基类中的受保护方法
    protectedOperation("Hello from Derived!");
    std::cout << "Derived class action finished." << std::endl;
    }
    };

    // 主函数,用于测试
    int main() {
    // 创建子类对象
    Derived derivedObj;

    // 调用子类对象的虚函数,该函数会调用基类中的受保护方法
    derivedObj.executeAction();

    return 0;
    }

    在这个示例中,Base 类是一个基类,它包含一个纯虚函数 executeAction() 和一个受保护的方法 protectedOperation()Derived 类是 Base 的子类,它重载了 executeAction() 函数并在其中调用了基类的 protectedOperation() 方法。在 main() 函数中,我们创建了一个 Derived 类的对象,并调用了它的 executeAction() 方法,该方法会输出一些信息并调用基类的受保护方法。

    请注意,这个示例主要是为了演示子类沙箱模式的概念,而不是一个完整的应用程序。在实际的应用程序中,基类和子类可能会包含更多的属性和方法,并且它们之间的关系可能会更加复杂。

工厂模式

函数工厂

  • 在工厂类声明一个返回类的实例的函数

  • 这段代码创建了一个怪物基类,然后通过继承实现了不同怪物,然后创建工厂来返回不同怪物的实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <iostream>
    #include <list>
    using namespace std;

    //怪物的基类
    class 怪物基类
    {
    public:
    怪物基类(string p_name):name(p_name) {};
    virtual ~怪物基类() {};
    void 共同方法()
    {
    cout << name << endl;
    }

    private:
    string name;
    };

    //怪物实现,都继承之怪物基类
    class 野猪 :public 怪物基类
    {
    public:

    野猪(string p_name) :怪物基类(p_name) {};
    ~野猪() {};
    

    };

    class 黑熊 :public 怪物基类
    {
    public:

    黑熊(string p_name) :怪物基类(p_name) {};
    ~黑熊() {};
    

    };

    class 蜘蛛 :public 怪物基类
    {
    public:

    蜘蛛(string p_name) :怪物基类(p_name) {};
    ~蜘蛛() {};
    

    };

    //声明一个怪物工厂,用于生产怪物
    class 怪物工厂
    {
    public:

    怪物基类* 创建野猪(string p_name)
    {
        return new 野猪(p_name);
    }
    怪物基类* 创建黑熊(string p_name)
    {
        return new 野猪(p_name);
    }
    怪物基类* 创建蜘蛛(string p_name)
    {
        return new 野猪(p_name);
    }
    

    };

    int main() {

    怪物工厂 工厂;
    怪物基类* new野猪=工厂.创建野猪("讨之助");
    怪物基类* new黑熊 = 工厂.创建黑熊("小明");
    怪物基类* new蜘蛛 = 工厂.创建蜘蛛("假面骑士");
    new野猪->共同方法();
    new黑熊->共同方法();
    new蜘蛛->共同方法();
    
    return 0;
    

    }

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
![www](/img/函数工厂模式.png)

#### 抽象工厂

+ 通过抽象工厂和抽象产品生产一系列的产品,比图我要进货手机,有安卓和苹果等品牌,我就需要这个手机工厂生产了这两种品牌的手机

```cpp
#include <iostream>
#include <list>
using namespace std;

//产品抽象类,所以苹果产品都继承这个类
class 苹果
{
public:
virtual ~苹果() {};
virtual void 输出产品名字()=0;
};

//苹果电脑实现
class 苹果电脑 :public 苹果
{
public:
~苹果电脑() {};
void 输出产品名字()
{
cout << "苹果电脑" << endl;
}
};

//苹果手机实现
class 苹果手机 :public 苹果
{
public:
~苹果手机() {};
void 输出产品名字()
{
cout << "苹果手机" << endl;
}
};

//安卓产品抽象类,索引安卓产品都继承这个类
class 安卓
{
public:
virtual ~安卓() {};
virtual void 输出产品名字()=0;
};

//安卓点啊弄实现
class 安卓电脑 :public 安卓
{
public:
~安卓电脑() {};
void 输出产品名字()
{
cout << "安卓电脑" << endl;
}
};

//安卓手机实现
class 安卓手机 :public 安卓
{
public:
~安卓手机() {};
void 输出产品名字()
{
cout << "安卓手机" << endl;
}
};

//工厂抽象类
class 数码工厂
{
public:
virtual ~数码工厂() {};
virtual 安卓* 生产安卓产品() = 0;
virtual 苹果* 生产苹果产品() = 0;
};

//工厂实现,可以生产各种品牌的电脑
class 电脑工厂 :public 数码工厂
{
public:
~电脑工厂() {};
安卓* 生产安卓产品()
{
return new 安卓电脑;
}
苹果* 生产苹果产品()
{
return new 苹果电脑;
}
};
//工厂实现,可以生产各种品牌的手机
class 手机工厂 :public 数码工厂
{
public:
~手机工厂() {};
安卓* 生产安卓产品()
{
return new 安卓手机;
}
苹果* 生产苹果产品()
{
return new 苹果手机;
}
};


int main() {

//我需要生产电脑
电脑工厂* 电脑商 = new 电脑工厂;
苹果* 苹果pro = 电脑商->生产苹果产品();
安卓* 安卓平板 = 电脑商->生产安卓产品();
苹果pro->输出产品名字();
安卓平板->输出产品名字();


//我需要手机
手机工厂* 手机商 = new 手机工厂();
苹果* 苹果15 = 手机商->生产苹果产品();
苹果15->输出产品名字();
return 0;
}

抽象工厂模式

  • 怪物工厂
  • 通过抽象怪物工厂创建一系列的类

抽象工厂模式

在游戏开发中,抽象工厂模式(Abstract Factory Pattern)的使用通常出现在以下场景:

  1. 需要创建多个相关或相互依赖的对象时:抽象工厂模式允许你定义一个接口来创建一组相关的对象,而无需指定它们的具体类。这在游戏开发中很常见,因为游戏通常包含许多相互关联的对象,如角色、武器、装备等。
  2. 需要处理多个产品族时:游戏可能会根据不同的版本、难度或模式而有不同的产品族(即一组相关的对象)。例如,一个游戏可能有不同的角色族(如战士、法师、盗贼等),每个角色族都有自己的武器、装备和技能。使用抽象工厂模式,你可以为每个角色族创建一个具体的工厂,该工厂负责创建该角色族所需的所有对象。
  3. 需要隐藏对象的创建细节时:在游戏开发中,你可能希望隐藏对象的创建细节,以便在不影响客户端代码的情况下更改对象的实现。抽象工厂模式允许你定义一个接口来创建对象,而客户端代码只需与该接口交互,而无需知道对象的实际创建过程。
  4. 需要提高系统的可扩展性和可维护性时:随着游戏的发展,你可能需要添加新的角色、武器或装备。使用抽象工厂模式,你可以轻松地添加新的产品族或产品,而无需修改现有的客户端代码。这有助于提高系统的可扩展性和可维护性。

总之,抽象工厂模式在游戏开发中是一种非常有用的设计模式,它可以帮助你创建和管理一组相关的对象,并隐藏对象的创建细节。如果你的游戏需要处理多个产品族或需要提高系统的可扩展性和可维护性,那么抽象工厂模式可能是一个很好的选择。

在游戏开发中,抽象工厂模式的一个典型应用例子是创建不同类型的角色(如战士、法师)以及他们各自的装备。以下是一个简化的例子来说明这一点:

首先,定义一些接口来表示不同类型的角色和装备:

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
// 角色接口
class Character {
public:
virtual ~Character() {}
virtual void equip() = 0; // 装备武器和装备
// 其他角色方法...
};

// 战士接口和具体实现
class Warrior : public Character {
public:
void equip() override {
std::cout << "Warrior equips sword and shield." << std::endl;
}
// 其他战士方法...
};

// 法师接口和具体实现
class Mage : public Character {
public:
void equip() override {
std::cout << "Mage equips staff and robe." << std::endl;
}
// 其他法师方法...
};

// 武器接口
class Weapon {
public:
virtual ~Weapon() {}
// 武器的方法...
};

// 战士武器和法师武器的具体实现(略)

// 装备接口
class Armor {
public:
virtual ~Armor() {}
// 装备的方法...
};

// 战士装备和法师装备的具体实现(略)

接下来,定义一个抽象工厂接口来创建角色和装备:

1
2
3
4
5
6
7
8
9
10
11
// 抽象工厂接口
class CharacterFactory {
public:
virtual ~CharacterFactory() {}
virtual Character* createWarrior() = 0;
virtual Character* createMage() = 0;
virtual Weapon* createWarriorWeapon() = 0;
virtual Weapon* createMageWeapon() = 0;
virtual Armor* createWarriorArmor() = 0;
virtual Armor* createMageArmor() = 0;
};

然后,为每个产品族(如战士和法师)创建一个具体的工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 战士工厂
class WarriorFactory : public CharacterFactory {
public:
Character* createWarrior() override { return new Warrior(); }
Weapon* createWarriorWeapon() override { /* 返回战士武器的实例 */ }
Armor* createWarriorArmor() override { /* 返回战士装备的实例 */ }
// 其他法师相关的方法在这里不需要实现
};

// 法师工厂
class MageFactory : public CharacterFactory {
public:
Character* createMage() override { return new Mage(); }
Weapon* createMageWeapon() override { /* 返回法师武器的实例 */ }
Armor* createMageArmor() override { /* 返回法师装备的实例 */ }
// 其他战士相关的方法在这里不需要实现
};

最后,在游戏的主逻辑或某个客户端代码中,你可以使用这些工厂来创建角色和装备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main() {
CharacterFactory* factory;

// 创建战士族
factory = new WarriorFactory();
Character* warrior = factory->createWarrior();
warrior->equip(); // 输出 "Warrior equips sword and shield."
// ... 其他战士相关的操作 ...
delete warrior;

// 创建法师族
delete factory; // 先删除前一个工厂实例
factory = new MageFactory();
Character* mage = factory->createMage();
mage->equip(); // 输出 "Mage equips staff and robe."
// ... 其他法师相关的操作 ...
delete mage;
delete factory;

return 0;
}

这个例子展示了如何使用抽象工厂模式来创建和管理游戏中不同类型的角色和装备。每个具体的工厂类负责创建它所属产品族的对象,从而实现了创建过程的封装和隔离。这有助于保持代码的清晰性和可维护性,并允许在不影响客户端代码的情况下添加新的产品族或修改现有产品族的实现。

装配系统

  • 所需节点Node3D,Marker3D作为部件放置的位置,方便调整位置

  • @tool
    extends Node3D
    class_name 装备模型
    
    @onready var 枪身: Marker3D = $"枪身"
    @onready var 枪管: Marker3D = $"枪管"
    @onready var 瞄准仪: Marker3D = $"瞄准仪"
    
    #资源,每当资源变更时刷新配件节点
    @export var 枪身资源:武器资源类:
        get: 
            return 枪身资源
        set(value):
            if value==枪身资源:
                return
            枪身资源=value
            武器资源更新()
    
    @export var 枪管资源:武器资源类:
        get: 
            return 枪管资源
        set(value):
            if value==枪管资源:
                return
            枪管资源=value
            武器资源更新()
    
    @export var 瞄准仪资源:武器资源类:
        get: 
            return 瞄准仪资源
        set(value):
            if value==瞄准仪资源:
                return
            瞄准仪资源=value
            武器资源更新()
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta: float) -> void:
        pass
    func 武器资源更新():
        #初始化节点,将配件全部清除
        if(枪身 and 枪身.get_child_count()>0):
            枪身.get_child(0).queue_free()
        if(枪管 and 枪管.get_child_count()>0):
            枪管.get_child(0).queue_free()
        if(瞄准仪 and 瞄准仪.get_child_count()>0):
            瞄准仪.get_child(0).queue_free()
    
        #检查资源是否存在
        if 枪身!=null and 枪身资源:
            var 武器场景:PackedScene=load(枪身资源.武器场景路径)
            枪身.add_child(武器场景.instantiate())
        else: #如果武器不存在,初始化其他配件
            枪管资源=null
            瞄准仪资源=null
    
        #检查其他配件,如果存在则添加到节点树
        if 枪管!=null and 枪管资源:
            var 枪管场景:PackedScene=load(枪管资源.武器场景路径)
            枪管.add_child(枪管场景.instantiate())
    
        if 瞄准仪!=null and 瞄准仪资源:
            var 瞄准仪场景:PackedScene=load(瞄准仪资源.武器场景路径)
            瞄准仪.add_child(瞄准仪场景.instantiate())
    
    
    + 背包,负责更新装备
      
      
      extends Control
      
      @export var 武器展示场景: 装备模型
      @export var 武器装备场景: 装备模型
      @export var 物品背包:Array[Resource]
      
      # Called when the node enters the scene tree for the first time.
      func _ready() -> void:
          pass # Replace with function body.
      
      
      # Called every frame. 'delta' is the elapsed time since the previous frame.
      
      func _process(delta: float) -> void:
      
          更新装备(武器展示场景);
          更新装备(武器装备场景);
          pass
      
      func 更新装备(data:装备模型):
      
          if data:
              data.枪身资源=物品背包[0]
              data.枪管资源=物品背包[1]
              data.瞄准仪资源=物品背包[2]
      
    
    1
      
    extends PanelContainer @onready var texture_rect: TextureRect @onready var 背包: Control = $"../../.." @export var 索引:int=0 # Called when the node enters the scene tree for the first time. func _ready() -> void: texture_rect=get_node("TextureRect") pass # Replace with function body. # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: if 背包.物品背包[索引]: texture_rect.texture=背包.物品背包[索引].icon else: texture_rect.texture=preload("res://Icon/空纹理.png") pass func _get_drag_data(position): if 背包.物品背包[索引]: var cpb =TextureRect.new() cpb.texture=背包.物品背包[索引].icon set_drag_preview(cpb) return 背包.物品背包[索引] func _can_drop_data(position, data): # 检查是否为武器基类 if data is Resource: return true else: return false #数据处理 func _drop_data(position, data): print(data) if 背包.物品背包[索引]==null: var ite=背包.物品背包.find(data) 背包.物品背包 [索引]=data 背包.物品背包[ite]=null else : var ite=背包.物品背包.find(data) var temp=背包.物品背包[索引] 背包.物品背包[索引]=data 背包.物品背包[ite]=temp
    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
    # 游戏能力系统
    ```cpp
    #ifndef TABILITY_H
    #define TABILITY_H

    #include <godot_cpp/classes/node.hpp>

    //#include <godot_cpp/core/class_db.hpp>
    #include <godot_cpp/variant/utility_functions.hpp> //输出字符串用
    using namespace godot;

    //#define VARIANT_ARG_MAX 8
    //#define VARIANT_ARG_DECLARE const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const //Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8

    class AbilitySystem : public Node
    {
    GDCLASS(AbilitySystem, Node);

    private:
    protected:
    static void _bind_methods();
    public:
    AbilitySystem();
    ~AbilitySystem();
    };
    #endif // TABILITY_H
    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
    /**************************************************************************/
    /* threen.cpp */
    /**************************************************************************/
    /* This file is part of: */
    /* GODOT ENGINE */
    /* https://godotengine.org */
    /**************************************************************************/
    /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
    /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
    /* */
    /* Permission is hereby granted, free of charge, to any person obtaining */
    /* a copy of this software and associated documentation files (the */
    /* "Software"), to deal in the Software without restriction, including */
    /* without limitation the rights to use, copy, modify, merge, publish, */
    /* distribute, sublicense, and/or sell copies of the Software, and to */
    /* permit persons to whom the Software is furnished to do so, subject to */
    /* the following conditions: */
    /* */
    /* The above copyright notice and this permission notice shall be */
    /* included in all copies or substantial portions of the Software. */
    /* */
    /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
    /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
    /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
    /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
    /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
    /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
    /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
    /**************************************************************************/

    #include "public/AbilitySystem.h"

    //#include "easing_equations.h"

    #include <godot_cpp/core/class_db.hpp>
    #include <godot_cpp/variant/utility_functions.hpp>

    using namespace godot;



    void AbilitySystem::_bind_methods()
    {
    }

    AbilitySystem::AbilitySystem() {

    };

    AbilitySystem::~AbilitySystem()
    {
    };