Compare commits

...

75 Commits

Author SHA1 Message Date
Florian Rival
5957c35afe Bump newIDE version 2019-05-23 23:46:56 +01:00
Florian Rival
4d0ac8d9a4 Update translations 2019-05-23 23:44:56 +01:00
Florian Rival
3a90d15ddb Remove useless translations markers 2019-05-23 23:00:25 +01:00
Florian Rival
d7bee58182 Improve support for JS code events by exposing the context of the events function, if any 2019-05-23 22:02:32 +01:00
Florian Rival
65a14eea7f Add tests for gdjs.VariablesContainer 2019-05-23 17:34:06 +01:00
Florian Rival
899b70c9bf Display errors for resources before warning 2019-05-22 08:39:47 +01:00
Todor Imreorov
b2de428820 Add warning for resources located outside of project folder (#1055) 2019-05-22 08:34:50 +01:00
Florian Rival
51060f806d Add help buttons 2019-05-21 23:33:50 +01:00
Florian Rival
6725a30111 Add more fallbacks to download a version of libGD.js 2019-05-21 21:24:30 +01:00
Todor Imreorov
b65eefc3ee Add npm/yarn commands to run Electron (#1057) 2019-05-21 20:12:00 +01:00
Florian Rival
0a710481af Fix formatting of expressions having code-only (hidden) parameters 2019-05-21 00:21:16 +01:00
Florian Rival
724d76e00d Only display extensions with behaviors in NewBehaviorDialog 2019-05-21 00:07:23 +01:00
Florian Rival
7e3966f04c Fix Prettier 2019-05-21 00:01:59 +01:00
Florian Rival
049483dbbd Fix support for calling behavior methods from other behavior methods 2019-05-20 23:36:13 +01:00
Florian Rival
b8270eb55e Fix typo 2019-05-19 23:28:37 +01:00
Florian Rival
e737ab2443 Merge pull request #1049 from 4ian/feature/runtime-behavior
Events based behaviors 🚀
2019-05-19 23:20:58 +01:00
Florian Rival
7fe3fa1c6f Fix search/tags in ExtensionsSearch 2019-05-19 22:19:58 +01:00
Florian Rival
8d0ba97fbf Add author to EventsFunctionsExtension 2019-05-19 22:13:17 +01:00
Florian Rival
0be7ee859a Fix SetReturnString argument type 2019-05-19 16:51:55 +01:00
Florian Rival
eeeafff2b2 Add tags in ExtensionsSearch 2019-05-18 17:15:37 +01:00
Florian Rival
2bccc31cbb Remove useless TODO 2019-05-18 16:17:45 +01:00
Florian Rival
60bb4ad69f Remove warnings from .vscode/c_cpp_properties.json (2) 2019-05-18 16:08:32 +01:00
Florian Rival
fdfe7b35f6 Allow to replace an extension when importing one 2019-05-18 16:08:00 +01:00
Florian Rival
457940ab04 Add import/export to file for events functions extensions 2019-05-18 15:31:50 +01:00
Florian Rival
c9a90b2b77 Add support for short description and tags in EventsFunctionsExtension 2019-05-18 12:05:21 +01:00
Florian Rival
4e23c46ca1 Remove warnings from .vscode/c_cpp_properties.json 2019-05-18 00:58:28 +01:00
Florian Rival
a98575133f Add button to explain how to create new behaviors 2019-05-18 00:58:04 +01:00
Florian Rival
b7e85078d1 Remove some TODOs and add messages if too much extensions to display 2019-05-18 00:47:46 +01:00
Florian Rival
1494a474f1 Add button to search extensions in ProjectManager 2019-05-17 19:49:11 +01:00
Florian Rival
c7ba85ce03 Display extension full header in ExtensionInstallDialog and handle errors 2019-05-17 19:16:39 +01:00
Florian Rival
327be156f3 Rework EventsFunctionsExtensionsLoader to use context 2019-05-17 08:54:14 +01:00
Florian Rival
2d850b0798 [WIP] Add ExtensionsSearch to NewBehaviorDialog 2019-05-16 21:03:51 +01:00
Florian Rival
7d144f19c0 Add missing translation tags 2019-05-15 21:33:13 +01:00
Florian Rival
6496095d7c Add help links for events based behaviors 2019-05-15 21:31:54 +01:00
Florian Rival
1778aa1bc3 Add copy/cut/paste for events functions 2019-05-12 23:35:15 +01:00
Florian Rival
7769c02a86 Fix flow 2019-05-10 16:15:06 +01:00
Florian Rival
a97486b92b Remove CopyOf prefix when pasting things in ProjectManager 2019-05-10 16:08:38 +01:00
Florian Rival
6315857651 Fix copy/cut/paste for Events Functions/Behaviors Extensions 2019-05-10 16:02:29 +01:00
Florian Rival
99238a099d Only allow changing behavior's object type if actually possible 2019-05-10 15:49:27 +01:00
Florian Rival
58db3beaf8 Add more tests for WholeProjectRefactorer 2019-05-10 14:05:04 +01:00
Florian Rival
ccb18a0da4 Fix opening of behavior function from events sheet and prevent using reserved names 2019-05-10 12:49:54 +01:00
Florian Rival
89ac323c8e Add support for automatic refactoring when events function (including expressions) is renamed 2019-05-10 11:37:33 +01:00
Florian Rival
3073b9f44d Fix typos 2019-05-09 11:25:40 +01:00
Florian Rival
32dd269d18 Add automatic refactoring of project when an events based behavior is renamed 2019-05-08 16:05:03 +01:00
Florian Rival
b96fda0f8c Run Prettier 2019-05-08 10:47:57 +01:00
Florian Rival
4b85f23969 Prepare the new refactorings in WholeProjectRefactorer for behaviors 2019-05-08 00:20:02 +01:00
Florian Rival
e670563c92 Add tests for WholeProjectRefactorer and events based functions/extensions 2019-05-07 18:11:52 +01:00
Florian Rival
1e9dab2117 Fix Behavior field not automatically filling when editing expressions 2019-05-07 14:32:04 +01:00
Florian Rival
aac867f960 Set default minimum FPS to 20 2019-05-07 13:56:08 +01:00
Florian Rival
9e89f51a59 Ensure behavior type is displayed in selector even if unknown 2019-05-07 12:28:04 +01:00
Florian Rival
2d31e910fa Add support for EventsBasedBehavior object type 2019-05-07 00:46:45 +01:00
Florian Rival
6332230c4c Add basic test game for EventsBasedBehavior 2019-05-06 23:32:37 +01:00
Florian Rival
3161f641d8 Rename ownerRemovedFromScene to onOwnerRemovedFromScene 2019-05-06 23:08:41 +01:00
Florian Rival
6dba9d9031 Improve EventsBasedBehavior edition in IDE 2019-05-06 22:51:50 +01:00
Florian Rival
433dc763d1 Separate Behavior into BehaviorContent and Behavior, and add RuntimeBehavior for GDCpp 2019-05-03 22:47:21 +01:00
Wend1go
99416e93cc Fix the display of two parameters in FileSystem extension (#1046) 2019-05-02 10:35:37 +01:00
Florian Rival
68e01e253e Bump newIDE version 2019-05-01 17:49:48 +01:00
Florian Rival
b764fbbde7 Use checksum to verify if a third party editor needs update.
Also a protection against potential tampering of third party editors.
2019-05-01 13:53:59 +01:00
Florian Rival
73121d5d53 [WIP] Add code generation for EventsBasedBehavior (2) 2019-04-29 21:48:28 +01:00
Florian Rival
0a14872660 Bump newIDE version 2019-04-29 20:46:39 +01:00
Florian Rival
78834cbbfc Update translations 2019-04-29 20:43:05 +01:00
Florian Rival
7b69d41857 Remove useless GetCustomCodeInMain and related methods 2019-04-29 11:56:02 +01:00
Florian Rival
668bd6a983 Fix action to set the string returned by an events function (SetReturnString) 2019-04-29 11:25:27 +01:00
Florian Rival
2353815fdf [WIP] Add code generation for EventsBasedBehavior 2019-04-26 17:49:06 +01:00
Florian Rival
9bbc5e426c [WIP] Add support for EventsBasedBehavior in newIDE 2019-04-26 14:12:59 +01:00
Florian Rival
7925e75652 [WIP] Add EventsBasedBehavior 2019-04-26 14:12:59 +01:00
Florian Rival
13bc2a5cd8 Remove more dead code 2019-04-26 14:12:59 +01:00
Florian Rival
bf8d1a6a28 Remove dead code from gd::Layout 2019-04-26 14:12:59 +01:00
Florian Rival
338f062781 Refactor EventsFunctionsExtension to use EventsFunctionsContainer/SerializableWithNameList 2019-04-26 14:12:59 +01:00
Florian Rival
2cf2622b9e Fix crash at save (missing functions after integrating GDevelop.js in the repository) 2019-04-26 14:12:23 +01:00
Florian Rival
6d92430e76 Fix size of the game window when exported with Electron 2019-04-25 09:44:50 +01:00
Florian Rival
9b5a6d8bf0 Try more previous revision to find a pre-built libGD.js 2019-04-24 21:35:33 +01:00
Florian Rival
90c3f4f72b Avoid useless copy of arrays in ForEach event generated code
Lists of objects were wrongly initialized, creating a useless performance hit. All the objects of the "for each" were copied into arrays, before being emptied. And this, for every single object in the for each loop. This was particularly bad when a lot of objects were picked by the for each (if we have N objects, complexity was N*N copies of the array).
2019-04-24 21:23:16 +01:00
Florian Rival
c3794950ab Rename methods to declare list of objects without picking from scene, for consistency 2019-04-24 21:23:16 +01:00
Florian Rival
e7516baa0b Update bug report template to mention web-app 2019-04-24 19:29:28 +01:00
360 changed files with 20601 additions and 7315 deletions

View File

@@ -21,4 +21,5 @@ Steps to reproduce the behavior:
## Other details
* Include any OS/browser version/smartphone that you're using
* Which version of GDevelop are you using? The desktop app or the web-app?
* Add any other context about the problem here.

View File

@@ -4,7 +4,6 @@
"name": "Mac",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/IDE",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
@@ -12,7 +11,6 @@
"${workspaceRoot}/ExtLibs/SFML/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
@@ -28,7 +26,6 @@
"path": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
@@ -108,4 +105,4 @@
}
],
"version": 4
}
}

View File

@@ -79,7 +79,8 @@
"mutex": "cpp",
"__hash": "cpp",
"__debug": "cpp",
"__threading_support": "cpp"
"__threading_support": "cpp",
"any": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

View File

@@ -24,6 +24,10 @@ void EventsCodeGenerationContext::InheritsFrom(
parent_.objectsListsToBeDeclared.end(),
std::inserter(alreadyDeclaredObjectsLists,
alreadyDeclaredObjectsLists.begin()));
std::copy(parent_.objectsListsWithoutPickingToBeDeclared.begin(),
parent_.objectsListsWithoutPickingToBeDeclared.end(),
std::inserter(alreadyDeclaredObjectsLists,
alreadyDeclaredObjectsLists.begin()));
std::copy(parent_.emptyObjectsListsToBeDeclared.begin(),
parent_.emptyObjectsListsToBeDeclared.end(),
std::inserter(alreadyDeclaredObjectsLists,
@@ -47,16 +51,23 @@ void EventsCodeGenerationContext::Reuse(
void EventsCodeGenerationContext::ObjectsListNeeded(
const gd::String& objectName) {
if (emptyObjectsListsToBeDeclared.find(objectName) ==
emptyObjectsListsToBeDeclared.end())
if (!IsToBeDeclared(objectName))
objectsListsToBeDeclared.insert(objectName);
depthOfLastUse[objectName] = GetContextDepth();
}
void EventsCodeGenerationContext::ObjectsListWithoutPickingNeeded(
const gd::String& objectName) {
if (!IsToBeDeclared(objectName))
objectsListsWithoutPickingToBeDeclared.insert(objectName);
depthOfLastUse[objectName] = GetContextDepth();
}
void EventsCodeGenerationContext::EmptyObjectsListNeeded(
const gd::String& objectName) {
if (objectsListsToBeDeclared.find(objectName) ==
objectsListsToBeDeclared.end())
if (!IsToBeDeclared(objectName))
emptyObjectsListsToBeDeclared.insert(objectName);
depthOfLastUse[objectName] = GetContextDepth();
@@ -66,6 +77,8 @@ std::set<gd::String> EventsCodeGenerationContext::GetAllObjectsToBeDeclared()
const {
std::set<gd::String> allObjectListsToBeDeclared(
objectsListsToBeDeclared.begin(), objectsListsToBeDeclared.end());
allObjectListsToBeDeclared.insert(objectsListsWithoutPickingToBeDeclared.begin(),
objectsListsWithoutPickingToBeDeclared.end());
allObjectListsToBeDeclared.insert(emptyObjectsListsToBeDeclared.begin(),
emptyObjectsListsToBeDeclared.end());

View File

@@ -5,7 +5,6 @@
*/
#ifndef EVENTSCODEGENERATIONCONTEXT_H
#define EVENTSCODEGENERATIONCONTEXT_H
#include <map>
#include <memory>
#include <set>
@@ -21,7 +20,7 @@ namespace gd {
* - The "current object", i.e the object being used by an action or a
* condition.
* - If conditions are being generated, the context keeps track of the depth of
* the conditions ( see GetCurrentConditionDepth )
* the conditions (see GetCurrentConditionDepth)
* - You can also get the context depth of the last use of an object list.
*/
class GD_CORE_API EventsCodeGenerationContext {
@@ -106,19 +105,31 @@ class GD_CORE_API EventsCodeGenerationContext {
const gd::String& GetCurrentObject() const { return currentObject; };
/**
* \brief Call this when an instruction in the event need an object list.
* \brief Call this when an instruction in the event needs an objects list.
*
* The list will be filled with objects from the scene if it is the first time
* it is requested, unless there is already an object list with this name (
* i.e. ObjectAlreadyDeclared(objectName) returns true ).
* it is requested, unless there is already an object list with this name
* (i.e. `ObjectAlreadyDeclared(objectName)` returns true).
*/
void ObjectsListNeeded(const gd::String& objectName);
/**
* Call this when an instruction in the event need an object list.
* An empty event list will be declared, without filling it with objects from
* the scene. If there is already an object list with this name, no new list
* will be declared again.
* Call this when an instruction in the event needs an empty objects list
* or the one already declared, if any.
*
* An empty objects list will be declared, without filling it with objects
* from the scene. If there is already an objects list with this name, no new
* list will be declared again.
*/
void ObjectsListWithoutPickingNeeded(const gd::String& objectName);
/**
* Call this when an instruction in the event needs an empty object list,
* even if one is already declared.
*
* An empty objects list will be declared, without filling it with objects
* from the scene. If there is already an object list with this name, it won't
* be used to initialize the new list, which will remain empty.
*/
void EmptyObjectsListNeeded(const gd::String& objectName);
@@ -152,8 +163,18 @@ class GD_CORE_API EventsCodeGenerationContext {
};
/**
* Return the objects lists which will be declared, but no filled, by the
* current context
* Return the objects lists which will be will be declared, without filling
* them with objects from the scene.
*/
const std::set<gd::String>& GetObjectsListsToBeDeclaredWithoutPicking()
const {
return objectsListsWithoutPickingToBeDeclared;
};
/**
* Return the objects lists which will be will be declared empty, without
* filling them with objects from the scene and without copying any previously
* declared objects list.
*/
const std::set<gd::String>& GetObjectsListsToBeDeclaredEmpty() const {
return emptyObjectsListsToBeDeclared;
@@ -207,6 +228,21 @@ class GD_CORE_API EventsCodeGenerationContext {
size_t GetCurrentConditionDepth() const { return customConditionDepth; }
private:
/**
* \brief Returns true if the given object is already going to be declared
* (either as a traditional objects list, or one without picking, or one
* empty).
*
*/
bool IsToBeDeclared(const gd::String& objectName) {
return objectsListsToBeDeclared.find(objectName) !=
objectsListsToBeDeclared.end() ||
objectsListsWithoutPickingToBeDeclared.find(objectName) !=
objectsListsWithoutPickingToBeDeclared.end() ||
emptyObjectsListsToBeDeclared.find(objectName) !=
emptyObjectsListsToBeDeclared.end();
};
std::set<gd::String>
alreadyDeclaredObjectsLists; ///< Objects lists already needed in a
///< parent context.
@@ -214,9 +250,16 @@ class GD_CORE_API EventsCodeGenerationContext {
objectsListsToBeDeclared; ///< Objects lists that will be declared in
///< this context.
std::set<gd::String>
emptyObjectsListsToBeDeclared; ///< Objects lists that will be declared
///< in this context, but not filled with
///< scene's objects.
objectsListsWithoutPickingToBeDeclared; ///< Objects lists that will be
///< declared in this context,
///< but not filled with scene's
///< objects.
std::set<gd::String>
emptyObjectsListsToBeDeclared; ///< Objects lists that will be
///< declared in this context,
///< but not filled with scene's
///< objects and not filled with any
///< previously existing objects list.
std::map<gd::String, unsigned int>
depthOfLastUse; ///< The context depth when an object was last used.
gd::String

View File

@@ -537,6 +537,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
action.GetParameters().size() < 2
? ""
: action.GetParameter(1).GetPlainString());
if (MetadataProvider::HasBehaviorAction(
platform, behaviorType, action.GetType()) &&
instrInfos.parameters.size() >= 2) {
@@ -696,6 +697,11 @@ vector<gd::String> EventsCodeGenerator::GenerateParametersCodes(
return arguments;
}
gd::String EventsCodeGenerator::GenerateGetBehaviorNameCode(
const gd::String& behaviorName) {
return ConvertToStringExplicit(behaviorName);
}
gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
EventsCodeGenerationContext& context) {
auto declareObjectList = [this](gd::String object,
@@ -740,7 +746,7 @@ gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
declarationsCode += objectListDeclaration + "\n";
}
for (auto object : context.GetObjectsListsToBeDeclaredEmpty()) {
for (auto object : context.GetObjectsListsToBeDeclaredWithoutPicking()) {
gd::String objectListDeclaration = "";
if (!context.ObjectAlreadyDeclared(object)) {
objectListDeclaration = "std::vector<RuntimeObject*> " +
@@ -751,6 +757,18 @@ gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
declarationsCode += objectListDeclaration + "\n";
}
for (auto object : context.GetObjectsListsToBeDeclaredEmpty()) {
gd::String objectListDeclaration = "";
if (!context.ObjectAlreadyDeclared(object)) {
objectListDeclaration = "std::vector<RuntimeObject*> " +
GetObjectListName(object, context) + ";\n";
context.SetObjectDeclared(object);
} else
objectListDeclaration = "std::vector<RuntimeObject*> " +
GetObjectListName(object, context) + ";\n";
declarationsCode += objectListDeclaration + "\n";
}
return declarationsCode;
}

View File

@@ -239,11 +239,6 @@ class GD_CORE_API EventsCodeGenerator {
customCodeOutsideMain += code;
};
/**
* \brief Add some code before events in the main function.
*/
void AddCustomCodeInMain(gd::String code) { customCodeInMain += code; };
/** \brief Get the set containing the include files.
*/
const std::set<gd::String>& GetIncludeFiles() const { return includeFiles; }
@@ -254,10 +249,6 @@ class GD_CORE_API EventsCodeGenerator {
return customCodeOutsideMain;
}
/** \brief Get the custom code to be inserted inside main function.
*/
const gd::String& GetCustomCodeInMain() const { return customCodeInMain; }
/** \brief Get the custom declaration to be inserted after includes.
*/
const std::set<gd::String>& GetCustomGlobalDeclaration() const {
@@ -308,7 +299,7 @@ class GD_CORE_API EventsCodeGenerator {
/**
* \brief Return true if the code generation is done for a given project and
* layout.
* layout. If not, this means that the code is generated for a function.
*/
bool HasProjectAndLayout() const { return hasProjectAndLayout; }
@@ -714,6 +705,11 @@ class GD_CORE_API EventsCodeGenerator {
virtual gd::String GenerateArgumentsList(
const std::vector<gd::String>& arguments, size_t startFrom = 0);
/**
* Generate the getter to get the name of the specified behavior.
*/
virtual gd::String GenerateGetBehaviorNameCode(const gd::String& behaviorName);
const gd::Platform& platform; ///< The platform being used.
gd::ObjectsContainer& globalObjectsAndGroups;
@@ -734,8 +730,6 @@ class GD_CORE_API EventsCodeGenerator {
///< can share the same list.
gd::String customCodeOutsideMain; ///< Custom code inserted before events (
///< and not in events function )
gd::String customCodeInMain; ///< Custom code inserted before events ( in
///< main function )
std::set<gd::String>
customGlobalDeclarations; ///< Custom global C++ declarations inserted
///< after includes

View File

@@ -266,9 +266,6 @@ class GD_CORE_API BaseEvent {
float percentDuringLastSession; ///< Total time used by the event during the
///< last run. Used for profiling.
protected:
mutable unsigned int renderedHeight;
private:
bool folded; ///< True if the subevents should be hidden in the events editor
bool disabled; ///< True if the event is disabled and must not be executed

View File

@@ -16,22 +16,15 @@ namespace gd {
gd::Expression Instruction::badExpression("");
Instruction::Instruction(gd::String type_)
: renderedHeightNeedUpdate(true),
renderedHeight(0),
selected(false),
type(type_),
: type(type_),
inverted(false) {
// ctor
parameters.reserve(8);
}
Instruction::Instruction(gd::String type_,
const std::vector<gd::Expression>& parameters_,
bool inverted_)
: renderedHeightNeedUpdate(true),
renderedHeight(0),
selected(false),
type(type_),
: type(type_),
inverted(inverted_),
parameters(parameters_) {
parameters.reserve(8);

View File

@@ -17,7 +17,7 @@ namespace gd {
* action.
*
* An instruction has a type, which define what it does, and some parameters. It
* can also be set as inverted ( when the instruction is a condition ) and it
* can also be set as inverted (when the instruction is a condition) and it
* can have sub instructions. This class does nothing particular except storing
* these data.
*
@@ -131,20 +131,6 @@ class GD_CORE_API Instruction {
*/
inline gd::InstructionsList& GetSubInstructions() { return subInstructions; };
/** \name Rendering
* Members related to the instruction rendering in an event editor.
*/
///@{
mutable bool
renderedHeightNeedUpdate; ///< True if the instruction height may have
///< changed and must be computed again.
mutable unsigned int renderedHeight; ///< Height of the instruction rendered
///< in an event editor.
mutable bool selected; ///< True if selected in an event editor.
///@}
private:
gd::String type; ///< Instruction type
bool inverted; ///< True if the instruction if inverted. Only applicable for

View File

@@ -56,7 +56,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
_("Functions"),
"res/function24.png",
"res/function16.png")
.AddParameter("expression", "The text to be returned")
.AddParameter("string", "The text to be returned")
.MarkAsAdvanced();
extension

View File

@@ -4,8 +4,8 @@
* reserved. This project is released under the MIT License.
*/
#include "BehaviorMetadata.h"
#include <algorithm>
#include <iostream>
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Project/Behavior.h"
@@ -88,6 +88,52 @@ gd::InstructionMetadata& BehaviorMetadata::AddAction(
#endif
}
gd::InstructionMetadata& BehaviorMetadata::AddScopedCondition(
const gd::String& name,
const gd::String& fullname,
const gd::String& description,
const gd::String& sentence,
const gd::String& group,
const gd::String& icon,
const gd::String& smallicon) {
#if defined(GD_IDE_ONLY)
gd::String nameWithNamespace = GetName() +gd::PlatformExtension::GetNamespaceSeparator() + name;
conditionsInfos[nameWithNamespace] = InstructionMetadata(extensionNamespace,
nameWithNamespace,
fullname,
description,
sentence,
group,
icon,
smallicon)
.SetHelpPath(GetHelpPath());
return conditionsInfos[nameWithNamespace];
#endif
}
gd::InstructionMetadata& BehaviorMetadata::AddScopedAction(
const gd::String& name,
const gd::String& fullname,
const gd::String& description,
const gd::String& sentence,
const gd::String& group,
const gd::String& icon,
const gd::String& smallicon) {
#if defined(GD_IDE_ONLY)
gd::String nameWithNamespace = GetName() + gd::PlatformExtension::GetNamespaceSeparator() + name;
actionsInfos[nameWithNamespace] = InstructionMetadata(extensionNamespace,
nameWithNamespace,
fullname,
description,
sentence,
group,
icon,
smallicon)
.SetHelpPath(GetHelpPath());
return actionsInfos[nameWithNamespace];
#endif
}
gd::ExpressionMetadata& BehaviorMetadata::AddExpression(
const gd::String& name,
const gd::String& fullname,
@@ -162,4 +208,8 @@ BehaviorMetadata& BehaviorMetadata::AddIncludeFile(
return *this;
}
const gd::String& BehaviorMetadata::GetName() const {
return instance->GetTypeName();
}
} // namespace gd

View File

@@ -19,11 +19,7 @@ class ExpressionMetadata;
namespace gd {
/**
* \brief Contains user-friendly information about a behavior type
*
* Implementations may derive from this class so as to provide more complete
* metadata if needed. ( For example, GDevelop C++ Platform is shared pointers
* to objects that will be cloned so as to create the behaviors... )
* \brief Contains user-friendly information about a behavior type.
*
* \ingroup Events
*/
@@ -44,7 +40,8 @@ class GD_CORE_API BehaviorMetadata {
virtual ~BehaviorMetadata(){};
/**
* Declare a new condition as being part of the extension.
* Declare a new condition as being part of the behavior.
* \deprecated Prefer using `AddScopedCondition`.
*/
gd::InstructionMetadata& AddCondition(const gd::String& name_,
const gd::String& fullname_,
@@ -55,7 +52,8 @@ class GD_CORE_API BehaviorMetadata {
const gd::String& smallicon_);
/**
* Declare a new action as being part of the extension.
* Declare a new action as being part of the behavior.
* \deprecated Prefer using `AddScopedAction`.
*/
gd::InstructionMetadata& AddAction(const gd::String& name_,
const gd::String& fullname_,
@@ -64,6 +62,28 @@ class GD_CORE_API BehaviorMetadata {
const gd::String& group_,
const gd::String& icon_,
const gd::String& smallicon_);
/**
* Declare a new condition as being part of the behavior.
*/
gd::InstructionMetadata& AddScopedCondition(const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& sentence_,
const gd::String& group_,
const gd::String& icon_,
const gd::String& smallicon_);
/**
* Declare a new action as being part of the behavior.
*/
gd::InstructionMetadata& AddScopedAction(const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& sentence_,
const gd::String& group_,
const gd::String& icon_,
const gd::String& smallicon_);
/**
* Declare a new action as being part of the extension.
*/
@@ -102,29 +122,54 @@ class GD_CORE_API BehaviorMetadata {
/**
* Get the help path of the behavior, relative to the documentation root.
*/
const gd::String &GetHelpPath() const { return helpPath; }
const gd::String& GetHelpPath() const { return helpPath; }
/**
* Set the help path of the behavior, relative to the documentation root.
*
*
* The behavior instructions will have this help path set by
* default, unless you call SetHelpPath on them.
*/
BehaviorMetadata &SetHelpPath(const gd::String &path) {
BehaviorMetadata& SetHelpPath(const gd::String& path) {
helpPath = path;
return *this;
}
const gd::String& GetName() const;
#if defined(GD_IDE_ONLY)
const gd::String& GetFullName() const { return fullname; }
const gd::String& GetDefaultName() const { return defaultName; }
const gd::String& GetDescription() const { return description; }
const gd::String& GetGroup() const { return group; }
const gd::String& GetIconFilename() const { return iconFilename; }
/**
* \brief Set the type of the object that this behavior can be used on.
*/
BehaviorMetadata& SetObjectType(const gd::String& objectType_) {
objectType = objectType_;
return *this;
}
/**
* \brief Get the type of the object that this behavior can be used on.
*
* \note An empty string means the base object, so any object.
*/
const gd::String& GetObjectType() const { return objectType; }
#endif
std::shared_ptr<gd::Behavior> Get() const { return instance; }
std::shared_ptr<gd::BehaviorsSharedData> GetSharedDataInstance() const {
return sharedDatasInstance;
/**
* \brief Return the associated gd::Behavior, handling behavior contents.
*/
gd::Behavior& Get() const { return *instance; }
/**
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
* shared data, if any (nullptr if none).
*/
gd::BehaviorsSharedData* GetSharedDataInstance() const {
return sharedDatasInstance.get();
}
#if defined(GD_IDE_ONLY)
@@ -145,8 +190,10 @@ class GD_CORE_API BehaviorMetadata {
gd::String description;
gd::String group;
gd::String iconFilename;
gd::String objectType;
#endif
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
std::shared_ptr<gd::Behavior> instance;
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance;
};

View File

@@ -5,7 +5,6 @@
*/
#include "Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/ChangesNotifier.h"
#include "GDCore/Project/Object.h"
#include "GDCore/String.h"
@@ -55,12 +54,13 @@ void Platform::RemoveExtension(const gd::String& name) {
}
}
extensionsLoaded.erase(remove_if(extensionsLoaded.begin(),
extensionsLoaded.end(),
[&name](std::shared_ptr<PlatformExtension> extension) {
return extension->GetName() == name;
}),
extensionsLoaded.end());
extensionsLoaded.erase(
remove_if(extensionsLoaded.begin(),
extensionsLoaded.end(),
[&name](std::shared_ptr<PlatformExtension> extension) {
return extension->GetName() == name;
}),
extensionsLoaded.end());
}
bool Platform::IsExtensionLoaded(const gd::String& name) const {
@@ -100,26 +100,24 @@ std::unique_ptr<gd::Object> Platform::CreateObject(
return std::unique_ptr<gd::Object>(std::move(object));
}
std::unique_ptr<gd::Behavior> Platform::CreateBehavior(
const gd::String& behaviorType) const {
gd::Behavior* Platform::GetBehavior(const gd::String& behaviorType) const {
for (std::size_t i = 0; i < extensionsLoaded.size(); ++i) {
std::unique_ptr<gd::Behavior> behavior =
extensionsLoaded[i]->CreateBehavior(behaviorType);
gd::Behavior* behavior = extensionsLoaded[i]->GetBehavior(behaviorType);
if (behavior) return behavior;
}
return nullptr;
}
std::shared_ptr<gd::BehaviorsSharedData> Platform::CreateBehaviorSharedDatas(
gd::BehaviorsSharedData* Platform::GetBehaviorSharedDatas(
const gd::String& behaviorType) const {
for (std::size_t i = 0; i < extensionsLoaded.size(); ++i) {
std::shared_ptr<gd::BehaviorsSharedData> behavior =
extensionsLoaded[i]->CreateBehaviorSharedDatas(behaviorType);
if (behavior != std::shared_ptr<gd::BehaviorsSharedData>()) return behavior;
gd::BehaviorsSharedData* behaviorSharedData =
extensionsLoaded[i]->GetBehaviorSharedDatas(behaviorType);
if (behaviorSharedData) return behaviorSharedData;
}
return std::shared_ptr<gd::BehaviorsSharedData>();
return nullptr;
}
#if defined(GD_IDE_ONLY)

View File

@@ -137,15 +137,16 @@ class GD_CORE_API Platform {
const gd::String& name) const;
/**
* \brief Create a behavior
* \brief Get the class handling the behavior with the given type, or
* `nullptr` if no behavior with the given type is found.
*/
std::unique_ptr<gd::Behavior> CreateBehavior(const gd::String& type) const;
gd::Behavior* GetBehavior(const gd::String& type) const;
/**
* \brief Create a behavior shared data object.
* \brief Get the class handling the behavior shared data with the given type,
* or `nullptr` if no behavior with the given type is found.
*/
std::shared_ptr<gd::BehaviorsSharedData> CreateBehaviorSharedDatas(
const gd::String& type) const;
gd::BehaviorsSharedData* GetBehaviorSharedDatas(const gd::String& type) const;
#if defined(GD_IDE_ONLY)
/**

View File

@@ -225,7 +225,6 @@ gd::BehaviorMetadata& PlatformExtension::GetBehaviorMetadata(
return badBehaviorMetadata;
}
#if defined(GD_IDE_ONLY)
std::vector<gd::String> PlatformExtension::GetBehaviorsTypes() const {
std::vector<gd::String> behaviors;
@@ -236,6 +235,7 @@ std::vector<gd::String> PlatformExtension::GetBehaviorsTypes() const {
return behaviors;
}
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::InstructionMetadata>&
PlatformExtension::GetAllActions() {
return actionsInfos;
@@ -349,22 +349,21 @@ CreateFunPtr PlatformExtension::GetObjectCreationFunctionPtr(
return NULL;
}
std::unique_ptr<gd::Behavior> PlatformExtension::CreateBehavior(
gd::Behavior* PlatformExtension::GetBehavior(
gd::String type) const {
if (behaviorsInfo.find(type) != behaviorsInfo.end())
return std::unique_ptr<gd::Behavior>(
behaviorsInfo.find(type)->second.Get()->Clone());
return &behaviorsInfo.find(type)->second.Get();
return nullptr;
}
std::shared_ptr<gd::BehaviorsSharedData>
PlatformExtension::CreateBehaviorSharedDatas(gd::String type) const {
gd::BehaviorsSharedData*
PlatformExtension::GetBehaviorSharedDatas(gd::String type) const {
if (behaviorsInfo.find(type) != behaviorsInfo.end() &&
behaviorsInfo.find(type)->second.GetSharedDataInstance())
return behaviorsInfo.find(type)->second.GetSharedDataInstance()->Clone();
return behaviorsInfo.find(type)->second.GetSharedDataInstance();
return std::shared_ptr<gd::BehaviorsSharedData>();
return nullptr;
}
void PlatformExtension::SetNameSpace(gd::String nameSpace_) {

View File

@@ -291,18 +291,19 @@ class GD_CORE_API PlatformExtension {
*/
std::shared_ptr<gd::BaseEvent> CreateEvent(gd::String eventType) const;
/**
* \brief Create a behavior
* \brief Get the gd::Behavior handling the given behavior type.
*
* Return NULL if \a behaviorType is not provided by the extension.
* Return nullptr if \a behaviorType is not provided by the extension.
*/
std::unique_ptr<gd::Behavior> CreateBehavior(gd::String behaviorType) const;
gd::Behavior* GetBehavior(gd::String behaviorType) const;
/**
* \brief Create shared data for a behavior
* \brief Get the gd::BehaviorsSharedData handling the given behavior shared
* data.
*
* Return NULL if \a behaviorType is not provided by the extension.
* Return nullptr if \a behaviorType is not provided by the extension.
*/
std::shared_ptr<gd::BehaviorsSharedData> CreateBehaviorSharedDatas(
gd::BehaviorsSharedData* GetBehaviorSharedDatas(
gd::String behaviorType) const;
/**

View File

@@ -71,4 +71,6 @@ bool ArbitraryEventsWorker::VisitInstruction(gd::Instruction& instruction,
return DoVisitInstruction(instruction, isCondition);
}
ArbitraryEventsWorkerWithContext::~ArbitraryEventsWorkerWithContext() {}
} // namespace gd

View File

@@ -14,7 +14,8 @@ namespace gd {
class Instruction;
class BaseEvent;
class EventsList;
}
class ObjectsContainer;
} // namespace gd
namespace gd {
@@ -23,6 +24,8 @@ namespace gd {
* instructions) and do some work on them. Can be used to implement refactoring
* for example.
*
* \see gd::ArbitraryEventsWorkerWithContext
*
* \ingroup IDE
*/
class GD_CORE_API ArbitraryEventsWorker {
@@ -71,6 +74,53 @@ class GD_CORE_API ArbitraryEventsWorker {
};
};
/**
* \brief An events worker that will know about the context (the objects
* container). Useful for workers working on expressions notably.
*
* \see gd::ArbitraryEventsWorker
*
* \ingroup IDE
*/
class GD_CORE_API ArbitraryEventsWorkerWithContext
: public ArbitraryEventsWorker {
public:
ArbitraryEventsWorkerWithContext()
: currentGlobalObjectsContainer(nullptr),
currentObjectsContainer(nullptr){};
virtual ~ArbitraryEventsWorkerWithContext();
/**
* \brief Launch the worker on the specified events list,
* giving the objects container on which the events are applying to.
*/
void Launch(gd::EventsList& events,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_) {
currentGlobalObjectsContainer = &globalObjectsContainer_;
currentObjectsContainer = &objectsContainer_;
ArbitraryEventsWorker::Launch(events);
};
void Launch(gd::EventsList& events) = delete;
protected:
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentGlobalObjectsContainer;
};
const gd::ObjectsContainer& GetObjectsContainer() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentObjectsContainer;
};
private:
const gd::ObjectsContainer* currentGlobalObjectsContainer;
const gd::ObjectsContainer* currentObjectsContainer;
};
} // namespace gd
#endif // GDCORE_ARBITRARYEVENTSWORKER_H

View File

@@ -9,13 +9,110 @@
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and change the given object name to a new one.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionFunctionRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionFunctionRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
const gd::String& behaviorType_,
const gd::String& objectType_,
const gd::String& oldFunctionName_,
const gd::String& newFunctionName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
behaviorType(behaviorType_),
objectType(objectType_),
oldFunctionName(oldFunctionName_),
newFunctionName(newFunctionName_){};
virtual ~ExpressionFunctionRenamer(){};
bool HasDoneRenaming() const { return hasDoneRenaming; }
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitFunctionNode(FunctionNode& node) override {
if (node.functionName == oldFunctionName) {
if (!objectType.empty() && !node.objectName.empty()) {
// Replace an object function
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
if (thisObjectType == behaviorType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
} else if (!behaviorType.empty() && !node.behaviorName.empty()) {
// Replace a behavior function
const gd::String& thisBehaviorType = gd::GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, node.behaviorName);
if (thisBehaviorType == behaviorType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
} else {
// Replace a free function
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
}
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
bool hasDoneRenaming;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::String& behaviorType; // The behavior type for which the expression
// must be replaced (optional)
const gd::String& objectType; // The object type for which the expression
// must be replaced (optional)
const gd::String& oldFunctionName;
const gd::String& newFunctionName;
};
bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
auto& metadata = isCondition ? gd::MetadataProvider::GetConditionMetadata(
@@ -26,20 +123,31 @@ bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
for (std::size_t pNb = 0; pNb < metadata.parameters.size() &&
pNb < instruction.GetParametersCount();
++pNb) {
// Replace object's name in parameters
if (gd::ParameterMetadata::IsExpression("number",
metadata.parameters[pNb].type) ||
gd::ParameterMetadata::IsExpression("string",
metadata.parameters[pNb].type)) {
// This raw replacement is theorically too broad and a ExpressionParser
// should be used instead with callbacks to rename only the function. But
// as ExpressionsRenamer is only used for renaming EventsFunction, which
// have namespaces (i.e: Extension::MyFunction), it's safe enough to do
// this raw search/replace.
instruction.SetParameter(
pNb,
instruction.GetParameter(pNb).GetPlainString().FindAndReplace(
oldType, newType));
const gd::String& type = metadata.parameters[pNb].type;
const gd::String& expression =
instruction.GetParameter(pNb).GetPlainString();
gd::ExpressionParser2 parser(
platform, GetGlobalObjectsContainer(), GetObjectsContainer());
auto node = gd::ParameterMetadata::IsExpression("number", type)
? parser.ParseExpression("number", expression)
: (gd::ParameterMetadata::IsExpression("string", type)
? parser.ParseExpression("string", expression)
: std::unique_ptr<gd::ExpressionNode>());
if (node) {
ExpressionFunctionRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
behaviorType,
objectType,
oldFunctionName,
newFunctionName);
node->Visit(renamer);
if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
}
}

View File

@@ -22,29 +22,51 @@ namespace gd {
* \brief Replace in expressions, in parameters of actions or conditions, calls
* to a function by another function.
*
* \note The replacement is done by making a raw search/replace in parameters
* that are expecting expressions or string expressions. Consequently, to avoid
* unwanted renaming, be sure to only use ExpressionsRenamer for expression
* calls that have an obvious name (in particular, make sure they have a
* namespace: Extension::Expression).
*
* \ingroup IDE
*/
class GD_CORE_API ExpressionsRenamer : public ArbitraryEventsWorker {
class GD_CORE_API ExpressionsRenamer : public ArbitraryEventsWorkerWithContext {
public:
ExpressionsRenamer(const gd::Platform& platform_,
const gd::String& oldType_,
const gd::String& newType_)
: platform(platform_), oldType(oldType_), newType(newType_){};
ExpressionsRenamer(const gd::Platform &platform_) : platform(platform_){};
virtual ~ExpressionsRenamer();
ExpressionsRenamer &SetReplacedFreeExpression(
const gd::String &oldFunctionName_, const gd::String &newFunctionName_) {
objectType = "";
behaviorType = "";
oldFunctionName = oldFunctionName_;
newFunctionName = newFunctionName_;
return *this;
}
ExpressionsRenamer &SetReplacedObjectExpression(
const gd::String &objectType_,
const gd::String &oldFunctionName_,
const gd::String &newFunctionName_) {
objectType = objectType_;
behaviorType = "";
oldFunctionName = oldFunctionName_;
newFunctionName = newFunctionName_;
return *this;
};
ExpressionsRenamer &SetReplacedBehaviorExpression(
const gd::String &behaviorType_,
const gd::String &oldFunctionName_,
const gd::String &newFunctionName_) {
objectType = "";
behaviorType = behaviorType_;
oldFunctionName = oldFunctionName_;
newFunctionName = newFunctionName_;
return *this;
};
private:
bool DoVisitInstruction(gd::Instruction& instruction,
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
const gd::Platform& platform;
gd::String oldType;
gd::String newType;
const gd::Platform &platform;
gd::String oldFunctionName;
gd::String newFunctionName;
gd::String behaviorType;
gd::String objectType;
};
} // namespace gd

View File

@@ -16,13 +16,19 @@ namespace gd {
void EventsFunctionTools::EventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer) {
// Functions don't have access to objects from the "outer" scope.
outputGlobalObjectsContainer.GetObjects().clear();
outputGlobalObjectsContainer.GetObjectGroups().Clear();
// Functions scope for objects is defined according
// to parameters
outputObjectsContainer.GetObjects().clear();
outputObjectsContainer.GetObjectGroups().Clear();
gd::ParameterMetadataTools::ParametersToObjectsContainer(
project, eventsFunction.GetParameters(), outputObjectsContainer);
outputObjectsContainer.GetObjectGroups() = eventsFunction.GetObjectGroups();
}
} // namespace gd
} // namespace gd

View File

@@ -33,6 +33,7 @@ class GD_CORE_API EventsFunctionTools {
static void EventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer);
};
} // namespace gd

View File

@@ -10,6 +10,8 @@
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
#include "GDCore/IDE/Events/InstructionsTypeRenamer.h"
#include "GDCore/IDE/EventsFunctionTools.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
@@ -18,6 +20,30 @@
#include "GDCore/Project/ObjectGroup.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace {
// These functions are doing the reverse of what is done when adding
// instructions/expression to extension/behaviors. If needed, they could be
// moved to gd::PlatformExtension to colocate the usage of the namespace
// separator?
gd::String GetEventsFunctionFullType(const gd::String& extensionName,
const gd::String& functionName) {
const auto& separator = gd::PlatformExtension::GetNamespaceSeparator();
return extensionName + separator + functionName;
}
gd::String GetBehaviorEventsFunctionFullType(const gd::String& extensionName,
const gd::String& behaviorName,
const gd::String& functionName) {
const auto& separator = gd::PlatformExtension::GetNamespaceSeparator();
return extensionName + separator + behaviorName + separator + functionName;
}
gd::String GetBehaviorFullType(const gd::String& extensionName,
const gd::String& behaviorName) {
const auto& separator = gd::PlatformExtension::GetNamespaceSeparator();
return extensionName + separator + behaviorName;
}
} // namespace
namespace gd {
@@ -26,23 +52,146 @@ void WholeProjectRefactorer::ExposeProjectEvents(
// See also gd::Project::ExposeResources for a method that traverse the whole
// project (this time for resources).
// Add layouts resources
// Add layouts events
for (std::size_t s = 0; s < project.GetLayoutsCount(); s++) {
worker.Launch(project.GetLayout(s).GetEvents());
}
// Add external events resources
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
worker.Launch(project.GetExternalEvents(s).GetEvents());
}
// Add events functions extensions resources
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto&& eventsFunction : eventsFunctionsExtension.GetEventsFunctions()) {
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
// Add (behavior) events functions
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction :
behaviorEventsFunctions.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
}
}
}
void WholeProjectRefactorer::ExposeProjectEvents(
gd::Project& project, gd::ArbitraryEventsWorkerWithContext& worker) {
// See also gd::Project::ExposeResources for a method that traverse the whole
// project (this time for resources).
// Add layouts events
for (std::size_t s = 0; s < project.GetLayoutsCount(); s++) {
auto& layout = project.GetLayout(s);
worker.Launch(layout.GetEvents(), project, layout);
}
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
const auto& externalEvents = project.GetExternalEvents(s);
const gd::String& associatedLayout = externalEvents.GetAssociatedLayout();
if (project.HasLayoutNamed(associatedLayout)) {
worker.Launch(project.GetExternalEvents(s).GetEvents(),
project,
project.GetLayout(associatedLayout));
}
}
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
project, *eventsFunction, globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(),
globalObjectsAndGroups,
objectsAndGroups);
}
// Add (behavior) events functions
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction :
behaviorEventsFunctions.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
project, *eventsFunction, globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(),
globalObjectsAndGroups,
objectsAndGroups);
}
}
}
}
std::set<gd::String>
WholeProjectRefactorer::GetAllObjectTypesUsingEventsBasedBehavior(
const gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior) {
std::set<gd::String> allTypes;
const gd::String behaviorType = GetBehaviorFullType(
eventsFunctionsExtension.GetName(), eventsBasedBehavior.GetName());
auto addTypesOfObjectsIn =
[&allTypes, &behaviorType](const gd::ObjectsContainer& objectsContainer) {
for (auto& object : objectsContainer.GetObjects()) {
for (auto& behaviorContent : object->GetAllBehaviorContents()) {
if (behaviorContent.second->GetTypeName() == behaviorType) {
allTypes.insert(object->GetType());
}
}
}
};
addTypesOfObjectsIn(project);
for (std::size_t s = 0; s < project.GetLayoutsCount(); s++) {
auto& layout = project.GetLayout(s);
addTypesOfObjectsIn(layout);
}
return allTypes;
}
void WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters(
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior) {
for (auto& eventsFunction :
eventsBasedBehavior.GetEventsFunctions().GetInternalVector()) {
auto& parameters = eventsFunction->GetParameters();
while (parameters.size() < 2) {
gd::ParameterMetadata newParameter;
parameters.push_back(newParameter);
}
parameters[0]
.SetType("object")
.SetName("Object")
.SetDescription("Object")
.SetExtraInfo(eventsBasedBehavior.GetObjectType());
parameters[1]
.SetType("behavior")
.SetName("Behavior")
.SetDescription("Behavior")
.SetExtraInfo(GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()));
}
}
void WholeProjectRefactorer::RenameEventsFunctionsExtension(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
@@ -50,41 +199,89 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension(
const gd::String& newName) {
auto renameEventsFunction =
[&project, &oldName, &newName](const gd::EventsFunction& eventsFunction) {
auto separator = gd::PlatformExtension::GetNamespaceSeparator();
gd::String oldType = oldName + separator + eventsFunction.GetName();
gd::String newType = newName + separator + eventsFunction.GetName();
DoRenameEventsFunction(
project,
eventsFunction,
GetEventsFunctionFullType(oldName, eventsFunction.GetName()),
GetEventsFunctionFullType(newName, eventsFunction.GetName()));
};
auto renameBehaviorEventsFunction =
[&project, &eventsFunctionsExtension, &oldName, &newName](
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction) {
if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) {
gd::InstructionsTypeRenamer renamer =
gd::InstructionsTypeRenamer(project, oldType, newType);
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(oldName,
eventsBasedBehavior.GetName(),
eventsFunction.GetName()),
GetBehaviorEventsFunctionFullType(newName,
eventsBasedBehavior.GetName(),
eventsFunction.GetName()));
ExposeProjectEvents(project, renamer);
} else if (eventsFunction.GetFunctionType() ==
gd::EventsFunction::Expression ||
eventsFunction.GetFunctionType() ==
gd::EventsFunction::StringExpression) {
gd::ExpressionsRenamer renamer = gd::ExpressionsRenamer(
project.GetCurrentPlatform(), oldType, newType);
ExposeProjectEvents(project, renamer);
// Nothing to do, expressions are not including the extension name
}
};
// Order is important: we first rename the expressions then the instructions,
// to avoid being unable to fetch the metadata (the types of parameters) of
// instructions after they are renamed.
for (auto&& eventsFunction : eventsFunctionsExtension.GetEventsFunctions()) {
// Free expressions
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression ||
eventsFunction->GetFunctionType() ==
gd::EventsFunction::StringExpression) {
renameEventsFunction(*eventsFunction);
}
}
for (auto&& eventsFunction : eventsFunctionsExtension.GetEventsFunctions()) {
// Behavior expressions
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression ||
eventsFunction->GetFunctionType() ==
gd::EventsFunction::StringExpression) {
renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction);
}
}
}
// Free instructions
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) {
renameEventsFunction(*eventsFunction);
}
}
// Behavior instructions
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) {
renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction);
}
}
}
// Finally, rename behaviors used in objects
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
DoRenameBehavior(
project,
GetBehaviorFullType(oldName, eventsBasedBehavior->GetName()),
GetBehaviorFullType(newName, eventsBasedBehavior->GetName()));
}
}
/**
@@ -99,27 +296,208 @@ void WholeProjectRefactorer::RenameEventsFunction(
const gd::EventsFunction& eventsFunction =
eventsFunctionsExtension.GetEventsFunction(oldFunctionName);
auto separator = gd::PlatformExtension::GetNamespaceSeparator();
gd::String oldType =
eventsFunctionsExtension.GetName() + separator + oldFunctionName;
gd::String newType =
eventsFunctionsExtension.GetName() + separator + newFunctionName;
DoRenameEventsFunction(
project,
eventsFunction,
GetEventsFunctionFullType(eventsFunctionsExtension.GetName(),
oldFunctionName),
GetEventsFunctionFullType(eventsFunctionsExtension.GetName(),
newFunctionName));
}
void WholeProjectRefactorer::RenameBehaviorEventsFunction(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& oldFunctionName,
const gd::String& newFunctionName) {
auto& eventsFunctions = eventsBasedBehavior.GetEventsFunctions();
if (!eventsFunctions.HasEventsFunctionNamed(oldFunctionName)) return;
const gd::EventsFunction& eventsFunction =
eventsFunctions.GetEventsFunction(oldFunctionName);
if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) {
gd::InstructionsTypeRenamer renamer =
gd::InstructionsTypeRenamer(project, oldType, newType);
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
oldFunctionName),
GetBehaviorEventsFunctionFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
newFunctionName));
ExposeProjectEvents(project, renamer);
} else if (eventsFunction.GetFunctionType() ==
gd::EventsFunction::Expression ||
eventsFunction.GetFunctionType() ==
gd::EventsFunction::StringExpression) {
gd::ExpressionsRenamer renamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform(), oldType, newType);
gd::ExpressionsRenamer(project.GetCurrentPlatform());
renamer.SetReplacedBehaviorExpression(
GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()),
oldFunctionName,
newFunctionName);
ExposeProjectEvents(project, renamer);
}
}
void WholeProjectRefactorer::RenameEventsBasedBehavior(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::String& oldBehaviorName,
const gd::String& newBehaviorName) {
auto& eventsBasedBehaviors =
eventsFunctionsExtension.GetEventsBasedBehaviors();
if (!eventsBasedBehaviors.Has(oldBehaviorName)) {
gd::LogWarning("Warning, " + oldBehaviorName +
" was not found when calling RenameEventsBasedBehavior.");
return;
}
auto& eventsBasedBehavior = eventsBasedBehaviors.Get(oldBehaviorName);
auto renameBehaviorEventsFunction =
[&project,
&eventsFunctionsExtension,
&eventsBasedBehavior,
&oldBehaviorName,
&newBehaviorName](const gd::EventsFunction& eventsFunction) {
if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) {
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
oldBehaviorName,
eventsFunction.GetName()),
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
newBehaviorName,
eventsFunction.GetName()));
ExposeProjectEvents(project, renamer);
} else if (eventsFunction.GetFunctionType() ==
gd::EventsFunction::Expression ||
eventsFunction.GetFunctionType() ==
gd::EventsFunction::StringExpression) {
// Nothing to do, expressions are not including the name of the
// behavior
}
};
// Order is important: we first rename the expressions then the instructions,
// to avoid being unable to fetch the metadata (the types of parameters) of
// instructions after they are renamed.
auto& behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
// Behavior expressions
for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Expression ||
eventsFunction->GetFunctionType() ==
gd::EventsFunction::StringExpression) {
renameBehaviorEventsFunction(*eventsFunction);
}
}
// Behavior instructions
for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
if (eventsFunction->GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction->GetFunctionType() == gd::EventsFunction::Condition) {
renameBehaviorEventsFunction(*eventsFunction);
}
}
DoRenameBehavior(
project,
GetBehaviorFullType(eventsFunctionsExtension.GetName(), oldBehaviorName),
GetBehaviorFullType(eventsFunctionsExtension.GetName(), newBehaviorName));
}
void WholeProjectRefactorer::DoRenameEventsFunction(
gd::Project& project,
const gd::EventsFunction& eventsFunction,
const gd::String& oldFullType,
const gd::String& newFullType) {
if (eventsFunction.GetFunctionType() == gd::EventsFunction::Action ||
eventsFunction.GetFunctionType() == gd::EventsFunction::Condition) {
gd::InstructionsTypeRenamer renamer =
gd::InstructionsTypeRenamer(project, oldFullType, newFullType);
ExposeProjectEvents(project, renamer);
} else if (eventsFunction.GetFunctionType() ==
gd::EventsFunction::Expression ||
eventsFunction.GetFunctionType() ==
gd::EventsFunction::StringExpression) {
gd::ExpressionsRenamer renamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
renamer.SetReplacedFreeExpression(oldFullType, newFullType);
ExposeProjectEvents(project, renamer);
}
}
void WholeProjectRefactorer::DoRenameBehavior(
gd::Project& project,
const gd::String& oldBehaviorType,
const gd::String& newBehaviorType) {
auto renameBehaviorTypeInBehaviorContent =
[&oldBehaviorType,
&newBehaviorType](gd::BehaviorContent& behaviorContent) {
if (behaviorContent.GetTypeName() == oldBehaviorType) {
behaviorContent.SetTypeName(newBehaviorType);
}
};
auto renameBehaviorTypeInObjects =
[&renameBehaviorTypeInBehaviorContent](
std::vector<std::unique_ptr<gd::Object> >& objectsList) {
for (auto& object : objectsList) {
for (auto& behaviorContent : object->GetAllBehaviorContents()) {
renameBehaviorTypeInBehaviorContent(*behaviorContent.second);
}
}
};
auto renameBehaviorTypeInParameters =
[&oldBehaviorType, &newBehaviorType](gd::EventsFunction& eventsFunction) {
for (auto& parameter : eventsFunction.GetParameters()) {
if (gd::ParameterMetadata::IsBehavior(parameter.GetType()) &&
parameter.GetExtraInfo() == oldBehaviorType) {
parameter.SetExtraInfo(newBehaviorType);
}
}
};
// Rename behavior in global objects
renameBehaviorTypeInObjects(project.GetObjects());
// Rename behavior in layout objects and layout behavior shared data.
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
gd::Layout& layout = project.GetLayout(i);
renameBehaviorTypeInObjects(layout.GetObjects());
for (auto& behaviorSharedDataContent : layout.GetAllBehaviorSharedData()) {
renameBehaviorTypeInBehaviorContent(*behaviorSharedDataContent.second);
}
}
// Rename in parameters of (free/behavior) events function
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
renameBehaviorTypeInParameters(*eventsFunction);
}
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction :
behaviorEventsFunctions.GetInternalVector()) {
renameBehaviorTypeInParameters(*eventsFunction);
}
}
}
}
void WholeProjectRefactorer::ObjectRemovedInLayout(gd::Project& project,
gd::Layout& layout,
const gd::String& objectName,

View File

@@ -5,13 +5,17 @@
*/
#ifndef GDCORE_WHOLEPROJECTREFACTORER_H
#define GDCORE_WHOLEPROJECTREFACTORER_H
#include <set>
#include <vector>
namespace gd {
class Project;
class Layout;
class String;
class EventsFunctionsExtension;
class EventsFunction;
class EventsBasedBehavior;
class ArbitraryEventsWorker;
class ArbitraryEventsWorkerWithContext;
} // namespace gd
namespace gd {
@@ -35,6 +39,15 @@ class GD_CORE_API WholeProjectRefactorer {
static void ExposeProjectEvents(gd::Project& project,
gd::ArbitraryEventsWorker& worker);
/**
* \brief Call the specified worker on all events of the project (layout,
* external events, events functions...)
*
* This should be the preferred way to traverse all the events of a project.
*/
static void ExposeProjectEvents(gd::Project& project,
gd::ArbitraryEventsWorkerWithContext& worker);
/**
* \brief Refactor the project after an events function extension is renamed
*/
@@ -53,6 +66,26 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& oldFunctionName,
const gd::String& newFunctionName);
/**
* \brief Refactor the project after an events function of a behavior is
* renamed.
*/
static void RenameBehaviorEventsFunction(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& oldFunctionName,
const gd::String& newFunctionName);
/**
* \brief Refactor the project after a behavior is renamed.
*/
static void RenameEventsBasedBehavior(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::String& oldBehaviorName,
const gd::String& newBehaviorName);
/**
* \brief Refactor the project after an object is renamed in a layout
*
@@ -95,12 +128,39 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& objectName,
bool removeEventsAndGroups = true);
/**
* \brief Return the set of all the types of the objects that are using the
* given behavior.
*/
static std::set<gd::String> GetAllObjectTypesUsingEventsBasedBehavior(
const gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior);
/**
* \brief Ensure (adding if necessary) that the functions of the given
* behavior have the proper mandatory parameters (the "Object" and
* "Behavior").
*/
static void EnsureBehaviorEventsFunctionsProperParameters(
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior);
virtual ~WholeProjectRefactorer(){};
private:
static std::vector<gd::String> GetAssociatedExternalLayouts(
gd::Project& project, gd::Layout& layout);
static void DoRenameEventsFunction(gd::Project& project,
const gd::EventsFunction& eventsFunction,
const gd::String& oldFullType,
const gd::String& newFullType);
static void DoRenameBehavior(gd::Project& project,
const gd::String& oldBehaviorType,
const gd::String& newBehaviorType);
WholeProjectRefactorer(){};
};

View File

@@ -5,7 +5,9 @@
*/
#include "GDCore/Project/Behavior.h"
#include <iostream>
#if defined(GD_IDE_ONLY)
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
namespace gd {
@@ -13,7 +15,7 @@ Behavior::~Behavior(){};
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> Behavior::GetProperties(
gd::Project& project) const {
const gd::SerializerElement& behaviorContent, gd::Project& project) const {
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
}

View File

@@ -16,9 +16,7 @@ namespace gd {
class SerializerElement;
class Project;
class Layout;
}
class RuntimeObject; // TODO : C++ Platform specific code below
class RuntimeScene;
} // namespace gd
namespace gd {
@@ -26,43 +24,32 @@ namespace gd {
* \brief Base class used to represents a behavior that can be applied to an
* object
*
* \see gd::BehaviorContent
* \see gd::BehaviorsSharedData
* \ingroup PlatformDefinition
*/
class GD_CORE_API Behavior {
public:
Behavior() : activated(true){};
Behavior(){};
virtual ~Behavior();
virtual Behavior* Clone() const { return new Behavior(*this); }
/**
* \brief Change the name identifying the behavior.
* \brief Return the type of the behavior
*/
virtual void SetName(const gd::String& name_) { name = name_; };
const gd::String& GetTypeName() const { return type; }
/**
* \brief Return the name identifying the behavior
* \brief Set the type of the behavior.
*/
virtual const gd::String& GetName() const { return name; }
/**
* \brief Return the name identifying the type of the behavior
*/
virtual const gd::String& GetTypeName() const { return type; }
/**
* \brief Change name identifying the type of the behavior.
*
* You should not need to use this method: The type is set by the IDE when the
* behavior is created or when the behavior is loaded from xml.
*/
virtual void SetTypeName(const gd::String& type_) { type = type_; };
void SetTypeName(const gd::String& type_) { type = type_; };
#if defined(GD_IDE_ONLY)
/**
* \brief Called when the IDE wants to know about the custom properties of the
behavior.
* behavior.
*
* Usage example:
* Implementation example:
\code
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Initial speed")].SetValue(gd::String::From(initialSpeed));
@@ -74,7 +61,7 @@ class GD_CORE_API Behavior {
* \see gd::PropertyDescriptor
*/
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
gd::Project& project) const;
const gd::SerializerElement& behaviorContent, gd::Project& project) const;
/**
* \brief Called when the IDE wants to update a custom property of the
@@ -83,107 +70,22 @@ class GD_CORE_API Behavior {
* \return false if the new value cannot be set
* \see gd::InitialInstance
*/
virtual bool UpdateProperty(const gd::String& name,
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) {
return false;
};
/**
* \brief Serialize the behavior.
*/
virtual void SerializeTo(gd::SerializerElement& element) const {};
#endif
/**
* \brief Unserialize the behavior.
* \brief Called to initialize the content with the default properties
* for the behavior.
*/
virtual void UnserializeFrom(const gd::SerializerElement& element){};
virtual void InitializeContent(gd::SerializerElement& behaviorContent){};
/** \name C++ Platform specific
* Members functions related to behaviors of GD C++ Platform.
* Should be moved to a separate RuntimeBehavior class in GDCpp.
*/
///@{
/**
* Set the object owning this behavior
*/
void SetOwner(RuntimeObject* owner_) {
object = owner_;
OnOwnerChanged();
};
/**
* Called at each frame before events. Call DoStepPreEvents.
*/
inline void StepPreEvents(RuntimeScene& scene) {
if (activated) DoStepPreEvents(scene);
};
/**
* Called at each frame after events. Call DoStepPostEvents.
*/
inline void StepPostEvents(RuntimeScene& scene) {
if (activated) DoStepPostEvents(scene);
};
/**
* De/Activate the behavior
*/
inline void Activate(bool enable = true) {
if (!activated && enable) {
activated = true;
OnActivate();
} else if (activated && !enable) {
activated = false;
OnDeActivate();
}
};
/**
* Return true if the behavior is activated
*/
inline bool Activated() const { return activated; };
/**
* Reimplement this method to do extra work when the behavior is activated
*/
virtual void OnActivate(){};
/**
* Reimplement this method to do extra work when the behavior is deactivated
*/
virtual void OnDeActivate(){};
///@}
protected:
gd::String name; ///< Name of the behavior
gd::String type; ///< The type indicate of which type is the behavior. ( To
///< test if we can do something, like actions, reserved to
///< specific behavior with it )
//////
// TODO : C++ Platform specific code below : To be put in a RuntimeBehavior
// class
/**
* Called at each frame before events
*/
virtual void DoStepPreEvents(RuntimeScene& scene){};
/**
* Called at each frame after events
*/
virtual void DoStepPostEvents(RuntimeScene& scene){};
/**
* Redefine this method so as to do special works when owner is set.
*/
virtual void OnOwnerChanged(){};
RuntimeObject* object; ///< Object owning the behavior
bool activated; ///< True if behavior is running
private:
gd::String type;
};
} // namespace gd

View File

@@ -0,0 +1,12 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/BehaviorContent.h"
namespace gd {
BehaviorContent::~BehaviorContent(){};
} // namespace gd

View File

@@ -0,0 +1,88 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_BEHAVIORCONTENT_H
#define GDCORE_BEHAVIORCONTENT_H
#include <map>
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/String.h"
#if defined(GD_IDE_ONLY)
namespace gd {
class PropertyDescriptor;
}
#endif
namespace gd {
class SerializerElement;
class Project;
class Layout;
} // namespace gd
namespace gd {
/**
* \brief Store the content (i.e: the properties) of a behavior of an object.
*
* \see gd::Behavior
* \see gd::BehaviorsSharedData
* \see gd::Object
* \ingroup PlatformDefinition
*/
class GD_CORE_API BehaviorContent {
public:
BehaviorContent(const gd::String& name_, const gd::String& type_)
: name(name_), type(type_){};
virtual ~BehaviorContent();
virtual BehaviorContent* Clone() const { return new BehaviorContent(*this); }
/**
* \brief Return the name identifying the behavior
*/
virtual const gd::String& GetName() const { return name; }
/**
* \brief Change the name identifying the behavior
*/
virtual void SetName(const gd::String& name_) { name = name_; }
/**
* \brief Get the type of the behavior.
*/
virtual const gd::String& GetTypeName() const { return type; }
/**
* \brief Change the type of the behavior
*/
virtual void SetTypeName(const gd::String& type_) { type = type_; }
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize the behavior content.
*/
virtual void SerializeTo(gd::SerializerElement& element) const {
element = content;
};
#endif
/**
* \brief Unserialize the behavior content.
*/
virtual void UnserializeFrom(const gd::SerializerElement& element) {
content = element;
};
const gd::SerializerElement& GetContent() const { return content; };
gd::SerializerElement& GetContent() { return content; };
protected:
gd::String name; ///< Name of the behavior
gd::String type; ///< The type of the behavior that is represented. Usually
///< in the form "ExtensionName::BehaviorTypeName"
gd::SerializerElement content; // Storage for the behavior properties
};
} // namespace gd
#endif // GDCORE_BEHAVIORCONTENT_H

View File

@@ -16,7 +16,7 @@ BehaviorsSharedData::~BehaviorsSharedData(){};
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> BehaviorsSharedData::GetProperties(
gd::Project& project) const {
const gd::SerializerElement& behaviorSharedDataContent, gd::Project& project) const {
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
}

View File

@@ -13,16 +13,10 @@
class BehaviorsRuntimeSharedData;
namespace gd {
class SerializerElement;
}
namespace gd {
class PropertyDescriptor;
}
namespace gd {
class Project;
}
namespace gd {
class Layout;
}
} // namespace gd
namespace gd {
@@ -33,30 +27,16 @@ namespace gd {
* Behaviors can use shared data, as if they were extending the gd::Layout
* class.
*
* \note GD C++ Platform extensions writers : Inherit from this class, and
* redefine Clone and CreateRuntimeSharedDatas.
*
* \ingroup GameEngine
*/
class GD_CORE_API BehaviorsSharedData {
public:
BehaviorsSharedData(){};
virtual ~BehaviorsSharedData();
virtual std::shared_ptr<gd::BehaviorsSharedData> Clone() const {
return std::shared_ptr<gd::BehaviorsSharedData>(
new BehaviorsSharedData(*this));
virtual gd::BehaviorsSharedData* Clone() const {
return new BehaviorsSharedData(*this);
}
/**
* \brief Change the name identifying the behavior.
*/
void SetName(gd::String name_) { name = name_; };
/**
* \brief Return the name identifying the behavior
*/
gd::String GetName() { return name; }
/**
* \brief Return the name identifying the type of the behavior
*/
@@ -65,10 +45,9 @@ class GD_CORE_API BehaviorsSharedData {
/**
* \brief Change name identifying the type of the behavior.
*/
virtual void SetTypeName(const gd::String& type_) { type = type_; };
void SetTypeName(const gd::String& type_) { type = type_; };
#if defined(GD_IDE_ONLY)
/**
* \brief Called when the IDE wants to know about the properties of the shared
data.
@@ -85,6 +64,7 @@ class GD_CORE_API BehaviorsSharedData {
* \see gd::PropertyDescriptor
*/
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializerElement& behaviorSharedDataContent,
gd::Project& project) const;
/**
@@ -93,37 +73,18 @@ class GD_CORE_API BehaviorsSharedData {
* \return false if the new value cannot be set
* \see gd::InitialInstance
*/
virtual bool UpdateProperty(const gd::String& name,
virtual bool UpdateProperty(gd::SerializerElement& behaviorSharedDataContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) {
return false;
};
/**
* \brief Serialize behaviors shared data.
*/
virtual void SerializeTo(SerializerElement& element) const {};
#endif
/**
* \brief Unserialize the behaviors shared data.
*/
virtual void UnserializeFrom(const SerializerElement& element){};
// TODO : GD C++ Platform specific code :
/**
* Create Runtime equivalent of the shared datas.
* Derived class have to redefine this so as to create an appropriate
* object containing runtime shared datas.
*/
virtual std::shared_ptr<BehaviorsRuntimeSharedData>
CreateRuntimeSharedDatas() {
return std::shared_ptr<BehaviorsRuntimeSharedData>();
}
virtual void InitializeContent(
gd::SerializerElement& behaviorSharedDataContent){};
private:
gd::String name; ///< A layout can have some behaviors with the same type,
///< but with different names.
gd::String type; ///< The type indicate of which type is the behavior.
};

View File

@@ -1 +0,0 @@
#include "ChangesNotifier.h"

View File

@@ -1,351 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_CHANGESNOTIFIER_H
#define GDCORE_CHANGESNOTIFIER_H
#include <iostream>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Project;
}
namespace gd {
class Layout;
}
namespace gd {
class ExternalLayout;
}
namespace gd {
class Object;
}
namespace gd {
class Behavior;
}
namespace gd {
class ExternalEvents;
}
namespace gd {
/**
* \brief Allows implementations to do specific work when some changes have been
* made in the IDE.
*
* For example, the C++ Platform triggers events recompilation when some changes
* are made.
*
* \ingroup IDE
*/
class ChangesNotifier {
public:
ChangesNotifier(){};
virtual ~ChangesNotifier(){};
/** \name Layouts
* Members functions called by the IDE so as to notify changes have been made
*/
///@{
/**
* \brief Called when a layout was added to a project
* \param project Related project
* \param layout Layout
*/
virtual void OnLayoutAdded(gd::Project& project, gd::Layout& layout) const {};
/**
* \brief Called when a layout was renamed
* \param project Related project
* \param layout Layout
* \param oldName Old name of the layout
*/
virtual void OnLayoutRenamed(gd::Project& project,
gd::Layout& layout,
const gd::String& oldName) const {};
/**
* \brief Called when a layout was removed from a project
* \param project Related project
* \param deletedLayout Name of the removed layout
*/
virtual void OnLayoutDeleted(gd::Project& project,
const gd::String deletedLayout) const {};
/**
* \brief Called when (layout or global) variables were modified
* \param project Related project
* \param layout Layout owning the variables, if applicable
*/
virtual void OnVariablesModified(gd::Project& project,
gd::Layout* layout = NULL) const {};
///@}
/** \name External Layouts
* Members functions called by the IDE so as to notify changes have been made
*/
///@{
/**
* \brief Called when an external layout was added to a project
* \param project Related project
* \param layout External layout
*/
virtual void OnExternalLayoutAdded(gd::Project& project,
gd::ExternalLayout& layout) const {};
/**
* \brief Called when an external layout was renamed
* \param project Related project
* \param layout External layout
* \param oldName Old name of the external layout
*/
virtual void OnExternalLayoutRenamed(gd::Project& project,
gd::ExternalLayout& layout,
const gd::String& oldName) const {};
/**
* \brief Called when an external layout was removed from a project
* \param project Related project
* \param deletedLayout Name of the removed external layout
*/
virtual void OnExternalLayoutDeleted(gd::Project& project,
const gd::String deletedLayout) const {};
///@}
/** \name External events
* Members functions called by the IDE so as to notify changes have been made
*/
///@{
/**
* \brief Called when external events were added to a project
* \param project Related project
* \param events External events
*/
virtual void OnExternalEventsAdded(gd::Project& project,
gd::ExternalEvents& events) const {};
/**
* \brief Called when external events were renamed
* \param project Related project
* \param events External events
* \param oldName Old name of the external events
*/
virtual void OnExternalEventsRenamed(gd::Project& project,
gd::ExternalEvents& events,
const gd::String& oldName) const {};
/**
* \brief Called when external events were removed from a project
* \param project Related project
* \param deletedLayout Name of the removed external events
*/
virtual void OnExternalEventsDeleted(gd::Project& project,
const gd::String deletedLayout) const {};
///@}
/** \name Events
* Members functions called by the IDE so as to notify changes have been made
*/
///@{
/**
* \brief Called when the events of a layout have been modified.
* \param project Related project
* \param layout Layout
* \param indirectChange true if the changes have been made "indirectly" by
* modifying for example some external events used by a layout \param
* sourceOfTheIndirectChange if indirectChange == true, contains the name of
* the external events which trigger the change.
*/
virtual void OnEventsModified(
gd::Project& project,
gd::Layout& layout,
bool indirectChange = false,
gd::String sourceOfTheIndirectChange = "") const {};
/**
* \brief Called when some external events have been modified.
* \param project Related project
* \param events External events
* \param indirectChange true if the changes have been made "indirectly" by
* modifying for example some external events used by a layout \param
* sourceOfTheIndirectChange if indirectChange == true, contains the name of
* the external events which trigger the change.
*/
virtual void OnEventsModified(
gd::Project& project,
gd::ExternalEvents& events,
bool indirectChange = false,
gd::String sourceOfTheIndirectChange = "") const {};
///@}
/** \name Objects and behaviors notifications
* Members functions called by the IDE so as to notify changes have been made
*/
///@{
/**
* \brief Called when an object has been edited
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Object
*/
virtual void OnObjectEdited(gd::Project& project,
gd::Layout* layout,
gd::Object& object) const {};
/**
* \brief Called when an object has been edited
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Object
*/
virtual void OnObjectAdded(gd::Project& project,
gd::Layout* layout,
gd::Object& object) const {};
/**
* \brief Called when an object has been renamed
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Object
* \param oldName Object old name
*/
virtual void OnObjectRenamed(gd::Project& project,
gd::Layout* layout,
gd::Object& object,
const gd::String& oldName) const {};
/**
* \brief Called when one or more objects have been deleted
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param objectName The name of the object removed
*/
virtual void OnObjectsDeleted(
gd::Project& project,
gd::Layout* layout,
const std::vector<gd::String>& deletedObjects) const {};
/**
* \brief Called when an object's variables have been changed
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Object
*/
virtual void OnObjectVariablesChanged(gd::Project& project,
gd::Layout* layout,
gd::Object& object) const {};
/**
* \brief Called when a behavior have been edited
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Related object
* \param behavior Behavior
*/
virtual void OnBehaviorEdited(gd::Project& project,
gd::Layout* layout,
gd::Object& object,
gd::Behavior& behavior) const {};
/**
* \brief Called when a behavior have been added
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Related object
* \param behavior Behavior
*/
virtual void OnBehaviorAdded(gd::Project& project,
gd::Layout* layout,
gd::Object& object,
gd::Behavior& behavior) const {};
/**
* \brief Called when a behavior have been renamed
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Related object
* \param behavior Behavior
* \param oldName Behavior old name
*/
virtual void OnBehaviorRenamed(gd::Project& project,
gd::Layout* layout,
gd::Object& object,
gd::Behavior& behavior,
const gd::String& oldName) const {};
/**
* \brief Called when a behavior have been deleted
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param object Related object
* \param behaviorName The name of the behavior removed
*/
virtual void OnBehaviorDeleted(gd::Project& project,
gd::Layout* layout,
gd::Object& object,
const gd::String& behaviorName) const {};
/**
* \brief Called when a group have been added
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param groupName The name of the group added
*/
virtual void OnObjectGroupAdded(gd::Project& project,
gd::Layout* layout,
const gd::String& groupName) const {};
/**
* \brief Called when a group has been edited
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param groupName The name of the group modified
*/
virtual void OnObjectGroupEdited(gd::Project& project,
gd::Layout* layout,
const gd::String& groupName) const {};
/**
* \brief Called when a group have been renamed
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param groupName The name of the group modified
* \param oldName Group's old name
*/
virtual void OnObjectGroupRenamed(gd::Project& project,
gd::Layout* layout,
const gd::String& groupName,
const gd::String& oldName) const {};
/**
* \brief Called when a group have been deleted
* \param project Related project
* \param layout Related layout ( can be NULL )
* \param groupName The name of the group removed
*/
virtual void OnObjectGroupDeleted(gd::Project& project,
gd::Layout* layout,
const gd::String& groupName) const {};
/**
* \brief Called when a resource have been added/removed/modified
* \param project Related project
* \param behaviorName The name of the resource which have been modified
*/
virtual void OnResourceModified(gd::Project& project,
const gd::String& resourceName) const {};
///@}
};
} // namespace gd
#endif // GDCORE_CHANGESNOTIFIER_H

View File

@@ -0,0 +1,43 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "EventsBasedBehavior.h"
#include "EventsFunctionsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
namespace gd {
EventsBasedBehavior::EventsBasedBehavior()
: name("MyBehavior"), fullName("My Behavior") {}
void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
element.SetAttribute("description", description);
element.SetAttribute("name", name);
element.SetAttribute("fullName", fullName);
element.SetAttribute("objectType", objectType);
gd::SerializerElement& eventsFunctionsElement =
element.AddChild("eventsFunctions");
eventsFunctionsContainer.SerializeEventsFunctionsTo(eventsFunctionsElement);
}
void EventsBasedBehavior::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
description = element.GetStringAttribute("description");
name = element.GetStringAttribute("name");
fullName = element.GetStringAttribute("fullName");
objectType = element.GetStringAttribute("objectType");
const gd::SerializerElement& eventsFunctionsElement =
element.GetChild("eventsFunctions");
eventsFunctionsContainer.UnserializeEventsFunctionsFrom(
project, eventsFunctionsElement);
}
} // namespace gd
#endif

View File

@@ -0,0 +1,134 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef GDCORE_EVENTSBASEDBEHAVIOR_H
#define GDCORE_EVENTSBASEDBEHAVIOR_H
#include <vector>
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
namespace gd {
class SerializerElement;
class Project;
} // namespace gd
namespace gd {
/**
* \brief Represents a behavior that is implemented with events.
*
* It's the responsibility of the IDE to run the logic to transform this into a
* real behavior, by declaring an extension and running code generation.
* See `EventsFunctionsExtensionsLoader`.
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsBasedBehavior {
public:
EventsBasedBehavior();
virtual ~EventsBasedBehavior(){};
/**
* \brief Return a pointer to a new EventsBasedBehavior constructed from
* this one.
*/
EventsBasedBehavior* Clone() const { return new EventsBasedBehavior(*this); };
/**
* \brief Get the description of the behavior, that is displayed in the
* editor.
*/
const gd::String& GetDescription() const { return description; };
/**
* \brief Set the description of the behavior, to be displayed in the editor.
*/
EventsBasedBehavior& SetDescription(const gd::String& description_) {
description = description_;
return *this;
}
/**
* \brief Get the internal name of the behavior.
*/
const gd::String& GetName() const { return name; };
/**
* \brief Set the internal name of the behavior.
*/
EventsBasedBehavior& SetName(const gd::String& name_) {
name = name_;
return *this;
}
/**
* \brief Get the name of the behavior, that is displayed in the editor.
*/
const gd::String& GetFullName() const { return fullName; };
/**
* \brief Set the name of the behavior, to be displayed in the editor.
*/
EventsBasedBehavior& SetFullName(const gd::String& fullName_) {
fullName = fullName_;
return *this;
}
/**
* \brief Get the object type the behavior should be used with.
*/
const gd::String& GetObjectType() const { return objectType; };
/**
* \brief Set the object type the behavior should be used with.
*/
EventsBasedBehavior& SetObjectType(const gd::String& objectType_) {
objectType = objectType_;
return *this;
}
/**
* \brief Return a reference to the functions of the events based behavior.
*/
EventsFunctionsContainer& GetEventsFunctions() {
return eventsFunctionsContainer;
}
/**
* \brief Return a const reference to the functions of the events based
* behavior.
*/
const EventsFunctionsContainer& GetEventsFunctions() const {
return eventsFunctionsContainer;
}
/** \name Serialization
*/
///@{
/**
* \brief Serialize the EventsBasedBehavior to the specified element
*/
void SerializeTo(gd::SerializerElement& element) const;
/**
* \brief Load the EventsBasedBehavior from the specified element
*/
void UnserializeFrom(gd::Project& project,
const gd::SerializerElement& element);
///@}
private:
gd::String name;
gd::String fullName;
gd::String description;
gd::String objectType;
gd::EventsFunctionsContainer eventsFunctionsContainer;
};
} // namespace gd
#endif // GDCORE_EVENTSBASEDBEHAVIOR_H
#endif

View File

@@ -0,0 +1,144 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef GDCORE_EVENTSFUNCTIONSCONTAINER_H
#define GDCORE_EVENTSFUNCTIONSCONTAINER_H
#include <vector>
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
class SerializerElement;
}
namespace gd {
/**
* \brief Used as a base class for classes that will own events-backed
* functions.
*
* \see gd::EventsFunctionContainer
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsFunctionsContainer
: private SerializableWithNameList<gd::EventsFunction> {
public:
/** \name Events Functions management
*/
///@{
/**
* \brief Check if the function with the specified name exists.
*/
bool HasEventsFunctionNamed(const gd::String& name) const {
return Has(name);
}
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
gd::EventsFunction& GetEventsFunction(const gd::String& name) {
return Get(name);
}
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
const gd::EventsFunction& GetEventsFunction(const gd::String& name) const {
return Get(name);
}
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
gd::EventsFunction& GetEventsFunction(std::size_t index) {
return Get(index);
}
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
const gd::EventsFunction& GetEventsFunction(std::size_t index) const {
return Get(index);
}
/**
* \brief Return the number of functions.
*/
std::size_t GetEventsFunctionsCount() const { return GetCount(); }
gd::EventsFunction& InsertNewEventsFunction(const gd::String& name,
std::size_t position) {
return InsertNew(name, position);
}
gd::EventsFunction& InsertEventsFunction(const gd::EventsFunction& object,
std::size_t position) {
return Insert(object, position);
}
void RemoveEventsFunction(const gd::String& name) { return Remove(name); }
void MoveEventsFunction(std::size_t oldIndex, std::size_t newIndex) {
return Move(oldIndex, newIndex);
};
/**
* \brief Provide a raw access to the vector containing the functions.
*/
const std::vector<std::unique_ptr<gd::EventsFunction>>& GetInternalVector()
const {
return elements;
};
/**
* \brief Provide a raw access to the vector containing the functions.
*/
std::vector<std::unique_ptr<gd::EventsFunction>>& GetInternalVector() {
return elements;
};
///@}
/** \name Serialization
*/
///@{
/**
* \brief Serialize events functions.
*/
void SerializeEventsFunctionsTo(SerializerElement& element) const {
return SerializeElementsTo("eventsFunction", element);
};
/**
* \brief Unserialize the events functions.
*/
void UnserializeEventsFunctionsFrom(gd::Project& project,
const SerializerElement& element) {
return UnserializeElementsFrom("eventsFunction", project, element);
};
///@}
protected:
/**
* Initialize object using another object. Used by copy-ctor and assign-op.
* Don't forget to update me if members were changed!
*/
void Init(const gd::EventsFunctionsContainer& other) {
return SerializableWithNameList<gd::EventsFunction>::Init(other);
};
};
} // namespace gd
#endif // GDCORE_EVENTSFUNCTIONSCONTAINER_H
#endif

View File

@@ -5,10 +5,10 @@
*/
#if defined(GD_IDE_ONLY)
#include "EventsFunctionsExtension.h"
#include "EventsBasedBehavior.h"
#include "EventsFunction.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/PolymorphicClone.h"
namespace gd {
@@ -29,131 +29,45 @@ EventsFunctionsExtension& EventsFunctionsExtension::operator=(
void EventsFunctionsExtension::Init(const gd::EventsFunctionsExtension& other) {
version = other.version;
extensionNamespace = other.extensionNamespace;
shortDescription = other.shortDescription;
description = other.description;
name = other.name;
fullName = other.fullName;
eventsFunctions = gd::Clone(other.eventsFunctions);
tags = other.tags;
author = other.author;
EventsFunctionsContainer::Init(other);
eventsBasedBehaviors = other.eventsBasedBehaviors;
}
void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
element.SetAttribute("version", version);
element.SetAttribute("extensionNamespace", extensionNamespace);
element.SetAttribute("shortDescription", shortDescription);
element.SetAttribute("description", description);
element.SetAttribute("name", name);
element.SetAttribute("fullName", fullName);
element.SetAttribute("tags", tags);
element.SetAttribute("author", author);
gd::SerializerElement& eventsFunctionsElement =
element.AddChild("eventsFunctions");
eventsFunctionsElement.ConsiderAsArrayOf("eventsFunction");
for (const auto& eventsFunction : eventsFunctions) {
eventsFunction->SerializeTo(
eventsFunctionsElement.AddChild("eventsFunction"));
}
SerializeEventsFunctionsTo(element.AddChild("eventsFunctions"));
eventsBasedBehaviors.SerializeElementsTo(
"eventsBasedBehavior", element.AddChild("eventsBasedBehaviors"));
}
void EventsFunctionsExtension::UnserializeFrom(
gd::Project& project, const SerializerElement& element) {
version = element.GetStringAttribute("version");
extensionNamespace = element.GetStringAttribute("extensionNamespace");
shortDescription = element.GetStringAttribute("shortDescription");
description = element.GetStringAttribute("description");
name = element.GetStringAttribute("name");
fullName = element.GetStringAttribute("fullName");
tags = element.GetStringAttribute("tags");
author = element.GetStringAttribute("author");
const gd::SerializerElement& eventsFunctionsElement =
element.GetChild("eventsFunctions");
eventsFunctions.clear();
eventsFunctionsElement.ConsiderAsArrayOf("eventsFunction");
for (std::size_t i = 0; i < eventsFunctionsElement.GetChildrenCount(); ++i) {
gd::EventsFunction& newEventsFunction =
InsertNewEventsFunction("", GetEventsFunctionsCount());
newEventsFunction.UnserializeFrom(project,
eventsFunctionsElement.GetChild(i));
}
}
bool EventsFunctionsExtension::HasEventsFunctionNamed(
const gd::String& name) const {
return find_if(eventsFunctions.begin(),
eventsFunctions.end(),
[&name](const std::unique_ptr<gd::EventsFunction>& function) {
return function->GetName() == name;
}) != eventsFunctions.end();
}
std::size_t EventsFunctionsExtension::GetEventsFunctionsCount() const {
return eventsFunctions.size();
}
gd::EventsFunction& EventsFunctionsExtension::InsertNewEventsFunction(
const gd::String& name, std::size_t position) {
gd::EventsFunction& newEventsFunction = *(*(eventsFunctions.insert(
position < eventsFunctions.size() ? eventsFunctions.begin() + position
: eventsFunctions.end(),
std::unique_ptr<gd::EventsFunction>(new EventsFunction()))));
newEventsFunction.SetName(name);
return newEventsFunction;
}
gd::EventsFunction& EventsFunctionsExtension::InsertEventsFunction(
const gd::EventsFunction& object, std::size_t position) {
gd::EventsFunction& newEventsFunction = *(*(eventsFunctions.insert(
position < eventsFunctions.size() ? eventsFunctions.begin() + position
: eventsFunctions.end(),
std::unique_ptr<gd::EventsFunction>(new EventsFunction(object)))));
return newEventsFunction;
}
void EventsFunctionsExtension::MoveEventsFunction(std::size_t oldIndex,
std::size_t newIndex) {
if (oldIndex >= eventsFunctions.size() || newIndex >= eventsFunctions.size())
return;
std::unique_ptr<gd::EventsFunction> object =
std::move(eventsFunctions[oldIndex]);
eventsFunctions.erase(eventsFunctions.begin() + oldIndex);
eventsFunctions.insert(eventsFunctions.begin() + newIndex, std::move(object));
}
void EventsFunctionsExtension::RemoveEventsFunction(const gd::String& name) {
std::vector<std::unique_ptr<gd::EventsFunction> >::iterator object =
find_if(eventsFunctions.begin(),
eventsFunctions.end(),
[&name](const std::unique_ptr<gd::EventsFunction>& function) {
return function->GetName() == name;
});
if (object == eventsFunctions.end()) return;
eventsFunctions.erase(object);
}
gd::EventsFunction& EventsFunctionsExtension::GetEventsFunction(
const gd::String& name) {
return *(
*find_if(eventsFunctions.begin(),
eventsFunctions.end(),
[&name](const std::unique_ptr<gd::EventsFunction>& function) {
return function->GetName() == name;
}));
}
const gd::EventsFunction& EventsFunctionsExtension::GetEventsFunction(
const gd::String& name) const {
return *(
*find_if(eventsFunctions.begin(),
eventsFunctions.end(),
[&name](const std::unique_ptr<gd::EventsFunction>& function) {
return function->GetName() == name;
}));
}
gd::EventsFunction& EventsFunctionsExtension::GetEventsFunction(std::size_t index) {
return *eventsFunctions[index];
}
const gd::EventsFunction& EventsFunctionsExtension::GetEventsFunction(std::size_t index) const {
return *eventsFunctions[index];
UnserializeEventsFunctionsFrom(project, element.GetChild("eventsFunctions"));
eventsBasedBehaviors.UnserializeElementsFrom(
"eventsBasedBehavior", project, element.GetChild("eventsBasedBehaviors"));
}
} // namespace gd

View File

@@ -8,8 +8,10 @@
#define GDCORE_EVENTSFUNCTIONEXTENSION_H
#include <vector>
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/String.h"
#include "GDCore/Tools/SerializableWithNameList.h"
namespace gd {
class SerializerElement;
class Project;
@@ -18,16 +20,18 @@ class Project;
namespace gd {
/**
* \brief Hold a list of Events Functions (gd::EventsFunction).
* \brief Hold a list of Events Functions (gd::EventsFunction) and Events Based
* Behaviors.
*
* Events functions can be generated as stand-alone functions, and
* converted to actions/conditions/expressions.
* Similarly, a gd::EventsFunctionsExtension can be converted to
* an extension.
* Events behaviors can be generated to a runtime behavior, with functions
* converted to behavior actions/conditions/expressions. Similarly, a
* gd::EventsFunctionsExtension can be converted to an extension.
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API EventsFunctionsExtension {
class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
public:
EventsFunctionsExtension();
EventsFunctionsExtension(const EventsFunctionsExtension&);
@@ -54,6 +58,12 @@ class GD_CORE_API EventsFunctionsExtension {
return *this;
}
const gd::String& GetShortDescription() const { return shortDescription; };
EventsFunctionsExtension& SetShortDescription(const gd::String& shortDescription_) {
shortDescription = shortDescription_;
return *this;
}
const gd::String& GetDescription() const { return description; };
EventsFunctionsExtension& SetDescription(const gd::String& description_) {
description = description_;
@@ -72,69 +82,32 @@ class GD_CORE_API EventsFunctionsExtension {
return *this;
}
/**
* \brief Check if the function with the specified name exists.
*/
bool HasEventsFunctionNamed(const gd::String& name) const;
const gd::String& GetTags() const { return tags; };
EventsFunctionsExtension& SetTags(const gd::String& tags_) {
tags = tags_;
return *this;
}
const gd::String& GetAuthor() const { return author; };
EventsFunctionsExtension& SetAuthor(const gd::String& author_) {
author = author_;
return *this;
}
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
* \brief Return a reference to the list of the events based behaviors.
*/
gd::EventsFunction& GetEventsFunction(const gd::String& name);
SerializableWithNameList<EventsBasedBehavior>& GetEventsBasedBehaviors() {
return eventsBasedBehaviors;
}
/**
* \brief Get the function with the specified name.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
* \brief Return a const reference to the list of the events based behaviors.
*/
const gd::EventsFunction& GetEventsFunction(const gd::String& name) const;
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
gd::EventsFunction& GetEventsFunction(std::size_t index);
/**
* \brief Get the function at the specified index in the list.
*
* \warning Trying to access to a not existing function will result in
* undefined behavior.
*/
const gd::EventsFunction& GetEventsFunction(std::size_t index) const;
/**
* \brief Return the number of functions.
*/
std::size_t GetEventsFunctionsCount() const;
gd::EventsFunction& InsertNewEventsFunction(const gd::String& name,
std::size_t position);
gd::EventsFunction& InsertEventsFunction(const gd::EventsFunction& object,
std::size_t position);
void RemoveEventsFunction(const gd::String& name);
void MoveEventsFunction(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Provide a raw access to the vector containing the functions.
*/
const std::vector<std::unique_ptr<gd::EventsFunction>>& GetEventsFunctions()
const SerializableWithNameList<EventsBasedBehavior>& GetEventsBasedBehaviors()
const {
return eventsFunctions;
};
/**
* \brief Provide a raw access to the vector containing the functions.
*/
std::vector<std::unique_ptr<gd::EventsFunction>>& GetEventsFunctions() {
return eventsFunctions;
};
return eventsBasedBehaviors;
}
/** \name Serialization
*/
@@ -160,10 +133,13 @@ class GD_CORE_API EventsFunctionsExtension {
gd::String version;
gd::String extensionNamespace;
gd::String shortDescription;
gd::String description;
gd::String name;
gd::String fullName;
std::vector<std::unique_ptr<gd::EventsFunction>> eventsFunctions;
gd::String tags;
gd::String author;
SerializableWithNameList<EventsBasedBehavior> eventsBasedBehaviors;
};
} // namespace gd

View File

@@ -12,6 +12,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorContent.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Project/InitialInstance.h"
#include "GDCore/Project/Layer.h"
@@ -28,7 +29,7 @@ using namespace std;
namespace gd {
gd::Layer Layout::badLayer;
gd::BehaviorsSharedData Layout::badBehaviorSharedData;
gd::BehaviorContent Layout::badBehaviorContent("", "");
Layout::Layout(const Layout& other) { Init(other); }
@@ -52,9 +53,7 @@ Layout::Layout()
disableInputWhenNotFocused(true)
#if defined(GD_IDE_ONLY)
,
profiler(NULL),
refreshNeeded(false),
compilationNeeded(true)
profiler(NULL)
#endif
{
gd::Layer layer;
@@ -68,45 +67,36 @@ void Layout::SetName(const gd::String& name_) {
};
bool Layout::HasBehaviorSharedData(const gd::String& behaviorName) {
return behaviorsInitialSharedDatas.find(behaviorName) !=
behaviorsInitialSharedDatas.end();
return behaviorsSharedData.find(behaviorName) != behaviorsSharedData.end();
}
std::vector<gd::String> Layout::GetAllBehaviorSharedDataNames() const {
std::vector<gd::String> allNames;
for (auto& it : behaviorsInitialSharedDatas) allNames.push_back(it.first);
for (auto& it : behaviorsSharedData) allNames.push_back(it.first);
return allNames;
}
const gd::BehaviorsSharedData& Layout::GetBehaviorSharedData(
const gd::BehaviorContent& Layout::GetBehaviorSharedData(
const gd::String& behaviorName) const {
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end()) return *it->second;
auto it = behaviorsSharedData.find(behaviorName);
if (it != behaviorsSharedData.end()) return *it->second;
return badBehaviorSharedData;
return badBehaviorContent;
}
gd::BehaviorsSharedData& Layout::GetBehaviorSharedData(
gd::BehaviorContent& Layout::GetBehaviorSharedData(
const gd::String& behaviorName) {
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end()) return *it->second;
auto it = behaviorsSharedData.find(behaviorName);
if (it != behaviorsSharedData.end()) return *it->second;
return badBehaviorSharedData;
return badBehaviorContent;
}
std::shared_ptr<gd::BehaviorsSharedData> Layout::GetBehaviorSharedDataSmartPtr(
const gd::String& behaviorName) {
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end()) return it->second;
return std::shared_ptr<gd::BehaviorsSharedData>();
}
const std::map<gd::String, std::shared_ptr<gd::BehaviorsSharedData> >&
const std::map<gd::String, std::unique_ptr<gd::BehaviorContent> >&
Layout::GetAllBehaviorSharedData() const {
return behaviorsInitialSharedDatas;
return behaviorsSharedData;
}
gd::Layer& Layout::GetLayer(const gd::String& name) {
@@ -206,20 +196,20 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
std::vector<gd::String> objectBehaviors =
initialObjects[i]->GetAllBehaviorNames();
for (unsigned int j = 0; j < objectBehaviors.size(); ++j) {
gd::Behavior& behavior =
auto& behaviorContent =
initialObjects[i]->GetBehavior(objectBehaviors[j]);
allBehaviorsTypes.push_back(behavior.GetTypeName());
allBehaviorsNames.push_back(behavior.GetName());
allBehaviorsTypes.push_back(behaviorContent.GetTypeName());
allBehaviorsNames.push_back(behaviorContent.GetName());
}
}
for (std::size_t i = 0; i < project.GetObjectsCount(); ++i) {
std::vector<gd::String> objectBehaviors =
project.GetObject(i).GetAllBehaviorNames();
for (std::size_t j = 0; j < objectBehaviors.size(); ++j) {
gd::Behavior& behavior =
auto& behaviorContent =
project.GetObject(i).GetBehavior(objectBehaviors[j]);
allBehaviorsTypes.push_back(behavior.GetTypeName());
allBehaviorsNames.push_back(behavior.GetName());
allBehaviorsTypes.push_back(behaviorContent.GetTypeName());
allBehaviorsNames.push_back(behaviorContent.GetName());
}
}
@@ -227,14 +217,16 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
for (std::size_t i = 0;
i < allBehaviorsTypes.size() && i < allBehaviorsNames.size();
++i) {
if (behaviorsInitialSharedDatas.find(allBehaviorsNames[i]) ==
behaviorsInitialSharedDatas.end()) {
std::shared_ptr<gd::BehaviorsSharedData> behaviorsSharedDatas =
project.CreateBehaviorSharedDatas(allBehaviorsTypes[i]);
if (behaviorsSharedDatas) {
behaviorsSharedDatas->SetName(allBehaviorsNames[i]);
behaviorsInitialSharedDatas[behaviorsSharedDatas->GetName()] =
behaviorsSharedDatas;
const gd::String& name = allBehaviorsNames[i];
if (behaviorsSharedData.find(name) == behaviorsSharedData.end()) {
gd::BehaviorsSharedData* behaviorSharedData =
project.GetBehaviorSharedDatas(allBehaviorsTypes[i]);
if (behaviorSharedData) {
auto behaviorContent =
gd::make_unique<gd::BehaviorContent>(name, allBehaviorsTypes[i]);
behaviorSharedData->InitializeContent(behaviorContent->GetContent());
behaviorsSharedData[name] = std::move(behaviorContent);
}
}
}
@@ -242,12 +234,8 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
// Remove useless shared data:
// First construct the list of existing shared data.
std::vector<gd::String> allSharedData;
for (std::map<gd::String,
std::shared_ptr<gd::BehaviorsSharedData> >::const_iterator it =
behaviorsInitialSharedDatas.begin();
it != behaviorsInitialSharedDatas.end();
++it) {
allSharedData.push_back(it->first);
for (const auto& it : behaviorsSharedData) {
allSharedData.push_back(it.first);
}
// Then delete shared data not linked to a behavior
@@ -255,7 +243,7 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
if (std::find(allBehaviorsNames.begin(),
allBehaviorsNames.end(),
allSharedData[i]) == allBehaviorsNames.end())
behaviorsInitialSharedDatas.erase(allSharedData[i]);
behaviorsSharedData.erase(allSharedData[i]);
}
}
@@ -290,17 +278,16 @@ void Layout::SerializeTo(SerializerElement& element) const {
SerializerElement& behaviorDatasElement =
element.AddChild("behaviorsSharedData");
behaviorDatasElement.ConsiderAsArrayOf("behaviorSharedData");
for (std::map<gd::String,
std::shared_ptr<gd::BehaviorsSharedData> >::const_iterator it =
behaviorsInitialSharedDatas.begin();
it != behaviorsInitialSharedDatas.end();
++it) {
for (const auto& it : behaviorsSharedData) {
SerializerElement& dataElement =
behaviorDatasElement.AddChild("behaviorSharedData");
dataElement.SetAttribute("type", it->second->GetTypeName());
dataElement.SetAttribute("name", it->second->GetName());
it->second->SerializeTo(dataElement);
it.second->SerializeTo(dataElement);
dataElement.RemoveChild("type"); // The content can contain type or name
// properties, remove them.
dataElement.RemoveChild("name");
dataElement.SetAttribute("type", it.second->GetTypeName());
dataElement.SetAttribute("name", it.second->GetName());
}
}
@@ -366,21 +353,27 @@ void Layout::UnserializeFrom(gd::Project& project,
element.GetChild("behaviorsSharedData", 0, deprecatedTag1);
behaviorsDataElement.ConsiderAsArrayOf("behaviorSharedData", deprecatedTag2);
for (unsigned int i = 0; i < behaviorsDataElement.GetChildrenCount(); ++i) {
SerializerElement& behaviorDataElement = behaviorsDataElement.GetChild(i);
SerializerElement& sharedDataElement = behaviorsDataElement.GetChild(i);
gd::String type =
behaviorDataElement.GetStringAttribute("type", "", "Type")
sharedDataElement.GetStringAttribute("type", "", "Type")
.FindAndReplace("Automatism",
"Behavior"); // Compatibility with GD <= 4
gd::String name = sharedDataElement.GetStringAttribute("name", "", "Name");
std::shared_ptr<gd::BehaviorsSharedData> sharedData =
project.CreateBehaviorSharedDatas(type);
if (sharedData != std::shared_ptr<gd::BehaviorsSharedData>()) {
sharedData->SetName(
behaviorDataElement.GetStringAttribute("name", "", "Name"));
sharedData->UnserializeFrom(behaviorDataElement);
behaviorsInitialSharedDatas[sharedData->GetName()] = sharedData;
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
// Compatibility with GD <= 4.0.98
// If there is only one child called "content" (in addition to "type" and
// "name"), it's the content of a JavaScript behavior. Move the content
// out of the "content" object (to put it directly at the root of the
// behavior shared data element).
if (sharedDataElement.HasChild("content")) {
behaviorContent->UnserializeFrom(sharedDataElement.GetChild("content"));
}
// end of compatibility code
else {
behaviorContent->UnserializeFrom(sharedDataElement);
}
behaviorsSharedData[name] = std::move(behaviorContent);
}
}
@@ -402,13 +395,10 @@ void Layout::Init(const Layout& other) {
initialObjects = gd::Clone(other.initialObjects);
behaviorsInitialSharedDatas.clear();
for (std::map<gd::String,
std::shared_ptr<gd::BehaviorsSharedData> >::const_iterator it =
other.behaviorsInitialSharedDatas.begin();
it != other.behaviorsInitialSharedDatas.end();
++it) {
behaviorsInitialSharedDatas[it->first] = it->second->Clone();
behaviorsSharedData.clear();
for (const auto& it : other.behaviorsSharedData) {
behaviorsSharedData[it.first] =
std::unique_ptr<gd::BehaviorContent>(it.second->Clone());
}
#if defined(GD_IDE_ONLY)
@@ -416,10 +406,7 @@ void Layout::Init(const Layout& other) {
associatedSettings = other.associatedSettings;
objectGroups = other.objectGroups;
compiledEventsFile = other.compiledEventsFile;
profiler = other.profiler;
SetCompilationNeeded(); // Force recompilation/refreshing
SetRefreshNeeded();
#endif
}
@@ -527,10 +514,11 @@ gd::String GD_CORE_API GetTypeOfBehavior(const gd::ObjectsContainer& project,
return "";
}
vector<gd::String> GD_CORE_API GetBehaviorsOfObject(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::String name,
bool searchInGroups) {
vector<gd::String> GD_CORE_API
GetBehaviorsOfObject(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::String name,
bool searchInGroups) {
bool behaviorsAlreadyInserted = false;
vector<gd::String> behaviors;

View File

@@ -11,10 +11,10 @@
#include <vector>
#include "GDCore/Events/EventsList.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/Layer.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#if defined(GD_IDE_ONLY)
@@ -24,8 +24,9 @@ namespace gd {
class BaseEvent;
class Object;
class Project;
class BehaviorContent;
class InitialInstancesContainer;
}
} // namespace gd
class TiXmlElement;
class BaseProfiler;
#undef GetObject // Disable an annoying macro
@@ -273,27 +274,20 @@ class GD_CORE_API Layout : public ObjectsContainer {
/**
* \brief Get the shared data stored for a behavior
*/
const gd::BehaviorsSharedData& GetBehaviorSharedData(
const gd::BehaviorContent& GetBehaviorSharedData(
const gd::String& behaviorName) const;
/**
* \brief Get the shared data stored for a behavior
*/
gd::BehaviorsSharedData& GetBehaviorSharedData(
const gd::String& behaviorName);
gd::BehaviorContent& GetBehaviorSharedData(const gd::String& behaviorName);
/**
* \brief Get a map of all shared data stored for behaviors
*/
const std::map<gd::String, std::shared_ptr<gd::BehaviorsSharedData> >&
const std::map<gd::String, std::unique_ptr<gd::BehaviorContent>>&
GetAllBehaviorSharedData() const;
/**
* \brief Get the (smart pointer to the) shared data stored for a behavior.
*/
std::shared_ptr<gd::BehaviorsSharedData> GetBehaviorSharedDataSmartPtr(
const gd::String& behaviorName);
#if defined(GD_IDE_ONLY)
/**
* Return the settings associated to the layout.
@@ -402,71 +396,6 @@ class GD_CORE_API Layout : public ObjectsContainer {
// TODO: GD C++ Platform specific code below
#if defined(GD_IDE_ONLY)
/** \name Events compilation and bitcode management
* Members functions related to managing the compilation of events and the
* resulting bitcodes.
*
* \see CodeCompilationHelpers
*/
///@{
/**
* Set that the events need to be compiled.
* \note The compilation is not launched at this time. It will for example
* occur when triggered by SceneEditorCanvas or if you manually add a task to
* the code compiler ( see CodeCompilationHelpers ).
*
* \see ChangesNotifier
* \see CodeCompilationHelpers
*/
void SetCompilationNeeded() { compilationNeeded = true; };
/**
* Must be called when compilation of events is over and so events are not
* considered "modified" anymore.
*/
void SetCompilationNotNeeded() { compilationNeeded = false; };
/**
* Return true if a compilation is needed.
* This method is usually called by SceneEditorCanvas when (re)loading a scene
* so as to know if it should launch compilation.
*/
bool CompilationNeeded() { return compilationNeeded; };
const gd::String& GetCompiledEventsFile() const { return compiledEventsFile; }
void SetCompiledEventsFile(const gd::String& file) {
compiledEventsFile = file;
}
///@}
/** \name Changes notification
* Members functions used to notify the editor ( mainly SceneEditorCanvas )
* that changes have been made and that refreshing should be made.
*/
///@{
/**
* Return true if important changes have been made and so the editors must
* reload the scene. ( Important changes may refers to objects modified,
* properties updated, objects groups modified, variables modified )
*/
bool RefreshNeeded() const { return refreshNeeded; }
/**
* Must be called when some important changes have been made and so the
* editors must reload the scene \see Scene::RefreshNeeded
*/
void SetRefreshNeeded() { refreshNeeded = true; }
/**
* Must be called when the editor ( i.e: SceneEditorCanvas ) managing the
* scene has reloaded it.
*/
void SetRefreshNotNeeded() { refreshNeeded = false; }
///@}
/**
* Get the profiler associated with the scene. Can be NULL.
*/
@@ -488,8 +417,8 @@ class GD_CORE_API Layout : public ObjectsContainer {
gd::VariablesContainer variables; ///< Variables list
gd::InitialInstancesContainer initialInstances; ///< Initial instances
std::vector<gd::Layer> initialLayers; ///< Initial layers
std::map<gd::String, std::shared_ptr<gd::BehaviorsSharedData> >
behaviorsInitialSharedDatas; ///< Initial shared datas of behaviors
std::map<gd::String, std::unique_ptr<gd::BehaviorContent>>
behaviorsSharedData; ///< Initial shared datas of behaviors
bool stopSoundsOnStartup; ///< True to make the scene stop all sounds at
///< startup.
bool standardSortMethod; ///< True to sort objects using standard sort.
@@ -501,10 +430,10 @@ class GD_CORE_API Layout : public ObjectsContainer {
/// focus.
static gd::Layer badLayer; ///< Null object, returned when GetLayer can not
///< find an appropriate layer.
static gd::BehaviorsSharedData
badBehaviorSharedData; ///< Null object, returned when
///< GetBehaviorSharedData can not find the
///< specified behavior shared data.
static gd::BehaviorContent
badBehaviorContent; ///< Null object, returned when
///< GetBehaviorSharedData can not find the
///< specified behavior shared data.
#if defined(GD_IDE_ONLY)
EventsList events; ///< Scene events
gd::LayoutEditorCanvasOptions associatedSettings;
@@ -513,14 +442,6 @@ class GD_CORE_API Layout : public ObjectsContainer {
// TODO: GD C++ Platform specific code below
#if defined(GD_IDE_ONLY)
BaseProfiler* profiler; ///< Pointer to the profiler. Can be NULL.
bool refreshNeeded; ///< If set to true, the IDE will reload the scene(
///< thanks to SceneEditorCanvas notably which check this
///< flag when the scene is being edited )
bool
compilationNeeded; ///< If set to true, the IDE will recompile the events
///< ( thanks to SceneEditorCanvas notably which check
///< this flag when the scene is being edited )
gd::String compiledEventsFile;
#endif
/**

View File

@@ -25,18 +25,15 @@ void Object::Init(const gd::Object& object) {
objectVariables = object.objectVariables;
behaviors.clear();
for (auto it = object.behaviors.cbegin(); it != object.behaviors.cend(); ++it)
behaviors[it->first] = std::unique_ptr<Behavior>(it->second->Clone());
for (auto& it : object.behaviors) {
behaviors[it.first] = gd::make_unique<gd::BehaviorContent>(*it.second);
}
}
std::vector<gd::String> Object::GetAllBehaviorNames() const {
std::vector<gd::String> allNameIdentifiers;
for (std::map<gd::String, std::unique_ptr<gd::Behavior> >::const_iterator it =
behaviors.begin();
it != behaviors.end();
++it)
allNameIdentifiers.push_back(it->first);
for (auto& it : behaviors) allNameIdentifiers.push_back(it.first);
return allNameIdentifiers;
}
@@ -48,7 +45,8 @@ bool Object::RenameBehavior(const gd::String& name, const gd::String& newName) {
behaviors.find(newName) != behaviors.end())
return false;
std::unique_ptr<Behavior> aut = std::move(behaviors.find(name)->second);
std::unique_ptr<BehaviorContent> aut =
std::move(behaviors.find(name)->second);
behaviors.erase(name);
behaviors[newName] = std::move(aut);
behaviors[newName]->SetName(newName);
@@ -56,11 +54,11 @@ bool Object::RenameBehavior(const gd::String& name, const gd::String& newName) {
return true;
}
gd::Behavior& Object::GetBehavior(const gd::String& name) {
gd::BehaviorContent& Object::GetBehavior(const gd::String& name) {
return *behaviors.find(name)->second;
}
const gd::Behavior& Object::GetBehavior(const gd::String& name) const {
const gd::BehaviorContent& Object::GetBehavior(const gd::String& name) const {
return *behaviors.find(name)->second;
}
@@ -68,13 +66,13 @@ bool Object::HasBehaviorNamed(const gd::String& name) const {
return behaviors.find(name) != behaviors.end();
}
bool Object::AddBehavior(Behavior* behavior) {
if (behavior && !HasBehaviorNamed(behavior->GetName())) {
behaviors[behavior->GetName()] = std::unique_ptr<Behavior>(behavior);
return true;
}
return false;
gd::BehaviorContent& Object::AddBehavior(
const gd::BehaviorContent& behaviorContent) {
const gd::String& behaviorName = behaviorContent.GetName();
auto newBehaviorContent =
gd::make_unique<gd::BehaviorContent>(behaviorContent);
behaviors[behaviorName] = std::move(newBehaviorContent);
return *behaviors[behaviorName];
}
#if defined(GD_IDE_ONLY)
@@ -84,15 +82,15 @@ std::map<gd::String, gd::PropertyDescriptor> Object::GetProperties(
return nothing;
}
gd::Behavior* Object::AddNewBehavior(gd::Project& project,
const gd::String& type,
const gd::String& name) {
std::unique_ptr<gd::Behavior> behavior =
project.GetCurrentPlatform().CreateBehavior(type);
gd::BehaviorContent* Object::AddNewBehavior(gd::Project& project,
const gd::String& type,
const gd::String& name) {
gd::Behavior* behavior = project.GetCurrentPlatform().GetBehavior(type);
if (behavior) {
behavior->SetName(name);
behaviors[name] = std::move(behavior);
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
behavior->InitializeContent(behaviorContent->GetContent());
behaviors[name] = std::move(behaviorContent);
return behaviors[name].get();
} else {
return nullptr;
@@ -110,7 +108,9 @@ Object::GetInitialInstanceProperties(const gd::InitialInstance& instance,
void Object::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
// Name and type are already loaded.
type = element.GetStringAttribute("type");
name = element.GetStringAttribute("name", name, "nom");
objectVariables.UnserializeFrom(
element.GetChild("variables", 0, "Variables"));
behaviors.clear();
@@ -120,19 +120,13 @@ void Object::UnserializeFrom(gd::Project& project,
for (std::size_t i = 0; i < element.GetChildrenCount("Automatism"); ++i) {
SerializerElement& behaviorElement = element.GetChild("Automatism", i);
gd::String autoType =
behaviorElement.GetStringAttribute("type", "", "Type")
.FindAndReplace("Automatism", "Behavior");
gd::String autoName =
behaviorElement.GetStringAttribute("name", "", "Name");
gd::String type = behaviorElement.GetStringAttribute("type", "", "Type")
.FindAndReplace("Automatism", "Behavior");
gd::String name = behaviorElement.GetStringAttribute("name", "", "Name");
std::unique_ptr<Behavior> behavior = project.CreateBehavior(autoType);
if (behavior) {
behavior->SetName(autoName);
behavior->UnserializeFrom(behaviorElement);
behaviors[autoName] = std::move(behavior);
} else
std::cout << "WARNING: Unknown behavior " << autoType << std::endl;
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
behaviorContent->UnserializeFrom(behaviorElement);
behaviors[name] = std::move(behaviorContent);
}
}
// End of compatibility code
@@ -143,18 +137,36 @@ void Object::UnserializeFrom(gd::Project& project,
for (std::size_t i = 0; i < behaviorsElement.GetChildrenCount(); ++i) {
SerializerElement& behaviorElement = behaviorsElement.GetChild(i);
gd::String autoType =
gd::String type =
behaviorElement.GetStringAttribute("type").FindAndReplace(
"Automatism", "Behavior"); // Compatibility with GD <= 4
gd::String autoName = behaviorElement.GetStringAttribute("name");
gd::String name = behaviorElement.GetStringAttribute("name");
std::unique_ptr<Behavior> behavior = project.CreateBehavior(autoType);
if (behavior) {
behavior->SetName(autoName);
behavior->UnserializeFrom(behaviorElement);
behaviors[autoName] = std::move(behavior);
} else
std::cout << "WARNING: Unknown behavior " << autoType << std::endl;
auto behaviorContent = gd::make_unique<gd::BehaviorContent>(name, type);
// Compatibility with GD <= 4.0.98
// If there is only one child called "content" (in addition to "type" and
// "name"), it's the content of a JavaScript behavior. Move the content
// out of the "content" object (to put it directly at the root of the
// behavior element).
if (behaviorElement.HasChild("content") &&
behaviorElement.GetAllChildren().size() == 3) {
SerializerElement& contentElement = behaviorElement.GetChild("content");
// Physics2 Behavior was using "type" for the type of the body. The name
// conflicts with the behavior "type". Rename it.
if (contentElement.HasChild("type")) {
contentElement.AddChild("bodyType")
.SetValue(contentElement.GetChild("type").GetStringValue());
contentElement.RemoveChild("type");
}
behaviorContent->UnserializeFrom(contentElement);
}
// end of compatibility code
else {
behaviorContent->UnserializeFrom(behaviorElement);
}
behaviors[name] = std::move(behaviorContent);
}
}
@@ -171,13 +183,15 @@ void Object::SerializeTo(SerializerElement& element) const {
behaviorsElement.ConsiderAsArrayOf("behavior");
std::vector<gd::String> allBehaviors = GetAllBehaviorNames();
for (std::size_t i = 0; i < allBehaviors.size(); ++i) {
const gd::BehaviorContent& behaviorContent = GetBehavior(allBehaviors[i]);
SerializerElement& behaviorElement = behaviorsElement.AddChild("behavior");
behaviorElement.SetAttribute("type",
GetBehavior(allBehaviors[i]).GetTypeName());
behaviorElement.SetAttribute("name",
GetBehavior(allBehaviors[i]).GetName());
GetBehavior(allBehaviors[i]).SerializeTo(behaviorElement);
behaviorContent.SerializeTo(behaviorElement);
behaviorElement.RemoveChild("type"); // The content can contain type or
// name properties, remove them.
behaviorElement.RemoveChild("name");
behaviorElement.SetAttribute("type", behaviorContent.GetTypeName());
behaviorElement.SetAttribute("name", behaviorContent.GetName());
}
DoSerializeTo(element);

View File

@@ -9,7 +9,7 @@
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorContent.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/MakeUnique.h"
@@ -177,7 +177,7 @@ class GD_CORE_API Object {
gd::Layout& layout) {
return false;
};
///@}
///@}
#endif
/** \name Behaviors management
@@ -194,12 +194,12 @@ class GD_CORE_API Object {
/**
* \brief Return a reference to the behavior called \a name.
*/
Behavior& GetBehavior(const gd::String& name);
BehaviorContent& GetBehavior(const gd::String& name);
/**
* \brief Return a reference to the behavior called \a name.
*/
const Behavior& GetBehavior(const gd::String& name) const;
const BehaviorContent& GetBehavior(const gd::String& name) const;
/**
* \brief Return true if object has a behavior called \a name.
@@ -217,33 +217,34 @@ class GD_CORE_API Object {
*/
bool RenameBehavior(const gd::String& name, const gd::String& newName);
/**
* \brief Add the specified behavior content to the object
*
* \return A reference to the newly added behavior content.
*/
gd::BehaviorContent& AddBehavior(const gd::BehaviorContent& behavior);
#if defined(GD_IDE_ONLY)
/**
* \brief Add the behavior of the specified \a type with the specified \a
* name.
*
* The project's current platform is used to create the behavior.
* The project's current platform is used to initialize the content.
*
* \return A pointer to the newly added behavior. NULL if the creation failed.
* \return A pointer to the newly added behavior content. NULL if the creation
* failed.
*/
gd::Behavior* AddNewBehavior(gd::Project& project,
const gd::String& type,
const gd::String& name);
gd::BehaviorContent* AddNewBehavior(gd::Project& project,
const gd::String& type,
const gd::String& name);
#endif
/**
* \brief Add the specified behavior to the object
* \note The object takes ownership of the behavior.
* \return true if the behavior was added, false otherwise (behavior with the
* same name already in the object)
* \brief Get a read-only access to the map containing the behaviors with
* their properties.
*/
bool AddBehavior(gd::Behavior* behavior);
/**
* \brief Get a read-only access to the map containing the behaviors.
*/
const std::map<gd::String, std::unique_ptr<gd::Behavior>>& GetAllBehaviors()
const {
const std::map<gd::String, std::unique_ptr<gd::BehaviorContent>>&
GetAllBehaviorContents() const {
return behaviors;
};
///@}
@@ -288,9 +289,10 @@ class GD_CORE_API Object {
gd::String name; ///< The full name of the object
gd::String type; ///< Which type is the object. ( To test if we can do
///< something reserved to some objects with it )
std::map<gd::String, std::unique_ptr<gd::Behavior>>
behaviors; ///< Contains all behaviors of the object. Behaviors are the
///< ownership of the object
std::map<gd::String, std::unique_ptr<gd::BehaviorContent>>
behaviors; ///< Contains all behaviors and their properties for the
///< object. Behavior contents are the ownership of the
///< object.
gd::VariablesContainer
objectVariables; ///< List of the variables of the object

View File

@@ -18,7 +18,6 @@
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/PlatformManager.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/ChangesNotifier.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
@@ -30,7 +29,6 @@
#include "GDCore/Project/SourceFile.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Serialization/Splitter.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Tools/Localization.h"
@@ -58,7 +56,7 @@ Project::Project()
windowWidth(800),
windowHeight(600),
maxFPS(60),
minFPS(10),
minFPS(20),
verticalSync(false),
scaleMode("linear"),
sizeOnStartupMode("adaptWidth"),
@@ -123,31 +121,31 @@ std::unique_ptr<gd::Object> Project::CreateObject(
return nullptr;
}
std::unique_ptr<gd::Behavior> Project::CreateBehavior(
const gd::String& type, const gd::String& platformName) {
gd::Behavior* Project::GetBehavior(const gd::String& type,
const gd::String& platformName) {
for (std::size_t i = 0; i < platforms.size(); ++i) {
if (!platformName.empty() && platforms[i]->GetName() != platformName)
continue;
std::unique_ptr<gd::Behavior> behavior = platforms[i]->CreateBehavior(type);
gd::Behavior* behavior = platforms[i]->GetBehavior(type);
if (behavior) return behavior;
}
return nullptr;
}
std::shared_ptr<gd::BehaviorsSharedData> Project::CreateBehaviorSharedDatas(
gd::BehaviorsSharedData* Project::GetBehaviorSharedDatas(
const gd::String& type, const gd::String& platformName) {
for (std::size_t i = 0; i < platforms.size(); ++i) {
if (!platformName.empty() && platforms[i]->GetName() != platformName)
continue;
std::shared_ptr<gd::BehaviorsSharedData> behavior =
platforms[i]->CreateBehaviorSharedDatas(type);
if (behavior) return behavior;
gd::BehaviorsSharedData* behaviorSharedData =
platforms[i]->GetBehaviorSharedDatas(type);
if (behaviorSharedData) return behaviorSharedData;
}
return std::shared_ptr<gd::BehaviorsSharedData>();
return nullptr;
}
#if defined(GD_IDE_ONLY)
@@ -979,8 +977,7 @@ void Project::ExposeResources(gd::ArbitraryResourceWorker& worker) {
// Add events functions extensions resources
for (std::size_t e = 0; e < GetEventsFunctionsExtensionsCount(); e++) {
auto& eventsFunctionsExtension = GetEventsFunctionsExtension(e);
for (auto&& eventsFunction :
eventsFunctionsExtension.GetEventsFunctions()) {
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
LaunchResourceWorkerOnEvents(*this, eventsFunction->GetEvents(), worker);
}
}
@@ -989,7 +986,6 @@ void Project::ExposeResources(gd::ArbitraryResourceWorker& worker) {
for (std::size_t j = 0; j < GetObjectsCount(); ++j) {
GetObject(j).ExposeResources(worker);
}
}
bool Project::HasSourceFile(gd::String name, gd::String language) const {

View File

@@ -8,14 +8,13 @@
#define GDCORE_PROJECT_H
#include <memory>
#include <vector>
#include "GDCore/String.h"
#include "GDCore/Project/ChangesNotifier.h"
#include "GDCore/Project/LoadingScreen.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/PlatformSpecificAssets.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
namespace gd {
class Platform;
class Layout;
@@ -38,7 +37,8 @@ class SerializerElement;
namespace gd {
/**
* \brief Base class used to represent a project of a platform
* \brief Base class representing a project (game), including all resources,
* scenes, objects, extensions...
*
* \ingroup PlatformDefinition
*/
@@ -347,35 +347,34 @@ class GD_CORE_API Project : public ObjectsContainer {
const gd::String& platformName = "");
/**
* Create a behavior of the given type.
* Get the behavior of the given type.
*
* \note A project can use more than one platform. In this case, the first
* platform supporting the behavior is used, unless \a platformName argument
* is not empty.<br> It is assumed that each platform provides an equivalent
* is not empty.
* It is assumed that each platform provides an equivalent
* behavior.
*
* \param project The project for which the object must be created.
* \param type The type of the behavior
* \param platformName The name of the platform to be used. If empty, the
* first platform supporting the object is used.
*/
std::unique_ptr<gd::Behavior> CreateBehavior(
const gd::String& type, const gd::String& platformName = "");
gd::Behavior* GetBehavior(const gd::String& type,
const gd::String& platformName = "");
/**
* Create behavior shared data of the given type.
* Get the behavior shared data of the given type.
*
* \note A project can use more than one platform. In this case, the first
* platform supporting the behavior shared data is used, unless \a
* platformName argument is not empty.<br> It is assumed that each platform
* provides equivalent behavior shared data.
* platformName argument is not empty.
* It is assumed that each platform provides equivalent behavior shared data.
*
* \param project The project for which the behavior shared data must be
* created. \param type The type of behavior shared data \param platformName
* The name of the platform to be used. If empty, the first platform
* supporting the object is used.
* \param type The type of behavior
* \param platformName The name of the platform to be used. If empty, the
* first platform supporting the object is used.
*/
std::shared_ptr<gd::BehaviorsSharedData> CreateBehaviorSharedDatas(
gd::BehaviorsSharedData* GetBehaviorSharedDatas(
const gd::String& type, const gd::String& platformName = "");
#if defined(GD_IDE_ONLY)
@@ -503,7 +502,7 @@ class GD_CORE_API Project : public ObjectsContainer {
* Get the minor version of GDevelop used to save the project.
*/
unsigned int GetLastSaveGDMinorVersion() { return gdMinorVersion; };
/**
* Get the minor version of GDevelop used to save the project.
*/
@@ -735,14 +734,15 @@ class GD_CORE_API Project : public ObjectsContainer {
const gd::String& name, std::size_t position);
/**
* \brief Adds a new events functions extension constructed from the layout
* passed as parameter.
* \brief Adds an events functions extension to the list.
*
* \note No pointer or reference must be kept on the extension passed as
* parameter.
* \note A copy of it is stored in the project (and returned).
* \return The newly stored events functions extension (a copy of the one
* passed as parameter).
*/
gd::EventsFunctionsExtension& InsertEventsFunctionsExtension(
const EventsFunctionsExtension& externalLayout, std::size_t position);
const EventsFunctionsExtension& eventsFunctionExtension,
std::size_t position);
/**
* Must delete the events functions extension named "name".

View File

@@ -15,19 +15,6 @@ namespace gd {
/**
* \brief The class used to save/load projects and GDCore classes
* from/to XML or JSON.
*
* Usage example, with TinyXML:
\code
//Unserialize from a XML string:
TiXmlDocument doc;
if ( !doc.Parse(xmlString.c_str()) )
return false; //Error in XML file!
TiXmlHandle hdl(&doc);
gd::SerializerElement rootElement;
gd::Serializer::FromXML(rootElement, hdl.FirstChildElement().Element());
game.UnserializeFrom(rootElement);
\endcode
*/
class GD_CORE_API Serializer {
public:

View File

@@ -66,7 +66,7 @@ bool SerializerElement::GetBoolAttribute(const gd::String& name,
return defaultValue;
}
gd::String SerializerElement::GetStringAttribute(
gd::String SerializerElement::GetStringAttribute( // TODO: Use const ref?
const gd::String& name,
gd::String defaultValue,
gd::String deprecatedName) const {
@@ -241,4 +241,31 @@ bool SerializerElement::HasChild(const gd::String& name,
return false;
}
void SerializerElement::RemoveChild(const gd::String &name) {
for (size_t i = 0; i < children.size();) {
if (children[i].first == name)
children.erase(children.begin() + i);
else
++i;
}
}
void SerializerElement::Init(const gd::SerializerElement& other) {
valueUndefined = other.valueUndefined;
elementValue = other.elementValue;
attributes = other.attributes;
children.clear();
for (const auto& child : other.children) {
children.push_back(
std::make_pair(child.first,
std::shared_ptr<SerializerElement>(
new SerializerElement(*child.second))));
}
isArray = other.isArray;
arrayOf = other.arrayOf;
deprecatedArrayOf = other.deprecatedArrayOf;
}
} // namespace gd

View File

@@ -16,14 +16,45 @@
namespace gd {
/**
* \brief An element used during serialization from/to XML or JSON.
* \brief A generic container that can represent a value (
* containing a string, double, bool or int), an object ("associative array",
* "dictionary") with children or an array (children indexed by numeric
* properties).
*
* It is used for serialization (to JSON or XML), or as a generic
* container for properties of objects (see for example gd::BehaviorContent).
*
* It also has specialized methods in GDevelop.js (see postjs.js) to be
* converted to a JavaScript object.
*
* \see gd::Serializer
*/
class GD_CORE_API SerializerElement {
public:
/**
* \brief Create an empty element with no value, no children and no
* attributes.
*/
SerializerElement();
/**
* \brief Create an element with the specified value.
*/
SerializerElement(const SerializerValue &value);
/**
* Copy constructor.
*/
SerializerElement(const gd::SerializerElement &object) { Init(object); };
/**
* Assignment operator.
*/
SerializerElement &operator=(const gd::SerializerElement &object) {
if ((this) != &object) Init(object);
return *this;
}
virtual ~SerializerElement();
/** \name Value
@@ -37,36 +68,107 @@ class GD_CORE_API SerializerElement {
valueUndefined = false;
elementValue = value;
}
/**
* \brief Set the value of the element, as a boolean.
*/
void SetValue(bool val) {
valueUndefined = false;
elementValue.SetBool(val);
}
/**
* \brief Set the value of the element, as a boolean.
*/
void SetBoolValue(bool val) { SetValue(val); }
/**
* \brief Set the value of the element, as a string.
*/
void SetValue(const gd::String &val) {
valueUndefined = false;
elementValue.SetString(val);
}
/**
* \brief Set the value of the element, as a string.
*/
void SetStringValue(const gd::String &val) { SetValue(val); }
/**
* \brief Set the value of the element, as an integer.
*/
void SetValue(int val) {
valueUndefined = false;
elementValue.SetInt(val);
}
/**
* \brief Set the value of the element, as an integer.
*/
void SetIntValue(int val) { SetValue(val); }
/**
* \brief Set the value of the element, as an unsigned integer.
*/
void SetValue(unsigned int val) {
valueUndefined = false;
elementValue.SetInt((int)val);
}
/**
* \brief Set the value of the element, as a double precision floating point
* number.
*/
void SetValue(double val) {
valueUndefined = false;
elementValue.SetDouble(val);
}
/**
* \brief Set the value of the element, as a double precision floating point
* number.
*/
void SetDoubleValue(double val) { SetValue(val); }
/**
* \brief Set the value of the element, as a floating point number.
*/
void SetValue(float val) { SetValue((double)val); }
/**
* \brief Get the value of the element.
* \brief Set the value of the element, as a floating point number.
*/
void SetFloatValue(float val) { SetValue(val); }
/**
* \brief Get the value of the element, as a generic gd::SerializerValue.
*
* If not value was set, an attribute named "value" is searched. If found, its
* value is returned.
* \note If not value was set, an attribute named "value" is searched (for
* backward compatiblity). If found, its value is returned.
*/
const SerializerValue &GetValue() const;
/**
* \brief Get the value, its type being a boolean.
*/
bool GetBoolValue() const { return GetValue().GetBool(); };
/**
* \brief Get the value, its type being a gd::String.
*/
gd::String GetStringValue() const { return GetValue().GetString(); };
/**
* \brief Get the value, its type being an int.
*/
int GetIntValue() const { return GetValue().GetInt(); };
/**
* \brief Get the value, its type being a double
*/
double GetDoubleValue() const { return GetValue().GetDouble(); };
/**
* \brief Return true if no value was set for the element.
*/
@@ -74,18 +176,31 @@ class GD_CORE_API SerializerElement {
///@}
/** \name Attributes
* Methods related to the attributes of the element
* Methods related to the attributes of the element.
*
* Attributes are stored differently than children elements, but
* are serialized to the same in JSON. Hence, the attribute getters
* will also search in children elements.
*/
///@{
/**
* \brief Set the value of an attribute of the element
* \brief Set the boolean value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
SerializerElement &SetAttribute(const gd::String &name, bool value);
/**
* \brief Set the value of an attribute of the element
* \brief Set the boolean value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
SerializerElement &SetBoolAttribute(const gd::String &name, bool value) {
return SetAttribute(name, value);
}
/**
* \brief Set the string value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
@@ -93,7 +208,17 @@ class GD_CORE_API SerializerElement {
const gd::String &value);
/**
* \brief Set the value of an attribute of the element
* \brief Set the string value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
SerializerElement &SetStringAttribute(const gd::String &name,
const gd::String &value) {
return SetAttribute(name, value);
}
/**
* \brief Set the string value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
@@ -103,25 +228,43 @@ class GD_CORE_API SerializerElement {
};
/**
* \brief Set the value of an attribute of the element
* \brief Set the integer value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
SerializerElement &SetAttribute(const gd::String &name, int value);
/**
* \brief Set the value of an attribute of the element
* \brief Set the integer value of an attribute of the element
* \param name The name of the attribute.
* \param value The value of the attribute.
*/
SerializerElement &SetIntAttribute(const gd::String &name, int value) {
return SetAttribute(name, value);
}
/**
* \brief Set the double precision floating point number value of an attribute
* of the element \param name The name of the attribute. \param value The
* value of the attribute.
*/
SerializerElement &SetAttribute(const gd::String &name, double value);
/**
* \brief Set the double precision floating point number value of an attribute
* of the element \param name The name of the attribute. \param value The
* value of the attribute.
*/
SerializerElement &SetDoubleAttribute(const gd::String &name, double value) {
return SetAttribute(name, value);
}
/**
* Get the value of an attribute being a boolean.
* \param name The name of the attribute
* \param defaultValue The value returned if the attribute is not found.
* \param deprecatedName An alternative name for the attribute that will be
* used if the first one doesn't exists.
* used if the first one doesn't exist.
*/
bool GetBoolAttribute(const gd::String &name,
bool defaultValue = false,
@@ -132,7 +275,7 @@ class GD_CORE_API SerializerElement {
* \param name The name of the attribute
* \param defaultValue The value returned if the attribute is not found.
* \param deprecatedName An alternative name for the attribute that will be
* used if the first one doesn't exists.
* used if the first one doesn't exist.
*/
gd::String GetStringAttribute(const gd::String &name,
gd::String defaultValue = "",
@@ -143,7 +286,7 @@ class GD_CORE_API SerializerElement {
* \param name The name of the attribute
* \param defaultValue The value returned if the attribute is not found.
* \param deprecatedName An alternative name for the attribute that will be
* used if the first one doesn't exists.
* used if the first one doesn't exist.
*/
int GetIntAttribute(const gd::String &name,
int defaultValue = 0,
@@ -154,7 +297,7 @@ class GD_CORE_API SerializerElement {
* \param name The name of the attribute
* \param defaultValue The value returned if the attribute is not found.
* \param deprecatedName An alternative name for the attribute that will be
* used if the first one doesn't exists.
* used if the first one doesn't exist.
*/
double GetDoubleAttribute(const gd::String &name,
double defaultValue = 0.0,
@@ -167,7 +310,7 @@ class GD_CORE_API SerializerElement {
bool HasAttribute(const gd::String &name) const;
/**
* \brief Return all the children of the element.
* \brief Return all the attributes of the element.
*/
const std::map<gd::String, SerializerValue> &GetAllAttributes() const {
return attributes;
@@ -223,13 +366,16 @@ class GD_CORE_API SerializerElement {
/**
* \brief Add a child at the end of the children list with the given name and
* return a reference to it. \param name The name of the new child.
* return a reference to it.
*
* \param name The name of the new child.
*/
SerializerElement &AddChild(gd::String name);
/**
* \brief Get a child of the element using its name.
* \param name The name of the new child.
*
* \param name The name of the child.
* \param name The index of the child
*/
SerializerElement &GetChild(gd::String name,
@@ -238,7 +384,9 @@ class GD_CORE_API SerializerElement {
/**
* \brief Get a child of the element using its index (when the element is
* considered as an array). \param name The index of the child
* considered as an array).
*
* \param name The index of the child
*/
SerializerElement &GetChild(std::size_t index) const;
@@ -262,6 +410,12 @@ class GD_CORE_API SerializerElement {
*/
bool HasChild(const gd::String &name, gd::String deprecatedName = "") const;
/**
* \brief Remove the child with the specified name
* \param name The name of the child to remove.
*/
void RemoveChild(const gd::String &name);
/**
* \brief Return all the children of the element.
*/
@@ -274,6 +428,12 @@ class GD_CORE_API SerializerElement {
static SerializerElement nullElement;
private:
/**
* Initialize element using another element. Used by copy-ctor and assign-op.
* Don't forget to update me if members were changed!
*/
void Init(const gd::SerializerElement& other);
bool valueUndefined; ///< If true, the element does not have a value.
SerializerValue elementValue;

View File

@@ -1,66 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "Splitter.h"
#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
std::vector<Splitter::SplitElement> Splitter::Split(
SerializerElement& element,
const std::set<gd::String>& tags,
gd::String path) {
std::vector<Splitter::SplitElement> elements;
for (auto& child : element.GetAllChildren()) {
auto& childElement = child.second;
gd::String ref = path + pathSeparator + child.first;
if (tags.find(ref) != tags.end()) {
gd::String refName = childElement->GetStringAttribute(nameAttribute);
SplitElement splitElement = {ref, refName, *childElement};
elements.push_back(splitElement);
SerializerElement refElement;
refElement.SetAttribute("referenceTo", ref);
refElement.SetAttribute("name", refName);
*childElement = refElement;
} else {
auto newElements = Split(*childElement, tags, ref);
elements.insert(elements.end(), newElements.begin(), newElements.end());
}
}
return elements;
}
void Splitter::Unsplit(
SerializerElement& element,
std::function<SerializerElement(gd::String path, gd::String name)> cb) {
for (auto& child : element.GetAllChildren()) {
auto& childElement = child.second;
if ((childElement->HasAttribute("referenceTo") &&
childElement->HasAttribute("name")) ||
(childElement->HasChild("referenceTo") &&
childElement->HasChild("name"))) {
SerializerElement newElement =
cb(childElement->GetStringAttribute("referenceTo"),
childElement->GetStringAttribute("name"));
*childElement = newElement;
}
Unsplit(*childElement, cb);
}
}
} // namespace gd

View File

@@ -1,63 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_SPLITTER_H
#define GDCORE_SPLITTER_H
#include <functional>
#include <set>
#include <vector>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
/**
* \brief Split a tree of SerializerElement according to tags name.
* It replaces cut subtree by a SerializerElement containing a reference to the
* cut subtree.
*/
class GD_CORE_API Splitter {
public:
virtual ~Splitter(){};
Splitter(gd::String pathSeparator_ = "/", gd::String nameAttribute_ = "name")
: pathSeparator(pathSeparator_), nameAttribute(nameAttribute_){};
/**
* \brief Represents an element as returned by gd::Splitter:Split
*/
struct SplitElement {
gd::String path;
gd::String name;
SerializerElement element;
};
/**
* \brief Split the tree of SerializerElement into a vector of subtrees,
* replacing the cut subtrees by a placeholder (a new SerializerElement
* containing attributes referencing the subtree).
*/
std::vector<SplitElement> Split(SerializerElement& element,
const std::set<gd::String>& tags,
gd::String path = "");
/**
* \brief Browse the tree of SerializerElement, calling the callback function
* each time a SerializerElement with a reference to a subtree is found.
* \param cb The callback. It must return the SerializerElement containing the
* subtree that will be used to replace the placeholder SerializerElement.
*/
void Unsplit(
SerializerElement& element,
std::function<SerializerElement(gd::String path, gd::String name)> cb);
private:
gd::String pathSeparator;
gd::String nameAttribute;
};
} // namespace gd
#endif

View File

@@ -103,8 +103,9 @@ class SPtrList {
bool Contains(const T& elementToSearch) const;
///@}
/** \name std::vector API compatibility
* These functions ensure that the class can be used just like a std::vector.
/** \name std::vector-like API
* These functions ensure that the class can be used just like a std::vector
* for iterations.
*/
///@{

View File

@@ -0,0 +1,218 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_SerializableWithNameList
#define GDCORE_SerializableWithNameList
#include <memory>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Project;
class SerializerElement;
} // namespace gd
namespace gd {
/**
* \brief A class template that store a list of elements that can be accessed by
* their names and serialized.
*
* The type T is supposed to have a method `GetName`, returning the `gd::String`
* representing the name of the object, and SerializeTo/UnserializeFrom.
*
* \note *Invariant*: each element in the list has a unique name.
*
* \note *Invalidation*: Elements can be re-ordered without invalidating them.
* Insertion/removal does not invalidate other elements. Remove/Clear delete
* elements from memory.
*/
template <typename T>
class SerializableWithNameList {
public:
SerializableWithNameList();
SerializableWithNameList(const SerializableWithNameList<T>&);
virtual ~SerializableWithNameList(){};
SerializableWithNameList<T>& operator=(
const SerializableWithNameList<T>& rhs);
/**
* \brief Insert the specified element to the list
* \note The element passed by parameter is copied.
* \param element The element that must be copied and inserted into the list
* \param position Insertion position. If the position is invalid, the object
* is inserted at the end of the objects list.
*
* \return A reference to the element in the list
*/
T& Insert(const T& element, size_t position = (size_t)-1);
/**
* \brief Copy elements from another list.
*/
void Insert(const SerializableWithNameList<T>& otherEvents,
size_t begin,
size_t end,
size_t position = (size_t)-1);
/**
* \brief Insert a new element (constructed from its default constructor) with
* the given name.
*
* \param name The name of the new element
* \param position Insertion position. If the position is invalid, the object
* is inserted at the end of the objects list.
*
* \return A reference to the element in the list
*/
T& InsertNew(const gd::String& name, size_t position = (size_t)-1);
/**
* \brief Return the number of elements.
*/
size_t GetCount() const { return elements.size(); };
/**
* \brief Return a reference to the element with the specified name.
*/
T& Get(const gd::String& name);
/**
* \brief Return a reference to the element with the specified name.
*/
const T& Get(const gd::String& name) const;
/**
* \brief Return a reference to the element at position \a index in the
* elements list.
*/
T& Get(size_t index) { return *elements[index]; };
/**
* \brief Return a reference to the element at position \a index in the
* elements list.
*/
const T& Get(size_t index) const { return *elements[index]; };
/**
* \brief Remove the element with the specified name, destroying it.
*/
void Remove(const gd::String& name);
/**
* \brief Remove the element at the specified index in the list, destroying
* it.
*/
void Remove(size_t index);
/**
* \brief Return true if there isn't any element in the list.
*/
bool IsEmpty() const { return elements.empty(); };
/**
* \brief Clear the list of elements, destroying all of them.
*/
void Clear() { return elements.clear(); };
/**
* \brief Move element at position `oldIndex` to position `newIndex`.
*
* Elements pointers/references won't be invalidated.
*/
void Move(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Return true if an element with the specified name exists.
*/
bool Has(const gd::String& name) const;
/** \name std::vector-like API
* These functions ensure that the class can be used just like a std::vector
* for iterations.
*/
///@{
/**
* \brief Alias for GetCount()
* \see SerializableWithNameList::GetCount.
*/
size_t size() const { return GetCount(); }
/**
* \brief Alias for IsEmpty()
* \see SerializableWithNameList::IsEmpty.
*/
bool empty() const { return IsEmpty(); }
/**
* \brief Alias for Get()
* \see SerializableWithNameList::Get.
*/
T& operator[](size_t index) { return Get(index); };
/**
* \brief Alias for Get()
* \see SerializableWithNameList::Get.
*/
const T& operator[](size_t index) const { return Get(index); };
/**
* \brief Alias for Get()
* \see SerializableWithNameList::Get.
*/
T& at(size_t index) { return Get(index); };
/**
* \brief Alias for Get()
* \see SerializableWithNameList::Get.
*/
const T& at(size_t index) const { return Get(index); };
///@}
/** \name Internal std::vector raw access
*/
///@{
/**
* \brief Provide a raw access to the vector containing the elements.
*/
const std::vector<std::unique_ptr<T>>& GetInternalVector() const {
return elements;
};
/**
* \brief Provide a raw access to the vector containing the elements.
*/
std::vector<std::unique_ptr<T>>& GetInternalVector() { return elements; };
///@}
/** \name Serialization
*/
///@{
void SerializeElementsTo(const gd::String& elementName,
SerializerElement& element) const;
void UnserializeElementsFrom(const gd::String& elementName,
gd::Project& project,
const SerializerElement& element);
void UnserializeElementsFrom(const gd::String& elementName,
const SerializerElement& element);
///@}
protected:
std::vector<std::unique_ptr<T>> elements;
/**
* Initialize from another list of elements, copying elements. Used by
* copy-ctor and assign-op. Don't forget to update me if members were changed!
*/
void Init(const SerializableWithNameList<T>& other);
};
} // namespace gd
#include "SerializableWithNameList.inl"
#endif

View File

@@ -0,0 +1,165 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/PolymorphicClone.h"
namespace gd {
template <typename T>
SerializableWithNameList<T>::SerializableWithNameList() {}
template <typename T>
void SerializableWithNameList<T>::Insert(
const SerializableWithNameList<T>& otherElements,
size_t begin,
size_t end,
size_t position) {
if (begin >= otherElements.size()) return;
if (end < begin) return;
if (end >= otherElements.size()) end = otherElements.size() - 1;
for (std::size_t insertPos = 0; insertPos <= (end - begin); insertPos++) {
if (position != (size_t)-1 && position + insertPos < elements.size())
elements.insert(elements.begin() + position + insertPos,
gd::Clone(otherElements.elements[begin + insertPos]));
else
elements.push_back(gd::Clone(otherElements.elements[begin + insertPos]));
}
}
template <typename T>
T& SerializableWithNameList<T>::Insert(const T& element, size_t position) {
T& newElement = *(*(elements.insert(
position < elements.size() ? elements.begin() + position : elements.end(),
std::unique_ptr<T>(new T(element)))));
return newElement;
}
template <typename T>
T& SerializableWithNameList<T>::InsertNew(const gd::String& name,
std::size_t position) {
T& newElement = *(*(elements.insert(
position < elements.size() ? elements.begin() + position : elements.end(),
std::unique_ptr<T>(new T()))));
newElement.SetName(name);
return newElement;
}
template <typename T>
void SerializableWithNameList<T>::Remove(size_t index) {
elements.erase(elements.begin() + index);
}
template <typename T>
void SerializableWithNameList<T>::Remove(const gd::String& name) {
typename std::vector<std::unique_ptr<T>>::iterator object =
find_if(elements.begin(),
elements.end(),
[&name](const std::unique_ptr<T>& function) {
return function->GetName() == name;
});
if (object == elements.end()) return;
elements.erase(object);
}
template <typename T>
T& SerializableWithNameList<T>::Get(const gd::String& name) {
return *(
*find_if(elements.begin(),
elements.end(),
[&name](const std::unique_ptr<T>& function) {
return function->GetName() == name;
}));
}
template <typename T>
const T& SerializableWithNameList<T>::Get(const gd::String& name) const {
return *(
*find_if(elements.begin(),
elements.end(),
[&name](const std::unique_ptr<T>& function) {
return function->GetName() == name;
}));
}
template <typename T>
bool SerializableWithNameList<T>::Has(const gd::String& name) const {
return find_if(elements.begin(),
elements.end(),
[&name](const std::unique_ptr<T>& function) {
return function->GetName() == name;
}) != elements.end();
}
template <typename T>
void SerializableWithNameList<T>::Move(std::size_t oldIndex,
std::size_t newIndex) {
if (oldIndex >= elements.size() || newIndex >= elements.size()) return;
std::unique_ptr<T> object = std::move(elements[oldIndex]);
elements.erase(elements.begin() + oldIndex);
elements.insert(elements.begin() + newIndex, std::move(object));
}
template <typename T>
void SerializableWithNameList<T>::SerializeElementsTo(
const gd::String& elementName, SerializerElement& serializerElement) const {
serializerElement.ConsiderAsArrayOf(elementName);
for (const auto& element : elements) {
element->SerializeTo(serializerElement.AddChild(elementName));
}
}
template <typename T>
void SerializableWithNameList<T>::UnserializeElementsFrom(
const gd::String& elementName,
gd::Project& project,
const SerializerElement& serializerElement) {
elements.clear();
serializerElement.ConsiderAsArrayOf(elementName);
for (std::size_t i = 0; i < serializerElement.GetChildrenCount(); ++i) {
T& newElement = InsertNew("", GetCount());
newElement.UnserializeFrom(project, serializerElement.GetChild(i));
}
}
template <typename T>
void SerializableWithNameList<T>::UnserializeElementsFrom(
const gd::String& elementName, const SerializerElement& serializerElement) {
elements.clear();
serializerElement.ConsiderAsArrayOf(elementName);
for (std::size_t i = 0; i < serializerElement.GetChildrenCount(); ++i) {
T& newElement = InsertNew("", GetCount());
newElement.UnserializeFrom(serializerElement.GetChild(i));
}
}
template <typename T>
SerializableWithNameList<T>::SerializableWithNameList(
const SerializableWithNameList<T>& other) {
Init(other);
}
template <typename T>
SerializableWithNameList<T>& SerializableWithNameList<T>::operator=(
const SerializableWithNameList<T>& other) {
if (this != &other) Init(other);
return *this;
}
template <typename T>
void SerializableWithNameList<T>::Init(
const gd::SerializableWithNameList<T>& other) {
elements = gd::Clone(other.elements);
}
} // namespace gd

View File

@@ -6,6 +6,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
@@ -24,6 +25,16 @@ void SetupProjectWithDummyPlatform(gd::Project &project,
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
extension->SetExtensionInformation(
"MyExtension", "My testing extension", "", "", "");
extension
->AddAction("DoSomething",
"Do something",
"This does something",
"Do something please",
"",
"",
"")
.AddParameter("expression", "Parameter 1 (a number)")
.SetFunctionName("doSomething");
extension->AddExpression("GetNumber", "Get me a number", "", "", "")
.SetFunctionName("getNumber");
extension
@@ -31,9 +42,7 @@ void SetupProjectWithDummyPlatform(gd::Project &project,
"GetVariableAsNumber", "Get me a variable value", "", "", "")
.AddParameter("scenevar", "Scene variable")
.SetFunctionName("returnVariable");
extension
->AddStrExpression(
"ToString", "ToString", "", "", "")
extension->AddStrExpression("ToString", "ToString", "", "", "")
.AddParameter("expression", "Number to convert to string")
.SetFunctionName("toString");
extension
@@ -116,12 +125,38 @@ void SetupProjectWithDummyPlatform(gd::Project &project,
.AddParameter("object", _("Object parameter"))
.AddParameter("objectPtr", _("Object parameter"))
.SetFunctionName("getObjectStringWith2ObjectParam");
// auto behavior = extension->AddBehavior("MyBehavior", "Dummy behavior",
// "MyBehavior", "", "", "","",
// gd::make_unique<gd::Behavior>(),
// gd::make_unique<gd::BehaviorsSharedData>());
auto behavior =
extension->AddBehavior("MyBehavior",
"Dummy behavior",
"MyBehavior",
"A dummy behavior for tests",
"",
"",
"",
gd::make_unique<gd::Behavior>(),
gd::make_unique<gd::BehaviorsSharedData>());
behavior
.AddAction("BehaviorDoSomething",
"Do something on behavior",
"This does something with the behavior",
"Do something with the behavior please",
"",
"",
"")
.AddParameter("expression", "Parameter 1 (a number)")
.SetFunctionName("behaviorDoSomething");
behavior
.AddStrExpression("GetBehaviorStringWith1Param",
"Get string from behavior with 1 param",
"",
"",
"")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "MyExtension::MyBehavior")
.AddParameter("expression", _("Number parameter"))
.SetFunctionName("getBehaviorStringWith1Param");
platform.AddExtension(baseObjectExtension);
platform.AddExtension(extension);
project.AddPlatform(platform);
}
}

View File

@@ -12,7 +12,19 @@ class Project;
class Platform;
} // namespace gd
/**
* Setup the platform with:
* - A base object
* - An extension providing:
* - An action (MyExtension::DoSomething).
* - Some expressions (GetNumber, GetVariableAsNumber, ToString, MouseX,
* GetGlobalVariableAsNumber, GetNumberWith2Params, GetNumberWith3Params),
* - A sprite object (MyExtension::BuiltinObject) with:
* - Expressions (GetObjectVariableAsNumber, GetObjectNumber,
* GetObjectStringWith1Param, GetObjectStringWith3Param,
* GetObjectStringWith2ObjectParam,)
*/
void SetupProjectWithDummyPlatform(gd::Project &project,
gd::Platform &platform);
#endif
#endif

View File

@@ -4,7 +4,7 @@
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering events of GDevelop Core.
* @file Tests covering events code generation context,
*/
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include <memory>
@@ -18,20 +18,20 @@
TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
/**
* Generate a tree of contexts with declared objects as below:
* c1 -> c1.object1, c1.object2, c1.empty1 (empty list
* c1 -> c1.object1, c1.object2, c1.noPicking1 (no picking
* request)
* / \
* c2.object1 <- c2 c3 -> c3.object1, c1.object2
* / \
* c4 c5 -> c5.object1, c5.empty1 (empty list request),
* c1.object2
* c4 c5 -> c5.object1, c5.noPicking1 (no picking request),
* c1.object2, c5.empty1
*/
unsigned int maxDepth = 0;
gd::EventsCodeGenerationContext c1(&maxDepth);
c1.ObjectsListNeeded("c1.object1");
c1.ObjectsListNeeded("c1.object2");
c1.EmptyObjectsListNeeded("c1.empty1");
c1.ObjectsListWithoutPickingNeeded("c1.noPicking1");
gd::EventsCodeGenerationContext c2;
c2.InheritsFrom(c1);
@@ -47,9 +47,10 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
gd::EventsCodeGenerationContext c5;
c5.InheritsFrom(c2);
c5.EmptyObjectsListNeeded("c5.empty1");
c5.ObjectsListWithoutPickingNeeded("c5.noPicking1");
c5.ObjectsListNeeded("c5.object1");
c5.ObjectsListNeeded("c1.object2");
c5.EmptyObjectsListNeeded("c5.empty1");
SECTION("Parenting") {
REQUIRE(c2.GetParentContext() == &c1);
@@ -72,36 +73,38 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
REQUIRE(c1.GetObjectsListsAlreadyDeclared() == std::set<gd::String>());
REQUIRE(c1.GetObjectsListsToBeDeclared() ==
std::set<gd::String>({"c1.object1", "c1.object2"}));
REQUIRE(c1.GetObjectsListsToBeDeclaredEmpty() ==
std::set<gd::String>({"c1.empty1"}));
REQUIRE(c1.GetObjectsListsToBeDeclaredWithoutPicking() ==
std::set<gd::String>({"c1.noPicking1"}));
REQUIRE(c1.GetAllObjectsToBeDeclared() ==
std::set<gd::String>({"c1.object1", "c1.object2", "c1.empty1"}));
std::set<gd::String>({"c1.object1", "c1.object2", "c1.noPicking1"}));
REQUIRE(c2.GetObjectsListsAlreadyDeclared() ==
std::set<gd::String>({"c1.object1", "c1.object2", "c1.empty1"}));
std::set<gd::String>({"c1.object1", "c1.object2", "c1.noPicking1"}));
REQUIRE(c2.GetObjectsListsToBeDeclared() ==
std::set<gd::String>({"c2.object1"}));
REQUIRE(c2.GetObjectsListsToBeDeclaredEmpty() == std::set<gd::String>());
REQUIRE(c2.GetObjectsListsToBeDeclaredWithoutPicking() == std::set<gd::String>());
REQUIRE(c2.GetAllObjectsToBeDeclared() ==
std::set<gd::String>({"c2.object1"}));
REQUIRE(c3.GetObjectsListsAlreadyDeclared() ==
std::set<gd::String>({"c1.object1", "c1.object2", "c1.empty1"}));
std::set<gd::String>({"c1.object1", "c1.object2", "c1.noPicking1"}));
REQUIRE(c3.GetObjectsListsToBeDeclared() ==
std::set<gd::String>({"c3.object1", "c1.object2"}));
REQUIRE(c3.GetObjectsListsToBeDeclaredEmpty() == std::set<gd::String>());
REQUIRE(c3.GetObjectsListsToBeDeclaredWithoutPicking() == std::set<gd::String>());
REQUIRE(c3.GetAllObjectsToBeDeclared() ==
std::set<gd::String>({"c3.object1", "c1.object2"}));
REQUIRE(c5.GetObjectsListsAlreadyDeclared() ==
std::set<gd::String>(
{"c1.object1", "c1.object2", "c1.empty1", "c2.object1"}));
{"c1.object1", "c1.object2", "c1.noPicking1", "c2.object1"}));
REQUIRE(c5.GetObjectsListsToBeDeclared() ==
std::set<gd::String>({"c5.object1", "c1.object2"}));
REQUIRE(c5.GetObjectsListsToBeDeclaredWithoutPicking() ==
std::set<gd::String>({"c5.noPicking1"}));
REQUIRE(c5.GetObjectsListsToBeDeclaredEmpty() ==
std::set<gd::String>({"c5.empty1"}));
REQUIRE(c5.GetAllObjectsToBeDeclared() ==
std::set<gd::String>({"c5.object1", "c5.empty1", "c1.object2"}));
std::set<gd::String>({"c5.object1", "c5.noPicking1", "c1.object2", "c5.empty1"}));
}
SECTION("ObjectAlreadyDeclared") {
@@ -130,6 +133,7 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
REQUIRE(c5.GetLastDepthObjectListWasNeeded("c1.object2") == 2);
REQUIRE(c5.GetLastDepthObjectListWasNeeded("c2.object1") == 1);
REQUIRE(c5.GetLastDepthObjectListWasNeeded("c5.object1") == 2);
REQUIRE(c5.GetLastDepthObjectListWasNeeded("c5.empty1") == 2);
}
SECTION("SetCurrentObject") {
@@ -145,12 +149,12 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
* Generate a tree of contexts with declared objects as below:
* ...
* \
* c5 -> c5.object1, c5.empty1 (empty list request),
* c5 -> c5.object1, c5.noPicking1 (no picking request),
* c1.object2
* /
* c6 (reuse c5) -> c5.object1, c6.object3
* /
* c7 -> c5.object1
* c7 -> c5.object1, c5.empty1 (empty list request)
*/
gd::EventsCodeGenerationContext c6;
c6.Reuse(c5);
@@ -161,6 +165,7 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
c7.InheritsFrom(c6);
c7.ObjectsListNeeded("c5.object1");
c7.ObjectsListNeeded("c6.object3");
c7.EmptyObjectsListNeeded("c5.empty1");
// c6 is reusing c5 context so it has the same depth:
REQUIRE(c6.GetParentContext() == &c5);
@@ -169,15 +174,16 @@ TEST_CASE("EventsCodeGenerationContext", "[common][events]") {
// c6 reuse the objects lists from c5:
REQUIRE(c6.IsSameObjectsList("c5.object1", c5) == true);
REQUIRE(c6.IsSameObjectsList("c5.empty1", c5) == true);
REQUIRE(c6.IsSameObjectsList("c5.noPicking1", c5) == true);
REQUIRE(c6.GetLastDepthObjectListWasNeeded("c5.object1") ==
c5.GetLastDepthObjectListWasNeeded("c5.object1"));
REQUIRE(c6.GetLastDepthObjectListWasNeeded("c5.empty1") ==
c5.GetLastDepthObjectListWasNeeded("c5.empty1"));
REQUIRE(c6.GetLastDepthObjectListWasNeeded("c5.noPicking1") ==
c5.GetLastDepthObjectListWasNeeded("c5.noPicking1"));
REQUIRE(c7.GetParentContext() == &c6);
REQUIRE(c7.GetContextDepth() == 3);
REQUIRE(c7.IsSameObjectsList("c5.object1", c6) == false);
REQUIRE(c7.IsSameObjectsList("c6.object3", c6) == false);
REQUIRE(c7.IsSameObjectsList("c5.empty1", c5) == false);
}
}

View File

@@ -0,0 +1,91 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/Project.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "catch.hpp"
TEST_CASE("EventsFunctionsContainer", "[common]") {
SECTION("Sanity checks") {
gd::EventsFunctionsContainer eventsFunctionContainer;
eventsFunctionContainer.InsertNewEventsFunction("Function1", 0);
eventsFunctionContainer.InsertNewEventsFunction("Function2", 1);
eventsFunctionContainer.InsertNewEventsFunction("Function3", 2);
// Check that copy operator is working
gd::EventsFunctionsContainer eventsFunctionContainer2 =
eventsFunctionContainer;
REQUIRE(eventsFunctionContainer2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionContainer2.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(2).GetName() ==
"Function3");
// Check that the copy has not somehow shared the same pointers
// to the events functions.
eventsFunctionContainer.GetEventsFunction(1).SetName("Function2.x");
eventsFunctionContainer2.GetEventsFunction(0).SetName("Function1.y");
REQUIRE(eventsFunctionContainer.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionContainer.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionContainer.GetEventsFunction(1).GetName() ==
"Function2.x");
REQUIRE(eventsFunctionContainer.GetEventsFunction(2).GetName() ==
"Function3");
REQUIRE(eventsFunctionContainer2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionContainer2.GetEventsFunction(0).GetName() ==
"Function1.y");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(2).GetName() ==
"Function3");
// Check removal
eventsFunctionContainer.RemoveEventsFunction("Function3");
REQUIRE(eventsFunctionContainer.GetEventsFunctionsCount() == 2);
REQUIRE(eventsFunctionContainer.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionContainer.GetEventsFunction(1).GetName() ==
"Function2.x");
REQUIRE(eventsFunctionContainer2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionContainer2.GetEventsFunction(0).GetName() ==
"Function1.y");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(2).GetName() ==
"Function3");
}
SECTION("Serialization") {
gd::Project project;
gd::EventsFunctionsContainer eventsFunctionContainer;
eventsFunctionContainer.InsertNewEventsFunction("Function1", 0);
eventsFunctionContainer.InsertNewEventsFunction("Function2", 1);
eventsFunctionContainer.InsertNewEventsFunction("Function3", 2);
gd::SerializerElement element;
eventsFunctionContainer.SerializeEventsFunctionsTo(element);
eventsFunctionContainer.RemoveEventsFunction("Function2");
gd::EventsFunctionsContainer eventsFunctionContainer2;
eventsFunctionContainer2.UnserializeEventsFunctionsFrom(project, element);
REQUIRE(eventsFunctionContainer.GetEventsFunctionsCount() == 2);
REQUIRE(eventsFunctionContainer.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionContainer.GetEventsFunction(1).GetName() ==
"Function3");
REQUIRE(eventsFunctionContainer2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionContainer2.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionContainer2.GetEventsFunction(2).GetName() ==
"Function3");
}
}

View File

@@ -0,0 +1,59 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "catch.hpp"
TEST_CASE("EventsFunctionsExtension", "[common]") {
SECTION("Sanity checks") {
gd::EventsFunctionsExtension eventsFunctionExtension;
eventsFunctionExtension.InsertNewEventsFunction("Function1", 0);
eventsFunctionExtension.InsertNewEventsFunction("Function2", 1);
eventsFunctionExtension.InsertNewEventsFunction("Function3", 2);
eventsFunctionExtension.GetEventsBasedBehaviors().InsertNew("MyBehavior",
0);
eventsFunctionExtension.GetEventsBasedBehaviors().InsertNew("MyBehavior2",
1);
// Check that copy operator is working
gd::EventsFunctionsExtension eventsFunctionExtension2 =
eventsFunctionExtension;
REQUIRE(eventsFunctionExtension2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionExtension2.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionExtension2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionExtension2.GetEventsFunction(2).GetName() ==
"Function3");
REQUIRE(eventsFunctionExtension2.GetEventsBasedBehaviors().GetCount() == 2);
REQUIRE(
eventsFunctionExtension2.GetEventsBasedBehaviors().Get(0).GetName() ==
"MyBehavior");
REQUIRE(
eventsFunctionExtension2.GetEventsBasedBehaviors().Get(1).GetName() ==
"MyBehavior2");
// Check that the copy has not somehow shared the same pointers
// to the events functions.
eventsFunctionExtension.GetEventsFunction(1).SetName("Function2.x");
eventsFunctionExtension2.GetEventsFunction(0).SetName("Function1.y");
REQUIRE(eventsFunctionExtension.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionExtension.GetEventsFunction(0).GetName() ==
"Function1");
REQUIRE(eventsFunctionExtension.GetEventsFunction(1).GetName() ==
"Function2.x");
REQUIRE(eventsFunctionExtension.GetEventsFunction(2).GetName() ==
"Function3");
REQUIRE(eventsFunctionExtension2.GetEventsFunctionsCount() == 3);
REQUIRE(eventsFunctionExtension2.GetEventsFunction(0).GetName() ==
"Function1.y");
REQUIRE(eventsFunctionExtension2.GetEventsFunction(1).GetName() ==
"Function2");
REQUIRE(eventsFunctionExtension2.GetEventsFunction(2).GetName() ==
"Function3");
}
}

View File

@@ -15,19 +15,45 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Serialization/Splitter.h"
#include "GDCore/Tools/SystemStats.h"
#include "GDCore/Tools/VersionWrapper.h"
#include "catch.hpp"
using namespace gd;
TEST_CASE("SerializerElement", "[common]") {
SECTION("Basics and copying") {
SerializerElement element;
element.AddChild("child1").SetStringValue("value123");
element.AddChild("child2").SetDoubleValue(45.6);
element.SetStringAttribute("attr1", "attr123");
SerializerElement copiedElement = element;
REQUIRE(element.GetChild("child1").GetStringValue() == "value123");
REQUIRE(element.GetChild("child2").GetDoubleValue() == 45.6);
REQUIRE(element.GetStringAttribute("attr1") == "attr123");
REQUIRE(copiedElement.GetChild("child1").GetStringValue() == "value123");
REQUIRE(copiedElement.GetChild("child2").GetDoubleValue() == 45.6);
REQUIRE(copiedElement.GetStringAttribute("attr1") == "attr123");
element.GetChild("child1").SetStringValue("value123 modified");
copiedElement.GetChild("child2").SetDoubleValue(45.678);
copiedElement.SetStringAttribute("attr1", "attr123 modified");
REQUIRE(element.GetChild("child1").GetStringValue() == "value123 modified");
REQUIRE(element.GetChild("child2").GetDoubleValue() == 45.6);
REQUIRE(element.GetStringAttribute("attr1") == "attr123");
REQUIRE(copiedElement.GetChild("child1").GetStringValue() == "value123");
REQUIRE(copiedElement.GetChild("child2").GetDoubleValue() == 45.678);
REQUIRE(copiedElement.GetStringAttribute("attr1") == "attr123 modified");
}
}
TEST_CASE("Serializer", "[common]") {
SECTION("JSON basics") {
gd::String originalJSON = "{\"ok\": true,\"hello\": \"world\"}";
SerializerElement element = Serializer::FromJSON(originalJSON);
REQUIRE(element.GetChild("ok").GetValue().GetBool() == true);
REQUIRE(element.GetChild("hello").GetValue().GetString() == "world");
REQUIRE(element.GetChild("ok").GetBoolValue() == true);
REQUIRE(element.GetChild("hello").GetStringValue() == "world");
gd::String json = Serializer::ToJSON(element);
REQUIRE(json == originalJSON);
@@ -38,10 +64,10 @@ TEST_CASE("Serializer", "[common]") {
"{\"\\\"hello\\\"\": \" \\\"quote\\\" \",\"caret-prop\": "
"1,\"special-\\b\\f\\n\\r\\t\\\"\": \"\\b\\f\\n\\r\\t\"}";
SerializerElement element = Serializer::FromJSON(originalJSON);
REQUIRE(element.GetChild("caret-prop").GetValue().GetBool() == true);
REQUIRE(element.GetChild("\"hello\"").GetValue().GetString() ==
REQUIRE(element.GetChild("caret-prop").GetBoolValue() == true);
REQUIRE(element.GetChild("\"hello\"").GetStringValue() ==
" \"quote\" ");
REQUIRE(element.GetChild("special-\b\f\n\r\t\"").GetValue().GetString() ==
REQUIRE(element.GetChild("special-\b\f\n\r\t\"").GetStringValue() ==
"\b\f\n\r\t");
gd::String json = Serializer::ToJSON(element);
@@ -54,11 +80,11 @@ TEST_CASE("Serializer", "[common]") {
u8"1,\"Hello 官话 world\": \"官话\"}";
SerializerElement element = Serializer::FromJSON(originalJSON);
REQUIRE(
element.GetChild(u8"Bonjour à tout le monde").GetValue().GetBool() ==
element.GetChild(u8"Bonjour à tout le monde").GetBoolValue() ==
true);
REQUIRE(element.GetChild(u8"Ich heiße GDevelop").GetValue().GetString() ==
REQUIRE(element.GetChild(u8"Ich heiße GDevelop").GetStringValue() ==
"Gut!");
REQUIRE(element.GetChild(u8"Hello 官话 world").GetValue().GetString() ==
REQUIRE(element.GetChild(u8"Hello 官话 world").GetStringValue() ==
u8"官话");
gd::String json = Serializer::ToJSON(element);
@@ -102,85 +128,4 @@ TEST_CASE("Serializer", "[common]") {
REQUIRE(unserializeAndSerializeToJSON(test2) == test2);
}
}
SECTION("Splitter") {
SECTION("Split elements") {
// Create some elements
SerializerElement root;
root.AddChild("a").AddChild("a1").SetValue(gd::String("hello"));
root.AddChild("b").AddChild("b1").SetValue(gd::String("world"));
root.AddChild("c").AddChild("c1").SetValue(3);
auto& layouts = root.AddChild("layouts");
layouts.ConsiderAsArrayOf("layout");
for (auto i = 0; i < 5; ++i) {
auto& layout = layouts.AddChild("layout");
layout.SetAttribute("name", "layout" + gd::String::From(i));
layout.AddChild("child").SetValue(42);
}
// And split them
gd::Splitter splitter;
auto splitElements = splitter.Split(root, {"/a/a1", "/layouts/layout"});
REQUIRE(splitElements.size() == 6);
REQUIRE(splitElements[0].path == "/a/a1");
REQUIRE(splitElements[1].path == "/layouts/layout");
REQUIRE(splitElements[1].name == "layout0");
REQUIRE(Serializer::ToJSON(splitElements[0].element) == "\"hello\"");
REQUIRE(Serializer::ToJSON(splitElements[1].element) ==
"{\"name\": \"layout0\",\"child\": 42}");
REQUIRE(Serializer::ToJSON(root) ==
"{\"a\": {\"a1\": {\"name\": \"\",\"referenceTo\": "
"\"/a/a1\"}},\"b\": {\"b1\": \"world\"},\"c\": {\"c1\": "
"3},\"layouts\": [{\"name\": \"layout0\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout1\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout2\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout3\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout4\",\"referenceTo\": "
"\"/layouts/layout\"}]}");
}
SECTION("Unsplit elements") {
// Get a JSON with elements being reference to split elements
gd::String originalJSON =
"{\"a\": {\"a1\": {\"name\": \"\",\"referenceTo\": "
"\"/a/a1\"}},\"b\": {\"b1\": \"world\"},\"c\": {\"c1\": "
"3},\"layouts\": [{\"name\": \"layout0\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout1\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout2\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout3\",\"referenceTo\": "
"\"/layouts/layout\"},{\"name\": \"layout4\",\"referenceTo\": "
"\"/layouts/layout\"}]}";
SerializerElement root = Serializer::FromJSON(originalJSON);
gd::Splitter splitter;
splitter.Unsplit(root, [](gd::String path, gd::String name) {
// Return new elements to replace the split elements
SerializerElement element;
element.SetAttribute("path", path);
element.SetAttribute("name", name);
element.AddChild("child").SetValue(name == "" ? 41 : 42);
return element;
});
// Check that we can now get elements, with all split elements
// replaced by the new ones.
root.GetChild("layouts").ConsiderAsArrayOf("layout");
REQUIRE(root.GetChild("a").GetChild("a1").GetStringAttribute("name") ==
"");
REQUIRE(root.GetChild("a")
.GetChild("a1")
.GetChild("child")
.GetValue()
.GetInt() == 41);
REQUIRE(root.GetChild("layouts").GetChild(0).GetStringAttribute("path") ==
"/layouts/layout");
REQUIRE(root.GetChild("layouts").GetChild(1).GetStringAttribute("name") ==
"layout1");
REQUIRE(root.GetChild("layouts")
.GetChild(2)
.GetChild("child")
.GetValue()
.GetInt() == 42);
}
}
}

View File

@@ -7,10 +7,13 @@
* @file Tests covering project refactoring
*/
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "DummyPlatform.h"
#include "GDCore/Events/Builtin/LinkEvent.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
@@ -21,14 +24,111 @@
namespace {
void SetupProjectWithDummyPlatform(gd::Project &project,
gd::Platform &platform) {
std::shared_ptr<gd::PlatformExtension> extension =
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
extension->AddObject<gd::Object>(
"Sprite", "Dummy Sprite", "Dummy sprite object", "");
platform.AddExtension(extension);
project.AddPlatform(platform);
gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
gd::Project &project) {
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
// Add a (free) function and a (free) expression
eventsExtension.InsertNewEventsFunction("MyEventsFunction", 0);
eventsExtension.InsertNewEventsFunction("MyEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
// Add some usage for them
{
auto &layout = project.InsertNewLayout("LayoutWithFreeFunctions", 0);
auto &externalEvents =
project.InsertNewExternalEvents("ExternalEventsWithFreeFunctions", 0);
externalEvents.SetAssociatedLayout("LayoutWithFreeFunctions");
// Create an event in the layout referring to
// MyEventsExtension::MyEventsFunction
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyEventsExtension::MyEventsFunction");
event.GetActions().Insert(instruction);
layout.GetEvents().InsertEvent(event);
}
// Create an event in the external events referring to
// MyEventsExtension::MyEventsFunctionExpression
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression(
"1 + MyEventsExtension::MyEventsFunctionExpression(123)"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
}
// Add a events based behavior
{
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().InsertNew(
"MyEventsBasedBehavior", 0);
eventsBasedBehavior.SetFullName("My events based behavior");
eventsBasedBehavior.SetDescription("An events based behavior for test");
auto &behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
behaviorEventsFunctions.InsertNewEventsFunction("MyBehaviorEventsFunction",
0);
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
}
// Add some usage in events
{
auto &layout = project.InsertNewLayout("LayoutWithBehaviorFunctions", 0);
auto &externalEvents = project.InsertNewExternalEvents(
"ExternalEventsWithBehaviorFunctions", 0);
externalEvents.SetAssociatedLayout("LayoutWithBehaviorFunctions");
auto &object = layout.InsertNewObject(
project, "MyExtension::Sprite", "ObjectWithMyBehavior", 0);
object.AddBehavior(gd::BehaviorContent(
"MyBehavior", "MyEventsExtension::MyEventsBasedBehavior"));
auto &globalObject = project.InsertNewObject(
project, "MyExtension::Sprite", "GlobalObjectWithMyBehavior", 0);
globalObject.AddBehavior(gd::BehaviorContent(
"MyBehavior", "MyEventsExtension::MyEventsBasedBehavior"));
// Create an event in the layout referring to
// MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunction
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType(
"MyEventsExtension::MyEventsBasedBehavior::MyBehaviorEventsFunction");
event.GetActions().Insert(instruction);
layout.GetEvents().InsertEvent(event);
}
// Create an event in ExternalEvents1 referring to
// MyEventsExtension::MyEventsFunctionExpression
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression("1 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123)"));
event.GetActions().Insert(instruction);
externalEvents.GetEvents().InsertEvent(event);
}
}
return eventsExtension;
}
} // namespace
@@ -47,8 +147,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
group1.AddObject("GlobalObject1");
layout1.GetObjectGroups().Insert(group1);
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::WholeProjectRefactorer::ObjectRemovedInLayout(
project, layout1, "Object1");
@@ -65,8 +165,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::InitialInstance instance1;
instance1.SetObjectName("Object1");
@@ -103,8 +203,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
externalLayout1.SetAssociatedLayout("Layout1");
externalLayout2.SetAssociatedLayout("Layout2");
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::InitialInstance instance1;
instance1.SetObjectName("Object1");
@@ -151,8 +251,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
group1.AddObject("GlobalObject1");
layout1.GetObjectGroups().Insert(group1);
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::WholeProjectRefactorer::ObjectRenamedInLayout(
project, layout1, "Object1", "Object3");
@@ -171,8 +271,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::InitialInstance instance1;
instance1.SetObjectName("Object1");
@@ -212,8 +312,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
externalLayout1.SetAssociatedLayout("Layout1");
externalLayout2.SetAssociatedLayout("Layout2");
layout1.InsertNewObject(project, "Sprite", "Object1", 0);
layout1.InsertNewObject(project, "Sprite", "Object2", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object2", 0);
gd::InitialInstance instance1;
instance1.SetObjectName("Object1");
@@ -254,4 +354,204 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"GlobalObject3") == true);
}
}
SECTION("Events extension renamed") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
gd::WholeProjectRefactorer::RenameEventsFunctionsExtension(
project, eventsExtension, "MyEventsExtension", "MyRenamedExtension");
// Check that events function calls in instructions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() == "MyRenamedExtension::MyEventsFunction");
// Check that events function calls in expressions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetExternalEvents("ExternalEventsWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"1 + MyRenamedExtension::MyEventsFunctionExpression(123)");
// Check that the type of the behavior was changed in the behaviors of
// objects. Name is *not* changed.
REQUIRE(project.GetLayout("LayoutWithBehaviorFunctions")
.GetObject("ObjectWithMyBehavior")
.GetBehavior("MyBehavior")
.GetTypeName() == "MyRenamedExtension::MyEventsBasedBehavior");
REQUIRE(project.GetObject("GlobalObjectWithMyBehavior")
.GetBehavior("MyBehavior")
.GetTypeName() == "MyRenamedExtension::MyEventsBasedBehavior");
// Check if events based behaviors functions have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyRenamedExtension::MyEventsBasedBehavior::"
"MyBehaviorEventsFunction");
// Check events based behaviors functions have *not* been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123)");
}
SECTION("(Free) events function renamed") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
gd::WholeProjectRefactorer::RenameEventsFunction(project,
eventsExtension,
"MyEventsFunction",
"MyRenamedEventsFunction");
gd::WholeProjectRefactorer::RenameEventsFunction(
project,
eventsExtension,
"MyEventsFunctionExpression",
"MyRenamedFunctionExpression");
// Check that events function calls in instructions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() == "MyEventsExtension::MyRenamedEventsFunction");
// Check that events function calls in expressions have been renamed
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetExternalEvents("ExternalEventsWithFreeFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"1 + MyEventsExtension::MyRenamedFunctionExpression(123)");
}
SECTION("Events based Behavior type renamed") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
gd::WholeProjectRefactorer::RenameEventsBasedBehavior(
project,
eventsExtension,
"MyEventsBasedBehavior",
"MyRenamedEventsBasedBehavior");
// Check that the type of the behavior was changed in the behaviors of
// objects. Name is *not* changed.
REQUIRE(project.GetLayout("LayoutWithBehaviorFunctions")
.GetObject("ObjectWithMyBehavior")
.GetBehavior("MyBehavior")
.GetTypeName() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
REQUIRE(project.GetObject("GlobalObjectWithMyBehavior")
.GetBehavior("MyBehavior")
.GetTypeName() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
// Check if events based behaviors functions have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyEventsExtension::MyRenamedEventsBasedBehavior::"
"MyBehaviorEventsFunction");
// Check events based behaviors functions have *not* been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123)");
}
SECTION("(Events based Behavior) events function renamed") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
gd::WholeProjectRefactorer::RenameBehaviorEventsFunction(
project,
eventsExtension,
eventsBasedBehavior,
"MyBehaviorEventsFunction",
"MyRenamedBehaviorEventsFunction");
gd::WholeProjectRefactorer::RenameBehaviorEventsFunction(
project,
eventsExtension,
eventsBasedBehavior,
"MyBehaviorEventsFunctionExpression",
"MyRenamedBehaviorEventsFunctionExpression");
// Check if events based behaviors functions have been renamed in
// instructions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetLayout("LayoutWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetType() ==
"MyEventsExtension::MyEventsBasedBehavior::"
"MyRenamedBehaviorEventsFunction");
// Check events based behaviors functions have been renamed in
// expressions
REQUIRE(static_cast<gd::StandardEvent &>(
project.GetExternalEvents("ExternalEventsWithBehaviorFunctions")
.GetEvents()
.GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"1 + "
"ObjectWithMyBehavior::MyBehavior."
"MyRenamedBehaviorEventsFunctionExpression(123)");
}
}

View File

@@ -4,224 +4,25 @@ GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#include "AnchorBehavior.h"
#include <SFML/Window.hpp>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <memory>
#include <set>
#include <map>
#include "GDCore/CommonTools.h"
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Extensions/Builtin/MathematicalTools.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeGame.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
AnchorBehavior::AnchorBehavior()
: m_relativeToOriginalWindowSize(true),
m_leftEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_rightEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_topEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_bottomEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_invalidDistances(true),
m_leftEdgeDistance(0.f),
m_rightEdgeDistance(0.f),
m_topEdgeDistance(0.f),
m_bottomEdgeDistance(0.f) {}
void AnchorBehavior::OnActivate() { m_invalidDistances = true; }
namespace {
sf::Vector2f mapFloatPixelToCoords(const sf::Vector2f& point,
const sf::RenderTarget& target,
const sf::View& view) {
// First, convert from viewport coordinates to homogeneous coordinates
sf::Vector2f normalized;
sf::IntRect viewport = target.getViewport(view);
normalized.x = -1.f + 2.f * (point.x - static_cast<float>(viewport.left)) /
static_cast<float>(viewport.width);
normalized.y = 1.f - 2.f * (point.y - static_cast<float>(viewport.top)) /
static_cast<float>(viewport.height);
// Then transform by the inverse of the view matrix
return view.getInverseTransform().transformPoint(normalized);
}
sf::Vector2f mapCoordsToFloatPixel(const sf::Vector2f& point,
const sf::RenderTarget& target,
const sf::View& view) {
// Note: almost the same as RenderTarget::mapCoordsToPixel except that the
// result is sf::Vector2f
// First, transform the point by the view matrix
sf::Vector2f normalized = view.getTransform().transformPoint(point);
// Then convert to viewport coordinates
sf::Vector2f pixel;
sf::IntRect viewport = target.getViewport(view);
pixel.x = (normalized.x + 1.f) / 2.f * static_cast<float>(viewport.width) +
static_cast<float>(viewport.left);
pixel.y = (-normalized.y + 1.f) / 2.f * static_cast<float>(viewport.height) +
static_cast<float>(viewport.top);
return pixel;
}
} // namespace
void AnchorBehavior::DoStepPreEvents(RuntimeScene& scene) {}
void AnchorBehavior::DoStepPostEvents(RuntimeScene& scene) {
const RuntimeLayer& layer = scene.GetRuntimeLayer(object->GetLayer());
const RuntimeCamera& firstCamera = layer.GetCamera(0);
if (m_invalidDistances) {
sf::Vector2u windowSize =
m_relativeToOriginalWindowSize
? sf::Vector2u(scene.game->getWindowOriginalWidth(),
scene.game->getWindowOriginalHeight())
: scene.renderWindow->getSize();
// Calculate the distances from the window's bounds.
sf::Vector2f topLeftPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX(), object->GetDrawableY()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
sf::Vector2f bottomRightPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX() + object->GetWidth(),
object->GetDrawableY() + object->GetHeight()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
// Left edge
if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_leftEdgeDistance = topLeftPixel.x;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_leftEdgeDistance = static_cast<float>(windowSize.x) - topLeftPixel.x;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_leftEdgeDistance = topLeftPixel.x / windowSize.x;
// Right edge
if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_rightEdgeDistance = bottomRightPixel.x;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_rightEdgeDistance =
static_cast<float>(windowSize.x) - bottomRightPixel.x;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_rightEdgeDistance = bottomRightPixel.x / windowSize.x;
// Top edge
if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_topEdgeDistance = topLeftPixel.y;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_topEdgeDistance = static_cast<float>(windowSize.y) - topLeftPixel.y;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
m_topEdgeDistance = topLeftPixel.y / static_cast<float>(windowSize.y);
// Bottom edge
if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_bottomEdgeDistance = bottomRightPixel.y;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_bottomEdgeDistance =
static_cast<float>(windowSize.y) - bottomRightPixel.y;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
m_bottomEdgeDistance =
bottomRightPixel.y / static_cast<float>(windowSize.y);
m_invalidDistances = false;
} else {
sf::Vector2u windowSize = scene.renderWindow->getSize();
// Move and resize the object if needed
sf::Vector2f topLeftPixel;
sf::Vector2f bottomRightPixel;
// Left edge
if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
topLeftPixel.x = m_leftEdgeDistance;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
topLeftPixel.x = static_cast<float>(windowSize.x) - m_leftEdgeDistance;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
topLeftPixel.x = m_leftEdgeDistance * static_cast<float>(windowSize.x);
// Top edge
if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
topLeftPixel.y = m_topEdgeDistance;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
topLeftPixel.y = static_cast<float>(windowSize.y) - m_topEdgeDistance;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
topLeftPixel.y = m_topEdgeDistance * static_cast<float>(windowSize.y);
// Right edge
if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
bottomRightPixel.x = m_rightEdgeDistance;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
bottomRightPixel.x =
static_cast<float>(windowSize.x) - m_rightEdgeDistance;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
bottomRightPixel.x =
m_rightEdgeDistance * static_cast<float>(windowSize.x);
// Bottom edge
if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
bottomRightPixel.y = m_bottomEdgeDistance;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
bottomRightPixel.y =
static_cast<float>(windowSize.y) - m_bottomEdgeDistance;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
bottomRightPixel.y =
m_bottomEdgeDistance * static_cast<float>(windowSize.y);
sf::Vector2f topLeftCoord = mapFloatPixelToCoords(
topLeftPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
sf::Vector2f bottomRightCoord = mapFloatPixelToCoords(
bottomRightPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
// Move and resize the object according to the anchors
if (m_rightEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetWidth(bottomRightCoord.x - topLeftCoord.x);
if (m_bottomEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetHeight(bottomRightCoord.y - topLeftCoord.y);
if (m_leftEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetX(topLeftCoord.x + object->GetX() - object->GetDrawableX());
if (m_topEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetY(topLeftCoord.y + object->GetY() - object->GetDrawableY());
}
}
void AnchorBehavior::UnserializeFrom(const gd::SerializerElement& element) {
m_relativeToOriginalWindowSize =
element.GetBoolAttribute("relativeToOriginalWindowSize");
m_leftEdgeAnchor =
static_cast<HorizontalAnchor>(element.GetIntAttribute("leftEdgeAnchor"));
m_rightEdgeAnchor =
static_cast<HorizontalAnchor>(element.GetIntAttribute("rightEdgeAnchor"));
m_topEdgeAnchor =
static_cast<VerticalAnchor>(element.GetIntAttribute("topEdgeAnchor"));
m_bottomEdgeAnchor =
static_cast<VerticalAnchor>(element.GetIntAttribute("bottomEdgeAnchor"));
void AnchorBehavior::InitializeContent(gd::SerializerElement& content) {
content.SetAttribute("relativeToOriginalWindowSize", true);
content.SetAttribute("leftEdgeAnchor",
static_cast<int>(ANCHOR_HORIZONTAL_NONE));
content.SetAttribute("rightEdgeAnchor",
static_cast<int>(ANCHOR_HORIZONTAL_NONE));
content.SetAttribute("topEdgeAnchor", static_cast<int>(ANCHOR_VERTICAL_NONE));
content.SetAttribute("bottomEdgeAnchor",
static_cast<int>(ANCHOR_VERTICAL_NONE));
}
#if defined(GD_IDE_ONLY)
void AnchorBehavior::SerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("relativeToOriginalWindowSize",
m_relativeToOriginalWindowSize);
element.SetAttribute("leftEdgeAnchor", static_cast<int>(m_leftEdgeAnchor));
element.SetAttribute("rightEdgeAnchor", static_cast<int>(m_rightEdgeAnchor));
element.SetAttribute("topEdgeAnchor", static_cast<int>(m_topEdgeAnchor));
element.SetAttribute("bottomEdgeAnchor",
static_cast<int>(m_bottomEdgeAnchor));
}
namespace {
gd::String GetAnchorAsString(AnchorBehavior::HorizontalAnchor anchor) {
if (anchor == AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_LEFT)
@@ -247,15 +48,18 @@ gd::String GetAnchorAsString(AnchorBehavior::VerticalAnchor anchor) {
} // namespace
std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
gd::Project& project) const {
const gd::SerializerElement& behaviorContent, gd::Project& project) const {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Relative to original window size")]
.SetValue(m_relativeToOriginalWindowSize ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("relativeToOriginalWindowSize")
? "true"
: "false")
.SetType("Boolean");
properties[_("Left edge anchor")]
.SetValue(GetAnchorAsString(m_leftEdgeAnchor))
.SetValue(GetAnchorAsString(static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("leftEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
@@ -263,7 +67,8 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.AddExtraInfo(_("Proportional"));
properties[_("Right edge anchor")]
.SetValue(GetAnchorAsString(m_rightEdgeAnchor))
.SetValue(GetAnchorAsString(static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("rightEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
@@ -271,7 +76,8 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.AddExtraInfo(_("Proportional"));
properties[_("Top edge anchor")]
.SetValue(GetAnchorAsString(m_topEdgeAnchor))
.SetValue(GetAnchorAsString(static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("topEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
@@ -279,7 +85,8 @@ std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(
.AddExtraInfo(_("Proportional"));
properties[_("Bottom edge anchor")]
.SetValue(GetAnchorAsString(m_bottomEdgeAnchor))
.SetValue(GetAnchorAsString(static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("bottomEdgeAnchor"))))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
@@ -315,19 +122,27 @@ AnchorBehavior::VerticalAnchor GetVerticalAnchorFromString(
}
} // namespace
bool AnchorBehavior::UpdateProperty(const gd::String& name,
bool AnchorBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) {
if (name == _("Relative to original window size"))
m_relativeToOriginalWindowSize = (value == "1");
behaviorContent.SetAttribute("relativeToOriginalWindowSize", value == "1");
else if (name == _("Left edge anchor"))
m_leftEdgeAnchor = GetHorizontalAnchorFromString(value);
behaviorContent.SetAttribute(
"leftEdgeAnchor",
static_cast<int>(GetHorizontalAnchorFromString(value)));
else if (name == _("Right edge anchor"))
m_rightEdgeAnchor = GetHorizontalAnchorFromString(value);
behaviorContent.SetAttribute(
"rightEdgeAnchor",
static_cast<int>(GetHorizontalAnchorFromString(value)));
else if (name == _("Top edge anchor"))
m_topEdgeAnchor = GetVerticalAnchorFromString(value);
behaviorContent.SetAttribute(
"topEdgeAnchor", static_cast<int>(GetVerticalAnchorFromString(value)));
else if (name == _("Bottom edge anchor"))
m_bottomEdgeAnchor = GetVerticalAnchorFromString(value);
behaviorContent.SetAttribute(
"bottomEdgeAnchor",
static_cast<int>(GetVerticalAnchorFromString(value)));
else
return false;

View File

@@ -3,24 +3,15 @@ GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#ifndef ANCHORBEHAVIOR_H
#define ANCHORBEHAVIOR_H
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/View.hpp>
#include <SFML/System/Vector2.hpp>
#include <vector>
#include "GDCpp/Runtime/Project/Behavior.h"
#include "GDCpp/Runtime/Project/Object.h"
namespace gd {
class Layout;
}
class RuntimeScene;
namespace gd {
class SerializerElement;
}
class RuntimeScenePlatformData;
class Project;
} // namespace gd
/**
* \brief Allow to anchor objects to the window's bounds.
@@ -41,50 +32,22 @@ class GD_EXTENSION_API AnchorBehavior : public Behavior {
ANCHOR_VERTICAL_PROPORTIONAL = 3
};
AnchorBehavior();
AnchorBehavior() {};
virtual ~AnchorBehavior(){};
virtual Behavior* Clone() const override { return new AnchorBehavior(*this); }
/**
* \brief Unserialize the behavior
*/
virtual void UnserializeFrom(const gd::SerializerElement& element) override;
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize the behavior
*/
virtual void SerializeTo(gd::SerializerElement& element) const override;
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializerElement& behaviorContent,
gd::Project& project) const override;
virtual bool UpdateProperty(const gd::String& name,
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) override;
#endif
virtual void OnActivate() override;
private:
virtual void DoStepPreEvents(RuntimeScene& scene) override;
virtual void DoStepPostEvents(RuntimeScene& scene) override;
bool m_relativeToOriginalWindowSize; ///< True if the original size of the
///< game window must be used.
HorizontalAnchor m_leftEdgeAnchor;
HorizontalAnchor m_rightEdgeAnchor;
VerticalAnchor m_topEdgeAnchor;
VerticalAnchor m_bottomEdgeAnchor;
bool m_invalidDistances;
// Distances (in window's units) from the XXX edge of the object to side of
// the window the edge is anchored on. Note: If the edge anchor is set to
// PROPORTIONAL, then it contains the ratio of the distance to the window
// size.
float m_leftEdgeDistance;
float m_rightEdgeDistance;
float m_topEdgeDistance;
float m_bottomEdgeDistance;
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override;
};
#endif // ANCHORBEHAVIOR_H

View File

@@ -0,0 +1,212 @@
/**
GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#include "AnchorRuntimeBehavior.h"
#include <SFML/Window.hpp>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <memory>
#include <set>
#include "GDCore/CommonTools.h"
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Extensions/Builtin/MathematicalTools.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeGame.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
AnchorRuntimeBehavior::AnchorRuntimeBehavior(
const gd::SerializerElement& behaviorContent)
: RuntimeBehavior(behaviorContent),
m_relativeToOriginalWindowSize(true),
m_leftEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_rightEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_topEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_bottomEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_invalidDistances(true),
m_leftEdgeDistance(0.f),
m_rightEdgeDistance(0.f),
m_topEdgeDistance(0.f),
m_bottomEdgeDistance(0.f) {
m_relativeToOriginalWindowSize =
behaviorContent.GetBoolAttribute("relativeToOriginalWindowSize");
m_leftEdgeAnchor = static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("leftEdgeAnchor"));
m_rightEdgeAnchor = static_cast<HorizontalAnchor>(
behaviorContent.GetIntAttribute("rightEdgeAnchor"));
m_topEdgeAnchor = static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("topEdgeAnchor"));
m_bottomEdgeAnchor = static_cast<VerticalAnchor>(
behaviorContent.GetIntAttribute("bottomEdgeAnchor"));
}
void AnchorRuntimeBehavior::OnActivate() { m_invalidDistances = true; }
namespace {
sf::Vector2f mapFloatPixelToCoords(const sf::Vector2f& point,
const sf::RenderTarget& target,
const sf::View& view) {
// First, convert from viewport coordinates to homogeneous coordinates
sf::Vector2f normalized;
sf::IntRect viewport = target.getViewport(view);
normalized.x = -1.f + 2.f * (point.x - static_cast<float>(viewport.left)) /
static_cast<float>(viewport.width);
normalized.y = 1.f - 2.f * (point.y - static_cast<float>(viewport.top)) /
static_cast<float>(viewport.height);
// Then transform by the inverse of the view matrix
return view.getInverseTransform().transformPoint(normalized);
}
sf::Vector2f mapCoordsToFloatPixel(const sf::Vector2f& point,
const sf::RenderTarget& target,
const sf::View& view) {
// Note: almost the same as RenderTarget::mapCoordsToPixel except that the
// result is sf::Vector2f
// First, transform the point by the view matrix
sf::Vector2f normalized = view.getTransform().transformPoint(point);
// Then convert to viewport coordinates
sf::Vector2f pixel;
sf::IntRect viewport = target.getViewport(view);
pixel.x = (normalized.x + 1.f) / 2.f * static_cast<float>(viewport.width) +
static_cast<float>(viewport.left);
pixel.y = (-normalized.y + 1.f) / 2.f * static_cast<float>(viewport.height) +
static_cast<float>(viewport.top);
return pixel;
}
} // namespace
void AnchorRuntimeBehavior::DoStepPreEvents(RuntimeScene& scene) {}
void AnchorRuntimeBehavior::DoStepPostEvents(RuntimeScene& scene) {
const RuntimeLayer& layer = scene.GetRuntimeLayer(object->GetLayer());
const RuntimeCamera& firstCamera = layer.GetCamera(0);
if (m_invalidDistances) {
sf::Vector2u windowSize =
m_relativeToOriginalWindowSize
? sf::Vector2u(scene.game->getWindowOriginalWidth(),
scene.game->getWindowOriginalHeight())
: scene.renderWindow->getSize();
// Calculate the distances from the window's bounds.
sf::Vector2f topLeftPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX(), object->GetDrawableY()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
sf::Vector2f bottomRightPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX() + object->GetWidth(),
object->GetDrawableY() + object->GetHeight()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
// Left edge
if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_leftEdgeDistance = topLeftPixel.x;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_leftEdgeDistance = static_cast<float>(windowSize.x) - topLeftPixel.x;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_leftEdgeDistance = topLeftPixel.x / windowSize.x;
// Right edge
if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_rightEdgeDistance = bottomRightPixel.x;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_rightEdgeDistance =
static_cast<float>(windowSize.x) - bottomRightPixel.x;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_rightEdgeDistance = bottomRightPixel.x / windowSize.x;
// Top edge
if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_topEdgeDistance = topLeftPixel.y;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_topEdgeDistance = static_cast<float>(windowSize.y) - topLeftPixel.y;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
m_topEdgeDistance = topLeftPixel.y / static_cast<float>(windowSize.y);
// Bottom edge
if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_bottomEdgeDistance = bottomRightPixel.y;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_bottomEdgeDistance =
static_cast<float>(windowSize.y) - bottomRightPixel.y;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
m_bottomEdgeDistance =
bottomRightPixel.y / static_cast<float>(windowSize.y);
m_invalidDistances = false;
} else {
sf::Vector2u windowSize = scene.renderWindow->getSize();
// Move and resize the object if needed
sf::Vector2f topLeftPixel;
sf::Vector2f bottomRightPixel;
// Left edge
if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
topLeftPixel.x = m_leftEdgeDistance;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
topLeftPixel.x = static_cast<float>(windowSize.x) - m_leftEdgeDistance;
else if (m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
topLeftPixel.x = m_leftEdgeDistance * static_cast<float>(windowSize.x);
// Top edge
if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
topLeftPixel.y = m_topEdgeDistance;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
topLeftPixel.y = static_cast<float>(windowSize.y) - m_topEdgeDistance;
else if (m_topEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
topLeftPixel.y = m_topEdgeDistance * static_cast<float>(windowSize.y);
// Right edge
if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
bottomRightPixel.x = m_rightEdgeDistance;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
bottomRightPixel.x =
static_cast<float>(windowSize.x) - m_rightEdgeDistance;
else if (m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
bottomRightPixel.x =
m_rightEdgeDistance * static_cast<float>(windowSize.x);
// Bottom edge
if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
bottomRightPixel.y = m_bottomEdgeDistance;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
bottomRightPixel.y =
static_cast<float>(windowSize.y) - m_bottomEdgeDistance;
else if (m_bottomEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
bottomRightPixel.y =
m_bottomEdgeDistance * static_cast<float>(windowSize.y);
sf::Vector2f topLeftCoord = mapFloatPixelToCoords(
topLeftPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
sf::Vector2f bottomRightCoord = mapFloatPixelToCoords(
bottomRightPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
// Move and resize the object according to the anchors
if (m_rightEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetWidth(bottomRightCoord.x - topLeftCoord.x);
if (m_bottomEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetHeight(bottomRightCoord.y - topLeftCoord.y);
if (m_leftEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetX(topLeftCoord.x + object->GetX() - object->GetDrawableX());
if (m_topEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetY(topLeftCoord.y + object->GetY() - object->GetDrawableY());
}
}

View File

@@ -0,0 +1,72 @@
/**
GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#ifndef ANCHORRuntimeBEHAVIOR_H
#define ANCHORRuntimeBEHAVIOR_H
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/View.hpp>
#include <SFML/System/Vector2.hpp>
#include <vector>
#include "GDCpp/Runtime/RuntimeBehavior.h"
#include "GDCpp/Runtime/Project/Object.h"
namespace gd {
class Layout;
}
class RuntimeScene;
namespace gd {
class SerializerElement;
}
class RuntimeScenePlatformData;
/**
* \brief Allow to anchor objects to the window's bounds.
*/
class GD_EXTENSION_API AnchorRuntimeBehavior : public RuntimeBehavior {
public:
enum HorizontalAnchor {
ANCHOR_HORIZONTAL_NONE = 0,
ANCHOR_HORIZONTAL_WINDOW_LEFT = 1,
ANCHOR_HORIZONTAL_WINDOW_RIGHT = 2,
ANCHOR_HORIZONTAL_PROPORTIONAL = 3
};
enum VerticalAnchor {
ANCHOR_VERTICAL_NONE = 0,
ANCHOR_VERTICAL_WINDOW_TOP = 1,
ANCHOR_VERTICAL_WINDOW_BOTTOM = 2,
ANCHOR_VERTICAL_PROPORTIONAL = 3
};
AnchorRuntimeBehavior(const gd::SerializerElement& behaviorContent);
virtual ~AnchorRuntimeBehavior(){};
virtual AnchorRuntimeBehavior* Clone() const override { return new AnchorRuntimeBehavior(*this); }
virtual void OnActivate() override;
private:
virtual void DoStepPreEvents(RuntimeScene& scene) override;
virtual void DoStepPostEvents(RuntimeScene& scene) override;
bool m_relativeToOriginalWindowSize; ///< True if the original size of the
///< game window must be used.
HorizontalAnchor m_leftEdgeAnchor;
HorizontalAnchor m_rightEdgeAnchor;
VerticalAnchor m_topEdgeAnchor;
VerticalAnchor m_bottomEdgeAnchor;
bool m_invalidDistances;
// Distances (in window's units) from the XXX edge of the object to side of
// the window the edge is anchored on. Note: If the edge anchor is set to
// PROPORTIONAL, then it contains the ratio of the distance to the window
// size.
float m_leftEdgeDistance;
float m_rightEdgeDistance;
float m_topEdgeDistance;
float m_bottomEdgeDistance;
};
#endif // ANCHORRuntimeBEHAVIOR_H

View File

@@ -6,6 +6,7 @@ This project is released under the MIT License.
*/
#include "AnchorBehavior.h"
#include "AnchorRuntimeBehavior.h"
#include "GDCpp/Extensions/ExtensionBase.h"
#include "GDCpp/Runtime/Project/BehaviorsSharedData.h"
@@ -28,12 +29,6 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
"AnchorBehavior",
std::make_shared<AnchorBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("AnchorBehavior/AnchorBehavior.h");
#endif
}
/**
@@ -47,6 +42,12 @@ class AnchorBehaviorCppExtension : public ExtensionBase {
*/
AnchorBehaviorCppExtension() {
DeclareAnchorBehaviorExtension(*this);
AddRuntimeBehavior<AnchorRuntimeBehavior>(
GetBehaviorMetadata("AnchorBehavior::AnchorBehavior"),
"AnchorRuntimeBehavior");
GetBehaviorMetadata("AnchorBehavior::AnchorBehavior")
.SetIncludeFile("AnchorBehavior/AnchorRuntimeBehavior.h");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};

View File

@@ -6,57 +6,6 @@ This project is released under the MIT License.
*/
#include "DestroyOutsideBehavior.h"
#include <SFML/Graphics.hpp>
#include <iostream>
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeLayer.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
DestroyOutsideBehavior::DestroyOutsideBehavior() : extraBorder(0) {}
void DestroyOutsideBehavior::DoStepPostEvents(RuntimeScene& scene) {
bool erase = true;
const RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
float objCenterX = object->GetDrawableX() + object->GetCenterX();
float objCenterY = object->GetDrawableY() + object->GetCenterY();
for (std::size_t cameraIndex = 0; cameraIndex < theLayer.GetCameraCount();
++cameraIndex) {
const RuntimeCamera& theCamera = theLayer.GetCamera(cameraIndex);
float boundingCircleRadius =
sqrt(object->GetWidth() * object->GetWidth() +
object->GetHeight() * object->GetHeight()) /
2.0;
if (objCenterX + boundingCircleRadius + extraBorder <
theCamera.GetViewCenter().x - theCamera.GetWidth() / 2.0 ||
objCenterX - boundingCircleRadius - extraBorder >
theCamera.GetViewCenter().x + theCamera.GetWidth() / 2.0 ||
objCenterY + boundingCircleRadius + extraBorder <
theCamera.GetViewCenter().y - theCamera.GetHeight() / 2.0 ||
objCenterY - boundingCircleRadius - extraBorder >
theCamera.GetViewCenter().y + theCamera.GetHeight() / 2.0) {
// Ok we are outside the camera area.
} else {
// The object can be viewed by the camera.
erase = false;
break;
}
}
if (erase) object->DeleteFromScene(scene);
}
#if defined(GD_IDE_ONLY)
void DestroyOutsideBehavior::SerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("extraBorder", extraBorder);
}
#endif
void DestroyOutsideBehavior::UnserializeFrom(
const gd::SerializerElement& element) {
extraBorder = element.GetDoubleAttribute("extraBorder");
}
DestroyOutsideBehavior::DestroyOutsideBehavior() {}

View File

@@ -7,20 +7,15 @@ This project is released under the MIT License.
#ifndef DRAGGABLEBEHAVIOR_H
#define DRAGGABLEBEHAVIOR_H
#include <SFML/System/Vector2.hpp>
#include <map>
#include "GDCpp/Runtime/Project/Behavior.h"
#include "GDCpp/Runtime/Project/Object.h"
class RuntimeScene;
namespace gd {
class SerializerElement;
}
namespace gd {
class Layout;
}
/**
* \brief Behavior that allows objects to be dragged with the mouse
* \brief Behavior that allows objects to be dragged with the mouse.
*/
class GD_EXTENSION_API DestroyOutsideBehavior : public Behavior {
public:
@@ -28,34 +23,7 @@ class GD_EXTENSION_API DestroyOutsideBehavior : public Behavior {
virtual ~DestroyOutsideBehavior(){};
virtual Behavior* Clone() const { return new DestroyOutsideBehavior(*this); }
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize the behavior.
*/
virtual void SerializeTo(gd::SerializerElement& element) const;
#endif
/**
* \brief Unserialize the behavior.
*/
virtual void UnserializeFrom(const gd::SerializerElement& element);
/**
* \brief Return the value of the extra border.
*/
bool GetExtraBorder() const { return extraBorder; };
/**
* \brief Set the value of the extra border, i.e the supplementary margin that
* the object must cross before being deleted.
*/
void SetExtraBorder(float extraBorder_) { extraBorder = extraBorder_; };
private:
virtual void DoStepPostEvents(RuntimeScene& scene);
float extraBorder; ///< The supplementary margin outside the screen that the
///< object must cross before being deleted.
};
#endif // DRAGGABLEBEHAVIOR_H

View File

@@ -0,0 +1,55 @@
/**
GDevelop - DestroyOutside Behavior Extension
Copyright (c) 2014-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "DestroyOutsideRuntimeBehavior.h"
#include <SFML/Graphics.hpp>
#include <iostream>
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeLayer.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
DestroyOutsideRuntimeBehavior::DestroyOutsideRuntimeBehavior(
const gd::SerializerElement& behaviorContent)
: RuntimeBehavior(behaviorContent), extraBorder(0) {
extraBorder = behaviorContent.GetDoubleAttribute("extraBorder");
}
void DestroyOutsideRuntimeBehavior::DoStepPostEvents(RuntimeScene& scene) {
bool erase = true;
const RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
float objCenterX = object->GetDrawableX() + object->GetCenterX();
float objCenterY = object->GetDrawableY() + object->GetCenterY();
for (std::size_t cameraIndex = 0; cameraIndex < theLayer.GetCameraCount();
++cameraIndex) {
const RuntimeCamera& theCamera = theLayer.GetCamera(cameraIndex);
float boundingCircleRadius =
sqrt(object->GetWidth() * object->GetWidth() +
object->GetHeight() * object->GetHeight()) /
2.0;
if (objCenterX + boundingCircleRadius + extraBorder <
theCamera.GetViewCenter().x - theCamera.GetWidth() / 2.0 ||
objCenterX - boundingCircleRadius - extraBorder >
theCamera.GetViewCenter().x + theCamera.GetWidth() / 2.0 ||
objCenterY + boundingCircleRadius + extraBorder <
theCamera.GetViewCenter().y - theCamera.GetHeight() / 2.0 ||
objCenterY - boundingCircleRadius - extraBorder >
theCamera.GetViewCenter().y + theCamera.GetHeight() / 2.0) {
// Ok we are outside the camera area.
} else {
// The object can be viewed by the camera.
erase = false;
break;
}
}
if (erase) object->DeleteFromScene(scene);
}

View File

@@ -0,0 +1,47 @@
/**
GDevelop - DestroyOutside Behavior Extension
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#ifndef DESTROYOUTSIDERUNTIMEBEHAVIOR_H
#define DESTROYOUTSIDERUNTIMEBEHAVIOR_H
#include <map>
#include "GDCpp/Runtime/Project/Object.h"
#include "GDCpp/Runtime/RuntimeBehavior.h"
class RuntimeScene;
namespace gd {
class SerializerElement;
}
/**
* \brief Behavior that allows objects to be dragged with the mouse
*/
class GD_EXTENSION_API DestroyOutsideRuntimeBehavior : public RuntimeBehavior {
public:
DestroyOutsideRuntimeBehavior(const gd::SerializerElement& behaviorContent);
virtual ~DestroyOutsideRuntimeBehavior(){};
virtual DestroyOutsideRuntimeBehavior* Clone() const {
return new DestroyOutsideRuntimeBehavior(*this);
}
/**
* \brief Return the value of the extra border.
*/
bool GetExtraBorder() const { return extraBorder; };
/**
* \brief Set the value of the extra border, i.e the supplementary margin that
* the object must cross before being deleted.
*/
void SetExtraBorder(float extraBorder_) { extraBorder = extraBorder_; };
private:
virtual void DoStepPostEvents(RuntimeScene& scene);
float extraBorder; ///< The supplementary margin outside the screen that the
///< object must cross before being deleted.
};
#endif // DESTROYOUTSIDERUNTIMEBEHAVIOR_H

View File

@@ -5,18 +5,19 @@ Copyright (c) 2014-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "DestroyOutsideBehavior.h"
#include "DestroyOutsideRuntimeBehavior.h"
#include "GDCpp/Extensions/ExtensionBase.h"
#include "DestroyOutsideBehavior.h"
void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
extension.SetExtensionInformation(
"DestroyOutsideBehavior",
_("Destroy Outside Screen Behavior"),
_("This Extension can be used to destroy objects when they go outside of "
"the borders of the game's window."),
"Florian Rival",
"Open source (MIT License)")
extension
.SetExtensionInformation("DestroyOutsideBehavior",
_("Destroy Outside Screen Behavior"),
_("This Extension can be used to destroy "
"objects when they go outside of "
"the borders of the game's window."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/behaviors/destroyoutside");
gd::BehaviorMetadata& aut =
@@ -32,8 +33,6 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
std::shared_ptr<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideBehavior.h");
aut.AddCondition("ExtraBorder",
_("Additional border"),
_("Compare the additional border that the object must cross "
@@ -49,7 +48,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetFunctionName("GetExtraBorder")
.SetManipulatedType("number")
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideBehavior.h");
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideRuntimeBehavior.h");
aut.AddAction("ExtraBorder",
_("Additional border"),
@@ -67,7 +66,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetExtraBorder")
.SetManipulatedType("number")
.SetGetter("GetExtraBorder")
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideBehavior.h");
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideRuntimeBehavior.h");
#endif
}
@@ -82,6 +81,12 @@ class DestroyOutsideBehaviorCppExtension : public ExtensionBase {
*/
DestroyOutsideBehaviorCppExtension() {
DeclareDestroyOutsideBehaviorExtension(*this);
AddRuntimeBehavior<DestroyOutsideRuntimeBehavior>(
GetBehaviorMetadata("DestroyOutsideBehavior::DestroyOutside"),
"DestroyOutsideRuntimeBehavior");
GetBehaviorMetadata("DestroyOutsideBehavior::DestroyOutside")
.SetIncludeFile("DestroyOutsideBehavior/DestroyOutsideRuntimeBehavior.h");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};

View File

@@ -6,68 +6,6 @@ This project is released under the MIT License.
*/
#include "DraggableBehavior.h"
#include <SFML/Graphics.hpp>
#include <iostream>
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeLayer.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
bool DraggableBehavior::somethingDragged = false;
bool DraggableBehavior::leftPressedLastFrame = false;
DraggableBehavior::DraggableBehavior() : dragged(false) {}
void DraggableBehavior::DoStepPreEvents(RuntimeScene& scene) {
// Begin drag ?
if (!dragged && scene.GetInputManager().IsMouseButtonPressed("Left") &&
!leftPressedLastFrame && !somethingDragged) {
RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
for (std::size_t cameraIndex = 0; cameraIndex < theLayer.GetCameraCount();
++cameraIndex) {
sf::Vector2f mousePos = scene.renderWindow->mapPixelToCoords(
scene.GetInputManager().GetMousePosition(),
theLayer.GetCamera(cameraIndex).GetSFMLView());
if (object->GetDrawableX() <= mousePos.x &&
object->GetDrawableX() + object->GetWidth() >= mousePos.x &&
object->GetDrawableY() <= mousePos.y &&
object->GetDrawableY() + object->GetHeight() >= mousePos.y) {
dragged = true;
somethingDragged = true;
xOffset = mousePos.x - object->GetX();
yOffset = mousePos.y - object->GetY();
dragCameraIndex = cameraIndex;
break;
}
}
}
// End dragging ?
else if (!scene.GetInputManager().IsMouseButtonPressed("Left")) {
dragged = false;
somethingDragged = false;
}
// Being dragging ?
if (dragged) {
RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
sf::Vector2f mousePos = scene.renderWindow->mapPixelToCoords(
scene.GetInputManager().GetMousePosition(),
theLayer.GetCamera(dragCameraIndex).GetSFMLView());
object->SetX(mousePos.x - xOffset);
object->SetY(mousePos.y - yOffset);
}
}
void DraggableBehavior::DoStepPostEvents(RuntimeScene& scene) {
leftPressedLastFrame = scene.GetInputManager().IsMouseButtonPressed("Left");
}
void DraggableBehavior::OnDeActivate() {
if (dragged) somethingDragged = false;
dragged = false;
}
DraggableBehavior::DraggableBehavior() {}

View File

@@ -14,8 +14,6 @@ This project is released under the MIT License.
class RuntimeScene;
namespace gd {
class SerializerElement;
}
namespace gd {
class Layout;
}
@@ -28,26 +26,7 @@ class GD_EXTENSION_API DraggableBehavior : public Behavior {
virtual ~DraggableBehavior(){};
virtual Behavior* Clone() const { return new DraggableBehavior(*this); }
/**
* \brief Return true if the object is being dragged.
*/
bool IsDragged() const { return dragged; };
virtual void OnDeActivate();
private:
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
float xOffset;
float yOffset;
std::size_t dragCameraIndex; ///< The camera being used to move the object. (
///< The layer is the object's layer ).
bool dragged; ///< True if the object is being dragged.
static bool somethingDragged; ///< Used to avoid start dragging an object
///< while another is being dragged.
static bool
leftPressedLastFrame; ///< Used to only start dragging when clicking.
};
#endif // DRAGGABLEBEHAVIOR_H

View File

@@ -0,0 +1,75 @@
/**
GDevelop - Draggable Behavior Extension
Copyright (c) 2014-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "DraggableRuntimeBehavior.h"
#include <SFML/Graphics.hpp>
#include <iostream>
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeLayer.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
bool DraggableRuntimeBehavior::somethingDragged = false;
bool DraggableRuntimeBehavior::leftPressedLastFrame = false;
DraggableRuntimeBehavior::DraggableRuntimeBehavior(
const gd::SerializerElement& behaviorContent)
: RuntimeBehavior(behaviorContent), dragged(false) {}
void DraggableRuntimeBehavior::DoStepPreEvents(RuntimeScene& scene) {
// Begin drag ?
if (!dragged && scene.GetInputManager().IsMouseButtonPressed("Left") &&
!leftPressedLastFrame && !somethingDragged) {
RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
for (std::size_t cameraIndex = 0; cameraIndex < theLayer.GetCameraCount();
++cameraIndex) {
sf::Vector2f mousePos = scene.renderWindow->mapPixelToCoords(
scene.GetInputManager().GetMousePosition(),
theLayer.GetCamera(cameraIndex).GetSFMLView());
if (object->GetDrawableX() <= mousePos.x &&
object->GetDrawableX() + object->GetWidth() >= mousePos.x &&
object->GetDrawableY() <= mousePos.y &&
object->GetDrawableY() + object->GetHeight() >= mousePos.y) {
dragged = true;
somethingDragged = true;
xOffset = mousePos.x - object->GetX();
yOffset = mousePos.y - object->GetY();
dragCameraIndex = cameraIndex;
break;
}
}
}
// End dragging ?
else if (!scene.GetInputManager().IsMouseButtonPressed("Left")) {
dragged = false;
somethingDragged = false;
}
// Being dragging ?
if (dragged) {
RuntimeLayer& theLayer = scene.GetRuntimeLayer(object->GetLayer());
sf::Vector2f mousePos = scene.renderWindow->mapPixelToCoords(
scene.GetInputManager().GetMousePosition(),
theLayer.GetCamera(dragCameraIndex).GetSFMLView());
object->SetX(mousePos.x - xOffset);
object->SetY(mousePos.y - yOffset);
}
}
void DraggableRuntimeBehavior::DoStepPostEvents(RuntimeScene& scene) {
leftPressedLastFrame = scene.GetInputManager().IsMouseButtonPressed("Left");
}
void DraggableRuntimeBehavior::OnDeActivate() {
if (dragged) somethingDragged = false;
dragged = false;
}

View File

@@ -0,0 +1,50 @@
/**
GDevelop - Draggable Behavior Extension
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#ifndef DRAGGABLERUNTIMEBEHAVIOR_H
#define DRAGGABLERUNTIMEBEHAVIOR_H
#include "GDCpp/Runtime/Project/Object.h"
#include "GDCpp/Runtime/RuntimeBehavior.h"
class RuntimeScene;
namespace gd {
class SerializerElement;
}
/**
* \brief Behavior that allows objects to be dragged with the mouse
*/
class GD_EXTENSION_API DraggableRuntimeBehavior : public RuntimeBehavior {
public:
DraggableRuntimeBehavior(const gd::SerializerElement& behaviorContent);
virtual ~DraggableRuntimeBehavior(){};
virtual DraggableRuntimeBehavior* Clone() const {
return new DraggableRuntimeBehavior(*this);
}
/**
* \brief Return true if the object is being dragged.
*/
bool IsDragged() const { return dragged; };
virtual void OnDeActivate();
private:
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
float xOffset;
float yOffset;
std::size_t dragCameraIndex; ///< The camera being used to move the object. (
///< The layer is the object's layer ).
bool dragged; ///< True if the object is being dragged.
static bool somethingDragged; ///< Used to avoid start dragging an object
///< while another is being dragged.
static bool
leftPressedLastFrame; ///< Used to only start dragging when clicking.
};
#endif // DRAGGABLERUNTIMEBEHAVIOR_H

View File

@@ -6,8 +6,8 @@ This project is released under the MIT License.
*/
#include "GDCpp/Extensions/ExtensionBase.h"
#include "DraggableBehavior.h"
#include "DraggableRuntimeBehavior.h"
void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
extension.SetExtensionInformation(
@@ -30,8 +30,6 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
std::shared_ptr<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("DraggableBehavior/DraggableBehavior.h");
aut.AddCondition("Dragged",
_("Being dragged"),
_("Check if the object is being dragged"),
@@ -42,9 +40,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "Draggable")
.SetFunctionName("IsDragged")
.SetIncludeFile("DraggableBehavior/DraggableBehavior.h");
.SetFunctionName("IsDragged");
#endif
}
@@ -59,6 +55,11 @@ class Extension : public ExtensionBase {
*/
Extension() {
DeclareDraggableBehaviorExtension(*this);
AddRuntimeBehavior<DraggableRuntimeBehavior>(
GetBehaviorMetadata("DraggableBehavior::Draggable"),
"DraggableRuntimeBehavior");
GetBehaviorMetadata("DraggableBehavior::Draggable")
.SetIncludeFile("DraggableBehavior/DraggableRuntimeBehavior.h");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};

View File

@@ -29,7 +29,7 @@ gdjs.DraggableRuntimeBehavior.prototype.onDeActivate = function() {
this._endDrag();
};
gdjs.DraggableRuntimeBehavior.prototype.ownerRemovedFromScene = function() {
gdjs.DraggableRuntimeBehavior.prototype.onOwnerRemovedFromScene = function() {
this.onDeActivate();
};

View File

@@ -14,59 +14,59 @@ module.exports = {
createExtension: function(_, gd) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
"MyDummyExtension",
"My Dummy Extension",
"An example of a declaration of an extension",
"Florian Rival",
"MIT"
'MyDummyExtension',
'My Dummy Extension',
'An example of a declaration of an extension',
'Florian Rival',
'MIT'
);
// Declare conditions, actions or expressions:
extension
.addCondition(
"MyNewCondition",
_("Dummy condition example"),
'MyNewCondition',
_('Dummy condition example'),
_(
"This is an example of a condition displayed in the events sheet. Will return true if the number is less than 10 and the length of the text is less than 5."
'This is an example of a condition displayed in the events sheet. Will return true if the number is less than 10 and the length of the text is less than 5.'
),
_("Call the example condition with _PARAM0_ and _PARAM1_"),
_("Dummy Extension"),
"res/conditions/camera24.png",
"res/conditions/camera.png"
_('Call the example condition with _PARAM0_ and _PARAM1_'),
_('Dummy Extension'),
'res/conditions/camera24.png',
'res/conditions/camera.png'
)
.addParameter("expression", _("Number 1"), "", false)
.addParameter("string", _("Text 1"), "", false)
.addParameter('expression', _('Number 1'), '', false)
.addParameter('string', _('Text 1'), '', false)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/ExampleJsExtension/examplejsextensiontools.js"
'Extensions/ExampleJsExtension/examplejsextensiontools.js'
)
.setFunctionName("gdjs.evtTools.exampleJsExtension.myConditionFunction");
.setFunctionName('gdjs.evtTools.exampleJsExtension.myConditionFunction');
extension
.addExpression(
"DummyExpression",
_("Dummy expression example"),
_("This is an example of an expression"),
_("Dummy Extension"),
"res/actions/camera.png"
'DummyExpression',
_('Dummy expression example'),
_('This is an example of an expression'),
_('Dummy Extension'),
'res/actions/camera.png'
)
.addParameter("expression", _("Maximum"), "", false)
.addParameter('expression', _('Maximum'), '', false)
.getCodeExtraInformation()
.setFunctionName("gdjs.random");
.setFunctionName('gdjs.random');
extension
.addStrExpression(
"DummyStrExpression",
_("Dummy string expression example"),
_("This is an example of an expression returning a string"),
_("Dummy Extension"),
"res/actions/camera.png"
'DummyStrExpression',
_('Dummy string expression example'),
_('This is an example of an expression returning a string'),
_('Dummy Extension'),
'res/actions/camera.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/ExampleJsExtension/examplejsextensiontools.js"
'Extensions/ExampleJsExtension/examplejsextensiontools.js'
)
.setFunctionName("gdjs.evtTools.exampleJsExtension.getString");
.setFunctionName('gdjs.evtTools.exampleJsExtension.getString');
// Declare a behavior.
// Create a new gd.BehaviorJsImplementation object and implement the methods
@@ -79,12 +79,12 @@ module.exports = {
propertyName,
newValue
) {
if (propertyName === "My first property") {
behaviorContent.property1 = newValue;
if (propertyName === 'My first property') {
behaviorContent.setStringAttribute('property1', newValue);
return true;
}
if (propertyName === "My other property") {
behaviorContent.property2 = newValue === "1";
if (propertyName === 'My other property') {
behaviorContent.setBoolAttribute('property2', newValue === '1');
return true;
}
@@ -94,40 +94,40 @@ module.exports = {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
behaviorProperties.set(
"My first property",
new gd.PropertyDescriptor(behaviorContent.property1)
'My first property',
new gd.PropertyDescriptor(
behaviorContent.getStringAttribute('property1')
)
);
behaviorProperties.set(
"My other property",
'My other property',
new gd.PropertyDescriptor(
behaviorContent.property2 ? "true" : "false"
).setType("Boolean")
behaviorContent.getBoolAttribute('property2') ? 'true' : 'false'
).setType('Boolean')
);
return behaviorProperties;
};
dummyBehavior.setRawJSONContent(
JSON.stringify({
property1: "Initial value 1",
property2: true
})
);
dummyBehavior.initializeContent = function(behaviorContent) {
behaviorContent.setStringAttribute('property1', 'Initial value 1');
behaviorContent.setBoolAttribute('property2', true);
};
extension
.addBehavior(
"DummyBehavior",
_("Dummy behavior for testing"),
"DummyBehavior",
_("This dummy behavior does nothing"),
"",
"CppPlatform/Extensions/topdownmovementicon.png",
"DummyBehavior",
'DummyBehavior',
_('Dummy behavior for testing'),
'DummyBehavior',
_('This dummy behavior does nothing'),
'',
'CppPlatform/Extensions/topdownmovementicon.png',
'DummyBehavior',
dummyBehavior,
new gd.BehaviorsSharedData()
)
.setIncludeFile("Extensions/ExampleJsExtension/dummyruntimebehavior.js")
.setIncludeFile('Extensions/ExampleJsExtension/dummyruntimebehavior.js')
// You can optionally include more than one file when the behavior is used:
.addIncludeFile(
"Extensions/ExampleJsExtension/examplejsextensiontools.js"
'Extensions/ExampleJsExtension/examplejsextensiontools.js'
);
// Declare another behavior, with shared data between the behaviors
@@ -140,8 +140,8 @@ module.exports = {
propertyName,
newValue
) {
if (propertyName === "My behavior property") {
behaviorContent.property1 = newValue;
if (propertyName === 'My behavior property') {
behaviorContent.setStringAttribute('property1', newValue);
return true;
}
@@ -151,17 +151,17 @@ module.exports = {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
behaviorProperties.set(
"My behavior property",
new gd.PropertyDescriptor(behaviorContent.property1)
'My behavior property',
new gd.PropertyDescriptor(
behaviorContent.getStringAttribute('property1')
)
);
return behaviorProperties;
};
dummyBehaviorWithSharedData.setRawJSONContent(
JSON.stringify({
property1: "Initial value 1"
})
);
dummyBehaviorWithSharedData.initializeContent = function(behaviorContent) {
behaviorContent.setStringAttribute('property1', 'Initial value 1');
};
var sharedData = new gd.BehaviorSharedDataJsImplementation();
sharedData.updateProperty = function(
@@ -169,8 +169,8 @@ module.exports = {
propertyName,
newValue
) {
if (propertyName === "My shared property") {
sharedContent.sharedProperty1 = newValue;
if (propertyName === 'My shared property') {
sharedContent.setStringAttribute('sharedProperty1', newValue);
return true;
}
@@ -180,35 +180,39 @@ module.exports = {
var sharedProperties = new gd.MapStringPropertyDescriptor();
sharedProperties.set(
"My shared property",
new gd.PropertyDescriptor(sharedContent.sharedProperty1 || "")
'My shared property',
new gd.PropertyDescriptor(
sharedContent.getStringAttribute('sharedProperty1')
)
);
return sharedProperties;
};
sharedData.setRawJSONContent(
JSON.stringify({
sharedProperty1: "Initial shared value 1"
})
);
sharedData.initializeContent = function(behaviorContent) {
behaviorContent.setStringAttribute(
'sharedProperty1',
'Initial shared value 1'
);
};
extension
.addBehavior(
"DummyBehaviorWithSharedData",
_("Dummy behavior with shared data for testing"),
"DummyBehaviorWithSharedData",
_("This dummy behavior uses shared data and does nothing"),
"",
"CppPlatform/Extensions/topdownmovementicon.png",
"DummyBehaviorWithSharedData",
'DummyBehaviorWithSharedData',
_('Dummy behavior with shared data for testing'),
'DummyBehaviorWithSharedData',
_('This dummy behavior uses shared data and does nothing'),
'',
'CppPlatform/Extensions/topdownmovementicon.png',
'DummyBehaviorWithSharedData',
dummyBehaviorWithSharedData,
sharedData
)
.setIncludeFile(
"Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.js"
'Extensions/ExampleJsExtension/dummywithshareddataruntimebehavior.js'
)
// You can optionally include more than one file when the behavior is used:
.addIncludeFile(
"Extensions/ExampleJsExtension/examplejsextensiontools.js"
'Extensions/ExampleJsExtension/examplejsextensiontools.js'
);
// Declare an object.
@@ -223,19 +227,19 @@ module.exports = {
propertyName,
newValue
) {
if (propertyName === "My first property") {
if (propertyName === 'My first property') {
objectContent.property1 = newValue;
return true;
}
if (propertyName === "My other property") {
objectContent.property2 = newValue === "1";
if (propertyName === 'My other property') {
objectContent.property2 = newValue === '1';
return true;
}
if (propertyName === "My third property") {
if (propertyName === 'My third property') {
objectContent.property3 = newValue;
return true;
}
if (propertyName === "myImage") {
if (propertyName === 'myImage') {
objectContent.myImage = newValue;
return true;
}
@@ -246,26 +250,26 @@ module.exports = {
var objectProperties = new gd.MapStringPropertyDescriptor();
objectProperties.set(
"My first property",
'My first property',
new gd.PropertyDescriptor(objectContent.property1)
);
objectProperties.set(
"My other property",
'My other property',
new gd.PropertyDescriptor(
objectContent.property2 ? "true" : "false"
).setType("boolean")
objectContent.property2 ? 'true' : 'false'
).setType('boolean')
);
objectProperties.set(
"My third property",
'My third property',
new gd.PropertyDescriptor(objectContent.property3.toString()).setType(
"number"
'number'
)
);
objectProperties.set(
"myImage",
'myImage',
new gd.PropertyDescriptor(objectContent.myImage)
.setType("resource")
.addExtraInfo("image")
.setType('resource')
.addExtraInfo('image')
.setLabel(
_("Image resource (won't be shown, just for demonstration purpose)")
)
@@ -275,10 +279,10 @@ module.exports = {
};
dummyObject.setRawJSONContent(
JSON.stringify({
property1: "Hello world",
property1: 'Hello world',
property2: true,
property3: 123,
myImage: ""
myImage: '',
})
);
@@ -290,12 +294,12 @@ module.exports = {
project,
layout
) {
if (propertyName === "My instance property") {
instance.setRawStringProperty("instanceprop1", newValue);
if (propertyName === 'My instance property') {
instance.setRawStringProperty('instanceprop1', newValue);
return true;
}
if (propertyName === "My other instance property") {
instance.setRawFloatProperty("instanceprop2", parseFloat(newValue));
if (propertyName === 'My other instance property') {
instance.setRawFloatProperty('instanceprop2', parseFloat(newValue));
return true;
}
@@ -310,16 +314,16 @@ module.exports = {
var instanceProperties = new gd.MapStringPropertyDescriptor();
instanceProperties.set(
"My instance property",
'My instance property',
new gd.PropertyDescriptor(
instance.getRawStringProperty("instanceprop1")
instance.getRawStringProperty('instanceprop1')
)
);
instanceProperties.set(
"My other instance property",
'My other instance property',
new gd.PropertyDescriptor(
instance.getRawFloatProperty("instanceprop2").toString()
).setType("number")
instance.getRawFloatProperty('instanceprop2').toString()
).setType('number')
);
return instanceProperties;
@@ -327,34 +331,34 @@ module.exports = {
const object = extension
.addObject(
"DummyObject",
_("Dummy object for testing"),
_("This dummy object does nothing"),
"CppPlatform/Extensions/topdownmovementicon.png",
'DummyObject',
_('Dummy object for testing'),
_('This dummy object does nothing'),
'CppPlatform/Extensions/topdownmovementicon.png',
dummyObject
)
.setIncludeFile("Extensions/ExampleJsExtension/dummyruntimeobject.js")
.setIncludeFile('Extensions/ExampleJsExtension/dummyruntimeobject.js')
.addIncludeFile(
"Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.js"
'Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.js'
);
object
.addAction(
"MyMethod",
_("Display a dummy text in Developer console"),
'MyMethod',
_('Display a dummy text in Developer console'),
_(
"Display a dummy text in Developer console. Open it with CTRL-SHIFT-J (Cmd-Alt-J on macOS)."
'Display a dummy text in Developer console. Open it with CTRL-SHIFT-J (Cmd-Alt-J on macOS).'
),
_("Display a dummy text for _PARAM0_, with params: _PARAM1_, _PARAM2_"),
"",
"res/conditions/camera24.png",
"res/conditions/camera.png"
_('Display a dummy text for _PARAM0_, with params: _PARAM1_, _PARAM2_'),
'',
'res/conditions/camera24.png',
'res/conditions/camera.png'
)
.addParameter("object", _("Object"), "DummyObject", false) // This parameter is mandatory for any object action/condition
.addParameter("expression", _("Number 1"), "", false)
.addParameter("string", _("Text 1"), "", false)
.addParameter('object', _('Object'), 'DummyObject', false) // This parameter is mandatory for any object action/condition
.addParameter('expression', _('Number 1'), '', false)
.addParameter('string', _('Text 1'), '', false)
.getCodeExtraInformation()
.setFunctionName("myMethod");
.setFunctionName('myMethod');
return extension;
},
@@ -371,22 +375,22 @@ module.exports = {
*/
runExtensionSanityTests: function(gd, extension) {
const dummyBehavior = extension
.getBehaviorMetadata("MyDummyExtension::DummyBehavior")
.getBehaviorMetadata('MyDummyExtension::DummyBehavior')
.get();
const sharedData = extension
.getBehaviorMetadata("MyDummyExtension::DummyBehaviorWithSharedData")
.getBehaviorMetadata('MyDummyExtension::DummyBehaviorWithSharedData')
.getSharedDataInstance();
return [
gd.ProjectHelper.sanityCheckBehaviorProperty(
dummyBehavior,
"My first property",
"Testing value"
'My first property',
'Testing value'
),
gd.ProjectHelper.sanityCheckBehaviorsSharedDataProperty(
sharedData,
"My shared property",
"Testing value"
)
'My shared property',
'Testing value'
),
];
},
/**
@@ -396,7 +400,7 @@ module.exports = {
*/
registerEditorConfigurations: function(objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
"MyDummyExtension::DummyObject",
'MyDummyExtension::DummyObject',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor()
);
},
@@ -435,8 +439,8 @@ module.exports = {
);
//Setup the PIXI object:
this._pixiObject = new PIXI.Text("This is a dummy object", {
align: "left"
this._pixiObject = new PIXI.Text('This is a dummy object', {
align: 'left',
});
this._pixiObject.anchor.x = 0.5;
this._pixiObject.anchor.y = 0.5;
@@ -455,7 +459,7 @@ module.exports = {
resourcesLoader,
object
) {
return "CppPlatform/Extensions/texticon24.png";
return 'CppPlatform/Extensions/texticon24.png';
};
/**
@@ -465,7 +469,7 @@ module.exports = {
// Read a property from the object
const property1Value = this._associatedObject
.getProperties(this.project)
.get("My first property")
.get('My first property')
.getValue();
this._pixiObject.text = property1Value;
@@ -496,8 +500,8 @@ module.exports = {
};
objectsRenderingService.registerInstanceRenderer(
"MyDummyExtension::DummyObject",
'MyDummyExtension::DummyObject',
RenderedDummyObjectInstance
);
}
},
};

View File

@@ -12,8 +12,8 @@ gdjs.DummyRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.RuntimeBehavior.call(this, runtimeScene, behaviorData, owner);
// Here you can access to the behavior data (JSON declared in JsExtension.js)
// using behaviorData.content:
this._textToSet = behaviorData.content.property1;
// using behaviorData:
this._textToSet = behaviorData.property1;
// You can also run arbitrary code at the creation of the behavior:
console.log("DummyRuntimeBehavior was created for object:", owner);

View File

@@ -115,4 +115,4 @@ gdjs.DummyRuntimeObject.prototype.myMethod = function(number1, text1) {
console.log("Congrats, this method was called on a DummyRuntimeObject");
console.log("Here is the object:", this);
console.log("Here are the arguments passed from events:", number1, text1);
};
};

View File

@@ -8,12 +8,12 @@ gdjs.DummyWithSharedDataRuntimeBehavior = function(runtimeScene, behaviorData, o
gdjs.RuntimeBehavior.call(this, runtimeScene, behaviorData, owner);
// Here you can access to the behavior data (JSON declared in JsExtension.js)
// using behaviorData.content:
this._textToSet = behaviorData.content.property1;
// using behaviorData:
this._textToSet = behaviorData.property1;
// You can also access to the shared data:
var sharedData = runtimeScene.getInitialSharedDataForBehavior(behaviorData.name);
this._textToSet = sharedData.content.sharedProperty1;
this._textToSet = sharedData.sharedProperty1;
// You can also run arbitrary code at the creation of the behavior:
console.log("DummyWithSharedDataRuntimeBehavior was created for object:", owner);

View File

@@ -120,7 +120,7 @@ module.exports = {
_(
'Save a scene variable (including, for structure, all the children) into a file in JSON format. Only use this on small files to avoid any lag or freeze during the the game execution.'
),
_('Save scene variable _PARAM0_ into file PARAM1 as JSON'),
_('Save scene variable _PARAM0_ into file _PARAM1_ as JSON'),
_('Filesystem/Windows, Linux, MacOS'),
'JsPlatform/Extensions/filesystem_save_file24.png',
'JsPlatform/Extensions/filesystem_save_file32.png'
@@ -146,7 +146,7 @@ module.exports = {
_(
"Save the scene variable (including, for structures, all the children) into a file in JSON format, asynchronously. Use this for large files to avoid any lag or freeze during game execution. The 'result' variable gets updated when the operation has finished."
),
_('Save scene variable _PARAM0_ into file PARAM1 as JSON'),
_('Save scene variable _PARAM0_ into file _PARAM1_ as JSON'),
_('Filesystem/Windows, Linux, MacOS/Asynchronous'),
'JsPlatform/Extensions/filesystem_save_file24.png',
'JsPlatform/Extensions/filesystem_save_file32.png'

View File

@@ -58,7 +58,7 @@ gdjs.PanelSpriteRuntimeObjectCocosRenderer.prototype._createTilingShaderAndUnifo
}
};
gdjs.PanelSpriteRuntimeObjectCocosRenderer.prototype.ownerRemovedFromScene = function() {
gdjs.PanelSpriteRuntimeObjectCocosRenderer.prototype.onOwnerRemovedFromScene = function() {
if (this._centerSpriteShader && this._centerSpriteShader.shader)
this._centerSpriteShader.shader.release();
if (this._rightSpriteShader && this._rightSpriteShader.shader)

View File

@@ -40,8 +40,8 @@ gdjs.PanelSpriteRuntimeObject.prototype.getRendererObject = function() {
gdjs.PanelSpriteRuntimeObject.prototype.onDeletedFromScene = function(runtimeScene) {
gdjs.RuntimeObject.prototype.onDeletedFromScene.call(this, runtimeScene);
if (this._renderer.ownerRemovedFromScene) {
this._renderer.ownerRemovedFromScene();
if (this._renderer.onOwnerRemovedFromScene) {
this._renderer.onOwnerRemovedFromScene();
}
};

View File

@@ -9,6 +9,8 @@ This project is released under the MIT License.
#include "GDCpp/Runtime/Project/BehaviorsSharedData.h"
#include "PathfindingBehavior.h"
#include "PathfindingObstacleBehavior.h"
#include "PathfindingRuntimeBehavior.h"
#include "PathfindingObstacleRuntimeBehavior.h"
void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
extension.SetExtensionInformation(
@@ -34,7 +36,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
aut.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("SetDestination",
_("Move to a position"),
@@ -50,7 +52,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Destination X position"))
.AddParameter("expression", _("Destination Y position"))
.SetFunctionName("MoveTo")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("PathFound",
_("Path found"),
@@ -63,7 +65,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("PathFound")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("DestinationReached",
_("Destination reached"),
@@ -76,7 +78,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("DestinationReached")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("CellWidth",
_("Width of the cells"),
@@ -94,7 +96,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetCellWidth")
.SetGetter("GetCellWidth")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition(
"CellWidth",
@@ -111,7 +113,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Width (pixels)"))
.SetFunctionName("GetCellWidth")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("CellHeight",
_("Height of the cells"),
@@ -129,7 +131,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetCellHeight")
.SetGetter("GetCellHeight")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition(
"CellHeight",
@@ -146,7 +148,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Height (pixels)"))
.SetFunctionName("GetCellHeight")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction(
"Acceleration",
@@ -164,7 +166,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetAcceleration")
.SetGetter("GetAcceleration")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("Acceleration",
_("Acceleration"),
@@ -180,7 +182,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.SetFunctionName("GetAcceleration")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction(
"MaxSpeed",
@@ -198,7 +200,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetMaxSpeed")
.SetGetter("GetMaxSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("MaxSpeed",
_("Maximum speed"),
@@ -214,7 +216,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.SetFunctionName("GetMaxSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("Speed",
_("Speed"),
@@ -231,7 +233,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetSpeed")
.SetGetter("GetSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("Speed",
_("Speed"),
@@ -247,7 +249,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.SetFunctionName("GetSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("AngularMaxSpeed",
_("Angular maximum speed"),
@@ -265,7 +267,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetAngularMaxSpeed")
.SetGetter("GetAngularMaxSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition(
"AngularMaxSpeed",
@@ -282,7 +284,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.SetFunctionName("GetAngularMaxSpeed")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction(
"AngleOffset",
@@ -301,7 +303,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetAngleOffset")
.SetGetter("GetAngleOffset")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("AngleOffset",
_("Rotation offset"),
@@ -317,7 +319,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.SetFunctionName("GetAngleOffset")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction(
"ExtraBorder",
@@ -336,7 +338,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetExtraBorder")
.SetGetter("GetExtraBorder")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("ExtraBorder",
_("Extra border"),
@@ -354,7 +356,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value (in pixels)"))
.SetFunctionName("GetExtraBorder")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction(
"AllowDiagonals",
@@ -369,7 +371,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("yesorno", _("Allow?"))
.SetFunctionName("SetAllowDiagonals")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("DiagonalsAllowed",
_("Diagonal movement"),
@@ -383,7 +385,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("DiagonalsAllowed")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddAction("RotateObject",
_("Rotate the object"),
@@ -397,7 +399,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("yesorno", _("Rotate object?"))
.SetFunctionName("SetRotateObject")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddCondition("ObjectRotated",
_("Object rotated"),
@@ -411,7 +413,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("IsObjectRotated")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("GetNodeX",
_("Get a waypoint X position"),
@@ -422,7 +424,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
.SetFunctionName("GetNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("GetNodeY",
_("Get a waypoint Y position"),
@@ -433,7 +435,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.AddParameter("expression", _("Node index (start at 0!)"))
.SetFunctionName("GetNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("NextNodeIndex",
_("Index of the next waypoint"),
@@ -443,7 +445,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeIndex")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("NodeCount",
_("Waypoint count"),
@@ -453,7 +455,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNodeCount")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("NextNodeX",
_("Get next waypoint X position"),
@@ -463,7 +465,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("NextNodeY",
_("Get next waypoint Y position"),
@@ -473,7 +475,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetNextNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("LastNodeX",
_("Last waypoint X position"),
@@ -483,7 +485,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeX")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("LastNodeY",
_("Last waypoint Y position"),
@@ -493,7 +495,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetLastNodeY")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("DestinationX",
_("Destination X position"),
@@ -503,7 +505,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationX")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("DestinationY",
_("Destination Y position"),
@@ -513,7 +515,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetDestinationY")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("Acceleration",
_("Acceleration"),
@@ -523,7 +525,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAcceleration")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("MaxSpeed",
_("Maximum speed"),
@@ -533,7 +535,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("Speed",
_("Speed"),
@@ -543,7 +545,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("AngularMaxSpeed",
_("Angular maximum speed"),
@@ -553,7 +555,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngularMaxSpeed")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("AngleOffset",
_("Rotation offset"),
@@ -563,7 +565,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetAngleOffset")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("ExtraBorder",
_("Extra border size"),
@@ -573,7 +575,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetExtraBorder")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("CellWidth",
_("Width of a cell"),
@@ -583,7 +585,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellWidth")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
aut.AddExpression("CellHeight",
_("Height of a cell"),
@@ -593,7 +595,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingBehavior")
.SetFunctionName("GetCellHeight")
.SetIncludeFile("PathfindingBehavior/PathfindingBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
#endif
}
@@ -610,7 +612,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
aut.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddAction("Cost",
_("Cost"),
@@ -627,7 +629,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("SetCost")
.SetGetter("GetCost")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddCondition("Cost",
_("Cost"),
@@ -643,7 +645,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Difficulty"))
.SetFunctionName("GetCost")
.SetManipulatedType("number")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddAction("SetImpassable",
_("Should object be impassable?"),
@@ -657,7 +659,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.AddParameter("yesorno", _("Impassable?"))
.SetFunctionName("SetImpassable")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddCondition("IsImpassable",
_("Is object impassable?"),
@@ -670,7 +672,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.SetFunctionName("IsImpassable")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
aut.AddExpression("Cost",
_("Cost"),
@@ -680,7 +682,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.SetFunctionName("GetCost")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleBehavior.h");
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
#endif
}
@@ -697,6 +699,17 @@ class PathfindingBehaviorCppExtension : public ExtensionBase {
*/
PathfindingBehaviorCppExtension() {
DeclarePathfindingBehaviorExtension(*this);
AddRuntimeBehavior<PathfindingRuntimeBehavior>(
GetBehaviorMetadata("PathfindingBehavior::Pathfinding"),
"PathfindingRuntimeBehavior");
GetBehaviorMetadata("PathfindingBehavior::Pathfinding")
.SetIncludeFile("PathfindingBehavior/PathfindingRuntimeBehavior.h");
AddRuntimeBehavior<PathfindingObstacleRuntimeBehavior>(
GetBehaviorMetadata("PathfindingBehavior::PathfindingObstacle"),
"PathfindingObstacleRuntimeBehavior");
GetBehaviorMetadata("PathfindingBehavior::PathfindingObstacle")
.SetIncludeFile("PathfindingBehavior/PathfindingObstacleRuntimeBehavior.h");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};

View File

@@ -6,687 +6,89 @@ This project is released under the MIT License.
*/
#include "PathfindingBehavior.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <memory>
#include <set>
#include <unordered_map>
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Extensions/Builtin/MathematicalTools.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "PathfindingObstacleBehavior.h"
#include "ScenePathfindingObstaclesManager.h"
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
/**
* \brief Internal tool class representing the position of a node when looking
* for a path.
*/
class NodePosition {
public:
NodePosition(int x_, int y_) : x(x_), y(y_){};
int x;
int y;
};
std::ostream& operator<<(std::ostream& stream, const NodePosition& nodePos) {
stream << nodePos.x << ";" << nodePos.y;
return stream;
}
bool operator==(const NodePosition& a, const NodePosition& b) {
return ((a.x == b.x) && (a.y == b.y));
}
namespace std {
/**
* \brief Tool function used to store a NodePosition as key in
* std::unordered_set.
*/
template <>
struct hash<NodePosition> {
std::size_t operator()(NodePosition const& n) const {
return (std::hash<int>()(n.x)) ^ (std::hash<int>()(n.y) << 1);
}
};
} // namespace std
namespace {
/**
* \brief Internal tool class representing a node when looking for a path
*/
class Node {
public:
Node()
: pos(0, 0),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
Node(int x, int y)
: pos(x, y),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
Node(const NodePosition& pos_)
: pos(pos_),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
NodePosition pos;
float cost; ///< The cost for traveling on this node
float smallestCost; ///< the cost to go to this node (when considering the
///< shortest path).
float estimateCost; ///< the estimate cost total to go to the destination
///< through this node (when considering the shortest
///< path).
const Node* parent; ///< The previous node to be visited to go to this node
///< (when considering the shortest path).
bool open; ///< true if the node is "open" (must be explored), false if
///< "close" (already explored)
/**
* \brief Tool function used to store a Node in a priority_queue.
*/
class NodeComparator {
public:
bool operator()(const Node* n1, const Node* n2) {
return n1->estimateCost < n2->estimateCost;
}
};
};
bool operator==(Node const& n1, Node const& n2) {
return n1.pos.x == n2.pos.x && n1.pos.y == n2.pos.y;
};
typedef float (*DistanceFunPtr)(const NodePosition&, const NodePosition&);
/**
* \brief Internal tool class containing the structures used by A* and members
* functions related to them.
*/
class SearchContext {
public:
SearchContext(ScenePathfindingObstaclesManager& obstacles_,
bool allowsDiagonal_ = true)
: obstacles(obstacles_),
finalNode(NULL),
destination(0, 0),
startX(0),
startY(0),
allowsDiagonal(allowsDiagonal_),
maxComplexityFactor(50),
cellWidth(20),
cellHeight(20),
leftBorder(0),
rightBorder(0),
topBorder(0),
bottomBorder(0) {
distanceFunction = allowsDiagonal ? &SearchContext::EuclideanDistance
: &SearchContext::ManhattanDistance;
}
/**
* \brief Set the start position.
* \param x The coordinate on X axis of the start position, in "world"
* coordinates. \param y The coordinate on Y axis of the start position, in
* "world" coordinates.
*/
SearchContext& SetStartPosition(float x, float y) {
startX = x;
startY = y;
return *this;
}
/**
* \brief Set the size to be considered for the object for which the path will
* be planned.
*/
SearchContext& SetObjectSize(float leftBorder_,
float topBorder_,
float rightBorder_,
float bottomBorder_) {
leftBorder = leftBorder_;
rightBorder = rightBorder_;
topBorder = topBorder_;
bottomBorder = bottomBorder_;
return *this;
}
/**
* \brief Change the size of a virtual cell, in pixels.
*/
SearchContext& SetCellSize(unsigned int cellWidth_,
unsigned int cellHeight_) {
cellWidth = cellWidth_;
cellHeight = cellHeight_;
return *this;
}
/**
* \brief Compute a path to the specified position, considering the obstacles
* and the start position passed in the constructor.
* \return true if computation found a path, in which case you can call
* GetFinalNode method to construct the path. \param x The coordinate on X
* axis of the target position, in "world" coordinates. \param y The
* coordinate on Y axis of the target position, in "world" coordinates.
*/
bool ComputePathTo(float targetX, float targetY) {
destination = NodePosition(GDRound(targetX / cellWidth),
GDRound(targetY / cellHeight));
NodePosition start(GDRound(startX / cellWidth),
GDRound(startY / cellHeight));
// Initialize the algorithm
allNodes.clear();
Node& startNode = GetNode(start);
startNode.smallestCost = 0;
startNode.estimateCost = 0 + distanceFunction(start, destination);
openNodes.clear();
openNodes.insert(&startNode);
// A* algorithm main loop
std::size_t iterationCount = 0;
std::size_t maxIterationCount =
startNode.estimateCost * maxComplexityFactor;
while (!openNodes.empty()) {
if (iterationCount++ > maxIterationCount)
return false; // Make sure we do not search forever.
Node* n = *openNodes.begin(); // Get the most promising node...
n->open = false; //...and flag it as explored
openNodes.erase(
openNodes.begin()); // Be sure to remove ONLY the first element!
// Check if we reached destination?
if (n->pos.x == destination.x && n->pos.y == destination.y) {
finalNode = n;
return true;
}
// No, so add neighbors to the nodes to explore.
InsertNeighbors(*n);
}
return false;
}
/**
* @return The final node of the computed path.
* Iterate on the parent member to create the path. Beware, the coordinates of
* the node must be multiplied by the cell size to get the "world" coordinates
* of the path.
*/
Node* GetFinalNode() const { return finalNode; }
private:
/**
* Insert the neighbors of the current node in the open list
* (Only if they are not closed, and if the cost is better than the already
* existing smallest cost).
*/
void InsertNeighbors(const Node& currentNode) {
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x, currentNode.pos.y + 1), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x, currentNode.pos.y - 1), currentNode, 1);
if (allowsDiagonal) {
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y + 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y - 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y - 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y + 1),
currentNode,
sqrt2);
}
}
/**
* \brief Get (or dynamically construct) a node.
*
* *All* nodes should be created using this method: The cost of the node is
* computed thanks to the objects flagged as obstacles.
*/
Node& GetNode(const NodePosition& pos) {
if (allNodes.find(pos) != allNodes.end()) return allNodes.find(pos)->second;
Node newNode(pos);
bool objectsOnCell = false;
const std::set<PathfindingObstacleBehavior*>& allObstacles =
obstacles.GetAllObstacles();
for (std::set<PathfindingObstacleBehavior*>::const_iterator it =
allObstacles.begin();
it != allObstacles.end();
++it) {
RuntimeObject* obj = (*it)->GetObject();
int topLeftCellX =
floor((obj->GetDrawableX() - rightBorder) / (float)cellWidth);
int topLeftCellY =
floor((obj->GetDrawableY() - bottomBorder) / (float)cellHeight);
int bottomRightCellX =
ceil((obj->GetDrawableX() + obj->GetWidth() + leftBorder) /
(float)cellWidth);
int bottomRightCellY =
ceil((obj->GetDrawableY() + obj->GetHeight() + topBorder) /
(float)cellHeight);
if (topLeftCellX < pos.x && pos.x < bottomRightCellX &&
topLeftCellY < pos.y && pos.y < bottomRightCellY) {
objectsOnCell = true;
if ((*it)->IsImpassable()) {
newNode.cost = -1;
break; // The cell is impassable, stop here.
} else // Superimpose obstacles
newNode.cost += (*it)->GetCost();
}
}
if (!objectsOnCell)
newNode.cost = 1; // Default cost when no objects put on the cell.
allNodes[pos] = newNode;
return allNodes[pos];
}
/**
* Compute the euclidean distance between two positions.
*/
static float EuclideanDistance(const NodePosition& a, const NodePosition& b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
/**
* Compute the taxi distance between two positions.
*/
static float ManhattanDistance(const NodePosition& a, const NodePosition& b) {
return abs(a.x - b.x) + abs(a.y - b.y);
}
/**
* Add a node to the openNodes (only if the cost to reach it is less than the
* existing cost, if any).
*/
void AddOrUpdateNode(const NodePosition& newNodePosition,
const Node& currentNode,
float factor) {
Node& neighbor = GetNode(newNodePosition);
if (!neighbor.open ||
neighbor.cost < 0) // cost < 0 means impassable obstacle
return;
// Update the node costs and parent if the path coming from currentNode is
// better:
if (neighbor.smallestCost == -1 ||
neighbor.smallestCost >
currentNode.smallestCost +
(currentNode.cost + neighbor.cost) / 2.0 * factor) {
if (neighbor.smallestCost != -1) // The node is already in the open list:
{
// remove it as its estimate cost will be updated.
auto it = openNodes.find(&neighbor);
if (it !=
openNodes.end()) // /!\ ALWAYS use an iterator with multiset::erase
openNodes.erase(it); // otherwise, other nodes which are equivalent
// get removed too.
}
neighbor.smallestCost = currentNode.smallestCost +
(currentNode.cost + neighbor.cost) / 2.0 * factor;
neighbor.parent = &currentNode;
neighbor.estimateCost =
neighbor.smallestCost + distanceFunction(neighbor.pos, destination);
openNodes.insert(&neighbor);
}
}
std::unordered_map<NodePosition, Node> allNodes; ///< All the nodes
std::multiset<Node*, Node::NodeComparator>
openNodes; ///< Only the open nodes (Such that Node::open == true)
const ScenePathfindingObstaclesManager&
obstacles; ///< A reference to all the obstacles of the scene
Node* finalNode; // If computation succeeded, the final node is stored here.
NodePosition destination;
int startX; ///< The start X position, in "world" coordinates (not in "node"
///< coordinates!).
int startY; ///< The start Y position, in "world" coordinates (not in "node"
///< coordinates!).
DistanceFunPtr distanceFunction;
bool allowsDiagonal; ///< True to allow diagonals when planning the path.
std::size_t maxComplexityFactor;
float cellWidth;
float cellHeight;
float leftBorder;
float rightBorder;
float topBorder;
float bottomBorder;
static const float sqrt2;
};
const float SearchContext::sqrt2 = 1.414213562;
} // namespace
PathfindingBehavior::PathfindingBehavior()
: parentScene(NULL),
sceneManager(NULL),
pathFound(false),
allowDiagonals(true),
acceleration(400),
maxSpeed(200),
angularMaxSpeed(180),
rotateObject(true),
angleOffset(0),
cellWidth(20),
cellHeight(20),
extraBorder(0),
speed(0),
angularSpeed(0),
timeOnSegment(0),
totalSegmentTime(0),
currentSegment(0),
reachedEnd(false) {}
void PathfindingBehavior::MoveTo(RuntimeScene& scene, float x, float y) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
path.clear();
// First be sure that there is a path to compute.
int targetCellX = GDRound(x / (float)cellWidth);
int targetCellY = GDRound(y / (float)cellHeight);
int startCellX = GDRound(object->GetX() / (float)cellWidth);
int startCellY = GDRound(object->GetY() / (float)cellHeight);
if (startCellX == targetCellX && startCellY == targetCellY) {
path.push_back(sf::Vector2f(object->GetX(), object->GetY()));
path.push_back(sf::Vector2f(x, y));
EnterSegment(0);
pathFound = true;
return;
}
// Start searching for a path
// TODO: Customizable heuristic.
::SearchContext ctx(*sceneManager, allowDiagonals);
ctx.SetCellSize(cellWidth, cellHeight)
.SetStartPosition(object->GetX(), object->GetY());
ctx.SetObjectSize(object->GetX() - object->GetDrawableX() + extraBorder,
object->GetY() - object->GetDrawableY() + extraBorder,
object->GetWidth() -
(object->GetX() - object->GetDrawableX()) + extraBorder,
object->GetHeight() -
(object->GetY() - object->GetDrawableY()) +
extraBorder);
if (ctx.ComputePathTo(x, y)) {
// Path found: memorize it
const ::Node* node = ctx.GetFinalNode();
while (node) {
path.push_back(sf::Vector2f(node->pos.x * (float)cellWidth,
node->pos.y * (float)cellHeight));
node = node->parent;
}
std::reverse(path.begin(), path.end());
path[0] = sf::Vector2f(object->GetX(), object->GetY());
EnterSegment(0);
pathFound = true;
return;
}
// Not path found
pathFound = false;
}
void PathfindingBehavior::EnterSegment(std::size_t segmentNumber) {
if (path.empty()) return;
currentSegment = segmentNumber;
if (currentSegment < path.size() - 1) {
sf::Vector2f newPath = (path[currentSegment + 1] - path[currentSegment]);
totalSegmentTime = sqrtf(newPath.x * newPath.x + newPath.y * newPath.y);
timeOnSegment = 0;
reachedEnd = false;
} else {
reachedEnd = true;
speed = 0;
}
}
void PathfindingBehavior::DoStepPreEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
if (!sceneManager) return;
if (path.empty() || reachedEnd) return;
// Update the speed of the object
float timeDelta =
static_cast<double>(object->GetElapsedTime(scene)) / 1000000.0;
speed += acceleration * timeDelta;
if (speed > maxSpeed) speed = maxSpeed;
angularSpeed = angularMaxSpeed; // No acceleration for angular speed for now
// Update the time on the segment and change segment if needed
timeOnSegment += speed * timeDelta;
if (timeOnSegment >= totalSegmentTime && currentSegment < path.size())
EnterSegment(currentSegment + 1);
// Position object on the segment and update its angle
sf::Vector2f newPos;
float pathAngle = object->GetAngle();
if (currentSegment < path.size() - 1) {
newPos = path[currentSegment] +
(path[currentSegment + 1] - path[currentSegment]) *
(timeOnSegment / totalSegmentTime);
pathAngle = atan2(path[currentSegment + 1].y - path[currentSegment].y,
path[currentSegment + 1].x - path[currentSegment].x) *
180 / 3.14159 +
angleOffset;
} else
newPos = path.back();
object->SetX(newPos.x);
object->SetY(newPos.y);
// Also update angle if needed
if (rotateObject) object->RotateTowardAngle(pathAngle, angularSpeed, scene);
}
void PathfindingBehavior::DoStepPostEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
}
float PathfindingBehavior::GetNodeX(std::size_t index) const {
if (index < path.size()) return path[index].x;
return 0;
}
float PathfindingBehavior::GetNodeY(std::size_t index) const {
if (index < path.size()) return path[index].y;
return 0;
}
std::size_t PathfindingBehavior::GetNextNodeIndex() const {
if (currentSegment + 1 < path.size())
return currentSegment + 1;
else
return path.size() - 1;
}
float PathfindingBehavior::GetNextNodeX() const {
if (path.empty()) return 0;
if (currentSegment + 1 < path.size())
return path[currentSegment + 1].x;
else
return path.back().x;
}
float PathfindingBehavior::GetNextNodeY() const {
if (path.empty()) return 0;
if (currentSegment + 1 < path.size())
return path[currentSegment + 1].y;
else
return path.back().y;
}
float PathfindingBehavior::GetLastNodeX() const {
if (path.size() < 2) return 0;
if (currentSegment < path.size() - 1)
return path[currentSegment].x;
else
return path[path.size() - 1].x;
}
float PathfindingBehavior::GetLastNodeY() const {
if (path.size() < 2) return 0;
if (currentSegment < path.size() - 1)
return path[currentSegment].y;
else
return path[path.size() - 1].y;
}
float PathfindingBehavior::GetDestinationX() const {
if (path.empty()) return 0;
return path.back().x;
}
float PathfindingBehavior::GetDestinationY() const {
if (path.empty()) return 0;
return path.back().y;
}
void PathfindingBehavior::UnserializeFrom(
const gd::SerializerElement& element) {
allowDiagonals = element.GetBoolAttribute("allowDiagonals");
acceleration = element.GetDoubleAttribute("acceleration");
maxSpeed = element.GetDoubleAttribute("maxSpeed");
angularMaxSpeed = element.GetDoubleAttribute("angularMaxSpeed");
rotateObject = element.GetBoolAttribute("rotateObject");
angleOffset = element.GetDoubleAttribute("angleOffset");
extraBorder = element.GetDoubleAttribute("extraBorder");
{
int value = element.GetIntAttribute("cellWidth", 0);
if (value > 0) cellWidth = value;
}
{
int value = element.GetIntAttribute("cellHeight", 0);
if (value > 0) cellHeight = value;
}
void PathfindingBehavior::InitializeContent(
gd::SerializerElement& behaviorContent) {
behaviorContent.SetAttribute("allowDiagonals", true);
behaviorContent.SetAttribute("acceleration", 400);
behaviorContent.SetAttribute("maxSpeed", 200);
behaviorContent.SetAttribute("angularMaxSpeed", 180);
behaviorContent.SetAttribute("rotateObject", true);
behaviorContent.SetAttribute("angleOffset", 0);
behaviorContent.SetAttribute("cellWidth", 20);
behaviorContent.SetAttribute("cellHeight", 20);
behaviorContent.SetAttribute("extraBorder", 0);
}
#if defined(GD_IDE_ONLY)
void PathfindingBehavior::SerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("allowDiagonals", allowDiagonals);
element.SetAttribute("acceleration", acceleration);
element.SetAttribute("maxSpeed", maxSpeed);
element.SetAttribute("angularMaxSpeed", angularMaxSpeed);
element.SetAttribute("rotateObject", rotateObject);
element.SetAttribute("angleOffset", angleOffset);
element.SetAttribute("cellWidth", (int)cellWidth);
element.SetAttribute("cellHeight", (int)cellHeight);
element.SetAttribute("extraBorder", extraBorder);
}
std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
gd::Project& project) const {
const gd::SerializerElement& behaviorContent, gd::Project& project) const {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Allows diagonals")]
.SetValue(allowDiagonals ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("allowDiagonals") ? "true"
: "false")
.SetType("Boolean");
properties[_("Acceleration")].SetValue(gd::String::From(acceleration));
properties[_("Max. speed")].SetValue(gd::String::From(maxSpeed));
properties[_("Rotate speed")].SetValue(gd::String::From(angularMaxSpeed));
properties[_("Acceleration")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
properties[_("Max. speed")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
properties[_("Rotate speed")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angularMaxSpeed")));
properties[_("Rotate object")]
.SetValue(rotateObject ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("rotateObject") ? "true"
: "false")
.SetType("Boolean");
properties[_("Angle offset")].SetValue(gd::String::From(angleOffset));
properties[_("Virtual cell width")].SetValue(gd::String::From(cellWidth));
properties[_("Virtual cell height")].SetValue(gd::String::From(cellHeight));
properties[_("Extra border size")].SetValue(gd::String::From(extraBorder));
properties[_("Angle offset")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
properties[_("Virtual cell width")].SetValue(
gd::String::From(behaviorContent.GetIntAttribute("cellWidth", 0)));
properties[_("Virtual cell height")].SetValue(
gd::String::From(behaviorContent.GetIntAttribute("cellHeight", 0)));
properties[_("Extra border size")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("extraBorder")));
return properties;
}
bool PathfindingBehavior::UpdateProperty(const gd::String& name,
bool PathfindingBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) {
if (name == _("Allows diagonals")) {
allowDiagonals = (value != "0");
behaviorContent.SetAttribute("allowDiagonals", (value != "0"));
return true;
}
if (name == _("Rotate object")) {
rotateObject = (value != "0");
behaviorContent.SetAttribute("rotateObject", (value != "0"));
return true;
}
if (name == _("Extra border size")) {
extraBorder = value.To<float>();
behaviorContent.SetAttribute("extraBorder", value.To<float>());
return true;
}
if (value.To<float>() < 0) return false;
if (name == _("Acceleration"))
acceleration = value.To<float>();
behaviorContent.SetAttribute("acceleration", value.To<float>());
else if (name == _("Max. speed"))
maxSpeed = value.To<float>();
behaviorContent.SetAttribute("maxSpeed", value.To<float>());
else if (name == _("Rotate speed"))
angularMaxSpeed = value.To<float>();
behaviorContent.SetAttribute("angularMaxSpeed", value.To<float>());
else if (name == _("Angle offset"))
angleOffset = value.To<float>();
behaviorContent.SetAttribute("angleOffset", value.To<float>());
else if (name == _("Virtual cell width"))
cellWidth = value.To<unsigned int>();
behaviorContent.SetAttribute("cellWidth", (int)value.To<unsigned int>());
else if (name == _("Virtual cell height"))
cellHeight = value.To<unsigned int>();
behaviorContent.SetAttribute("cellHeight", (int)value.To<unsigned int>());
else
return false;
return true;
}
#endif

View File

@@ -27,113 +27,28 @@ class RuntimeScenePlatformData;
*/
class GD_EXTENSION_API PathfindingBehavior : public Behavior {
public:
PathfindingBehavior();
PathfindingBehavior(){};
virtual ~PathfindingBehavior(){};
virtual Behavior* Clone() const { return new PathfindingBehavior(*this); }
/**
* \brief Compute and move on the path to the specified destination.
*/
void MoveTo(RuntimeScene& scene, float x, float y);
// Path information:
/**
* \brief Return true if the latest call to MoveTo succeeded.
*/
bool PathFound() { return pathFound; }
/**
* \brief Return true if the object reached its destination
*/
bool DestinationReached() { return reachedEnd; }
float GetNodeX(std::size_t index) const;
float GetNodeY(std::size_t index) const;
std::size_t GetNextNodeIndex() const;
std::size_t GetNodeCount() const { return path.size(); };
float GetNextNodeX() const;
float GetNextNodeY() const;
float GetLastNodeX() const;
float GetLastNodeY() const;
float GetDestinationX() const;
float GetDestinationY() const;
// Configuration:
bool DiagonalsAllowed() { return allowDiagonals; };
float GetAcceleration() { return acceleration; };
float GetMaxSpeed() { return maxSpeed; };
float GetAngularMaxSpeed() { return angularMaxSpeed; };
bool IsObjectRotated() { return rotateObject; }
float GetAngleOffset() { return angleOffset; };
unsigned int GetCellWidth() { return cellWidth; };
unsigned int GetCellHeight() { return cellHeight; };
float GetExtraBorder() { return extraBorder; };
void SetAllowDiagonals(bool allowDiagonals_) {
allowDiagonals = allowDiagonals_;
};
void SetAcceleration(float acceleration_) { acceleration = acceleration_; };
void SetMaxSpeed(float maxSpeed_) { maxSpeed = maxSpeed_; };
void SetAngularMaxSpeed(float angularMaxSpeed_) {
angularMaxSpeed = angularMaxSpeed_;
};
void SetRotateObject(bool rotateObject_) { rotateObject = rotateObject_; };
void SetAngleOffset(float angleOffset_) { angleOffset = angleOffset_; };
void SetCellWidth(unsigned int cellWidth_) { cellWidth = cellWidth_; };
void SetCellHeight(unsigned int cellHeight_) { cellHeight = cellHeight_; };
void SetExtraBorder(float extraBorder_) { extraBorder = extraBorder_; };
float GetSpeed() { return speed; };
void SetSpeed(float speed_) { speed = speed_; };
/**
* \brief Unserialize the behavior
*/
virtual void UnserializeFrom(const gd::SerializerElement& element);
virtual Behavior* Clone() const override {
return new PathfindingBehavior(*this);
}
#if defined(GD_IDE_ONLY)
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializerElement& behaviorContent,
gd::Project& project) const override;
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) override;
#endif
/**
* \brief Serialize the behavior
*/
virtual void SerializeTo(gd::SerializerElement& element) const;
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
gd::Project& project) const;
virtual bool UpdateProperty(const gd::String& name,
const gd::String& value,
gd::Project& project);
#endif
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override;
private:
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
void EnterSegment(std::size_t segmentNumber);
RuntimeScene* parentScene; ///< The scene the object belongs to.
ScenePathfindingObstaclesManager*
sceneManager; ///< The platform objects manager associated to the scene.
std::vector<sf::Vector2f> path; ///< The computed path
bool pathFound;
// Behavior configuration:
bool allowDiagonals;
float acceleration;
float maxSpeed;
float angularMaxSpeed;
bool rotateObject; ///< If true, the object is rotated according to the
///< current segment's angle.
float angleOffset; ///< Angle offset (added to the angle calculated with the
///< segment)
unsigned int cellWidth;
unsigned int cellHeight;
float extraBorder;
// Attributes used for traveling on the path:
float speed;
float angularSpeed;
float timeOnSegment;
float totalSegmentTime;
std::size_t currentSegment;
bool reachedEnd;
};
#endif // PATHFINDINGBEHAVIOR_H

View File

@@ -5,106 +5,45 @@ Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "PathfindingObstacleBehavior.h"
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "ScenePathfindingObstaclesManager.h"
#if defined(GD_IDE_ONLY)
#include <iostream>
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#include "GDCore/Tools/Localization.h"
#endif
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
PathfindingObstacleBehavior::PathfindingObstacleBehavior()
: parentScene(NULL),
sceneManager(NULL),
registeredInManager(false),
impassable(true),
cost(2) {}
PathfindingObstacleBehavior::~PathfindingObstacleBehavior() {
if (sceneManager && registeredInManager) sceneManager->RemoveObstacle(this);
}
void PathfindingObstacleBehavior::DoStepPreEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
if (sceneManager) // Remove the object from any old scene manager.
sceneManager->RemoveObstacle(this);
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
registeredInManager = false;
}
if (!activated && registeredInManager) {
if (sceneManager) sceneManager->RemoveObstacle(this);
registeredInManager = false;
} else if (activated && !registeredInManager) {
if (sceneManager) {
sceneManager->AddObstacle(this);
registeredInManager = true;
}
}
}
void PathfindingObstacleBehavior::DoStepPostEvents(RuntimeScene& scene) {}
void PathfindingObstacleBehavior::OnActivate() {
if (sceneManager) {
sceneManager->AddObstacle(this);
registeredInManager = true;
}
}
void PathfindingObstacleBehavior::OnDeActivate() {
if (sceneManager) sceneManager->RemoveObstacle(this);
registeredInManager = false;
}
void PathfindingObstacleBehavior::UnserializeFrom(
const gd::SerializerElement& element) {
impassable = element.GetBoolAttribute("impassable");
cost = element.GetDoubleAttribute("cost");
void PathfindingObstacleBehavior::InitializeContent(
gd::SerializerElement& behaviorContent) {
behaviorContent.SetAttribute("impassable", true);
behaviorContent.SetAttribute("cost", 2);
}
#if defined(GD_IDE_ONLY)
void PathfindingObstacleBehavior::SerializeTo(
gd::SerializerElement& element) const {
element.SetAttribute("impassable", impassable);
element.SetAttribute("cost", cost);
}
std::map<gd::String, gd::PropertyDescriptor>
PathfindingObstacleBehavior::GetProperties(gd::Project& project) const {
PathfindingObstacleBehavior::GetProperties(
const gd::SerializerElement& behaviorContent, gd::Project& project) const {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Impassable obstacle")]
.SetValue(impassable ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("impassable") ? "true"
: "false")
.SetType("Boolean");
properties[_("Cost (if not impassable)")].SetValue(gd::String::From(cost));
properties[_("Cost (if not impassable)")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("cost")));
return properties;
}
bool PathfindingObstacleBehavior::UpdateProperty(const gd::String& name,
const gd::String& value,
gd::Project& project) {
bool PathfindingObstacleBehavior::UpdateProperty(
gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project) {
if (name == _("Impassable obstacle")) {
impassable = (value != "0");
behaviorContent.SetAttribute("impassable", (value != "0"));
return true;
}
if (value.To<float>() < 0) return false;
if (name == _("Cost (if not impassable)"))
cost = value.To<float>();
behaviorContent.SetAttribute("cost", value.To<float>());
else
return false;

View File

@@ -7,26 +7,14 @@ This project is released under the MIT License.
#ifndef PATHFINDINGOBSTACLEBEHAVIOR_H
#define PATHFINDINGOBSTACLEBEHAVIOR_H
#include <map>
#include "GDCpp/Runtime/Project/Behavior.h"
#include "GDCpp/Runtime/RuntimeObject.h"
class ScenePathfindingObstaclesManager;
class RuntimeScene;
namespace gd {
class SerializerElement;
}
#if defined(GD_IDE_ONLY)
#include <map>
namespace gd {
class PropertyDescriptor;
}
namespace gd {
class Project;
}
namespace gd {
class Layout;
}
#endif
} // namespace gd
/**
* \brief Behavior that mark object as being obstacles for objects using
@@ -34,62 +22,26 @@ class Layout;
*/
class GD_EXTENSION_API PathfindingObstacleBehavior : public Behavior {
public:
PathfindingObstacleBehavior();
virtual ~PathfindingObstacleBehavior();
virtual Behavior* Clone() const {
PathfindingObstacleBehavior(){};
virtual ~PathfindingObstacleBehavior(){};
virtual Behavior* Clone() const override {
return new PathfindingObstacleBehavior(*this);
}
/**
* \brief Return the object owning this behavior.
*/
RuntimeObject* GetObject() const { return object; }
/**
* \brief Return true if the obstacle is impassable.
*/
bool IsImpassable() const { return impassable; }
/**
* \brief Set the object as impassable or not.
*/
void SetImpassable(bool impassable_ = true) { impassable = impassable_; }
/**
* \brief Return the cost of moving on the object.
*/
float GetCost() const { return cost; }
/**
* \brief Change the cost of moving on the object.
*/
void SetCost(float newCost) { cost = newCost; }
virtual void UnserializeFrom(const gd::SerializerElement& element);
#if defined(GD_IDE_ONLY)
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
gd::Project& project) const;
virtual bool UpdateProperty(const gd::String& name,
const gd::SerializerElement& behaviorContent,
gd::Project& project) const override;
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value,
gd::Project& project);
virtual void SerializeTo(gd::SerializerElement& element) const;
gd::Project& project) override;
#endif
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override;
private:
virtual void OnActivate();
virtual void OnDeActivate();
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
RuntimeScene* parentScene; ///< The scene the object belongs to.
ScenePathfindingObstaclesManager*
sceneManager; ///< The obstacles manager associated to the scene.
bool registeredInManager; ///< True if the behavior is registered in the list
///< of obstacles of the scene.
bool impassable;
float cost; ///< The cost of moving on the obstacle (for when impassable ==
///< false)
};
#endif // PATHFINDINGOBSTACLEBEHAVIOR_H

View File

@@ -0,0 +1,76 @@
/**
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "PathfindingObstacleRuntimeBehavior.h"
#include <memory>
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "ScenePathfindingObstaclesManager.h"
#if defined(GD_IDE_ONLY)
#include <iostream>
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#include "GDCore/Tools/Localization.h"
#endif
PathfindingObstacleRuntimeBehavior::PathfindingObstacleRuntimeBehavior(
const gd::SerializerElement& behaviorContent)
: RuntimeBehavior(behaviorContent),
parentScene(NULL),
sceneManager(NULL),
registeredInManager(false),
impassable(true),
cost(2) {
impassable = behaviorContent.GetBoolAttribute("impassable");
cost = behaviorContent.GetDoubleAttribute("cost");
}
PathfindingObstacleRuntimeBehavior::~PathfindingObstacleRuntimeBehavior() {
if (sceneManager && registeredInManager) sceneManager->RemoveObstacle(this);
}
void PathfindingObstacleRuntimeBehavior::DoStepPreEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
if (sceneManager) // Remove the object from any old scene manager.
sceneManager->RemoveObstacle(this);
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
registeredInManager = false;
}
if (!activated && registeredInManager) {
if (sceneManager) sceneManager->RemoveObstacle(this);
registeredInManager = false;
} else if (activated && !registeredInManager) {
if (sceneManager) {
sceneManager->AddObstacle(this);
registeredInManager = true;
}
}
}
void PathfindingObstacleRuntimeBehavior::DoStepPostEvents(RuntimeScene& scene) {
}
void PathfindingObstacleRuntimeBehavior::OnActivate() {
if (sceneManager) {
sceneManager->AddObstacle(this);
registeredInManager = true;
}
}
void PathfindingObstacleRuntimeBehavior::OnDeActivate() {
if (sceneManager) sceneManager->RemoveObstacle(this);
registeredInManager = false;
}

View File

@@ -0,0 +1,72 @@
/**
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#ifndef PATHFINDINGOBSTACLERUNTIMEBEHAVIOR_H
#define PATHFINDINGOBSTACLERUNTIMEBEHAVIOR_H
#include "GDCpp/Runtime/RuntimeBehavior.h"
#include "GDCpp/Runtime/RuntimeObject.h"
class ScenePathfindingObstaclesManager;
class RuntimeScene;
namespace gd {
class SerializerElement;
}
/**
* \brief Behavior that mark object as being obstacles for objects using
* pathfinding behavior.
*/
class GD_EXTENSION_API PathfindingObstacleRuntimeBehavior : public RuntimeBehavior {
public:
PathfindingObstacleRuntimeBehavior(const gd::SerializerElement& behaviorContent);
virtual ~PathfindingObstacleRuntimeBehavior();
virtual PathfindingObstacleRuntimeBehavior* Clone() const {
return new PathfindingObstacleRuntimeBehavior(*this);
}
/**
* \brief Return the object owning this behavior.
*/
RuntimeObject* GetObject() const { return object; }
/**
* \brief Return true if the obstacle is impassable.
*/
bool IsImpassable() const { return impassable; }
/**
* \brief Set the object as impassable or not.
*/
void SetImpassable(bool impassable_ = true) { impassable = impassable_; }
/**
* \brief Return the cost of moving on the object.
*/
float GetCost() const { return cost; }
/**
* \brief Change the cost of moving on the object.
*/
void SetCost(float newCost) { cost = newCost; }
private:
virtual void OnActivate();
virtual void OnDeActivate();
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
RuntimeScene* parentScene; ///< The scene the object belongs to.
ScenePathfindingObstaclesManager*
sceneManager; ///< The obstacles manager associated to the scene.
bool registeredInManager; ///< True if the behavior is registered in the list
///< of obstacles of the scene.
bool impassable;
float cost; ///< The cost of moving on the obstacle (for when impassable ==
///< false)
};
#endif // PATHFINDINGOBSTACLERUNTIMEBEHAVIOR_H

View File

@@ -0,0 +1,615 @@
/**
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "PathfindingRuntimeBehavior.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <memory>
#include <set>
#include <unordered_map>
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Extensions/Builtin/MathematicalTools.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "PathfindingObstacleRuntimeBehavior.h"
#include "ScenePathfindingObstaclesManager.h"
/**
* \brief Internal tool class representing the position of a node when looking
* for a path.
*/
class NodePosition {
public:
NodePosition(int x_, int y_) : x(x_), y(y_){};
int x;
int y;
};
std::ostream& operator<<(std::ostream& stream, const NodePosition& nodePos) {
stream << nodePos.x << ";" << nodePos.y;
return stream;
}
bool operator==(const NodePosition& a, const NodePosition& b) {
return ((a.x == b.x) && (a.y == b.y));
}
namespace std {
/**
* \brief Tool function used to store a NodePosition as key in
* std::unordered_set.
*/
template <>
struct hash<NodePosition> {
std::size_t operator()(NodePosition const& n) const {
return (std::hash<int>()(n.x)) ^ (std::hash<int>()(n.y) << 1);
}
};
} // namespace std
namespace {
/**
* \brief Internal tool class representing a node when looking for a path
*/
class Node {
public:
Node()
: pos(0, 0),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
Node(int x, int y)
: pos(x, y),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
Node(const NodePosition& pos_)
: pos(pos_),
cost(0),
smallestCost(-1),
estimateCost(-1),
parent(NULL),
open(true){};
NodePosition pos;
float cost; ///< The cost for traveling on this node
float smallestCost; ///< the cost to go to this node (when considering the
///< shortest path).
float estimateCost; ///< the estimate cost total to go to the destination
///< through this node (when considering the shortest
///< path).
const Node* parent; ///< The previous node to be visited to go to this node
///< (when considering the shortest path).
bool open; ///< true if the node is "open" (must be explored), false if
///< "close" (already explored)
/**
* \brief Tool function used to store a Node in a priority_queue.
*/
class NodeComparator {
public:
bool operator()(const Node* n1, const Node* n2) {
return n1->estimateCost < n2->estimateCost;
}
};
};
bool operator==(Node const& n1, Node const& n2) {
return n1.pos.x == n2.pos.x && n1.pos.y == n2.pos.y;
};
typedef float (*DistanceFunPtr)(const NodePosition&, const NodePosition&);
/**
* \brief Internal tool class containing the structures used by A* and members
* functions related to them.
*/
class SearchContext {
public:
SearchContext(ScenePathfindingObstaclesManager& obstacles_,
bool allowsDiagonal_ = true)
: obstacles(obstacles_),
finalNode(NULL),
destination(0, 0),
startX(0),
startY(0),
allowsDiagonal(allowsDiagonal_),
maxComplexityFactor(50),
cellWidth(20),
cellHeight(20),
leftBorder(0),
rightBorder(0),
topBorder(0),
bottomBorder(0) {
distanceFunction = allowsDiagonal ? &SearchContext::EuclideanDistance
: &SearchContext::ManhattanDistance;
}
/**
* \brief Set the start position.
* \param x The coordinate on X axis of the start position, in "world"
* coordinates. \param y The coordinate on Y axis of the start position, in
* "world" coordinates.
*/
SearchContext& SetStartPosition(float x, float y) {
startX = x;
startY = y;
return *this;
}
/**
* \brief Set the size to be considered for the object for which the path will
* be planned.
*/
SearchContext& SetObjectSize(float leftBorder_,
float topBorder_,
float rightBorder_,
float bottomBorder_) {
leftBorder = leftBorder_;
rightBorder = rightBorder_;
topBorder = topBorder_;
bottomBorder = bottomBorder_;
return *this;
}
/**
* \brief Change the size of a virtual cell, in pixels.
*/
SearchContext& SetCellSize(unsigned int cellWidth_,
unsigned int cellHeight_) {
cellWidth = cellWidth_;
cellHeight = cellHeight_;
return *this;
}
/**
* \brief Compute a path to the specified position, considering the obstacles
* and the start position passed in the constructor.
* \return true if computation found a path, in which case you can call
* GetFinalNode method to construct the path. \param x The coordinate on X
* axis of the target position, in "world" coordinates. \param y The
* coordinate on Y axis of the target position, in "world" coordinates.
*/
bool ComputePathTo(float targetX, float targetY) {
destination = NodePosition(GDRound(targetX / cellWidth),
GDRound(targetY / cellHeight));
NodePosition start(GDRound(startX / cellWidth),
GDRound(startY / cellHeight));
// Initialize the algorithm
allNodes.clear();
Node& startNode = GetNode(start);
startNode.smallestCost = 0;
startNode.estimateCost = 0 + distanceFunction(start, destination);
openNodes.clear();
openNodes.insert(&startNode);
// A* algorithm main loop
std::size_t iterationCount = 0;
std::size_t maxIterationCount =
startNode.estimateCost * maxComplexityFactor;
while (!openNodes.empty()) {
if (iterationCount++ > maxIterationCount)
return false; // Make sure we do not search forever.
Node* n = *openNodes.begin(); // Get the most promising node...
n->open = false; //...and flag it as explored
openNodes.erase(
openNodes.begin()); // Be sure to remove ONLY the first element!
// Check if we reached destination?
if (n->pos.x == destination.x && n->pos.y == destination.y) {
finalNode = n;
return true;
}
// No, so add neighbors to the nodes to explore.
InsertNeighbors(*n);
}
return false;
}
/**
* @return The final node of the computed path.
* Iterate on the parent member to create the path. Beware, the coordinates of
* the node must be multiplied by the cell size to get the "world" coordinates
* of the path.
*/
Node* GetFinalNode() const { return finalNode; }
private:
/**
* Insert the neighbors of the current node in the open list
* (Only if they are not closed, and if the cost is better than the already
* existing smallest cost).
*/
void InsertNeighbors(const Node& currentNode) {
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x, currentNode.pos.y + 1), currentNode, 1);
AddOrUpdateNode(
NodePosition(currentNode.pos.x, currentNode.pos.y - 1), currentNode, 1);
if (allowsDiagonal) {
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y + 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x + 1, currentNode.pos.y - 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y - 1),
currentNode,
sqrt2);
AddOrUpdateNode(
NodePosition(currentNode.pos.x - 1, currentNode.pos.y + 1),
currentNode,
sqrt2);
}
}
/**
* \brief Get (or dynamically construct) a node.
*
* *All* nodes should be created using this method: The cost of the node is
* computed thanks to the objects flagged as obstacles.
*/
Node& GetNode(const NodePosition& pos) {
if (allNodes.find(pos) != allNodes.end()) return allNodes.find(pos)->second;
Node newNode(pos);
bool objectsOnCell = false;
const std::set<PathfindingObstacleRuntimeBehavior*>& allObstacles =
obstacles.GetAllObstacles();
for (std::set<PathfindingObstacleRuntimeBehavior*>::const_iterator it =
allObstacles.begin();
it != allObstacles.end();
++it) {
RuntimeObject* obj = (*it)->GetObject();
int topLeftCellX =
floor((obj->GetDrawableX() - rightBorder) / (float)cellWidth);
int topLeftCellY =
floor((obj->GetDrawableY() - bottomBorder) / (float)cellHeight);
int bottomRightCellX =
ceil((obj->GetDrawableX() + obj->GetWidth() + leftBorder) /
(float)cellWidth);
int bottomRightCellY =
ceil((obj->GetDrawableY() + obj->GetHeight() + topBorder) /
(float)cellHeight);
if (topLeftCellX < pos.x && pos.x < bottomRightCellX &&
topLeftCellY < pos.y && pos.y < bottomRightCellY) {
objectsOnCell = true;
if ((*it)->IsImpassable()) {
newNode.cost = -1;
break; // The cell is impassable, stop here.
} else // Superimpose obstacles
newNode.cost += (*it)->GetCost();
}
}
if (!objectsOnCell)
newNode.cost = 1; // Default cost when no objects put on the cell.
allNodes[pos] = newNode;
return allNodes[pos];
}
/**
* Compute the euclidean distance between two positions.
*/
static float EuclideanDistance(const NodePosition& a, const NodePosition& b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
/**
* Compute the taxi distance between two positions.
*/
static float ManhattanDistance(const NodePosition& a, const NodePosition& b) {
return abs(a.x - b.x) + abs(a.y - b.y);
}
/**
* Add a node to the openNodes (only if the cost to reach it is less than the
* existing cost, if any).
*/
void AddOrUpdateNode(const NodePosition& newNodePosition,
const Node& currentNode,
float factor) {
Node& neighbor = GetNode(newNodePosition);
if (!neighbor.open ||
neighbor.cost < 0) // cost < 0 means impassable obstacle
return;
// Update the node costs and parent if the path coming from currentNode is
// better:
if (neighbor.smallestCost == -1 ||
neighbor.smallestCost >
currentNode.smallestCost +
(currentNode.cost + neighbor.cost) / 2.0 * factor) {
if (neighbor.smallestCost != -1) // The node is already in the open list:
{
// remove it as its estimate cost will be updated.
auto it = openNodes.find(&neighbor);
if (it !=
openNodes.end()) // /!\ ALWAYS use an iterator with multiset::erase
openNodes.erase(it); // otherwise, other nodes which are equivalent
// get removed too.
}
neighbor.smallestCost = currentNode.smallestCost +
(currentNode.cost + neighbor.cost) / 2.0 * factor;
neighbor.parent = &currentNode;
neighbor.estimateCost =
neighbor.smallestCost + distanceFunction(neighbor.pos, destination);
openNodes.insert(&neighbor);
}
}
std::unordered_map<NodePosition, Node> allNodes; ///< All the nodes
std::multiset<Node*, Node::NodeComparator>
openNodes; ///< Only the open nodes (Such that Node::open == true)
const ScenePathfindingObstaclesManager&
obstacles; ///< A reference to all the obstacles of the scene
Node* finalNode; // If computation succeeded, the final node is stored here.
NodePosition destination;
int startX; ///< The start X position, in "world" coordinates (not in "node"
///< coordinates!).
int startY; ///< The start Y position, in "world" coordinates (not in "node"
///< coordinates!).
DistanceFunPtr distanceFunction;
bool allowsDiagonal; ///< True to allow diagonals when planning the path.
std::size_t maxComplexityFactor;
float cellWidth;
float cellHeight;
float leftBorder;
float rightBorder;
float topBorder;
float bottomBorder;
static const float sqrt2;
};
const float SearchContext::sqrt2 = 1.414213562;
} // namespace
PathfindingRuntimeBehavior::PathfindingRuntimeBehavior(
const gd::SerializerElement& behaviorContent)
: RuntimeBehavior(behaviorContent),
parentScene(NULL),
sceneManager(NULL),
pathFound(false),
allowDiagonals(true),
acceleration(400),
maxSpeed(200),
angularMaxSpeed(180),
rotateObject(true),
angleOffset(0),
cellWidth(20),
cellHeight(20),
extraBorder(0),
speed(0),
angularSpeed(0),
timeOnSegment(0),
totalSegmentTime(0),
currentSegment(0),
reachedEnd(false) {
allowDiagonals = behaviorContent.GetBoolAttribute("allowDiagonals");
acceleration = behaviorContent.GetDoubleAttribute("acceleration");
maxSpeed = behaviorContent.GetDoubleAttribute("maxSpeed");
angularMaxSpeed = behaviorContent.GetDoubleAttribute("angularMaxSpeed");
rotateObject = behaviorContent.GetBoolAttribute("rotateObject");
angleOffset = behaviorContent.GetDoubleAttribute("angleOffset");
extraBorder = behaviorContent.GetDoubleAttribute("extraBorder");
{
int value = behaviorContent.GetIntAttribute("cellWidth", 0);
if (value > 0) cellWidth = value;
}
{
int value = behaviorContent.GetIntAttribute("cellHeight", 0);
if (value > 0) cellHeight = value;
}
}
void PathfindingRuntimeBehavior::MoveTo(RuntimeScene& scene, float x, float y) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
path.clear();
// First be sure that there is a path to compute.
int targetCellX = GDRound(x / (float)cellWidth);
int targetCellY = GDRound(y / (float)cellHeight);
int startCellX = GDRound(object->GetX() / (float)cellWidth);
int startCellY = GDRound(object->GetY() / (float)cellHeight);
if (startCellX == targetCellX && startCellY == targetCellY) {
path.push_back(sf::Vector2f(object->GetX(), object->GetY()));
path.push_back(sf::Vector2f(x, y));
EnterSegment(0);
pathFound = true;
return;
}
// Start searching for a path
// TODO: Customizable heuristic.
::SearchContext ctx(*sceneManager, allowDiagonals);
ctx.SetCellSize(cellWidth, cellHeight)
.SetStartPosition(object->GetX(), object->GetY());
ctx.SetObjectSize(object->GetX() - object->GetDrawableX() + extraBorder,
object->GetY() - object->GetDrawableY() + extraBorder,
object->GetWidth() -
(object->GetX() - object->GetDrawableX()) + extraBorder,
object->GetHeight() -
(object->GetY() - object->GetDrawableY()) +
extraBorder);
if (ctx.ComputePathTo(x, y)) {
// Path found: memorize it
const ::Node* node = ctx.GetFinalNode();
while (node) {
path.push_back(sf::Vector2f(node->pos.x * (float)cellWidth,
node->pos.y * (float)cellHeight));
node = node->parent;
}
std::reverse(path.begin(), path.end());
path[0] = sf::Vector2f(object->GetX(), object->GetY());
EnterSegment(0);
pathFound = true;
return;
}
// Not path found
pathFound = false;
}
void PathfindingRuntimeBehavior::EnterSegment(std::size_t segmentNumber) {
if (path.empty()) return;
currentSegment = segmentNumber;
if (currentSegment < path.size() - 1) {
sf::Vector2f newPath = (path[currentSegment + 1] - path[currentSegment]);
totalSegmentTime = sqrtf(newPath.x * newPath.x + newPath.y * newPath.y);
timeOnSegment = 0;
reachedEnd = false;
} else {
reachedEnd = true;
speed = 0;
}
}
void PathfindingRuntimeBehavior::DoStepPreEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
if (!sceneManager) return;
if (path.empty() || reachedEnd) return;
// Update the speed of the object
float timeDelta =
static_cast<double>(object->GetElapsedTime(scene)) / 1000000.0;
speed += acceleration * timeDelta;
if (speed > maxSpeed) speed = maxSpeed;
angularSpeed = angularMaxSpeed; // No acceleration for angular speed for now
// Update the time on the segment and change segment if needed
timeOnSegment += speed * timeDelta;
if (timeOnSegment >= totalSegmentTime && currentSegment < path.size())
EnterSegment(currentSegment + 1);
// Position object on the segment and update its angle
sf::Vector2f newPos;
float pathAngle = object->GetAngle();
if (currentSegment < path.size() - 1) {
newPos = path[currentSegment] +
(path[currentSegment + 1] - path[currentSegment]) *
(timeOnSegment / totalSegmentTime);
pathAngle = atan2(path[currentSegment + 1].y - path[currentSegment].y,
path[currentSegment + 1].x - path[currentSegment].x) *
180 / 3.14159 +
angleOffset;
} else
newPos = path.back();
object->SetX(newPos.x);
object->SetY(newPos.y);
// Also update angle if needed
if (rotateObject) object->RotateTowardAngle(pathAngle, angularSpeed, scene);
}
void PathfindingRuntimeBehavior::DoStepPostEvents(RuntimeScene& scene) {
if (parentScene != &scene) // Parent scene has changed
{
parentScene = &scene;
sceneManager = parentScene
? &ScenePathfindingObstaclesManager::managers[&scene]
: NULL;
}
}
float PathfindingRuntimeBehavior::GetNodeX(std::size_t index) const {
if (index < path.size()) return path[index].x;
return 0;
}
float PathfindingRuntimeBehavior::GetNodeY(std::size_t index) const {
if (index < path.size()) return path[index].y;
return 0;
}
std::size_t PathfindingRuntimeBehavior::GetNextNodeIndex() const {
if (currentSegment + 1 < path.size())
return currentSegment + 1;
else
return path.size() - 1;
}
float PathfindingRuntimeBehavior::GetNextNodeX() const {
if (path.empty()) return 0;
if (currentSegment + 1 < path.size())
return path[currentSegment + 1].x;
else
return path.back().x;
}
float PathfindingRuntimeBehavior::GetNextNodeY() const {
if (path.empty()) return 0;
if (currentSegment + 1 < path.size())
return path[currentSegment + 1].y;
else
return path.back().y;
}
float PathfindingRuntimeBehavior::GetLastNodeX() const {
if (path.size() < 2) return 0;
if (currentSegment < path.size() - 1)
return path[currentSegment].x;
else
return path[path.size() - 1].x;
}
float PathfindingRuntimeBehavior::GetLastNodeY() const {
if (path.size() < 2) return 0;
if (currentSegment < path.size() - 1)
return path[currentSegment].y;
else
return path[path.size() - 1].y;
}
float PathfindingRuntimeBehavior::GetDestinationX() const {
if (path.empty()) return 0;
return path.back().x;
}
float PathfindingRuntimeBehavior::GetDestinationY() const {
if (path.empty()) return 0;
return path.back().y;
}

View File

@@ -0,0 +1,121 @@
/**
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#ifndef PATHFINDINGRUNTIMEBEHAVIOR_H
#define PATHFINDINGRUNTIMEBEHAVIOR_H
#include <SFML/System/Vector2.hpp>
#include <vector>
#include "GDCpp/Runtime/RuntimeBehavior.h"
#include "GDCpp/Runtime/Project/Object.h"
namespace gd {
class Layout;
}
class RuntimeScene;
class PlatformBehavior;
class ScenePathfindingObstaclesManager;
namespace gd {
class SerializerElement;
}
class RuntimeScenePlatformData;
/**
* \brief Compute path for objects avoiding obstacles
*/
class GD_EXTENSION_API PathfindingRuntimeBehavior : public RuntimeBehavior {
public:
PathfindingRuntimeBehavior(const gd::SerializerElement& behaviorContent);
virtual ~PathfindingRuntimeBehavior(){};
virtual RuntimeBehavior* Clone() const { return new PathfindingRuntimeBehavior(*this); }
/**
* \brief Compute and move on the path to the specified destination.
*/
void MoveTo(RuntimeScene& scene, float x, float y);
// Path information:
/**
* \brief Return true if the latest call to MoveTo succeeded.
*/
bool PathFound() { return pathFound; }
/**
* \brief Return true if the object reached its destination
*/
bool DestinationReached() { return reachedEnd; }
float GetNodeX(std::size_t index) const;
float GetNodeY(std::size_t index) const;
std::size_t GetNextNodeIndex() const;
std::size_t GetNodeCount() const { return path.size(); };
float GetNextNodeX() const;
float GetNextNodeY() const;
float GetLastNodeX() const;
float GetLastNodeY() const;
float GetDestinationX() const;
float GetDestinationY() const;
// Configuration:
bool DiagonalsAllowed() { return allowDiagonals; };
float GetAcceleration() { return acceleration; };
float GetMaxSpeed() { return maxSpeed; };
float GetAngularMaxSpeed() { return angularMaxSpeed; };
bool IsObjectRotated() { return rotateObject; }
float GetAngleOffset() { return angleOffset; };
unsigned int GetCellWidth() { return cellWidth; };
unsigned int GetCellHeight() { return cellHeight; };
float GetExtraBorder() { return extraBorder; };
void SetAllowDiagonals(bool allowDiagonals_) {
allowDiagonals = allowDiagonals_;
};
void SetAcceleration(float acceleration_) { acceleration = acceleration_; };
void SetMaxSpeed(float maxSpeed_) { maxSpeed = maxSpeed_; };
void SetAngularMaxSpeed(float angularMaxSpeed_) {
angularMaxSpeed = angularMaxSpeed_;
};
void SetRotateObject(bool rotateObject_) { rotateObject = rotateObject_; };
void SetAngleOffset(float angleOffset_) { angleOffset = angleOffset_; };
void SetCellWidth(unsigned int cellWidth_) { cellWidth = cellWidth_; };
void SetCellHeight(unsigned int cellHeight_) { cellHeight = cellHeight_; };
void SetExtraBorder(float extraBorder_) { extraBorder = extraBorder_; };
float GetSpeed() { return speed; };
void SetSpeed(float speed_) { speed = speed_; };
private:
virtual void DoStepPreEvents(RuntimeScene& scene);
virtual void DoStepPostEvents(RuntimeScene& scene);
void EnterSegment(std::size_t segmentNumber);
RuntimeScene* parentScene; ///< The scene the object belongs to.
ScenePathfindingObstaclesManager*
sceneManager; ///< The platform objects manager associated to the scene.
std::vector<sf::Vector2f> path; ///< The computed path
bool pathFound;
// Behavior configuration:
bool allowDiagonals;
float acceleration;
float maxSpeed;
float angularMaxSpeed;
bool rotateObject; ///< If true, the object is rotated according to the
///< current segment's angle.
float angleOffset; ///< Angle offset (added to the angle calculated with the
///< segment)
unsigned int cellWidth;
unsigned int cellHeight;
float extraBorder;
// Attributes used for traveling on the path:
float speed;
float angularSpeed;
float timeOnSegment;
float totalSegmentTime;
std::size_t currentSegment;
bool reachedEnd;
};
#endif // PATHFINDINGRUNTIMEBEHAVIOR_H

View File

@@ -6,13 +6,13 @@ This project is released under the MIT License.
*/
#include "ScenePathfindingObstaclesManager.h"
#include <iostream>
#include "PathfindingObstacleBehavior.h"
#include "PathfindingObstacleRuntimeBehavior.h"
std::map<RuntimeScene*, ScenePathfindingObstaclesManager>
ScenePathfindingObstaclesManager::managers;
ScenePathfindingObstaclesManager::~ScenePathfindingObstaclesManager() {
for (std::set<PathfindingObstacleBehavior*>::iterator it =
for (std::set<PathfindingObstacleRuntimeBehavior*>::iterator it =
allObstacles.begin();
it != allObstacles.end();
++it) {
@@ -21,10 +21,10 @@ ScenePathfindingObstaclesManager::~ScenePathfindingObstaclesManager() {
}
void ScenePathfindingObstaclesManager::AddObstacle(
PathfindingObstacleBehavior* obstacle) {
PathfindingObstacleRuntimeBehavior* obstacle) {
allObstacles.insert(obstacle);
}
void ScenePathfindingObstaclesManager::RemoveObstacle(
PathfindingObstacleBehavior* obstacle) {
PathfindingObstacleRuntimeBehavior* obstacle) {
allObstacles.erase(obstacle);
}

View File

@@ -9,10 +9,13 @@ This project is released under the MIT License.
#include <map>
#include <set>
#include "GDCpp/Runtime/RuntimeScene.h"
class PathfindingObstacleBehavior;
class PathfindingObstacleRuntimeBehavior;
/**
* \brief Contains lists of all obstacle related objects of a scene.
*
* \note Could be drastically improved by using spatial hashing (see JS
* implementation).
*/
class ScenePathfindingObstaclesManager {
public:
@@ -29,23 +32,23 @@ class ScenePathfindingObstaclesManager {
* \brief Notify the manager that there is a new obstacle on the scene.
* \param obstacle The new obstacle
*/
void AddObstacle(PathfindingObstacleBehavior* obstacle);
void AddObstacle(PathfindingObstacleRuntimeBehavior* obstacle);
/**
* \brief Notify the manager that a obstacle was removed from the scene.
* \param obstacle The removed obstacle
*/
void RemoveObstacle(PathfindingObstacleBehavior* obstacle);
void RemoveObstacle(PathfindingObstacleRuntimeBehavior* obstacle);
/**
* \brief Get a read only access to the list of all obstacles
*/
const std::set<PathfindingObstacleBehavior*>& GetAllObstacles() const {
const std::set<PathfindingObstacleRuntimeBehavior*>& GetAllObstacles() const {
return allObstacles;
}
private:
std::set<PathfindingObstacleBehavior*>
std::set<PathfindingObstacleRuntimeBehavior*>
allObstacles; ///< The list of all obstacles of the scene.
};

View File

@@ -104,7 +104,7 @@ gdjs.PathfindingObstacleRuntimeBehavior = function(runtimeScene, behaviorData, o
gdjs.PathfindingObstacleRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.PathfindingObstacleRuntimeBehavior.thisIsARuntimeBehaviorConstructor = "PathfindingBehavior::PathfindingObstacleBehavior";
gdjs.PathfindingObstacleRuntimeBehavior.prototype.ownerRemovedFromScene = function() {
gdjs.PathfindingObstacleRuntimeBehavior.prototype.onOwnerRemovedFromScene = function() {
if ( this._manager && this._registeredInManager ) this._manager.removeObstacle(this);
};

View File

@@ -8,189 +8,229 @@ This project is released under the MIT License.
* @file Tests for the Pathfinding extension.
*/
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "GDCore/CommonTools.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/RuntimeGame.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Extensions/Builtin/ObjectTools.h"
#include "../PathfindingBehavior.h"
#include "../PathfindingObstacleBehavior.h"
#include "../PathfindingObstacleRuntimeBehavior.h"
#include "../PathfindingRuntimeBehavior.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCpp/Extensions/Builtin/ObjectTools.h"
#include "GDCpp/Runtime/RuntimeGame.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "catch.hpp"
//Mock objects that can have a specific size
// Mock objects that can have a specific size
class ResizableRuntimeObject : public RuntimeObject {
public:
ResizableRuntimeObject(RuntimeScene & scene, const gd::Object & obj) :
RuntimeObject(scene, obj)
{}
public:
ResizableRuntimeObject(RuntimeScene &scene, const gd::Object &obj)
: RuntimeObject(scene, obj) {}
float GetWidth() const override { return width; }
float GetHeight() const override { return height; }
void SetWidth(float newWidth) override { width = newWidth; }
void SetHeight(float newHeight) override { height = newHeight; }
float GetWidth() const override { return width; }
float GetHeight() const override { return height; }
void SetWidth(float newWidth) override { width = newWidth; }
void SetHeight(float newHeight) override { height = newHeight; }
private:
float width;
float height;
private:
float width;
float height;
};
TEST_CASE( "PathfindingBehavior", "[game-engine][pathfinding]" ) {
SECTION("Basics") {
//Prepare some objects and the context
RuntimeGame game;
gd::Object playerObj("player");
PathfindingBehavior * behavior = new PathfindingBehavior();
behavior->SetName("Pathfinding");
playerObj.AddBehavior(behavior);
namespace {
template <class TRuntimeBehavior, class TBehavior>
std::unique_ptr<TRuntimeBehavior> CreateNewRuntimeBehavior() {
gd::SerializerElement behaviorContent;
TBehavior behavior;
behavior.InitializeContent(behaviorContent);
return std::move(gd::make_unique<TRuntimeBehavior>(behaviorContent));
};
} // namespace
RuntimeScene scene(NULL, &game);
RuntimeObject player(scene, playerObj);
TEST_CASE("PathfindingRuntimeBehavior", "[game-engine][pathfinding]") {
SECTION("Basics") {
// Prepare some objects and the context
RuntimeGame game;
gd::Object playerObject("player");
RuntimeScene scene(NULL, &game);
RuntimeObject player(scene, playerObject);
player.AddBehavior("Pathfinding",
CreateNewRuntimeBehavior<PathfindingRuntimeBehavior,
PathfindingBehavior>());
PathfindingBehavior * runtimeBehavior =
static_cast<PathfindingBehavior *>(player.GetBehaviorRawPointer("Pathfinding"));
PathfindingRuntimeBehavior *runtimeBehavior =
static_cast<PathfindingRuntimeBehavior *>(
player.GetBehaviorRawPointer("Pathfinding"));
//Check that a path can be computed without obstacles
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 66);
REQUIRE(runtimeBehavior->GetNodeX(65) == 1200); //Check that destination
REQUIRE(runtimeBehavior->GetNodeY(65) == 1300); //is correct
// Check that a path can be computed without obstacles
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 66);
REQUIRE(runtimeBehavior->GetNodeX(65) == 1200); // Check that destination
REQUIRE(runtimeBehavior->GetNodeY(65) == 1300); // is correct
//Add an obstacle
gd::Object obstacleObj("obstacle");
obstacleObj.AddBehavior(new PathfindingObstacleBehavior());
std::unique_ptr<ResizableRuntimeObject> obstacle(new ResizableRuntimeObject(scene, obstacleObj));
obstacle->SetX(1100);
obstacle->SetY(1200);
obstacle->SetWidth(200);
obstacle->SetHeight(200);
// Add an obstacle
gd::Object obstacleObj("obstacle");
std::unique_ptr<ResizableRuntimeObject> obstacle(
new ResizableRuntimeObject(scene, obstacleObj));
scene.objectsInstances.AddObject(std::move(obstacle));
obstacle->AddBehavior(
"PathfindingObstacle",
CreateNewRuntimeBehavior<PathfindingObstacleRuntimeBehavior,
PathfindingObstacleBehavior>());
scene.RenderAndStep();
obstacle->SetX(1100);
obstacle->SetY(1200);
obstacle->SetWidth(200);
obstacle->SetHeight(200);
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == false);
REQUIRE(runtimeBehavior->GetNodeCount() == 0);
}
SECTION("Obstacle in the middle") {
//Prepare some objects and the context
RuntimeGame game;
scene.objectsInstances.AddObject(std::move(obstacle));
gd::Object playerObj("player");
auto behavior = new PathfindingBehavior();
behavior->SetName("Pathfinding");
playerObj.AddBehavior(behavior);
scene.RenderAndStep();
gd::Object obstacleObj("obstacle");
obstacleObj.AddBehavior(new PathfindingObstacleBehavior());
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == false);
REQUIRE(runtimeBehavior->GetNodeCount() == 0);
}
SECTION("Obstacle in the middle") {
// Prepare some objects and the context
RuntimeGame game;
RuntimeScene scene(NULL, &game);
auto * player = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
auto * obstacle = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new ResizableRuntimeObject(scene, obstacleObj)));
gd::Object playerObj("player");
gd::Object obstacleObj("obstacle");
obstacle->SetX(300);
obstacle->SetY(600);
obstacle->SetWidth(32);
obstacle->SetHeight(32);
scene.RenderAndStep();
RuntimeScene scene(NULL, &game);
auto *player = scene.objectsInstances.AddObject(
std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
PathfindingBehavior * runtimeBehavior =
static_cast<PathfindingBehavior *>(player->GetBehaviorRawPointer("Pathfinding"));
player->AddBehavior("Pathfinding",
CreateNewRuntimeBehavior<PathfindingRuntimeBehavior,
PathfindingBehavior>());
auto *obstacle =
scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(
new ResizableRuntimeObject(scene, obstacleObj)));
obstacle->AddBehavior(
"PathfindingObstacle",
CreateNewRuntimeBehavior<PathfindingObstacleRuntimeBehavior,
PathfindingObstacleBehavior>());
//Check that a path changes when adding obstacles
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeX(30) == 540);
REQUIRE(runtimeBehavior->GetNodeY(30) == 600);
REQUIRE(runtimeBehavior->GetNodeCount() == 66);
obstacle->SetX(300);
obstacle->SetY(600);
obstacle->SetWidth(32);
obstacle->SetHeight(32);
scene.RenderAndStep();
//Enlarge the obstacle
obstacle->SetWidth(600);
scene.RenderAndStep();
PathfindingRuntimeBehavior *runtimeBehavior =
static_cast<PathfindingRuntimeBehavior *>(
player->GetBehaviorRawPointer("Pathfinding"));
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 77);
// Check that a path changes when adding obstacles
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeX(30) == 540);
REQUIRE(runtimeBehavior->GetNodeY(30) == 600);
REQUIRE(runtimeBehavior->GetNodeCount() == 66);
//Enlarge more
obstacle->SetX(0);
obstacle->SetWidth(1300);
scene.RenderAndStep();
// Enlarge the obstacle
obstacle->SetWidth(600);
scene.RenderAndStep();
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 92);
}
SECTION("Obstacles making a corridor") {
//Prepare some objects and the context
RuntimeGame game;
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 77);
gd::Object playerObj("player");
auto behavior = new PathfindingBehavior();
behavior->SetName("Pathfinding");
playerObj.AddBehavior(behavior);
// Enlarge more
obstacle->SetX(0);
obstacle->SetWidth(1300);
scene.RenderAndStep();
gd::Object obstacleObj("obstacle");
obstacleObj.AddBehavior(new PathfindingObstacleBehavior());
runtimeBehavior->MoveTo(scene, 1200, 1300);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 92);
}
SECTION("Obstacles making a corridor") {
// Prepare some objects and the context
RuntimeGame game;
RuntimeScene scene(NULL, &game);
gd::Object playerObj("player");
gd::Object obstacleObj("obstacle");
auto * player = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
auto * obstacle1 = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new ResizableRuntimeObject(scene, obstacleObj)));
auto * obstacle2 = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new ResizableRuntimeObject(scene, obstacleObj)));
RuntimeScene scene(NULL, &game);
obstacle1->SetX(-20);
obstacle2->SetX(20);
obstacle1->SetWidth(20);
obstacle1->SetHeight(20);
obstacle2->SetWidth(20);
obstacle2->SetHeight(20);
player->SetX(5);
player->SetY(25);
scene.RenderAndStep();
auto *player = scene.objectsInstances.AddObject(
std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
PathfindingBehavior * runtimeBehavior =
static_cast<PathfindingBehavior *>(player->GetBehaviorRawPointer("Pathfinding"));
player->AddBehavior("Pathfinding",
CreateNewRuntimeBehavior<PathfindingRuntimeBehavior,
PathfindingBehavior>());
auto *obstacle1 =
scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(
new ResizableRuntimeObject(scene, obstacleObj)));
obstacle1->AddBehavior(
"PathfindingObstacle",
CreateNewRuntimeBehavior<PathfindingObstacleRuntimeBehavior,
PathfindingObstacleBehavior>());
auto *obstacle2 =
scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(
new ResizableRuntimeObject(scene, obstacleObj)));
obstacle1->AddBehavior(
"PathfindingObstacle",
CreateNewRuntimeBehavior<PathfindingObstacleRuntimeBehavior,
PathfindingObstacleBehavior>());
//Check that a path changes when adding obstacles
runtimeBehavior->MoveTo(scene, 5, -5);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 2);
}
SECTION("Diagonals") {
//Prepare some objects and the context
RuntimeGame game;
obstacle1->SetX(-20);
obstacle2->SetX(20);
obstacle1->SetWidth(20);
obstacle1->SetHeight(20);
obstacle2->SetWidth(20);
obstacle2->SetHeight(20);
player->SetX(5);
player->SetY(25);
scene.RenderAndStep();
gd::Object playerObj("player");
auto behavior = new PathfindingBehavior();
behavior->SetName("Pathfinding");
playerObj.AddBehavior(behavior);
PathfindingRuntimeBehavior *runtimeBehavior =
static_cast<PathfindingRuntimeBehavior *>(
player->GetBehaviorRawPointer("Pathfinding"));
RuntimeScene scene(NULL, &game);
auto * player = scene.objectsInstances.AddObject(std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
// Check that a path changes when adding obstacles
runtimeBehavior->MoveTo(scene, 5, -5);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 2);
}
SECTION("Diagonals") {
// Prepare some objects and the context
RuntimeGame game;
PathfindingBehavior * runtimeBehavior =
static_cast<PathfindingBehavior *>(player->GetBehaviorRawPointer("Pathfinding"));
gd::Object playerObj("player");
//Test a specific path that can lead to false computations
//in case the algorithm open nodes list is not implemented properly
//and can remove node with same cost.
runtimeBehavior->MoveTo(scene, 1*20, 4*20);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 5);
REQUIRE(runtimeBehavior->GetNodeX(0) == 0);
REQUIRE(runtimeBehavior->GetNodeY(0) == 0);
REQUIRE(runtimeBehavior->GetNodeX(1) == 0);
REQUIRE(runtimeBehavior->GetNodeY(1) == 20);
REQUIRE(runtimeBehavior->GetNodeX(2) == 0);
REQUIRE(runtimeBehavior->GetNodeY(2) == 40);
REQUIRE(runtimeBehavior->GetNodeX(3) == 0);
REQUIRE(runtimeBehavior->GetNodeY(3) == 60);
REQUIRE(runtimeBehavior->GetNodeX(4) == 20);
REQUIRE(runtimeBehavior->GetNodeY(4) == 80);
}
RuntimeScene scene(NULL, &game);
auto *player = scene.objectsInstances.AddObject(
std::unique_ptr<RuntimeObject>(new RuntimeObject(scene, playerObj)));
player->AddBehavior("Pathfinding",
CreateNewRuntimeBehavior<PathfindingRuntimeBehavior,
PathfindingBehavior>());
PathfindingRuntimeBehavior *runtimeBehavior =
static_cast<PathfindingRuntimeBehavior *>(
player->GetBehaviorRawPointer("Pathfinding"));
// Test a specific path that can lead to false computations
// in case the algorithm open nodes list is not implemented properly
// and can remove node with same cost.
runtimeBehavior->MoveTo(scene, 1 * 20, 4 * 20);
REQUIRE(runtimeBehavior->PathFound() == true);
REQUIRE(runtimeBehavior->GetNodeCount() == 5);
REQUIRE(runtimeBehavior->GetNodeX(0) == 0);
REQUIRE(runtimeBehavior->GetNodeY(0) == 0);
REQUIRE(runtimeBehavior->GetNodeX(1) == 0);
REQUIRE(runtimeBehavior->GetNodeY(1) == 20);
REQUIRE(runtimeBehavior->GetNodeX(2) == 0);
REQUIRE(runtimeBehavior->GetNodeY(2) == 40);
REQUIRE(runtimeBehavior->GetNodeX(3) == 0);
REQUIRE(runtimeBehavior->GetNodeY(3) == 60);
REQUIRE(runtimeBehavior->GetNodeX(4) == 20);
REQUIRE(runtimeBehavior->GetNodeY(4) == 80);
}
}

View File

@@ -27,98 +27,98 @@ module.exports = {
propertyName,
newValue
) {
if (propertyName === 'type') {
behaviorContent.type = newValue;
if (propertyName === 'bodyType') {
behaviorContent.getChild('bodyType').setStringValue(newValue);
return true;
}
if (propertyName === 'bullet') {
behaviorContent.bullet = newValue === '1';
behaviorContent.getChild('bullet').setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'fixedRotation') {
behaviorContent.fixedRotation = newValue === '1';
behaviorContent.getChild('fixedRotation').setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'canSleep') {
behaviorContent.canSleep = newValue === '1';
behaviorContent.getChild('canSleep').setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'shape') {
behaviorContent.shape = newValue;
behaviorContent.getChild('shape').setStringValue(newValue);
return true;
}
if (propertyName === 'shapeDimensionA') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeDimensionA = newValue;
behaviorContent.getChild('shapeDimensionA').setDoubleValue(newValue);
return true;
}
if (propertyName === 'shapeDimensionB') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeDimensionB = newValue;
behaviorContent.getChild('shapeDimensionB').setDoubleValue(newValue);
return true;
}
if (propertyName === 'shapeOffsetX') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeOffsetX = newValue;
behaviorContent.getChild('shapeOffsetX').setDoubleValue(newValue);
return true;
}
if (propertyName === 'shapeOffsetY') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeOffsetY = newValue;
behaviorContent.getChild('shapeOffsetY').setDoubleValue(newValue);
return true;
}
if (propertyName === 'polygonOrigin') {
behaviorContent.polygonOrigin = newValue;
behaviorContent.getChild('polygonOrigin').setStringValue(newValue);
return true;
}
if (propertyName === 'vertices') {
behaviorContent.vertices = JSON.parse(newValue);
behaviorContent.setChild('vertices', gd.Serializer.fromJSON(newValue));
return true;
}
if (propertyName === 'density') {
behaviorContent.density = parseFloat(newValue);
behaviorContent.getChild('density').setDoubleValue(parseFloat(newValue));
return true;
}
if (propertyName === 'friction') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.friction = newValue;
behaviorContent.getChild('friction').setDoubleValue(newValue);
return true;
}
if (propertyName === 'restitution') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.restitution = newValue;
behaviorContent.getChild('restitution').setDoubleValue(newValue);
return true;
}
if (propertyName === 'linearDamping') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.linearDamping = newValue;
behaviorContent.getChild('linearDamping').setDoubleValue(newValue);
return true;
}
if (propertyName === 'angularDamping') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.angularDamping = newValue;
behaviorContent.getChild('angularDamping').setDoubleValue(newValue);
return true;
}
if (propertyName === 'gravityScale') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.gravityScale = newValue;
behaviorContent.getChild('gravityScale').setDoubleValue(newValue);
return true;
}
if (propertyName === 'layers') {
behaviorContent.layers = parseInt(newValue);
behaviorContent.getChild('layers').setIntValue(parseInt(newValue, 10));
return true;
}
if (propertyName === 'masks') {
behaviorContent.masks = parseInt(newValue);
behaviorContent.getChild('masks').setIntValue(parseInt(newValue, 10));
return true;
}
};
@@ -126,8 +126,8 @@ module.exports = {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
behaviorProperties.set(
'type',
new gd.PropertyDescriptor(behaviorContent.type)
'bodyType',
new gd.PropertyDescriptor(behaviorContent.getChild('bodyType').getStringValue())
.setType('Choice')
.setLabel('Type')
.addExtraInfo('Static')
@@ -136,27 +136,31 @@ module.exports = {
);
behaviorProperties.set(
'bullet',
new gd.PropertyDescriptor(behaviorContent.bullet ? 'true' : 'false')
new gd.PropertyDescriptor(
behaviorContent.getChild('bullet').getBoolValue() ? 'true' : 'false'
)
.setType('Boolean')
.setLabel('Bullet')
);
behaviorProperties.set(
'fixedRotation',
new gd.PropertyDescriptor(
behaviorContent.fixedRotation ? 'true' : 'false'
behaviorContent.getChild('fixedRotation').getBoolValue() ? 'true' : 'false'
)
.setType('Boolean')
.setLabel('Fixed Rotation')
);
behaviorProperties.set(
'canSleep',
new gd.PropertyDescriptor(behaviorContent.canSleep ? 'true' : 'false')
new gd.PropertyDescriptor(
behaviorContent.getChild('canSleep').getBoolValue() ? 'true' : 'false'
)
.setType('Boolean')
.setLabel('Can Sleep')
);
behaviorProperties.set(
'shape',
new gd.PropertyDescriptor(behaviorContent.shape)
new gd.PropertyDescriptor(behaviorContent.getChild('shape').getStringValue())
.setType('Choice')
.setLabel('Shape')
.addExtraInfo('Box')
@@ -166,31 +170,41 @@ module.exports = {
);
behaviorProperties.set(
'shapeDimensionA',
new gd.PropertyDescriptor(behaviorContent.shapeDimensionA.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('shapeDimensionA').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Shape Dimension A')
);
behaviorProperties.set(
'shapeDimensionB',
new gd.PropertyDescriptor(behaviorContent.shapeDimensionB.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('shapeDimensionB').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Shape Dimension B')
);
behaviorProperties.set(
'shapeOffsetX',
new gd.PropertyDescriptor(behaviorContent.shapeOffsetX.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('shapeOffsetX').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Shape Offset X')
);
behaviorProperties.set(
'shapeOffsetY',
new gd.PropertyDescriptor(behaviorContent.shapeOffsetY.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('shapeOffsetY').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Shape Offset Y')
);
behaviorProperties.set(
'polygonOrigin',
new gd.PropertyDescriptor(behaviorContent.polygonOrigin || 'Center')
new gd.PropertyDescriptor(
behaviorContent.getChild('polygonOrigin').getStringValue() || 'Center'
)
.setType('Choice')
.setLabel('Polygon Origin')
.addExtraInfo('Center')
@@ -200,54 +214,70 @@ module.exports = {
behaviorProperties.set(
'vertices',
new gd.PropertyDescriptor(
JSON.stringify(behaviorContent.vertices || [])
gd.Serializer.toJSON(behaviorContent.getChild('vertices')) || '[]'
).setLabel('Vertices')
);
behaviorProperties.set(
'density',
new gd.PropertyDescriptor(behaviorContent.density.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('density').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Density')
);
behaviorProperties.set(
'friction',
new gd.PropertyDescriptor(behaviorContent.friction.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('friction').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Friction')
);
behaviorProperties.set(
'restitution',
new gd.PropertyDescriptor(behaviorContent.restitution.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('restitution').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Restitution')
);
behaviorProperties.set(
'linearDamping',
new gd.PropertyDescriptor(behaviorContent.linearDamping.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('linearDamping').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Linear Damping')
);
behaviorProperties.set(
'angularDamping',
new gd.PropertyDescriptor(behaviorContent.angularDamping.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('angularDamping').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Angular Damping')
);
behaviorProperties.set(
'gravityScale',
new gd.PropertyDescriptor(behaviorContent.gravityScale.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('gravityScale').getDoubleValue().toString(10)
)
.setType('Number')
.setLabel('Gravity Scale')
);
behaviorProperties.set(
'layers',
new gd.PropertyDescriptor(behaviorContent.layers.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('layers').getIntValue().toString(10)
)
.setType('Number')
.setLabel('Layers')
);
behaviorProperties.set(
'masks',
new gd.PropertyDescriptor(behaviorContent.masks.toString(10))
new gd.PropertyDescriptor(
behaviorContent.getChild('masks').getIntValue().toString(10)
)
.setType('Number')
.setLabel('Masks')
);
@@ -255,29 +285,27 @@ module.exports = {
return behaviorProperties;
};
physics2Behavior.setRawJSONContent(
JSON.stringify({
type: 'Dynamic',
bullet: false,
fixedRotation: false,
canSleep: true,
shape: 'Box',
shapeDimensionA: 0,
shapeDimensionB: 0,
shapeOffsetX: 0,
shapeOffsetY: 0,
polygonOrigin: 'Center',
vertices: [],
density: 1.0,
friction: 0.3,
restitution: 0.1,
linearDamping: 0.1,
angularDamping: 0.1,
gravityScale: 1,
layers: 1,
masks: 1,
})
);
physics2Behavior.initializeContent = function(behaviorContent) {
behaviorContent.addChild('bodyType').setStringValue('Dynamic');
behaviorContent.addChild('bullet').setBoolValue(false);
behaviorContent.addChild('fixedRotation').setBoolValue(false);
behaviorContent.addChild('canSleep').setBoolValue(true);
behaviorContent.addChild('shape').setStringValue('Box');
behaviorContent.addChild('shapeDimensionA').setDoubleValue(0);
behaviorContent.addChild('shapeDimensionB').setDoubleValue(0);
behaviorContent.addChild('shapeOffsetX').setDoubleValue(0);
behaviorContent.addChild('shapeOffsetY').setDoubleValue(0);
behaviorContent.addChild('polygonOrigin').setStringValue('Center');
behaviorContent.addChild('vertices').considerAsArray();
behaviorContent.addChild('density').setDoubleValue(1.0);
behaviorContent.addChild('friction').setDoubleValue(0.3);
behaviorContent.addChild('restitution').setDoubleValue(0.1);
behaviorContent.addChild('linearDamping').setDoubleValue(0.1);
behaviorContent.addChild('angularDamping').setDoubleValue(0.1);
behaviorContent.addChild('gravityScale').setDoubleValue(1);
behaviorContent.addChild('layers').setIntValue(1);
behaviorContent.addChild('masks').setIntValue(1);
};
var sharedData = new gd.BehaviorSharedDataJsImplementation();
sharedData.updateProperty = function(
@@ -288,25 +316,25 @@ module.exports = {
if (propertyName === 'gravityX') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
sharedContent.gravityX = newValue;
sharedContent.getChild('gravityX').setDoubleValue(newValue);
return true;
}
if (propertyName === 'gravityY') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
sharedContent.gravityY = newValue;
sharedContent.getChild('gravityY').setDoubleValue(newValue);
return true;
}
if (propertyName === 'scaleX') {
newValue = parseInt(newValue, 10);
if (newValue !== newValue) return false;
sharedContent.scaleX = newValue;
sharedContent.getChild('scaleX').setDoubleValue(newValue);
return true;
}
if (propertyName === 'scaleY') {
newValue = parseInt(newValue, 10);
if (newValue !== newValue) return false;
sharedContent.scaleY = newValue;
sharedContent.getChild('scaleY').setDoubleValue(newValue);
return true;
}
@@ -314,42 +342,41 @@ module.exports = {
};
sharedData.getProperties = function(sharedContent) {
var sharedProperties = new gd.MapStringPropertyDescriptor();
console.log(gd.Serializer.toJSON(sharedContent));
sharedProperties.set(
'gravityX',
new gd.PropertyDescriptor(sharedContent.gravityX.toString(10)).setType(
'Number'
)
new gd.PropertyDescriptor(
sharedContent.getChild('gravityX').getDoubleValue().toString(10)
).setType('Number')
);
sharedProperties.set(
'gravityY',
new gd.PropertyDescriptor(sharedContent.gravityY.toString(10)).setType(
'Number'
)
new gd.PropertyDescriptor(
sharedContent.getChild('gravityY').getDoubleValue().toString(10)
).setType('Number')
);
sharedProperties.set(
'scaleX',
new gd.PropertyDescriptor(sharedContent.scaleX.toString(10)).setType(
'Number'
)
new gd.PropertyDescriptor(
sharedContent.getChild('scaleX').getDoubleValue().toString(10)
).setType('Number')
);
sharedProperties.set(
'scaleY',
new gd.PropertyDescriptor(sharedContent.scaleY.toString(10)).setType(
'Number'
)
new gd.PropertyDescriptor(
sharedContent.getChild('scaleY').getDoubleValue().toString(10)
).setType('Number')
);
return sharedProperties;
};
sharedData.setRawJSONContent(
JSON.stringify({
gravityX: 0,
gravityY: 9.8,
scaleX: 100,
scaleY: 100,
})
);
sharedData.initializeContent = function(behaviorContent) {
behaviorContent.getChild("gravityX").setDoubleValue(0);
behaviorContent.getChild("gravityY").setDoubleValue(9.8);
behaviorContent.getChild("scaleX").setDoubleValue(100);
behaviorContent.getChild("scaleY").setDoubleValue(100);
}
var aut = extension
// extension
@@ -357,7 +384,9 @@ module.exports = {
'Physics2Behavior',
_('Physics Engine 2.0'),
'Physics2',
_('Simulate realistic object physics, with gravity, forces, joints, etc.'),
_(
'Simulate realistic object physics, with gravity, forces, joints, etc.'
),
'',
'res/physics32.png',
'Physics2Behavior',
@@ -1031,7 +1060,7 @@ module.exports = {
.setManipulatedType('number')
.setGetter('getGravityScale');
aut
aut
.addExpression(
'GravityScale',
_('Gravity scale of the object'),

View File

@@ -1,10 +1,10 @@
gdjs.Physics2SharedData = function(runtimeScene, sharedData) {
this.gravityX = sharedData.content.gravityX;
this.gravityY = sharedData.content.gravityY;
this.gravityX = sharedData.gravityX;
this.gravityY = sharedData.gravityY;
this.scaleX =
sharedData.content.scaleX === 0 ? 100 : sharedData.content.scaleX;
sharedData.scaleX === 0 ? 100 : sharedData.scaleX;
this.scaleY =
sharedData.content.scaleY === 0 ? 100 : sharedData.content.scaleY;
sharedData.scaleY === 0 ? 100 : sharedData.scaleY;
this.invScaleX = 1 / this.scaleX;
this.invScaleY = 1 / this.scaleY;
@@ -194,28 +194,28 @@ gdjs.Physics2SharedData.prototype.removeJoint = function(jointId) {
gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) {
gdjs.RuntimeBehavior.call(this, runtimeScene, behaviorData, owner);
this.type = behaviorData.content.type;
this.bullet = behaviorData.content.bullet;
this.fixedRotation = behaviorData.content.fixedRotation;
this.canSleep = behaviorData.content.canSleep;
this.shape = behaviorData.content.shape;
this.shapeDimensionA = behaviorData.content.shapeDimensionA;
this.shapeDimensionB = behaviorData.content.shapeDimensionB;
this.shapeOffsetX = behaviorData.content.shapeOffsetX;
this.shapeOffsetY = behaviorData.content.shapeOffsetY;
this.polygonOrigin = behaviorData.content.polygonOrigin;
this.bodyType = behaviorData.bodyType;
this.bullet = behaviorData.bullet;
this.fixedRotation = behaviorData.fixedRotation;
this.canSleep = behaviorData.canSleep;
this.shape = behaviorData.shape;
this.shapeDimensionA = behaviorData.shapeDimensionA;
this.shapeDimensionB = behaviorData.shapeDimensionB;
this.shapeOffsetX = behaviorData.shapeOffsetX;
this.shapeOffsetY = behaviorData.shapeOffsetY;
this.polygonOrigin = behaviorData.polygonOrigin;
this.polygon =
this.shape === 'Polygon'
? gdjs.Physics2RuntimeBehavior.getPolygon(behaviorData.content.vertices)
? gdjs.Physics2RuntimeBehavior.getPolygon(behaviorData.vertices)
: null;
this.density = behaviorData.content.density;
this.friction = behaviorData.content.friction;
this.restitution = behaviorData.content.restitution;
this.linearDamping = behaviorData.content.linearDamping;
this.angularDamping = behaviorData.content.angularDamping;
this.gravityScale = behaviorData.content.gravityScale;
this.layers = behaviorData.content.layers;
this.masks = behaviorData.content.masks;
this.density = behaviorData.density;
this.friction = behaviorData.friction;
this.restitution = behaviorData.restitution;
this.linearDamping = behaviorData.linearDamping;
this.angularDamping = behaviorData.angularDamping;
this.gravityScale = behaviorData.gravityScale;
this.layers = behaviorData.layers;
this.masks = behaviorData.masks;
this.shapeScale = 1;
this.currentContacts = this.currentContacts || [];
this.currentContacts.length = 0;
@@ -268,7 +268,7 @@ gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() {
}
};
gdjs.Physics2RuntimeBehavior.prototype.ownerRemovedFromScene = function() {
gdjs.Physics2RuntimeBehavior.prototype.onOwnerRemovedFromScene = function() {
this.onDeActivate();
};
@@ -512,9 +512,9 @@ gdjs.Physics2RuntimeBehavior.prototype.createBody = function() {
// Set body settings
bodyDef.set_type(
this.type === 'Static'
this.bodyType === 'Static'
? Box2D.b2_staticBody
: this.type === 'Kinematic'
: this.bodyType === 'Kinematic'
? Box2D.b2_kinematicBody
: Box2D.b2_dynamicBody
);
@@ -656,14 +656,14 @@ gdjs.Physics2RuntimeBehavior.setTimeScaleFromObject = function(
};
gdjs.Physics2RuntimeBehavior.prototype.isDynamic = function() {
return this.type === 'Dynamic';
return this.bodyType === 'Dynamic';
};
gdjs.Physics2RuntimeBehavior.prototype.setDynamic = function() {
// Check if there is no modification
if (this.type === 'Dynamic') return;
if (this.bodyType === 'Dynamic') return;
// Change body type
this.type = 'Dynamic';
this.bodyType = 'Dynamic';
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
@@ -675,14 +675,14 @@ gdjs.Physics2RuntimeBehavior.prototype.setDynamic = function() {
};
gdjs.Physics2RuntimeBehavior.prototype.isStatic = function() {
return this.type === 'Static';
return this.bodyType === 'Static';
};
gdjs.Physics2RuntimeBehavior.prototype.setStatic = function() {
// Check if there is no modification
if (this.type === 'Static') return;
if (this.bodyType === 'Static') return;
// Change body type
this.type = 'Static';
this.bodyType = 'Static';
// If there is no body, set a new one
if (this._body === null) {
this.createBody();
@@ -694,14 +694,14 @@ gdjs.Physics2RuntimeBehavior.prototype.setStatic = function() {
};
gdjs.Physics2RuntimeBehavior.prototype.isKinematic = function() {
return this.type === 'Kinematic';
return this.bodyType === 'Kinematic';
};
gdjs.Physics2RuntimeBehavior.prototype.setKinematic = function() {
// Check if there is no modification
if (this.type === 'Kinematic') return;
if (this.bodyType === 'Kinematic') return;
// Change body type
this.type = 'Kinematic';
this.bodyType = 'Kinematic';
// If there is no body, set a new one
if (this._body === null) {
this.createBody();

View File

@@ -5,15 +5,15 @@ Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include "ContactListener.h"
#include "PhysicsBehavior.h"
#include "PhysicsRuntimeBehavior.h"
void ContactListener::BeginContact(b2Contact *contact) {
if (!contact->GetFixtureA()->GetBody() || !contact->GetFixtureB()->GetBody())
return;
PhysicsBehavior *behavior1 = static_cast<PhysicsBehavior *>(
PhysicsRuntimeBehavior *behavior1 = static_cast<PhysicsRuntimeBehavior *>(
contact->GetFixtureA()->GetBody()->GetUserData());
PhysicsBehavior *behavior2 = static_cast<PhysicsBehavior *>(
PhysicsRuntimeBehavior *behavior2 = static_cast<PhysicsRuntimeBehavior *>(
contact->GetFixtureB()->GetBody()->GetUserData());
behavior1->currentContacts.insert(behavior2);
behavior2->currentContacts.insert(behavior1);
@@ -23,9 +23,9 @@ void ContactListener::EndContact(b2Contact *contact) {
if (!contact->GetFixtureA()->GetBody() || !contact->GetFixtureB()->GetBody())
return;
PhysicsBehavior *behavior1 = static_cast<PhysicsBehavior *>(
PhysicsRuntimeBehavior *behavior1 = static_cast<PhysicsRuntimeBehavior *>(
contact->GetFixtureA()->GetBody()->GetUserData());
PhysicsBehavior *behavior2 = static_cast<PhysicsBehavior *>(
PhysicsRuntimeBehavior *behavior2 = static_cast<PhysicsRuntimeBehavior *>(
contact->GetFixtureB()->GetBody()->GetUserData());
behavior1->currentContacts.erase(behavior2);
behavior2->currentContacts.erase(behavior1);

View File

@@ -11,7 +11,9 @@ This project is released under the MIT License.
#include "GDCpp/Extensions/ExtensionBase.h"
#include "PhysicsBehavior.h"
#include "PhysicsRuntimeBehavior.h"
#include "ScenePhysicsDatas.h"
#include "RuntimeScenePhysicsDatas.h"
void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
extension
@@ -38,8 +40,6 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<ScenePhysicsDatas>());
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
aut.AddAction("SetStatic",
_("Make the object static"),
_("Make the object immovable."),
@@ -51,7 +51,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetStatic")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetDynamic",
_("Make the object dynamic"),
@@ -65,7 +65,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetDynamic")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("IsDynamic",
_("The object is dynamic"),
@@ -79,7 +79,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.SetFunctionName("IsDynamic")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetFixedRotation",
_("Fix rotation"),
@@ -92,7 +92,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFixedRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction(
"AddRevoluteJoint",
@@ -109,7 +109,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Hinge Y position"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddRevoluteJoint")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("AddRevoluteJointBetweenObjects",
_("Add a hinge between two objects"),
@@ -135,7 +135,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
true)
.SetDefaultValue("0")
.SetFunctionName("AddRevoluteJointBetweenObjects")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ActAddGearJointBetweenObjects",
_("Add a gear between two objects"),
@@ -151,7 +151,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.SetDefaultValue("1")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddGearJointBetweenObjects")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetFreeRotation",
_("Make object's rotation free"),
@@ -164,7 +164,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFreeRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("IsFixedRotation",
_("Fixed rotation"),
@@ -177,7 +177,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsFixedRotation")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetAsBullet",
_("Treat object like a bullet."),
@@ -191,7 +191,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("DontSetAsBullet",
_("Do not treat object like a bullet"),
@@ -205,7 +205,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("DontSetAsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("IsBullet",
_("Object is treated like a bullet"),
@@ -218,7 +218,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsBullet")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ApplyImpulse",
_("Apply an impulse"),
@@ -233,7 +233,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y component ( Newtons/Seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulse")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ApplyImpulseUsingPolarCoordinates",
_("Apply an impulse (angle)"),
@@ -250,7 +250,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseUsingPolarCoordinates")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction(
"ApplyImpulseTowardPosition",
@@ -268,7 +268,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseTowardPosition")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ApplyForce",
_("Add a force"),
@@ -283,7 +283,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y component ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForce")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ApplyForceUsingPolarCoordinates",
_("Apply a force ( angle )"),
@@ -299,7 +299,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceUsingPolarCoordinates")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction(
"ApplyForceTowardPosition",
@@ -317,7 +317,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceTowardPosition")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("ApplyTorque",
_("Add a torque (a rotation)"),
@@ -331,7 +331,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Torque value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyTorque")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetLinearVelocity",
_("Linear velocity"),
@@ -346,7 +346,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition(
"LinearVelocityX",
@@ -363,7 +363,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityX")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition(
"LinearVelocityY",
@@ -380,7 +380,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("LinearVelocity",
_("Linear speed"),
@@ -396,7 +396,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetAngularVelocity",
_("Angular speed"),
@@ -410,7 +410,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("New value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("AngularVelocity",
_("Angular speed"),
@@ -426,7 +426,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("LinearDamping",
_("Linear damping"),
@@ -442,7 +442,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("CollisionWith",
_("Collision"),
@@ -459,7 +459,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("objectList", _("Object"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("CollisionWith")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetLinearDamping",
_("Linear damping"),
@@ -473,7 +473,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition("AngularDamping",
_("Angular damping"),
@@ -489,7 +489,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetAngularDamping",
_("Angular damping"),
@@ -503,7 +503,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetGravity",
_("Gravity"),
@@ -518,7 +518,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetGravity")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetPolygonScaleX",
_("Change the X scale of a collision polygon"),
@@ -534,7 +534,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleX")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddAction("SetPolygonScaleY",
_("Change the Y scale of a collision polygon"),
@@ -550,7 +550,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleY")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition(
"GetPolygonScaleX",
@@ -568,7 +568,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleX")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddCondition(
"GetPolygonScaleY",
@@ -586,7 +586,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY")
.SetManipulatedType("number")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("PolygonScaleX",
_("Collision polygon X scale"),
@@ -597,7 +597,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleX")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("PolygonScaleY",
_("Collision polygon Y scale"),
@@ -608,7 +608,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("LinearVelocity",
_("Linear speed"),
@@ -619,7 +619,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("LinearVelocityX",
_("X component"),
@@ -630,7 +630,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityX")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("LinearVelocityY",
_("Y component"),
@@ -641,7 +641,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("AngularVelocity",
_("Angular speed"),
@@ -652,7 +652,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("LinearDamping",
_("Linear damping"),
@@ -663,7 +663,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
aut.AddExpression("AngularDamping",
_("Angular damping"),
@@ -674,7 +674,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping")
.SetIncludeFile("PhysicsBehavior/PhysicsBehavior.h");
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
#endif
}
@@ -691,6 +691,15 @@ class PhysicsBehaviorCppExtension : public ExtensionBase {
*/
PhysicsBehaviorCppExtension() {
DeclarePhysicsBehaviorExtension(*this);
AddRuntimeBehavior<PhysicsRuntimeBehavior>(
GetBehaviorMetadata("PhysicsBehavior::PhysicsBehavior"),
"PhysicsRuntimeBehavior");
GetBehaviorMetadata("PhysicsBehavior::PhysicsBehavior")
.SetIncludeFile("PhysicsBehavior/PhysicsRuntimeBehavior.h");
AddBehaviorsRuntimeSharedData<RuntimeScenePhysicsDatas>(
GetBehaviorMetadata("PhysicsBehavior::PhysicsBehavior"));
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};

View File

@@ -7,15 +7,9 @@ This project is released under the MIT License.
#include "PhysicsBehavior.h"
#include <string>
#include "Box2D/Box2D.h"
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/Project/Project.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "RuntimeScenePhysicsDatas.h"
#include "Triangulation/triangulate.h"
#if defined(GD_IDE_ONLY)
#include <map>
@@ -24,717 +18,42 @@ This project is released under the MIT License.
#undef GetObject
PhysicsBehavior::PhysicsBehavior()
: Behavior(),
shapeType(Box),
dynamic(true),
fixedRotation(false),
isBullet(false),
massDensity(1),
averageFriction(0.8),
averageRestitution(0),
linearDamping(0.1),
angularDamping(0.1),
body(NULL),
runtimeScenesPhysicsDatas(NULL) {
polygonHeight = 200;
polygonWidth = 200;
automaticResizing = false;
polygonPositioning = OnCenter;
polygonScaleX = 1;
polygonScaleY = 1;
}
PhysicsBehavior::~PhysicsBehavior() {
if (runtimeScenesPhysicsDatas != NULL && body)
runtimeScenesPhysicsDatas->world->DestroyBody(body);
}
/**
* Called at each frame before events :
* Simulate the world if necessary and update body positions.
*/
void PhysicsBehavior::DoStepPreEvents(RuntimeScene &scene) {
if (!body) CreateBody(scene);
if (!runtimeScenesPhysicsDatas
->stepped) // Simulate the world, once at each frame
{
runtimeScenesPhysicsDatas->StepWorld(
static_cast<double>(scene.GetTimeManager().GetElapsedTime()) /
1000000.0,
6,
10);
runtimeScenesPhysicsDatas->stepped = true;
}
// Update object position according to Box2D body
b2Vec2 position = body->GetPosition();
object->SetX(position.x * runtimeScenesPhysicsDatas->GetScaleX() -
object->GetWidth() / 2 + object->GetX() -
object->GetDrawableX());
object->SetY(-position.y * runtimeScenesPhysicsDatas->GetScaleY() -
object->GetHeight() / 2 + object->GetY() -
object->GetDrawableY()); // Y axis is inverted
object->SetAngle(-body->GetAngle() * 180.0f / b2_pi); // Angles are inverted
objectOldX = object->GetX();
objectOldY = object->GetY();
objectOldAngle = object->GetAngle();
};
/**
* Called at each frame after events :
* Update Box2D body if necessary
*/
void PhysicsBehavior::DoStepPostEvents(RuntimeScene &scene) {
if (!body) CreateBody(scene);
// Note: Strange bug here, using SpriteObject, the tests objectOldWidth !=
// newWidth and objectOldHeight != newHeight keeps being true even if the two
// values were exactly the same. Maybe a floating point round error ( the
// values were integer yet in my tests! ) so we cast the values to int to
// ensure that the body is not continuously recreated.
float newWidth = object->GetWidth();
float newHeight = object->GetHeight();
if ((int)objectOldWidth != (int)newWidth ||
(int)objectOldHeight != (int)newHeight) {
double oldAngularVelocity = body->GetAngularVelocity();
b2Vec2 oldVelocity = body->GetLinearVelocity();
runtimeScenesPhysicsDatas->world->DestroyBody(body);
CreateBody(scene);
body->SetAngularVelocity(oldAngularVelocity);
body->SetLinearVelocity(oldVelocity);
}
runtimeScenesPhysicsDatas->stepped = false; // Prepare for a new simulation
if (objectOldX == object->GetX() && objectOldY == object->GetY() &&
objectOldAngle == object->GetAngle())
return;
b2Vec2 oldPos;
oldPos.x = (object->GetDrawableX() + object->GetWidth() / 2) *
runtimeScenesPhysicsDatas->GetInvScaleX();
oldPos.y = -(object->GetDrawableY() + object->GetHeight() / 2) *
runtimeScenesPhysicsDatas->GetInvScaleY(); // Y axis is inverted
body->SetTransform(
oldPos, -object->GetAngle() * b2_pi / 180.0f); // Angles are inverted
body->SetAwake(true);
}
/**
* Prepare Box2D body, and set up also runtimeScenePhysicsDatasPtr.
*/
void PhysicsBehavior::CreateBody(const RuntimeScene &scene) {
if (runtimeScenesPhysicsDatas == NULL)
runtimeScenesPhysicsDatas = static_cast<RuntimeScenePhysicsDatas *>(
scene.GetBehaviorSharedData(name).get());
// Create body from object
b2BodyDef bodyDef;
bodyDef.type = dynamic ? b2_dynamicBody : b2_staticBody;
bodyDef.position.Set((object->GetDrawableX() + object->GetWidth() / 2) *
runtimeScenesPhysicsDatas->GetInvScaleX(),
-(object->GetDrawableY() + object->GetHeight() / 2) *
runtimeScenesPhysicsDatas->GetInvScaleY());
bodyDef.angle = -object->GetAngle() * b2_pi / 180.0f; // Angles are inverted
bodyDef.angularDamping = angularDamping > 0.0f ? angularDamping : 0.0f;
bodyDef.linearDamping = linearDamping > 0.0f ? linearDamping : 0.0f;
bodyDef.bullet = isBullet;
bodyDef.fixedRotation = fixedRotation;
body = runtimeScenesPhysicsDatas->world->CreateBody(&bodyDef);
body->SetUserData(this);
// Setup body
if (shapeType == Circle) {
b2FixtureDef fixtureDef;
b2CircleShape circle;
circle.m_radius =
(object->GetWidth() * runtimeScenesPhysicsDatas->GetInvScaleX() +
object->GetHeight() * runtimeScenesPhysicsDatas->GetInvScaleY()) /
4; // Radius is based on the average of height and width
if (circle.m_radius <= 0) circle.m_radius = 1;
fixtureDef.shape = &circle;
fixtureDef.density = massDensity;
fixtureDef.friction = averageFriction;
fixtureDef.restitution = averageRestitution;
body->CreateFixture(&fixtureDef);
} else if (shapeType == CustomPolygon && polygonCoords.size() > 2) {
// Make a polygon triangulation to make possible to use a concave polygon
// and more than 8 edged polygons
std::vector<sf::Vector2f> resultOfTriangulation;
Triangulate::Process(polygonCoords, resultOfTriangulation);
// Iterate over all triangles
for (std::size_t i = 0; i < resultOfTriangulation.size() / 3; i++) {
b2FixtureDef fixtureDef;
b2PolygonShape dynamicBox;
// Create vertices
b2Vec2 vertices[3];
std::size_t b = 0;
for (int a = 2; a >= 0; a--) // Box2D use another direction for vertices
{
if (polygonPositioning == OnOrigin) {
vertices[b].Set(
(resultOfTriangulation.at(i * 3 + a).x * GetPolygonScaleX() -
object->GetWidth() / 2 -
(object->GetDrawableX() - object->GetX())) *
runtimeScenesPhysicsDatas->GetInvScaleX(),
(((object->GetHeight() -
(resultOfTriangulation.at(i * 3 + a).y * GetPolygonScaleY())) -
object->GetHeight() / 2 +
(object->GetDrawableY() - object->GetY())) *
runtimeScenesPhysicsDatas->GetInvScaleY()));
}
/*else if(polygonPositioning == OnTopLeftCorner)
{
vertices[b].Set((resultOfTriangulation.at(i*3 + a).x *
GetPolygonScaleX() - object->GetWidth()/2 ) *
runtimeScenesPhysicsDatas->GetInvScaleX(),
(((object->GetHeight() -
(resultOfTriangulation.at(i*3 + a).y * GetPolygonScaleY())) -
object->GetHeight()/2) *
runtimeScenesPhysicsDatas->GetInvScaleY()));
}*/
else if (polygonPositioning == OnCenter) {
vertices[b].Set(
(resultOfTriangulation.at(i * 3 + a).x * GetPolygonScaleX()) *
runtimeScenesPhysicsDatas->GetInvScaleX(),
(((object->GetHeight() -
(resultOfTriangulation.at(i * 3 + a).y * GetPolygonScaleY())) -
object->GetHeight()) *
runtimeScenesPhysicsDatas->GetInvScaleY()));
}
b++;
}
dynamicBox.Set(vertices, 3);
fixtureDef.shape = &dynamicBox;
fixtureDef.density = massDensity;
fixtureDef.friction = averageFriction;
fixtureDef.restitution = averageRestitution;
body->CreateFixture(&fixtureDef);
}
} else {
b2FixtureDef fixtureDef;
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox((object->GetWidth() > 0 ? object->GetWidth() : 1.0f) *
runtimeScenesPhysicsDatas->GetInvScaleX() / 2,
(object->GetHeight() > 0 ? object->GetHeight() : 1.0f) *
runtimeScenesPhysicsDatas->GetInvScaleY() / 2);
fixtureDef.shape = &dynamicBox;
fixtureDef.density = massDensity;
fixtureDef.friction = averageFriction;
fixtureDef.restitution = averageRestitution;
body->CreateFixture(&fixtureDef);
}
objectOldWidth = object->GetWidth();
objectOldHeight = object->GetHeight();
}
void PhysicsBehavior::OnDeActivate() {
if (runtimeScenesPhysicsDatas && body) {
runtimeScenesPhysicsDatas->world->DestroyBody(body);
body = NULL; // Of course: body can ( and will ) be reused: Make sure we
// nullify the pointer as the body was destroyed.
}
}
/**
* Set a body to be static
*/
void PhysicsBehavior::SetStatic(RuntimeScene &scene) {
dynamic = false;
if (!body) CreateBody(scene);
body->SetType(b2_staticBody);
}
/**
* Set a body to be dynamic
*/
void PhysicsBehavior::SetDynamic(RuntimeScene &scene) {
dynamic = true;
if (!body) CreateBody(scene);
body->SetType(b2_dynamicBody);
body->SetAwake(true);
}
/**
* Set rotation to be fixed
*/
void PhysicsBehavior::SetFixedRotation(RuntimeScene &scene) {
fixedRotation = true;
if (!body) CreateBody(scene);
body->SetFixedRotation(true);
}
/**
* Set rotation to be free
*/
void PhysicsBehavior::SetFreeRotation(RuntimeScene &scene) {
fixedRotation = false;
if (!body) CreateBody(scene);
body->SetFixedRotation(false);
}
/**
* Consider object as bullet, for better collision handling
*/
void PhysicsBehavior::SetAsBullet(RuntimeScene &scene) {
isBullet = true;
if (!body) CreateBody(scene);
body->SetBullet(true);
}
/**
* Don't consider object as bullet, for faster collision handling
*/
void PhysicsBehavior::DontSetAsBullet(RuntimeScene &scene) {
isBullet = false;
if (!body) CreateBody(scene);
body->SetBullet(false);
}
/**
* Apply an impulse
*/
void PhysicsBehavior::ApplyImpulse(double xCoordinate,
double yCoordinate,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->ApplyLinearImpulse(b2Vec2(xCoordinate, -yCoordinate),
body->GetPosition());
}
/**
* Apply a impulse
*/
void PhysicsBehavior::ApplyImpulseUsingPolarCoordinates(float angle,
float length,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->ApplyLinearImpulse(b2Vec2(cos(angle * b2_pi / 180.0f) * length,
-sin(angle * b2_pi / 180.0f) * length),
body->GetPosition());
}
/**
* Apply a impulse
*/
void PhysicsBehavior::ApplyImpulseTowardPosition(float xPosition,
float yPosition,
float length,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
float angle = atan2(yPosition * runtimeScenesPhysicsDatas->GetInvScaleY() +
body->GetPosition().y,
xPosition * runtimeScenesPhysicsDatas->GetInvScaleX() -
body->GetPosition().x);
body->ApplyLinearImpulse(b2Vec2(cos(angle) * length, -sin(angle) * length),
body->GetPosition());
}
/**
* Apply a force
*/
void PhysicsBehavior::ApplyForce(double xCoordinate,
double yCoordinate,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->ApplyForce(b2Vec2(xCoordinate, -yCoordinate), body->GetPosition());
}
/**
* Apply a force
*/
void PhysicsBehavior::ApplyForceUsingPolarCoordinates(float angle,
float length,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->ApplyForce(b2Vec2(cos(angle * b2_pi / 180.0f) * length,
-sin(angle * b2_pi / 180.0f) * length),
body->GetPosition());
}
/**
* Apply a force
*/
void PhysicsBehavior::ApplyForceTowardPosition(float xPosition,
float yPosition,
float length,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
float angle = atan2(yPosition * runtimeScenesPhysicsDatas->GetInvScaleY() +
body->GetPosition().y,
xPosition * runtimeScenesPhysicsDatas->GetInvScaleX() -
body->GetPosition().x);
body->ApplyForce(b2Vec2(cos(angle) * length, -sin(angle) * length),
body->GetPosition());
}
/**
* Apply a torque
*/
void PhysicsBehavior::ApplyTorque(double torque, RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->ApplyTorque(torque);
}
/**
* Change linear velocity
*/
void PhysicsBehavior::SetLinearVelocity(double xVelocity,
double yVelocity,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetLinearVelocity(b2Vec2(xVelocity, -yVelocity));
}
/**
* Change angular velocity
*/
void PhysicsBehavior::SetAngularVelocity(double angularVelocity,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetAngularVelocity(angularVelocity);
}
/**
* Change linear damping
*/
void PhysicsBehavior::SetLinearDamping(float linearDamping_,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetLinearDamping(linearDamping_);
}
/**
* Change angular damping
*/
void PhysicsBehavior::SetAngularDamping(float angularDamping_,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetAngularDamping(angularDamping_);
}
/**
* Add an hinge between two objects
*/
void PhysicsBehavior::AddRevoluteJointBetweenObjects(
RuntimeObject *object,
RuntimeScene &scene,
float xPosRelativeToMassCenter,
float yPosRelativeToMassCenter) {
if (!body) CreateBody(scene);
if (object == NULL || !object->HasBehaviorNamed(name)) return;
b2Body *otherBody =
static_cast<PhysicsBehavior *>(object->GetBehaviorRawPointer(name))
->GetBox2DBody(scene);
if (body == otherBody) return;
b2RevoluteJointDef jointDef;
jointDef.Initialize(
body,
otherBody,
body->GetWorldCenter() +
b2Vec2(xPosRelativeToMassCenter *
runtimeScenesPhysicsDatas->GetInvScaleX(),
yPosRelativeToMassCenter *
runtimeScenesPhysicsDatas->GetInvScaleY()));
runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef);
}
/**
* Add an hinge to an object
*/
void PhysicsBehavior::AddRevoluteJoint(float xPosition,
float yPosition,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
b2RevoluteJointDef jointDef;
jointDef.Initialize(
body,
runtimeScenesPhysicsDatas->staticBody,
b2Vec2(xPosition * runtimeScenesPhysicsDatas->GetInvScaleX(),
-yPosition * runtimeScenesPhysicsDatas->GetInvScaleY()));
runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef);
}
/**
* Change gravity
*/
void PhysicsBehavior::SetGravity(float xGravity,
float yGravity,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
runtimeScenesPhysicsDatas->world->SetGravity(b2Vec2(xGravity, -yGravity));
}
/**
* Add a gear joint between two objects
*/
void PhysicsBehavior::AddGearJointBetweenObjects(RuntimeObject *object,
float ratio,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
if (object == NULL || !object->HasBehaviorNamed(name)) return;
b2Body *otherBody =
static_cast<PhysicsBehavior *>(object->GetBehaviorRawPointer(name))
->GetBox2DBody(scene);
if (body == otherBody) return;
// Gear joint need a revolute joint to the ground for the two objects
b2RevoluteJointDef jointDef1;
jointDef1.Initialize(
runtimeScenesPhysicsDatas->staticBody, body, body->GetWorldCenter());
b2RevoluteJointDef jointDef2;
jointDef2.Initialize(runtimeScenesPhysicsDatas->staticBody,
otherBody,
otherBody->GetWorldCenter());
b2GearJointDef jointDef;
jointDef.bodyA = body;
jointDef.bodyB = otherBody;
jointDef.joint1 = runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef1);
jointDef.joint2 = runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef2);
jointDef.ratio = ratio * b2_pi;
runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef);
}
void PhysicsBehavior::SetLinearVelocityX(double xVelocity,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetLinearVelocity(b2Vec2(xVelocity, body->GetLinearVelocity().y));
}
void PhysicsBehavior::SetLinearVelocityY(double yVelocity,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
body->SetLinearVelocity(b2Vec2(body->GetLinearVelocity().x, -yVelocity));
}
float PhysicsBehavior::GetLinearVelocityX(RuntimeScene &scene) {
if (!body) CreateBody(scene);
return body->GetLinearVelocity().x;
}
float PhysicsBehavior::GetLinearVelocityY(RuntimeScene &scene) {
if (!body) CreateBody(scene);
return -body->GetLinearVelocity().y;
}
float PhysicsBehavior::GetLinearVelocity(RuntimeScene &scene) {
if (!body) CreateBody(scene);
return sqrt(body->GetLinearVelocity().x * body->GetLinearVelocity().x +
body->GetLinearVelocity().y * body->GetLinearVelocity().y);
}
double PhysicsBehavior::GetAngularVelocity(const RuntimeScene &scene) {
if (!body) CreateBody(scene);
return body->GetAngularVelocity();
}
double PhysicsBehavior::GetLinearDamping(const RuntimeScene &scene) {
if (!body) CreateBody(scene);
return body->GetLinearDamping();
}
double PhysicsBehavior::GetAngularDamping(const RuntimeScene &scene) {
if (!body) CreateBody(scene);
return body->GetAngularDamping();
}
/**
* Test if there is a contact with another object
*/
bool PhysicsBehavior::CollisionWith(
std::map<gd::String, std::vector<RuntimeObject *> *> otherObjectsLists,
RuntimeScene &scene) {
if (!body) CreateBody(scene);
// Getting a list of all objects which are tested
std::vector<RuntimeObject *> objects;
for (std::map<gd::String, std::vector<RuntimeObject *> *>::const_iterator it =
otherObjectsLists.begin();
it != otherObjectsLists.end();
++it) {
if (it->second != NULL) {
objects.reserve(objects.size() + it->second->size());
std::copy(
it->second->begin(), it->second->end(), std::back_inserter(objects));
}
}
// Test if an object of the list is in collision with our object.
std::vector<RuntimeObject *>::const_iterator obj_end = objects.end();
for (std::vector<RuntimeObject *>::iterator obj = objects.begin();
obj != obj_end;
++obj) {
std::set<PhysicsBehavior *>::const_iterator it = currentContacts.begin();
std::set<PhysicsBehavior *>::const_iterator end = currentContacts.end();
for (; it != end; ++it) {
if ((*it)->GetObject() == (*obj)) return true;
}
}
return false;
}
bool PhysicsBehavior::IsStatic() { return !dynamic; }
bool PhysicsBehavior::IsDynamic() { return dynamic; }
void PhysicsBehavior::SetPolygonCoords(const std::vector<sf::Vector2f> &vec) {
polygonCoords = vec;
}
const std::vector<sf::Vector2f> &PhysicsBehavior::GetPolygonCoords() const {
return polygonCoords;
}
bool PhysicsBehavior::HasAutomaticResizing() const { return automaticResizing; }
void PhysicsBehavior::SetAutomaticResizing(bool b) { automaticResizing = b; }
float PhysicsBehavior::GetPolygonScaleX() const {
if (automaticResizing)
return object->GetWidth() / polygonWidth;
else
return polygonScaleX;
}
void PhysicsBehavior::SetPolygonScaleX(float scX, RuntimeScene &scene) {
polygonScaleX = scX;
runtimeScenesPhysicsDatas->world->DestroyBody(body);
CreateBody(scene);
}
float PhysicsBehavior::GetPolygonScaleY() const {
if (automaticResizing)
return object->GetHeight() / polygonHeight;
else
return polygonScaleY;
}
void PhysicsBehavior::SetPolygonScaleY(float scY, RuntimeScene &scene) {
polygonScaleY = scY;
runtimeScenesPhysicsDatas->world->DestroyBody(body);
CreateBody(scene);
}
#if defined(GD_IDE_ONLY)
void PhysicsBehavior::SerializeTo(gd::SerializerElement &element) const {
element.SetAttribute("dynamic", dynamic);
element.SetAttribute("fixedRotation", fixedRotation);
element.SetAttribute("isBullet", isBullet);
element.SetAttribute("massDensity", massDensity);
element.SetAttribute("averageFriction", averageFriction);
element.SetAttribute("linearDamping", linearDamping);
element.SetAttribute("angularDamping", angularDamping);
if (shapeType == Circle)
element.SetAttribute("shapeType", "Circle");
else if (shapeType == CustomPolygon)
element.SetAttribute("shapeType", "CustomPolygon");
else
element.SetAttribute("shapeType", "Box");
if (polygonPositioning == OnOrigin)
element.SetAttribute("positioning", "OnOrigin");
else
element.SetAttribute("positioning", "OnCenter");
element.SetAttribute("autoResizing", automaticResizing);
element.SetAttribute("polygonWidth", polygonWidth);
element.SetAttribute("polygonHeight", polygonHeight);
element.SetAttribute(
void PhysicsBehavior::InitializeContent(
gd::SerializerElement &behaviorContent) {
behaviorContent.SetAttribute("dynamic", true);
behaviorContent.SetAttribute("fixedRotation", false);
behaviorContent.SetAttribute("isBullet", false);
behaviorContent.SetAttribute("massDensity", 1);
behaviorContent.SetAttribute("averageFriction", 0.8);
behaviorContent.SetAttribute("linearDamping", 0.1);
behaviorContent.SetAttribute("angularDamping", 0.1);
behaviorContent.SetAttribute("shapeType", "Box");
behaviorContent.SetAttribute("positioning", "OnCenter");
behaviorContent.SetAttribute("autoResizing", false);
behaviorContent.SetAttribute("polygonWidth", 200);
behaviorContent.SetAttribute("polygonHeight", 200);
std::vector<sf::Vector2f> polygonCoords;
behaviorContent.SetAttribute(
"coordsList",
PhysicsBehavior::GetStringFromCoordsVector(GetPolygonCoords(), '/', ';'));
element.SetAttribute("averageRestitution", averageRestitution);
}
#endif
void PhysicsBehavior::UnserializeFrom(const gd::SerializerElement &element) {
dynamic = element.GetBoolAttribute("dynamic");
fixedRotation = element.GetBoolAttribute("fixedRotation");
isBullet = element.GetBoolAttribute("isBullet");
massDensity = element.GetDoubleAttribute("massDensity");
averageFriction = element.GetDoubleAttribute("averageFriction");
averageRestitution = element.GetDoubleAttribute("averageRestitution");
linearDamping = element.GetDoubleAttribute("linearDamping");
angularDamping = element.GetDoubleAttribute("angularDamping");
gd::String shape = element.GetStringAttribute("shapeType");
if (shape == "Circle")
shapeType = Circle;
else if (shape == "CustomPolygon")
shapeType = CustomPolygon;
else
shapeType = Box;
if (element.GetStringAttribute("positioning", "OnOrigin") == "OnOrigin")
polygonPositioning = OnOrigin;
else
polygonPositioning = OnCenter;
automaticResizing = element.GetBoolAttribute("autoResizing", false);
polygonWidth = element.GetDoubleAttribute("polygonWidth");
polygonHeight = element.GetDoubleAttribute("polygonHeight");
gd::String coordsStr = element.GetStringAttribute("coordsList");
SetPolygonCoords(
PhysicsBehavior::GetCoordsVectorFromString(coordsStr, '/', ';'));
PhysicsBehavior::GetStringFromCoordsVector(polygonCoords, '/', ';'));
behaviorContent.SetAttribute("averageRestitution", 0);
}
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> PhysicsBehavior::GetProperties(
gd::Project &project) const {
const gd::SerializerElement &behaviorContent, gd::Project &project) const {
std::map<gd::String, gd::PropertyDescriptor> properties;
gd::String shapeTypeStr = _("Box (rectangle)");
if (shapeType == Box)
shapeTypeStr = _("Box (rectangle)");
else if (shapeType == Circle)
gd::String shape = behaviorContent.GetStringAttribute("shapeType");
if (shape == "Circle")
shapeTypeStr = _("Circle");
else if (shapeType == CustomPolygon)
else if (shape == "CustomPolygon")
shapeTypeStr = _("Custom polygon");
else
shapeTypeStr = _("Box (rectangle)");
properties[_("Shape")]
.SetValue(shapeTypeStr)
@@ -743,61 +62,67 @@ std::map<gd::String, gd::PropertyDescriptor> PhysicsBehavior::GetProperties(
.AddExtraInfo(_("Circle"));
properties[_("Dynamic object")]
.SetValue(dynamic ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("dynamic") ? "true" : "false")
.SetType("Boolean");
properties[_("Fixed rotation")]
.SetValue(fixedRotation ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("fixedRotation") ? "true"
: "false")
.SetType("Boolean");
properties[_("Consider as bullet (better collision handling)")]
.SetValue(isBullet ? "true" : "false")
.SetValue(behaviorContent.GetBoolAttribute("isBullet") ? "true" : "false")
.SetType("Boolean");
properties[_("Mass density")].SetValue(gd::String::From(massDensity));
properties[_("Friction")].SetValue(gd::String::From(averageFriction));
properties[_("Restitution (elasticity)")].SetValue(
gd::String::From(averageRestitution));
properties[_("Linear Damping")].SetValue(gd::String::From(linearDamping));
properties[_("Angular Damping")].SetValue(gd::String::From(angularDamping));
properties[_("Mass density")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("massDensity")));
properties[_("Friction")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("averageFriction")));
properties[_("Restitution (elasticity)")].SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("averageRestitution")));
properties[_("Linear Damping")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("linearDamping")));
properties[_("Angular Damping")].SetValue(
gd::String::From(behaviorContent.GetDoubleAttribute("angularDamping")));
properties[_("PLEASE_ALSO_SHOW_EDIT_BUTTON_THANKS")].SetValue("");
return properties;
}
bool PhysicsBehavior::UpdateProperty(const gd::String &name,
bool PhysicsBehavior::UpdateProperty(gd::SerializerElement &behaviorContent,
const gd::String &name,
const gd::String &value,
gd::Project &project) {
if (name == _("Shape")) {
if (value == _("Box (rectangle)"))
shapeType = Box;
behaviorContent.SetAttribute("shapeType", "Box");
else if (value == _("Circle"))
shapeType = Circle;
behaviorContent.SetAttribute("shapeType", "Circle");
else if (value == _("Custom polygon"))
shapeType = CustomPolygon;
behaviorContent.SetAttribute("shapeType", "CustomPolygon");
}
if (name == _("Dynamic object")) {
dynamic = (value != "0");
behaviorContent.SetAttribute("dynamic", (value != "0"));
}
if (name == _("Fixed rotation")) {
fixedRotation = (value != "0");
behaviorContent.SetAttribute("fixedRotation", (value != "0"));
}
if (name == _("Consider as bullet (better collision handling)")) {
isBullet = (value != "0");
behaviorContent.SetAttribute("isBullet", (value != "0"));
}
if (name == _("Mass density")) {
massDensity = value.To<float>();
behaviorContent.SetAttribute("massDensity", value.To<float>());
}
if (name == _("Friction")) {
averageFriction = value.To<float>();
behaviorContent.SetAttribute("averageFriction", value.To<float>());
}
if (name == _("Restitution (elasticity)")) {
averageRestitution = value.To<float>();
behaviorContent.SetAttribute("averageRestitution", value.To<float>());
}
if (name == _("Linear Damping")) {
if (value.To<float>() < 0) return false;
linearDamping = value.To<float>();
behaviorContent.SetAttribute("linearDamping", value.To<float>());
}
if (name == _("Angular Damping")) {
if (value.To<float>() < 0) return false;
angularDamping = value.To<float>();
behaviorContent.SetAttribute("angularDamping", value.To<float>());
}
return true;
@@ -819,22 +144,3 @@ gd::String PhysicsBehavior::GetStringFromCoordsVector(
return coordsStr;
}
std::vector<sf::Vector2f> PhysicsBehavior::GetCoordsVectorFromString(
const gd::String &str, char32_t coordsSep, char32_t composantSep) {
std::vector<sf::Vector2f> coordsVec;
std::vector<gd::String> coordsDecomposed = str.Split(coordsSep);
for (std::size_t a = 0; a < coordsDecomposed.size(); a++) {
std::vector<gd::String> coordXY =
coordsDecomposed.at(a).Split(composantSep);
if (coordXY.size() != 2) continue;
sf::Vector2f newCoord(coordXY.at(0).To<float>(), coordXY.at(1).To<float>());
coordsVec.push_back(newCoord);
}
return coordsVec;
}

Some files were not shown because too many files have changed in this diff Show More