├── .clang-format ├── .github └── workflows │ └── cmake-build.yaml ├── .gitignore ├── CMakeLists.txt ├── ReadMe.md ├── Todo.md ├── demo ├── CMakeLists.txt ├── cmake │ ├── FindSDL2.cmake │ ├── copydll.cmake │ ├── fetch_sdl.cmake │ └── utility.cmake ├── include │ ├── anim.hpp │ ├── common_systems.hpp │ ├── consts.hpp │ ├── defs.hpp │ ├── game_ctx.hpp │ ├── gaming_systems.hpp │ ├── pch.hpp │ ├── physics.hpp │ ├── renderer.hpp │ ├── restart_systems.hpp │ ├── sprite.hpp │ ├── tank.hpp │ ├── texture.hpp │ ├── ticker.hpp │ ├── vmath.hpp │ ├── welcome_systems.hpp │ └── window.hpp ├── resources │ ├── bomb.bmp │ ├── falling_stone.bmp │ ├── gameover.bmp │ ├── how-to-play.bmp │ ├── how_to_start.bmp │ ├── land.bmp │ ├── numbers.bmp │ ├── shell.bmp │ └── tank.bmp └── src │ ├── common_systems.cpp │ ├── game_ctx.cpp │ ├── gaming_systems.cpp │ ├── main.cpp │ ├── renderer.cpp │ ├── restart_systems.cpp │ ├── texture.cpp │ ├── welcome_systems.cpp │ └── window.cpp ├── snapshot └── snapshot.png ├── src └── gecs │ ├── config │ └── config.hpp │ ├── container │ ├── compress_pair.hpp │ └── dense_map.hpp │ ├── core │ ├── ident.hpp │ ├── singlton.hpp │ ├── type_list.hpp │ └── utility.hpp │ ├── entity │ ├── commands.hpp │ ├── entity.hpp │ ├── event_dispatcher.hpp │ ├── fwd.hpp │ ├── querier.hpp │ ├── registry.hpp │ ├── registry_wrapper.hpp │ ├── resource.hpp │ ├── sigh_mixin.hpp │ ├── sparse_set.hpp │ ├── storage.hpp │ ├── system_constructor.hpp │ └── world.hpp │ ├── gecs.hpp │ └── signal │ ├── delegate.hpp │ ├── dispatcher.hpp │ ├── fwd.hpp │ ├── sigh.hpp │ └── sink.hpp └── test ├── CMakeLists.txt ├── catch.hpp ├── core ├── CMakeLists.txt ├── type_list.cpp └── utility.cpp ├── entity ├── CMakeLists.txt ├── commands.cpp ├── entity.cpp ├── event_dispatcher.cpp ├── querier.cpp ├── registry.cpp ├── resource.cpp ├── sparse_set.cpp └── storage.cpp ├── gecs_example.cpp └── signal ├── CMakeLists.txt ├── delegate.cpp ├── sigh.cpp └── sink.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: Right 7 | AlignConsecutiveMacros: None 8 | AlignConsecutiveAssignments: None 9 | AlignConsecutiveBitFields: None 10 | AlignConsecutiveDeclarations: None 11 | AlignEscapedNewlines: Left 12 | AlignOperands: Align 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: Inline 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: WithoutElse 22 | AllowShortLoopsOnASingleLine: true 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: Yes 27 | AttributeMacros: 28 | - __capability 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BraceWrapping: 32 | AfterCaseLabel: false 33 | AfterClass: false 34 | AfterControlStatement: Never 35 | AfterEnum: false 36 | AfterFunction: false 37 | AfterNamespace: false 38 | AfterObjCDeclaration: false 39 | AfterStruct: false 40 | AfterUnion: false 41 | AfterExternBlock: false 42 | BeforeCatch: false 43 | BeforeElse: false 44 | BeforeLambdaBody: false 45 | BeforeWhile: false 46 | IndentBraces: false 47 | SplitEmptyFunction: true 48 | SplitEmptyRecord: true 49 | SplitEmptyNamespace: true 50 | BreakBeforeBinaryOperators: None 51 | BreakBeforeConceptDeclarations: true 52 | BreakBeforeBraces: Attach 53 | BreakBeforeInheritanceComma: false 54 | BreakInheritanceList: BeforeColon 55 | BreakBeforeTernaryOperators: true 56 | BreakConstructorInitializersBeforeComma: false 57 | BreakConstructorInitializers: BeforeColon 58 | BreakAfterJavaFieldAnnotations: false 59 | BreakStringLiterals: true 60 | ColumnLimit: 80 61 | CommentPragmas: '^ IWYU pragma:' 62 | QualifierAlignment: Leave 63 | CompactNamespaces: false 64 | ConstructorInitializerIndentWidth: 4 65 | ContinuationIndentWidth: 4 66 | Cpp11BracedListStyle: true 67 | DeriveLineEnding: true 68 | DerivePointerAlignment: true 69 | DisableFormat: false 70 | EmptyLineAfterAccessModifier: Never 71 | EmptyLineBeforeAccessModifier: LogicalBlock 72 | ExperimentalAutoDetectBinPacking: false 73 | PackConstructorInitializers: NextLine 74 | BasedOnStyle: '' 75 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 76 | AllowAllConstructorInitializersOnNextLine: true 77 | FixNamespaceComments: true 78 | ForEachMacros: 79 | - foreach 80 | - Q_FOREACH 81 | - BOOST_FOREACH 82 | IfMacros: 83 | - KJ_IF_MAYBE 84 | # IncludeBlocks: Regroup 85 | # IncludeCategories: 86 | # - Regex: '^' 87 | # Priority: 2 88 | # SortPriority: 0 89 | # CaseSensitive: false 90 | # - Regex: '^<.*\.h>' 91 | # Priority: 1 92 | # SortPriority: 0 93 | # CaseSensitive: false 94 | # - Regex: '^<.*' 95 | # Priority: 2 96 | # SortPriority: 0 97 | # CaseSensitive: false 98 | # - Regex: '.*' 99 | # Priority: 3 100 | # SortPriority: 0 101 | # CaseSensitive: false 102 | # IncludeIsMainRegex: '([-_](test|unittest))?$' 103 | # IncludeIsMainSourceRegex: '' 104 | IndentAccessModifiers: false 105 | IndentCaseLabels: true 106 | IndentCaseBlocks: false 107 | IndentGotoLabels: true 108 | IndentPPDirectives: None 109 | IndentExternBlock: AfterExternBlock 110 | IndentRequires: false 111 | IndentWidth: 4 112 | IndentWrappedFunctionNames: false 113 | InsertTrailingCommas: None 114 | JavaScriptQuotes: Leave 115 | JavaScriptWrapImports: true 116 | KeepEmptyLinesAtTheStartOfBlocks: false 117 | LambdaBodyIndentation: Signature 118 | MacroBlockBegin: '' 119 | MacroBlockEnd: '' 120 | MaxEmptyLinesToKeep: 1 121 | NamespaceIndentation: None 122 | ObjCBinPackProtocolList: Never 123 | ObjCBlockIndentWidth: 2 124 | ObjCBreakBeforeNestedBlockParam: true 125 | ObjCSpaceAfterProperty: false 126 | ObjCSpaceBeforeProtocolList: true 127 | PenaltyBreakAssignment: 2 128 | PenaltyBreakBeforeFirstCallParameter: 1 129 | PenaltyBreakComment: 300 130 | PenaltyBreakFirstLessLess: 120 131 | PenaltyBreakOpenParenthesis: 0 132 | PenaltyBreakString: 1000 133 | PenaltyBreakTemplateDeclaration: 10 134 | PenaltyExcessCharacter: 1000000 135 | PenaltyReturnTypeOnItsOwnLine: 200 136 | PenaltyIndentedWhitespace: 0 137 | PointerAlignment: Left 138 | PPIndentWidth: -1 139 | RawStringFormats: 140 | - Language: Cpp 141 | Delimiters: 142 | - cc 143 | - CC 144 | - cpp 145 | - Cpp 146 | - CPP 147 | - 'c++' 148 | - 'C++' 149 | CanonicalDelimiter: '' 150 | BasedOnStyle: google 151 | - Language: TextProto 152 | Delimiters: 153 | - pb 154 | - PB 155 | - proto 156 | - PROTO 157 | EnclosingFunctions: 158 | - EqualsProto 159 | - EquivToProto 160 | - PARSE_PARTIAL_TEXT_PROTO 161 | - PARSE_TEST_PROTO 162 | - PARSE_TEXT_PROTO 163 | - ParseTextOrDie 164 | - ParseTextProtoOrDie 165 | - ParseTestProto 166 | - ParsePartialTestProto 167 | CanonicalDelimiter: pb 168 | BasedOnStyle: google 169 | ReferenceAlignment: Pointer 170 | ReflowComments: true 171 | RemoveBracesLLVM: false 172 | SeparateDefinitionBlocks: Always 173 | ShortNamespaceLines: 1 174 | # SortIncludes: CaseSensitive 175 | SortJavaStaticImport: Before 176 | SortUsingDeclarations: true 177 | SpaceAfterCStyleCast: false 178 | SpaceAfterLogicalNot: false 179 | SpaceAfterTemplateKeyword: true 180 | SpaceBeforeAssignmentOperators: true 181 | SpaceBeforeCaseColon: false 182 | SpaceBeforeCpp11BracedList: false 183 | SpaceBeforeCtorInitializerColon: true 184 | SpaceBeforeInheritanceColon: true 185 | SpaceBeforeParens: ControlStatements 186 | SpaceBeforeParensOptions: 187 | AfterControlStatements: true 188 | AfterForeachMacros: true 189 | AfterFunctionDefinitionName: false 190 | AfterFunctionDeclarationName: false 191 | AfterIfMacros: true 192 | AfterOverloadedOperator: false 193 | BeforeNonEmptyParentheses: false 194 | SpaceAroundPointerQualifiers: Default 195 | SpaceBeforeRangeBasedForLoopColon: true 196 | SpaceInEmptyBlock: false 197 | SpaceInEmptyParentheses: false 198 | SpacesBeforeTrailingComments: 2 199 | SpacesInAngles: Never 200 | SpacesInConditionalStatement: false 201 | SpacesInContainerLiterals: true 202 | SpacesInCStyleCastParentheses: false 203 | SpacesInLineCommentPrefix: 204 | Minimum: 1 205 | Maximum: -1 206 | SpacesInParentheses: false 207 | SpacesInSquareBrackets: false 208 | SpaceBeforeSquareBrackets: false 209 | BitFieldColonSpacing: Both 210 | Standard: Auto 211 | StatementAttributeLikeMacros: 212 | - Q_EMIT 213 | StatementMacros: 214 | - Q_UNUSED 215 | - QT_REQUIRE_VERSION 216 | TabWidth: 8 217 | UseCRLF: false 218 | UseTab: Never 219 | WhitespaceSensitiveMacros: 220 | - STRINGIZE 221 | - PP_STRINGIZE 222 | - BOOST_PP_STRINGIZE 223 | - NS_SWIFT_NAME 224 | - CF_SWIFT_NAME 225 | ... 226 | 227 | -------------------------------------------------------------------------------- /.github/workflows/cmake-build.yaml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml 3 | name: CMake on multiple platforms 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. 17 | fail-fast: false 18 | 19 | # Set up a matrix to run the following 3 configurations: 20 | # 1. 21 | # 2. 22 | # 3. 23 | # 24 | # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. 25 | matrix: 26 | os: [ubuntu-latest, windows-latest] 27 | build_type: [Release] 28 | c_compiler: [gcc, clang, cl] 29 | include: 30 | - os: windows-latest 31 | c_compiler: cl 32 | cpp_compiler: cl 33 | - os: ubuntu-latest 34 | c_compiler: gcc 35 | cpp_compiler: g++ 36 | - os: ubuntu-latest 37 | c_compiler: clang 38 | cpp_compiler: clang++ 39 | exclude: 40 | - os: windows-latest 41 | c_compiler: gcc 42 | - os: windows-latest 43 | c_compiler: clang 44 | - os: ubuntu-latest 45 | c_compiler: cl 46 | 47 | steps: 48 | - uses: actions/checkout@v3 49 | 50 | - name: Set reusable strings 51 | # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. 52 | id: strings 53 | shell: bash 54 | run: | 55 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" 56 | 57 | - name: Configure CMake 58 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 59 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 60 | run: > 61 | cmake -B ${{ steps.strings.outputs.build-output-dir }} 62 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} 63 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} 64 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 65 | -DGECS_BUILD_TEST=ON 66 | -S ${{ github.workspace }} 67 | 68 | - name: Build 69 | # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 70 | run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} 71 | 72 | - name: Test 73 | working-directory: ${{ steps.strings.outputs.build-output-dir }} 74 | # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 75 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 76 | run: ctest --build-config ${{ matrix.build_type }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build 2 | .vscode 3 | compile_commands.json -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(gecs 3 | LANGUAGES CXX) 4 | 5 | add_library(gecs INTERFACE) 6 | target_include_directories(gecs INTERFACE "./src") 7 | target_compile_features(gecs INTERFACE cxx_std_17) 8 | 9 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 10 | 11 | option(GECS_BUILD_TEST "enable unittest" OFF) 12 | 13 | if (PROJECT_IS_TOP_LEVEL OR GECS_BUILD_TEST) 14 | enable_testing() 15 | add_subdirectory(test) 16 | endif() 17 | 18 | option(GECS_BUILD_DEMO "build demo" OFF) 19 | 20 | if (GECS_BUILD_DEMO) 21 | add_subdirectory(demo) 22 | endif() -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # GECS 2 | 3 | `gecs` 是一个参考了[EnTT](https://github.com/skypjack/entt)源码结构,和[Bevy-ECS](https://bevyengine.org/)API的用于游戏开发的ECS系统。采用C++17。 4 | 5 | ## 使用方法 6 | 7 | `demo`下有一个完整的例子 8 | 9 | ### 基本例子 10 | 11 | `gecs`的API大量借鉴了`bevy`游戏引擎,下面是一个简单例子: 12 | 13 | ```cpp 14 | // 包含头文件 15 | #include "gecs/gecs.hpp" 16 | 17 | using namespace gecs; 18 | 19 | // 一个component类型 20 | struct Name { 21 | std::string name; 22 | }; 23 | 24 | // 一个resource类型 25 | struct Res { 26 | int value; 27 | }; 28 | 29 | // 每帧都会更新的system 30 | void update_system(commands cmds, querier querier, resource res) { 31 | for (auto& [_, name] : querier) { 32 | std::cout << name.name << std::endl; 33 | } 34 | 35 | std::cout << res->value << std::endl; 36 | } 37 | 38 | int main() { 39 | world world; 40 | 41 | // 得到Lambda对应的函数指针 42 | constexpr auto startup = +[](commands cmds) { 43 | auto entity1 = cmds.create(); 44 | cmds.emplace(entity1, Name{"ent1"}); 45 | auto entity2 = cmds.create(); 46 | cmds.emplace(entity2, Name{"ent2"}); 47 | 48 | cmds.emplace_resource(Res{123}); 49 | }; 50 | 51 | // 注册这个函数指针 52 | world.regist_startup_system(); 53 | 54 | // 使用普通函数 55 | world.regist_update_system(); 56 | // 使用函数指针也可 57 | // world.regist_update_system<&update_system>(); 58 | 59 | world.startup(); 60 | world.update(); 61 | 62 | return 0; 63 | } 64 | ``` 65 | 66 | ### world 67 | 68 | `world` 是整个ECS的核心类,管理几乎所有ECS数据。 69 | 70 | 使用默认构造函数创建一个即可: 71 | 72 | ```cpp 73 | gecs::world world; 74 | ``` 75 | 76 | 一个典型的ECS程序一般如下: 77 | 78 | ```cpp 79 | // 创建world 80 | gecs::world world; 81 | 82 | // 声明一个registry 83 | auto& reg = gaming_world.regist_registry("gaming"); 84 | 85 | // 注册startup system 86 | reg.regist_starup_system(); 87 | reg.regist_starup_system(); 88 | ... 89 | 90 | // 注册update system 91 | reg.regist_update_system(); 92 | reg.regist_update_system(); 93 | ... 94 | 95 | // 启动ECS 96 | world.startup(); 97 | 98 | // 游戏循环中每帧更新ECS 99 | while (shouldClose()) { 100 | world.update(); 101 | } 102 | 103 | // 结束ECS 104 | // 也可以不调用,world析构时会自动调用 105 | world.shutdown(); 106 | ``` 107 | 108 | `world`是由多个`registry`组成的。`registry`中存储着有关的entity,component,system。只有`resource`是在各个`registry`间是通用的。 109 | 110 | 使用`world.regist_registry(name)`注册一个`registry`。然后可以将系统注册在此`registry`上。 111 | 112 | 一般来说你不会使用超过一个的`registry`。 113 | 114 | ### system 115 | 116 | `system`分为两种: 117 | * `startup system`:在启动时执行一次,主要用于初始化数据 118 | * `update system`:每帧运行一次 119 | 120 | `system`**不是**`std::function`类型,而是普通函数类型。所以若想使用lambda,则不能有任何捕获。 121 | 122 | `system`没有固定的函数声明,但只能包含零个或多个`querier`/`resource`/`commands`。参数顺序没有要求。 123 | 124 | `startup system`使用`regist_startup_system`即可注册: 125 | 126 | ```cpp 127 | world.regist_startup_system(); 128 | ``` 129 | 130 | `update system`使用`regist_update_system`即可注册: 131 | 132 | ```cpp 133 | // 使用lambda,无捕获的lambda会被转换为普通函数,在lambda前面加`+`可以获得对应函数类型 134 | // 含有两个querier和一个resource 135 | world.regist_update_system<+[](querier q1, querier q2, resource res)>(); 136 | // 含有一个commands和一个q1 137 | world.regist_update_system<+[](commands cmd, querier q1)>(); 138 | ``` 139 | 140 | ### querier和resource 141 | 142 | #### querier 143 | 144 | `querier`用于从`world`中查询拥有某种组件的实体,一般作为`system`的参数: 145 | 146 | ```cpp 147 | // q1查询所有含有Name实体的组件,q2查询所有函数Family实体的组件。并且Name组件不可变,Family可变 148 | void update_system(querier q1, querier> q2) { ... } 149 | ``` 150 | 151 | 只有使用`mut`模板包裹组件类型时,才能够得到可变组件。这是为了之后对各个系统进行并行执行打下基础。 152 | 153 | 可以直接遍历`querier`来得到所有实体和对应组件: 154 | 155 | ```cpp 156 | for (auto& [entity, name] : q1) { 157 | ... 158 | } 159 | 160 | // 组件很多时按querier类型中声明的顺序得到 161 | for (auto& [entity, comp1, comp2, comp3] : multi_queirer) { 162 | ... 163 | } 164 | ``` 165 | 166 | 可以使用一些条件来进行查询: 167 | 168 | * `only`:要求实体只能拥有指定的组件,使用此条件时不能有其他参数: 169 | ```cpp 170 | void update_system(querier> q); // 会查询所有只含有Comp1, Comp2的组件 171 | 172 | void update_system(querier>); // 非法!only只能单独存在且只有一个 173 | ``` 174 | * `without`:要求实体不能拥有此组件,语句中只能有一个`without`,并且语句中必须含有其他的无条件查询类型: 175 | ```cpp 176 | void system(querier>); //查询所有含有Comp1但不含有Comp2的组件 177 | void system(querier>); // 查询所有含Comp1,但不含有Comp2和Comp3的组件 178 | 179 | void system(querier>); // 非法!必须含有至少一个无查询条件的类型 180 | ``` 181 | 182 | #### resource 183 | 184 | `resource`则是对资源的获取。资源是一种在ECS中唯一的组件: 185 | 186 | ```cpp 187 | void system(resource res) { 188 | // 通过operator->直接获得。不存在资源会导致程序崩溃! 189 | res->name = "ent"; 190 | } 191 | ``` 192 | 193 | ### commands 194 | 195 | `commands`是用于向`world`中添加/删除实体/组件/资源的类: 196 | 197 | ```cpp 198 | void system(commands cmds) { 199 | // 创建entity 200 | auto entity = cmds.create(); 201 | // 附加组件到实体 202 | cmds.emplace(entity, Name{"ent"}); 203 | 204 | // 从实体上删除组件 205 | cmds.remove(entity); 206 | 207 | // 删除实体及其所有组件 208 | cmds.destroy(entity); 209 | 210 | // 设置资源 211 | cmds.emplace_resource(Res{}); 212 | 213 | // 移除并释放资源 214 | cmds.remove_resource(); 215 | } 216 | ``` 217 | 218 | 有些组件必须一起创建才能正常工作,而`Bundle`可以一次性创建多个组件以防遗忘: 219 | `Bundle`不是一个具体类,而是用户自定义的POD类。类中的所有成员变量会被作为component附加在entity上: 220 | 221 | ```cpp 222 | struct Comp1 {}; 223 | struct Comp2 {}; 224 | 225 | // 定义一个bundle 226 | struct CompBundle { 227 | Comp1 comp1; 228 | Comp2 comp2; 229 | }; 230 | 231 | // in main(): 232 | cmds.emplace_bundle(entity, CompBundle{...}); 233 | ``` 234 | 235 | 创建之后`entity`将会拥有`Comp1`和`Comp2`两个组件。 236 | 237 | ### registry 238 | 239 | `registry`是当前world中的registry类型,保存着和此registry有关的所有entity,component,system的信息。并且可以对其进行任意操作。 240 | **不到万不得已不推荐使用此**类型。 241 | 242 | 要想使用可以在`system`中通过`gecs::registry`得到,多个`registry`底层是同一个(当前`registry`) 243 | 244 | ```cpp 245 | void system(gecs::registry reg); 246 | ``` 247 | 248 | ### state 249 | 250 | `state`用于切换`registry`中的状态。每个`state`都存储着一系列的system。通过切换`state`可以快速在同一个`registry`中切换不同的功能。 251 | 252 | 使用`registry.add_state(numeric)`来创建一个`state`。`state`由整数或者枚举表示(推荐枚举): 253 | 254 | ```cpp 255 | enum class States { 256 | State1, 257 | State2, 258 | }; 259 | 260 | registry.add_state(States::State1); 261 | ``` 262 | 263 | 使用如下方法向`state`中添加一个系统: 264 | 265 | ```cpp 266 | // 添加一个开始系统 267 | registry.regist_enter_system_to_state(GameState::Welcome) 268 | // 添加一个退出系统 269 | .regist_exit_system_to_state(GameState::Welcome) 270 | // 添加一个更新系统 271 | .regist_update_system_to_state(GameState::Welcome); 272 | ``` 273 | 274 | 每次切换`state`的时候,都会调用当前`state`的所有exit system,并调用新`state`的所有enter system。切换`state`使用: 275 | 276 | ```cpp 277 | registry.switch_state_immediatly(state); 278 | registry.switch_state(state); 279 | ``` 280 | 281 | 来切换`state`。`switch_state()`会延迟到这一帧结束时切换。 282 | 283 | `state`的系统和`registry`中的系统执行顺序如下: 284 | 285 | ```cpp 286 | registry::startup 287 | | 288 | state::enter 289 | | 290 | |<-------------- 291 | | | 292 | registry::update | 293 | | game loop 294 | state::update | 295 | | | 296 | |--------------| 297 | | 298 | state::exit 299 | | 300 | registry::shutdown 301 | ``` 302 | 303 | ### 系统的增加和删除 304 | 305 | 使用`registry`可以直接增加/删除系统: 306 | 307 | ```cpp 308 | // 为registry增加/删除系统 309 | registry.regist_startup_system(); 310 | registry.remove_startup_system(); 311 | 312 | // 为state增加/删除系统 313 | registry.regist_enter_system_to_state(State::State1); 314 | registry.remove_enter_system_to_state(State::State1); 315 | ``` 316 | 317 | 注意:**为`registry`增加/删除的系统会立刻应用上,而为`state`增加/删除的系统会在`world.update()`末尾附加上。** 318 | 319 | 这意味着在某个startup系统中,可以为`registry`的startup系统增加新系统,增加的系统在之后会被执行。而若在`state`的某个enter system中新增另一个enter system,则毫无意义,因为新增的system会在`state`的所有enter system调用完毕后再附加在`state`上。除非你从另一个`state`切换到这个`state`,这样此`state`会再次调用所有的enter system(包括后附加的)。 320 | 321 | exit system同理。 322 | 323 | 目前暂不支持在system中提供增加/删除registry system的方法,因为这样做会导致system混乱。原则上来说,registry的system只能在ECS启动前完成全部初始化。但是可以在运行时改变`state`的system(通过`registry`)。 324 | 325 | ### signal系统 326 | 327 | signal类似于Qt的信号槽或Godot的signal。用于更好地实现观察者模式。 328 | 329 | 在`system`声明中,可以使用`event_dispatcher`来注册/触发/缓存一个T类型事件: 330 | 331 | ```cpp 332 | void Startup(gecs::commands cmds, gecs::event_dispatcher quit, 333 | gecs::event_dispatcher keyboard); 334 | ``` 335 | 336 | `event_dispatcher`可以链接多个回调函数,以便于在事件触发时自动调用此函数: 337 | 338 | ```cpp 339 | constexpr auto f = +[](const SDL_QuitEvent& event, 340 | gecs::resource> ctx) { 341 | ctx->shouldClose = true; 342 | }; 343 | 344 | // 使用sink()函数获得信号槽,然后增加一个函数 345 | quit.sink().add(); 346 | ``` 347 | 348 | 函数会按照加入的顺序被调用。 349 | 350 | 函数的声明和`system`一样,只是第一个参数必须是事件类型`T`相关的`const T&`。 351 | 352 | 删除事件回调函数也是通过信号槽删除,这里不再演示。 353 | 354 | 想要缓存新事件,可以使用`enqueue()`函数: 355 | 356 | ```cpp 357 | void EventDispatcher(gecs::resource> ctx, 358 | gecs::event_dispatcher quit, 359 | gecs::event_dispatcher keyboard) { 360 | while (SDL_PollEvent(&ctx->event)) { 361 | if (ctx->event.type == SDL_QUIT) { 362 | // 放入一个SDL_QuitEvent 363 | quit.enqueue(ctx->event.quit); 364 | } 365 | if (ctx->event.type == SDL_KEYDOWN || ctx->event.type == SDL_KEYUP) { 366 | // 放入一个SDL_KeyboardEvent 367 | keyboard.enqueue(ctx->event.key); 368 | } 369 | } 370 | } 371 | ``` 372 | 373 | 缓存的事件会被放在缓存列表里,可以被一次性全部触发: 374 | 375 | ```cpp 376 | quit.trigger_cached(); 377 | 378 | // 如果有必要,触发完不要忘了删除所有缓存事件 379 | quit.clear_cache(); 380 | 381 | // 或者,也可以调用update()自动做上面两个事情 382 | quit.update(); 383 | ``` 384 | 385 | 如果不触发,在`world.update()`的最后(所有`update system`调用后)会自动触发所有事件并删除。 386 | 387 | 如果想要立刻触发,使用: 388 | 389 | ```cpp 390 | quit.trigger(YourQuitEvent); 391 | ``` 392 | 393 | 来触发。 394 | 395 | ### Demo 396 | 397 | 为了测试ECS的稳定性,编写了一个Demo。默认是不编译的,需要SDL2库。请在根目录下运行。 398 | 399 | ![demo](./snapshot/snapshot.png) -------------------------------------------------------------------------------- /Todo.md: -------------------------------------------------------------------------------- 1 | ## 未完成部分 2 | 3 | * 多System并行执行 4 | * 增加从一个Entity克隆另一个Entity的功能 5 | -------------------------------------------------------------------------------- /demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(src DEMO_SRC) 2 | 3 | include(cmake/utility.cmake) 4 | include(cmake/copydll.cmake) 5 | include(cmake/FindSDL2.cmake) 6 | 7 | add_executable(demo ${DEMO_SRC}) 8 | target_link_libraries(demo PRIVATE gecs SDL2) 9 | target_include_directories(demo PRIVATE ./include) 10 | target_compile_features(demo PRIVATE cxx_std_17) 11 | CopyDLL(demo) -------------------------------------------------------------------------------- /demo/cmake/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # download SDL for github action 2 | message("find package: SDL2 ...") 3 | find_package(SDL2 QUIET) 4 | 5 | # check SDL 6 | if (SDL2_FOUND) 7 | if (NOT EXISTS ${SDL2_INCLUDE_DIRS}/SDL.h) 8 | set(SDL2_FOUND OFF) 9 | else() 10 | add_library(SDL2 INTERFACE IMPORTED GLOBAL) 11 | target_link_libraries(SDL2 INTERFACE SDL2::SDL2 SDL2::SDL2main) 12 | message("SDL2 information:") 13 | message("\tinclude dir: ${SDL2_INCLUDE_DIRS}") 14 | message("\tSDL libs: ${SDL2_LIBRARIES}") 15 | endif() 16 | endif() 17 | 18 | if (NOT SDL2_FOUND) 19 | message("cmake find SDL2 failed! use pkg-config...") 20 | 21 | find_package(PkgConfig QUIET) 22 | if (PKG_CONFIG_FOUND) 23 | message("pkg-config found OK") 24 | pkg_check_modules(SDL2 sdl2 QUIET IMPORTED_TARGET) 25 | if (SDL2_FOUND) 26 | add_library(SDL2 ALIAS PkgConfig::SDL2) 27 | else() 28 | message("pkg-config can't find SDL2!") 29 | endif() 30 | else() 31 | message("found pkg-config failed!") 32 | endif() 33 | endif() 34 | 35 | # config SDL2 for windows 36 | if (WIN32 AND NOT SDL2_FOUND) 37 | set(SDL2_ROOT "" CACHE PATH "SDL2 root directory") 38 | 39 | if (NOT SDL2_ROOT) 40 | message(FATAL_ERROR "found SDL2 failed! please set SDL2_ROOT to SDL2 lib root path") 41 | endif() 42 | 43 | # 1 - GNU-like 44 | # 2 - MSVC 45 | set(compiler_type 1) 46 | 47 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" ) 48 | set(compiler_type 2) 49 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") 50 | set(compiler_type 2) 51 | endif() 52 | 53 | # 1 - x86 54 | # 2 - x64 55 | set(arch_type 1) 56 | 57 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 58 | set(arch_type 2) 59 | endif() 60 | 61 | # some var to store SDL compile info 62 | set(sdl_include_dir "") 63 | set(sdlmain_lib "") 64 | set(sdl_lib "") 65 | set(sdl_dll "") 66 | set(sdl_otherlibs "") 67 | 68 | if (${compiler_type} EQUAL 1) # for GNU like compiler 69 | message("your compiler is GNU-like") 70 | 71 | set(sdl_dir ${SDL2_ROOT}/i686-w64-mingw32) 72 | if (${arch_type} EQUAL 2) 73 | set(sdl_dir ${SDL2_ROOT}/x86_64-w64-mingw32) 74 | endif() 75 | 76 | set(sdl_include_dir ${sdl_dir}/include/SDL2) 77 | set(sdl_static_lib_dir ${sdl_dir}/lib) 78 | set(sdlmain_lib ${sdl_static_lib_dir}/libSDL2main.a) 79 | set(sdl_lib ${sdl_static_lib_dir}/libSDL2main.a) 80 | set(sdl_dll ${sdl_dir}/bin/SDL2.dll) 81 | set(sdl_otherlibs "-lmingw32 -lSDL2main -lSDL2") 82 | else() # for MSVC compiler 83 | message("your compiler is based on MSVC") 84 | set(sdl_lib_dir ${SDL2_ROOT}/lib/x86) 85 | 86 | if (${arch_type} EQUAL 2) 87 | set(sdl_lib_dir ${SDL2_ROOT}/lib/x64) 88 | endif() 89 | 90 | set(sdl_lib ${sdl_lib_dir}/SDL2.lib) 91 | set(sdlmain_lib ${sdl_lib_dir}/SDL2main.lib) 92 | set(sdl_include_dir ${SDL2_ROOT}/include) 93 | set(sdl_dll ${sdl_lib_dir}/SDL2.dll) 94 | endif() 95 | 96 | # SDL2 main 97 | add_library(SDL2main STATIC IMPORTED) 98 | set_target_properties(SDL2main 99 | PROPERTIES 100 | IMPORTED_LOCATION ${sdlmain_lib} 101 | IMPORTED_LINK_INTERFACE_LANGUAGES C) 102 | 103 | add_library(SDL2core SHARED IMPORTED) 104 | target_include_directories(SDL2core INTERFACE ${sdl_include_dir}) 105 | set_target_properties(SDL2core 106 | PROPERTIES 107 | IMPORTED_IMPLIB ${sdl_lib} 108 | IMPORTED_LOCATION ${sdl_dll} 109 | IMPORTED_LINK_INTERFACE_LANGUAGES C) 110 | 111 | add_library(SDL2 INTERFACE IMPORTED GLOBAL) 112 | target_link_libraries(SDL2 INTERFACE SDL2main SDL2core) 113 | 114 | message("SDL2 information:") 115 | message("\tinclude dir: ${SDL2_ROOT}/include") 116 | message("\tSDL main library dir: ${sdlmain_lib}") 117 | message("\tSDL lib library dir: ${sdl_lib}") 118 | message("\tdynamic lib: ${sdl_dll}") 119 | 120 | set(SDL2_FOUND ON) 121 | endif() 122 | 123 | if (NOT WIN32 AND NOT SDL2_FOUND) 124 | message(FATAL_ERROR "can't find SDL2!") 125 | endif() -------------------------------------------------------------------------------- /demo/cmake/copydll.cmake: -------------------------------------------------------------------------------- 1 | macro(CopyDLL target_name) 2 | add_custom_command(TARGET ${target_name} 3 | POST_BUILD 4 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 5 | COMMAND_EXPAND_LISTS) 6 | endmacro(CopyDLL) 7 | -------------------------------------------------------------------------------- /demo/cmake/fetch_sdl.cmake: -------------------------------------------------------------------------------- 1 | function(FetchSDL_MSVC) 2 | include(FetchContent) 3 | 4 | message(STATUS "fetching SDL2 ...") 5 | FetchContent_Declare( 6 | SDL2 7 | URL https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-VC.zip 8 | ) 9 | 10 | message(STATUS "fetching SDL2_image ...") 11 | FetchContent_Declare( 12 | SDL2_image 13 | URL https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-VC.zip 14 | ) 15 | 16 | message(STATUS "fetching SDL2_ttf ...") 17 | FetchContent_Declare( 18 | SDL2_ttf 19 | URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-VC.zip 20 | ) 21 | 22 | message(STATUS "fetching SDL2_mixer ...") 23 | FetchContent_Declare( 24 | SDL2_mixer 25 | URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-VC.zip 26 | ) 27 | 28 | FetchContent_MakeAvailable(SDL2 SDL2_image SDL2_ttf SDL2_mixer) 29 | 30 | set(SDL2_ROOT ${sdl2_SOURCE_DIR} CACHE PATH "SDL2 root directory") 31 | set(SDL2_MIXER_ROOT ${sdl2_mixer_SOURCE_DIR} CACHE PATH "SDL2_mixer root directory") 32 | set(SDL2_TTF_ROOT ${sdl2_ttf_SOURCE_DIR} CACHE PATH "SDL2_ttf root directory") 33 | set(SDL2_IMAGE_ROOT ${sdl2_image_SOURCE_DIR} CACHE PATH "SDL2_image root directory") 34 | endfunction(FetchSDL_MSVC) 35 | 36 | function(FetchSDL_MINGW) 37 | include(FetchContent) 38 | 39 | message(STATUS "fetching SDL2 ...") 40 | FetchContent_Declare( 41 | SDL2 42 | URL https://github.com/libsdl-org/SDL/releases/download/release-2.26.2/SDL2-devel-2.26.2-mingw.zip 43 | ) 44 | 45 | message(STATUS "fetching SDL2_image ...") 46 | FetchContent_Declare( 47 | SDL2_image 48 | URL https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-mingw.zip 49 | ) 50 | 51 | message(STATUS "fetching SDL2_ttf ...") 52 | FetchContent_Declare( 53 | SDL2_ttf 54 | URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-mingw.zip 55 | ) 56 | 57 | message(STATUS "fetching SDL2_mixer ...") 58 | FetchContent_Declare( 59 | SDL2_mixer 60 | URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-mingw.zip 61 | ) 62 | 63 | FetchContent_MakeAvailable(SDL2 SDL2_image SDL2_ttf SDL2_mixer) 64 | 65 | set(SDL2_ROOT ${sdl2_SOURCE_DIR} CACHE PATH "SDL2 root directory") 66 | set(SDL2_MIXER_ROOT ${sdl2_mixer_SOURCE_DIR} CACHE PATH "SDL2_mixer root directory") 67 | set(SDL2_TTF_ROOT ${sdl2_ttf_SOURCE_DIR} CACHE PATH "SDL2_ttf root directory") 68 | set(SDL2_IMAGE_ROOT ${sdl2_image_SOURCE_DIR} CACHE PATH "SDL2_image root directory") 69 | endfunction(FetchSDL_MINGW) 70 | 71 | function(FetchSDL) 72 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR 73 | (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 74 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")) # use MSVC 75 | FetchSDL_MSVC() 76 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR 77 | (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 78 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU")) # use MINGW 79 | FetchSDL_MINGW() 80 | endif() 81 | endfunction() -------------------------------------------------------------------------------- /demo/cmake/utility.cmake: -------------------------------------------------------------------------------- 1 | function(IsMSVCBackend RETURN_VALUE) 2 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR 3 | (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 4 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")) 5 | set(${RETURN_VALUE} 1 PARENT_SCOPE) 6 | else() 7 | set(${RETURN_VALUE} 0 PARENT_SCOPE) 8 | endif() 9 | endfunction() 10 | 11 | function(IsMinGWBackend RETURN_VALUE) 12 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR 13 | (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 14 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU" AND WIN32)) # use MINGW 15 | set(${RETURN_VALUE} 1 PARENT_SCOPE) 16 | else() 17 | set(${RETURN_VALUE} 0 PARENT_SCOPE) 18 | endif() 19 | endfunction() 20 | 21 | function(IsGNUBackend RETURN_VALUE) 22 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR 23 | (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 24 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU" AND (UNIX OR APPLE))) # use GNU G++ 25 | set(${RETURN_VALUE} 1 PARENT_SCOPE) 26 | else() 27 | set(${RETURN_VALUE} 0 PARENT_SCOPE) 28 | endif() 29 | endfunction() 30 | 31 | function(IsX64Compiler RETURN_VALUE) 32 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 33 | set(${RETURN_VALUE} 1 PARENT_SCOPE) 34 | else() 35 | set(${RETURN_VALUE} 0 PARENT_SCOPE) 36 | endif() 37 | endfunction() -------------------------------------------------------------------------------- /demo/include/anim.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "texture.hpp" 4 | 5 | class Frame final { 6 | public: 7 | Frame(Image image, int duration) : image_(image), duration_(duration) {} 8 | 9 | const Image& GetImage() const { return image_; } 10 | 11 | int Duration() const { return duration_; } 12 | 13 | private: 14 | Image image_; 15 | int duration_; 16 | }; 17 | 18 | class Animation final { 19 | public: 20 | Animation() = default; 21 | 22 | Animation(const std::vector& frames) : frames_(frames) {} 23 | 24 | template 25 | auto& Add(Args&&... args) { 26 | frames_.emplace_back(std::forward(args)...); 27 | return *this; 28 | } 29 | 30 | auto& SetLoop(int loop) { 31 | loop_ = loop; 32 | return *this; 33 | } 34 | 35 | int GetLoop() const { return loop_; } 36 | 37 | const Image& CurImage() const { return frames_[cur_frame_].GetImage(); } 38 | 39 | void Update() { 40 | if (!IsPlaying()) { 41 | return; 42 | } 43 | 44 | if (IsFinish()) { 45 | if (loop_ != 0) { 46 | if (loop_ > 0) { 47 | loop_--; 48 | } 49 | cur_frame_ = 0; 50 | tick_ = 0; 51 | } else { 52 | playing_ = false; 53 | } 54 | } 55 | 56 | if (!IsFinish()) { 57 | int duration = frames_[cur_frame_].Duration(); 58 | if (tick_ >= duration) { 59 | tick_ -= duration; 60 | cur_frame_++; 61 | } 62 | tick_++; 63 | } 64 | } 65 | 66 | bool IsPlaying() const { return playing_; } 67 | 68 | auto& Play() { 69 | playing_ = true; 70 | return *this; 71 | } 72 | 73 | auto& Stop() { 74 | playing_ = false; 75 | return *this; 76 | } 77 | 78 | bool IsFinish() const { 79 | return cur_frame_ == int(frames_.size()) - 1 && 80 | tick_ == frames_[cur_frame_].Duration(); 81 | } 82 | 83 | int CurFrameIdx() const { return cur_frame_; } 84 | 85 | private: 86 | std::vector frames_; 87 | int cur_frame_ = 0; 88 | int tick_ = 0; 89 | int loop_ = 0; 90 | bool playing_ = false; 91 | }; 92 | 93 | class AnimManager final { 94 | public: 95 | auto& Create(const std::string name, const Animation& anim) { 96 | return anims_.emplace(name, anim).first->second; 97 | } 98 | 99 | auto& Create(const std::string name, const std::vector& frames) { 100 | return anims_.emplace(name, frames).first->second; 101 | } 102 | 103 | const Animation* Find(const std::string name) const { 104 | if (auto it = anims_.find(name); it != anims_.end()) { 105 | return &it->second; 106 | } else { 107 | return nullptr; 108 | } 109 | } 110 | 111 | Animation* Find(const std::string name) { 112 | return const_cast(std::as_const(*this).Find(name)); 113 | } 114 | 115 | private: 116 | std::unordered_map anims_; 117 | }; -------------------------------------------------------------------------------- /demo/include/common_systems.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.hpp" 4 | #include "game_ctx.hpp" 5 | 6 | void UpdateAnim(gecs::querier> anims); 7 | void UpdateRigidbody(gecs::querier, RigidBody> bodies); 8 | void UpdateAnimToImage(gecs::querier> querier); 9 | void RenderSprite(gecs::querier querier, 10 | gecs::resource ctx); 11 | void UpdateTicker(gecs::querier> tickers); -------------------------------------------------------------------------------- /demo/include/consts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pch.hpp" 4 | 5 | constexpr SDL_Color KeyColor = {50, 50, 50, 255}; 6 | constexpr float TankSpeed = 5; 7 | constexpr int TankInitLife = 3; 8 | 9 | static const Vector2 WindowSize = {1024.0f, 720.0f}; 10 | static const float ScaleFactor = 2.0; 11 | static const Vector2 CanvaSize = WindowSize / 2.0; 12 | static const float TankY = CanvaSize.h - 80; 13 | constexpr int FallingStoneDuration = 30; 14 | static const Vector2 FallingStoneVel = {0, 3}; 15 | -------------------------------------------------------------------------------- /demo/include/defs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "anim.hpp" 4 | #include "consts.hpp" 5 | #include "game_ctx.hpp" 6 | #include "physics.hpp" 7 | #include "sprite.hpp" 8 | 9 | enum DepthLayer { 10 | LandDepth = 0, 11 | TankDepth, 12 | FallingStoneDepth, 13 | BulletDepth, 14 | BombDepth, 15 | WelcomeDepth, 16 | UIDepth, 17 | }; 18 | 19 | enum class GameState { 20 | Welcome = 0, 21 | Gaming, 22 | Restart, 23 | }; 24 | 25 | // a tag for bomb animation 26 | struct Bomb {}; 27 | 28 | // a tag for land 29 | struct Land {}; 30 | 31 | // a tag for welcome scene entities 32 | struct WelcomeScene {}; 33 | // a tag for gaming scene entities 34 | struct GamingScene {}; 35 | // a tag for restart scene entities 36 | struct RestartScene {}; 37 | 38 | // a tag for falling stone entity 39 | struct FallingStone { 40 | int hp; 41 | }; 42 | 43 | // a tag for Bullet entity 44 | struct Bullet { 45 | int damage; 46 | }; 47 | 48 | // a tag for player's tank entity 49 | struct Tank { 50 | int hp; 51 | int score; 52 | }; 53 | 54 | inline gecs::entity CreateBullet(const Vector2& init_pos, const Vector2& vel, 55 | gecs::commands cmds, 56 | gecs::resource anim_mgr) { 57 | auto entity = cmds.create(); 58 | cmds.emplace(entity); 59 | cmds.emplace(entity, *(anim_mgr->Find("shell_fly"))); 60 | cmds.emplace(entity, Image{}, init_pos, DepthLayer::BulletDepth); 61 | cmds.emplace(entity, Bullet{1}); 62 | cmds.emplace(entity, RigidBody{ 63 | vel, Rect{10, 0, 12, 20} 64 | }); 65 | return entity; 66 | } 67 | 68 | inline gecs::entity CreateFallingStone(const Vector2& init_pos, 69 | const Vector2& vel, gecs::commands cmds, 70 | gecs::resource anim_mgr) { 71 | auto entity = cmds.create(); 72 | cmds.emplace(entity); 73 | cmds.emplace(entity, *(anim_mgr->Find("falling_stone"))); 74 | cmds.emplace(entity, Image{}, init_pos, 75 | DepthLayer::FallingStoneDepth); 76 | cmds.emplace(entity, FallingStone{1}); 77 | cmds.emplace(entity, RigidBody{ 78 | vel, Rect{4, 35, 24, 26} 79 | }); 80 | return entity; 81 | } 82 | 83 | inline RigidBody& CreateBombAnim(const Vector2& init_pos, const Vector2& vel, 84 | gecs::commands cmds, 85 | gecs::resource anim_mgr) { 86 | auto entity = cmds.create(); 87 | cmds.emplace(entity); 88 | cmds.emplace(entity, *(anim_mgr->Find("bomb"))); 89 | cmds.emplace(entity, Image{}, init_pos, DepthLayer::BombDepth); 90 | cmds.emplace(entity); 91 | return cmds.emplace(entity, RigidBody{vel, Rect{}}); 92 | } -------------------------------------------------------------------------------- /demo/include/game_ctx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.hpp" 4 | #include "ticker.hpp" 5 | #include "window.hpp" 6 | 7 | struct BulletCreator final { 8 | gecs::entity operator()(gecs::commands cmds) const { 9 | return cmds.create(); 10 | } 11 | }; 12 | 13 | struct GameContext final { 14 | std::unique_ptr window; 15 | std::unique_ptr renderer; 16 | bool shouldClose; 17 | Ticker stone_falling_ticker; 18 | SDL_Event event; 19 | bool debugMode = false; 20 | uint32_t score = 0; 21 | 22 | static GameContext Create(const std::string& title, int w, int h, 23 | int falling_elapse); 24 | }; -------------------------------------------------------------------------------- /demo/include/gaming_systems.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gecs/gecs.hpp" 4 | #include "defs.hpp" 5 | #include "game_ctx.hpp" 6 | 7 | #include 8 | 9 | // a class for score 10 | struct Score { int order; }; 11 | 12 | void RemoveBullet(gecs::commands cmds, gecs::querier querier, 13 | gecs::resource> ctx); 14 | 15 | void RenderCollideBox(gecs::resource> ctx, 16 | gecs::querier querier); 17 | 18 | void RemoveFinishedBombAnim(gecs::commands cmds, 19 | gecs::querier querier); 20 | 21 | void FallingStoneGenerate(gecs::commands cmds, 22 | gecs::resource anim_mgr, 23 | gecs::resource> ctx); 24 | 25 | void MoveTank( 26 | gecs::querier, gecs::mut> querier); 27 | 28 | void ShootBullet(gecs::commands cmds, gecs::resource anim_mgr, 29 | gecs::querier> querier, 30 | gecs::resource> ctx); 31 | 32 | void PlayAnimByVel( 33 | gecs::querier, RigidBody> querier); 34 | 35 | void OnEnterGaming(gecs::commands cmds, gecs::resource anim_mgr, gecs::resource ts_mgr, gecs::resource> ctx); 36 | void UpdateScoreImage(gecs::resource ctx, 37 | gecs::querier> scores, 38 | gecs::resource ts_mgr); 39 | 40 | void OnExitGaming(gecs::commands cmds, gecs::querier querier); 41 | 42 | template 43 | void CollideHandle(gecs::commands cmds, 44 | gecs::querier querier1, 45 | gecs::querier querier2, 46 | gecs::resource anim_mgr, 47 | gecs::resource> ctx) { 48 | float min = std::numeric_limits::max(); 49 | std::vector> entities1; 50 | std::vector entities2; 51 | 52 | for (auto&& [ent1, _, body1, sprite1] : querier1) { 53 | gecs::entity entity = gecs::null_entity; 54 | Rect entity1_rect(body1.collide.position + sprite1.position, 55 | body1.collide.size); 56 | 57 | for (auto&& [ent2, _, body2, sprite2] : querier2) { 58 | if (!cmds.alive(ent2)) { 59 | break; 60 | } 61 | Rect entity2_rect(body2.collide.position + sprite2.position, 62 | body2.collide.size); 63 | if (IsRectIntersect(entity2_rect, entity1_rect)) { 64 | float t = 65 | RectCollidTime(entity1_rect, entity2_rect, body1.velocity); 66 | if (t < min) { 67 | entity = ent2; 68 | } 69 | } 70 | } 71 | 72 | if (entity != gecs::null_entity) { 73 | entities1.push_back({ 74 | {entity1_rect.x - 7, entity1_rect.y - 34}, 75 | ent1 76 | }); 77 | entities2.push_back(entity); 78 | } 79 | } 80 | 81 | bool is_game_over = false; 82 | 83 | for (auto&& [pos, ent] : entities1) { 84 | cmds.destroy(ent); 85 | auto& body = CreateBombAnim(pos, FallingStoneVel, cmds, anim_mgr); 86 | if constexpr (std::is_same_v) { 87 | body.velocity.Set(0, 0); 88 | } 89 | if constexpr (std::is_same_v) { 90 | ctx->score ++; 91 | } 92 | if constexpr (std::is_same_v) { 93 | is_game_over = true; 94 | } 95 | } 96 | 97 | if constexpr (!std::is_same_v) { 98 | for (auto ent : entities2) { 99 | cmds.destroy(ent); 100 | } 101 | } 102 | 103 | if (is_game_over) { 104 | cmds.switch_state(GameState::Restart); 105 | } 106 | } -------------------------------------------------------------------------------- /demo/include/pch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SDL.h" 4 | #include "gecs/gecs.hpp" 5 | #include "vmath.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | -------------------------------------------------------------------------------- /demo/include/physics.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vmath.hpp" 4 | 5 | struct RigidBody final { 6 | Vector2 velocity; 7 | Rect collide; 8 | }; 9 | 10 | inline bool IsRectIntersect(const Rect& r1, const Rect& r2) { 11 | return !(r1.x >= r2.x + r2.w || r1.y >= r2.y + r2.h || 12 | r1.x + r1.w <= r2.x || r1.y + r1.h <= r2.y); 13 | } 14 | 15 | //! @brief get collide time by velocity, rect must colliding 16 | inline float RectCollidTime(const Rect& src, const Rect& dst, 17 | const Vector2& src_vel) { 18 | Vector2 old_pos = src.position - src_vel; 19 | Vector2 half_src_size = src.size / 2.0; 20 | float x_percent = 21 | src_vel.x == 0 22 | ? 0 23 | : std::min( 24 | (dst.position.x + dst.size.w + half_src_size.w) / src_vel.x, 25 | (dst.position.x - half_src_size.w) / src_vel.x); 26 | float y_percent = 27 | src_vel.y == 0 28 | ? 0 29 | : std::min( 30 | (dst.position.y + dst.size.h + half_src_size.h) / src_vel.y, 31 | (dst.position.y - half_src_size.h) / src_vel.y); 32 | return std::max(x_percent, y_percent); 33 | } 34 | -------------------------------------------------------------------------------- /demo/include/renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.hpp" 3 | #include "texture.hpp" 4 | #include "window.hpp" 5 | 6 | class Renderer final { 7 | public: 8 | friend class Texture; 9 | 10 | Renderer(const Window&); 11 | 12 | void SetColor(const SDL_Color&); 13 | void Clear(); 14 | void Present(); 15 | void DrawRect(const Rect& rect); 16 | void FillRect(const Rect& rect); 17 | void DrawLine(const Vector2& p1, const Vector2& p2); 18 | void DrawTexture(Texture& texture, const Rect&, int x, int y); 19 | void DrawImage(const Image& image, const Vector2& position, 20 | const std::optional& size); 21 | void DrawImage(const Image& image, const Vector2& position, 22 | const Vector2& scale, float rotation = 0); 23 | void SetScale(const Vector2& scale); 24 | 25 | private: 26 | inline static auto RendererDestroy = [](SDL_Renderer* renderer) { 27 | SDL_DestroyRenderer(renderer); 28 | }; 29 | 30 | std::unique_ptr renderer_; 31 | }; -------------------------------------------------------------------------------- /demo/include/restart_systems.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.hpp" 4 | #include "gecs/gecs.hpp" 5 | 6 | void OnEnterRestart(gecs::commands cmds, gecs::resource ts_mgr, gecs::event_dispatcher keyboard); 7 | void OnExitRestart(gecs::commands cmds, gecs::querier querier, gecs::event_dispatcher keyboard); -------------------------------------------------------------------------------- /demo/include/sprite.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "texture.hpp" 4 | 5 | struct Sprite final { 6 | Image image; 7 | Vector2 position; 8 | int depth; 9 | 10 | Sprite(const Image& image, const Vector2& pos, int depth) 11 | : image(image), position(pos), depth(depth) {} 12 | }; -------------------------------------------------------------------------------- /demo/include/tank.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Tank final { }; -------------------------------------------------------------------------------- /demo/include/texture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.hpp" 3 | 4 | class Renderer; 5 | 6 | class Texture final { 7 | public: 8 | friend class Renderer; 9 | 10 | Texture(Renderer* renderer, const std::string& filename, 11 | const SDL_Color& keycolor, std::optional tilesheetIdx); 12 | 13 | const auto& GetSize() const { return size_; } 14 | 15 | bool IsTilesheet() const { return tilesheetIdx_.has_value(); } 16 | 17 | auto GetTilesheetIdx() const { return tilesheetIdx_; } 18 | 19 | private: 20 | inline static auto DestroyTexture = [](SDL_Texture* texture) { 21 | SDL_DestroyTexture(texture); 22 | }; 23 | std::optional tilesheetIdx_; 24 | 25 | Renderer* renderer_; 26 | Vector2 size_; 27 | std::unique_ptr texture_; 28 | }; 29 | 30 | class Image final { 31 | public: 32 | friend class Renderer; 33 | 34 | Image() : texture_(nullptr) {} 35 | 36 | Image(const Texture& texture); 37 | Image(const Texture& texture, Rect rect); 38 | 39 | auto Size() const { return rect_.size; } 40 | 41 | private: 42 | const Texture* texture_; 43 | Rect rect_; 44 | }; 45 | 46 | class Tilesheet final { 47 | public: 48 | Tilesheet(Texture& texture, int col, int row); 49 | 50 | int GetRow() const { return row_; } 51 | 52 | int GetCol() const { return col_; } 53 | 54 | auto& GetTileSize() const { return tileSize_; } 55 | 56 | Image Get(int col, int row) const { 57 | return Image(texture_, 58 | Rect({col * tileSize_.w, row * tileSize_.h}, tileSize_)); 59 | } 60 | 61 | private: 62 | Texture& texture_; 63 | int row_; 64 | int col_; 65 | Size tileSize_; 66 | }; 67 | 68 | class TextureManager final { 69 | public: 70 | Texture& Load(const std::string& name, const std::string& filename, 71 | const SDL_Color& keycolor); 72 | Tilesheet& LoadTilesheet(const std::string& name, 73 | const std::string& filename, 74 | const SDL_Color& keycolor, int col, int row); 75 | const Texture* Find(const std::string& name) const; 76 | const Tilesheet* FindTilesheet(const std::string& name) const; 77 | 78 | TextureManager(Renderer* renderer) : renderer_(renderer) {} 79 | 80 | TextureManager(const TextureManager&) = delete; 81 | TextureManager& operator=(const TextureManager&) = delete; 82 | 83 | private: 84 | Renderer* renderer_; 85 | std::unordered_map> textures_; 86 | std::vector> tilesheets_; 87 | }; -------------------------------------------------------------------------------- /demo/include/ticker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pch.hpp" 4 | 5 | class Ticker final { 6 | public: 7 | Ticker(int duration) : duration_(duration), tick_(0) {} 8 | 9 | void Update() { 10 | if (tick_ < duration_) { 11 | tick_++; 12 | } 13 | } 14 | 15 | bool isFinish() const { return tick_ >= duration_; } 16 | 17 | void Reset() { tick_ = 0; } 18 | 19 | private: 20 | int duration_; 21 | int tick_; 22 | }; -------------------------------------------------------------------------------- /demo/include/vmath.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct Vector2 final { 8 | union { 9 | float data[2]; 10 | 11 | struct { 12 | float x, y; 13 | }; 14 | 15 | struct { 16 | float w, h; 17 | }; 18 | }; 19 | 20 | Vector2() { Set(0, 0); } 21 | 22 | Vector2(float x, float y) { Set(x, y); } 23 | 24 | explicit Vector2(float value) { Set(value, value); } 25 | 26 | void Set(float x, float y) { 27 | this->x = x; 28 | this->y = y; 29 | } 30 | 31 | bool operator==(const Vector2& o) const { 32 | return this->x == o.x && this->y == o.y; 33 | } 34 | 35 | bool operator!=(const Vector2& o) const { return !(*this == o); } 36 | 37 | Vector2 operator+(const Vector2& o) const { 38 | return Vector2{o.x + x, o.y + y}; 39 | } 40 | 41 | Vector2 operator-(const Vector2& o) const { 42 | return Vector2{x - o.x, y - o.y}; 43 | } 44 | 45 | Vector2 operator*(const Vector2& o) const { 46 | return Vector2{o.x * x, o.y * y}; 47 | } 48 | 49 | Vector2 operator/(const Vector2& o) const { 50 | return Vector2{x / o.x, y / o.y}; 51 | } 52 | 53 | Vector2 operator*(float value) const { 54 | return Vector2{x * value, y * value}; 55 | } 56 | 57 | Vector2 operator/(float value) const { 58 | return Vector2{x / value, y / value}; 59 | } 60 | 61 | Vector2& operator+=(const Vector2& o) { 62 | *this = *this + o; 63 | return *this; 64 | } 65 | 66 | Vector2& operator-=(const Vector2& o) { 67 | *this = *this - o; 68 | return *this; 69 | } 70 | 71 | Vector2& operator*=(const Vector2& o) { 72 | *this = *this * o; 73 | return *this; 74 | } 75 | 76 | Vector2& operator/=(const Vector2& o) { 77 | *this = *this / o; 78 | return *this; 79 | } 80 | 81 | float LengthSqrd() const { return Dot(*this); } 82 | 83 | float Length() const { return std::sqrt(LengthSqrd()); } 84 | 85 | float Dot(const Vector2& o) const { return x * o.x + y * o.y; } 86 | 87 | Vector2 Normalize() const { 88 | float len = Length(); 89 | if (std::abs(len) <= std::numeric_limits::epsilon()) { 90 | return *this; 91 | } 92 | return *this / Length(); 93 | } 94 | }; 95 | 96 | inline Vector2 operator*(float value, const Vector2& v) { 97 | return v * value; 98 | } 99 | 100 | inline Vector2 operator-(const Vector2& v) { 101 | return {-v.x, -v.y}; 102 | } 103 | 104 | using Size = Vector2; 105 | 106 | template 107 | class Matrix final { 108 | public: 109 | Matrix(int w, int h) 110 | : data_(std::unique_ptr(new T[w * h])), w_(w), h_(h) {} 111 | 112 | void Fill(const T& value) { 113 | for (int i = 0; i < w_ * h_; i++) { 114 | GetByIndex(i) = value; 115 | } 116 | } 117 | 118 | T& Get(int x, int y) { return data_.get()[x + y * w_]; } 119 | 120 | const T& Get(int x, int y) const { return data_.get()[x + y * w_]; } 121 | 122 | T& GetByIndex(int idx) { return data_.get()[idx]; } 123 | 124 | int MaxSize() const { return w_ * h_; } 125 | 126 | int Width() const { return w_; } 127 | 128 | int Height() const { return h_; } 129 | 130 | bool IsIn(int x, int y) const { 131 | return x >= 0 && x < w_ && y >= 0 && y < h_; 132 | } 133 | 134 | private: 135 | std::unique_ptr data_; 136 | int w_; 137 | int h_; 138 | }; 139 | 140 | struct Rect final { 141 | union { 142 | SDL_FRect sdlrect; 143 | 144 | struct { 145 | Vector2 position; 146 | Size size; 147 | }; 148 | 149 | struct { 150 | float x, y, w, h; 151 | }; 152 | }; 153 | 154 | Rect() : x(0), y(0), w(0), h(0) {} 155 | 156 | Rect(const Vector2& pos, const Vector2& size) { 157 | this->position = pos; 158 | this->size = size; 159 | } 160 | 161 | Rect(float x, float y, float w, float h) { 162 | this->position.Set(x, y); 163 | this->size.Set(w, h); 164 | } 165 | 166 | bool IsIntersect(const Rect& rect) const { 167 | return !(position.x + size.x <= rect.position.x || 168 | position.x >= rect.position.x + rect.size.x || 169 | position.y + size.y <= rect.position.y || 170 | position.y >= rect.position.y + rect.size.y); 171 | } 172 | 173 | Vector2 Center() const { return position + size / 2.0; } 174 | }; -------------------------------------------------------------------------------- /demo/include/welcome_systems.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.hpp" 4 | #include "game_ctx.hpp" 5 | 6 | void OnEnterWelcome(gecs::commands cmds, gecs::resource img_mgr, gecs::event_dispatcher keyboard); 7 | void OnExitWelcome(gecs::event_dispatcher keyboard); -------------------------------------------------------------------------------- /demo/include/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pch.hpp" 4 | 5 | inline auto WindowDestroy = [](SDL_Window* window) { 6 | SDL_DestroyWindow(window); 7 | }; 8 | 9 | class Window final { 10 | public: 11 | friend class Renderer; 12 | 13 | Window(const std::string& title, int w, int h); 14 | 15 | private: 16 | std::unique_ptr window_; 17 | }; -------------------------------------------------------------------------------- /demo/resources/bomb.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/bomb.bmp -------------------------------------------------------------------------------- /demo/resources/falling_stone.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/falling_stone.bmp -------------------------------------------------------------------------------- /demo/resources/gameover.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/gameover.bmp -------------------------------------------------------------------------------- /demo/resources/how-to-play.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/how-to-play.bmp -------------------------------------------------------------------------------- /demo/resources/how_to_start.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/how_to_start.bmp -------------------------------------------------------------------------------- /demo/resources/land.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/land.bmp -------------------------------------------------------------------------------- /demo/resources/numbers.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/numbers.bmp -------------------------------------------------------------------------------- /demo/resources/shell.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/shell.bmp -------------------------------------------------------------------------------- /demo/resources/tank.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/demo/resources/tank.bmp -------------------------------------------------------------------------------- /demo/src/common_systems.cpp: -------------------------------------------------------------------------------- 1 | #include "common_systems.hpp" 2 | 3 | void UpdateAnim(gecs::querier> anims) { 4 | for (auto&& [_, anim] : anims) { 5 | anim.Update(); 6 | } 7 | } 8 | 9 | void UpdateRigidbody(gecs::querier, RigidBody> bodies) { 10 | for (auto&& [entity, sprite, body] : bodies) { 11 | sprite.position += body.velocity; 12 | } 13 | } 14 | 15 | void UpdateAnimToImage(gecs::querier> querier) { 16 | for (auto&& [_, anim, sprite] : querier) { 17 | if (anim.IsPlaying()) { 18 | sprite.image = anim.CurImage(); 19 | } 20 | } 21 | } 22 | 23 | void RenderSprite(gecs::querier querier, 24 | gecs::resource ctx) { 25 | for (auto&& [_, sprite] : 26 | querier.sort_by([](const Sprite& s1, const Sprite& s2) { 27 | return s1.depth < s2.depth; 28 | })) { 29 | ctx->renderer->DrawImage(sprite.image, sprite.position, std::nullopt); 30 | } 31 | } 32 | 33 | void UpdateTicker(gecs::querier> tickers) { 34 | for (auto&& [_, ticker] : tickers) { 35 | ticker.Update(); 36 | } 37 | } -------------------------------------------------------------------------------- /demo/src/game_ctx.cpp: -------------------------------------------------------------------------------- 1 | #include "game_ctx.hpp" 2 | 3 | GameContext GameContext::Create(const std::string& title, int w, int h, 4 | int falling_elapse) { 5 | auto window = std::make_unique(title, w, h); 6 | auto renderer = std::make_unique(*window); 7 | 8 | return GameContext{std::move(window), std::move(renderer), false, 9 | Ticker(falling_elapse)}; 10 | } -------------------------------------------------------------------------------- /demo/src/gaming_systems.cpp: -------------------------------------------------------------------------------- 1 | #include "gaming_systems.hpp" 2 | #include 3 | 4 | void OnEnterGaming(gecs::commands cmds, gecs::resource anim_mgr, 5 | gecs::resource ts_mgr, 6 | gecs::resource> ctx) { 7 | ctx->score = 0; 8 | 9 | auto tank_entity = cmds.create(); 10 | cmds.emplace(tank_entity); 11 | 12 | auto& tank_anim = *anim_mgr->Find("tank_move"); 13 | cmds.emplace(tank_entity, tank_anim); 14 | cmds.emplace(tank_entity, 15 | RigidBody{ 16 | Vector2{0, 0}, 17 | Rect{2, 10, 28, 22} 18 | }); 19 | cmds.emplace(tank_entity, ts_mgr->FindTilesheet("tank")->Get(0, 0), 20 | Vector2{CanvaSize.w / 2.0f, TankY}, 21 | DepthLayer::TankDepth); 22 | cmds.emplace(tank_entity, Tank{TankInitLife, 0}); 23 | cmds.emplace(tank_entity, 10); 24 | 25 | auto land_entity = cmds.create(); 26 | cmds.emplace(land_entity); 27 | cmds.emplace( 28 | land_entity, 29 | Image(*ts_mgr->Find("land")), 30 | Vector2{0.0f, CanvaSize.h - 100.0f}, DepthLayer::LandDepth); 31 | cmds.emplace(land_entity); 32 | cmds.emplace( 33 | land_entity, RigidBody{ 34 | Vector2{0, 0}, 35 | Rect{0, 50, CanvaSize.w, CanvaSize.h} 36 | }); 37 | 38 | auto score_entity1 = cmds.create(); 39 | auto score_entity2 = cmds.create(); 40 | cmds.emplace(score_entity1); 41 | cmds.emplace(score_entity2); 42 | uint32_t units = ctx->score % 10; 43 | uint32_t tens = ctx->score / 10; 44 | if (tens != 0) { 45 | cmds.emplace(score_entity1, 46 | ts_mgr->FindTilesheet("numbers")->Get(tens, 0), 47 | Vector2{10, 10}, DepthLayer::UIDepth); 48 | } else { 49 | cmds.emplace(score_entity1, 50 | Image{}, 51 | Vector2{10, 10}, DepthLayer::UIDepth); 52 | } 53 | cmds.emplace(score_entity1, Score{0}); 54 | cmds.emplace(score_entity2, 55 | ts_mgr->FindTilesheet("numbers")->Get(units, 0), 56 | Vector2{28, 10}, DepthLayer::UIDepth); 57 | cmds.emplace(score_entity2, Score{1}); 58 | } 59 | 60 | void RemoveBullet(gecs::commands cmds, gecs::querier querier, 61 | gecs::resource> ctx) { 62 | for (auto&& [entity, bullet, sprite] : querier) { 63 | if (sprite.position.y + 32 < 0 || sprite.position.y >= CanvaSize.h) { 64 | cmds.destroy(entity); 65 | } 66 | } 67 | } 68 | 69 | void RenderCollideBox(gecs::resource> ctx, 70 | gecs::querier querier) { 71 | if (ctx->debugMode) { 72 | for (auto&& [_, sprite, body] : querier) { 73 | ctx->renderer->SetColor({0, 255, 0, 255}); 74 | ctx->renderer->DrawRect(Rect{ 75 | sprite.position + body.collide.position, body.collide.size}); 76 | } 77 | } 78 | } 79 | 80 | void RemoveFinishedBombAnim(gecs::commands cmds, 81 | gecs::querier querier) { 82 | for (auto&& [ent, _, anim] : querier) { 83 | if (anim.IsFinish()) { 84 | cmds.destroy(ent); 85 | } 86 | } 87 | } 88 | 89 | 90 | void FallingStoneGenerate(gecs::commands cmds, 91 | gecs::resource anim_mgr, 92 | gecs::resource> ctx) { 93 | ctx->stone_falling_ticker.Update(); 94 | if (ctx->stone_falling_ticker.isFinish()) { 95 | auto dist = std::uniform_int_distribution(50, CanvaSize.w - 50); 96 | auto d = std::random_device(); 97 | float x = dist(d); 98 | 99 | CreateFallingStone(Vector2(x, -100), FallingStoneVel, cmds, anim_mgr); 100 | ctx->stone_falling_ticker.Reset(); 101 | } 102 | } 103 | 104 | void MoveTank( 105 | gecs::querier, gecs::mut> querier) { 106 | for (auto&& [_, tank, sprite, rigidbody] : querier) { 107 | if (SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_A]) { 108 | rigidbody.velocity = Vector2{-TankSpeed, 0}; 109 | } else if (SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_D]) { 110 | rigidbody.velocity = Vector2{TankSpeed, 0}; 111 | } else { 112 | rigidbody.velocity.Set(0, 0); 113 | } 114 | sprite.position.x = 115 | std::clamp(sprite.position.x, 0, CanvaSize.w - 32); 116 | } 117 | } 118 | 119 | void ShootBullet(gecs::commands cmds, gecs::resource anim_mgr, 120 | gecs::querier> querier, 121 | gecs::resource> ctx) { 122 | for (auto&& [_, tank, sprite, ticker] : querier) { 123 | if (ticker.isFinish() && 124 | SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_J]) { 125 | auto entity = CreateBullet(sprite.position - Vector2(0, 16), 126 | Vector2{0, -5}, cmds, anim_mgr); 127 | 128 | ticker.Reset(); 129 | } 130 | } 131 | } 132 | 133 | void PlayAnimByVel( 134 | gecs::querier, RigidBody> querier) { 135 | for (auto&& [_, tank, anim, body] : querier) { 136 | if (body.velocity.x == 0) { 137 | anim.Stop(); 138 | } else { 139 | anim.Play(); 140 | } 141 | } 142 | } 143 | 144 | void UpdateScoreImage(gecs::resource ctx, 145 | gecs::querier> scores, 146 | gecs::resource ts_mgr) { 147 | scores.sort_by([](const Score& a, const Score& b){ 148 | return a.order < b.order; 149 | }); 150 | 151 | uint32_t units = ctx->score % 10; 152 | uint32_t tens = ctx->score / 10; 153 | auto it = scores.begin(); 154 | { 155 | auto&& [_, score, sprite] = *it; 156 | if (tens != 0) { 157 | sprite.image = ts_mgr->FindTilesheet("numbers")->Get(tens, 0); 158 | } 159 | } 160 | it ++; 161 | { 162 | auto&& [_, score, sprite] = *it; 163 | sprite.image = ts_mgr->FindTilesheet("numbers")->Get(units, 0); 164 | } 165 | } 166 | 167 | void OnExitGaming(gecs::commands cmds, gecs::querier querier) { 168 | for (auto&& [ent, _] : querier) { 169 | cmds.destroy(ent); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /demo/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "common_systems.hpp" 2 | #include "defs.hpp" 3 | #include "game_ctx.hpp" 4 | #include "gaming_systems.hpp" 5 | #include "restart_systems.hpp" 6 | #include "welcome_systems.hpp" 7 | 8 | 9 | // startup system to init SDL and resources 10 | void Startup(gecs::commands cmds, gecs::event_dispatcher quit, 11 | gecs::event_dispatcher keyboard) { 12 | SDL_Init(SDL_INIT_EVERYTHING); 13 | 14 | auto& ctx = cmds.emplace_resource( 15 | GameContext::Create("demo", 1024, 720, FallingStoneDuration)); 16 | ctx.renderer->SetScale(Vector2{ScaleFactor}); 17 | 18 | auto& anim_mgr = cmds.emplace_resource(); 19 | 20 | constexpr auto f = +[](const SDL_QuitEvent& event, 21 | gecs::resource> ctx) { 22 | ctx->shouldClose = true; 23 | }; 24 | quit.sink().add(); 25 | 26 | constexpr auto f2 = +[](const SDL_KeyboardEvent& event, 27 | gecs::resource> ctx) { 28 | if (event.type == SDL_KEYDOWN && 29 | event.keysym.scancode == SDL_SCANCODE_G) { 30 | ctx->debugMode = !ctx->debugMode; 31 | } 32 | }; 33 | keyboard.sink().add(); 34 | 35 | auto& ts_mgr = cmds.emplace_resource(ctx.renderer.get()); 36 | ts_mgr.Load("how_to_start", "demo/resources/how_to_start.bmp", KeyColor); 37 | ts_mgr.Load("how_to_play", "demo/resources/how-to-play.bmp", KeyColor); 38 | ts_mgr.Load("land", "demo/resources/land.bmp", KeyColor); 39 | ts_mgr.Load("gameover", "demo/resources/gameover.bmp", KeyColor); 40 | auto& tank_ts = 41 | ts_mgr.LoadTilesheet("tank", "demo/resources/tank.bmp", KeyColor, 2, 1); 42 | auto& shell_ts = ts_mgr.LoadTilesheet("shell", "demo/resources/shell.bmp", 43 | KeyColor, 2, 1); 44 | auto& falling_stone_ts = ts_mgr.LoadTilesheet( 45 | "falling_stone", "demo/resources/falling_stone.bmp", KeyColor, 2, 1); 46 | auto& bomb_ts = 47 | ts_mgr.LoadTilesheet("bomb", "demo/resources/bomb.bmp", KeyColor, 6, 1); 48 | ts_mgr.LoadTilesheet("numbers", "demo/resources/numbers.bmp", KeyColor, 10, 49 | 1); 50 | anim_mgr 51 | .Create("shell_fly", 52 | {Frame(shell_ts.Get(0, 0), 3), Frame(shell_ts.Get(1, 0), 3)}) 53 | .SetLoop(-1) 54 | .Play(); 55 | anim_mgr 56 | .Create("falling_stone", {Frame(falling_stone_ts.Get(0, 0), 3), 57 | Frame(falling_stone_ts.Get(1, 0), 3)}) 58 | .SetLoop(-1) 59 | .Play(); 60 | anim_mgr 61 | .Create("bomb", 62 | {Frame(bomb_ts.Get(0, 0), 2), Frame(bomb_ts.Get(1, 0), 2), 63 | Frame(bomb_ts.Get(2, 0), 2), Frame(bomb_ts.Get(3, 0), 2), 64 | Frame(bomb_ts.Get(4, 0), 2)}) 65 | .SetLoop(-1) 66 | .Play(); 67 | anim_mgr 68 | .Create("tank_move", 69 | {Frame(tank_ts.Get(0, 0), 3), Frame(tank_ts.Get(1, 0), 3)}) 70 | .SetLoop(-1) 71 | .Play(); 72 | } 73 | 74 | // shutdown system to clear all resources and close SDL 75 | void Shutdown(gecs::commands cmds) { 76 | cmds.remove_resource(); 77 | SDL_Quit(); 78 | } 79 | 80 | // dispatch `SDL_QuitEvent` to quit game 81 | void EventDispatcher(gecs::resource> ctx, 82 | gecs::event_dispatcher quit, 83 | gecs::event_dispatcher keyboard) { 84 | while (SDL_PollEvent(&ctx->event)) { 85 | if (ctx->event.type == SDL_QUIT) { 86 | quit.enqueue(ctx->event.quit); 87 | } 88 | if (ctx->event.type == SDL_KEYDOWN || ctx->event.type == SDL_KEYUP) { 89 | keyboard.enqueue(ctx->event.key); 90 | } 91 | } 92 | } 93 | 94 | // clear screen and render present 95 | void RenderUpdate(gecs::resource ctx) { 96 | ctx->renderer->Present(); 97 | ctx->renderer->SetColor({50, 50, 50, 255}); 98 | ctx->renderer->Clear(); 99 | SDL_Delay(30); 100 | } 101 | 102 | int main(int argc, char** argv) { 103 | gecs::world gaming_world; 104 | 105 | // regist all systems 106 | // startup system 107 | gaming_world.regist_registry("gaming") 108 | .regist_startup_system() 109 | // shutdown system 110 | .regist_shutdown_system() 111 | // update systems 112 | // all state system 113 | .regist_update_system() 114 | .regist_update_system() 115 | .regist_update_system() 116 | .regist_update_system() 117 | .regist_update_system() 118 | .regist_update_system() 119 | .regist_update_system() 120 | // welcome state 121 | .add_state(GameState::Welcome) 122 | .regist_enter_system_to_state(GameState::Welcome) 123 | .regist_exit_system_to_state(GameState::Welcome) 124 | // gaming state 125 | .add_state(GameState::Gaming) 126 | .regist_enter_system_to_state(GameState::Gaming) 127 | .regist_update_system_to_state( 128 | GameState::Gaming) 129 | .regist_update_system_to_state(GameState::Gaming) 130 | .regist_update_system_to_state(GameState::Gaming) 131 | .regist_update_system_to_state(GameState::Gaming) 132 | .regist_update_system_to_state(GameState::Gaming) 133 | .regist_update_system_to_state(GameState::Gaming) 134 | .regist_update_system_to_state( 135 | GameState::Gaming) 136 | .regist_update_system_to_state>( 137 | GameState::Gaming) 138 | .regist_update_system_to_state>( 139 | GameState::Gaming) 140 | .regist_update_system_to_state>( 141 | GameState::Gaming) 142 | .regist_update_system_to_state(GameState::Gaming) 143 | .regist_exit_system_to_state(GameState::Gaming) 144 | // restart state 145 | .add_state(GameState::Restart) 146 | .regist_enter_system_to_state(GameState::Restart) 147 | .regist_exit_system_to_state(GameState::Restart) 148 | // start state 149 | .start_with_state(GameState::Welcome); 150 | 151 | // startup ecs 152 | gaming_world.start_with("gaming"); 153 | gaming_world.startup(); 154 | 155 | // use world to access GameContext directly 156 | gecs::resource res = 157 | gaming_world.cur_registry()->res(); 158 | while (!res->shouldClose) { 159 | // update ecs 160 | gaming_world.update(); 161 | } 162 | 163 | // shutdown ecs 164 | gaming_world.shutdown(); 165 | return 0; 166 | } 167 | -------------------------------------------------------------------------------- /demo/src/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.hpp" 2 | 3 | Renderer::Renderer(const Window& window) 4 | : renderer_(SDL_CreateRenderer(window.window_.get(), -1, 0), 5 | RendererDestroy) {} 6 | 7 | void Renderer::SetColor(const SDL_Color& c) { 8 | SDL_SetRenderDrawColor(renderer_.get(), c.r, c.g, c.b, c.a); 9 | } 10 | 11 | void Renderer::Clear() { 12 | SDL_RenderClear(renderer_.get()); 13 | } 14 | 15 | void Renderer::Present() { 16 | SDL_RenderPresent(renderer_.get()); 17 | } 18 | 19 | void Renderer::DrawRect(const Rect& rect) { 20 | SDL_Rect r = {static_cast(rect.x), static_cast(rect.y), 21 | static_cast(rect.w), static_cast(rect.h)}; 22 | SDL_RenderDrawRect(renderer_.get(), &r); 23 | } 24 | 25 | void Renderer::FillRect(const Rect& rect) { 26 | SDL_Rect r = {static_cast(rect.x), static_cast(rect.y), 27 | static_cast(rect.w), static_cast(rect.h)}; 28 | SDL_RenderFillRect(renderer_.get(), &r); 29 | } 30 | 31 | void Renderer::DrawLine(const Vector2& p1, const Vector2& p2) { 32 | SDL_RenderDrawLineF(renderer_.get(), p1.x, p1.y, p2.x, p2.y); 33 | } 34 | 35 | void Renderer::DrawTexture(Texture& texture, const Rect& rect, int x, int y) { 36 | SDL_Rect src = {static_cast(rect.x), static_cast(rect.y), 37 | static_cast(rect.w), static_cast(rect.h)}; 38 | SDL_Rect dst = {x, y, static_cast(rect.w), static_cast(rect.h)}; 39 | SDL_RenderCopy(renderer_.get(), texture.texture_.get(), &src, &dst); 40 | } 41 | 42 | void Renderer::DrawImage(const Image& image, const Vector2& position, 43 | const std::optional& size) { 44 | if (!image.texture_) { 45 | return; 46 | } 47 | SDL_FRect dst = {position.x, position.y, size ? size->w : image.rect_.w, 48 | size ? size->h : image.rect_.h}; 49 | SDL_Rect src = { 50 | static_cast(image.rect_.x), static_cast(image.rect_.y), 51 | static_cast(image.rect_.w), static_cast(image.rect_.h)}; 52 | SDL_RenderCopyF(renderer_.get(), image.texture_->texture_.get(), &src, 53 | &dst); 54 | } 55 | 56 | void Renderer::DrawImage(const Image& image, const Vector2& position, 57 | const Vector2& scale, float rotation) { 58 | SDL_FRect dst = {position.x, position.y, std::abs(scale.x) * image.rect_.w, 59 | std::abs(scale.y) * image.rect_.h}; 60 | SDL_Rect src = { 61 | static_cast(image.rect_.x), static_cast(image.rect_.y), 62 | static_cast(image.rect_.w), static_cast(image.rect_.h)}; 63 | uint32_t flip = SDL_RendererFlip::SDL_FLIP_NONE; 64 | flip |= scale.x < 0 ? SDL_RendererFlip::SDL_FLIP_HORIZONTAL : 0; 65 | flip |= scale.y < 0 ? SDL_RendererFlip::SDL_FLIP_VERTICAL : 0; 66 | SDL_RenderCopyExF(renderer_.get(), image.texture_->texture_.get(), &src, 67 | &dst, rotation, nullptr, 68 | static_cast(flip)); 69 | } 70 | 71 | void Renderer::SetScale(const Vector2& scale) { 72 | SDL_RenderSetScale(renderer_.get(), scale.x, scale.y); 73 | } -------------------------------------------------------------------------------- /demo/src/restart_systems.cpp: -------------------------------------------------------------------------------- 1 | #include "restart_systems.hpp" 2 | 3 | constexpr auto change_state = +[](const SDL_KeyboardEvent& key, gecs::commands cmds) { 4 | if (key.type == SDL_KEYDOWN && key.keysym.scancode == SDL_SCANCODE_R) { 5 | cmds.switch_state(GameState::Gaming); 6 | } 7 | }; 8 | 9 | void OnEnterRestart(gecs::commands cmds, gecs::resource ts_mgr, gecs::event_dispatcher keyboard) { 10 | auto entity = cmds.create(); 11 | cmds.emplace(entity, *(ts_mgr->Find("gameover")), Vector2{170, 170}, DepthLayer::UIDepth); 12 | cmds.emplace(entity); 13 | 14 | keyboard.sink().add(); 15 | } 16 | 17 | void OnExitRestart(gecs::commands cmds, gecs::querier querier, gecs::event_dispatcher keyboard) { 18 | for (auto&& [ent, _] : querier) { 19 | cmds.destroy(ent); 20 | } 21 | keyboard.sink().remove(); 22 | } 23 | -------------------------------------------------------------------------------- /demo/src/texture.cpp: -------------------------------------------------------------------------------- 1 | #include "texture.hpp" 2 | #include "renderer.hpp" 3 | 4 | Texture::Texture(Renderer* renderer, const std::string& filename, 5 | const SDL_Color& keycolor, std::optional tilesheetIdx) 6 | : renderer_(renderer), 7 | texture_(nullptr, DestroyTexture), 8 | tilesheetIdx_(tilesheetIdx) { 9 | SDL_Surface* surface = SDL_LoadBMP(filename.c_str()); 10 | if (!surface) { 11 | SDL_Log("[Texture]: load image %s failed: %s", filename.c_str(), 12 | SDL_GetError()); 13 | texture_ = nullptr; 14 | } else { 15 | size_.Set(static_cast(surface->w), 16 | static_cast(surface->h)); 17 | SDL_SetColorKey( 18 | surface, SDL_TRUE, 19 | SDL_MapRGB(surface->format, keycolor.r, keycolor.g, keycolor.b)); 20 | texture_.reset( 21 | SDL_CreateTextureFromSurface(renderer->renderer_.get(), surface)); 22 | if (!texture_) { 23 | SDL_Log("[Texture]: create texture from %s failed: %s", 24 | filename.c_str(), SDL_GetError()); 25 | } 26 | } 27 | } 28 | 29 | Texture& TextureManager::Load(const std::string& name, 30 | const std::string& filename, 31 | const SDL_Color& keycolor) { 32 | return *textures_ 33 | .emplace(name, std::make_unique( 34 | renderer_, filename, keycolor, std::nullopt)) 35 | .first->second; 36 | } 37 | 38 | Tilesheet& TextureManager::LoadTilesheet(const std::string& name, 39 | const std::string& filename, 40 | const SDL_Color& keycolor, int col, 41 | int row) { 42 | auto& texture = textures_ 43 | .emplace(name, std::make_unique( 44 | renderer_, filename, keycolor, 45 | tilesheets_.size())) 46 | .first->second; 47 | tilesheets_.emplace_back(std::make_unique(*texture, col, row)); 48 | return *tilesheets_.back(); 49 | } 50 | 51 | const Tilesheet* TextureManager::FindTilesheet(const std::string& name) const { 52 | if (auto it = textures_.find(name); it != textures_.end()) { 53 | auto& texture = it->second; 54 | if (!texture->IsTilesheet()) { 55 | SDL_Log("[TextureManager][WARN]: texture %s is a pure texture", 56 | name.c_str()); 57 | } 58 | return tilesheets_[texture->GetTilesheetIdx().value()].get(); 59 | } 60 | return nullptr; 61 | } 62 | 63 | const Texture* TextureManager::Find(const std::string& name) const { 64 | if (auto it = textures_.find(name); it != textures_.end()) { 65 | if (it->second->IsTilesheet()) { 66 | SDL_Log("[TextureManager][WARN]: texture %s is a tilesheet", 67 | name.c_str()); 68 | } 69 | return it->second.get(); 70 | } 71 | return nullptr; 72 | } 73 | 74 | Image::Image(const Texture& texture) 75 | : texture_(&texture), rect_({0, 0}, texture.GetSize()) {} 76 | 77 | Image::Image(const Texture& texture, Rect rect) : texture_(&texture), rect_(rect) {} 78 | 79 | Tilesheet::Tilesheet(Texture& texture, int col, int row) 80 | : texture_(texture), 81 | row_(row), 82 | col_(col), 83 | tileSize_(texture.GetSize().w / col, texture.GetSize().h / row) {} -------------------------------------------------------------------------------- /demo/src/welcome_systems.cpp: -------------------------------------------------------------------------------- 1 | #include "welcome_systems.hpp" 2 | 3 | constexpr auto to_gaming_state = +[](const SDL_KeyboardEvent& keyboard, gecs::commands cmds, gecs::querier querier) { 4 | if (keyboard.type == SDL_KEYDOWN) { 5 | for (auto&& [ent, _] : querier) { 6 | cmds.destroy(ent); 7 | } 8 | 9 | cmds.switch_state(GameState::Gaming); 10 | } 11 | }; 12 | 13 | void OnEnterWelcome(gecs::commands cmds, gecs::resource img_mgr, gecs::event_dispatcher keyboard) { 14 | auto how_to_play_ent = cmds.create(); 15 | cmds.emplace(how_to_play_ent); 16 | cmds.emplace(how_to_play_ent, Image(*(img_mgr->Find("how_to_play"))), Vector2(140, 100), WelcomeDepth); 17 | 18 | auto how_to_start_ent = cmds.create(); 19 | cmds.emplace(how_to_start_ent); 20 | cmds.emplace(how_to_start_ent, Image(*(img_mgr->Find("how_to_start"))), Vector2(40, 270), WelcomeDepth); 21 | 22 | keyboard.sink().add(); 23 | } 24 | 25 | void OnExitWelcome(gecs::event_dispatcher keyboard) { 26 | keyboard.sink().clear(); 27 | } -------------------------------------------------------------------------------- /demo/src/window.cpp: -------------------------------------------------------------------------------- 1 | #include "window.hpp" 2 | 3 | Window::Window(const std::string &title, int w, int h) 4 | : window_(SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, 5 | SDL_WINDOWPOS_CENTERED, w, h, SDL_WINDOW_SHOWN), 6 | WindowDestroy) {} -------------------------------------------------------------------------------- /snapshot/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualGMQ/gecs/b720a46c3736ea513630abbeb33587085eb43fb0/snapshot/snapshot.png -------------------------------------------------------------------------------- /src/gecs/config/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gecs/entity/entity.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef ENTITY_NUMERIC_TYPE 10 | #define ENTITY_NUMERIC_TYPE uint32_t 11 | #endif 12 | 13 | #ifndef SPARSE_PAGE_SIZE 14 | #define SPARSE_PAGE_SIZE 4096 15 | #endif 16 | 17 | namespace gecs { 18 | 19 | struct fake_type_info {}; 20 | 21 | template 22 | fake_type_info* get_fake_type_info() { 23 | static fake_type_info typeinfo; 24 | return &typeinfo; 25 | } 26 | 27 | } 28 | 29 | #ifndef GECS_TYPE_INFO_TYPE 30 | #define GECS_TYPE_INFO_TYPE const fake_type_info* 31 | #endif 32 | 33 | #ifndef GECS_GET_TYPE_INFO 34 | #define GECS_GET_TYPE_INFO(type) get_fake_type_info() 35 | #endif 36 | 37 | namespace gecs::internal { 38 | 39 | struct fake_reference_any final { 40 | template 41 | fake_reference_any(const T&) {} 42 | 43 | fake_reference_any() = default; 44 | }; 45 | 46 | } 47 | 48 | #ifndef GECS_ANY 49 | #define GECS_ANY ::gecs::internal::fake_reference_any 50 | #endif 51 | 52 | #ifndef GECS_MAKE_ANY_REF 53 | #define GECS_MAKE_ANY_REF(x) ::gecs::internal::fake_reference_any(x) 54 | #endif 55 | 56 | namespace gecs { 57 | 58 | namespace config { 59 | 60 | using type_info = GECS_TYPE_INFO_TYPE; 61 | 62 | enum class Entity : ENTITY_NUMERIC_TYPE {}; 63 | constexpr uint32_t PageSize = SPARSE_PAGE_SIZE; 64 | 65 | inline std::ostream& operator<<(std::ostream& o, Entity entity) { 66 | o << "Ent(" << static_cast(entity) << ")"; 67 | return o; 68 | } 69 | 70 | inline bool operator==(Entity e1, Entity e2) { 71 | return internal::entity_to_integral(e1) == internal::entity_to_integral(e2); 72 | } 73 | 74 | inline bool operator!=(Entity e1, Entity e2) { 75 | return !(e1 == e2); 76 | } 77 | 78 | inline bool operator==(Entity e1, uint64_t num) { 79 | return internal::entity_to_integral(e1) == num; 80 | } 81 | 82 | inline bool operator!=(Entity e1, uint64_t num) { 83 | return !(e1 == num); 84 | } 85 | 86 | inline bool operator==(uint64_t num, Entity e) { 87 | return e == num; 88 | } 89 | 90 | inline bool operator!=(uint64_t num, Entity e) { 91 | return !(e == num); 92 | } 93 | 94 | using id_type = size_t; 95 | 96 | 97 | 98 | } // namespace config 99 | 100 | } // namespace gecs 101 | -------------------------------------------------------------------------------- /src/gecs/container/compress_pair.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace gecs { 7 | 8 | namespace internal { 9 | 10 | template 11 | constexpr bool is_ebco_t = std::is_empty_v && !std::is_final_v; 12 | 13 | template 14 | struct CompressPairElement final { 15 | using reference = T&; 16 | using const_reference = const reference; 17 | 18 | template 19 | CompressPairElement(Args&&... args) noexcept( 20 | std::is_nothrow_constructible_v) 21 | : value(std::forward(args)...) {} 22 | 23 | template , int> = 0> 24 | CompressPairElement() : value{} {} 25 | 26 | const_reference Get() const { return value; } 27 | 28 | reference Get() { 29 | return const_cast(std::as_const(*this).get()); 30 | } 31 | 32 | T value; 33 | }; 34 | 35 | template 36 | struct CompressPairElement>> final { 37 | using reference = T&; 38 | using const_reference = const reference; 39 | 40 | template 41 | CompressPairElement(T&& t) noexcept {} 42 | 43 | reference Get() noexcept { return *this; } 44 | 45 | const_reference auto& Get() const noexcept { return *this; } 46 | }; 47 | 48 | } // namespace internal 49 | 50 | template 51 | class CompressPair final : private internal::CompressPairElement, 52 | private internal::CompressPairElement { 53 | public: 54 | using first_base = internal::CompressPairElement; 55 | using second_base = internal::CompressPairElement; 56 | 57 | using first_element_reference = First&; 58 | using first_element_const_reference = const first_element_reference; 59 | using second_element_reference = Second&; 60 | using second_element_const_reference = const second_element_reference; 61 | 62 | CompressPair(const First& first, const Second& second) 63 | : first_base(first), second_base(second) {} 64 | 65 | CompressPair(First&& first, Second&& second) 66 | : first_base(std::move(first)), second_base(std::move(second)) {} 67 | 68 | first_element_reference First() noexcept { 69 | return static_cast(first_base::get()); 70 | } 71 | 72 | second_element_reference Second() noexcept { 73 | return static_cast(second_base::get()); 74 | } 75 | 76 | first_element_const_reference First() const noexcept { 77 | return const_cast( 78 | std::as_const(*this).first()); 79 | } 80 | 81 | second_element_const_reference Second() const noexcept { 82 | return const_cast( 83 | std::as_const(*this).second()); 84 | } 85 | }; 86 | 87 | } // namespace gecs -------------------------------------------------------------------------------- /src/gecs/container/dense_map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "compress_pair.hpp" 4 | #include "core/utility.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace gecs { 11 | 12 | namespace internal { 13 | 14 | template 15 | class dense_map_iterator final { 16 | public: 17 | using container_type = Container; 18 | using pointer = Container::const_pointer; 19 | using const_pointer = Container::const_pointer; 20 | using reference = typename container_type::const_reference; 21 | using const_reference = typename container_type::const_reference; 22 | using difference_type = typename container_type::difference_type; 23 | using value_type = typename container_type::value_type; 24 | using iterator_category = std::random_access_iterator_tag; 25 | 26 | constexpr dense_map_iterator(const Container& container, 27 | difference_type offset) noexcept 28 | : container_(&container), offset_(offset) {} 29 | 30 | constexpr dense_map_iterator(const Container& container, 31 | container_type::const_iterator it) noexcept 32 | : container_(&container), 33 | offset_(std::distance(container.begin(), it)) {} 34 | 35 | constexpr reference operator*() noexcept { 36 | return *(container_->begin() + index()); 37 | } 38 | 39 | constexpr reference operator*() const noexcept { 40 | return std::as_const(*this).operator*(); 41 | } 42 | 43 | constexpr const pointer operator->() const noexcept { return &operator*(); } 44 | 45 | constexpr pointer operator->() noexcept { 46 | return const_cast(std::as_const(*this).operator->()); 47 | } 48 | 49 | constexpr const_pointer data() const noexcept { 50 | return container_ ? container_->data() : nullptr; 51 | } 52 | 53 | constexpr pointer data() noexcept { 54 | return const_cast(std::as_const(*this).data()); 55 | } 56 | 57 | constexpr auto& operator+=(const difference_type step) noexcept { 58 | return offset_ -= step, *this; 59 | } 60 | 61 | constexpr auto& operator-=(const difference_type step) noexcept { 62 | return operator+=(-step); 63 | } 64 | 65 | constexpr auto operator+(const difference_type step) const noexcept { 66 | auto copy = *this; 67 | return copy += step; 68 | } 69 | 70 | constexpr auto operator-(const difference_type step) const noexcept { 71 | return operator+(-step); 72 | } 73 | 74 | constexpr auto operator++(int) noexcept { 75 | auto copy = *this; 76 | return ++(*this), copy; 77 | } 78 | 79 | constexpr auto& operator++() noexcept { return --offset_, *this; } 80 | 81 | constexpr auto operator--(int) noexcept { 82 | sparse_set_iterator copy = *this; 83 | return --(*this), copy; 84 | } 85 | 86 | constexpr auto& operator--() noexcept { return ++offset_, *this; } 87 | 88 | constexpr bool operator==(const dense_map_iterator& o) const noexcept { 89 | return o.container_ == container_ && o.offset_ == offset_; 90 | } 91 | 92 | constexpr bool operator!=(const dense_map_iterator& o) const noexcept { 93 | return !(*this == o); 94 | } 95 | 96 | constexpr reference operator[](const difference_type index) const noexcept { 97 | return data()[index() - index]; 98 | } 99 | 100 | private: 101 | const Container* container_; 102 | Container::difference_type offset_; 103 | 104 | inline size_t index() const { return offset_ - 1; } 105 | }; 106 | 107 | } // namespace internal 108 | 109 | template 110 | class dense_map final { 111 | public: 112 | using element_type = std::pair using sparse_container_type = 113 | std::vector; 114 | using packed_container_type = std::vector; 115 | using iterator = 116 | internal::dense_map_iterator>; 117 | using const_iterator = iterator; 118 | 119 | dense_map(size_t init_hash_size = 8) { 120 | sparse_.resize(init_hash_size, null_sparse_element); 121 | } 122 | 123 | template 124 | void insert_or_donothing(T&& key, U&& value) { 125 | size_t index = this->index(key); 126 | if (!is_null(sparse_[index])) { 127 | return; 128 | } else { 129 | packed_.First().emplace_back(std::make_pair( 130 | std::forward(key), std::forward(value))); 131 | sparse_[index] = packed_.size() - 1; 132 | } 133 | } 134 | 135 | template 136 | void insert_or_replace(T&& key, U&& value) { 137 | size_t index = this->index(key); 138 | packed_.First().emplace_back(std::make_pair( 139 | std::forward(key), std::forward(value))); 140 | sparse_[index] = packed_.size() - 1; 141 | } 142 | 143 | iterator begin() { return iterator{&packed_, packed_.size()}; } 144 | 145 | cosnt_iterator begin() const { return std::as_const(*this).begin(); } 146 | 147 | iterator end() { return iterator{&packed_, 0}; } 148 | 149 | const_iterator end() const { return std::as_const(*this).end(); } 150 | 151 | const_iterator cend() const { return end(); } 152 | 153 | const_iterator cbegin() const { return begin(); } 154 | 155 | const_iterator find(const Key& key) const { 156 | size_t index = this->index(key); 157 | if (is_null(sparse_[index])) { 158 | return cend(); 159 | } else { 160 | return const_iterator{&packed_, index + 1}; 161 | } 162 | } 163 | 164 | iterator find(const Key& key) { 165 | return const_cast(std::as_const(*this).find(key)); 166 | } 167 | 168 | void remove(const Key& key) { 169 | size_t index = sparse_[this->index(key)]; 170 | index = null_sparse_element; 171 | } 172 | 173 | auto rbegin() const { return std::make_reverse_iterator(end()); } 174 | 175 | auto rend() const { return std::make_reverse_iterator(begin()); } 176 | 177 | auto crbegin() const { return rbegin(); } 178 | 179 | auto crend() const { return rend(); } 180 | 181 | auto size() const { return packed_.size(); } 182 | 183 | auto capacity() const { return packed_.capacity(); } 184 | 185 | auto shrink_to_fit() { 186 | packed_.shrink_to_fit(); 187 | sparse_.shrink_to_fit(); 188 | } 189 | 190 | void clear() { 191 | packed_.clear(); 192 | sparse_.clear(); 193 | } 194 | 195 | bool empty() const { return packed_.empty(); } 196 | 197 | private: 198 | CompressPair packed_; 199 | sparse_container_type sparse_; 200 | float threshold_ = 0.85; 201 | 202 | static constexpr auto null_sparse_element = 203 | std::numeric_limits::max(); 204 | 205 | inline bool is_null(size_t elem) { return elem == null_sparse_element; } 206 | 207 | inline size_t index(const Key& key) { 208 | return quick_mod(Hasher{}(key), sparse_.size()); 209 | } 210 | }; 211 | 212 | } // namespace gecs -------------------------------------------------------------------------------- /src/gecs/core/ident.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gecs/config/config.hpp" 4 | #include 5 | 6 | namespace gecs { 7 | 8 | namespace internal { 9 | struct component_tag; 10 | struct resource_tag; 11 | struct event_dispatcher_tag; 12 | } // namespace internal 13 | 14 | template 15 | struct basic_id_generator final { 16 | public: 17 | using value_type = config::id_type; 18 | 19 | template 20 | static value_type gen() noexcept { 21 | value_type value = get_new_id(); 22 | return value; 23 | } 24 | 25 | static value_type typeinfo_id(config::type_info typeinfo) noexcept { 26 | if (auto it = typeinfo_map_.find(typeinfo); it != typeinfo_map_.end()) { 27 | return it->second; 28 | } else { 29 | return typeinfo_map_.emplace(typeinfo, curr_++).second; 30 | } 31 | } 32 | 33 | private: 34 | inline static value_type curr_ = {}; 35 | inline static std::unordered_map 36 | typeinfo_map_; 37 | 38 | template 39 | static value_type get_new_id() noexcept { 40 | config::type_info type = GECS_GET_TYPE_INFO(U); 41 | if (auto it = typeinfo_map_.find(type); it != typeinfo_map_.end()) { 42 | return it->second; 43 | } else { 44 | auto id = curr_++; 45 | typeinfo_map_.emplace(type, id); 46 | return id; 47 | } 48 | } 49 | }; 50 | 51 | using component_id_generator = basic_id_generator; 52 | using dispatcher_id_generator = 53 | basic_id_generator; 54 | 55 | } // namespace gecs -------------------------------------------------------------------------------- /src/gecs/core/singlton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gecs { 6 | 7 | template 8 | class singlton; 9 | 10 | /** 11 | * @brief abstract singlton class for convienient implement Singlton Pattern 12 | * 13 | * this singlton will init singlton automatically when call instance(); 14 | * 15 | * @tparam T 16 | * @tparam Loader a function object to point out how to create the singlton 17 | */ 18 | template 19 | class singlton { 20 | public: 21 | virtual ~singlton() = default; 22 | 23 | static T& instance() { 24 | static T instance = Loader{}(); 25 | return instance; 26 | } 27 | }; 28 | 29 | /** 30 | * @brief abstract singlton class for convienient implement Singlton Pattern 31 | * 32 | * this singlton must call init() to create instance before use instance(), and 33 | * release singlton call destroy() after use 34 | * 35 | * @tparam T 36 | * @tparam Loader a function object to point out how to create the singlton 37 | */ 38 | 39 | template 40 | class singlton { 41 | public: 42 | virtual ~singlton() = default; 43 | 44 | static T& instance() { 45 | GECS_ASSERT(instance_, "instance not init, you must call T::init() before get this " 46 | "singlton"); 47 | return *instance; 48 | } 49 | 50 | template 51 | static void init(Args&&... args) { 52 | instance_ = std::make_unique(Loader{}(std::forward(args)...)); 53 | } 54 | 55 | static void destroy() { instance_.reset(); } 56 | 57 | private: 58 | inline static std::unique_ptr instance_; 59 | }; 60 | 61 | } // namespace gecs -------------------------------------------------------------------------------- /src/gecs/core/type_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace gecs { 8 | 9 | /** 10 | * @brief a type container to store types 11 | * 12 | * @tparam Ts types 13 | */ 14 | template 15 | struct type_list { 16 | using self_type = type_list; 17 | static constexpr size_t size = sizeof...(Ts); 18 | }; 19 | 20 | namespace detail { 21 | 22 | template 23 | struct list_element; 24 | 25 | template