├── .gitignore ├── .gitmodules ├── .vscode ├── extensions.json ├── tasks.json ├── launch.json └── godot.natvis ├── src ├── CMakeLists.txt ├── GDExtensionTemplate.h ├── GDExtensionTemplate.cpp ├── Version.h.in ├── RegisterExtension.cpp ├── Example.h └── Example.cpp ├── templates ├── template.debug.gdextension.in ├── template.release.gdextension.in └── CMakeLists.txt ├── cmake ├── ccache.cmake ├── ClangFormat.cmake ├── GitVersionInfo.cmake ├── GetGitRevisionDescription.cmake.in ├── CompilerWarnings.cmake └── GetGitRevisionDescription.cmake ├── LICENSE.md ├── CHANGELOG.md ├── support_files └── icons │ └── Example.svg ├── CMakePresets.json ├── .github └── workflows │ └── main.yml ├── .clang-format ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | .vscode/settings.json 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/godot-cpp"] 2 | path = extern/godot-cpp 3 | url = https://github.com/godotengine/godot-cpp.git 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools-extension-pack", 4 | "ms-vscode.cmake-tools" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | target_sources( ${PROJECT_NAME} 4 | PRIVATE 5 | Example.h 6 | Example.cpp 7 | GDExtensionTemplate.h 8 | GDExtensionTemplate.cpp 9 | RegisterExtension.cpp 10 | ) 11 | 12 | target_include_directories( ${PROJECT_NAME} 13 | PRIVATE 14 | "src" 15 | ) 16 | -------------------------------------------------------------------------------- /templates/template.debug.gdextension.in: -------------------------------------------------------------------------------- 1 | [configuration] 2 | entry_symbol = "GDExtensionInit" 3 | compatibility_minimum = 4.1 4 | 5 | [icons] 6 | Example = "icons/Example.svg" 7 | 8 | [libraries] 9 | linux.debug.x86_64 = "lib/Linux-x86_64/lib${PROJECT_NAME}-d.so" 10 | macos.debug = "lib/Darwin-Universal/lib${PROJECT_NAME}-d.dylib" 11 | windows.debug.x86_64 = "lib/Windows-AMD64/${LIB_PREFIX}${PROJECT_NAME}-d.dll" 12 | -------------------------------------------------------------------------------- /templates/template.release.gdextension.in: -------------------------------------------------------------------------------- 1 | [configuration] 2 | entry_symbol = "GDExtensionInit" 3 | compatibility_minimum = 4.1 4 | 5 | [icons] 6 | Example = "icons/Example.svg" 7 | 8 | [libraries] 9 | linux.release.x86_64 = "lib/Linux-x86_64/lib${PROJECT_NAME}.so" 10 | macos.release = "lib/Darwin-universal/lib${PROJECT_NAME}.dylib" 11 | windows.release.x86_64 = "lib/Windows-AMD64/${LIB_PREFIX}${PROJECT_NAME}.dll" 12 | -------------------------------------------------------------------------------- /src/GDExtensionTemplate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // SPDX-License-Identifier: Unlicense 3 | 4 | #include "godot_cpp/classes/object.hpp" 5 | 6 | namespace godot 7 | { 8 | class ClassDB; 9 | }; 10 | 11 | class GDExtensionTemplate : public godot::Object 12 | { 13 | GDCLASS( GDExtensionTemplate, godot::Object ) 14 | 15 | public: 16 | static godot::String version(); 17 | static godot::String godotCPPVersion(); 18 | 19 | private: 20 | static void _bind_methods(); 21 | }; 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cmake", 6 | "label": "CMake: build", 7 | "command": "build", 8 | "targets": [ 9 | "all" 10 | ], 11 | "preset": "${command:cmake.activeBuildPresetName}", 12 | "group": "build", 13 | "problemMatcher": [], 14 | "detail": "CMake template build task" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /cmake/ccache.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | # See: https://crascit.com/2016/04/09/using-ccache-with-cmake/ 4 | find_program( CCACHE_PROGRAM ccache ) 5 | 6 | if ( CCACHE_PROGRAM ) 7 | # get version information 8 | execute_process( 9 | COMMAND "${CCACHE_PROGRAM}" --version 10 | OUTPUT_VARIABLE CCACHE_VERSION 11 | ) 12 | 13 | string( REGEX MATCH "[^\r\n]*" CCACHE_VERSION ${CCACHE_VERSION} ) 14 | 15 | message( STATUS "Using ccache: ${CCACHE_PROGRAM} (${CCACHE_VERSION})" ) 16 | 17 | # Turn on ccache for all targets 18 | set( CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}" ) 19 | set( CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}" ) 20 | 21 | unset( CCACHE_VERSION ) 22 | endif() 23 | -------------------------------------------------------------------------------- /cmake/ClangFormat.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | find_program( CLANG_FORMAT_PROGRAM NAMES clang-format ) 4 | 5 | if ( CLANG_FORMAT_PROGRAM ) 6 | # get version information 7 | execute_process( 8 | COMMAND "${CLANG_FORMAT_PROGRAM}" --version 9 | OUTPUT_VARIABLE CLANG_FORMAT_VERSION 10 | OUTPUT_STRIP_TRAILING_WHITESPACE 11 | ) 12 | 13 | message( STATUS "Using clang-format: ${CLANG_FORMAT_PROGRAM} (${CLANG_FORMAT_VERSION})" ) 14 | 15 | get_target_property( CLANG_FORMAT_SOURCES ${PROJECT_NAME} SOURCES ) 16 | 17 | # Remove some files from the list 18 | list( FILTER CLANG_FORMAT_SOURCES EXCLUDE REGEX ".*/extern/.*" ) 19 | list( FILTER CLANG_FORMAT_SOURCES EXCLUDE REGEX ".*/gen/.*" ) 20 | list( FILTER CLANG_FORMAT_SOURCES EXCLUDE REGEX ".*/*.gdextension.in" ) 21 | list( FILTER CLANG_FORMAT_SOURCES EXCLUDE REGEX ".*/Version.h.in" ) 22 | 23 | add_custom_target( clang-format 24 | COMMAND "${CLANG_FORMAT_PROGRAM}" --style=file -i ${CLANG_FORMAT_SOURCES} 25 | COMMENT "Running clang-format..." 26 | COMMAND_EXPAND_LISTS 27 | VERBATIM 28 | ) 29 | 30 | unset( CLANG_FORMAT_VERSION ) 31 | unset( CLANG_FORMAT_SOURCES ) 32 | endif() 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Windows Launch", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${command:cmake.launchTargetPath}", 12 | "args": [], 13 | "cwd": "${command:cmake.buildDirectory}/bin", 14 | "preLaunchTask": "CMake: build", 15 | "internalConsoleOptions": "openOnSessionStart", 16 | "console": "internalConsole", 17 | "environment": [ 18 | { 19 | "name": "Path", 20 | "value": "${env:Path};" 21 | } 22 | ], 23 | "visualizerFile": "${workspaceFolder}/.vscode/godot.natvis" 24 | }, 25 | { 26 | "name": "macOS/Linux Launch", 27 | "type": "lldb", 28 | "request": "launch", 29 | "program": "${command:cmake.launchTargetPath}", 30 | "args": [], 31 | "cwd": "${command:cmake.buildDirectory}/bin", 32 | "visualizerFile": "${workspaceFolder}/.vscode/godot.natvis" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /cmake/GitVersionInfo.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | find_program( GIT_PROGRAM git ) 4 | 5 | if ( GIT_PROGRAM ) 6 | # get version information 7 | execute_process( 8 | COMMAND "${GIT_PROGRAM}" --version 9 | OUTPUT_VARIABLE GIT_VERSION 10 | OUTPUT_STRIP_TRAILING_WHITESPACE 11 | ) 12 | 13 | message( STATUS "Using git: ${GIT_PROGRAM} (${GIT_VERSION})" ) 14 | 15 | include( GetGitRevisionDescription ) 16 | 17 | get_git_head_revision( GIT_REFSPEC GIT_SHA1 ) 18 | git_describe( GIT_SHORT ) 19 | 20 | string( TOUPPER ${PROJECT_NAME} UPPER_PROJECT_NAME ) 21 | 22 | set( VERSION_INPUT_FILE "src/Version.h.in" ) 23 | set( VERSION_OUTPUT_FILE "${CMAKE_BINARY_DIR}/gen/Version.h" ) 24 | 25 | configure_file( "${VERSION_INPUT_FILE}" "${VERSION_OUTPUT_FILE}" ) 26 | 27 | target_sources( ${PROJECT_NAME} 28 | PRIVATE 29 | "${VERSION_INPUT_FILE}" 30 | "${VERSION_OUTPUT_FILE}" 31 | ) 32 | 33 | get_filename_component( VERSION_OUTPUT_FILE_DIR ${VERSION_OUTPUT_FILE} DIRECTORY ) 34 | 35 | target_include_directories( ${PROJECT_NAME} 36 | PRIVATE 37 | ${VERSION_OUTPUT_FILE_DIR} 38 | ) 39 | 40 | message( STATUS "${PROJECT_NAME} version: ${GIT_SHORT}" ) 41 | 42 | unset( VERSION_INPUT_FILE ) 43 | unset( VERSION_OUTPUT_FILE ) 44 | unset( VERSION_OUTPUT_FILE_DIR ) 45 | unset( GIT_VERSION ) 46 | endif() 47 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Internal file for GetGitRevisionDescription.cmake 3 | # 4 | # Requires CMake 2.6 or newer (uses the 'function' command) 5 | # 6 | # Original Author: 7 | # 2009-2010 Ryan Pavlik 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright 2009-2012, Iowa State University 12 | # Copyright 2011-2015, Contributors 13 | # Distributed under the Boost Software License, Version 1.0. 14 | # (See accompanying file LICENSE_1_0.txt or copy at 15 | # http://www.boost.org/LICENSE_1_0.txt) 16 | # SPDX-License-Identifier: BSL-1.0 17 | 18 | set(HEAD_HASH) 19 | 20 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 21 | 22 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 23 | if(HEAD_CONTENTS MATCHES "ref") 24 | # named branch 25 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 26 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 27 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 28 | else() 29 | configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) 30 | file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) 31 | if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") 32 | set(HEAD_HASH "${CMAKE_MATCH_1}") 33 | endif() 34 | endif() 35 | else() 36 | # detached HEAD 37 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 38 | endif() 39 | 40 | if(NOT HEAD_HASH) 41 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 42 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 43 | endif() 44 | -------------------------------------------------------------------------------- /templates/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | add_custom_target( templates 4 | SOURCES 5 | template.debug.gdextension.in 6 | template.release.gdextension.in 7 | ) 8 | 9 | add_dependencies( ${PROJECT_NAME} templates ) 10 | 11 | # We shouldn't be relying on CMAKE_BUILD_TYPE (see https://github.com/asmaloney/GDExtensionTemplate/issues/25) 12 | # But until we fix it here and in godot-cpp, ensure it's one we expect. 13 | set ( ALLOWED_BUILDS "Debug;Release" ) 14 | if ( NOT "${CMAKE_BUILD_TYPE}" IN_LIST ALLOWED_BUILDS ) 15 | message( FATAL_ERROR "CMAKE_BUILD_TYPE must be set to Debug or Release" ) 16 | endif() 17 | 18 | # Get our gdextension input file name based on build type 19 | string( TOLOWER ${CMAKE_BUILD_TYPE} BUILD_TYPE ) 20 | set( GD_EXTENSION_FILE_INPUT template.${BUILD_TYPE}.gdextension.in ) 21 | 22 | # Workaround to add the "lib" prefix to the library in our template file if using MSYS2. 23 | if ( MINGW ) 24 | set( LIB_PREFIX "lib") 25 | endif() 26 | 27 | # Generate our project's .gdextension file from the template 28 | set( GD_EXTENSION_FILE ${PROJECT_NAME}.gdextension ) 29 | configure_file( ${GD_EXTENSION_FILE_INPUT} ${PROJECT_BINARY_DIR}/${PROJECT_NAME}/${GD_EXTENSION_FILE} ) 30 | 31 | # Install the gdextension file from the build directory 32 | install( 33 | FILES ${BUILD_OUTPUT_DIR}/${GD_EXTENSION_FILE} 34 | DESTINATION ${INSTALL_DIR} 35 | ) 36 | 37 | unset( ALLOWED_BUILDS ) 38 | unset( BUILD_TYPE ) 39 | unset( GD_EXTENSION_FILE ) 40 | unset( GD_EXTENSION_FILE_INPUT ) 41 | unset( LIB_PREFIX ) 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## 4.2 - 2023-12-16 6 | 7 | ### New 8 | 9 | - Add CMake preset and VSCode support ([#61](https://github.com/asmaloney/GDExtensionTemplate/pull/61)) 10 | 11 | ### Changed 12 | 13 | - Updated example code ([#63](https://github.com/asmaloney/GDExtensionTemplate/pull/63)) 14 | - Updated bindings for Godot 4.1 ([#62](https://github.com/asmaloney/GDExtensionTemplate/pull/62)) 15 | 16 | ## 4.1 - 2023-07-06 17 | 18 | ### Changed 19 | 20 | - Updated bindings, updated initialization code, and added compatibility in .gdextensions for Godot 4.1 ([#56](https://github.com/asmaloney/GDExtensionTemplate/pull/56)) 21 | 22 | ## 4.0.3 - 2023-05-22 23 | 24 | ### Changed 25 | 26 | - Updated bindings for Godot 4.0.3 ([#53](https://github.com/asmaloney/GDExtensionTemplate/pull/53)) 27 | 28 | ## 4.0.2 - 2023-04-04 29 | 30 | ### Changed 31 | 32 | - Updated bindings for Godot 4.0.2 ([#52](https://github.com/asmaloney/GDExtensionTemplate/pull/52)) 33 | 34 | ## 4.0.1 - 2023-03-25 35 | 36 | ### Changed 37 | 38 | - Updated bindings for Godot 4.0.1 ([#50](https://github.com/asmaloney/GDExtensionTemplate/pull/50)) 39 | 40 | ### Fixed 41 | 42 | - {cmake} Don't override the debug posfix if CMAKE_DEBUG_POSTFIX defined. ([#51](https://github.com/asmaloney/GDExtensionTemplate/pull/51)) 43 | 44 | ## 4.0 - 2023-03-01 45 | 46 | ### Changed 47 | 48 | - Updated bindings for Godot 4.0 ([#49](https://github.com/asmaloney/GDExtensionTemplate/pull/49)) 49 | -------------------------------------------------------------------------------- /src/GDExtensionTemplate.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | 3 | #include "godot_cpp/core/class_db.hpp" 4 | #include "godot_cpp/core/version.hpp" 5 | 6 | #include "GDExtensionTemplate.h" 7 | #include "Version.h" 8 | 9 | /// @file 10 | /// GDExtensionTemplate example implementation. 11 | 12 | /*! 13 | @brief Get the version string for this extension. 14 | 15 | @details 16 | The version string is generated by cmake using src/Version.h.in. 17 | 18 | It uses the form " -<# commits since last tag>-". 19 | If there are no commits since the last tag, only the tag is shown. 20 | 21 | @return The version string (e.g. "Foo v1.2.3-gdedbd01"). 22 | */ 23 | godot::String GDExtensionTemplate::version() 24 | { 25 | return VersionInfo::VERSION_STR.data(); 26 | } 27 | 28 | /*! 29 | @brief Get the godot-cpp version string for this extension. 30 | 31 | @details 32 | The version string is generated using godot-cpp's core/version.hpp. 33 | 34 | @return The version string (e.g. "godot-cpp v4.2.0-stable"). 35 | */ 36 | godot::String GDExtensionTemplate::godotCPPVersion() 37 | { 38 | return "godot-cpp v" + godot::uitos( GODOT_VERSION_MAJOR ) + "." + 39 | godot::uitos( GODOT_VERSION_MINOR ) + "." + godot::uitos( GODOT_VERSION_PATCH ) + "-" + 40 | GODOT_VERSION_STATUS; 41 | } 42 | 43 | /// Bind our methods so GDScript can access them. 44 | void GDExtensionTemplate::_bind_methods() 45 | { 46 | godot::ClassDB::bind_static_method( "GDExtensionTemplate", godot::D_METHOD( "version" ), 47 | &GDExtensionTemplate::version ); 48 | godot::ClassDB::bind_static_method( "GDExtensionTemplate", 49 | godot::D_METHOD( "godot_cpp_version" ), 50 | &GDExtensionTemplate::godotCPPVersion ); 51 | } 52 | -------------------------------------------------------------------------------- /support_files/icons/Example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // This file is generated by cmake. Changes will be overwritten. 3 | // clang-format off 4 | 5 | #include 6 | 7 | // Creates a version number for use in macro comparisons. 8 | // 9 | // Example: 10 | // 11 | // // Check if the version is less than 2.1.0 12 | // #if ${UPPER_PROJECT_NAME}_VERSION < ${UPPER_PROJECT_NAME}_VERSION_CHECK(2, 1, 0) 13 | // // do stuff 14 | // #endif 15 | // 16 | // Returns an integer which may be used in comparisons 17 | #define ${UPPER_PROJECT_NAME}_VERSION_CHECK( major, minor, patch ) ( ((major)<<16) | ((minor)<<8) | (patch) ) 18 | 19 | #define ${UPPER_PROJECT_NAME}_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} 20 | #define ${UPPER_PROJECT_NAME}_VERSION_MINOR ${PROJECT_VERSION_MINOR} 21 | #define ${UPPER_PROJECT_NAME}_VERSION_PATCH ${PROJECT_VERSION_PATCH} 22 | 23 | // The version number of this extension. Used for #if comparisons. 24 | // This is generated using the version set in the CMake project macro. 25 | #define ${UPPER_PROJECT_NAME}_VERSION ${UPPER_PROJECT_NAME}_VERSION_CHECK( ${PROJECT_VERSION_MAJOR}, ${PROJECT_VERSION_MINOR}, ${PROJECT_VERSION_PATCH} ) 26 | 27 | namespace VersionInfo { 28 | // Project name and version as a string. 29 | // This is generated using the project name from the cmake project macro and the current git commit information. 30 | // 31 | // It uses the form " -<# commits since last tag>-". 32 | // If there are no commits since the last tag, only the tag is shown. 33 | constexpr std::string_view VERSION_STR = "${PROJECT_NAME} ${GIT_SHORT}"; 34 | 35 | // The version information as a string. 36 | // This is generated using the current git commit information. 37 | // 38 | // It uses the form "-<# commits since last tag>-". 39 | // If there are no commits since the last tag, only the tag is shown. 40 | constexpr std::string_view VERSION_SHORT_STR = "${GIT_SHORT}"; 41 | 42 | // The full git SHA1 hash as a string. 43 | // This is generated using the current git commit information. 44 | constexpr std::string_view GIT_SHA1_STR = "${GIT_SHA1}"; 45 | } 46 | -------------------------------------------------------------------------------- /src/RegisterExtension.cpp: -------------------------------------------------------------------------------- 1 | // Copied from godot-cpp/test/src and modified. 2 | 3 | #include "gdextension_interface.h" 4 | 5 | #include "godot_cpp/core/class_db.hpp" 6 | #include "godot_cpp/core/defs.hpp" 7 | #include "godot_cpp/godot.hpp" 8 | 9 | #include "Example.h" 10 | #include "GDExtensionTemplate.h" 11 | 12 | /// @file 13 | /// Register our classes with Godot. 14 | 15 | namespace 16 | { 17 | /// @brief Called by Godot to let us register our classes with Godot. 18 | /// 19 | /// @param p_level the level being initialized by Godot 20 | /// 21 | /// @see GDExtensionInit 22 | void initializeExtension( godot::ModuleInitializationLevel p_level ) 23 | { 24 | if ( p_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE ) 25 | { 26 | return; 27 | } 28 | 29 | godot::ClassDB::register_class(); 30 | godot::ClassDB::register_class(); 31 | godot::ClassDB::register_class(); 32 | godot::ClassDB::register_class( true ); 33 | godot::ClassDB::register_abstract_class(); 34 | 35 | godot::ClassDB::register_class(); 36 | } 37 | 38 | /// @brief Called by Godot to let us do any cleanup. 39 | /// 40 | /// @see GDExtensionInit 41 | void uninitializeExtension( godot::ModuleInitializationLevel p_level ) 42 | { 43 | if ( p_level != godot::MODULE_INITIALIZATION_LEVEL_SCENE ) 44 | { 45 | return; 46 | } 47 | } 48 | } 49 | 50 | extern "C" 51 | { 52 | /// @brief This is the entry point for the shared library. 53 | /// 54 | /// @note The name of this function must match the "entry_symbol" in 55 | /// templates/template.*.gdextension.in 56 | /// 57 | /// @param p_get_proc_address the interface (need more info) 58 | /// @param p_library the library (need more info) 59 | /// @param r_initialization the intialization (need more info) 60 | /// 61 | /// @returns GDExtensionBool 62 | GDExtensionBool GDE_EXPORT GDExtensionInit( 63 | GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, 64 | GDExtensionInitialization *r_initialization ) 65 | { 66 | { 67 | godot::GDExtensionBinding::InitObject init_obj( p_get_proc_address, p_library, 68 | r_initialization ); 69 | 70 | init_obj.register_initializer( initializeExtension ); 71 | init_obj.register_terminator( uninitializeExtension ); 72 | init_obj.set_minimum_library_initialization_level( 73 | godot::MODULE_INITIALIZATION_LEVEL_SCENE ); 74 | 75 | return init_obj.init(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 5, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 23, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "default", 11 | "displayName": "default", 12 | "description": "Default preset that are inherited by all", 13 | "generator": "Ninja", 14 | "hidden": true, 15 | "environment": { 16 | "PROJECT_NAME": "MyGodotExtention" 17 | } 18 | }, 19 | { 20 | "name": "windows-debug", 21 | "displayName": "64bit Windows Debug", 22 | "inherits": "default", 23 | "binaryDir": "${sourceDir}/../build_${env:PROJECT_NAME}_Windows-AMD64", 24 | "condition": { 25 | "type": "equals", 26 | "lhs": "${hostSystemName}", 27 | "rhs": "Windows" 28 | }, 29 | "toolset": { 30 | "value": "host=x64", 31 | "strategy": "external" 32 | }, 33 | "architecture": { 34 | "value": "x64", 35 | "strategy": "external" 36 | }, 37 | "cacheVariables": { 38 | "CMAKE_C_COMPILER": "cl.exe", 39 | "CMAKE_CXX_COMPILER": "cl.exe", 40 | "CMAKE_BUILD_TYPE": "Debug" 41 | } 42 | }, 43 | { 44 | "name": "windows-release", 45 | "displayName": "64bit Windows Release", 46 | "inherits": "windows-debug", 47 | "cacheVariables": { 48 | "CMAKE_BUILD_TYPE": "Release" 49 | } 50 | }, 51 | { 52 | "name": "linux-debug", 53 | "displayName": "64bit Linux Debug", 54 | "inherits": "default", 55 | "binaryDir": "${sourceDir}/../build_${env:PROJECT_NAME}_Linux-x86_64", 56 | "condition": { 57 | "type": "equals", 58 | "lhs": "${hostSystemName}", 59 | "rhs": "Linux" 60 | }, 61 | "cacheVariables": { 62 | "CMAKE_CXX_COMPILER": "g++", 63 | "CMAKE_C_COMPILER": "gcc", 64 | "CMAKE_BUILD_TYPE": "Debug" 65 | } 66 | }, 67 | { 68 | "name": "linux-release", 69 | "displayName": "64bit Linux Release", 70 | "inherits": "linux-debug", 71 | "cacheVariables": { 72 | "CMAKE_BUILD_TYPE": "Release" 73 | } 74 | }, 75 | { 76 | "name": "macOS-debug", 77 | "displayName": "64bit macOS Debug", 78 | "inherits": "default", 79 | "binaryDir": "${sourceDir}/../build_${env:PROJECT_NAME}_Darwin-Universal", 80 | "condition": { 81 | "type": "equals", 82 | "lhs": "${hostSystemName}", 83 | "rhs": "Darwin" 84 | }, 85 | "cacheVariables": { 86 | "CMAKE_CXX_COMPILER": "clang++", 87 | "CMAKE_C_COMPILER": "clang", 88 | "CMAKE_BUILD_TYPE": "Debug" 89 | } 90 | }, 91 | { 92 | "name": "macOS-release", 93 | "displayName": "64bit macOS Release", 94 | "inherits": "macOS-debug", 95 | "cacheVariables": { 96 | "CMAKE_BUILD_TYPE": "Release" 97 | } 98 | } 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.gitignore' 7 | - '*.md' 8 | 9 | jobs: 10 | lint: 11 | name: 🧹 Lint / 📜 C++ 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Run clang-format style check 16 | uses: jidicula/clang-format-action@v4.9.0 17 | with: 18 | clang-format-version: '15' 19 | exclude-regex: 'extern' 20 | 21 | build: 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | build_type: ['Debug', 'Release'] 26 | config: 27 | - { name: '🍏 macOS Clang', short-name: 'macOS', os: macos-latest } 28 | - { name: '🐧 Linux GCC', short-name: 'Linux', os: ubuntu-latest } 29 | - { 30 | name: '🪟 Windows MSVC', 31 | short-name: 'Windows', 32 | os: windows-latest, 33 | } 34 | 35 | name: 🛠 Build / ${{ matrix.config.name }} (${{ matrix.build_type }}) 36 | runs-on: ${{ matrix.config.os }} 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v3 41 | with: 42 | submodules: recursive 43 | 44 | - name: Install Dependencies (macOS) 45 | if: matrix.config.os == 'macos-latest' 46 | run: brew install ccache 47 | 48 | - name: Install Dependencies (Linux) 49 | if: matrix.config.os == 'ubuntu-latest' 50 | run: | 51 | sudo apt-get update 52 | sudo apt-get install -y ccache ninja-build 53 | 54 | - name: Install Dependencies (Windows) 55 | if: matrix.config.os == 'windows-latest' 56 | run: | 57 | choco upgrade ccache ninja 58 | 59 | - name: Setup MSVC (Windows) 60 | if: matrix.config.os == 'windows-latest' 61 | uses: ilammy/msvc-dev-cmd@v1 62 | 63 | - name: ccache 64 | uses: hendrikmuhs/ccache-action@v1.2 65 | with: 66 | max-size: '10G' 67 | key: ${{ matrix.config.os }}-${{ matrix.build_type }} 68 | 69 | - name: Configure 70 | run: > 71 | mkdir GDExtension-build 72 | 73 | cmake 74 | -B GDExtension-build 75 | -G "Ninja" 76 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 77 | --install-prefix ${{ github.workspace }}/install-${{ matrix.build_type }} 78 | . 79 | 80 | - name: Build 81 | run: cmake --build GDExtension-build 82 | 83 | - name: Install 84 | run: cmake --install GDExtension-build 85 | 86 | - name: Upload artifact (Debug) 87 | if: matrix.build_type == 'Debug' 88 | uses: actions/upload-artifact@v4 89 | with: 90 | name: ${{ github.event.repository.name }}-${{ matrix.config.short-name }}-Debug 91 | path: | 92 | ${{ github.workspace }}/install-${{ matrix.build_type }}/* 93 | 94 | - name: Upload artifact (Release) 95 | if: matrix.build_type == 'Release' 96 | uses: actions/upload-artifact@v4 97 | with: 98 | name: ${{ github.event.repository.name }}-${{ matrix.config.short-name }}-Release 99 | path: | 100 | ${{ github.workspace }}/install-${{ matrix.build_type }}/* 101 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Options are listed here: 2 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | --- 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: true 12 | AllowAllConstructorInitializersOnNextLine: true 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: None 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: MultiLine 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BitFieldColonSpacing: Both 27 | BraceWrapping: 28 | AfterCaseLabel: true 29 | AfterClass: true 30 | AfterControlStatement: true 31 | AfterEnum: true 32 | AfterFunction: true 33 | AfterNamespace: true 34 | AfterStruct: true 35 | AfterUnion: true 36 | AfterExternBlock: true 37 | BeforeCatch: true 38 | BeforeElse: true 39 | IndentBraces: false 40 | SplitEmptyFunction: true 41 | SplitEmptyRecord: true 42 | SplitEmptyNamespace: true 43 | BreakBeforeBinaryOperators: None 44 | BreakBeforeBraces: Custom 45 | BreakBeforeInheritanceComma: false 46 | BreakInheritanceList: BeforeColon 47 | BreakBeforeTernaryOperators: true 48 | BreakConstructorInitializersBeforeComma: false 49 | BreakConstructorInitializers: AfterColon 50 | BreakStringLiterals: true 51 | ColumnLimit: 100 52 | CompactNamespaces: false 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 54 | ConstructorInitializerIndentWidth: 4 55 | ContinuationIndentWidth: 4 56 | Cpp11BracedListStyle: false 57 | DeriveLineEnding: false 58 | DerivePointerAlignment: false 59 | EmptyLineBeforeAccessModifier: LogicalBlock 60 | EmptyLineAfterAccessModifier: Never 61 | FixNamespaceComments: false 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: true 72 | IndentPPDirectives: None 73 | IndentWidth: 4 74 | IndentWrappedFunctionNames: true 75 | InsertBraces: true 76 | KeepEmptyLinesAtTheStartOfBlocks: true 77 | Language: Cpp 78 | MaxEmptyLinesToKeep: 1 79 | NamespaceIndentation: All 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 19 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyBreakTemplateDeclaration: 10 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 1000 88 | PointerAlignment: Right 89 | ReflowComments: true 90 | SortIncludes: true 91 | SortUsingDeclarations: true 92 | SpaceAfterCStyleCast: false 93 | SpaceAfterLogicalNot: false 94 | SpaceAfterTemplateKeyword: true 95 | SpaceBeforeAssignmentOperators: true 96 | SpaceBeforeCaseColon: false 97 | SpaceBeforeCpp11BracedList: false 98 | SpaceBeforeCtorInitializerColon: true 99 | SpaceBeforeInheritanceColon: true 100 | SpaceBeforeParens: ControlStatements 101 | SpaceBeforeRangeBasedForLoopColon: true 102 | SpaceInEmptyParentheses: false 103 | SpacesBeforeTrailingComments: 1 104 | SpacesInAngles: false 105 | SpacesInContainerLiterals: true 106 | SpacesInCStyleCastParentheses: false 107 | SpacesInParentheses: true 108 | SpacesInSquareBrackets: false 109 | Standard: c++17 110 | TabWidth: 4 111 | UseCRLF: false 112 | UseTab: Never 113 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | # by Andy Maloney 3 | 4 | string( TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPERCASE ) 5 | 6 | if ( NOT MSVC ) 7 | option( ${PROJECT_NAME_UPPERCASE}_WARN_EVERYTHING "Turn on all warnings (not recommended - used for lib development)" OFF ) 8 | endif() 9 | 10 | option( ${PROJECT_NAME_UPPERCASE}_WARNING_AS_ERROR "Treat warnings as errors" ON ) 11 | 12 | # Add warnings based on compiler 13 | # Set some helper variables for readability 14 | set( compiler_is_clang "$,$>" ) 15 | set( compiler_is_gnu "$" ) 16 | set( compiler_is_msvc "$" ) 17 | 18 | target_compile_options( ${PROJECT_NAME} 19 | PRIVATE 20 | # MSVC only 21 | $<${compiler_is_msvc}: 22 | /W4 23 | 24 | /w14263 # 'function': member function does not override any base class virtual member function 25 | /w14296 # 'operator': expression is always 'boolean_value' 26 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 27 | /w14545 # expression before comma evaluates to a function which is missing an argument list 28 | /w14546 # function call before comma missing argument list 29 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 30 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 31 | /w14619 # pragma warning: there is no warning number 'number' 32 | /w14640 # thread un-safe static member initialization 33 | /w14905 # wide string literal cast to 'LPSTR' 34 | /w14906 # string literal cast to 'LPWSTR' 35 | 36 | # Disable warnings which bleed through from godot-cpp's macros. 37 | /wd4100 # unreferenced formal parameter 38 | > 39 | 40 | # Clang and GNU 41 | $<$: 42 | -Wall 43 | -Wcast-align 44 | -Wctor-dtor-privacy 45 | -Wextra 46 | -Wformat=2 47 | -Wnon-virtual-dtor 48 | -Wnull-dereference 49 | -Woverloaded-virtual 50 | -Wpedantic 51 | -Wshadow 52 | -Wunused 53 | -Wwrite-strings 54 | 55 | # Disable warnings which bleed through from godot-cpp's macros. 56 | -Wno-unused-parameter 57 | > 58 | 59 | # Clang only 60 | $<${compiler_is_clang}: 61 | -Wdocumentation 62 | -Wimplicit-fallthrough 63 | > 64 | 65 | # GNU only 66 | $<${compiler_is_gnu}: 67 | -Walloc-zero 68 | -Wduplicated-branches 69 | -Wduplicated-cond 70 | -Wlogical-op 71 | > 72 | ) 73 | 74 | # Turn on (almost) all warnings on Clang, Apple Clang, and GNU. 75 | # Useful for internal development, but too noisy for general development. 76 | function( set_warn_everything ) 77 | message( STATUS "[${PROJECT_NAME}] Turning on (almost) all warnings") 78 | 79 | target_compile_options( ${PROJECT_NAME} 80 | PRIVATE 81 | # Clang and GNU 82 | $<$: 83 | -Weverything 84 | -Wno-c++98-compat 85 | -Wno-c++98-compat-pedantic 86 | -Wno-padded 87 | > 88 | ) 89 | endfunction() 90 | 91 | if ( NOT MSVC AND ${PROJECT_NAME_UPPERCASE}_WARN_EVERYTHING ) 92 | set_warn_everything() 93 | endif() 94 | 95 | # Treat warnings as errors 96 | function( set_warning_as_error ) 97 | message( STATUS "[${PROJECT_NAME}] Treating warnings as errors") 98 | 99 | if ( CMAKE_VERSION VERSION_GREATER_EQUAL "3.24" ) 100 | set_target_properties( ${PROJECT_NAME} 101 | PROPERTIES 102 | COMPILE_WARNING_AS_ERROR ON 103 | ) 104 | else() 105 | target_compile_options( ${PROJECT_NAME} 106 | PRIVATE 107 | $<${compiler_is_msvc}:/WX> 108 | $<$:-Werror> 109 | ) 110 | endif() 111 | endfunction() 112 | 113 | if ( ${PROJECT_NAME_UPPERCASE}_WARNING_AS_ERROR ) 114 | set_warning_as_error() 115 | endif() 116 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Unlicense 2 | 3 | cmake_minimum_required( VERSION 3.22 ) 4 | 5 | message( STATUS "Using CMake ${CMAKE_VERSION}" ) 6 | 7 | # Require out-of-source builds 8 | file( TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH ) 9 | 10 | if ( EXISTS "${LOC_PATH}" ) 11 | message( FATAL_ERROR "You cannot build in the source directory. Please use a build subdirectory." ) 12 | endif() 13 | 14 | # Add paths to modules 15 | list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) 16 | 17 | # Turn on link time optimization for everything 18 | set( CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON ) 19 | 20 | # Output compile commands to compile_commands.json (for debugging CMake issues) 21 | set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) 22 | 23 | # Build universal lib on macOS 24 | # Note that CMAKE_OSX_ARCHITECTURES must be set before project(). 25 | if ( APPLE ) 26 | set( CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "" ) 27 | endif() 28 | 29 | # Main project information 30 | project( GDExtensionTemplate 31 | LANGUAGES 32 | CXX 33 | VERSION 34 | 0.1.0 35 | ) 36 | 37 | # Create our library 38 | add_library( ${PROJECT_NAME} SHARED ) 39 | 40 | target_compile_features( ${PROJECT_NAME} 41 | PRIVATE 42 | cxx_std_17 43 | ) 44 | 45 | # LIB_ARCH is the architecture being built. It is set to the build system's architecture. 46 | # For macOS, we build a universal library (both arm64 and x86_64). 47 | set( LIB_ARCH ${CMAKE_SYSTEM_PROCESSOR} ) 48 | if ( APPLE ) 49 | set( LIB_ARCH "universal" ) 50 | endif() 51 | 52 | # LIB_DIR is where the actual library ends up. This is used in both the build directory and the 53 | # install directory and needs to be consistent with the paths in the gdextension file. 54 | # e.g. linux.release.x86_64 = "lib/Linux-x86_64/libGDExtensionTemplate.so" 55 | set( LIB_DIR "lib/${CMAKE_SYSTEM_NAME}-${LIB_ARCH}" ) 56 | 57 | message( STATUS "Building ${PROJECT_NAME} for ${LIB_ARCH} on ${CMAKE_SYSTEM_NAME}") 58 | 59 | # BUILD_OUTPUT_DIR is where we put the resulting library (in the build directory) 60 | set( BUILD_OUTPUT_DIR "${PROJECT_BINARY_DIR}/${PROJECT_NAME}/" ) 61 | 62 | set_target_properties( ${PROJECT_NAME} 63 | PROPERTIES 64 | CXX_VISIBILITY_PRESET hidden 65 | VISIBILITY_INLINES_HIDDEN true 66 | RUNTIME_OUTPUT_DIRECTORY "${BUILD_OUTPUT_DIR}/${LIB_DIR}" 67 | LIBRARY_OUTPUT_DIRECTORY "${BUILD_OUTPUT_DIR}/${LIB_DIR}" 68 | ) 69 | 70 | if( NOT DEFINED CMAKE_DEBUG_POSTFIX ) 71 | set_target_properties( ${PROJECT_NAME} 72 | PROPERTIES 73 | DEBUG_POSTFIX "-d" 74 | ) 75 | endif() 76 | 77 | # Copy over additional files from the support_files directory 78 | add_custom_command( 79 | TARGET ${PROJECT_NAME} POST_BUILD 80 | COMMAND ${CMAKE_COMMAND} -E copy_directory 81 | "${CMAKE_SOURCE_DIR}/support_files" 82 | ${BUILD_OUTPUT_DIR} 83 | ) 84 | 85 | # Warnings 86 | include( CompilerWarnings ) 87 | 88 | # Create and include version info file from git 89 | include( GitVersionInfo ) 90 | 91 | add_subdirectory( src ) 92 | 93 | # Install library, extension file, and support files in ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME} 94 | set( INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/" ) 95 | 96 | message( STATUS "Install directory: ${INSTALL_DIR}") 97 | 98 | install( TARGETS ${PROJECT_NAME} 99 | LIBRARY 100 | DESTINATION ${INSTALL_DIR}/${LIB_DIR} 101 | RUNTIME 102 | DESTINATION ${INSTALL_DIR}/${LIB_DIR} 103 | ) 104 | 105 | # Copy over support files 106 | install( DIRECTORY "${CMAKE_SOURCE_DIR}/support_files/" 107 | DESTINATION ${INSTALL_DIR} 108 | PATTERN ".*" EXCLUDE 109 | ) 110 | 111 | add_subdirectory( templates ) 112 | 113 | # ccache 114 | # Turns on ccache if found 115 | include( ccache ) 116 | 117 | # Formatting 118 | # Adds a custom target to format all the code at once 119 | include( ClangFormat ) 120 | 121 | # godot-cpp 122 | # From here: https://github.com/godotengine/godot-cpp 123 | if ( NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/extern/godot-cpp/Makefile" ) 124 | message( 125 | FATAL_ERROR 126 | "[${PROJECT_NAME}] The godot-cpp submodule was not downloaded. Please update submodules: git submodule update --init --recursive." 127 | ) 128 | endif() 129 | 130 | set( GODOT_CPP_SYSTEM_HEADERS ON CACHE BOOL "" FORCE ) 131 | 132 | add_subdirectory( extern/godot-cpp ) 133 | 134 | set_target_properties( godot-cpp 135 | PROPERTIES 136 | CXX_VISIBILITY_PRESET hidden # visibility needs to be the same as the main library 137 | ) 138 | 139 | target_link_libraries( ${PROJECT_NAME} 140 | PRIVATE 141 | godot-cpp 142 | ) 143 | -------------------------------------------------------------------------------- /src/Example.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Copied from godot-cpp/test/src and modified. 3 | 4 | #include "godot_cpp/classes/control.hpp" 5 | #include "godot_cpp/classes/global_constants.hpp" 6 | #include "godot_cpp/classes/tile_map.hpp" 7 | #include "godot_cpp/classes/tile_set.hpp" 8 | #include "godot_cpp/classes/viewport.hpp" 9 | #include "godot_cpp/core/binder_common.hpp" 10 | #include "godot_cpp/variant/variant.hpp" 11 | 12 | class ExampleRef : public godot::RefCounted 13 | { 14 | GDCLASS( ExampleRef, RefCounted ) 15 | 16 | public: 17 | ExampleRef(); 18 | ~ExampleRef() override; 19 | 20 | void setId( int inID ); 21 | int getID() const; 22 | 23 | bool wasPostInitialized() const 24 | { 25 | return mPostInitialized; 26 | } 27 | 28 | protected: 29 | static void _bind_methods(); 30 | 31 | void _notification( int inWhat ); 32 | 33 | private: 34 | static int sInstanceCount; 35 | static int sLastID; 36 | 37 | int mID; 38 | bool mPostInitialized = false; 39 | }; 40 | 41 | class ExampleMin : public godot::Control 42 | { 43 | GDCLASS( ExampleMin, Control ) 44 | 45 | protected: 46 | static void _bind_methods(); 47 | }; 48 | 49 | class Example : public godot::Control 50 | { 51 | GDCLASS( Example, godot::Control ) 52 | 53 | public: 54 | // Constants. 55 | enum Constants 56 | { 57 | FIRST, 58 | ANSWER_TO_EVERYTHING = 42, 59 | }; 60 | 61 | enum 62 | { 63 | CONSTANT_WITHOUT_ENUM = 314, 64 | }; 65 | 66 | enum Flags 67 | { 68 | FLAG_ONE = 1, 69 | FLAG_TWO = 2, 70 | }; 71 | 72 | Example(); 73 | ~Example() override; 74 | 75 | // Functions. 76 | void simpleFunc(); 77 | void simpleConstFunc() const; 78 | godot::String returnSomething( const godot::String &inBase ); 79 | godot::Viewport *returnSomethingConst() const; 80 | godot::Ref returnEmptyRef() const; 81 | ExampleRef *returnExtendedRef() const; 82 | godot::Ref extendedRefChecks( godot::Ref inRef ) const; 83 | godot::Variant varargsFunc( const godot::Variant **inArgs, GDExtensionInt inArgCount, 84 | GDExtensionCallError &outError ); 85 | int varargsFuncNonVoidReturn( const godot::Variant **inArgs, GDExtensionInt inArgCount, 86 | GDExtensionCallError &outError ); 87 | void varargsFuncVoidReturn( const godot::Variant **inArgs, GDExtensionInt inArgCount, 88 | GDExtensionCallError &outError ); 89 | 90 | void emitCustomSignal( const godot::String &inName, int inValue ); 91 | int defArgs( int inA = 100, int inB = 200 ) const; 92 | 93 | godot::Array testArray() const; 94 | void testTypedArrayArg( const godot::TypedArray &inArray ); 95 | godot::TypedArray testTypedArray() const; 96 | godot::Dictionary testDictionary() const; 97 | Example *testNodeArgument( Example *inNode ) const; 98 | godot::String testStringOps() const; 99 | godot::String testStrUtility() const; 100 | bool testStringIsFortyTwo( const godot::String &inString ) const; 101 | godot::String testStringResize( godot::String ioString ) const; 102 | int testVectorOps() const; 103 | 104 | bool testObjectCastToNode( godot::Object *inObject ) const; 105 | bool testObjectCastToControl( godot::Object *inObject ) const; 106 | bool testObjectCastToExample( godot::Object *inObject ) const; 107 | 108 | godot::Vector2i testVariantVector2iConversion( const godot::Variant &inVariant ) const; 109 | int testVariantIntConversion( const godot::Variant &inVariant ) const; 110 | float testVariantFloatConversion( const godot::Variant &inVariant ) const; 111 | godot::Variant testVariantCall( godot::Variant inVariant ); 112 | godot::Variant testVariantIterator( const godot::Variant &inVariant ); 113 | 114 | void testAddChild( godot::Node *inNode ); 115 | void testSetTileset( godot::TileMap *inTilemap, 116 | const godot::Ref &inTileset ) const; 117 | 118 | godot::Callable testCallableMP(); 119 | godot::Callable testCallableMPRet(); 120 | godot::Callable testCallableMPRetC() const; 121 | godot::Callable testCallableMPStatic() const; 122 | godot::Callable testCallableMPStaticRet() const; 123 | godot::Callable testCustomCallable() const; 124 | 125 | void callableBind(); 126 | 127 | void unboundMethod1( godot::Object *inObject, godot::String inString, int inInt ); 128 | godot::String unboundMethod2( godot::Object *inObject, godot::String inString, int inInt ); 129 | godot::String unboundMethod3( godot::Object *inObject, godot::String inString, 130 | int inInt ) const; 131 | static void unboundStaticMethod1( Example *inObject, godot::String inString, int inInt ); 132 | static godot::String unboundStaticMethod2( godot::Object *inObject, godot::String inString, 133 | int inInt ); 134 | 135 | godot::BitField testBitfield( godot::BitField inFlags ); 136 | 137 | // RPC 138 | void testRPC( int inValue ); 139 | void testSendRPC( int inValue ); 140 | int returnLastRPCArg(); 141 | 142 | // Property 143 | void setCustomPosition( const godot::Vector2 &inPos ); 144 | godot::Vector2 getCustomPosition() const; 145 | godot::Vector4 getV4() const; 146 | 147 | bool testPostInitialize() const; 148 | 149 | // Static method. 150 | static int testStatic( int inA, int inB ); 151 | static void testStatic2(); 152 | 153 | // Virtual function override (no need to bind manually). 154 | virtual bool _has_point( const godot::Vector2 &inPoint ) const override; 155 | 156 | protected: 157 | static void _bind_methods(); 158 | 159 | void _notification( int inWhat ); 160 | bool _set( const godot::StringName &inName, const godot::Variant &inValue ); 161 | bool _get( const godot::StringName &inName, godot::Variant &outReturn ) const; 162 | void _get_property_list( godot::List *outList ) const; 163 | bool _property_can_revert( const godot::StringName &inName ) const; 164 | bool _property_get_revert( const godot::StringName &inName, godot::Variant &outProperty ) const; 165 | void _validate_property( godot::PropertyInfo &inProperty ) const; 166 | 167 | godot::String _to_string() const; 168 | 169 | private: 170 | godot::Vector2 mCustomPosition; 171 | godot::Vector3 mPropertyFromList; 172 | godot::Vector2 mDProp[3]; 173 | 174 | int mLastRPCArg = 0; 175 | }; 176 | 177 | VARIANT_ENUM_CAST( Example::Constants ); 178 | VARIANT_BITFIELD_CAST( Example::Flags ); 179 | 180 | enum EnumWithoutClass 181 | { 182 | OUTSIDE_OF_CLASS = 512 183 | }; 184 | VARIANT_ENUM_CAST( EnumWithoutClass ); 185 | 186 | class ExampleVirtual : public godot::Object 187 | { 188 | GDCLASS( ExampleVirtual, godot::Object ) 189 | 190 | protected: 191 | static void _bind_methods(); 192 | }; 193 | 194 | class ExampleAbstract : public godot::Object 195 | { 196 | GDCLASS( ExampleAbstract, godot::Object ) 197 | 198 | protected: 199 | static void _bind_methods(); 200 | }; 201 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake: -------------------------------------------------------------------------------- 1 | # - Returns a version string from Git 2 | # 3 | # These functions force a re-configure on each git commit so that you can 4 | # trust the values of the variables in your build system. 5 | # 6 | # get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) 7 | # 8 | # Returns the refspec and sha hash of the current head revision 9 | # 10 | # git_describe( [ ...]) 11 | # 12 | # Returns the results of git describe on the source tree, and adjusting 13 | # the output so that it tests false if an error occurs. 14 | # 15 | # git_describe_working_tree( [ ...]) 16 | # 17 | # Returns the results of git describe on the working tree (--dirty option), 18 | # and adjusting the output so that it tests false if an error occurs. 19 | # 20 | # git_get_exact_tag( [ ...]) 21 | # 22 | # Returns the results of git describe --exact-match on the source tree, 23 | # and adjusting the output so that it tests false if there was no exact 24 | # matching tag. 25 | # 26 | # git_local_changes() 27 | # 28 | # Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. 29 | # Uses the return code of "git diff-index --quiet HEAD --". 30 | # Does not regard untracked files. 31 | # 32 | # Requires CMake 2.6 or newer (uses the 'function' command) 33 | # 34 | # Original Author: 35 | # 2009-2020 Ryan Pavlik 36 | # http://academic.cleardefinition.com 37 | # 38 | # Copyright 2009-2013, Iowa State University. 39 | # Copyright 2013-2020, Ryan Pavlik 40 | # Copyright 2013-2020, Contributors 41 | # SPDX-License-Identifier: BSL-1.0 42 | # Distributed under the Boost Software License, Version 1.0. 43 | # (See accompanying file LICENSE_1_0.txt or copy at 44 | # http://www.boost.org/LICENSE_1_0.txt) 45 | 46 | if(__get_git_revision_description) 47 | return() 48 | endif() 49 | set(__get_git_revision_description YES) 50 | 51 | # We must run the following at "include" time, not at function call time, 52 | # to find the path to this module rather than the path to a calling list file 53 | get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) 54 | 55 | # Function _git_find_closest_git_dir finds the next closest .git directory 56 | # that is part of any directory in the path defined by _start_dir. 57 | # The result is returned in the parent scope variable whose name is passed 58 | # as variable _git_dir_var. If no .git directory can be found, the 59 | # function returns an empty string via _git_dir_var. 60 | # 61 | # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and 62 | # neither foo nor bar contain a file/directory .git. This wil return 63 | # C:/bla/.git 64 | # 65 | function(_git_find_closest_git_dir _start_dir _git_dir_var) 66 | set(cur_dir "${_start_dir}") 67 | set(git_dir "${_start_dir}/.git") 68 | while(NOT EXISTS "${git_dir}") 69 | # .git dir not found, search parent directories 70 | set(git_previous_parent "${cur_dir}") 71 | get_filename_component(cur_dir "${cur_dir}" DIRECTORY) 72 | if(cur_dir STREQUAL git_previous_parent) 73 | # We have reached the root directory, we are not in git 74 | set(${_git_dir_var} 75 | "" 76 | PARENT_SCOPE) 77 | return() 78 | endif() 79 | set(git_dir "${cur_dir}/.git") 80 | endwhile() 81 | set(${_git_dir_var} 82 | "${git_dir}" 83 | PARENT_SCOPE) 84 | endfunction() 85 | 86 | function(get_git_head_revision _refspecvar _hashvar) 87 | _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) 88 | 89 | if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") 90 | set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) 91 | else() 92 | set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) 93 | endif() 94 | if(NOT "${GIT_DIR}" STREQUAL "") 95 | file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" 96 | "${GIT_DIR}") 97 | if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) 98 | # We've gone above the CMake root dir. 99 | set(GIT_DIR "") 100 | endif() 101 | endif() 102 | if("${GIT_DIR}" STREQUAL "") 103 | set(${_refspecvar} 104 | "GITDIR-NOTFOUND" 105 | PARENT_SCOPE) 106 | set(${_hashvar} 107 | "GITDIR-NOTFOUND" 108 | PARENT_SCOPE) 109 | return() 110 | endif() 111 | 112 | # Check if the current source dir is a git submodule or a worktree. 113 | # In both cases .git is a file instead of a directory. 114 | # 115 | if(NOT IS_DIRECTORY ${GIT_DIR}) 116 | # The following git command will return a non empty string that 117 | # points to the super project working tree if the current 118 | # source dir is inside a git submodule. 119 | # Otherwise the command will return an empty string. 120 | # 121 | execute_process( 122 | COMMAND "${GIT_EXECUTABLE}" rev-parse 123 | --show-superproject-working-tree 124 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 125 | OUTPUT_VARIABLE out 126 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 127 | if(NOT "${out}" STREQUAL "") 128 | # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule 129 | file(READ ${GIT_DIR} submodule) 130 | string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE 131 | ${submodule}) 132 | string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) 133 | get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) 134 | get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} 135 | ABSOLUTE) 136 | set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") 137 | else() 138 | # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree 139 | file(READ ${GIT_DIR} worktree_ref) 140 | # The .git directory contains a path to the worktree information directory 141 | # inside the parent git repo of the worktree. 142 | # 143 | string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir 144 | ${worktree_ref}) 145 | string(STRIP ${git_worktree_dir} git_worktree_dir) 146 | _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) 147 | set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") 148 | endif() 149 | else() 150 | set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") 151 | endif() 152 | set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") 153 | if(NOT EXISTS "${GIT_DATA}") 154 | file(MAKE_DIRECTORY "${GIT_DATA}") 155 | endif() 156 | 157 | if(NOT EXISTS "${HEAD_SOURCE_FILE}") 158 | return() 159 | endif() 160 | set(HEAD_FILE "${GIT_DATA}/HEAD") 161 | configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) 162 | 163 | configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" 164 | "${GIT_DATA}/grabRef.cmake" @ONLY) 165 | include("${GIT_DATA}/grabRef.cmake") 166 | 167 | set(${_refspecvar} 168 | "${HEAD_REF}" 169 | PARENT_SCOPE) 170 | set(${_hashvar} 171 | "${HEAD_HASH}" 172 | PARENT_SCOPE) 173 | endfunction() 174 | 175 | function(git_describe _var) 176 | if(NOT GIT_FOUND) 177 | find_package(Git QUIET) 178 | endif() 179 | get_git_head_revision(refspec hash) 180 | if(NOT GIT_FOUND) 181 | set(${_var} 182 | "GIT-NOTFOUND" 183 | PARENT_SCOPE) 184 | return() 185 | endif() 186 | if(NOT hash) 187 | set(${_var} 188 | "HEAD-HASH-NOTFOUND" 189 | PARENT_SCOPE) 190 | return() 191 | endif() 192 | 193 | # TODO sanitize 194 | #if((${ARGN}" MATCHES "&&") OR 195 | # (ARGN MATCHES "||") OR 196 | # (ARGN MATCHES "\\;")) 197 | # message("Please report the following error to the project!") 198 | # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") 199 | #endif() 200 | 201 | #message(STATUS "Arguments to execute_process: ${ARGN}") 202 | 203 | execute_process( 204 | COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} 205 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 206 | RESULT_VARIABLE res 207 | OUTPUT_VARIABLE out 208 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 209 | if(NOT res EQUAL 0) 210 | set(out "${out}-${res}-NOTFOUND") 211 | endif() 212 | 213 | set(${_var} 214 | "${out}" 215 | PARENT_SCOPE) 216 | endfunction() 217 | 218 | function(git_describe_working_tree _var) 219 | if(NOT GIT_FOUND) 220 | find_package(Git QUIET) 221 | endif() 222 | if(NOT GIT_FOUND) 223 | set(${_var} 224 | "GIT-NOTFOUND" 225 | PARENT_SCOPE) 226 | return() 227 | endif() 228 | 229 | execute_process( 230 | COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} 231 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 232 | RESULT_VARIABLE res 233 | OUTPUT_VARIABLE out 234 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 235 | if(NOT res EQUAL 0) 236 | set(out "${out}-${res}-NOTFOUND") 237 | endif() 238 | 239 | set(${_var} 240 | "${out}" 241 | PARENT_SCOPE) 242 | endfunction() 243 | 244 | function(git_get_exact_tag _var) 245 | git_describe(out --exact-match ${ARGN}) 246 | set(${_var} 247 | "${out}" 248 | PARENT_SCOPE) 249 | endfunction() 250 | 251 | function(git_local_changes _var) 252 | if(NOT GIT_FOUND) 253 | find_package(Git QUIET) 254 | endif() 255 | get_git_head_revision(refspec hash) 256 | if(NOT GIT_FOUND) 257 | set(${_var} 258 | "GIT-NOTFOUND" 259 | PARENT_SCOPE) 260 | return() 261 | endif() 262 | if(NOT hash) 263 | set(${_var} 264 | "HEAD-HASH-NOTFOUND" 265 | PARENT_SCOPE) 266 | return() 267 | endif() 268 | 269 | execute_process( 270 | COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- 271 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 272 | RESULT_VARIABLE res 273 | OUTPUT_VARIABLE out 274 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 275 | if(res EQUAL 0) 276 | set(${_var} 277 | "CLEAN" 278 | PARENT_SCOPE) 279 | else() 280 | set(${_var} 281 | "DIRTY" 282 | PARENT_SCOPE) 283 | endif() 284 | endfunction() 285 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub](https://img.shields.io/github/license/asmaloney/GDExtensionTemplate)](LICENSE) ![Build](https://github.com/asmaloney/GDExtensionTemplate/actions/workflows/main.yml/badge.svg) 2 | 3 | # GDExtensionTemplate 4 | 5 | This project is meant as a starting point for creating new C++/CMake-based Godot 4 extensions. The goal is to lower the barrier to entry to building a GDExtension using [CMake](https://cmake.org). 6 | 7 | It is currently set up to work with the **[Godot 4.2](https://github.com/godotengine/godot/releases/tag/4.2-stable)** release (see [tags](https://github.com/asmaloney/GDExtensionTemplate/tags) for previous versions). 8 | 9 | Since the majority of C++ open source projects use CMake, I wanted to offer an alternative to the _scons_ system for building Godot extensions (if you use _scons_, check out Nathan Franke's [gdextension](https://github.com/nathanfranke/gdextension) template or Patrick's [GDExtensionSummator](https://github.com/paddy-exe/GDExtensionSummator) template). 10 | 11 | > **Note:** This project is not meant to be a dependency. It is intended to be copied (not forked) and made into your own project. Git itself doesn't provide a nice way to do this (as far as I can tell), but GitHub provides a **Use this template** button (beside where you clone a repo). This will [create a copy for you](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template) without all the history. 12 | 13 | ## Features 14 | 15 | This template project sets up a lot of the build details so you can get started and focus on your code: 16 | 17 | - includes **[godot-cpp](https://github.com/godotengine/godot-cpp) as a submodule** and links it statically to your shared library 18 | - creates `.gdextension` files based on your project name 19 | - copies over any support files from the `support_files` directory into your extension directory 20 | - includes example of adding custom icons to your classes/nodes ([see below](#custom-node-icons)) 21 | - automatically generates a _**Version.h**_ header file which: 22 | - includes a preprocessor macro for conditional compilation 23 | ```cpp 24 | #if GDEXTENSIONTEMPLATE_VERSION < GDEXTENSIONTEMPLATE_VERSION_CHECK(2, 1, 0) 25 | // do stuff 26 | #endif 27 | ``` 28 | - includes git information in the version strings (e.g. `GDExtensionTemplate v1.0.1-gf6446f8`) 29 | - includes an example which exposes the version string to GDScript so you can call it like this 30 | ```py 31 | print( GDExtensionTemplate.version() ) 32 | ``` 33 | - keeps itself up-to-date when the git branch/tag/HEAD changes 34 | - uses **[ccache](https://ccache.dev/)** (if available) for faster rebuilds 35 | - builds **universal library** (x86_64 and arm64) on macOS 36 | - provides **cmake targets**: 37 | - _install_: install all files with the correct structure to `CMAKE_INSTALL_PREFIX` 38 | - _clang-format_: runs `clang-format` on all sources 39 | - includes **GitHub workflows** (CI) for: 40 | - building the extension on **Linux x86_64** (gcc), **macOS universal** (clang), and **Windows x86_64** (MSVC) 41 | - generating debug & release packages on each commit 42 | - using `ccache` to improve CI build times when available 43 | - checking code formatting using `clang-format` 44 | 45 | ## Prerequisites 46 | 47 | To use this locally on your machine, you will need the following: 48 | 49 | - **[CMake](https://cmake.org/)** v3.22+ 50 | - C++ Compiler with at least **C++17** support (any recent compiler) 51 | - (optional) **[ccache](https://ccache.dev/)** for faster rebuilds 52 | - (optional) **[clang-format](https://clang.llvm.org/docs/ClangFormat.html)** for linting and automatic code formatting (CI uses clang-format version 15) 53 | 54 | The GitHub actions (CI) are set up to include all of these tools. To see how to download them on your platform, take a look at the [workflow](.github/workflows/main.yml) file. 55 | 56 | ## How To Use 57 | 58 | ### Setup 59 | 60 | To use this for your own project: 61 | 62 | - _copy_ this repository and rename the directory to the name of your extension 63 | > GitHub provides a **Use this template** button (beside where you clone a repo). This will [create a copy for you](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template) without all the history. 64 | - in _CMakeLists.txt_, change `GDExtensionTemplate` in the `project` macro to the name of your extension 65 | ```cmake 66 | project( 67 | LANGUAGES 68 | CXX 69 | VERSION 70 | 0.1.0 71 | ) 72 | ``` 73 | If you also have plain C files in your project, add `C` to the languages. 74 | - replace the example code in `src` with your own (I would suggest keeping _RegisterExtension.cpp_ and using it to register your classes) 75 | > **Note:** If you change the entry symbol (`GDExtensionInit`) in _RegisterExtension.cpp_, you will need to update your `templates/*.gdextension.in` files. 76 | - replace `CHANGELOG.md` with your own (I would encourage adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the use of [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ) 77 | - replace this `README.md` with your own 78 | - replace custom node icon ([see below](#custom-node-icons)) 79 | 80 | Optional: 81 | 82 | - [contribute to the project](#how-to-contribute) (it's not just 💰!) 83 | - change the platforms/architectures you want to support: 84 | - edit the gdextension templates (`templates/*.gdextension.in`) 85 | - change the GitHub workflows to build the right stuff 86 | - change the `.clang-format` config file to fit your C++ style ([option documentation](https://clang.llvm.org/docs/ClangFormatStyleOptions.html)) 87 | - change the compiler warnings you want to enforce (see [CompilerWarnings.cmake](cmake/CompilerWarnings.cmake)) 88 | - change the LICENSE 89 | 90 | ### Custom Node Icons 91 | 92 | I have included a custom icon for the `Example` node (icon is [CC0](https://creativecommons.org/public-domain/cc0/) from [SVGRepo](https://www.svgrepo.com/svg/207485/gardening-autumn)), so you will want to remove or modify it for your own classes/nodes. 93 | 94 | The icon itself is in `support_files/icons` it is referenced in the `templates/*.gdextension.in` files. 95 | 96 | To add an icon for your custom node: 97 | 98 | - add the icon file to `support_files/icons` and name it after your node (e.g. `MyNode.svg`) 99 | - in each of the `templates/*.gdextension.in` files add an entry for your node in the `[icons]` section: 100 | ``` 101 | MyNode = "icons/MyNode.svg" 102 | ``` 103 | 104 | Everything in the `support_files` directory is copied into your extension, so if you don't want to use icons, remove that directory and remove the `[icons]` section from the `templates/*.gdextension.in` files. 105 | 106 | ### Build & Install 107 | 108 | Here's an example of how to build & install a release version (use the terminal to run the following commands in the parent directory of this repo): 109 | 110 | #### Not MSVC 111 | 112 | ```sh 113 | $ cmake -B GDExtensionTemplate-build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=GDExtensionTemplate-install GDExtensionTemplate 114 | $ cmake --build GDExtensionTemplate-build --parallel 115 | $ cmake --install GDExtensionTemplate-build 116 | ``` 117 | 118 | #### MSVC 119 | 120 | ```sh 121 | $ cmake -B GDExtensionTemplate-build -G"Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=GDExtensionTemplate-install GDExtensionTemplate 122 | $ cmake --build GDExtensionTemplate-build --config Release 123 | $ cmake --install GDExtensionTemplate-build 124 | ``` 125 | 126 | This tells CMake to use `Visual Studio 2022`. There is a list of Visual Studio generators [on the CMake site](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#visual-studio-generators) - pick the one you are using. 127 | 128 | ### Cmake Options 129 | 130 | This template defines the following additional CMake options: 131 | 132 | | Option | Description | Default | 133 | | ------------------------------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | 134 | | `CCACHE_PROGRAM` | Path to `ccache` for faster rebuilds | This is automatically set **ON** if `ccache` is found. If you do not want to use it, set this to "". | 135 | | `CLANG_FORMAT_PROGRAM` | Path to `clang-format` for code formatting. | This is automatically set **ON** if `clang-format` is on. If you do not want to use it, set this to "". | 136 | | `${PROJECT_NAME_UPPERCASE}_WARN_EVERYTHING` (e.g. FOO_WARN_EVERYTHING) | Turns on all warnings. (Not available for MSVC.) | **OFF** (too noisy, but can be useful sometimes) | 137 | | `${PROJECT_NAME_UPPERCASE}_WARNING_AS_ERROR` (e.g. FOO_WARNING_AS_ERROR) | Turns warnings into errors. | **ON** | 138 | 139 | ## Ongoing Updates 140 | 141 | Once your project is up and running you might want to keep up with newer versions of Godot & godot-cpp. 142 | 143 | The key thing is that the version of _godot-cpp_ must match the version of Godot you are using (see the [godot-cpp Versioning section](https://github.com/godotengine/godot-cpp#versioning)). So if you want to use _Godot 4.0 stable_, then you need to match that with the [correct tag in godot-cpp](https://github.com/godotengine/godot-cpp/tags). 144 | 145 | Once you know the correct version of godot-cpp, change the submodule (_extern/godot-cpp_) in your extension to point at that version. 146 | 147 | Updating the submodule and making any necessary changes to your code due to changes in the API are the only things you need to pin to a specific version of Godot. 148 | 149 | ## How To Contribute 150 | 151 | These are some of the things you can do to contribute to the project: 152 | 153 | ### ✍ Write About The Project 154 | 155 | If you find the project useful, spread the word! Articles, mastodon posts, tweets, blog posts, instagram photos - whatever you're into. 156 | 157 | ### ⭐️ Add a Star 158 | 159 | If you found this project useful, please consider starring it! It helps me gauge how useful this project is. 160 | 161 | ### ☝ Raise Issues 162 | 163 | If you run into something which doesn't work as expected, raising [an issue](https://github.com/asmaloney/GDExtensionTemplate/issues) with all the relevant information to reproduce it would be helpful. 164 | 165 | ### 🐞 Bug Fixes & 🧪 New Things 166 | 167 | I am happy to review any [pull requests](https://github.com/asmaloney/GDExtensionTemplate/pulls). Please keep them as short as possible. Each pull request should be atomic and only address one issue. This helps with the review process. 168 | 169 | Note that I will not accept everything, but I welcome discussion. If you are proposing a big change, please raise it as [an issue](https://github.com/asmaloney/GDExtensionTemplate/issues) first for discussion. 170 | 171 | ### 💰 Financial 172 | 173 | Given that I'm an independent developer without funding, financial support is always appreciated. If you would like to support the project financially, you can use [GitHub sponsors](https://github.com/sponsors/asmaloney) or [Ko-fi](https://ko-fi.com/asmaloney) for one-off or recurring support. Thank you! 174 | -------------------------------------------------------------------------------- /.vscode/godot.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0 6 | 7 | _cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0 8 | _cowdata._ptr 9 | 10 | 11 | 12 | 13 | 14 | 15 | count 16 | 17 | count 18 | data 19 | 20 | 21 | 22 | 23 | 24 | 25 | _data ? (_data->size_cache) : 0 26 | 27 | _data ? (_data->size_cache) : 0 28 | _data->first 29 | next_ptr 30 | value 31 | 32 | 33 | 34 | 35 | 36 | 37 | num_elements 38 | 39 | num_elements 40 | head_element 41 | next 42 | data 43 | 44 | 45 | 46 | 47 | 48 | 49 | *(reinterpret_cast<int*>(_cowdata._ptr) - 1) 50 | 51 | *(reinterpret_cast<int*>(_cowdata._ptr) - 1) 52 | reinterpret_cast<VMap<$T1,$T2>::Pair*>(_cowdata._ptr) 53 | 54 | 55 | 56 | 57 | 58 | {dynamic_cast<CallableCustomMethodPointerBase*>(key.custom)->text} 59 | 60 | 61 | 66 | 67 | 68 | nil 69 | {_data._bool} 70 | {_data._int} 71 | {_data._float} 72 | {_data._transform2d} 73 | {_data._aabb} 74 | {_data._basis} 75 | {_data._transform3d} 76 | {_data._projection} 77 | {*(String *)_data._mem} 78 | {*(Vector2 *)_data._mem} 79 | {*(Rect2 *)_data._mem} 80 | {*(Vector3 *)_data._mem} 81 | {*(Vector4 *)_data._mem} 82 | {*(Plane *)_data._mem} 83 | {*(Quaternion *)_data._mem} 84 | {*(Color *)_data._mem} 85 | {*(NodePath *)_data._mem} 86 | {*(::RID *)_data._mem} 87 | {*(Object *)_data._mem} 88 | {*(Dictionary *)_data._mem} 89 | {*(Array *)_data._mem} 90 | {reinterpret_cast<const Variant::PackedArrayRef<unsigned char>*>(_data.packed_array)->array} 91 | {reinterpret_cast<const Variant::PackedArrayRef<int>*>(_data.packed_array)->array} 92 | 95 | {reinterpret_cast<const Variant::PackedArrayRef<float>*>(_data.packed_array)->array} 96 | {reinterpret_cast<const Variant::PackedArrayRef<double>*>(_data.packed_array)->array} 97 | {reinterpret_cast<const Variant::PackedArrayRef<String>*>(_data.packed_array)->array} 98 | {reinterpret_cast<const Variant::PackedArrayRef<Vector2>*>(_data.packed_array)->array} 99 | {reinterpret_cast<const Variant::PackedArrayRef<Vector3>*>(_data.packed_array)->array} 100 | {reinterpret_cast<const Variant::PackedArrayRef<Color>*>(_data.packed_array)->array} 101 | 102 | ((String *)(_data._mem))->_cowdata._ptr,s32 103 | 104 | 105 | _data._bool 106 | _data._int 107 | _data._float 108 | _data._transform2d 109 | _data._aabb 110 | _data._basis 111 | _data._transform3d 112 | *(String *)_data._mem 113 | *(Vector2 *)_data._mem 114 | *(Rect2 *)_data._mem 115 | *(Vector3 *)_data._mem 116 | *(Plane *)_data._mem 117 | *(Quaternion *)_data._mem 118 | *(Color *)_data._mem 119 | *(NodePath *)_data._mem 120 | *(::RID *)_data._mem 121 | *(Object *)_data._mem 122 | *(Dictionary *)_data._mem 123 | *(Array *)_data._mem 124 | reinterpret_cast<const Variant::PackedArrayRef<unsigned char>*>(_data.packed_array)->array 125 | *(PackedInt32Array *)_data._mem 126 | *(PackedInt64Array *)_data._mem 127 | *(PackedFloat32Array *)_data._mem 128 | *(PackedFloat64Array *)_data._mem 129 | *(PackedStringArray *)_data._mem 130 | *(PackedVector2Array *)_data._mem 131 | *(PackedVector3Array *)_data._mem 132 | *(PackedColorArray *)_data._mem 133 | 134 | 135 | 136 | 137 | [empty] 138 | {_cowdata._ptr,s32} 139 | _cowdata._ptr,s32 140 | 141 | 142 | 143 | {*reinterpret_cast<void**>(opaque),s32} 144 | 145 | *reinterpret_cast<void**>(opaque) 146 | *reinterpret_cast<void**>(opaque),s32 147 | 148 | 149 | 150 | 151 | {_data->cname} 152 | {_data->name,s32} 153 | [empty] 154 | _data->cname 155 | _data->name,s32 156 | 157 | 158 | 159 | 160 | {(*reinterpret_cast<const char***>(opaque))[1],s8} 161 | {(*reinterpret_cast<const char***>(opaque))[2],s32} 162 | 163 | *reinterpret_cast<void**>(opaque) 164 | (*reinterpret_cast<const char***>(opaque))+1 165 | (*reinterpret_cast<const char***>(opaque))[1],s8 166 | 167 | 168 | 169 | 170 | "{user.name}" {slot_map} 171 | "{slot_map} 172 | 173 | 174 | 175 | {{{x},{y}}} 176 | 177 | x 178 | y 179 | 180 | 181 | 182 | 183 | {{{x},{y},{z}}} 184 | 185 | x 186 | y 187 | z 188 | 189 | 190 | 191 | 192 | Quaternion {{{x},{y},{z},{w}}} 193 | 194 | x 195 | y 196 | z 197 | w 198 | 199 | 200 | 201 | 202 | Color {{{r},{g},{b},{a}}} 203 | 204 | r 205 | g 206 | b 207 | a 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /src/Example.cpp: -------------------------------------------------------------------------------- 1 | // Copied from godot-cpp/test/src and modified. 2 | 3 | #include "godot_cpp/classes/global_constants.hpp" 4 | #include "godot_cpp/classes/label.hpp" 5 | #include "godot_cpp/core/class_db.hpp" 6 | #include "godot_cpp/variant/utility_functions.hpp" 7 | 8 | #include "Example.h" 9 | 10 | // Used to mark unused parameters to indicate intent and suppress warnings. 11 | #define UNUSED( expr ) (void)( expr ) 12 | 13 | namespace 14 | { 15 | constexpr int MAGIC_NUMBER = 42; 16 | 17 | class MyCallableCustom : public godot::CallableCustom 18 | { 19 | public: 20 | virtual uint32_t hash() const 21 | { 22 | return 27; 23 | } 24 | 25 | virtual godot::String get_as_text() const 26 | { 27 | return ""; 28 | } 29 | 30 | static bool compare_equal_func( const CallableCustom *inA, const CallableCustom *inB ) 31 | { 32 | return inA == inB; 33 | } 34 | 35 | virtual CompareEqualFunc get_compare_equal_func() const 36 | { 37 | return &MyCallableCustom::compare_equal_func; 38 | } 39 | 40 | static bool compare_less_func( const CallableCustom *inA, const CallableCustom *inB ) 41 | { 42 | return reinterpret_cast( inA ) < reinterpret_cast( inB ); 43 | } 44 | 45 | virtual CompareLessFunc get_compare_less_func() const 46 | { 47 | return &MyCallableCustom::compare_less_func; 48 | } 49 | 50 | bool is_valid() const 51 | { 52 | return true; 53 | } 54 | 55 | virtual godot::ObjectID get_object() const 56 | { 57 | return godot::ObjectID(); 58 | } 59 | 60 | virtual void call( const godot::Variant **inArguments, int inArgcount, 61 | godot::Variant &outReturnValue, 62 | GDExtensionCallError &outCallError ) const 63 | { 64 | UNUSED( inArguments ); 65 | UNUSED( inArgcount ); 66 | 67 | outReturnValue = "Hi"; 68 | outCallError.error = GDEXTENSION_CALL_OK; 69 | } 70 | }; 71 | } 72 | 73 | //// ExampleRef 74 | 75 | int ExampleRef::sInstanceCount = 0; 76 | int ExampleRef::sLastID = 0; 77 | 78 | ExampleRef::ExampleRef() 79 | { 80 | mID = ++sLastID; 81 | sInstanceCount++; 82 | 83 | godot::UtilityFunctions::print( 84 | "ExampleRef ", godot::itos( mID ), 85 | " created, current instance count: ", godot::itos( sInstanceCount ) ); 86 | } 87 | 88 | ExampleRef::~ExampleRef() 89 | { 90 | sInstanceCount--; 91 | godot::UtilityFunctions::print( 92 | "ExampleRef ", godot::itos( mID ), 93 | " destroyed, current instance count: ", godot::itos( sInstanceCount ) ); 94 | } 95 | 96 | void ExampleRef::setId( int inID ) 97 | { 98 | mID = inID; 99 | } 100 | 101 | int ExampleRef::getID() const 102 | { 103 | return mID; 104 | } 105 | 106 | void ExampleRef::_bind_methods() 107 | { 108 | godot::ClassDB::bind_method( godot::D_METHOD( "set_id", "id" ), &ExampleRef::setId ); 109 | godot::ClassDB::bind_method( godot::D_METHOD( "get_id" ), &ExampleRef::getID ); 110 | 111 | godot::ClassDB::bind_method( godot::D_METHOD( "was_post_initialized" ), 112 | &ExampleRef::wasPostInitialized ); 113 | } 114 | 115 | void ExampleRef::_notification( int inWhat ) 116 | { 117 | if ( inWhat == NOTIFICATION_POSTINITIALIZE ) 118 | { 119 | mPostInitialized = true; 120 | } 121 | } 122 | 123 | //// ExampleMin 124 | 125 | void ExampleMin::_bind_methods() 126 | { 127 | } 128 | 129 | //// Example 130 | 131 | Example::Example() 132 | { 133 | godot::UtilityFunctions::print( "Constructor." ); 134 | } 135 | 136 | Example::~Example() 137 | { 138 | godot::UtilityFunctions::print( "Destructor." ); 139 | } 140 | 141 | // Methods. 142 | void Example::simpleFunc() 143 | { 144 | godot::UtilityFunctions::print( " Simple func called." ); 145 | } 146 | 147 | void Example::simpleConstFunc() const 148 | { 149 | godot::UtilityFunctions::print( " Simple const func called." ); 150 | } 151 | 152 | godot::String Example::returnSomething( const godot::String &inBase ) 153 | { 154 | godot::UtilityFunctions::print( " Return something called." ); 155 | 156 | return inBase; 157 | } 158 | 159 | godot::Viewport *Example::returnSomethingConst() const 160 | { 161 | godot::UtilityFunctions::print( " Return something const called." ); 162 | 163 | if ( is_inside_tree() ) 164 | { 165 | godot::Viewport *result = get_viewport(); 166 | 167 | return result; 168 | } 169 | 170 | return nullptr; 171 | } 172 | 173 | godot::Ref Example::returnEmptyRef() const 174 | { 175 | godot::Ref ref; 176 | return ref; 177 | } 178 | 179 | ExampleRef *Example::returnExtendedRef() const 180 | { 181 | // You can instance and return a refcounted object like this, but keep in mind that refcounting 182 | // starts with the returned object and it will be destroyed when all references are destroyed. 183 | // If you store this pointer you run the risk of having a pointer to a destroyed object. 184 | return memnew( ExampleRef() ); 185 | } 186 | 187 | godot::Ref Example::extendedRefChecks( godot::Ref inRef ) const 188 | { 189 | // This is the preferred way of instancing and returning a refcounted object: 190 | godot::Ref ref; 191 | ref.instantiate(); 192 | 193 | godot::UtilityFunctions::print( 194 | " Example ref checks called with value: ", inRef->get_instance_id(), 195 | ", returning value: ", ref->get_instance_id() ); 196 | 197 | return ref; 198 | } 199 | 200 | godot::Variant Example::varargsFunc( const godot::Variant **inArgs, GDExtensionInt inArgCount, 201 | GDExtensionCallError &outError ) 202 | { 203 | UNUSED( inArgs ); 204 | UNUSED( outError ); 205 | 206 | godot::UtilityFunctions::print( " Varargs (Variant return) called with ", 207 | godot::String::num_int64( inArgCount ), " arguments" ); 208 | 209 | return inArgCount; 210 | } 211 | 212 | int Example::varargsFuncNonVoidReturn( const godot::Variant **inArgs, GDExtensionInt inArgCount, 213 | GDExtensionCallError &outError ) 214 | { 215 | UNUSED( inArgs ); 216 | UNUSED( outError ); 217 | 218 | godot::UtilityFunctions::print( " Varargs (int return) called with ", 219 | godot::String::num_int64( inArgCount ), " arguments" ); 220 | 221 | return MAGIC_NUMBER; 222 | } 223 | 224 | void Example::varargsFuncVoidReturn( const godot::Variant **inArgs, GDExtensionInt inArgCount, 225 | GDExtensionCallError &outError ) 226 | { 227 | UNUSED( inArgs ); 228 | UNUSED( outError ); 229 | 230 | godot::UtilityFunctions::print( " Varargs (no return) called with ", 231 | godot::String::num_int64( inArgCount ), " arguments" ); 232 | } 233 | 234 | void Example::emitCustomSignal( const godot::String &inName, int inValue ) 235 | { 236 | emit_signal( "custom_signal", inName, inValue ); 237 | } 238 | 239 | int Example::defArgs( int inA, int inB ) const 240 | { 241 | return inA + inB; 242 | } 243 | 244 | godot::Array Example::testArray() const 245 | { 246 | godot::Array arr; 247 | 248 | arr.resize( 2 ); 249 | arr[0] = godot::Variant( 1 ); 250 | arr[1] = godot::Variant( 2 ); 251 | 252 | return arr; 253 | } 254 | 255 | void Example::testTypedArrayArg( const godot::TypedArray &inArray ) 256 | { 257 | for ( int i = 0; i < inArray.size(); ++i ) 258 | { 259 | godot::UtilityFunctions::print( inArray[i] ); 260 | } 261 | } 262 | 263 | godot::TypedArray Example::testTypedArray() const 264 | { 265 | godot::TypedArray arr; 266 | 267 | arr.resize( 2 ); 268 | arr[0] = godot::Vector2( 1, 2 ); 269 | arr[1] = godot::Vector2( 2, 3 ); 270 | 271 | return arr; 272 | } 273 | 274 | godot::Dictionary Example::testDictionary() const 275 | { 276 | godot::Dictionary dict; 277 | 278 | dict["hello"] = "world"; 279 | dict["foo"] = "bar"; 280 | 281 | return dict; 282 | } 283 | 284 | Example *Example::testNodeArgument( Example *inNode ) const 285 | { 286 | // This should use godot::String::num_uint64(), but it is currently broken: 287 | // https://github.com/godotengine/godot-cpp/issues/1014 288 | godot::UtilityFunctions::print( 289 | " Test node argument called with ", 290 | ( inNode != nullptr ) 291 | ? godot::String::num_int64( static_cast( inNode->get_instance_id() ) ) 292 | : "null" ); 293 | return inNode; 294 | } 295 | 296 | godot::String Example::testStringOps() const 297 | { 298 | godot::String s = godot::String( "A" ); 299 | s += "B"; 300 | s += "C"; 301 | s += char32_t( 0x010E ); 302 | s = s + "E"; 303 | 304 | return s; 305 | } 306 | 307 | godot::String Example::testStrUtility() const 308 | { 309 | return godot::UtilityFunctions::str( "Hello, ", "World", "! The answer is ", 42 ); 310 | } 311 | 312 | bool Example::testStringIsFortyTwo( const godot::String &inString ) const 313 | { 314 | return strcmp( inString.utf8().ptr(), "forty two" ) == 0; 315 | } 316 | 317 | godot::String Example::testStringResize( godot::String ioString ) const 318 | { 319 | int64_t orig_len = ioString.length(); 320 | 321 | // This cast is to fix an issue with the API. 322 | // See: https://github.com/godotengine/godot-cpp/issues/1338 323 | ioString.resize( static_cast( orig_len ) + 3 ); 324 | 325 | char32_t *data = ioString.ptrw(); 326 | data[orig_len + 0] = '!'; 327 | data[orig_len + 1] = '?'; 328 | data[orig_len + 2] = '\0'; 329 | 330 | return ioString; 331 | } 332 | 333 | int Example::testVectorOps() const 334 | { 335 | godot::PackedInt32Array arr; 336 | arr.push_back( 10 ); 337 | arr.push_back( 20 ); 338 | arr.push_back( 30 ); 339 | arr.push_back( 45 ); 340 | 341 | int ret = 0; 342 | for ( const int32_t &E : arr ) 343 | { 344 | ret += E; 345 | } 346 | 347 | return ret; 348 | } 349 | 350 | bool Example::testObjectCastToNode( godot::Object *inObject ) const 351 | { 352 | return godot::Object::cast_to( inObject ) != nullptr; 353 | } 354 | 355 | bool Example::testObjectCastToControl( godot::Object *inObject ) const 356 | { 357 | return godot::Object::cast_to( inObject ) != nullptr; 358 | } 359 | 360 | bool Example::testObjectCastToExample( godot::Object *inObject ) const 361 | { 362 | return godot::Object::cast_to( inObject ) != nullptr; 363 | } 364 | 365 | godot::Vector2i Example::testVariantVector2iConversion( const godot::Variant &inVariant ) const 366 | { 367 | return inVariant; 368 | } 369 | 370 | int Example::testVariantIntConversion( const godot::Variant &inVariant ) const 371 | { 372 | return inVariant; 373 | } 374 | 375 | float Example::testVariantFloatConversion( const godot::Variant &inVariant ) const 376 | { 377 | return inVariant; 378 | } 379 | 380 | godot::Variant Example::testVariantCall( godot::Variant inVariant ) 381 | { 382 | return inVariant.call( "test", "hello" ); 383 | } 384 | 385 | godot::Variant Example::testVariantIterator( const godot::Variant &inVariant ) 386 | { 387 | godot::Array output; 388 | godot::Variant iter; 389 | 390 | bool is_init_valid = true; 391 | 392 | if ( !inVariant.iter_init( iter, is_init_valid ) ) 393 | { 394 | if ( !is_init_valid ) 395 | { 396 | return "iter_init: not valid"; 397 | } 398 | 399 | return output; 400 | } 401 | 402 | bool is_iter_next_valid = true; 403 | bool is_iter_get_valid = true; 404 | 405 | do 406 | { 407 | if ( !is_iter_next_valid ) 408 | { 409 | return "iter_next: not valid"; 410 | } 411 | 412 | godot::Variant value = inVariant.iter_get( iter, is_iter_get_valid ); 413 | 414 | if ( !is_iter_get_valid ) 415 | { 416 | return "iter_get: not valid"; 417 | } 418 | 419 | output.push_back( ( static_cast( value ) ) + 5 ); 420 | 421 | } while ( inVariant.iter_next( iter, is_iter_next_valid ) ); 422 | 423 | if ( !is_iter_next_valid ) 424 | { 425 | return "iter_next: not valid"; 426 | } 427 | 428 | return output; 429 | } 430 | 431 | void Example::testAddChild( godot::Node *inNode ) 432 | { 433 | add_child( inNode ); 434 | } 435 | 436 | void Example::testSetTileset( godot::TileMap *inTilemap, 437 | const godot::Ref &inTileset ) const 438 | { 439 | inTilemap->set_tileset( inTileset ); 440 | } 441 | 442 | godot::Callable Example::testCallableMP() 443 | { 444 | return callable_mp( this, &Example::unboundMethod1 ); 445 | } 446 | 447 | godot::Callable Example::testCallableMPRet() 448 | { 449 | return callable_mp( this, &Example::unboundMethod2 ); 450 | } 451 | 452 | godot::Callable Example::testCallableMPRetC() const 453 | { 454 | return callable_mp( this, &Example::unboundMethod3 ); 455 | } 456 | 457 | godot::Callable Example::testCallableMPStatic() const 458 | { 459 | return callable_mp_static( &Example::unboundStaticMethod1 ); 460 | } 461 | 462 | godot::Callable Example::testCallableMPStaticRet() const 463 | { 464 | return callable_mp_static( &Example::unboundStaticMethod2 ); 465 | } 466 | 467 | godot::Callable Example::testCustomCallable() const 468 | { 469 | return godot::Callable( memnew( MyCallableCustom ) ); 470 | } 471 | 472 | void Example::callableBind() 473 | { 474 | godot::Callable c = godot::Callable( this, "emit_custom_signal" ).bind( "bound", 11 ); 475 | c.call(); 476 | } 477 | 478 | void Example::unboundMethod1( godot::Object *inObject, godot::String inString, int inInt ) 479 | { 480 | godot::String test = "unbound_method1: "; 481 | test += inObject->get_class(); 482 | test += " - " + inString; 483 | emitCustomSignal( test, inInt ); 484 | } 485 | 486 | godot::String Example::unboundMethod2( godot::Object *inObject, godot::String inString, int inInt ) 487 | { 488 | godot::String test = "unbound_method2: "; 489 | test += inObject->get_class(); 490 | test += " - " + inString; 491 | test += " - " + godot::itos( inInt ); 492 | return test; 493 | } 494 | 495 | godot::String Example::unboundMethod3( godot::Object *inObject, godot::String inString, 496 | int inInt ) const 497 | { 498 | godot::String test = "unbound_method3: "; 499 | test += inObject->get_class(); 500 | test += " - " + inString; 501 | test += " - " + godot::itos( inInt ); 502 | return test; 503 | } 504 | 505 | void Example::unboundStaticMethod1( Example *inObject, godot::String inString, int inInt ) 506 | { 507 | godot::String test = "unbound_static_method1: "; 508 | test += inObject->get_class(); 509 | test += " - " + inString; 510 | inObject->emitCustomSignal( test, inInt ); 511 | } 512 | 513 | godot::String Example::unboundStaticMethod2( godot::Object *inObject, godot::String inString, 514 | int inInt ) 515 | { 516 | godot::String test = "unbound_static_method2: "; 517 | test += inObject->get_class(); 518 | test += " - " + inString; 519 | test += " - " + godot::itos( inInt ); 520 | return test; 521 | } 522 | 523 | godot::BitField Example::testBitfield( godot::BitField inFlags ) 524 | { 525 | godot::UtilityFunctions::print( " Got BitField: ", godot::String::num_int64( inFlags ) ); 526 | return inFlags; 527 | } 528 | 529 | // RPC 530 | void Example::testRPC( int inValue ) 531 | { 532 | mLastRPCArg = inValue; 533 | } 534 | 535 | void Example::testSendRPC( int inValue ) 536 | { 537 | rpc( "test_rpc", inValue ); 538 | } 539 | 540 | int Example::returnLastRPCArg() 541 | { 542 | return mLastRPCArg; 543 | } 544 | 545 | // Properties 546 | void Example::setCustomPosition( const godot::Vector2 &inPos ) 547 | { 548 | mCustomPosition = inPos; 549 | } 550 | 551 | godot::Vector2 Example::getCustomPosition() const 552 | { 553 | return mCustomPosition; 554 | } 555 | 556 | godot::Vector4 Example::getV4() const 557 | { 558 | return { 1.2f, 3.4f, 5.6f, 7.8f }; 559 | } 560 | 561 | bool Example::testPostInitialize() const 562 | { 563 | godot::Ref new_example_ref; 564 | 565 | new_example_ref.instantiate(); 566 | 567 | return new_example_ref->wasPostInitialized(); 568 | } 569 | 570 | // Static methods 571 | int Example::testStatic( int inA, int inB ) 572 | { 573 | return inA + inB; 574 | } 575 | 576 | void Example::testStatic2() 577 | { 578 | godot::UtilityFunctions::print( " void static" ); 579 | } 580 | 581 | // Virtual function override. 582 | bool Example::_has_point( const godot::Vector2 &inPoint ) const 583 | { 584 | auto *label = godot::Control::get_node( "Label" ); 585 | 586 | label->set_text( "Got point: " + godot::Variant( inPoint ).stringify() ); 587 | 588 | return false; 589 | } 590 | 591 | void Example::_bind_methods() 592 | { 593 | // Methods. 594 | godot::ClassDB::bind_method( godot::D_METHOD( "simple_func" ), &Example::simpleFunc ); 595 | godot::ClassDB::bind_method( godot::D_METHOD( "simple_const_func" ), 596 | &Example::simpleConstFunc ); 597 | godot::ClassDB::bind_method( godot::D_METHOD( "return_something" ), &Example::returnSomething ); 598 | godot::ClassDB::bind_method( godot::D_METHOD( "return_something_const" ), 599 | &Example::returnSomethingConst ); 600 | godot::ClassDB::bind_method( godot::D_METHOD( "return_empty_ref" ), &Example::returnEmptyRef ); 601 | godot::ClassDB::bind_method( godot::D_METHOD( "return_extended_ref" ), 602 | &Example::returnExtendedRef ); 603 | godot::ClassDB::bind_method( godot::D_METHOD( "extended_ref_checks", "ref" ), 604 | &Example::extendedRefChecks ); 605 | 606 | godot::ClassDB::bind_method( godot::D_METHOD( "test_array" ), &Example::testArray ); 607 | godot::ClassDB::bind_method( godot::D_METHOD( "test_tarray_arg", "array" ), 608 | &Example::testTypedArrayArg ); 609 | godot::ClassDB::bind_method( godot::D_METHOD( "test_tarray" ), &Example::testTypedArray ); 610 | godot::ClassDB::bind_method( godot::D_METHOD( "test_dictionary" ), &Example::testDictionary ); 611 | godot::ClassDB::bind_method( godot::D_METHOD( "test_node_argument" ), 612 | &Example::testNodeArgument ); 613 | 614 | godot::ClassDB::bind_method( godot::D_METHOD( "test_string_ops" ), &Example::testStringOps ); 615 | godot::ClassDB::bind_method( godot::D_METHOD( "test_str_utility" ), &Example::testStrUtility ); 616 | godot::ClassDB::bind_method( godot::D_METHOD( "test_string_is_forty_two" ), 617 | &Example::testStringIsFortyTwo ); 618 | godot::ClassDB::bind_method( godot::D_METHOD( "test_string_resize" ), 619 | &Example::testStringResize ); 620 | 621 | godot::ClassDB::bind_method( godot::D_METHOD( "test_vector_ops" ), &Example::testVectorOps ); 622 | 623 | godot::ClassDB::bind_method( godot::D_METHOD( "test_object_cast_to_node", "object" ), 624 | &Example::testObjectCastToNode ); 625 | godot::ClassDB::bind_method( godot::D_METHOD( "test_object_cast_to_control", "object" ), 626 | &Example::testObjectCastToControl ); 627 | godot::ClassDB::bind_method( godot::D_METHOD( "test_object_cast_to_example", "object" ), 628 | &Example::testObjectCastToExample ); 629 | 630 | godot::ClassDB::bind_method( godot::D_METHOD( "test_variant_vector2i_conversion", "variant" ), 631 | &Example::testVariantVector2iConversion ); 632 | godot::ClassDB::bind_method( godot::D_METHOD( "test_variant_int_conversion", "variant" ), 633 | &Example::testVariantIntConversion ); 634 | godot::ClassDB::bind_method( godot::D_METHOD( "test_variant_float_conversion", "variant" ), 635 | &Example::testVariantFloatConversion ); 636 | godot::ClassDB::bind_method( godot::D_METHOD( "test_variant_call", "variant" ), 637 | &Example::testVariantCall ); 638 | godot::ClassDB::bind_method( godot::D_METHOD( "test_variant_iterator", "input" ), 639 | &Example::testVariantIterator ); 640 | 641 | godot::ClassDB::bind_method( godot::D_METHOD( "test_add_child", "node" ), 642 | &Example::testAddChild ); 643 | godot::ClassDB::bind_method( godot::D_METHOD( "test_set_tileset", "tilemap", "tileset" ), 644 | &Example::testSetTileset ); 645 | 646 | godot::ClassDB::bind_method( godot::D_METHOD( "test_callable_mp" ), &Example::testCallableMP ); 647 | godot::ClassDB::bind_method( godot::D_METHOD( "test_callable_mp_ret" ), 648 | &Example::testCallableMPRet ); 649 | godot::ClassDB::bind_method( godot::D_METHOD( "test_callable_mp_retc" ), 650 | &Example::testCallableMPRetC ); 651 | godot::ClassDB::bind_method( godot::D_METHOD( "test_callable_mp_static" ), 652 | &Example::testCallableMPStatic ); 653 | godot::ClassDB::bind_method( godot::D_METHOD( "test_callable_mp_static_ret" ), 654 | &Example::testCallableMPStaticRet ); 655 | godot::ClassDB::bind_method( godot::D_METHOD( "test_custom_callable" ), 656 | &Example::testCustomCallable ); 657 | 658 | godot::ClassDB::bind_method( godot::D_METHOD( "test_bitfield", "flags" ), 659 | &Example::testBitfield ); 660 | 661 | godot::ClassDB::bind_method( godot::D_METHOD( "test_rpc", "value" ), &Example::testRPC ); 662 | godot::ClassDB::bind_method( godot::D_METHOD( "test_send_rpc", "value" ), 663 | &Example::testSendRPC ); 664 | godot::ClassDB::bind_method( godot::D_METHOD( "return_last_rpc_arg" ), 665 | &Example::returnLastRPCArg ); 666 | 667 | godot::ClassDB::bind_method( godot::D_METHOD( "def_args", "a", "b" ), &Example::defArgs, 668 | DEFVAL( 100 ), DEFVAL( 200 ) ); 669 | godot::ClassDB::bind_method( godot::D_METHOD( "callable_bind" ), &Example::callableBind ); 670 | godot::ClassDB::bind_method( godot::D_METHOD( "test_post_initialize" ), 671 | &Example::testPostInitialize ); 672 | 673 | godot::ClassDB::bind_static_method( "Example", godot::D_METHOD( "test_static", "a", "b" ), 674 | &Example::testStatic ); 675 | godot::ClassDB::bind_static_method( "Example", godot::D_METHOD( "test_static2" ), 676 | &Example::testStatic2 ); 677 | 678 | { 679 | godot::MethodInfo mi; 680 | mi.arguments.emplace_back( godot::Variant::STRING, "some_argument" ); 681 | mi.name = "varargs_func"; 682 | 683 | godot::ClassDB::bind_vararg_method( godot::METHOD_FLAGS_DEFAULT, "varargs_func", 684 | &Example::varargsFunc, mi ); 685 | } 686 | 687 | { 688 | godot::MethodInfo mi; 689 | mi.arguments.emplace_back( godot::Variant::STRING, "some_argument" ); 690 | mi.name = "varargs_func_nv"; 691 | 692 | godot::ClassDB::bind_vararg_method( godot::METHOD_FLAGS_DEFAULT, "varargs_func_nv", 693 | &Example::varargsFuncNonVoidReturn, mi ); 694 | } 695 | 696 | { 697 | godot::MethodInfo mi; 698 | mi.arguments.emplace_back( godot::Variant::STRING, "some_argument" ); 699 | mi.name = "varargs_func_void"; 700 | 701 | godot::ClassDB::bind_vararg_method( godot::METHOD_FLAGS_DEFAULT, "varargs_func_void", 702 | &Example::varargsFuncVoidReturn, mi ); 703 | } 704 | 705 | // Properties. 706 | ADD_GROUP( "Test group", "group_" ); 707 | ADD_SUBGROUP( "Test subgroup", "group_subgroup_" ); 708 | 709 | godot::ClassDB::bind_method( godot::D_METHOD( "get_custom_position" ), 710 | &Example::getCustomPosition ); 711 | godot::ClassDB::bind_method( godot::D_METHOD( "get_v4" ), &Example::getV4 ); 712 | godot::ClassDB::bind_method( godot::D_METHOD( "set_custom_position", "position" ), 713 | &Example::setCustomPosition ); 714 | ADD_PROPERTY( godot::PropertyInfo( godot::Variant::VECTOR2, "group_subgroup_custom_position" ), 715 | "set_custom_position", "get_custom_position" ); 716 | 717 | // Signals. 718 | ADD_SIGNAL( godot::MethodInfo( "custom_signal", 719 | godot::PropertyInfo( godot::Variant::STRING, "name" ), 720 | godot::PropertyInfo( godot::Variant::INT, "value" ) ) ); 721 | godot::ClassDB::bind_method( godot::D_METHOD( "emit_custom_signal", "name", "value" ), 722 | &Example::emitCustomSignal ); 723 | 724 | // Constants. 725 | BIND_ENUM_CONSTANT( FIRST ) 726 | BIND_ENUM_CONSTANT( ANSWER_TO_EVERYTHING ) 727 | 728 | BIND_BITFIELD_FLAG( FLAG_ONE ); 729 | BIND_BITFIELD_FLAG( FLAG_TWO ); 730 | 731 | BIND_CONSTANT( CONSTANT_WITHOUT_ENUM ); 732 | BIND_ENUM_CONSTANT( OUTSIDE_OF_CLASS ); 733 | } 734 | 735 | void Example::_notification( int inWhat ) 736 | { 737 | godot::UtilityFunctions::print( "Notification: ", godot::String::num( inWhat ) ); 738 | } 739 | 740 | bool Example::_set( const godot::StringName &inName, const godot::Variant &inValue ) 741 | { 742 | godot::String name = inName; 743 | 744 | if ( name.begins_with( "dproperty" ) ) 745 | { 746 | int64_t index = name.get_slicec( '_', 1 ).to_int(); 747 | mDProp[index] = inValue; 748 | 749 | return true; 750 | } 751 | 752 | if ( name == "property_from_list" ) 753 | { 754 | mPropertyFromList = inValue; 755 | 756 | return true; 757 | } 758 | 759 | return false; 760 | } 761 | 762 | bool Example::_get( const godot::StringName &inName, godot::Variant &outReturn ) const 763 | { 764 | godot::String name = inName; 765 | 766 | if ( name.begins_with( "dproperty" ) ) 767 | { 768 | int64_t index = name.get_slicec( '_', 1 ).to_int(); 769 | outReturn = mDProp[index]; 770 | 771 | return true; 772 | } 773 | 774 | if ( name == "property_from_list" ) 775 | { 776 | outReturn = mPropertyFromList; 777 | 778 | return true; 779 | } 780 | 781 | return false; 782 | } 783 | 784 | void Example::_get_property_list( godot::List *outList ) const 785 | { 786 | outList->push_back( godot::PropertyInfo( godot::Variant::VECTOR3, "property_from_list" ) ); 787 | 788 | for ( int i = 0; i < 3; ++i ) 789 | { 790 | outList->push_back( 791 | godot::PropertyInfo( godot::Variant::VECTOR2, "dproperty_" + godot::itos( i ) ) ); 792 | } 793 | } 794 | 795 | bool Example::_property_can_revert( const godot::StringName &inName ) const 796 | { 797 | if ( inName == godot::StringName( "property_from_list" ) && 798 | mPropertyFromList != godot::Vector3( MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER ) ) 799 | { 800 | return true; 801 | } 802 | 803 | return false; 804 | }; 805 | 806 | bool Example::_property_get_revert( const godot::StringName &inName, 807 | godot::Variant &outProperty ) const 808 | { 809 | if ( inName == godot::StringName( "property_from_list" ) ) 810 | { 811 | outProperty = godot::Vector3( MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER ); 812 | 813 | return true; 814 | } 815 | 816 | return false; 817 | }; 818 | 819 | void Example::_validate_property( godot::PropertyInfo &inProperty ) const 820 | { 821 | godot::String name = inProperty.name; 822 | 823 | // Test hiding the "mouse_filter" property from the editor. 824 | if ( name == "mouse_filter" ) 825 | { 826 | inProperty.usage = godot::PROPERTY_USAGE_NO_EDITOR; 827 | } 828 | } 829 | 830 | godot::String Example::_to_string() const 831 | { 832 | return "[ GDExtension::Example <--> Instance ID:" + godot::uitos( get_instance_id() ) + " ]"; 833 | } 834 | 835 | //// ExampleVirtual 836 | 837 | void ExampleVirtual::_bind_methods() 838 | { 839 | } 840 | 841 | //// ExampleAbstract 842 | 843 | void ExampleAbstract::_bind_methods() 844 | { 845 | } 846 | --------------------------------------------------------------------------------