诸葛温侯 há 10 meses atrás
pai
commit
42d1f79fdd
44 ficheiros alterados com 1884 adições e 7 exclusões
  1. 16 0
      C++/案例一 游戏服务器/全局变量.md
  2. 61 0
      C++/案例一 游戏服务器/网络连接.md
  3. 4 0
      C/0、可执行程序/概述.md
  4. 37 0
      C/1、基础知识/errno错误码.md
  5. 61 0
      C/1、基础知识/network网络模块.md
  6. 13 0
      C/1、基础知识/static修饰符.md
  7. 21 0
      C/2、网络模块/1、基础概念.md
  8. 213 0
      C/2、网络模块/2、网络实例.md
  9. 98 0
      C/2、网络模块/3、网络事件.md
  10. 53 0
      C/2、网络模块/4、网络缓冲区.md
  11. 96 0
      C/2、网络模块/5、网络套接字.md
  12. 22 0
      C/2、网络模块/调用逻辑.md
  13. 175 0
      C/3、服务器模块/1、服务.md
  14. 43 0
      C/3、服务器模块/2、时间控制器.md
  15. 131 0
      C/3、服务器模块/3、事件机制.md
  16. 103 0
      C/9、动态库加载/概述.md
  17. 270 0
      C/test.md
  18. 9 0
      C/基础知识.md
  19. 1 0
      Common/apike.md
  20. 3 0
      Common/快捷键.md
  21. 39 0
      Unity/修改日志/10. 日志管理器 Logger.md
  22. 74 0
      Unity/修改日志/11、日志管理器 ConfigDataManager.md
  23. 114 0
      Unity/修改日志/12、本地数据结构 ReadLocalData.md
  24. 0 0
      Unity/修改日志/8、输入信号处理器 InputActionsHandler.md
  25. 62 0
      Unity/修改日志/9、单例模式 Singleton.md
  26. 10 0
      Unity/基础知识/3、鼠标点击事件检测.md
  27. 22 4
      Unity/基础知识/基础知识.md
  28. 0 0
      Unity/案例/1、/移动.md
  29. 0 0
      Unity/案例/2、2DRPG/概述.md
  30. 0 0
      Unity/案例/3、农场/1、前期准备.md
  31. 0 0
      Unity/案例/3、农场/2、添加角色.md
  32. 0 0
      Unity/案例/3、农场/3、动作触发.md
  33. 0 0
      Unity/案例/3、农场/4、场景设置.md
  34. 0 0
      Unity/案例/3、农场/5、道具设定.md
  35. 0 0
      Unity/案例/3、农场/6、UI.md
  36. 0 0
      Unity/案例/4、少儿学习/概述.md
  37. 3 2
      Unity/案例/5、三消/1、概述.md
  38. 0 0
      Unity/案例/5、三消/2、关卡选择.md
  39. 0 0
      Unity/案例/5、三消/3、渲染逻辑.md
  40. 23 0
      Unity/案例/5、三消/名词定义.md
  41. 28 0
      Unity/案例/5、三消/建项步骤.md
  42. 62 0
      Unity/案例/6、3DRPG/1、概述.md
  43. 3 0
      Unity/案例/6、3DRPG/2、MainLogic.md
  44. 14 1
      Unity/项目/不一样的江湖/1、游戏策划/1、角色系统.md

+ 16 - 0
C++/案例一 游戏服务器/全局变量.md

@@ -0,0 +1,16 @@
+## 平台选项
+PLATFORM_WINDOWS
+PLATFORM_WIN32
+PLATFORM_WIN64
+PLATFORM_LINUX
+PLATFORM_MACOS
+
+## 编译选项
+
+## 数字选项
+FALSE->0
+TRUE->1
+NULL->0
+
+VOID->void
+CONST->const

+ 61 - 0
C++/案例一 游戏服务器/网络连接.md

@@ -0,0 +1,61 @@
+# 网络连接
+## 套接字 
+SOCKET_T fd = socket(AF_INET, SOCK_STREAM, 0);
+创建一个套接字,套接字是用于进行网络通信的一种机制。
+
+`socket`函数作用是创建一个套接字,他接受三个参数:
+- 地址族(address family)  `AF_INET`表示IPv4地址族
+- 套接字类型(socket type)   `SOCK_STREAM`表示使用面向连接的可靠字节流传输方式
+- 协议类型(protocol type)   `0`表示自动选中合适的协议(通常为TCP)
+
+`socket`函数返回一个类型为`SOCKET_T`的套接字文件描述(`file descriptor`)
+
+## 网络轮询
+网络轮询是一种用于处理多个套接字(sockets)的事件的机制。它的原理是将所有需要监听的套接字注册到一个统一的事件管理机制中,然后在一个循环中不断地查询和处理这些事件。这种方式避免了传统的阻塞式 I/O,使得单个线程可以有效地处理多个并发的网络连接。
+
+在 Linux 系统中,epoll 是一种用于实现网络轮询的高效机制。它通过以下步骤工作:
+
+创建一个 epoll 实例,通过 epoll_create 系统调用获取一个 epoll 文件描述符。
+将需要监听的套接字添加到 epoll 实例中,通过 epoll_ctl 系统调用注册读或写等待事件,并关联一个自定义的指针或标识符。
+进入事件循环,通过 epoll_wait 系统调用等待事件的发生。这个调用会阻塞线程,直到至少一个事件发生或到达超时时间。
+当有事件发生时,epoll_wait 将返回一个就绪事件的列表。
+循环遍历就绪事件的列表,根据事件的类型和关联的指针或标识符,执行相应的操作(如读取或写入数据)。
+重复步骤 3 到 5,不断地监听和处理事件。
+epoll 的高效性在于它使用了内核中的事件驱动机制。当套接字上的事件发生时,内核会将事件通知到 epoll 实例,并且只有就绪的套接字会被返回,减少了无效的轮询开销。
+
+因此,网络轮询的原理是使用 epoll 等机制,通过集中管理和监听套接字上的事件,实现高效的并发网络处理。
+
+## 初始化Socket参数
+- fd = i + 1;
+- status = SOCKET_STATUS_INVALID;
+- event_mask = 0;
+- user_data = -1;
+- user_type = -1;
+- addr = 0;
+- port = 0;
+- receive_buffer = NULL;
+- write_head = NULL;
+- write_tail = NULL;
+- write_buffer_size = 0;
+- write_buffer_max = INT_MAX;
+
+## 初始化网络结构参数
+- max_sockets = max_connect;  // 设置可以处理的最大套接字数量
+- num_events = 0;
+- ev = malloc(max_connect * sizeof(struct network_poll_event));
+- messages = malloc(max_connect * sizeof(struct network_message));
+- sockets = alloc_sockets(max_connect);
+- free_socket = &self->sockets[0];
+- tail_socket = &self->sockets[max_connect - 1];
+// 创建一个接受缓冲区,这个接受缓冲区由max个read_buffer组成
+- receive_pool = create_network_buffer(max_connect, read_buffer);
+
+## 创建网络结构时同时分配所有的空间
+
+在创建网络结构时一次性分配所有的socket连接,而不是逐个分配,是为了提高效率和性能。
+
+每个socket连接都需要一定的内存空间来存储其状态和相关信息。如果每次需要新建一个socket连接时都进行动态分配内存,会导致频繁的内存分配和释放操作,增加了系统开销。此外,动态分配内存也可能导致内存碎片化,降低内存利用率。
+
+通过一次性分配所有socket连接的方式,可以在一次内存分配操作中分配连续的内存块,避免了频繁的内存分配和释放操作。同时,连续的内存块还能提供更高的内存访问效率,对于一些需要频繁访问socket连接的网络操作,可以提高处理速度和性能。
+
+当然,一次性分配所有socket连接也需要合理控制分配的最大数量,以避免浪费过多内存。需要根据实际需求和系统资源进行权衡和调整。

+ 4 - 0
C/0、可执行程序/概述.md

@@ -0,0 +1,4 @@
+# 
+``` bash
+jhk config.txt 
+```

+ 37 - 0
C/1、基础知识/errno错误码.md

@@ -0,0 +1,37 @@
+# C语言基础知识
+## errno错误码
+errno是一个C语言的全局变量,类型为int。它在头文件<errno.h>中定义,并被用来表示函数调用的错误码。
+
+errno的具体取值是由操作系统或库函数来设置的,它会在函数调用出错时被设置为相应的错误码。通过检查errno的值,我们可以获取函数调用是否成功以及出错的具体原因。
+
+需要注意的是,只有在函数调用返回错误时,errno的值才是有效的。在函数调用成功时,errno的值不会被修改。
+
+在<errno.h>中定义了一些常见的errno错误码,例如:
+
+EACCES:权限不足
+EINVAL:无效的参数
+EIO:输入/输出错误
+ENOENT:文件不存在
+EINTR:被信号中断的系统调用
+
+我们可以通过包含<errno.h>头文件来访问errno变量,并使用perror()或strerror()函数来获取相应的错误信息。
+
+perror()函数用于将errno的值和相应的错误信息输出到标准错误流stderr。
+strerror()函数用于将errno的值作为参数,返回相应的错误信息字符串。
+以下是一个示例程序,演示如何使用errno变量和perror()函数:
+
+```c
+#include <stdio.h>
+#include <errno.h>
+
+int main() {
+    FILE *file = fopen("nonexistent.txt", "r");
+    if (file == NULL) {
+        perror("Failed to open file");
+        printf("Error code: %d\n", errno);
+        printf("Error message: %s\n", strerror(errno));
+    }
+
+    return 0;
+}
+```

+ 61 - 0
C/1、基础知识/network网络模块.md

@@ -0,0 +1,61 @@
+# C语言基础知识
+## network网络模块
+网络模块是用来处理网络通信的程序组件。其中包含`缓冲区`和`轮询机制`可以实现高效的网络数据处理。
+
+缓冲区用来存储接收或发送的数据。它通常由一个固定大小的字节数组组成,数据会被逐个字节或块地写入或读取。缓冲区的大小可以根据需要进行调整,较大的缓冲区可以减少频繁的读写操作,提高数据处理效率。
+
+轮询机制是指不断循环检查网络连接状态和等待接收或发送数据的操作。它可以通过非阻塞的方式来监听套接字(Socket)的读写事件。在每次循环中,程序会调用轮询函数,该函数会检查缓冲区中是否有数据可读或写入,并采取相应的操作。轮询机制可以实现同时监听多个套接字的读写事件,提高程序的并发处理能力。
+
+下面是一个简单的网络模块的伪代码示例:
+
+```c
+// 定义缓冲区大小
+#define BUFFER_SIZE 1024
+
+// 定义套接字
+int socket;
+
+// 定义缓冲区
+char buffer[BUFFER_SIZE];
+
+// 主循环
+while (true) {
+    // 轮询套接字的读写事件
+    poll(socket, buffer);
+
+    // 处理接收到的数据
+    if (buffer has data) {
+        // 处理接收到的数据
+        process_received_data(buffer);
+        // 清空缓冲区
+        clear_buffer(buffer);
+    }
+
+    // 处理需要发送的数据
+    if (buffer has data to send) {
+        // 发送数据
+        send_data(buffer);
+        // 清空缓冲区
+        clear_buffer(buffer);
+    }
+
+    // 等待一段时间后重复循环
+    wait();
+}
+```
+
+## 其他方案
+
+除了使用缓冲区和轮询机制外,还有其他一些更好的方案可以改进网络模块的性能和效率。以下是一些常用的优化方案:
+
+1. 使用事件驱动模型:事件驱动模型通过使用操作系统提供的异步IO(如epoll、kqueue等)来监听和处理套接字的读写事件,避免了轮询机制中的频繁检查操作,提高了程序的效率。
+
+2. 使用多线程/多进程:使用多线程或多进程可以实现并发处理多个连接,从而提高网络模块的吞吐量。可以将各个连接分配给不同的线程或进程处理,以防止阻塞导致的性能下降。
+
+3. 使用非阻塞IO和事件回调:非阻塞IO将套接字设置为非阻塞模式,使得读写操作不会阻塞整个程序,而是立即返回。结合事件回调机制,可以在数据准备好后立即通知程序进行处理,提高响应速度。
+
+4. 使用缓存池:缓存池用于管理动态内存的分配和释放,可以减少频繁的内存分配和释放操作,提高程序的效率。通过预先创建一定数量的缓存块,并在需要时重用这些缓存块,可以减少内存碎片和系统调用次数,提高性能。
+
+5. 使用零拷贝技术:零拷贝技术可以减少数据在用户空间和内核空间之间的拷贝次数,从而降低CPU和内存的开销。可以通过使用mmap系统调用直接将数据映射到内存中,或使用sendfile系统调用将文件内容直接发送到套接字,以实现零拷贝。
+
+需要注意的是,优化方案的选择应根据具体的应用场景和需求进行评估和决策。不同的方案适用于不同的情况,需要综合考虑性能、可维护性、复杂度等因素来做出决策。

+ 13 - 0
C/1、基础知识/static修饰符.md

@@ -0,0 +1,13 @@
+# C语言基础知识
+## static修饰符
+### 1、在函数内部使用static修饰局部变量
+当在函数内部声明一个变量并使用`static`修饰时,该变量被称为`静态局部变量`。静态局部变量与普通局部变量相比,具有以下特点:
+
+- 静态局部变量在函数执行完毕后仍然存在于内存中,下一次调用该函数时仍然可以使用该变量的值,而普通局部变量在函数执行完毕后会被销毁。
+- 静态局部变量的作用域仅限于声明它的函数内部,其他函数无法访问该变量。
+- 静态局部变量的初始值只会被赋值一次,即使多次调用函数也不会重新初始化。
+
+### 2、在全局变量和函数上使用static修饰
+当在全局变量或函数前面使用`static`修饰时,该变量或函数被限定为当前文件的私有作用域,其他文件无法访问该变量或函数。
+
+这种用法通常用于在多个文件中定义同名函数或变量时,避免命名冲突。

+ 21 - 0
C/2、网络模块/1、基础概念.md

@@ -0,0 +1,21 @@
+# 基础概念
+
+## 网络轮询
+
+在网络连接中,轮询是一种常见的技术,用于检测和处理连接的事件和数据。它主要用于实时监测连接状态、接收数据、发送数据和处理错误。通过轮询,我们可以在没有阻塞的情况下对连接进行实时操作。
+
+在网络编程中,有多种实现轮询的方式,下面介绍两种常见的方式:
+
+1. **基于阻塞 I/O 的轮询**:
+   在基于阻塞 I/O 的轮询中,通过使用系统调用(如 `select`、`poll`、`epoll` 等)实现轮询操作。
+   - 在每次轮询时,通过调用相应的系统调用监视多个文件描述符(包括连接套接字)的状态。
+   - 当有事件发生时(如有连接请求、数据到达等),系统调用会返回,并通过检查相应的标志位确定发生了哪些事件。
+   - 根据事件的类型和对应的文件描述符,执行相应的操作(如接收数据、发送数据、处理错误等),然后进行下一次轮询。
+
+2. **基于非阻塞 I/O 和事件驱动的轮询**:
+   在基于非阻塞 I/O 和事件驱动的轮询中,利用非阻塞 I/O 和事件模型来实现轮询操作。
+   - 使用非阻塞套接字,使得读取和写入操作不会阻塞主线程。
+   - 通过事件驱动模型(如回调函数、状态机等)来处理连接的事件和数据。当有事件发生时,例如数据到达或连接关闭,会触发相应的事件处理函数来处理事件。
+   - 使用事件循环来实现轮询操作。事件循环不断地检查连接是否有事件发生,并调用相应的事件处理函数进行处理。
+
+轮询是实现网络连接及数据处理的重要机制。它允许我们并发地处理多个连接,实时地检测连接状态和数据到达,以及相应地进行处理。具体使用哪种轮询方式,取决于应用程序的需求和规模,以及所使用的编程语言和网络库的特性。

+ 213 - 0
C/2、网络模块/2、网络实例.md

@@ -0,0 +1,213 @@
+# 网络模块
+
+## 2、网络实例
+
+在每个使用网络模块的程序中,我们都需要创建一个网络实例。
+
+### 2.1 网络实例结构体
+```c
+// 网络实例结构体
+typedef struct network {
+    struct network_event_manager event_manger;          // 网络事件管理器
+    int network_event_count;                            // 当前网络事件数量(todo 暂时我认为是当前)
+    struct network_poll_event *poll_event;              // 网络轮询事件(网络事件内部机制就是轮询)
+
+    int error_code;                                     // 错误代码
+    int max_sockets;                                    // 最大套接字数量
+
+    struct network_message *messages;                   // 网络消息数组
+    struct network_socket *sockets;                     // 套接字数组
+    struct network_socket *free_socket;                 // 空闲套接字链表
+    struct network_socket *tail_socket;                 // 套接字链表的尾部
+    struct network_buffer *receive_pool;                // 接收缓冲区池
+} TNetwork;
+```
+
+### 2.2 创建网络实例
+
+创建一个网络实例(network)时, 需要设定一个允许的最大连接数(`max_connection_count`) 和 每个连接需要用到的接收缓冲区大小(`pef_connection_recevie_buffer_size`)。
+
+```c
+/**
+ * @brief 创建并初始化一个网络实例
+ *
+ * 这个方法主要的功能是给网络实例需要用到的参数分配合适的空间;
+ * 这个空间基本是所有空间的分配,根据最大连接数来决定。、
+ *
+ * @参数 max_connection_count 最大连接数,也就是这个网络实例允许的最大连接数
+ * @参数 per_connection_receive_buffer_size 每个连接的接收缓冲区的大小
+ * @返回 指向这个网络实例的指针
+ * @例子
+ * N = network_create(65535, 64*1024);
+ * 创建一个支持最大连接数为65535,每个连接的接收缓冲区为64KB的网络实例;
+ * 这个方法一般都是网络初始化中被调用。
+ */
+struct network *network_create(int max_connection_count, int per_connection_receive_buffer_size) {
+    // 验证必要参数:最大连接数和每个连接对应的接收缓冲区大小不能为0
+    if (max_connection_count == 0 || per_connection_receive_buffer_size == 0) {
+        return NULL;
+    }
+
+    // 分配网络实例结构的内存空间
+    struct network *self = malloc(sizeof(struct network));
+    if (self == NULL) {
+        return NULL;
+    }
+
+    // 初始化网络事件管理器
+    if (network_event_manager_init(&self->event_manger, max_connection_count)) {
+        free(self);
+        return NULL;
+    };
+
+    // 分配并初始化网络事件数组的空间
+    self->network_event_count = 0;             // 设置当前网络事件的数量为0
+    self->events = malloc(max_connection_count * sizeof(struct network_event)); // 给事件数组分配空间
+    if (self->events == NULL) {
+        free(self);
+        return NULL;
+    }
+
+    // 设置可以处理的最大套接字数量并分配套接字数组的空间
+    self->max_sockets = max_connection_count;  // 设置可以处理的最大套接字数量
+    self->sockets = network_socket_alloc_all_sockets(max_connection_count); // 给套接字数组分配空间
+    if (self->sockets == NULL) {
+        free(self->events);
+        free(self);
+        return NULL;
+    }
+    self->free_socket = &self->sockets[0];      // 空闲套接字默认指向 套接字数组顶部
+    self->tail_socket = &self->sockets[max_connection_count - 1]; // 套接字数组的尾部
+
+    // 分配并初始化网络消息数组的空间
+    self->messages = malloc(max_connection_count * sizeof(struct network_message)); // 给网络消息分配空间
+    if (self->messages == NULL) {
+        free(self->sockets);
+        free(self->events);
+        free(self);
+        return NULL;
+    }
+
+    // 创建一个接收缓冲区,这个接受缓冲区由max个read_buffer组成
+    self->receive_pool = network_buffer_create(max_connection_count, per_connection_receive_buffer_size);
+    if (self->receive_pool == NULL) {
+        free(self->messages);
+        free(self->sockets);
+        free(self->events);
+        free(self);
+        return NULL;
+    }
+    return self;
+}
+```
+
+### 2.3 释放网络实例
+```c
+/**
+ * @brief 释放指定的网络实例
+ * @param self 指定的网络结构
+ */
+void network_free(struct network *self) {
+    // 如果需要释放的实例为空,直接返回
+    if (self == NULL) return;
+
+    // 把网络实例中所有的套接字关闭
+    int i;
+    for (i = 0; i < self->max_sockets; ++i) {
+        struct network_socket *s = &self->sockets[i];
+        if (s->status >= SOCKET_STATUS_OPENED) {
+            close_socket(self, s);
+        }
+    }
+
+    // 释放相关资源
+    free(self->sockets);
+    self->free_socket = NULL;
+    self->tail_socket = NULL;
+    free(self->events);
+    free(self->messages);
+    network_buffer_free(self->receive_pool);
+
+    // 销毁网络事件管理器
+    network_event_manager_destroy(&self->event_manger);
+    free(self);
+}
+```
+
+### 2.4 网络实例开启监听
+```c
+/**
+ * @brief 开始监听指定地址和端口的连接请求
+ * 此函数创建一个套接字并将其绑定到指定的地址和端口,在该套接字上开始监听连接请求。
+ * 随后,它创建一个网络套接字结构,将其添加到网络结构中,并将其订阅为可读事件。
+ * @param self 指向网络结构体的指针。
+ * @param addr 要绑定的IPv4地址。
+ * @param port 要绑定的端口号。
+ * @param write_buffer_max 写缓冲区的最大大小。
+ * @param user_data 用户指定的数据。
+ * @param user_type 用户指定的类型。
+ * @return 0 表示成功;否则表示错误代码。
+ */
+int network_listen(struct network *self, uint32_t addr, uint16_t port, int write_buffer_max, int user_data, int user_type) {
+    int error = 0;
+
+    // 创建套接字,用于后续的操作
+    SOCKET_T fd = socket(AF_INET, SOCK_STREAM, 0);
+
+    // 创建套接字失败
+    if (fd == SOCKET_INVALID) {
+        error = SOCKET_ERROR_CODE;
+        return NETWORK_ERROR(error);
+    }
+
+    // 将套接字设置为非阻塞、设置关闭时不作处理、设置地址重用
+    if (SOCKET_NON_BLOCKING(fd) == -1 ||
+        SOCKET_CLOSE_ONE_EXEC(fd) == -1 ||
+        SOCKET_REUSE_ADDR(fd) == -1) {
+        error = SOCKET_ERROR_CODE;
+        SOCKET_CLOSE(fd);
+        return NETWORK_ERROR(error);
+    }
+
+    // 设置绑定的地址和端口
+    struct sockaddr_in my_addr;
+    memset(&my_addr, 0, sizeof(struct sockaddr_in));
+    my_addr.sin_family = AF_INET;
+    my_addr.sin_port = htons(port);
+    my_addr.sin_addr.s_addr = addr;
+    if (bind(fd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) {
+        error = SOCKET_ERROR_CODE;
+        SOCKET_CLOSE(fd);
+        return NETWORK_ERROR(error);
+    }
+
+    // 开始监听连接请求
+    if (listen(fd, LISTEN_BACK_LOG) == -1) {
+        error = SOCKET_ERROR_CODE;
+        SOCKET_CLOSE(fd);
+        return NETWORK_ERROR(error);
+    }
+
+    // 初始化网络套接字
+    struct network_socket *s = network_socket_init(self, fd, addr, port, write_buffer_max, user_data, user_type);
+    // 初始化失败
+    if (s == NULL) {
+        error = NETWORK_ERROR_CREATE_SOCKET;
+        SOCKET_CLOSE(fd);
+        return NETWORK_ERROR(error);
+    }
+
+    // 订阅套接字,将套接字纳入事件管理器中
+    if (network_socket_subscribe(self, s, NETWORK_EVENT_READABLE)) {
+        error = SOCKET_ERROR_CODE;
+        network_socket_close(self, s);
+        return NETWORK_ERROR(error);
+    }
+
+    // 设置套接字状态为正在监听
+    s->status = SOCKET_STATUS_LISTENING;
+    return 0;
+}
+```
+
+### 开启网络监听

+ 98 - 0
C/2、网络模块/3、网络事件.md

@@ -0,0 +1,98 @@
+# 网络模块
+
+## 网络事件
+
+我们将会赋予网络实例一个网络事件管理器(`network_event_manager`),而内部我们会使用轮询机制(`poll`)。
+
+### 网络事件结构体
+```c
+/**
+ * @brief 网络事件结构体
+ *
+ * 用于表示网络事件的属性和状态。
+ */
+typedef struct network_event {
+    void *user_data; // 用户数据指针
+    int read; // 表示是否可读取的标记:0 表示不可读取,非零值表示可读取
+    int write; // 表示是否可写入的标记:0 表示不可写入,非零值表示可写入
+} TNetworkEvent;
+```
+
+
+### 网络事件管理器结构体
+``` c
+// 网络事件处理器结构体
+typedef struct network_event_manager {
+    int capacity;                       // 需要的容量
+    void **user_data;                   // 指向用户数据的指针(void*类型)
+    int max_fd;                         // 最大文件描述符值
+    fd_set read_fds;                    // 读文件描述符集合
+    fd_set write_fds;                   // 读文件描述符集合
+    fd_set temp_read_fds;               // 临时读文件描述符集合
+    fd_set temp_write_fds;              // 临时写文件描述符集合
+} TNetworkPoll;
+```
+
+### 初始化管理器
+#### 1、概念一
+
+理论上,我们在设计最大连接数时,设置为2的倍数是一个最常见的优化策略,但具体的设计仍然取决于应用程序的需求和实际情况。在做出决策时,可以考虑预期的连接数、硬件和系统限制以及性能需求等因素。
+
+假设你的最大连接数是18,不是2的倍数。如果按照设计容量的方式,需要分配一个容量为32的内存空间来存储这些连接。这意味着有14个连接的内存空间会浪费,因为它们没有被使用。
+
+而如果最大连接数是16,是2的倍数,那么需要的内存容量刚刚好是16,不会产生浪费。
+
+在设计网络应用程序时,尽可能选择2的倍数作为最大连接数,可以最大程度地减少内存浪费。但这并不是绝对必要的,尤其是当连接数相对较小的时候,内存浪费可能影响不大。
+
+另外,内存空间的浪费也可以通过其他手段进行优化,比如内存池技术,可以重复使用已分配的内存块来减少内存碎片和浪费。
+
+因此,在设计网络应用程序时,要综合考虑连接数的预估,内存利用率,以及其他优化技术,以选择适合的最大连接数,并避免过多的内存浪费。
+
+#### 2、概念二
+
+sizeof(void *) 的结果代表指针的大小,即指针类型在特定系统架构下所占的字节数。在大多数32位系统中,指针大小通常为4字节(32位),而在大多数64位系统中,指针大小通常为8字节(64位)。
+
+需要注意的是,指针的大小可能取决于所使用的编译器、操作系统和硬件架构。不同的系统和编译器可能有不同的规定。通常情况下,sizeof(void *) 的结果可以通过 C 语言的 sizeof 运算符来获取。
+
+为了确保代码的可移植性和兼容性,对于指针大小的依赖应尽可能减小,并尽量使用标准的整数类型(如 int32_t 或 intptr_t)来代替依赖于指针大小的操作。
+
+``` c
+/**
+ * @brief 初始化网络事件管理器
+ * 
+ * 这个方法将会根据网络实例中的设计的最大连接数来初始化网络实例中的事件管理器;
+ * 容量的分配将会由最大连接数决定的;
+ * 我们还分配所有的内存来存储用户数据;
+ * @param event_manager 网络实例中的事件管理器地址
+ * @param max_connection_count 网络实例中的最大连接数
+ * @return 成功时返回0,失败时返回1
+ * @example 这个方法主要就是在创建网络实例中进行调用
+ * network_create -> network_event_manager_init(&self->event_manger, max_connection_count)
+ */
+int network_event_manager_init(struct network_event_manager *event_manager, int max_connection_count) {
+    // 根据最大连接数计算网络事件管理器需要的容量
+    int capacity = 1;
+    while (capacity < max_connection_count) {
+        capacity *= 2;
+    }
+
+    // 根据指针大小计算需要分配的空间大小
+    size_t data_size = sizeof(void *) * capacity;
+
+    // 设置事件管理器的容量
+    event_manager->capacity = capacity;
+
+    // 为用户数据分配内存,并初始化为0
+    event_manager->user_data = malloc(data_size);
+    if (event_manager->user_data == NULL) {
+        return 1;
+    }
+    memset(event_manager->user_data, 0, data_size);
+
+    // 初始化最大文件描述符和读写文件描述符集合
+    event_manager->max_fd = -1;
+    FD_ZERO(&event_manager->read_fds);
+    FD_ZERO(&event_manager->write_fds);
+    return 0;
+}
+```

+ 53 - 0
C/2、网络模块/4、网络缓冲区.md

@@ -0,0 +1,53 @@
+# 网络模块
+
+## 网络缓冲区
+
+我们的网络缓冲区(`network_buffer`) 会由多个网络缓冲区块(`network_buffer_block`)组成,一般而言我们在创建网络实例时,就会创建这个网络缓冲区,而每一个连接就是一个网络缓冲区块,每个网络缓冲区快的尺寸根据设定而来。
+
+所以我们假设创建了一个允许65535个网络连接的网络实例,每个网络连接将会占用64KB的大小,那么就会创建65535个网络缓冲区快,每个区块大小为64KB
+
+### 网络缓冲区结构体
+```c
+/**
+ * 网络缓冲区结构体
+ */
+typedef struct network_buffer {
+    int max_size;                       // 最大缓冲区大小
+    int block_size;                     // 块大小
+    char *blocks;                       // 存储块的数组
+} TNetworkBuffer;
+```
+
+### 网络缓冲区块结构体
+```c
+/**
+ * @brief 网络缓冲区块结构体
+ */
+typedef struct network_buffer_block {
+    int size;                           // 块的大小
+    int read_ptr;                       // 读取指针
+    int write_ptr;                      // 写入指针
+} TNetworkBlock;
+```
+
+### 创建网络缓冲区
+```c
+/**
+ * @brief 创建网络缓冲区
+ * @参数 maximum_size 缓冲区需要创建的最大数量,即多少个socket连接
+ * @参数 block_size 每个块的大小,即每个socket连接需要的尺寸
+ * @返回 成功:每个块的大小;失败:返回NULL
+ */
+ 
+struct network_buffer *network_buffer_create(int maximum_size, int block_size) {
+    // 检查参数,如果
+    if (maximum_size == 0 || block_size == 0) {
+        return NULL;
+    }
+
+    struct network_buffer *nb = malloc(sizeof(struct network_buffer) + maximum_size * block_size);
+    nb->max_size = maximum_size;
+    nb->block_size = block_size;
+    return nb;
+}
+```

+ 96 - 0
C/2、网络模块/5、网络套接字.md

@@ -0,0 +1,96 @@
+# 网络模块
+
+## 网络套接字
+
+### 套接字文件描述符
+SOCKET_T fd = socket(AF_INET, SOCK_STREAM, 0);
+
+创建一个套接字,套接字是用于进行网络通信的一种机制。
+
+`socket`函数作用是创建一个套接字,他接受三个参数:
+- 地址族(address family)  `AF_INET`表示IPv4地址族
+- 套接字类型(socket type)   `SOCK_STREAM`表示使用面向连接的可靠字节流传输方式
+- 协议类型(protocol type)   `0`表示自动选中合适的协议(通常为TCP)
+
+`socket` 函数将返回一个套接字描述符 (`fd`),这个描述符可以用于后续的套接字操作,如绑定 (`bind`)、监听 (`listen`)、连接 (`connect`)、发送 (`send`)、接收 (`recv`) 等。
+
+`socket` 返回的类型 `SOCKET_T` 将由系统来决定。
+
+### 网络套接字结构体
+```c
+/**
+ * @brief 网络套接字结构体
+ */
+typedef struct network_socket {
+    SOCKET_T fd;                                    // 套接字文件描述符
+    int status;                                     // 套接字状态,对应SOCKET_STATUS
+    int event_mask;                                 // 事件掩码,一般是指是网络事件可读写
+    int user_data;                                  // 用户数据,这里一般是指service_id
+    int user_type;                                  // 用户类型,这里一般是CLI_UN_TRUST, CLI_CMD, CLI_GAME, CLI_REDIS
+    uint32_t addr;                                  // IP地址
+    uint16_t port;                                  // 端口
+    struct network_buffer_block *receive_buffer;    // 接收缓冲区,一般指缓冲区块
+    struct socket_buffer *write_head;               // 写数据头部指针
+    struct socket_buffer *write_tail;               // 写数据尾部指针
+    int write_buffer_max;                           // 写缓冲区最大容量
+    int write_buffer_size;                          // 写缓冲区当前大小
+} TNetworkSocket;
+```
+
+### 初始化网络套接字
+```c
+/**
+ * @brief 初始化网络套接字
+ * 调用这个方法之前,网络实例必须初始化
+ *
+ * @param self 网络实例
+ * @param fd 套接字文件描述符
+ * @param addr IP地址
+ * @param port 端口号
+ * @param write_buffer_max 最大写缓冲区大小
+ * @param user_data 用户数据
+ * @param user_type 用户类型
+ * @return 成功创建的套接字对象,如果没有可用的套接字则返回NULL
+ */
+struct network_socket *network_socket_init(
+        struct network *self, SOCKET_T fd, uint32_t addr, uint16_t port, int write_buffer_max, int user_data,
+        int user_type) {
+
+    // 验证空闲套接字链表是否存在
+    // 正常来讲在调用这个方法之前所有的内容空间已经分配,所以不应该为NULL
+    if (self->free_socket == NULL)
+        return NULL;
+
+    // 获取空闲套接字链表
+    struct network_socket *socket = self->free_socket;
+
+    // 如果套接字的文件描述符(fd)已经被分配,则将该套接字标记为网络对象上的空闲套接字;否则,将空闲套接字指针设置为NULL。
+    if (socket->fd >= 0)
+        self->free_socket = &self->sockets[socket->fd];
+    else
+        self->free_socket = NULL;
+
+    // 确保套接字的事件掩码(event_mask)为0
+    assert(socket->event_mask == 0);
+
+    // 设置套接字的各种属性
+    socket->fd = fd;
+    socket->status = SOCKET_STATUS_SUSPEND; // 设置套接字为挂起状态
+    socket->user_data = user_data;
+    socket->user_type = user_type;
+    socket->addr = addr;
+    socket->port = port;
+
+    // 分配套接字的接收缓冲区
+    socket->receive_buffer = network_buffer_block_create(self->receive_pool, socket - self->sockets);
+    
+    // 接字的写缓冲区大小为0,并确保最大写缓冲区大小大于0
+    socket->write_buffer_size = 0;
+    socket->write_buffer_max = write_buffer_max;
+    if (socket->write_buffer_max <= 0)
+        socket->write_buffer_max = INT_MAX;
+
+    // 返回创建的套接字对象
+    return socket;
+}
+```

+ 22 - 0
C/2、网络模块/调用逻辑.md

@@ -0,0 +1,22 @@
+# 调用逻辑
+
+## 网络模块初始化
+创建网络实例 static struct network* N = NULL
+
+net_init() -> N  = network_create(max_connection_count, per_connection_receive_buffer_size )
+
+network_create -> network
+network_create -> network_event_manager
+
+## 网络监听
+net_listen() -> network_listen(N,) -> network_poll(N,)
+
+network_listen(N,) -> 创建网络套接字文件描述符(fd) -> 设置套接字参数 -> 绑定套接字bind(fd,) -> 监听套接字listen(fd,) -> 初始化网络套接字信息 -> 订阅套接字到事件管理器中(只读套接字)
+
+初始化网络套接字信息network_socket_init()-> 找到空闲的套接字 -> 然后初始化这个套接字
+
+订阅套接字到事件管理器中 network_socket_subscribe() -> 主要是负责把当前的socket连接加入到需要轮询的事件管理器中
+
+## 网络轮询
+
+network_poll(N,) -> network_event_manager_poll

+ 175 - 0
C/3、服务器模块/1、服务.md

@@ -0,0 +1,175 @@
+# 一、服务
+
+## 1、服务结构体
+
+```c++
+/**
+ * @brief 服务结构体
+ */
+typedef struct service {
+    std::vector<service *>::size_type serviceId;
+    bool inited;
+    CModule &module;
+} TService;
+```
+
+## 2、 服务器管理器
+### 2.1 创建所有服务流程
+
+#### 2.1.1 创建所有服务
+``` c++
+/**
+ * 创建所有的服务
+ * @return
+ */
+bool ServiceManager::createServices() {
+    //todo 这里后面从环境变量中获取
+    std::string service_names = "gate";
+    if (service_names.empty() && !loadServices(service_names)) {
+        std::cerr << "服务加载失败,当前加载的服务位:" << service_names << std::endl;
+    }
+    return true;
+}
+```
+
+#### 2.1.2 加载所有服务
+``` c++
+/**
+ * @brief 加载多个服务
+ * @参数 name 服务名称列表,多个服务名称用逗号分隔
+ * @返回 true 表示所有服务加载成功,false 表示至少一个服务加载失败或服务名称为空
+ */
+bool ServiceManager::loadServices(const std::string &name) {
+    // 验证服务器名称字符串是否为空
+    if (name.empty()) {
+        // 服务名称为空,返回加载失败
+        return false;
+    }
+
+    // 获取所有需要加载的服务名称
+    std::vector<std::string> tokens;
+
+    size_t startPos = 0;
+    size_t endPos = name.find(',');
+
+    // 将服务名称解析为单独的字符串,并存储到 tokens 向量中
+    while (endPos != std::string::npos) {
+        tokens.push_back(name.substr(startPos, endPos - startPos));
+        startPos = endPos + 1;
+        endPos = name.find(',', startPos);
+    }
+
+    tokens.push_back(name.substr(startPos));
+
+    // 循环加载服务,只要一个失败就认为加载失败
+    bool success = true;
+
+    for (const auto &token: tokens) {
+        if (!createService(token)) {
+            // 加载服务失败,设置 success 为 false,并跳出循环
+            success = false;
+            break;
+        }
+    }
+
+    return success;
+}
+```
+
+#### 2.1.3 创建服务
+``` c++
+/**
+ * @brief 创建服务
+ * @参数 name 服务名称
+ * @返回 true 表示服务创建成功,false 表示服务创建失败或已存在
+ */
+bool ServiceManager::createService(const std::string &name) {
+    auto service = findService(name);
+    if (service) {
+        return true;
+    }
+
+    // 分配空间
+    service = static_cast<TService *>(malloc(sizeof(*service)));
+    memset(service, 0, sizeof(*service));
+
+    // 加载服务对应的动态库
+    if (!ModuleController::getInstance()->loadModule(&service->module, name)) {
+        delete (service);
+        return false;
+    }
+
+    // 将服务纳入管理
+    insertService(service);
+
+    return true;
+}
+```
+
+#### 2.1.4 将服务纳入管理
+``` c++
+/**
+ * 将服务纳入管理
+ * @param service 需要纳入管理的服务
+ */
+void ServiceManager::insertService(TService *service) {
+    serviceList.push_back(service);
+    service->serviceId = serviceList.size() - 1;
+}
+```
+
+### 2.2 初始化所有服务流程
+#### 2.2.1 初始化所有服务
+``` c++
+/**
+ * 初始化服务
+ * @param name 如果 name 不为空,则初始化指定名称的服务
+ *             如果 name 为空,则初始化所有服务
+ * @return true 表示服务初始化成功,false 表示服务初始化失败或服务不存在
+ */
+bool ServiceManager::initServices(const std::string &name) {
+    if (!name.empty()) {
+        // 初始化指定名称的服务
+        TService *service = findService(name);
+
+        if (service) {
+            // 调用 initService 函数来初始化服务
+            return initService(service);
+        }
+
+        // 指定名称的服务不存在
+        return false;
+    } else {
+        // 初始化所有服务
+        for (auto service: serviceList) {
+            if (service) {
+                if (!initService(service)) {
+                    // 初始化服务失败
+                    return false;
+                }
+            }
+        }
+    }
+
+    // 所有服务初始化成功
+    return true;
+}
+```
+
+#### 2.2.2 初始化单个服务
+``` c++
+/**
+ * 初始化单个服务
+ * @param service 服务
+ * @return
+ */
+bool ServiceManager::initService(TService *service) {
+    if (!service->inited) {
+        if (!service->module.init(service)) {
+            return false;
+        }
+        service->inited = true;
+    }
+    return true;
+}
+```

+ 43 - 0
C/3、服务器模块/2、时间控制器.md

@@ -0,0 +1,43 @@
+# 一、时间控制器
+
+时间控制器主要为了获取服务器时间,包括系统级别的当前时间,系统启动的时间,持续的时间。
+
+## 1、实例结构
+```c++
+//
+// Created by 诸葛温侯 on 2023/9/25.
+//
+class TimeController {
+public:
+    static TimeController *getInstance();
+
+    static uint64_t getSystemNowTime();
+
+    static uint64_t getSystemElapsedTime();
+
+    void setElapsedTime();
+
+    void setDirty();
+
+    uint64_t getNowTime();
+
+    uint64_t getElapsedTime();
+
+private:
+    TimeController();
+
+    ~TimeController();
+
+private:
+    static TimeController *instance;
+
+    // 系统启动时间,仅在系统启动时记录
+    uint64_t startTime;;
+
+    // 系统启动持续时间
+    uint64_t elapsedTime;
+
+    // 是否需要更新持续时间 
+    bool dirty;
+};
+```

+ 131 - 0
C/3、服务器模块/3、事件机制.md

@@ -0,0 +1,131 @@
+# 服务器模块
+## 3、事件机制
+
+### 3.1 事件结构体
+```c++
+/**
+ * @brief 事件结构体
+ */
+typedef struct event {
+    int serviceId;      // 事件所属服务
+    int interval;       // 事件间隔
+    uint64_t nextTime;  // 下一次触发事件
+} TEvent;
+```
+
+### 3.2 事件管理器
+```c++
+class EventManager {
+public:
+    static EventManager *getInstance();
+
+    // 注册事件
+    void registerEvent(int serviceId, int interval, uint64_t elapsed_time);
+    // 调度超时事件
+    void dispatchTimeoutEvent();
+    // 获取下一事件最大的超时时间
+    int getNextEventMaxTimeout();
+    // 获取事件管理器中最接近的事件时间
+    uint64_t getClosetTime();
+
+private:
+    EventManager();
+
+    ~EventManager();
+
+private:
+    static EventManager *instance;
+    std::vector<event *> events;
+};
+```
+
+### 3.2.1 注册事件
+```c++
+/**
+ * @brief 注册一个事件到事件管理器中
+ * @参数 serviceId 事件的服务ID
+ * @参数 interval 事件的时间间隔
+ * @参数 elapsedTime 当前经过的时间
+ */
+void EventManager::registerEvent(int serviceId, int interval, uint64_t elapsedTime) {
+    auto *e = new event;
+    e->serviceId = serviceId;
+    e->interval = interval;
+    e->nextTime = elapsedTime + interval;
+    events.push_back(e);
+}
+```
+
+### 3.2.2 获取下一事件时间
+```c++
+/**
+ * @brief 获取事件管理器中最接近的事件时间
+ * @返回 最接近的事件时间,如果不存在事件,则返回特殊值 -1
+ */
+uint64_t EventManager::getClosetTime() {
+    uint64_t closestTime = std::numeric_limits<uint64_t>::max(); // 初始化最接近时间为最大可能值
+
+    // 遍历事件管理器中的所有事件
+    for (const auto &e: events) {
+        // 更新最接近时间
+        if (e->nextTime < closestTime) {
+            closestTime = e->nextTime;
+        }
+    }
+
+    if (closestTime == std::numeric_limits<uint64_t>::max()) {
+        closestTime = -1; // 如果不存在事件,则将最接近时间置为 -1
+    }
+
+    return closestTime;
+}
+```
+
+### 3.2.3 获取下一事件最大超时时间
+```c++
+/**
+ * @brief 获取下一个事件的最大超时时间
+ * @返回 最大的超时时间
+ */
+int EventManager::getNextEventMaxTimeout() {
+    // 获取系统级消耗时间
+    uint32_t elapsedTime = TimeController::getSystemElapsedTime();
+
+    // 设置需要更新
+    TimeController::getInstance()->setDirty();
+
+    // 下一个最近事件的时间
+    uint64_t nextTime = getClosetTime();
+
+    // 初始化超时时间
+    int timeout = -1;
+
+    if (nextTime != -1) {
+        timeout = nextTime > elapsedTime ? static_cast<int>(nextTime - elapsedTime) : 0;
+    }
+    return timeout;
+}
+```
+
+### 3.2.4 调度超时事件
+```c++
+/**
+ * @brief 触发超时事件,更新下一次触发时间并执行相应的通知服务操作
+ */
+void EventManager::dispatchTimeoutEvent() {
+    // 更新时间戳为当前经过的时间
+    TimeController::getInstance()->setElapsedTime();
+
+    // 遍历所有事件
+    for (const auto &e: events) {
+        if (e->nextTime < TimeController::getInstance()->getElapsedTime()) {
+            // 如果事件的下一次触发时间早于当前经过的时间
+            // 执行相应的通知服务操作
+            // service_notify_time(e->serviceId);
+
+            // 更新事件的下一次触发时间
+            e->nextTime += e->interval;
+        }
+    }
+}
+```

+ 103 - 0
C/9、动态库加载/概述.md

@@ -0,0 +1,103 @@
+```c++
+//
+// Created by 诸葛温侯 on 2023/9/27.
+//
+
+#ifndef JIANGHUKE_SERVICE_GATE_H
+#define JIANGHUKE_SERVICE_GATE_H
+
+class ServiceGate {
+public:
+    ServiceGate();
+
+    void create();
+};
+
+#endif //JIANGHUKE_SERVICE_GATE_H
+
+```
+
+``` c++
+//
+// Created by 诸葛温侯 on 2023/9/25.
+//
+#include "service_gate.h"
+#include <iostream>
+
+ServiceGate::ServiceGate() = default;
+
+void ServiceGate::create() {
+    std::cout << "haha" << std::endl;
+}
+
+// 导出create函数
+extern "C" {
+void create() {
+    ServiceGate gate;
+    gate.create();
+}
+}
+```
+
+```c++
+#include <iostream>
+
+#ifndef WIN32
+
+#include <dlfcn.h>
+
+#   define OPEN_LIBRARY(libName) dlopen(libName, RTLD_NOW | RTLD_GLOBAL)
+#   define CLOSE_LIBRARY(handle) dlclose(handle)
+#   define GET_SYMBOL(handle, symName) dlsym(handle, symName)
+#   define GET_ERROR() dlerror()
+#else
+
+#include <winsock2.h>
+#include <Windows.h>
+
+#ifdef UNICODE
+#   define OPEN_LIBRARY(libName) LoadLibraryW(libName)
+#else
+#   define OPEN_LIBRARY(libName) LoadLibraryA(libName)
+#endif
+
+#   define CLOSE_LIBRARY(handle) FreeLibrary(static_cast<HMODULE>(handle))
+#   define GET_SYMBOL(handle, symName) GetProcAddress(static_cast<HMODULE>(handle), symName)
+#   define GET_ERROR() "Windows error"
+
+#endif
+
+struct module {
+    void (*create)();
+};
+
+int main(int argc, char *argv[]) {
+    void *libraryHandle = OPEN_LIBRARY("./service_gate.so");
+    if (libraryHandle == nullptr) {
+        // 动态库加载失败
+        const char *error = GET_ERROR();
+        std::cout << error << std::endl;
+    }
+
+    // 获取create函数指针
+    typedef void (*CreateFunc)();
+    auto create = reinterpret_cast<CreateFunc>(GET_SYMBOL(libraryHandle, "create"));
+
+    if (create == nullptr) {
+        // 获取函数指针失败
+        const char *error = GET_ERROR();
+        std::cout << error << std::endl;
+    }
+
+    module mod{};
+    mod.create = create;
+
+    mod.create();
+
+
+    CLOSE_LIBRARY(libraryHandle);
+    return 0;
+
+}
+
+```

+ 270 - 0
C/test.md

@@ -0,0 +1,270 @@
+```c++
+//
+//
+//#include <iostream>
+//#include <Python.h>
+//
+//PyObject* globalDict; // 全局字典,用于存储从 config.py 中读取的变量
+//
+//void sc_env_load() {
+//    Py_Initialize();
+//
+////    // 执行 config.py 文件
+////    PyObject* module = PyImport_ImportModule("config");
+////    if (module) {
+////        // 获取全局字典
+////        globalDict = PyModule_GetDict(module);
+////
+////        // 释放 Python 对象
+////        Py_DECREF(module);
+////    } else {
+////        std::cout << "Failed to load config.py." << std::endl;
+////    }
+//    // 执行 config.py 文件
+//    FILE* file = fopen(R"(D:\Code\C++\Core\cmake-build-debug\bin\config_gate.py)", "r");
+//    if (file) {
+//        fseek(file, 0L, SEEK_END);
+//        int size = ftell(file);
+//        fseek(file, 0L, SEEK_SET);
+//
+//        char* buffer = new char[size + 1];
+//        fread(buffer, sizeof(char), size, file);
+//        buffer[size] = '\0';
+//
+//        fclose(file);
+//
+//        // 执行文件内容
+//        PyRun_SimpleString(buffer);
+//        delete[] buffer;
+//
+//        // 获取全局字典
+//        PyObject* mainModule = PyImport_AddModule("__main__");
+//        if (mainModule) {
+//            globalDict = PyModule_GetDict(mainModule);
+//        }
+//    } else {
+//        std::cout << "Failed to open config.py." << std::endl;
+//    }
+//}
+//
+//const char* get_python_variable(const char* key) {
+//    if (globalDict) {
+//        PyObject* pyValue = PyDict_GetItemString(globalDict, key);
+//        if (pyValue) {
+//            PyObject* pyReprStr = PyObject_Repr(pyValue);
+//            if (pyReprStr) {
+//                PyObject* pyUtf8Str = PyUnicode_AsUTF8String(pyReprStr);
+//                if (pyUtf8Str) {
+//                    const char* strValue = PyBytes_AsString(pyUtf8Str);
+//                    if (strValue) {
+//                        // 复制字符串值并返回
+//                        char* result = new char[strlen(strValue) + 1];
+//                        strcpy(result, strValue);
+//                        // 释放 Python 对象
+//                        Py_DECREF(pyReprStr);
+//                        Py_DECREF(pyUtf8Str);
+//                        return result;
+//                    }
+//                    Py_DECREF(pyUtf8Str);
+//                }
+//                Py_DECREF(pyReprStr);
+//            }
+//        }
+//    }
+//
+//    std::cout << "Variable " << key << " not found." << std::endl;
+//    return nullptr;
+//}
+//
+//int main() {
+//    // 加载配置
+//    sc_env_load();
+//
+//    // 获取 ip 变量值
+//    const char* ip = get_python_variable("iip");
+//    if (ip) {
+//        std::cout << "IP: " << ip << std::endl;
+//    }
+//
+//    // 获取 port 变量值
+//    const char* port = get_python_variable("port");
+//    if (port) {
+//        std::cout << "Port: " << port << std::endl;
+//    }
+//
+//    // 获取 port 变量值
+//    const char* web_addr = get_python_variable("web_addr");
+//    if (port) {
+//        std::cout << "web_addr: " << web_addr << std::endl;
+//    }
+//
+//    // 获取 port 变量值
+//    const char* hb = get_python_variable("hb");
+//    if (port) {
+//        std::cout << "hb: " << hb << std::endl;
+//    }
+//
+//    // 释放 Python 解释器
+//    Py_Finalize();
+//
+//    return 0;
+//}
+
+#include <iostream>
+#include <Python.h>
+
+PyObject* globalDict; // 全局字典,用于存储从config.py中读取的变量
+
+void sc_env_load() {
+    Py_Initialize();
+
+    // 打开并读取config.py文件
+    FILE* file = fopen("config.py", "r");
+    if (file) {
+        fseek(file, 0L, SEEK_END);
+        int size = ftell(file);
+        fseek(file, 0L, SEEK_SET);
+
+        char* buffer = new char[size + 1];
+        fread(buffer, sizeof(char), size, file);
+        buffer[size] = '\0';
+
+        fclose(file);
+
+        // 执行文件内容
+        PyRun_SimpleString(buffer);
+        delete[] buffer;
+
+        // 获取全局字典
+        PyObject* mainModule = PyImport_AddModule("__main__");
+        if (mainModule) {
+            globalDict = PyModule_GetDict(mainModule);
+        }
+    } else {
+        std::cout << "Failed to open config.py." << std::endl;
+    }
+}
+
+const char* get_python_variable(const char* key) {
+    if (globalDict) {
+        PyObject* pyValue = PyDict_GetItemString(globalDict, key);
+        if (pyValue) {
+            PyObject* pyReprStr = PyObject_Repr(pyValue);
+            if (pyReprStr) {
+                PyObject* pyUtf8Str = PyUnicode_AsUTF8String(pyReprStr);
+                if (pyUtf8Str) {
+                    const char* strValue = PyBytes_AsString(pyUtf8Str);
+                    if (strValue) {
+                        // 复制字符串值并返回
+                        char* result = new char[strlen(strValue) + 1];
+                        strcpy(result, strValue);
+                        // 释放 Python 对象
+                        Py_DECREF(pyReprStr);
+                        Py_DECREF(pyUtf8Str);
+                        return result;
+                    }
+                    Py_DECREF(pyUtf8Str);
+                }
+                Py_DECREF(pyReprStr);
+            }
+        }
+    }
+
+    // 返回空指针表示未找到变量
+    return nullptr;
+}
+
+int main() {
+    // 加载配置
+    sc_env_load();
+
+    // 获取 gate_clientlive 变量
+    const char* gate_clientlive = get_python_variable("gate_clientlive");
+    if (gate_clientlive) {
+        std::cout << "Gate ClientLive: " << gate_clientlive << std::endl;
+    } else {
+        std::cout << "Gate ClientLive not found." << std::endl;
+    }
+
+    // 获取 gate_need_load 变量
+    const char* gate_need_load = get_python_variable("gate_need_load");
+    if (gate_need_load) {
+        std::cout << "Gate NeedLoad: " << gate_need_load << std::endl;
+    } else {
+        std::cout << "Gate NeedLoad not found." << std::endl;
+    }
+
+    // 释放 Python 解释器
+    Py_Finalize();
+
+    return 0;
+}
+
+```
+
+```c++
+#include <Python.h>
+#include <iostream>
+
+PyObject *mainModule;
+PyObject *globalDict;
+
+void init_python() {
+    Py_Initialize();  // 初始化Python解释器
+    mainModule = PyImport_AddModule("__main__");  // 获取 __main__ 模块对象
+    globalDict = PyModule_GetDict(mainModule);  // 获取全局变量字典
+}
+
+void set_python_variable(const char* key, const char* value) {
+    PyObject* pyKey = PyUnicode_FromString(key);
+    PyObject* pyValue = PyUnicode_FromString(value);
+    PyDict_SetItem(globalDict, pyKey, pyValue);
+    Py_DECREF(pyKey);
+    Py_DECREF(pyValue);
+}
+
+const char* get_python_variable(const char* key) {
+    PyObject* pyKey = PyUnicode_FromString(key);
+    PyObject* pyValue = PyDict_GetItem(globalDict, pyKey);
+    Py_DECREF(pyKey);
+    if (pyValue != NULL) {
+        return PyUnicode_AsUTF8(pyValue);
+    }
+    return NULL;
+}
+
+void cleanup_python() {
+    Py_XDECREF(globalDict);
+    Py_XDECREF(mainModule);
+    Py_Finalize();  // 清理Python解释器
+}
+
+void sc_env_load(const char* file) {
+    FILE* scriptFile = fopen(file, "r");
+    if (scriptFile == NULL) {
+        std::cout << "Failed to open script file: " << file << std::endl;
+        return;
+    }
+
+    PyRun_SimpleFile(scriptFile, file);
+    fclose(scriptFile);
+}
+
+void test_python_variables() {
+    const char* ip = get_python_variable("ip");
+    if (ip != NULL) {
+        std::cout << "ip: " << ip << std::endl;
+    } else {
+        std::cout << "ip is not defined." << std::endl;
+    }
+}
+
+int main() {
+    init_python();
+    sc_env_load("config.py");
+    test_python_variables();
+    cleanup_python();
+
+    return 0;
+}
+```

+ 9 - 0
C/基础知识.md

@@ -0,0 +1,9 @@
+使用 char* 还是 std::string 取决于具体的需求和偏好。
+
+char* 是传统的 C 风格字符串,用于处理字符数组。它具有更低的内存开销和更高的执行效率,但需要手动管理内存和字符串操作。使用 char* 可能需要更多的代码来处理字符串的分配、释放、复制和连接等操作,而且容易出现缓冲区溢出等问题。
+
+std::string 是 C++ 标准库提供的字符串类,封装了常见的字符串操作和自动管理内存的功能。它具有更高的安全性和便利性,可以方便地进行字符串连接、复制和格式化等操作,而无需手动管理内存。但是,使用 std::string 会稍微增加一些额外的开销。
+
+综上所述,如果您在代码中有大量的字符串操作,或者需要更高的安全性和便利性,那么使用 std::string 可能更合适。但如果您对性能要求较高,有明确的内存管理需求,或者在已经有现有的 C 风格字符串代码基础上进行维护,那么使用 char* 可能更适合。
+
+无论选择哪种方式,都应当根据具体需求和场景进行评估和选择,以便在代码中保持一致性和可维护性。

+ 1 - 0
Common/apike.md

@@ -0,0 +1 @@
+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

+ 3 - 0
Common/快捷键.md

@@ -0,0 +1,3 @@
+Visual Studio 格式化 Ctrl+K -> Ctrl+F
+Visual Studio 注释 Ctrl+K -> Ctrl+C
+Visual Studio 取消注释 Ctrl+K -> Ctrl+U

+ 39 - 0
Unity/修改日志/10. 日志管理器 Logger.md

@@ -0,0 +1,39 @@
+# Logger 日志管理器
+#### 2023.09.08
+
+```c#
+using UnityEngine;
+
+public class Logger
+{
+    /// <summary>
+    /// 显示日志信息
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="args"></param>
+    public static void Log(string message, params object[] args)
+    {
+        Debug.LogFormat("Log: {0}", string.Format(message, args));
+    }
+
+    /// <summary>
+    /// 显示错误信息
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="args"></param>
+    public static void Error(string message, params object[] args)
+    {
+        Debug.LogErrorFormat("Error: {0}", string.Format(message, args));
+    }
+
+    /// <summary>
+    /// 显示警告信息
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="args"></param>
+    public static void Notice(string message, params object[] args)
+    {
+        Debug.LogWarningFormat("Notice: {0}", string.Format(message, args));
+    }
+}
+```

+ 74 - 0
Unity/修改日志/11、日志管理器 ConfigDataManager.md

@@ -0,0 +1,74 @@
+# 日志管理器 ConfigDataManager
+#### 2023.09.08
+```c# 
+public class ConfigDataManager : SingletonBase<ConfigDataManager> {
+    private DataReadLodingTips m_pLoadingTipsConfig;
+
+    public void Init()
+    {
+        // 读取游戏的TIPS信息
+        m_pLoadingTipsConfig = new DataReadLoadingTips();
+        m_pLoadingTipsConfig.Path = PathConstants.TIP_PATH;
+        m_pLoadingTipsConfig.Init();
+        read_config(m_pLoadingTipsConfig);
+    }
+
+    /// <summary>
+    ///  获取游戏的TIPS信息
+    /// </summary>
+    /// <returns></returns>
+    public DataReadLoadingTips GetLoadingTipsConfig()
+    {
+        return m_pLoadingTipsConfig;
+    }
+
+    /// <summary>
+    /// 具体的读取配置文件
+    /// </summary>
+    /// <param name="dataBase"></param>
+    /// <param name="special"></param>
+    private void read_config(DataReadBase dataBase, int special = 0)
+    {
+        //Debug.Log("begin read config : " + dataBase.Path);
+        TextAsset ta = null;
+        BundleMemManager.DebugVersion = true;
+
+        if (BundleMemManager.DebugVersion)
+        {
+            ta = BundleMemManager.Instance.LoadResource(dataBase.Path) as TextAsset;
+        }
+        else
+        {
+            AssetBundle bundle = BundleMemManager.Instance.ConfigBundle;
+            ta = bundle.LoadAsset(ToolFunc.TrimPath(dataBase.Path)) as TextAsset;
+        }
+
+        if (ta != null)
+        {
+            XmlDocument xmlDoc = new XmlDocument();
+            xmlDoc.LoadXml(ta.text);
+            XmlNodeList nodeList = xmlDoc.SelectSingleNode(dataBase.GetRootNodeName()).ChildNodes;
+            for (int k = 0; k < nodeList.Count; k++)
+            {
+                XmlElement xe = nodeList.Item(k) as XmlElement;
+                if (xe == null)
+                    continue;
+                string key = xe.GetAttribute("ID");
+                for (int i = 0; i < xe.Attributes.Count; i++)
+                {
+                    XmlAttribute attr = xe.Attributes[i];
+                    try
+                    {
+                        dataBase.AppendAttribute(int.Parse(key), attr.Name, attr.Value);
+                    }
+                    catch (System.Exception ex)
+                    {
+                        Logger.Notice("读取配置文件时发生错误:{0}", ex.ToString());
+                    }
+
+                }
+            }
+        }
+    }
+}
+```

+ 114 - 0
Unity/修改日志/12、本地数据结构 ReadLocalData.md

@@ -0,0 +1,114 @@
+# 12、本地数据结构 ReadLocalData
+#### 2023.09.08
+
+##### 基础类
+主要定义了基础的属性和必须实现的方法,其中定义个了配置文件的路径和一个用来存储配置信息的空间
+``` c#
+using System.Collections;
+
+/// <summary>
+/// 本地数据基础类
+/// </summary>
+public class DataReadBase 
+{
+    /// <summary>
+    /// 数据文件路径
+    /// </summary>
+    public string m_strPath;
+
+    /// <summary>
+    /// 哈希表,用来存储数据的空间
+    /// </summary>
+    protected Hashtable m_hashData;
+
+    public void Init()
+    {
+        m_hashData = new Hashtable();
+    }
+
+    /// <summary>
+    /// 获取Root节点的名称
+    /// </summary>
+    /// <returns></returns>
+    public virtual string GetRootNodeName() { return ""; }
+
+    /// <summary>
+    /// 追加属性值
+    /// </summary>
+    /// <param name="key"></param>
+    /// <param name="name"></param>
+    /// <param name="value"></param>
+    public virtual void AppendAttribute(int key, string name, string value) { }
+
+    /// <summary>
+    /// 是否存在对应的关键字
+    /// </summary>
+    /// <param name="key"></param>
+    /// <returns></returns>
+    public virtual bool HasRecord(int key) { return Data.ContainsKey(key); }
+}
+```
+
+
+##### Tips信息
+```c#
+/// <summary>
+/// 加载Tips提示
+/// </summary>
+public class LoadingTipsItem
+{
+    public int id;
+    public string tip;
+}
+
+/// <summary>
+/// 读取加载Tips提示数据
+/// </summary>
+public class DataReadLoadingTips : DataReadBase
+{
+
+    public override string GetRootNodeName()
+    {
+        return "RECORDS";
+    }
+
+    public override void AppendAttribute(int key, string name, string value)
+    {
+        LoadingTipsItem di;
+
+        if (!Data.ContainsKey(key))
+        {
+            di = new LoadingTipsItem();
+            Data.Add(key, di);
+        }
+
+        di = (LoadingTipsItem)Data[key];
+
+        switch (name)
+        {
+            case "ID":
+                di.id = int.Parse(value);
+                break;
+            case "Tips":
+                di.tip = value;
+                break;
+        }
+    }
+
+    public LoadingTipsItem GetTipsData(int key)
+    {
+        if (!Data.ContainsKey(key))
+        {
+            LoadingTipsItem temp = new LoadingTipsItem();
+            return temp;
+        }
+        return (LoadingTipsItem)Data[key];
+    }
+
+    public int GetTipSize()
+    {
+        return Data.Count;
+    }
+}
+
+```

+ 0 - 0
Unity/修改日志/7、输入信号处理器 InputActionsHandler.md → Unity/修改日志/8、输入信号处理器 InputActionsHandler.md


+ 62 - 0
Unity/修改日志/9、单例模式 Singleton.md

@@ -0,0 +1,62 @@
+# SingletonBase 单例模式
+#### 2023.09.08
+
+
+```c#
+using System;
+using UnityEngine;
+
+public class SingletonBase<T> where T : new()
+{
+	private static T s_instance;
+
+	public static T GetInstance()
+	{
+		if (s_instance == null)
+		{
+			s_instance = new T();
+		}
+		return s_instance;
+	}
+
+	public static bool IsCreated()
+	{
+		return (s_instance != null);
+	}
+}
+```
+
+## 改版后
+```c#
+using System;
+using System.Reflection;
+using System.Threading;
+
+public abstract class SingletonBase<T> where T : class
+{
+    private static readonly Lazy<T> instance = new Lazy<T>(() =>
+    {
+        var type = typeof(T);
+
+        // 使用反射来获取类型的默认构造函数
+        var constructor = type.GetConstructor(
+            BindingFlags.Instance | BindingFlags.NonPublic,
+            null, Type.EmptyTypes, null);
+
+        if (constructor == null)
+        {
+            throw new InvalidOperationException($"No private constructor found for '{type.Name}'.");
+        }
+
+        // 使用默认构造函数创建实例
+        return (T)constructor.Invoke(null);
+    }, LazyThreadSafetyMode.ExecutionAndPublication);
+
+    public static T Instance => instance.Value;
+
+    protected SingletonBase()
+    {
+        // 受保护的构造函数
+    }
+}
+```

+ 10 - 0
Unity/基础知识/3、鼠标点击事件检测.md

@@ -0,0 +1,10 @@
+# 鼠标点击事件检测
+## 
+场景中添加得对应得Object,需要添加一个Box Collider 2D 组件,并且将该Object得Layer改成Item。
+```c#
+if(Input.GetMouseButton(0))
+{
+    // 用于检测与鼠标位置重叠得任何对象是否附加了碰撞器,并且该对象在"Item"图层中
+    Collider2D hit = Physics2D.OverlapPoint(Camera.main.ScreenToWorldPoint(Input.mousePosition), 1 << LayerMask.NameToLayer("Item"));
+}
+```

+ 22 - 4
Unity/基础知识/基础知识.md

@@ -1,5 +1,5 @@
 # 基础知识
-## Time.deltaTime
+## 一、Time.deltaTime
 `Time.deltaTime`是一个用于计算每一帧的时间间隔的浮点数值。它表示从上一帧到当前帧之间的时间差。
 通常,在游戏中需要根据每一帧的时间间隔来进行动画、物理模拟和其他与时间相关的计算。Time.deltaTime可以用于将这些计算与不同帧率的设备保持一致,以便在不同的硬件上实现平滑和一致的游戏体验。
 例如,你可以使用Time.deltaTime来调整移动速度,使得对象在不同帧率下移动的距离相同,从而避免由于帧率变化导致的移动速度变化。
@@ -17,8 +17,26 @@ void Update()
 ```
 在这个示例中,Time.deltaTime用于计算每一帧的时间间隔,并将其乘以移动速度来获取每一帧应该移动的距离。这样可以确保对象的移动速度在不同帧率下保持一致。
 
-## Update
+## 二、Update
 `Update`函数在每一帧中被调用,通常用于处理玩家输入、移动物体、执行游戏逻辑等。它是最常用的更新函数之一,因为它在每一帧中都会被调用,而且可以处理实时的用户输入和动态变化的情况。但需要注意的是,由于不同设备上的帧率可能不同,Update函数并不是固定时间间隔被调用的。
 
-## FixedUpdate
-`FixedUpdate`函数与Update函数类似,但它以固定的时间间隔被调用,通常是每一帧的物理时间步长。这使得FixedUpdate函数在处理涉及物理模拟或刚体运动的逻辑时非常有用。例如,如果你在游戏中使用了物理引擎来模拟对象的运动,那么将物理相关的代码放在FixedUpdate函数中可以保证物理计算以恒定的速率进行,使得游戏在不同帧率下的行为更加稳定和一致。
+## 三、FixedUpdate
+`FixedUpdate`函数与Update函数类似,但它以固定的时间间隔被调用,通常是每一帧的物理时间步长。这使得FixedUpdate函数在处理涉及物理模拟或刚体运动的逻辑时非常有用。例如,如果你在游戏中使用了物理引擎来模拟对象的运动,那么将物理相关的代码放在FixedUpdate函数中可以保证物理计算以恒定的速率进行,使得游戏在不同帧率下的行为更加稳定和一致。
+
+## 四、如何转换Enum值
+``` c#
+public enum Type
+{
+    None = 0,
+    B,
+    C
+}
+
+public void AA(int number)
+{
+    Type enumValue = (Type)Enum.Parse(typeof(Type), number.ToString());
+    
+    // 在此处使用枚举值
+    Debug.Log("Enum value: " + enumValue);
+}
+```

+ 0 - 0
Unity/案例/案例一/移动.md → Unity/案例/1、/移动.md


+ 0 - 0
Unity/案例/案例二/概述.md → Unity/案例/2、2DRPG/概述.md


+ 0 - 0
Unity/案例/案例三 农场/1、前期准备.md → Unity/案例/3、农场/1、前期准备.md


+ 0 - 0
Unity/案例/案例三 农场/2、添加角色.md → Unity/案例/3、农场/2、添加角色.md


+ 0 - 0
Unity/案例/案例三 农场/3、动作触发.md → Unity/案例/3、农场/3、动作触发.md


+ 0 - 0
Unity/案例/案例三 农场/4、场景设置.md → Unity/案例/3、农场/4、场景设置.md


+ 0 - 0
Unity/案例/案例三 农场/5、道具设定.md → Unity/案例/3、农场/5、道具设定.md


+ 0 - 0
Unity/案例/案例三 农场/6、UI.md → Unity/案例/3、农场/6、UI.md


+ 0 - 0
Unity/案例/案例四/概述.md → Unity/案例/4、少儿学习/概述.md


+ 3 - 2
Unity/案例/案例五/1、概述.md → Unity/案例/5、三消/1、概述.md

@@ -2,9 +2,10 @@
 
 ## 关卡消除地图说明
 
-关卡消除地图是一个二维矩阵,具体尺寸可以分为列(Col)和行(Row),而这个二维矩阵又有块(Square)组成,这个块上面将会放入一个实体(SquareBlocks)或者是一个物品(Item)
+关卡消除地图是一个二维矩阵,具体尺寸可以分为列(Col)和行(Row),而这个二维矩阵又有块`(Square)`组成,这个块上面将会放入一个实体`(SquareBlocks)`
+或者是一个物品`(Item)`
 
-而我们在初始化地图时,我们将创建一个可以存放所有块数据的数组中(squaresArray)和一个可以存放所有块实体的数组(squareBlocksArray)
+而我们在初始化地图时,我们将创建一个可以存放所有块数据的数组中`(squaresArray)`和一个可以存放所有块实体的数组 `(squareBlocksArray)`
 
 ### 初始化逻辑
 

+ 0 - 0
Unity/案例/案例五/2、关卡选择.md → Unity/案例/5、三消/2、关卡选择.md


+ 0 - 0
Unity/案例/案例五/3、渲染逻辑.md → Unity/案例/5、三消/3、渲染逻辑.md


+ 23 - 0
Unity/案例/5、三消/名词定义.md

@@ -0,0 +1,23 @@
+#  三消游戏
+
+## 名词定义
+### 1、Item -> 消除的对象
+``` c#
+public enum ItemType {
+}
+```
+
+### 2、Square - > 地图块(消除块)
+根据关卡设定的宽高,需要先铺上块,然后才能再在上面放Item
+
+```c#
+public enum SquareType {
+
+}
+```
+
+### 3、Boost -> 辅助道具
+``` c#
+public enum BoostType {
+}
+```

+ 28 - 0
Unity/案例/5、三消/建项步骤.md

@@ -0,0 +1,28 @@
+# 三消游戏
+
+## 建项步骤
+### 1、创建实体类Item和Square
+这个2个实体是整个游戏最基础的对象
+
+### 2、根据设定的row(行)和col(列)来创建关卡对应的棋盘
+#### 2.1 关卡管理器(LevelManager)
+我们将会创建一个全局的关卡管理器,来负责关卡相关的操作,其中包括初始化关卡(InitLevel)等。这个关卡管理器将采用单例模式。
+
+#### 2.2 资源管理器(ResourceManager)
+我们将会创建一个全局的资源管理,主要负责加载和管理相关的资源
+
+#### 2.3 初始化关卡(InitLevel)
+我们将在关卡管理器中创建这样一个方法,作为测试用途,我们将在关卡管理器的Start方法中调用它,从而达到初始化关卡的目的。
+
+#### 2.4 生成关卡Square
+在初始化关卡的方法中我们将会调用生成square的方法,也就是`GenerateSquares`,在这个方法中我们将会根据关卡总的行和高,循环调用Square控制器来创建Square(`CreateSquare`),并将其摆放在正确的位置。
+
+#### 2.5 生成关卡Item
+在初始化关卡的方法中继生成完Square之后,我们需要在这些Square上生成Item,所以我们在`InitLevel`中中继续调用方法`GenerateItems`,然后在该方法中继续循环调用Item控制器来创建Item(`CreateItem`),
+
+#### 2.6 生成多种颜色的Item
+当Item被创建时,Item本身会根据所在的行和列来判断周边的颜色,然后自动生成一个颜色,这个方法在`GenColor`中
+
+#### 2.7 完成消除逻辑
+整体的消除逻辑都放在关卡管理器(`LevelManager`)的Update中,其中涉及到的相关内容可供参考。
+1、通过判断点击的Item的Color有没有发生改变,来判断手势滑动的的连贯性。

+ 62 - 0
Unity/案例/6、3DRPG/1、概述.md

@@ -0,0 +1,62 @@
+# 3D RPG游戏
+## 一、概述
+
+## 二、核心功能
+### 1、事件管理器(EventDispatcher)
+游戏大部分逻辑都是通过事件进行触发的,
+
+1、首先需要在事件管理器中声明相关的事件,例如
+```c# 
+/// <summary>
+/// 监听对话框确认按钮
+/// </summary>
+/// <param name="type"></param>
+public delegate void HandleDialogSure(eDialogSureType type);
+
+public event HandleDialogSure DialogSure;
+public void OnDialogSure(eDialogSureType type)
+{
+    if (DialogSure != null)
+    {
+        DialogSure(type);
+    }
+}
+```
+
+2、然后再需要的地方订阅该事件
+```c#
+// 订阅监听退出确认对话框事件
+EventDispatcher.Instance.DialogSure += OnExit;
+
+private void OnExit(){
+    // 对应的逻辑
+}
+```
+
+3、最后再需要触发的地方触发该事件
+``` c#
+EventDispatcher.Instance.OnDialogSure();
+```
+
+### 2、配置管理器(ConfigDataManager)
+
+在游戏启动的时候,初始化配置管理器,在这个初始化方法中可以定义需要初始化的配置信息是什么,之后在需要的时候可以在使用相关管理器来读取相关的数据。
+
+```c#
+public void Init() {
+    // 读取游戏的TIPS信息
+    m_pLoadingTipsConfig = new DataReadLoadingTips();
+    m_pLoadingTipsConfig.m_strPath = PathConstants.TIP_PATH;
+    m_pLoadingTipsConfig.Init();        // 这里主要分配了空间
+    read_config(m_pLoadingTipsConfig);  // 通过读取配置文件然后存入相关空间
+}
+```
+
+## 三、辅助功能
+### 1、日志管理器(Logger)
+定义一个常用的日志管理器,具体可以查看[日志管理器](../../修改日志/10.%20日志管理器%20Logger.md)
+
+### 2、本地数据结构
+定义了本地会读取到的配置数据的结构,具体可以查看[本地数据结构](../../修改日志/12、本地数据结构%20ReadLocalData.md)
+
+

+ 3 - 0
Unity/案例/6、3DRPG/2、MainLogic.md

@@ -0,0 +1,3 @@
+# 3D RPG游戏
+## 二、核心逻辑(MainLogic)
+### 

+ 14 - 1
Unity/项目/不一样的江湖/1、游戏策划/1、角色系统.md

@@ -6,4 +6,17 @@
 游戏中,玩家角色允许根据实际穿着情况来显示不同的的外观,根据整体像素游戏的客观条件,我们将会允许玩家在形象方面改变以下形象:
 
 - 整体服饰(body),因为像素游戏角色本来就不大,所以不区分是上装还是下装或者是鞋子什么的。
-- 头部装饰(head)
+- 头部装饰(head)
+
+
+## 神话特色
+不同种族,在有些种族人类也会是奴隶
+
+## 上界
+谪仙
+
+## 地图参考
+### 1、炼气十万年
+### 2、五行战神
+### 3、诛仙
+价值观、人物形象