Compare commits

...

15 Commits

Author SHA1 Message Date
Clément Pasteau
428fb7cf3d Last fixes 2025-09-19 16:48:51 +02:00
Clément Pasteau
946b6eadb3 Move & rename stuff 2025-09-19 10:02:59 +02:00
Clément Pasteau
2c8523c88d Fix car & character sync + pathfinding sync 2025-09-19 10:02:59 +02:00
Clément Pasteau
b81cbbac81 Sync tilemaps for load/save 2025-09-19 10:02:59 +02:00
Clément Pasteau
bb41026db6 Fix typing and imports 2025-09-19 10:02:59 +02:00
Clément Pasteau
e94567fdb5 Fix variable usage 2025-09-19 10:02:59 +02:00
Clément Pasteau
e08491f5f1 Fixes and stuff removed 2025-09-19 10:02:59 +02:00
Clément Pasteau
e24de3c164 Remove unused example 2025-09-19 10:02:59 +02:00
Clément Pasteau
d550e36294 More fixes 2025-09-19 10:02:59 +02:00
Clément Pasteau
f5dfca9811 Sync more for custom objects 2025-09-19 10:02:59 +02:00
Clément Pasteau
6993a05bd4 Move stuff around and fix rehydrating the save variable 2025-09-19 10:02:59 +02:00
Clément Pasteau
a44891bedc Reorder stuff so it's more easily understandable 2025-09-19 10:02:59 +02:00
Clément Pasteau
14de91a871 Sync async tasks and other fixes 2025-09-19 10:02:58 +02:00
Clément Pasteau
3918896542 Sync tweens, layers, triggers, fix sounds and improve namings 2025-09-19 10:02:58 +02:00
Clément Pasteau
5156431ee1 Save & load actions 2025-09-19 10:02:58 +02:00
91 changed files with 24195 additions and 1696 deletions

View File

@@ -42,15 +42,19 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t relationalOperatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
std::size_t relationalOperatorIndex =
instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "relationalOperator") {
if (instrInfos.parameters.GetParameter(i).GetType() ==
"relationalOperator") {
relationalOperatorIndex = i;
}
}
// Ensure that there is at least one parameter after the relational operator
if (relationalOperatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
if (relationalOperatorIndex + 1 >=
instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -87,20 +91,23 @@ gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) {
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " +
rhs;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(
const gd::String& operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" &&
operatorString != "!=" && operatorString != "startsWith" &&
operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
/**
@@ -124,7 +131,8 @@ gd::String EventsCodeGenerator::GenerateOperatorCall(
const gd::String& getterStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -195,7 +203,8 @@ gd::String EventsCodeGenerator::GenerateCompoundOperatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -248,7 +257,8 @@ gd::String EventsCodeGenerator::GenerateMutatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -323,34 +333,42 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
// Insert code only parameters and be sure there is no lack of parameter.
while (condition.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (condition.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = condition.GetParameters();
parameters.push_back(gd::Expression(""));
condition.SetParameters(parameters);
}
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
@@ -366,44 +384,46 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Set up the context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = condition.GetParameter(0).GetPlainString();
const gd::String &behaviorName =
const gd::String& objectName = condition.GetParameter(0).GetPlainString();
const gd::String& behaviorName =
condition.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -415,15 +435,14 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
// Prepare arguments and generate the whole condition code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateBehaviorCondition(
realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
conditionCode += GenerateBehaviorCondition(realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
}
@@ -493,26 +512,29 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
}
bool EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos) {
const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos) {
bool isAnyBehaviorMissing = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), instrInfos.parameters,
[this, &isAnyBehaviorMissing,
&instrInfos](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
instrInfos.parameters,
[this, &isAnyBehaviorMissing, &instrInfos](
const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex) {
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
const gd::String &behaviorName = parameterValue.GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& behaviorName = parameterValue.GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehaviorInObjectOrGroup(
lastObjectName, behaviorName);
const gd::String &expectedBehaviorType =
const gd::String& expectedBehaviorType =
parameterMetadata.GetExtraInfo();
if (!expectedBehaviorType.empty() &&
actualBehaviorType != expectedBehaviorType) {
const auto &objectParameterMetadata =
const auto& objectParameterMetadata =
instrInfos.GetParameter(lastObjectIndex);
// Event functions crash if some objects in a group are missing
// the required behaviors, since they lose reference to the original
@@ -523,10 +545,12 @@ bool EventsCodeGenerator::CheckBehaviorParameters(
isAnyBehaviorMissing = true;
}
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
actualBehaviorType, expectedBehaviorType, lastObjectName);
if (diagnosticReport)
diagnosticReport->Add(projectDiagnostic);
gd::ProjectDiagnostic::ErrorType::MissingBehavior,
"",
actualBehaviorType,
expectedBehaviorType,
lastObjectName);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
}
}
});
@@ -539,7 +563,8 @@ bool EventsCodeGenerator::CheckBehaviorParameters(
gd::String EventsCodeGenerator::GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
gd::String actionCode;
const gd::InstructionMetadata& instrInfos =
@@ -564,33 +589,41 @@ gd::String EventsCodeGenerator::GenerateActionCode(
: instrInfos.codeExtraInformation.functionCallName;
// Be sure there is no lack of parameter.
while (action.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (action.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = action.GetParameters();
parameters.push_back(gd::Expression(""));
action.SetParameters(parameters);
}
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
@@ -608,43 +641,46 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = action.GetParameter(0).GetPlainString();
const gd::String &behaviorName = action.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& objectName = action.GetParameter(0).GetPlainString();
const gd::String& behaviorName = action.GetParameter(1).GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
AddIncludeFiles(autoInfo.includeFiles);
@@ -656,15 +692,15 @@ gd::String EventsCodeGenerator::GenerateActionCode(
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode +=
GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
actionCode += GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
}
@@ -676,7 +712,8 @@ gd::String EventsCodeGenerator::GenerateActionCode(
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
optionalAsyncCallbackName,
optionalAsyncCallbackId);
}
return actionCode;
@@ -689,8 +726,8 @@ gd::String EventsCodeGenerator::GenerateLocalVariablesStackAccessor() {
}
gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
const gd::Expression &variableExpression,
EventsCodeGenerationContext &context) {
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context) {
const auto variableName = gd::ExpressionVariableNameFinder::GetVariableName(
*variableExpression.GetRootNode());
@@ -701,8 +738,12 @@ gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
: "scenevar";
return gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, variableParameterType,
variableExpression.GetPlainString(), "", "AllowUndeclaredVariable");
*this,
context,
variableParameterType,
variableExpression.GetPlainString(),
"",
"AllowUndeclaredVariable");
}
const EventsCodeGenerator::CallbackDescriptor
@@ -749,6 +790,11 @@ EventsCodeGenerator::GenerateCallback(
AddCustomCodeOutsideMain(callbackCode);
const gd::String idToCallbackMapUpdate = GetCodeNamespaceAccessor() +
"idToCallbackMap.set(" + callbackID +
", " + callbackFunctionName + ");\n";
AddCustomCodeOutsideMain(idToCallbackMapUpdate);
std::set<gd::String> requiredObjects;
// Build the list of all objects required by the callback. Any object that has
// already been declared could have gone through previous object picking, so
@@ -808,13 +854,28 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (ParameterMetadata::IsExpression("number", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "number", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"number",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("string", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "string", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"string",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("variable", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, metadata.GetType(), parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
metadata.GetType(),
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsObject(metadata.GetType())) {
// It would be possible to run a gd::ExpressionCodeGenerator if later
// objects can have nested objects, or function returning objects.
@@ -849,7 +910,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
metadata.GetType() == "atlasResource" ||
metadata.GetType() == "spineResource" ||
// Deprecated, old parameter names:
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
metadata.GetType() == "password" ||
metadata.GetType() == "musicfile" ||
metadata.GetType() == "soundfile") {
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.GetType() == "mouse") {
@@ -999,7 +1061,8 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
for (std::size_t eId = 0; eId < events.size(); ++eId) {
auto& event = events[eId];
if (event.HasVariables()) {
GetProjectScopedContainers().GetVariablesContainersList().Push(event.GetVariables());
GetProjectScopedContainers().GetVariablesContainersList().Push(
event.GetVariables());
}
// Each event has its own context : Objects picked in an event are totally
@@ -1124,7 +1187,7 @@ gd::String EventsCodeGenerator::GenerateFreeCondition(
instrInfos.codeExtraInformation.functionCallName);
} else {
predicate = instrInfos.codeExtraInformation.functionCallName + "(" +
GenerateArgumentsList(arguments, 0) + ")";
GenerateArgumentsList(arguments, 0) + ")";
}
// Add logical not if needed
@@ -1168,7 +1231,7 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
instrInfos, arguments, objectFunctionCallNamePart, 1);
} else {
predicate = objectFunctionCallNamePart + "(" +
GenerateArgumentsList(arguments, 1) + ")";
GenerateArgumentsList(arguments, 1) + ")";
}
if (conditionInverted) predicate = GenerateNegatedPredicate(predicate);
@@ -1200,18 +1263,20 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
}
gd::String EventsCodeGenerator::GenerateFreeAction(
const gd::String& functionCallName,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Generate call
gd::String call;
if (instrInfos.codeExtraInformation.type == "number" ||
instrInfos.codeExtraInformation.type == "string" ||
// Boolean actions declared with addExpressionAndConditionAndAction uses
// MutatorAndOrAccessor even though they don't declare an operator parameter.
// Boolean operators are only used with SetMutators or SetCustomCodeGenerator.
// MutatorAndOrAccessor even though they don't declare an operator
// parameter. Boolean operators are only used with SetMutators or
// SetCustomCodeGenerator.
(instrInfos.codeExtraInformation.type == "boolean" &&
instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::AccessType::Mutators)) {
@@ -1224,23 +1289,19 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
instrInfos.codeExtraInformation.optionalAssociatedInstruction);
else if (instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::Mutators)
call =
GenerateMutatorCall(instrInfos,
arguments,
functionCallName);
call = GenerateMutatorCall(instrInfos, arguments, functionCallName);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName);
call =
GenerateCompoundOperatorCall(instrInfos, arguments, functionCallName);
} else {
call = functionCallName + "(" +
GenerateArgumentsList(arguments) + ")";
call = functionCallName + "(" + GenerateArgumentsList(arguments) + ")";
}
if (!optionalAsyncCallbackName.empty())
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
call = "runtimeScene.getAsyncTasksManager().addTask(" + call + ", " +
optionalAsyncCallbackName + ")";
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
", asyncObjectsList)";
}
return call + ";\n";
}
@@ -1252,7 +1313,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1293,7 +1355,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1308,17 +1371,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
2);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName,
2);
instrInfos, arguments, functionCallName, 2);
return "For each picked object \"" + objectName + "\", call " + call +
" for behavior \"" + behaviorName + "\".\n";
} else {
gd::String argumentsStr = GenerateArgumentsList(arguments, 2);
call = functionCallName + "(" +
argumentsStr + ")";
call = functionCallName + "(" + argumentsStr + ")";
return "For each picked object \"" + objectName + "\", call " + call + "(" +
argumentsStr + ")" + " for behavior \"" + behaviorName + "\"" +
@@ -1373,42 +1432,47 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property) {
return "getProperty" + property.GetName() + "()";
}
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode) {
return "setProperty" + property.GetName() + "(" + operandCode + ")";
}
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata &parameter) {
return "getParameter" + parameter.GetName() + "()";
}
const gd::ParameterMetadata& parameter) {
return "getParameter" + parameter.GetName() + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
projectScopedContainers(
gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project_,
layout)),
hasProjectAndLayout(true),
project(&project_),
scene(&layout),
@@ -1417,7 +1481,7 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
@@ -1432,6 +1496,6 @@ EventsCodeGenerator::EventsCodeGenerator(
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
} // namespace gd

View File

@@ -9,9 +9,9 @@
#include <utility>
#include <vector>
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
@@ -62,7 +62,7 @@ class GD_CORE_API EventsCodeGenerator {
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
virtual ~EventsCodeGenerator() {};
/**
* \brief Preprocess an events list (replacing for example links with the
@@ -160,7 +160,8 @@ class GD_CORE_API EventsCodeGenerator {
gd::String GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
struct CallbackDescriptor {
CallbackDescriptor(const gd::String functionName_,
@@ -168,7 +169,7 @@ class GD_CORE_API EventsCodeGenerator {
const std::set<gd::String> requiredObjects_)
: functionName(functionName_),
argumentsList(argumentsList_),
requiredObjects(requiredObjects_){};
requiredObjects(requiredObjects_) {};
/**
* The name by which the function can be invoked.
*/
@@ -338,9 +339,9 @@ class GD_CORE_API EventsCodeGenerator {
}
/**
* @brief Give access to the project scoped containers as code generation might
* push and pop variable containers (for local variables).
* This could be passed as a parameter recursively in code generation, but this requires
* @brief Give access to the project scoped containers as code generation
* might push and pop variable containers (for local variables). This could be
* passed as a parameter recursively in code generation, but this requires
* heavy refactoring. Instead, we use this single instance.
*/
gd::ProjectScopedContainers& GetProjectScopedContainers() {
@@ -387,9 +388,7 @@ class GD_CORE_API EventsCodeGenerator {
diagnosticReport = diagnosticReport_;
}
gd::DiagnosticReport* GetDiagnosticReport() {
return diagnosticReport;
}
gd::DiagnosticReport* GetDiagnosticReport() { return diagnosticReport; }
/**
* \brief Generate the full name for accessing to a boolean variable used for
@@ -513,16 +512,16 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Generate an any variable getter that fallbacks on scene variable for
* compatibility reason.
*/
gd::String
GenerateAnyOrSceneVariableGetter(const gd::Expression &variableExpression,
EventsCodeGenerationContext &context);
gd::String GenerateAnyOrSceneVariableGetter(
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode);
protected:
protected:
virtual const gd::String GenerateRelationalOperatorCodes(
const gd::String& operatorString);
@@ -643,16 +642,16 @@ protected:
gd::EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String
GenerateParameterGetterWithoutCasting(const gd::ParameterMetadata &parameter);
virtual gd::String GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata& parameter);
/**
* \brief Generate the code to reference an object which is
@@ -769,7 +768,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateObjectAction(
const gd::String& objectName,
@@ -778,7 +778,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateBehaviorAction(
const gd::String& objectName,
@@ -788,7 +789,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
gd::String GenerateRelationalOperatorCall(
const gd::InstructionMetadata& instrInfos,
@@ -837,9 +839,8 @@ protected:
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
bool CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos);
bool CheckBehaviorParameters(const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos);
const gd::Platform& platform; ///< The platform being used.
@@ -876,4 +877,3 @@ protected:
};
} // namespace gd

View File

@@ -110,9 +110,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Object3DNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Object3DNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
@@ -123,8 +125,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: Object3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)

View File

@@ -452,9 +452,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Cube3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Cube3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
fo: this._facesOrientation,
bfu: this._backFaceUpThroughWhichAxisRotation,
@@ -466,9 +468,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: Cube3DObjectNetworkSyncData
networkSyncData: Cube3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;

View File

@@ -1,12 +1,16 @@
namespace gdjs {
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
type CustomObject3DNetworkSyncDataType = {
z: float;
d: float;
rx: float;
ry: float;
ifz: boolean;
ccz: float;
};
type CustomObject3DNetworkSyncData = CustomObjectNetworkSyncData &
CustomObject3DNetworkSyncDataType;
/**
* Base class for 3D custom objects.
*/
@@ -85,21 +89,25 @@ namespace gdjs {
}
}
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): CustomObject3DNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
ifz: this.isFlippedZ(),
ccz: this._customCenterZ,
};
}
updateFromNetworkSyncData(
networkSyncData: CustomObject3DNetworkSyncDataType
networkSyncData: CustomObject3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
@@ -107,6 +115,8 @@ namespace gdjs {
if (networkSyncData.ry !== undefined)
this.setRotationY(networkSyncData.ry);
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
if (networkSyncData.ccz !== undefined)
this._customCenterZ = networkSyncData.ccz;
}
/**

View File

@@ -216,9 +216,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Model3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Model3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
op: this._originPoint,
cp: this._centerPoint,
@@ -231,9 +233,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: Model3DObjectNetworkSyncData
networkSyncData: Model3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;

View File

@@ -145,9 +145,11 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): BBTextObjectNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BBTextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
text: this._text,
o: this._opacity,
c: this._color,
@@ -162,9 +164,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: BBTextObjectNetworkSyncData
networkSyncData: BBTextObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (this._text !== undefined) {
this.setBBText(networkSyncData.text);
}

View File

@@ -155,9 +155,11 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): BitmapTextObjectNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BitmapTextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
text: this._text,
opa: this._opacity,
tint: this._tint,
@@ -172,9 +174,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: BitmapTextObjectNetworkSyncData
networkSyncData: BitmapTextObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (this._text !== undefined) {
this.setText(networkSyncData.text);
}

View File

@@ -87,16 +87,21 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): LightNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): LightNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
rad: this.getRadius(),
col: this.getColor(),
};
}
updateFromNetworkSyncData(networkSyncData: LightNetworkSyncData): void {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: LightNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.rad !== undefined) {
this.setRadius(networkSyncData.rad);

View File

@@ -729,7 +729,9 @@ namespace gdjs {
behavior.playerNumber = ownerPlayerNumber;
}
instance.updateFromNetworkSyncData(messageData);
instance.updateFromNetworkSyncData(messageData, {
clearInputs: false,
});
setLastClockReceivedForInstanceOnScene({
sceneNetworkId,
@@ -1737,7 +1739,7 @@ namespace gdjs {
return;
}
runtimeScene.updateFromNetworkSyncData(messageData);
runtimeScene.updateFromNetworkSyncData(messageData, {});
} else {
// If the game is not ready to receive game update messages, we need to save the data for later use.
// This can happen when joining a game that is already running.
@@ -1890,7 +1892,7 @@ namespace gdjs {
const messageData = message.getData();
const messageSender = message.getSender();
if (gdjs.multiplayer.isReadyToSendOrReceiveGameUpdateMessages()) {
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
} else {
// If the game is not ready to receive game update messages, we need to save the data for later use.
// This can happen when joining a game that is already running.
@@ -1918,7 +1920,7 @@ namespace gdjs {
// Reapply the game saved updates.
lastReceivedGameSyncDataUpdates.getUpdates().forEach((messageData) => {
debugLogger.info(`Reapplying saved update of game.`);
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
});
// Game updates are always applied properly, so we can clear them.
lastReceivedGameSyncDataUpdates.clear();
@@ -1937,7 +1939,7 @@ namespace gdjs {
debugLogger.info(`Reapplying saved update of scene ${sceneNetworkId}.`);
runtimeScene.updateFromNetworkSyncData(messageData);
runtimeScene.updateFromNetworkSyncData(messageData, {});
// We only remove the message if it was successfully applied, so it can be reapplied later,
// in case we were not on the right scene.
lastReceivedSceneSyncDataUpdates.remove(messageData);

View File

@@ -278,7 +278,7 @@ namespace gdjs {
const instanceNetworkId = this._getOrCreateInstanceNetworkId();
const objectName = this.owner.getName();
const objectNetworkSyncData = this.owner.getNetworkSyncData();
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
// this._logToConsoleWithThrottle(
// `Synchronizing object ${this.owner.getName()} (instance ${
@@ -448,7 +448,7 @@ namespace gdjs {
objectOwner: this.playerNumber,
objectName,
instanceNetworkId,
objectNetworkSyncData: this.owner.getNetworkSyncData(),
objectNetworkSyncData: this.owner.getNetworkSyncData({}),
sceneNetworkId,
});
this._sendDataToPeersWithIncreasedClock(
@@ -598,7 +598,7 @@ namespace gdjs {
debugLogger.info(
'Sending update message to move the object immediately.'
);
const objectNetworkSyncData = this.owner.getNetworkSyncData();
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
const {
messageName: updateMessageName,
messageData: updateMessageData,

View File

@@ -119,18 +119,21 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): PanelSpriteNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PanelSpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
op: this.getOpacity(),
color: this.getColor(),
};
}
updateFromNetworkSyncData(
networkSyncData: PanelSpriteNetworkSyncData
networkSyncData: PanelSpriteNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
// Texture is not synchronized, see if this is asked or not.

View File

@@ -370,9 +370,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): ParticleEmitterObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): ParticleEmitterObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
prms: this.particleRotationMinSpeed,
prmx: this.particleRotationMaxSpeed,
mpc: this.maxParticlesCount,
@@ -399,9 +401,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
syncData: ParticleEmitterObjectNetworkSyncData
syncData: ParticleEmitterObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData);
super.updateFromNetworkSyncData(syncData, options);
if (syncData.x !== undefined) {
this.setX(syncData.x);
}

View File

@@ -6,7 +6,7 @@ namespace gdjs {
const logger = new gdjs.Logger('Pathfinding behavior');
interface PathfindingNetworkSyncDataType {
// Syncing the path should be enough to have a good prediction.
// Syncing the path and its position on it should be enough to have a good prediction.
path: FloatPoint[];
pf: boolean;
sp: number;
@@ -15,6 +15,7 @@ namespace gdjs {
tss: number;
re: boolean;
ma: number;
dos: number;
}
export interface PathfindingNetworkSyncData extends BehaviorNetworkSyncData {
@@ -133,9 +134,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): PathfindingNetworkSyncData {
getNetworkSyncData(
options: GetNetworkSyncDataOptions
): PathfindingNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
path: this._path,
pf: this._pathFound,
@@ -145,14 +148,16 @@ namespace gdjs {
tss: this._totalSegmentDistance,
re: this._reachedEnd,
ma: this._movementAngle,
dos: this._distanceOnSegment,
},
};
}
updateFromNetworkSyncData(
networkSyncData: PathfindingNetworkSyncData
networkSyncData: PathfindingNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.path !== undefined) {
this._path = behaviorSpecificProps.path;
@@ -181,6 +186,9 @@ namespace gdjs {
if (behaviorSpecificProps.ma !== undefined) {
this._movementAngle = behaviorSpecificProps.ma;
}
if (behaviorSpecificProps.dos !== undefined) {
this._distanceOnSegment = behaviorSpecificProps.dos;
}
}
setCellWidth(width: float): void {

View File

@@ -499,7 +499,9 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Physics2NetworkSyncData {
getNetworkSyncData(
options: GetNetworkSyncDataOptions
): Physics2NetworkSyncData {
const bodyProps = this._body
? {
tpx: this._body.GetTransform().get_p().get_x(),
@@ -520,7 +522,7 @@ namespace gdjs {
aw: undefined,
};
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
...bodyProps,
layers: this.layers,
@@ -529,45 +531,13 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Physics2NetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: Physics2NetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (
behaviorSpecificProps.tpx !== undefined &&
behaviorSpecificProps.tpy !== undefined &&
behaviorSpecificProps.tqa !== undefined
) {
if (this._body) {
this._body.SetTransform(
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
behaviorSpecificProps.tqa
);
}
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined
) {
if (this._body) {
this._body.SetLinearVelocity(
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
);
}
}
if (behaviorSpecificProps.av !== undefined) {
if (this._body) {
this._body.SetAngularVelocity(behaviorSpecificProps.av);
}
}
if (behaviorSpecificProps.aw !== undefined) {
if (this._body) {
this._body.SetAwake(behaviorSpecificProps.aw);
}
}
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
@@ -576,6 +546,38 @@ namespace gdjs {
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
}
this.updateBodyFromObject();
if (!this._body) return;
if (
behaviorSpecificProps.tpx !== undefined &&
behaviorSpecificProps.tpy !== undefined &&
behaviorSpecificProps.tqa !== undefined
) {
this._body.SetTransform(
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
behaviorSpecificProps.tqa
);
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined
) {
this._body.SetLinearVelocity(
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
);
}
if (behaviorSpecificProps.av !== undefined) {
this._body.SetAngularVelocity(behaviorSpecificProps.av);
}
if (behaviorSpecificProps.aw !== undefined) {
this._body.SetAwake(behaviorSpecificProps.aw);
}
}
onDeActivate() {

View File

@@ -495,7 +495,9 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): Physics3DNetworkSyncData {
override getNetworkSyncData(
options: GetNetworkSyncDataOptions
): Physics3DNetworkSyncData {
let bodyProps;
if (this._body) {
const position = this._body.GetPosition();
@@ -537,7 +539,7 @@ namespace gdjs {
};
}
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
...bodyProps,
layers: this.layers,
@@ -547,27 +549,40 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: Physics3DNetworkSyncData
networkSyncData: Physics3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
}
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
}
this._needToRecreateShape = true;
this._needToRecreateBody = true;
this.updateBodyFromObject();
if (!this._body) return;
if (
behaviorSpecificProps.px !== undefined &&
behaviorSpecificProps.py !== undefined &&
behaviorSpecificProps.pz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetPosition(
this._body.GetID(),
this.getRVec3(
behaviorSpecificProps.px,
behaviorSpecificProps.py,
behaviorSpecificProps.pz
),
Jolt.EActivation_DontActivate
);
}
this._sharedData.bodyInterface.SetPosition(
this._body.GetID(),
this.getRVec3(
behaviorSpecificProps.px,
behaviorSpecificProps.py,
behaviorSpecificProps.pz
),
Jolt.EActivation_DontActivate
);
}
if (
behaviorSpecificProps.rx !== undefined &&
@@ -575,56 +590,44 @@ namespace gdjs {
behaviorSpecificProps.rz !== undefined &&
behaviorSpecificProps.rw !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetRotation(
this._body.GetID(),
this.getQuat(
behaviorSpecificProps.rx,
behaviorSpecificProps.ry,
behaviorSpecificProps.rz,
behaviorSpecificProps.rw
),
Jolt.EActivation_DontActivate
);
}
this._sharedData.bodyInterface.SetRotation(
this._body.GetID(),
this.getQuat(
behaviorSpecificProps.rx,
behaviorSpecificProps.ry,
behaviorSpecificProps.rz,
behaviorSpecificProps.rw
),
Jolt.EActivation_DontActivate
);
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined &&
behaviorSpecificProps.lvz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetLinearVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.lvx,
behaviorSpecificProps.lvy,
behaviorSpecificProps.lvz
)
);
}
this._sharedData.bodyInterface.SetLinearVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.lvx,
behaviorSpecificProps.lvy,
behaviorSpecificProps.lvz
)
);
}
if (
behaviorSpecificProps.avx !== undefined &&
behaviorSpecificProps.avy !== undefined &&
behaviorSpecificProps.avz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetAngularVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.avx,
behaviorSpecificProps.avy,
behaviorSpecificProps.avz
)
);
}
}
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
}
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
this._sharedData.bodyInterface.SetAngularVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.avx,
behaviorSpecificProps.avy,
behaviorSpecificProps.avz
)
);
}
}
@@ -921,31 +924,58 @@ namespace gdjs {
this.updateBodyFromObject();
}
recreateBody() {
if (!this._body) {
this._createBody();
return;
}
recreateBody(previousBodyData?: {
linearVelocityX: float;
linearVelocityY: float;
linearVelocityZ: float;
angularVelocityX: float;
angularVelocityY: float;
angularVelocityZ: float;
}) {
const bodyInterface = this._sharedData.bodyInterface;
const linearVelocity = this._body.GetLinearVelocity();
const linearVelocityX = linearVelocity.GetX();
const linearVelocityY = linearVelocity.GetY();
const linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = this._body.GetAngularVelocity();
const angularVelocityX = angularVelocity.GetX();
const angularVelocityY = angularVelocity.GetY();
const angularVelocityZ = angularVelocity.GetZ();
const linearVelocityX = previousBodyData
? previousBodyData.linearVelocityX
: this._body
? this._body.GetLinearVelocity().GetX()
: 0;
const linearVelocityY = previousBodyData
? previousBodyData.linearVelocityY
: this._body
? this._body.GetLinearVelocity().GetY()
: 0;
const linearVelocityZ = previousBodyData
? previousBodyData.linearVelocityZ
: this._body
? this._body.GetLinearVelocity().GetZ()
: 0;
const angularVelocityX = previousBodyData
? previousBodyData.angularVelocityX
: this._body
? this._body.GetAngularVelocity().GetX()
: 0;
const angularVelocityY = previousBodyData
? previousBodyData.angularVelocityY
: this._body
? this._body.GetAngularVelocity().GetY()
: 0;
const angularVelocityZ = previousBodyData
? previousBodyData.angularVelocityZ
: this._body
? this._body.GetAngularVelocity().GetZ()
: 0;
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
if (this._body) {
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
}
this._createBody();
if (!this._body) {
return;
}
const bodyID = this._body.GetID();
bodyInterface.SetLinearVelocity(
bodyID,

View File

@@ -12,6 +12,7 @@ namespace gdjs {
etm: float;
esm: float;
ei: float;
es: float;
}
export interface PhysicsCar3DNetworkSyncData extends BehaviorNetworkSyncData {
@@ -96,7 +97,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
private _dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -168,12 +169,37 @@ namespace gdjs {
this._isHookedToPhysicsStep = true;
}
// Destroy the body before switching the bodyUpdater,
// to ensure the body of the previous bodyUpdater is not left alive.
// (would be a memory leak and would create a phantom body in the physics world)
// But transfer the linear and angular velocity to the new body,
// so the body doesn't stop when it is recreated.
let previousBodyData = {
linearVelocityX: 0,
linearVelocityY: 0,
linearVelocityZ: 0,
angularVelocityX: 0,
angularVelocityY: 0,
angularVelocityZ: 0,
};
if (behavior._body) {
const linearVelocity = behavior._body.GetLinearVelocity();
previousBodyData.linearVelocityX = linearVelocity.GetX();
previousBodyData.linearVelocityY = linearVelocity.GetY();
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = behavior._body.GetAngularVelocity();
previousBodyData.angularVelocityX = angularVelocity.GetX();
previousBodyData.angularVelocityY = angularVelocity.GetY();
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
behavior.bodyUpdater.destroyBody();
}
behavior.bodyUpdater =
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
this,
behavior.bodyUpdater
);
behavior.recreateBody();
behavior.recreateBody(previousBodyData);
return this._physics3D;
}
@@ -273,13 +299,15 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): PhysicsCar3DNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PhysicsCar3DNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
props: {
lek: this._wasLeftKeyPressed,
rik: this._wasRightKeyPressed,
@@ -291,14 +319,16 @@ namespace gdjs {
etm: this._engineTorqueMax,
esm: this._engineSpeedMax,
ei: this._engineInertia,
es: this.getEngineSpeed(),
},
};
}
override updateFromNetworkSyncData(
networkSyncData: PhysicsCar3DNetworkSyncData
networkSyncData: PhysicsCar3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
this._hasPressedForwardKey = behaviorSpecificProps.upk;
@@ -311,9 +341,15 @@ namespace gdjs {
this._engineTorqueMax = behaviorSpecificProps.etm;
this._engineSpeedMax = behaviorSpecificProps.esm;
this._engineInertia = behaviorSpecificProps.ei;
if (this._vehicleController) {
this._vehicleController
.GetEngine()
.SetCurrentRPM(behaviorSpecificProps.es);
}
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// When the object is synchronized from the network, the inputs must not be cleared,
// except if asked specifically.
this._clearInputsBetweenFrames = !!options.clearInputs;
}
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
@@ -490,7 +526,7 @@ namespace gdjs {
this._previousAcceleratorStickForce = this._acceleratorStickForce;
this._previousSteeringStickForce = this._steeringStickForce;
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
this._hasPressedForwardKey = false;
this._hasPressedBackwardKey = false;
this._hasPressedRightKey = false;

View File

@@ -102,7 +102,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
private _dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
/**
* A very small value compare to 1 pixel, yet very huge compare to rounding errors.
@@ -207,10 +207,35 @@ namespace gdjs {
this._isHookedToPhysicsStep = true;
}
// Destroy the body before switching the bodyUpdater,
// to ensure the body of the previous bodyUpdater is not left alive.
// (would be a memory leak and would create a phantom body in the physics world)
// But transfer the linear and angular velocity to the new body,
// so the body doesn't stop when it is recreated.
let previousBodyData = {
linearVelocityX: 0,
linearVelocityY: 0,
linearVelocityZ: 0,
angularVelocityX: 0,
angularVelocityY: 0,
angularVelocityZ: 0,
};
if (behavior._body) {
const linearVelocity = behavior._body.GetLinearVelocity();
previousBodyData.linearVelocityX = linearVelocity.GetX();
previousBodyData.linearVelocityY = linearVelocity.GetY();
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = behavior._body.GetAngularVelocity();
previousBodyData.angularVelocityX = angularVelocity.GetX();
previousBodyData.angularVelocityY = angularVelocity.GetY();
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
behavior.bodyUpdater.destroyBody();
}
behavior.bodyUpdater =
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
behavior.collisionChecker = this.collisionChecker;
behavior.recreateBody();
behavior.recreateBody(previousBodyData);
// Always begin in the direction of the object.
this._forwardAngle = this.owner.getAngle();
@@ -277,13 +302,15 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): PhysicsCharacter3DNetworkSyncData {
override getNetworkSyncData(
options: GetNetworkSyncDataOptions
): PhysicsCharacter3DNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
fwa: this._forwardAngle,
fws: this._currentForwardSpeed,
@@ -306,9 +333,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: PhysicsCharacter3DNetworkSyncData
networkSyncData: PhysicsCharacter3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
this._forwardAngle = behaviorSpecificProps.fwa;
@@ -328,8 +356,8 @@ namespace gdjs {
this._timeSinceCurrentJumpStart = behaviorSpecificProps.tscjs;
this._jumpKeyHeldSinceJumpStart = behaviorSpecificProps.jkhsjs;
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// Clear user inputs between frames only if requested.
this._clearInputsBetweenFrames = !!options.clearInputs;
}
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
@@ -650,7 +678,7 @@ namespace gdjs {
this._wasJumpKeyPressed = this._hasPressedJumpKey;
this._wasStickUsed = this._hasUsedStick;
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
this._hasPressedForwardKey = false;
this._hasPressedBackwardKey = false;
this._hasPressedRightKey = false;

View File

@@ -147,7 +147,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
_dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
// This is useful when the object is synchronized over the network,
// object is controlled by the network and we want to ensure the current player
// cannot control it.
@@ -227,14 +227,16 @@ namespace gdjs {
this._state = this._falling;
}
getNetworkSyncData(): PlatformerObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PlatformerObjectNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
this._ignoreDefaultControlsAsSyncedByNetwork = false;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
props: {
cs: this._currentSpeed,
@@ -263,9 +265,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: PlatformerObjectNetworkSyncData
networkSyncData: PlatformerObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.cs !== this._currentSpeed) {
@@ -346,10 +349,10 @@ namespace gdjs {
this._state.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
}
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// Clear user inputs between frames only if requested.
this._clearInputsBetweenFrames = !!options.clearInputs;
// And we are not using the default controls.
this._ignoreDefaultControlsAsSyncedByNetwork = true;
this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
@@ -545,8 +548,9 @@ namespace gdjs {
this._wasJumpKeyPressed = this._jumpKey;
this._wasReleasePlatformKeyPressed = this._releasePlatformKey;
this._wasReleaseLadderKeyPressed = this._releaseLadderKey;
//4) Do not forget to reset pressed keys
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
// Reset the keys only if the inputs are not supposed to survive between frames.
// (Most of the time, except if this object is synchronized by an external source)
this._leftKey = false;

View File

@@ -0,0 +1,191 @@
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
* Changes in this file are watched and automatically imported if the editor
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
*
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
* ⚠️ If you make a change and the extension is not loaded, open the developer console
* and search for any errors.
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
'SaveState',
_('Save State'),
_('Allows to save and load the full state of a game.'),
'Neyl Mahfouf',
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/save-state')
.setCategory('Save & Load');
extension
.addInstructionOrExpressionGroupMetadata(_('Save State'))
.setIcon('res/actions/saveDown.svg');
extension
.addAction(
'SaveGameSnapshotToVariable',
_('Save game to a variable'),
_('Takes a snapshot of the game and save it to a variable.'),
_('Save the game in variable _PARAM1_'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('variable', _('Variable to store the save to'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.saveVariableGameSnapshot');
extension
.addAction(
'SaveGameSnapshotToStorage',
_('Save game to device storage'),
_('Takes a snapshot of the game and save it to device storage.'),
_('Save the game to device storage under key _PARAM1_'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('string', _('Storage key to save to'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.saveStorageGameSnapshot');
extension
.addAction(
'LoadGameSnapshotFromVariable',
_('Load game from variable'),
_('Load game from a variable save snapshot.'),
_('Load the game from variable _PARAM0_'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.addParameter('variable', _('Variable to load the game from'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.loadGameFromVariableSnapshot');
extension
.addAction(
'LoadGameSnapshotFromStorage',
_('Load game from device storage'),
_('Load game from device storage save snapshot.'),
_('Load the game from device storage under key _PARAM0_.'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.addParameter('string', _('Storage key to load the game from'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.loadGameFromStorageSnapshot');
extension
.addExpressionAndCondition(
'number',
'TimeSinceLastSave',
_('Time since last save'),
_(
'Time since the last save, in seconds. Returns -1 if no save happened, and a positive number otherwise.'
),
_('Time since the last save'),
'',
'res/actions/saveDown.svg'
)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.getSecondsSinceLastSave')
.setGetter('gdjs.saveState.getSecondsSinceLastSave');
extension
.addExpressionAndCondition(
'number',
'TimeSinceLastLoad',
_('Time since last load'),
_(
'Time since the last load, in seconds. Returns -1 if no load happened, and a positive number otherwise.'
),
_('Time since the last load'),
'',
'res/actions/saveDown.svg'
)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.getSecondsSinceLastLoad')
.setGetter('gdjs.saveState.getSecondsSinceLastLoad');
extension
.addCondition(
'SaveJustSucceeded',
_('Save just succeeded'),
_('the save just succeeded'),
_('the save just succeeded'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasSaveJustSucceeded');
extension
.addCondition(
'SaveJustFailed',
_('Save just failed'),
_('the save just failed'),
_('the save just failed'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasSaveJustFailed');
extension
.addCondition(
'LoadJustSucceeded',
_('Load just succeeded'),
_('the load just succeeded'),
_('the load just succeeded'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasLoadJustSucceeded');
extension
.addCondition(
'LoadJustFailed',
_('Load just failed'),
_('the load just failed'),
_('the load just failed'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasLoadJustFailed');
return extension;
},
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -0,0 +1,360 @@
namespace gdjs {
const logger = new gdjs.Logger('Save state');
export type LoadRequestOptions = {
loadStorageName?: string;
loadVariable?: gdjs.Variable;
};
export namespace saveState {
export const getIndexedDbDatabaseName = () => {
const gameId = gdjs.projectData.properties.projectUuid;
return `gdevelop-game-${gameId}`;
};
export const getIndexedDbObjectStore = () => {
return `game-saves`;
};
export const getIndexedDbStorageKey = (key: string) => {
return `save-${key}`;
};
const getNetworkSyncOptions: GetNetworkSyncDataOptions = {
syncObjectIdentifiers: true,
syncAllVariables: true,
syncAllBehaviors: true,
syncSceneTimers: true,
syncOnceTriggers: true,
syncSounds: true,
syncTweens: true,
syncLayers: true,
syncAsyncTasks: true,
syncSceneVisualProps: true,
syncFullTileMaps: true,
};
const updateFromNetworkSyncDataOptions: UpdateFromNetworkSyncDataOptions = {
clearSceneStack: true,
preventInitialInstancesCreation: true,
preventSoundsStoppingOnStartup: true,
clearInputs: true,
keepControl: true,
ignoreVariableOwnership: true,
};
let lastSaveTime: number | null = null;
let lastLoadTime: number | null = null;
let saveJustSucceeded: boolean = false;
let saveJustFailed: boolean = false;
let loadJustSucceeded: boolean = false;
let loadJustFailed: boolean = false;
let loadRequestOptions: LoadRequestOptions | null = null;
export const getSecondsSinceLastSave = (): number => {
if (!lastSaveTime) return -1;
return Math.floor((Date.now() - lastSaveTime) / 1000);
};
export const getSecondsSinceLastLoad = (): number => {
if (!lastLoadTime) return -1;
return Math.floor((Date.now() - lastLoadTime) / 1000);
};
export const hasSaveJustSucceeded = () => {
return saveJustSucceeded;
};
export const hasLoadJustSucceeded = () => {
return loadJustSucceeded;
};
export const hasSaveJustFailed = () => {
return saveJustFailed;
};
export const hasLoadJustFailed = () => {
return loadJustFailed;
};
export const markSaveJustSucceeded = () => {
saveJustSucceeded = true;
lastSaveTime = Date.now();
};
export const markLoadJustSucceeded = () => {
loadJustSucceeded = true;
lastLoadTime = Date.now();
};
export const markSaveJustFailed = () => {
saveJustFailed = true;
};
export const markLoadJustFailed = () => {
loadJustFailed = true;
};
// Ensure that the condition "save/load just succeeded/failed" are valid only for one frame.
gdjs.registerRuntimeScenePostEventsCallback(() => {
saveJustSucceeded = false;
saveJustFailed = false;
loadJustSucceeded = false;
loadJustFailed = false;
});
gdjs.registerRuntimeScenePostEventsCallback(
(runtimeScene: gdjs.RuntimeScene) => {
loadGameSnapshotAtTheEndOfFrameIfAny(runtimeScene);
}
);
const getGameSaveState = (runtimeScene: RuntimeScene) => {
const gameSaveState: GameSaveState = {
gameNetworkSyncData: {},
layoutNetworkSyncDatas: [],
};
const gameData = runtimeScene
.getGame()
.getNetworkSyncData(getNetworkSyncOptions);
const scenes = runtimeScene.getGame().getSceneStack().getAllScenes();
gameSaveState.gameNetworkSyncData = gameData || {};
scenes.forEach((scene, index) => {
gameSaveState.layoutNetworkSyncDatas[index] = {
sceneData: {} as LayoutNetworkSyncData,
objectDatas: {},
};
// First collect all object sync data, as they may generate unique
// identifiers like their networkId.
const sceneRuntimeObjects = scene.getAdhocListOfAllInstances();
for (const key in sceneRuntimeObjects) {
if (sceneRuntimeObjects.hasOwnProperty(key)) {
const object = sceneRuntimeObjects[key];
const objectSyncData = object.getNetworkSyncData(
getNetworkSyncOptions
);
gameSaveState.layoutNetworkSyncDatas[index].objectDatas[object.id] =
objectSyncData;
}
}
// Collect all scene data in the end.
const sceneDatas = (scene.getNetworkSyncData(getNetworkSyncOptions) ||
[]) as LayoutNetworkSyncData;
gameSaveState.layoutNetworkSyncDatas[index].sceneData = sceneDatas;
});
return gameSaveState;
};
export const saveVariableGameSnapshot = async function (
currentScene: RuntimeScene,
variable: gdjs.Variable
) {
try {
const gameSaveState = getGameSaveState(currentScene);
variable.fromJSObject(gameSaveState);
markSaveJustSucceeded();
} catch (error) {
logger.error('Error saving to variable:', error);
markSaveJustFailed();
}
};
export const saveStorageGameSnapshot = async function (
currentScene: RuntimeScene,
storageKey: string
) {
try {
const gameSaveState = getGameSaveState(currentScene);
await gdjs.indexedDb.saveToIndexedDB(
getIndexedDbDatabaseName(),
getIndexedDbObjectStore(),
getIndexedDbStorageKey(storageKey),
gameSaveState
);
markSaveJustSucceeded();
} catch (error) {
logger.error('Error saving to IndexedDB:', error);
markSaveJustFailed();
}
};
export const loadGameFromVariableSnapshot = async function (
variable: gdjs.Variable
) {
// The information is saved, so that the load can be done
// at the end of the frame,
// and avoid possible conflicts with running events.
loadRequestOptions = {
loadVariable: variable,
};
};
export const loadGameFromStorageSnapshot = async function (
storageName: string
) {
// The information is saved, so that the load can be done
// at the end of the frame,
// and avoid possible conflicts with running events.
loadRequestOptions = {
loadStorageName: storageName,
};
};
const loadGameSnapshotAtTheEndOfFrameIfAny = function (
runtimeScene: RuntimeScene
) {
if (!loadRequestOptions) return;
const optionsToApply = loadRequestOptions;
// Reset it so we don't load it twice.
loadRequestOptions = null;
if (optionsToApply.loadVariable) {
const sceneVariables = runtimeScene.getVariables();
const variablePathInScene =
sceneVariables.getVariablePathInContainerByLoopingThroughAllVariables(
optionsToApply.loadVariable
);
const gameVariables = runtimeScene.getGame().getVariables();
const variablePathIngame =
gameVariables.getVariablePathInContainerByLoopingThroughAllVariables(
optionsToApply.loadVariable
);
const saveState =
optionsToApply.loadVariable.toJSObject() as GameSaveState;
try {
loadGameFromSave(runtimeScene, saveState, {
variableToRehydrate: optionsToApply.loadVariable,
variablePathInScene: variablePathInScene,
variablePathInGame: variablePathIngame,
});
markLoadJustSucceeded();
} catch (error) {
logger.error('Error loading from variable:', error);
markLoadJustFailed();
}
} else if (optionsToApply.loadStorageName) {
gdjs.indexedDb
.loadFromIndexedDB(
getIndexedDbDatabaseName(),
getIndexedDbObjectStore(),
getIndexedDbStorageKey(optionsToApply.loadStorageName)
)
.then((jsonData) => {
const saveState = jsonData as GameSaveState;
loadGameFromSave(runtimeScene, saveState);
markLoadJustSucceeded();
})
.catch((error) => {
logger.error('Error loading from IndexedDB:', error);
markLoadJustFailed();
});
}
};
const loadGameFromSave = (
runtimeScene: RuntimeScene,
saveState: GameSaveState,
saveOptions?: {
variableToRehydrate: gdjs.Variable;
variablePathInScene: string[] | null;
variablePathInGame: string[] | null;
}
): void => {
// Save the content of the save, as it will be erased after the load.
const variableToRehydrateNetworkSyncData = saveOptions
? saveOptions.variableToRehydrate.getNetworkSyncData(
getNetworkSyncOptions
)
: null;
// First update the game, which will update the variables,
// and set the scene stack to update when ready.
const runtimeGame = runtimeScene.getGame();
runtimeGame.updateFromNetworkSyncData(
saveState.gameNetworkSyncData,
updateFromNetworkSyncDataOptions
);
// Apply the scene stack updates, as we are at the end of a frame,
// we can safely do it.
const sceneStack = runtimeGame.getSceneStack();
sceneStack.applyUpdateFromNetworkSyncDataIfAny(
updateFromNetworkSyncDataOptions
);
// Then get all scenes, which we assume will be the expected ones
// after the load has been done, so we can update them,
// and create their objects.
const runtimeScenes = sceneStack.getAllScenes();
runtimeScenes.forEach((scene, index) => {
const layoutSyncData = saveState.layoutNetworkSyncDatas[index];
if (!layoutSyncData) return;
// Create objects first, so they are available for the scene update,
// especially so that they have a networkId defined.
const objectDatas = layoutSyncData.objectDatas;
for (const id in objectDatas) {
const objectNetworkSyncData = objectDatas[id];
const objectName = objectNetworkSyncData.n;
if (!objectName) {
logger.warn('Tried to recreate an object without a name.');
continue;
}
const object = scene.createObject(objectName);
if (object) {
object.updateFromNetworkSyncData(
objectNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
// Update the scene last.
scene.updateFromNetworkSyncData(
layoutSyncData.sceneData,
updateFromNetworkSyncDataOptions
);
});
// Finally, if the save was done in a variable,
// rehydrate the variable where the save was done,
// as it has been erased by the load.
if (saveOptions && variableToRehydrateNetworkSyncData) {
const currentScene = sceneStack.getCurrentScene();
if (!currentScene) return;
const sceneVariables = currentScene.getVariables();
const gameVariables = currentScene.getGame().getVariables();
const { variablePathInScene, variablePathInGame } = saveOptions;
if (variablePathInScene && variablePathInScene.length > 0) {
const variableName =
variablePathInScene[variablePathInScene.length - 1];
const variableInScene =
sceneVariables.getVariableFromPath(variablePathInScene);
if (variableInScene) {
const variableNetworkSyncData: VariableNetworkSyncData = {
name: variableName,
...variableToRehydrateNetworkSyncData,
};
variableInScene.updateFromNetworkSyncData(
variableNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
if (variablePathInGame && variablePathInGame.length > 0) {
const variableName =
variablePathInGame[variablePathInGame.length - 1];
const variableInGame =
gameVariables.getVariableFromPath(variablePathInGame);
if (variableInGame) {
const variableNetworkSyncData: VariableNetworkSyncData = {
name: variableName,
...variableToRehydrateNetworkSyncData,
};
variableInGame.updateFromNetworkSyncData(
variableNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
}
};
}
}

View File

@@ -111,9 +111,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): SpineNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): SpineNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
opa: this._opacity,
scaX: this.getScaleX(),
scaY: this.getScaleY(),
@@ -127,8 +129,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(syncData: SpineNetworkSyncData): void {
super.updateFromNetworkSyncData(syncData);
updateFromNetworkSyncData(
syncData: SpineNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData, options);
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
this.setOpacity(syncData.opa);

View File

@@ -267,9 +267,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): TextInputNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): TextInputNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
opa: this.getOpacity(),
txt: this.getText(),
frn: this.getFontResourceName(),
@@ -288,8 +290,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(syncData: TextInputNetworkSyncData): void {
super.updateFromNetworkSyncData(syncData);
updateFromNetworkSyncData(
syncData: TextInputNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData, options);
if (syncData.opa !== undefined) this.setOpacity(syncData.opa);
if (syncData.txt !== undefined) this.setText(syncData.txt);

View File

@@ -52,6 +52,7 @@ namespace gdjs {
c: number[];
scale: number;
ta: string;
vta: string;
wrap: boolean;
wrapw: float;
oena: boolean;
@@ -179,9 +180,6 @@ namespace gdjs {
if (oldContent.text !== newContent.text) {
this.setText(newContent.text);
}
if (oldContent.underlined !== newContent.underlined) {
return false;
}
if (oldContent.textAlignment !== newContent.textAlignment) {
this.setTextAlignment(newContent.textAlignment);
}
@@ -220,12 +218,18 @@ namespace gdjs {
if ((oldContent.lineHeight || 0) !== (newContent.lineHeight || 0)) {
this.setLineHeight(newContent.lineHeight || 0);
}
if (oldContent.underlined !== newContent.underlined) {
return false;
}
return true;
}
override getNetworkSyncData(): TextObjectNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): TextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
str: this._str,
o: this.opacity,
cs: this._characterSize,
@@ -236,6 +240,7 @@ namespace gdjs {
c: this._color,
scale: this.getScale(),
ta: this._textAlign,
vta: this._verticalTextAlignment,
wrap: this._wrapping,
wrapw: this._wrappingWidth,
oena: this._isOutlineEnabled,
@@ -253,9 +258,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: TextObjectNetworkSyncData
networkSyncData: TextObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.str !== undefined) {
this.setText(networkSyncData.str);
}
@@ -286,8 +292,8 @@ namespace gdjs {
if (networkSyncData.ta !== undefined) {
this.setTextAlignment(networkSyncData.ta);
}
if (networkSyncData.ta !== undefined) {
this.setVerticalTextAlignment(networkSyncData.ta);
if (networkSyncData.vta !== undefined) {
this.setVerticalTextAlignment(networkSyncData.vta);
}
if (networkSyncData.wrap !== undefined) {
this.setWrapping(networkSyncData.wrap);

View File

@@ -9,7 +9,7 @@ import {
* A tile map model.
*
* Tile map files are parsed into this model by {@link TiledTileMapLoader} or {@link LDtkTileMapLoader}.
* This model is used for rending ({@link TileMapRuntimeObjectPixiRenderer})
* This model is used for rendering ({@link TileMapRuntimeObjectPixiRenderer})
* and hitboxes handling ({@link TransformedCollisionTileMap}).
* This allows to support new file format with only a new parser.
*/
@@ -71,7 +71,7 @@ export declare class EditableTileMap {
tileSetRowCount: number;
}
): EditableTileMap;
toJSObject(): Object;
toJSObject(): EditableTileMapAsJsObject;
/**
* @returns The tile map width in pixels.
*/
@@ -205,7 +205,7 @@ declare abstract class AbstractEditableLayer {
*/
constructor(tileMap: EditableTileMap, id: integer);
setVisible(visible: boolean): void;
toJSObject(): Object;
toJSObject(): EditableTileMapLayerAsJsObject;
/**
* @returns true if the layer is visible.
*/
@@ -284,7 +284,7 @@ export declare class EditableTileMapLayer extends AbstractEditableLayer {
tileMap: EditableTileMap,
isTileIdValid: (tileId: number) => boolean
): EditableTileMapLayer;
toJSObject(): Object;
toJSObject(): EditableTileMapLayerAsJsObject;
/**
* The opacity (between 0-1) of the layer
*/

View File

@@ -2,7 +2,6 @@
namespace gdjs {
export type SimpleTileMapObjectDataType = {
content: {
opacity: number;
atlasImage: string;
rowCount: number;
columnCount: number;
@@ -16,8 +15,7 @@ namespace gdjs {
export type SimpleTileMapNetworkSyncDataType = {
op: number;
ai: string;
// TODO: Support tilemap synchronization. Find an efficient way to send tiles changes.
tm?: TileMapHelper.EditableTileMapAsJsObject;
};
export type SimpleTileMapNetworkSyncData = ObjectNetworkSyncData &
@@ -38,6 +36,7 @@ namespace gdjs {
_opacity: float = 255;
_atlasImage: string;
_tileMapManager: gdjs.TileMap.TileMapRuntimeManager;
_tileMap: TileMapHelper.EditableTileMap | null = null;
_renderer: gdjs.TileMapRuntimeObjectPixiRenderer;
readonly _rowCount: number;
readonly _columnCount: number;
@@ -62,7 +61,6 @@ namespace gdjs {
objectData: SimpleTileMapObjectDataType
) {
super(instanceContainer, objectData);
this._opacity = objectData.content.opacity;
this._atlasImage = objectData.content.atlasImage;
this._rowCount = objectData.content.rowCount;
this._columnCount = objectData.content.columnCount;
@@ -87,16 +85,19 @@ namespace gdjs {
instanceContainer
);
this._loadInitialTileMap((tileMap: TileMapHelper.EditableTileMap) => {
this._renderer.updatePosition();
this._loadTileMap(
this._initialTileMapAsJsObject,
(tileMap: TileMapHelper.EditableTileMap) => {
this._renderer.updatePosition();
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
tileMap,
this._hitBoxTag
);
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
tileMap,
this._hitBoxTag
);
this.updateTransformation();
});
this.updateTransformation();
}
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
@@ -139,8 +140,8 @@ namespace gdjs {
);
if (!shouldContinue) return;
if (this._collisionTileMap) {
const tileMap = this._renderer.getTileMap();
if (tileMap) this._collisionTileMap.updateFromTileMap(tileMap);
if (this._tileMap)
this._collisionTileMap.updateFromTileMap(this._tileMap);
}
this._isTileMapDirty = false;
}
@@ -150,9 +151,6 @@ namespace gdjs {
oldObjectData: SimpleTileMapObjectData,
newObjectData: SimpleTileMapObjectData
): boolean {
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
this.setOpacity(newObjectData.content.opacity);
}
if (
oldObjectData.content.atlasImage !== newObjectData.content.atlasImage
) {
@@ -163,27 +161,58 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): SimpleTileMapNetworkSyncData {
return {
...super.getNetworkSyncData(),
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): SimpleTileMapNetworkSyncData {
const syncData: SimpleTileMapNetworkSyncData = {
...super.getNetworkSyncData(syncOptions),
op: this._opacity,
ai: this._atlasImage,
};
if (this._tileMap && syncOptions.syncFullTileMaps) {
const currentTileMapAsJsObject = this._tileMap.toJSObject();
syncData.tm = currentTileMapAsJsObject;
}
return syncData;
}
updateFromNetworkSyncData(
networkSyncData: SimpleTileMapNetworkSyncData
networkSyncData: SimpleTileMapNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (
networkSyncData.op !== undefined &&
networkSyncData.op !== this._opacity
) {
this.setOpacity(networkSyncData.op);
}
if (networkSyncData.ai !== undefined) {
// TODO: support changing the atlas texture
if (networkSyncData.tm !== undefined) {
this._loadTileMap(
networkSyncData.tm,
(tileMap: TileMapHelper.EditableTileMap) => {
if (networkSyncData.w !== undefined) {
this.setWidth(networkSyncData.w);
}
if (networkSyncData.h !== undefined) {
this.setHeight(networkSyncData.h);
}
if (networkSyncData.op !== undefined) {
this.setOpacity(networkSyncData.op);
}
// 4. Update position (calculations based on renderer's dimensions).
this._renderer.updatePosition();
if (this._collisionTileMap) {
// If collision tile map is already defined, only update it.
this._collisionTileMap.updateFromTileMap(tileMap);
} else {
this._collisionTileMap =
new gdjs.TileMap.TransformedCollisionTileMap(
tileMap,
this._hitBoxTag
);
}
this.updateTransformation();
}
);
}
}
@@ -199,39 +228,43 @@ namespace gdjs {
// 2. Update the renderer so that it updates the tilemap object
// (used for width and position calculations).
this._loadInitialTileMap((tileMap: TileMapHelper.EditableTileMap) => {
// 3. Set custom dimensions & opacity if applicable.
if (initialInstanceData.customSize) {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this._loadTileMap(
this._initialTileMapAsJsObject,
(tileMap: TileMapHelper.EditableTileMap) => {
// 3. Set custom dimensions & opacity if applicable.
if (initialInstanceData.customSize) {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
// 4. Update position (calculations based on renderer's dimensions).
this._renderer.updatePosition();
// 4. Update position (calculations based on renderer's dimensions).
this._renderer.updatePosition();
if (this._collisionTileMap) {
// If collision tile map is already defined, there's a good chance it means
// extraInitializationFromInitialInstance is called when hot reloading the
// scene so the collision is tile map is updated instead of being re-created.
this._collisionTileMap.updateFromTileMap(tileMap);
} else {
this._collisionTileMap = new gdjs.TileMap.TransformedCollisionTileMap(
tileMap,
this._hitBoxTag
);
if (this._collisionTileMap) {
// If collision tile map is already defined, there's a good chance it means
// extraInitializationFromInitialInstance is called when hot reloading the
// scene so the collision is tile map is updated instead of being re-created.
this._collisionTileMap.updateFromTileMap(tileMap);
} else {
this._collisionTileMap =
new gdjs.TileMap.TransformedCollisionTileMap(
tileMap,
this._hitBoxTag
);
}
this.updateTransformation();
}
this.updateTransformation();
});
);
}
private _loadInitialTileMap(
private _loadTileMap(
tileMapAsJsObject: TileMapHelper.EditableTileMapAsJsObject,
tileMapLoadingCallback: (tileMap: TileMapHelper.EditableTileMap) => void
): void {
if (!this._initialTileMapAsJsObject) return;
if (this._columnCount <= 0 || this._rowCount <= 0) {
console.error(
`Tilemap object ${this.name} is not configured properly.`
@@ -240,7 +273,7 @@ namespace gdjs {
}
this._tileMapManager.getOrLoadSimpleTileMap(
this._initialTileMapAsJsObject,
tileMapAsJsObject,
this.name,
this._tileSize,
this._columnCount,
@@ -284,7 +317,8 @@ namespace gdjs {
// getOrLoadTextureCache already log warns and errors.
return;
}
this._renderer.updatePixiTileMap(tileMap, textureCache);
this._tileMap = tileMap;
this._renderer.refreshPixiTileMap(textureCache);
tileMapLoadingCallback(tileMap);
},
(error) => {
@@ -640,7 +674,7 @@ namespace gdjs {
}
getTileAtGridCoordinates(columnIndex: integer, rowIndex: integer): integer {
return this._renderer.getTileId(columnIndex, rowIndex, 0);
return this.getTileId(columnIndex, rowIndex, 0);
}
setTileAtPosition(tileId: number, x: float, y: float) {
@@ -654,11 +688,10 @@ namespace gdjs {
columnIndex: integer,
rowIndex: integer
) {
const tileMap = this._renderer._tileMap;
if (!tileMap) {
if (!this._tileMap) {
return;
}
const layer = tileMap.getTileLayer(this._layerIndex);
const layer = this._tileMap.getTileLayer(this._layerIndex);
if (!layer) {
return;
}
@@ -670,8 +703,8 @@ namespace gdjs {
if (this._collisionTileMap) {
const oldTileDefinition =
oldTileId !== undefined && tileMap.getTileDefinition(oldTileId);
const newTileDefinition = tileMap.getTileDefinition(tileId);
oldTileId !== undefined && this._tileMap.getTileDefinition(oldTileId);
const newTileDefinition = this._tileMap.getTileDefinition(tileId);
const hadFullHitBox =
!!oldTileDefinition &&
oldTileDefinition.hasFullHitBox(this._hitBoxTag);
@@ -707,7 +740,7 @@ namespace gdjs {
rowIndex: integer,
flip: boolean
) {
this._renderer.flipTileOnY(columnIndex, rowIndex, 0, flip);
this.flipTileOnY(columnIndex, rowIndex, 0, flip);
this._isTileMapDirty = true;
// No need to invalidate hit boxes since at the moment, collision mask
// cannot be configured on each tile.
@@ -718,7 +751,7 @@ namespace gdjs {
rowIndex: integer,
flip: boolean
) {
this._renderer.flipTileOnX(columnIndex, rowIndex, 0, flip);
this.flipTileOnX(columnIndex, rowIndex, 0, flip);
this._isTileMapDirty = true;
// No need to invalidate hit boxes since at the moment, collision mask
// cannot be configured on each tile.
@@ -728,22 +761,22 @@ namespace gdjs {
const [columnIndex, rowIndex] =
this.getGridCoordinatesFromSceneCoordinates(x, y);
return this._renderer.isTileFlippedOnX(columnIndex, rowIndex, 0);
return this.isTileFlippedOnX(columnIndex, rowIndex, 0);
}
isTileFlippedOnXAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
return this._renderer.isTileFlippedOnX(columnIndex, rowIndex, 0);
return this.isTileFlippedOnX(columnIndex, rowIndex, 0);
}
isTileFlippedOnYAtPosition(x: float, y: float) {
const [columnIndex, rowIndex] =
this.getGridCoordinatesFromSceneCoordinates(x, y);
return this._renderer.isTileFlippedOnY(columnIndex, rowIndex, 0);
return this.isTileFlippedOnY(columnIndex, rowIndex, 0);
}
isTileFlippedOnYAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
return this._renderer.isTileFlippedOnY(columnIndex, rowIndex, 0);
return this.isTileFlippedOnY(columnIndex, rowIndex, 0);
}
removeTileAtPosition(x: float, y: float) {
@@ -753,11 +786,10 @@ namespace gdjs {
}
removeTileAtGridCoordinates(columnIndex: integer, rowIndex: integer) {
const tileMap = this._renderer._tileMap;
if (!tileMap) {
if (!this._tileMap) {
return;
}
const layer = tileMap.getTileLayer(this._layerIndex);
const layer = this._tileMap.getTileLayer(this._layerIndex);
if (!layer) {
return;
}
@@ -779,24 +811,28 @@ namespace gdjs {
setGridRowCount(targetRowCount: integer) {
if (targetRowCount <= 0) return;
this._renderer.setGridRowCount(targetRowCount);
if (!this._tileMap) return;
this._tileMap.setDimensionY(targetRowCount);
this._isTileMapDirty = true;
this.invalidateHitboxes();
}
setGridColumnCount(targetColumnCount: integer) {
if (targetColumnCount <= 0) return;
this._renderer.setGridColumnCount(targetColumnCount);
if (!this._tileMap) return;
this._tileMap.setDimensionX(targetColumnCount);
this._isTileMapDirty = true;
this.invalidateHitboxes();
}
getGridRowCount(): integer {
return this._renderer.getGridRowCount();
if (!this._tileMap) return 0;
return this._tileMap.getDimensionY();
}
getGridColumnCount(): integer {
return this._renderer.getGridColumnCount();
if (!this._tileMap) return 0;
return this._tileMap.getDimensionX();
}
getTilesetColumnCount(): integer {
@@ -806,6 +842,73 @@ namespace gdjs {
getTilesetRowCount(): integer {
return this._rowCount;
}
getTileMap(): TileMapHelper.EditableTileMap | null {
return this._tileMap;
}
getTileMapWidth() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getWidth() : 20;
}
getTileMapHeight() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getHeight() : 20;
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @returns The tile's id.
*/
getTileId(x: integer, y: integer, layerIndex: integer): integer {
if (!this._tileMap) return -1;
return this._tileMap.getTileId(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
if (!this._tileMap) return;
this._tileMap.flipTileOnY(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
if (!this._tileMap) return;
this._tileMap.flipTileOnX(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
if (!this._tileMap) return false;
return this._tileMap.isTileFlippedOnX(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
if (!this._tileMap) return false;
return this._tileMap.isTileFlippedOnY(x, y, layerIndex);
}
}
gdjs.registerObject(
'TileMap::SimpleTileMap',

View File

@@ -189,9 +189,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): TilemapCollisionMaskNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): TilemapCollisionMaskNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
tmjf: this.getTilemapJsonFile(),
tsjf: this.getTilesetJsonFile(),
dm: this.getDebugMode(),
@@ -204,9 +206,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: TilemapCollisionMaskNetworkSyncData
networkSyncData: TilemapCollisionMaskNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.tmjf !== undefined) {
this.setTilemapJsonFile(networkSyncData.tmjf);

View File

@@ -10,8 +10,6 @@ namespace gdjs {
private _object:
| gdjs.TileMapRuntimeObject
| gdjs.SimpleTileMapRuntimeObject;
// TODO Move this attribute in the object as it's a model.
_tileMap: TileMapHelper.EditableTileMap | null = null;
private _pixiObject: PIXI.tilemap.CompositeTilemap;
@@ -51,40 +49,9 @@ namespace gdjs {
this._pixiObject.tileAnim[0] += 1;
}
updatePixiTileMap(
tileMap: TileMapHelper.EditableTileMap,
textureCache: TileMapHelper.TileTextureCache
) {
this._tileMap = tileMap;
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
this._pixiObject,
tileMap,
textureCache,
// @ts-ignore
this._object._displayMode,
this._object._layerIndex
);
}
refreshPixiTileMap(textureCache: TileMapHelper.TileTextureCache) {
if (!this._tileMap) return;
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
this._pixiObject,
this._tileMap,
textureCache,
// @ts-ignore
this._object._displayMode,
this._object._layerIndex
);
}
getTileMap(): TileMapHelper.EditableTileMap | null {
return this._tileMap;
}
updatePosition(): void {
this._pixiObject.pivot.x = this.getTileMapWidth() / 2;
this._pixiObject.pivot.y = this.getTileMapHeight() / 2;
this._pixiObject.pivot.x = this._object.getTileMapWidth() / 2;
this._pixiObject.pivot.y = this._object.getTileMapHeight() / 2;
this._pixiObject.position.x = this._object.x + this.getWidth() / 2;
this._pixiObject.position.y = this._object.y + this.getHeight() / 2;
}
@@ -98,7 +65,7 @@ namespace gdjs {
// opacity. Setting alpha on each layer tile might not be useful as
// each layer would be separately transparent instead of the whole tilemap.
this._pixiObject.alpha = this._object._opacity / 255;
const tileMap = this._tileMap;
const tileMap = this._object.getTileMap();
if (!tileMap) return;
for (const layer of tileMap.getLayers()) {
if (
@@ -114,44 +81,34 @@ namespace gdjs {
}
}
getTileMapWidth() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getWidth() : 20;
}
getTileMapHeight() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getHeight() : 20;
}
setWidth(width: float): void {
this._pixiObject.scale.x = width / this.getTileMapWidth();
this._pixiObject.scale.x = width / this._object.getTileMapWidth();
this._pixiObject.position.x = this._object.x + width / 2;
}
setHeight(height: float): void {
this._pixiObject.scale.y = height / this.getTileMapHeight();
this._pixiObject.scale.y = height / this._object.getTileMapHeight();
this._pixiObject.position.y = this._object.y + height / 2;
}
setScaleX(scaleX: float): void {
this._pixiObject.scale.x = scaleX;
const width = scaleX * this.getTileMapWidth();
const width = scaleX * this._object.getTileMapWidth();
this._pixiObject.position.x = this._object.x + width / 2;
}
setScaleY(scaleY: float): void {
this._pixiObject.scale.y = scaleY;
const height = scaleY * this.getTileMapHeight();
const height = scaleY * this._object.getTileMapHeight();
this._pixiObject.position.y = this._object.y + height / 2;
}
getWidth(): float {
return this.getTileMapWidth() * this._pixiObject.scale.x;
return this._object.getTileMapWidth() * this._pixiObject.scale.x;
}
getHeight(): float {
return this.getTileMapHeight() * this._pixiObject.scale.y;
return this._object.getTileMapHeight() * this._pixiObject.scale.y;
}
getScaleX(): float {
@@ -162,91 +119,17 @@ namespace gdjs {
return this._pixiObject.scale.y;
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @returns The tile's id.
*/
getTileId(x: integer, y: integer, layerIndex: integer): integer {
const tileMap = this._tileMap;
if (!tileMap) return -1;
return tileMap.getTileId(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
const tileMap = this._tileMap;
refreshPixiTileMap(textureCache: TileMapHelper.TileTextureCache) {
const tileMap = this._object.getTileMap();
if (!tileMap) return;
tileMap.flipTileOnY(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
const tileMap = this._tileMap;
if (!tileMap) return;
tileMap.flipTileOnX(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
const tileMap = this._tileMap;
if (!tileMap) return false;
return tileMap.isTileFlippedOnX(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
const tileMap = this._tileMap;
if (!tileMap) return false;
return tileMap.isTileFlippedOnY(x, y, layerIndex);
}
/**
* @param targetRowCount The number of rows to have.
*/
setGridRowCount(targetRowCount: integer) {
const tileMap = this._tileMap;
if (!tileMap) return;
return tileMap.setDimensionY(targetRowCount);
}
/**
* @param targetColumnCount The number of rows to have.
*/
setGridColumnCount(targetColumnCount: integer) {
const tileMap = this._tileMap;
if (!tileMap) return;
return tileMap.setDimensionX(targetColumnCount);
}
getGridRowCount(): integer {
const tileMap = this._tileMap;
if (!tileMap) return 0;
return tileMap.getDimensionY();
}
getGridColumnCount(): integer {
const tileMap = this._tileMap;
if (!tileMap) return 0;
return tileMap.getDimensionX();
TileMapHelper.PixiTileMapHelper.updatePixiTileMap(
this._pixiObject,
tileMap,
textureCache,
// @ts-ignore
this._displayMode,
this._object._layerIndex
);
}
destroy(): void {

View File

@@ -2,7 +2,6 @@
namespace gdjs {
export type TilemapObjectDataType = {
content: {
opacity: number;
tilemapJsonFile: string;
tilesetJsonFile: string;
tilemapAtlasImage: string;
@@ -38,7 +37,7 @@ namespace gdjs {
implements gdjs.Resizable, gdjs.Scalable, gdjs.OpacityHandler
{
_frameElapsedTime: float = 0;
_opacity: float;
_opacity: float = 255;
_tilemapJsonFile: string;
_tilesetJsonFile: string;
_tilemapAtlasImage: string;
@@ -48,11 +47,14 @@ namespace gdjs {
_animationSpeedScale: number;
_animationFps: number;
_tileMapManager: gdjs.TileMap.TileMapRuntimeManager;
_tileMap: TileMapHelper.EditableTileMap | null = null;
_renderer: gdjs.TileMapRuntimeObjectPixiRenderer;
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: TilemapObjectData
) {
super(instanceContainer, objectData);
this._opacity = objectData.content.opacity;
this._tilemapJsonFile = objectData.content.tilemapJsonFile;
this._tilesetJsonFile = objectData.content.tilesetJsonFile;
this._tilemapAtlasImage = objectData.content.tilemapAtlasImage;
@@ -93,9 +95,6 @@ namespace gdjs {
oldObjectData: TilemapObjectData,
newObjectData: TilemapObjectData
): boolean {
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
this.setOpacity(newObjectData.content.opacity);
}
if (
oldObjectData.content.tilemapJsonFile !==
newObjectData.content.tilemapJsonFile
@@ -145,9 +144,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): TilemapNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): TilemapNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
op: this._opacity,
tmjf: this._tilemapJsonFile,
tsjf: this._tilesetJsonFile,
@@ -159,8 +160,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: TilemapNetworkSyncData): void {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: TilemapNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.op !== undefined) {
this.setOpacity(networkSyncData.op);
@@ -230,7 +234,9 @@ namespace gdjs {
// getOrLoadTextureCache already log warns and errors.
return;
}
this._renderer.updatePixiTileMap(tileMap, textureCache);
this._tileMap = tileMap;
this._renderer.refreshPixiTileMap(textureCache);
this.invalidateHitboxes();
}
);
}
@@ -427,6 +433,73 @@ namespace gdjs {
getScaleY(): float {
return this._renderer.getScaleY();
}
getTileMap(): TileMapHelper.EditableTileMap | null {
return this._tileMap;
}
getTileMapWidth() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getWidth() : 20;
}
getTileMapHeight() {
const tileMap = this._tileMap;
return tileMap ? tileMap.getHeight() : 20;
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @returns The tile's id.
*/
getTileId(x: integer, y: integer, layerIndex: integer): integer {
if (!this._tileMap) return -1;
return this._tileMap.getTileId(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnY(x: integer, y: integer, layerIndex: integer, flip: boolean) {
if (!this._tileMap) return;
this._tileMap.flipTileOnY(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
* @param flip true if the tile should be flipped.
*/
flipTileOnX(x: integer, y: integer, layerIndex: integer, flip: boolean) {
if (!this._tileMap) return;
this._tileMap.flipTileOnX(x, y, layerIndex, flip);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnX(x: integer, y: integer, layerIndex: integer): boolean {
if (!this._tileMap) return false;
return this._tileMap.isTileFlippedOnX(x, y, layerIndex);
}
/**
* @param x The layer column.
* @param y The layer row.
* @param layerIndex The layer index.
*/
isTileFlippedOnY(x: integer, y: integer, layerIndex: integer): boolean {
if (!this._tileMap) return false;
return this._tileMap.isTileFlippedOnY(x, y, layerIndex);
}
}
gdjs.registerObject('TileMap::TileMap', gdjs.TileMapRuntimeObject);
}

View File

@@ -78,9 +78,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): TiledSpriteNetworkSyncData {
getNetworkSyncData(
syncOptons: GetNetworkSyncDataOptions
): TiledSpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptons),
xo: this.getXOffset(),
yo: this.getYOffset(),
op: this.getOpacity(),
@@ -89,9 +91,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: TiledSpriteNetworkSyncData
networkSyncData: TiledSpriteNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
// Texture is not synchronized, see if this is asked or not.

View File

@@ -63,7 +63,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
_dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
// This is useful when the object is synchronized over the network,
// object is controlled by the network and we want to ensure the current player
// cannot control it.
@@ -109,14 +109,16 @@ namespace gdjs {
: behaviorData.useLegacyTurnBack;
}
getNetworkSyncData(): TopDownMovementNetworkSyncData {
getNetworkSyncData(
options: GetNetworkSyncDataOptions
): TopDownMovementNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
this._ignoreDefaultControlsAsSyncedByNetwork = false;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
a: this._angle,
xv: this._xVelocity,
@@ -134,9 +136,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: TopDownMovementNetworkSyncData
networkSyncData: TopDownMovementNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.a !== undefined) {
@@ -173,10 +176,10 @@ namespace gdjs {
this._stickForce = behaviorSpecificProps.sf;
}
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// And we are not using the default controls.
this._ignoreDefaultControlsAsSyncedByNetwork = true;
// Clear user inputs between frames only if requested.
this._clearInputsBetweenFrames = !!options.clearInputs;
// And ignore default controls if not asked otherwise.
this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
@@ -581,7 +584,7 @@ namespace gdjs {
this._wasRightKeyPressed = this._rightKey;
this._wasUpKeyPressed = this._upKey;
this._wasDownKeyPressed = this._downKey;
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
this._leftKey = false;
this._rightKey = false;
this._upKey = false;

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,15 @@ Copyright (c) 2010-2023 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
const logger = new gdjs.Logger('Tween');
interface TweenBehaviorNetworkSyncDataType {
tweenManager: TweenManagerNetworkSyncData;
}
export interface TweenBehaviorNetworkSyncData
extends BehaviorNetworkSyncData {
props: TweenBehaviorNetworkSyncDataType;
}
interface IColorable extends gdjs.RuntimeObject {
setColor(color: string): void;
getColor(): string;
@@ -59,6 +68,305 @@ namespace gdjs {
const exponentialInterpolation =
gdjs.evtTools.common.exponentialInterpolation;
const tweenObjectValueSetter = () => {};
const getTweenVariableSetter = (variable: gdjs.Variable) => {
return (value: float) => variable.setNumber(value);
};
const getTweenObjectPositionXSetter = (object: gdjs.RuntimeObject) => {
return (value: float) => object.setX(value);
};
const getTweenObjectPositionYSetter = (object: gdjs.RuntimeObject) => {
return (value: float) => object.setY(value);
};
const getTweenObjectPositionZSetter = (object: gdjs.RuntimeObject) => {
if (!is3D(object)) return () => {};
return (value: float) => object.setZ(value);
};
const getTweenObjectPositionSetter = (object: gdjs.RuntimeObject) => {
return ([x, y]: float[]) => object.setPosition(x, y);
};
const getTweenObjectAngleSetter = (object: gdjs.RuntimeObject) => {
return (value: float) => object.setAngle(value);
};
const getTweenObjectWidthSetter = (object: gdjs.RuntimeObject) => {
return (value: float) => object.setWidth(value);
};
const getTweenObjectHeightSetter = (object: gdjs.RuntimeObject) => {
return (value: float) => object.setHeight(value);
};
const getTweenObjectRotationXSetter = (
object: gdjs.RuntimeObject & gdjs.Base3DHandler
) => {
return (value: float) => object.setRotationX(value);
};
const getTweenObjectRotationYSetter = (
object: gdjs.RuntimeObject & gdjs.Base3DHandler
) => {
return (value: float) => object.setRotationY(value);
};
const getTweenObjectDepthSetter = (
object: gdjs.RuntimeObject & gdjs.Base3DHandler
) => {
return (value: float) => object.setDepth(value);
};
const getTweenObjectScaleXYSetter = (
object: gdjs.RuntimeObject & gdjs.Scalable,
scaleFromCenterOfObject: boolean
) => {
return scaleFromCenterOfObject
? ([scaleX, scaleY]: float[]) => {
const oldX = object.getCenterXInScene();
const oldY = object.getCenterYInScene();
object.setScaleX(scaleX);
object.setScaleY(scaleY);
object.setCenterPositionInScene(oldX, oldY);
}
: ([scaleX, scaleY]: float[]) => {
object.setScaleX(scaleX);
object.setScaleY(scaleY);
};
};
const getTweenObjectScaleSetter = (
object: gdjs.RuntimeObject & gdjs.Scalable,
object3d: (gdjs.RuntimeObject & gdjs.Base3DHandler) | null,
scaleFromCenterOfObject: boolean
) => {
return scaleFromCenterOfObject
? (scale: float) => {
const oldX = object.getCenterXInScene();
const oldY = object.getCenterYInScene();
const oldZ = object3d ? object3d.getCenterZInScene() : 0;
object.setScale(scale);
object.setCenterXInScene(oldX);
object.setCenterYInScene(oldY);
if (object3d) {
object3d.setCenterZInScene(oldZ);
}
}
: (scale: float) => object.setScale(scale);
};
const getTweenObjectScaleXSetter = (
object: gdjs.RuntimeObject & gdjs.Scalable,
scaleFromCenterOfObject: boolean
) => {
return scaleFromCenterOfObject
? (scaleX: float) => {
const oldX = object.getCenterXInScene();
object.setScaleX(scaleX);
object.setCenterXInScene(oldX);
}
: (scaleX: float) => object.setScaleX(scaleX);
};
const getTweenObjectScaleYSetter = (
object: gdjs.RuntimeObject & gdjs.Scalable,
scaleFromCenterOfObject: boolean
) => {
return scaleFromCenterOfObject
? (scaleY: float) => {
const oldY = object.getCenterYInScene();
object.setScaleY(scaleY);
object.setCenterYInScene(oldY);
}
: (scaleY: float) => object.setScaleY(scaleY);
};
const getTweenObjectOpacitySetter = (
object: gdjs.RuntimeObject & gdjs.OpacityHandler
) => {
return (value: float) => object.setOpacity(value);
};
const getTweenObjectCharacterSizeSetter = (object: ICharacterScalable) => {
return (value: float) => object.setCharacterSize(value);
};
const getTweenObjectNumberEffectPropertySetter = (
effect: PixiFiltersTools.Filter,
propertyName: string
) => {
return (value: float) => {
effect.updateDoubleParameter(propertyName, value);
};
};
const getTweenObjectColorEffectPropertySetter = (
effect: PixiFiltersTools.Filter,
propertyName: string
) => {
return ([hue, saturation, lightness]: number[]) => {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
};
};
const getTweenObjectColorSetter = (
object: IColorable,
useHSLColorTransition: boolean
) => {
if (useHSLColorTransition) {
return ([hue, saturation, lightness]: number[]) => {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
object.setColor(
Math.floor(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
);
};
} else {
return ([red, green, blue]: number[]) => {
object.setColor(
Math.floor(red) + ';' + Math.floor(green) + ';' + Math.floor(blue)
);
};
}
};
const getTweenObjectColorHSLSetter = (object: IColorable) => {
return ([hue, saturation, lightness]: number[]) => {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
object.setColor(
Math.floor(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
);
};
};
const tweenSetterFactory =
(object: RuntimeObject) =>
(tweenInformation: TweenInformationNetworkSyncData) => {
const type = tweenInformation.type;
const variablePath = tweenInformation.variablePath;
const effectName = tweenInformation.effectName;
const propertyName = tweenInformation.propertyName;
const scaleFromCenterOfObject =
!!tweenInformation.scaleFromCenterOfObject;
const useHSLColorTransition = !!tweenInformation.useHSLColorTransition;
if (type === 'objectValue') {
return tweenObjectValueSetter;
}
if (type === 'variable' && variablePath) {
const variable = object
.getVariables()
.getVariableFromPath(variablePath);
if (!variable) return () => {};
return getTweenVariableSetter(variable);
}
if (type === 'positionX') {
return getTweenObjectPositionXSetter(object);
}
if (type === 'positionY') {
return getTweenObjectPositionYSetter(object);
}
if (type === 'position') {
return getTweenObjectPositionSetter(object);
}
if (type === 'positionZ') {
return getTweenObjectPositionZSetter(object);
}
if (type === 'width') {
return getTweenObjectWidthSetter(object);
}
if (type === 'height') {
return getTweenObjectHeightSetter(object);
}
if (type === 'depth') {
if (!is3D(object)) return () => {};
return getTweenObjectDepthSetter(object);
}
if (type === 'angle') {
return getTweenObjectAngleSetter(object);
}
if (type === 'rotationX') {
if (!is3D(object)) return () => {};
return getTweenObjectRotationXSetter(object);
}
if (type === 'rotationY') {
if (!is3D(object)) return () => {};
return getTweenObjectRotationYSetter(object);
}
if (type === 'scale') {
if (!isScalable(object)) return () => {};
const object3d = is3D(object) ? object : null;
return getTweenObjectScaleSetter(
object,
object3d,
scaleFromCenterOfObject
);
}
if (type === 'scaleXY') {
if (!isScalable(object)) return () => {};
return getTweenObjectScaleXYSetter(object, scaleFromCenterOfObject);
}
if (type === 'scaleX') {
if (!isScalable(object)) return () => {};
return getTweenObjectScaleXSetter(object, scaleFromCenterOfObject);
}
if (type === 'scaleY') {
if (!isScalable(object)) return () => {};
return getTweenObjectScaleYSetter(object, scaleFromCenterOfObject);
}
if (type === 'opacity') {
if (!isOpaque(object)) return () => {};
return getTweenObjectOpacitySetter(object);
}
if (type === 'characterSize') {
if (!isCharacterScalable(object)) return () => {};
return getTweenObjectCharacterSizeSetter(object);
}
if (type === 'numberEffectProperty' && effectName && propertyName) {
const effect = object.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The object "${object.name}" doesn't have any effect called "${effectName}"`
);
}
return getTweenObjectNumberEffectPropertySetter(effect, propertyName);
}
if (type === 'colorEffectProperty' && effectName && propertyName) {
const effect = object.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The object "${object.name}" doesn't have any effect called "${effectName}"`
);
}
return getTweenObjectColorEffectPropertySetter(effect, propertyName);
}
if (type === 'objectColor') {
if (!isColorable(object)) return () => {};
return getTweenObjectColorSetter(object, useHSLColorTransition);
}
if (type === 'objectColorHSL') {
if (!isColorable(object)) return () => {};
return getTweenObjectColorHSLSetter(object);
}
return () => {};
};
export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior {
private _tweens = new gdjs.evtTools.tween.TweenManager();
private _isActive: boolean = true;
@@ -84,6 +392,43 @@ namespace gdjs {
return true;
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): TweenBehaviorNetworkSyncData {
return {
...super.getNetworkSyncData(syncOptions),
props: {
tweenManager: this._tweens.getNetworkSyncData(),
},
};
}
updateFromNetworkSyncData(
networkSyncData: TweenBehaviorNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.props.tweenManager) {
this._tweens.updateFromNetworkSyncData(
networkSyncData.props.tweenManager,
(tweenInformationNetworkSyncData) => {
return this.owner;
},
(tweenInformationNetworkSyncData) => {
return tweenSetterFactory(this.owner)(
tweenInformationNetworkSyncData
);
},
(tweenInformationNetworkSyncData) => {
return tweenInformationNetworkSyncData.destroyObjectWhenFinished
? () => this._deleteFromScene()
: null;
}
);
}
}
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._tweens.step();
}
@@ -126,7 +471,12 @@ namespace gdjs {
linearInterpolation,
fromValue,
toValue,
(value: float) => variable.setNumber(value),
getTweenVariableSetter(variable),
{
type: 'variable',
variable,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -208,7 +558,12 @@ namespace gdjs {
linearInterpolation,
variable.getValue() as number,
toValue,
(value: float) => variable.setNumber(value),
getTweenVariableSetter(variable),
{
type: 'variable',
variable,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -243,7 +598,11 @@ namespace gdjs {
: linearInterpolation,
fromValue,
toValue,
(value: float) => {},
tweenObjectValueSetter,
{
type: 'objectValue',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -322,7 +681,11 @@ namespace gdjs {
linearInterpolation,
[this.owner.getX(), this.owner.getY()],
[toX, toY],
([x, y]) => this.owner.setPosition(x, y),
getTweenObjectPositionSetter(this.owner),
{
type: 'position',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -394,7 +757,11 @@ namespace gdjs {
linearInterpolation,
this.owner.getX(),
toX,
(value: float) => this.owner.setX(value),
getTweenObjectPositionXSetter(this.owner),
{
type: 'positionX',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -466,7 +833,11 @@ namespace gdjs {
linearInterpolation,
this.owner.getY(),
toY,
(value: float) => this.owner.setY(value),
getTweenObjectPositionYSetter(this.owner),
{
type: 'positionY',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -543,7 +914,11 @@ namespace gdjs {
linearInterpolation,
owner.getZ(),
toZ,
(value: float) => owner.setZ(value),
getTweenObjectPositionZSetter(owner),
{
type: 'positionZ',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -615,7 +990,11 @@ namespace gdjs {
linearInterpolation,
this.owner.getAngle(),
toAngle,
(value: float) => this.owner.setAngle(value),
getTweenObjectAngleSetter(this.owner),
{
type: 'angle',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -648,7 +1027,11 @@ namespace gdjs {
linearInterpolation,
owner.getRotationX(),
toAngle,
(value: float) => owner.setRotationX(value),
getTweenObjectRotationXSetter(owner),
{
type: 'rotationX',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -681,7 +1064,11 @@ namespace gdjs {
linearInterpolation,
owner.getRotationY(),
toAngle,
(value: float) => owner.setRotationY(value),
getTweenObjectRotationYSetter(owner),
{
type: 'rotationY',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -769,19 +1156,6 @@ namespace gdjs {
if (toScaleX < 0) toScaleX = 0;
if (toScaleY < 0) toScaleY = 0;
const setValue = scaleFromCenterOfObject
? ([scaleX, scaleY]: float[]) => {
const oldX = owner.getCenterXInScene();
const oldY = owner.getCenterYInScene();
owner.setScaleX(scaleX);
owner.setScaleY(scaleY);
owner.setCenterPositionInScene(oldX, oldY);
}
: ([scaleX, scaleY]: float[]) => {
owner.setScaleX(scaleX);
owner.setScaleY(scaleY);
};
this._tweens.addMultiTween(
identifier,
timeSource,
@@ -790,7 +1164,11 @@ namespace gdjs {
interpolation,
[owner.getScaleX(), owner.getScaleY()],
[toScaleX, toScaleY],
setValue,
getTweenObjectScaleXYSetter(owner, scaleFromCenterOfObject),
{
type: 'scaleXY',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -830,20 +1208,6 @@ namespace gdjs {
// when the 3D extension is not used.
const owner3d = is3D(owner) ? owner : null;
const setValue = scaleFromCenterOfObject
? (scale: float) => {
const oldX = owner.getCenterXInScene();
const oldY = owner.getCenterYInScene();
const oldZ = owner3d ? owner3d.getCenterZInScene() : 0;
owner.setScale(scale);
owner.setCenterXInScene(oldX);
owner.setCenterYInScene(oldY);
if (owner3d) {
owner3d.setCenterZInScene(oldZ);
}
}
: (scale: float) => owner.setScale(scale);
this._tweens.addSimpleTween(
identifier,
this.owner,
@@ -852,7 +1216,12 @@ namespace gdjs {
exponentialInterpolation,
owner.getScale(),
toScale,
setValue,
getTweenObjectScaleSetter(owner, owner3d, scaleFromCenterOfObject),
{
type: 'scale',
scaleFromCenterOfObject,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -929,14 +1298,6 @@ namespace gdjs {
const owner = this.owner;
if (!isScalable(owner)) return;
const setValue = scaleFromCenterOfObject
? (scaleX: float) => {
const oldX = owner.getCenterXInScene();
owner.setScaleX(scaleX);
owner.setCenterXInScene(oldX);
}
: (scaleX: float) => owner.setScaleX(scaleX);
this._tweens.addSimpleTween(
identifier,
timeSource,
@@ -945,7 +1306,12 @@ namespace gdjs {
interpolation,
owner.getScaleX(),
toScaleX,
setValue,
getTweenObjectScaleXSetter(owner, scaleFromCenterOfObject),
{
type: 'scaleX',
scaleFromCenterOfObject,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1022,14 +1388,6 @@ namespace gdjs {
const owner = this.owner;
if (!isScalable(owner)) return;
const setValue = scaleFromCenterOfObject
? (scaleY: float) => {
const oldY = owner.getCenterYInScene();
owner.setScaleY(scaleY);
owner.setCenterYInScene(oldY);
}
: (scaleY: float) => owner.setScaleY(scaleY);
this._tweens.addSimpleTween(
identifier,
timeSource,
@@ -1038,7 +1396,12 @@ namespace gdjs {
interpolation,
owner.getScaleY(),
toScaleY,
setValue,
getTweenObjectScaleYSetter(owner, scaleFromCenterOfObject),
{
type: 'scaleY',
scaleFromCenterOfObject,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1113,7 +1476,11 @@ namespace gdjs {
linearInterpolation,
owner.getOpacity(),
toOpacity,
(value: float) => owner.setOpacity(value),
getTweenObjectOpacitySetter(owner),
{
type: 'opacity',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1153,10 +1520,12 @@ namespace gdjs {
linearInterpolation,
effect ? effect.getDoubleParameter(propertyName) : 0,
toValue,
(value: float) => {
if (effect) {
effect.updateDoubleParameter(propertyName, value);
}
getTweenObjectNumberEffectPropertySetter(effect, propertyName),
{
type: 'numberEffectProperty',
effectName,
propertyName,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
@@ -1210,22 +1579,12 @@ namespace gdjs {
rgbToColor[1],
rgbToColor[2]
),
([hue, saturation, lightness]) => {
if (effect) {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
}
getTweenObjectColorEffectPropertySetter(effect, propertyName),
{
type: 'colorEffectProperty',
effectName,
propertyName,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
@@ -1305,43 +1664,20 @@ namespace gdjs {
const rgbFromColor: float[] = gdjs.rgbOrHexToRGBColor(owner.getColor());
const rgbToColor: float[] = gdjs.rgbOrHexToRGBColor(toColorStr);
let initialValue;
let targetedValue;
let setValue;
if (useHSLColorTransition) {
initialValue = gdjs.evtTools.tween.rgbToHsl(
rgbFromColor[0],
rgbFromColor[1],
rgbFromColor[2]
);
targetedValue = gdjs.evtTools.tween.rgbToHsl(
rgbToColor[0],
rgbToColor[1],
rgbToColor[2]
);
setValue = ([hue, saturation, lightness]) => {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
owner.setColor(
Math.round(rgbFromHslColor[0]) +
';' +
Math.round(rgbFromHslColor[1]) +
';' +
Math.round(rgbFromHslColor[2])
);
};
} else {
initialValue = rgbFromColor;
targetedValue = rgbToColor;
setValue = ([red, green, blue]) => {
owner.setColor(
Math.floor(red) + ';' + Math.floor(green) + ';' + Math.floor(blue)
);
};
}
const initialValue = useHSLColorTransition
? gdjs.evtTools.tween.rgbToHsl(
rgbFromColor[0],
rgbFromColor[1],
rgbFromColor[2]
)
: rgbFromColor;
const targetedValue = useHSLColorTransition
? gdjs.evtTools.tween.rgbToHsl(
rgbToColor[0],
rgbToColor[1],
rgbToColor[2]
)
: rgbToColor;
this._tweens.addMultiTween(
identifier,
@@ -1351,7 +1687,12 @@ namespace gdjs {
linearInterpolation,
initialValue,
targetedValue,
setValue,
getTweenObjectColorSetter(owner, useHSLColorTransition),
{
type: 'objectColor',
useHSLColorTransition,
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1462,25 +1803,13 @@ namespace gdjs {
duration,
easing,
linearInterpolation,
hslFromColor,
[toH, toS, toL],
([hue, saturation, lightness]) => {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
owner.setColor(
Math.round(rgbFromHslColor[0]) +
';' +
Math.round(rgbFromHslColor[1]) +
';' +
Math.round(rgbFromHslColor[2])
);
getTweenObjectColorHSLSetter(owner),
{
type: 'objectColorHSL',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1558,7 +1887,11 @@ namespace gdjs {
interpolation,
owner.getCharacterSize(),
toSize,
(value: float) => owner.setCharacterSize(value),
getTweenObjectCharacterSizeSetter(owner),
{
type: 'characterSize',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1630,7 +1963,11 @@ namespace gdjs {
linearInterpolation,
this.owner.getWidth(),
toWidth,
(value: float) => this.owner.setWidth(value),
getTweenObjectWidthSetter(this.owner),
{
type: 'width',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1702,7 +2039,11 @@ namespace gdjs {
linearInterpolation,
this.owner.getHeight(),
toHeight,
(value: float) => this.owner.setHeight(value),
getTweenObjectHeightSetter(this.owner),
{
type: 'height',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}
@@ -1779,7 +2120,11 @@ namespace gdjs {
linearInterpolation,
owner.getDepth(),
toDepth,
(value: float) => owner.setDepth(value),
getTweenObjectDepthSetter(owner),
{
type: 'depth',
destroyObjectWhenFinished,
},
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
);
}

View File

@@ -10,6 +10,127 @@ namespace gdjs {
export namespace tween {
const logger = new gdjs.Logger('Tween');
const getTweenVariableSetter = (variable: gdjs.Variable) => {
return (value: float) => variable.setNumber(value);
};
const tweenLayoutValueSetter = (value: float) => {};
const tweenLayerValueSetter = (value: float) => {};
const getTweenLayerCameraPositionSetter = (layer: gdjs.RuntimeLayer) => {
return ([x, y]: Array<float>) => {
layer.setCameraX(x);
layer.setCameraY(y);
};
};
const getTweenLayerCameraRotationSetter = (layer: gdjs.RuntimeLayer) => {
return (value: float) => layer.setCameraRotation(value);
};
const getTweenLayerCameraZoomSetter = (layer: gdjs.RuntimeLayer) => {
return (value: float) => layer.setCameraZoom(value);
};
const getTweenNumberEffectPropertySetter = (
effect: PixiFiltersTools.Filter,
propertyName: string
) => {
return (value: float) => {
if (effect) {
effect.updateDoubleParameter(propertyName, value);
}
};
};
const getTweenColorEffectPropertySetter = (
effect: PixiFiltersTools.Filter,
propertyName: string
) => {
return ([hue, saturation, lightness]: Array<float>) => {
if (effect) {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
}
};
};
// Factory to get the tween setter based on type and options
export const tweenSetterFactory =
(runtimeScene: RuntimeScene) =>
(tweenInformation: TweenInformationNetworkSyncData) => {
const type = tweenInformation.type;
const layerName = tweenInformation.layerName;
const variablePath = tweenInformation.variablePath;
const effectName = tweenInformation.effectName;
const propertyName = tweenInformation.propertyName;
if (type === 'variable' && variablePath) {
const variable = runtimeScene
.getVariables()
.getVariableFromPath(variablePath);
if (!variable) {
return () => {};
}
return getTweenVariableSetter(variable);
}
if (type === 'cameraZoom' && layerName !== undefined) {
const layer = runtimeScene.getLayer(layerName);
return getTweenLayerCameraZoomSetter(layer);
}
if (type === 'cameraRotation' && layerName !== undefined) {
const layer = runtimeScene.getLayer(layerName);
return getTweenLayerCameraRotationSetter(layer);
}
if (type === 'cameraPosition' && layerName !== undefined) {
const layer = runtimeScene.getLayer(layerName);
return getTweenLayerCameraPositionSetter(layer);
}
if (
type === 'colorEffectProperty' &&
layerName !== undefined &&
effectName &&
propertyName
) {
const layer = runtimeScene.getLayer(layerName);
const effect = layer.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The layer "${layerName}" doesn't have any effect called "${effectName}"`
);
}
return getTweenColorEffectPropertySetter(effect, propertyName);
}
if (
type === 'numberEffectProperty' &&
layerName !== undefined &&
effectName &&
propertyName
) {
const layer = runtimeScene.getLayer(layerName);
const effect = layer.getRendererEffects()[effectName];
if (!effect) {
logger.error(
`The layer "${layerName}" doesn't have any effect called "${effectName}"`
);
}
return getTweenNumberEffectPropertySetter(effect, propertyName);
}
if (type === 'layoutValue') {
return tweenLayoutValueSetter;
}
if (type === 'layerValue') {
return tweenLayerValueSetter;
}
return () => {};
};
export const getTweensMap = (runtimeScene: RuntimeScene) =>
runtimeScene._tweens ||
(runtimeScene._tweens = new gdjs.evtTools.tween.TweenManager());
@@ -20,6 +141,44 @@ namespace gdjs {
gdjs.evtTools.tween.getTweensMap(runtimeScene).step();
});
gdjs.registerRuntimeSceneGetSyncDataCallback(
function (runtimeScene, currentLayoutSyncData, syncOptions) {
if (!syncOptions.syncTweens) return;
const tweensNetworkSyncData = gdjs.evtTools.tween
.getTweensMap(runtimeScene)
.getNetworkSyncData();
currentLayoutSyncData.tween = tweensNetworkSyncData;
}
);
gdjs.registerRuntimeSceneUpdateFromSyncDataCallback(
function (runtimeScene, receivedSyncData, syncOptions) {
if (!receivedSyncData.tween) return;
gdjs.evtTools.tween
.getTweensMap(runtimeScene)
.updateFromNetworkSyncData(
receivedSyncData.tween,
(tweenInformationNetworkSyncData) => {
if (tweenInformationNetworkSyncData.layerName !== undefined) {
return runtimeScene.getLayer(
tweenInformationNetworkSyncData.layerName
);
}
return runtimeScene;
},
(tweenInformationNetworkSyncData) => {
return gdjs.evtTools.tween.tweenSetterFactory(runtimeScene)(
tweenInformationNetworkSyncData
);
},
// No onFinish for scene tweens.
() => null
);
}
);
export const sceneTweenExists = (
runtimeScene: RuntimeScene,
id: string
@@ -130,7 +289,10 @@ namespace gdjs {
: linearInterpolation,
fromValue,
toValue,
(value: float) => {}
tweenLayoutValueSetter,
{
type: 'layoutValue',
}
);
};
@@ -167,7 +329,11 @@ namespace gdjs {
: linearInterpolation,
fromValue,
toValue,
(value: float) => {}
tweenLayerValueSetter,
{
type: 'layerValue',
layerName,
}
);
};
@@ -197,7 +363,11 @@ namespace gdjs {
linearInterpolation,
from,
to,
(value: float) => variable.setNumber(value)
getTweenVariableSetter(variable),
{
type: 'variable',
variable,
}
);
};
@@ -250,7 +420,11 @@ namespace gdjs {
linearInterpolation,
variable.getValue() as number,
toValue,
(value: float) => variable.setNumber(value)
getTweenVariableSetter(variable),
{
type: 'variable',
variable,
}
);
};
@@ -329,9 +503,10 @@ namespace gdjs {
linearInterpolation,
[layer.getCameraX(), layer.getCameraY()],
[toX, toY],
([x, y]) => {
layer.setCameraX(x);
layer.setCameraY(y);
getTweenLayerCameraPositionSetter(layer),
{
type: 'cameraPosition',
layerName,
}
);
};
@@ -408,7 +583,11 @@ namespace gdjs {
interpolation,
layer.getCameraZoom(),
toZoom,
(value: float) => layer.setCameraZoom(value)
getTweenLayerCameraZoomSetter(layer),
{
type: 'cameraZoom',
layerName,
}
);
};
@@ -478,7 +657,11 @@ namespace gdjs {
linearInterpolation,
layer.getCameraRotation(),
toRotation,
(value: float) => layer.setCameraRotation(value)
getTweenLayerCameraRotationSetter(layer),
{
type: 'cameraRotation',
layerName,
}
);
};
@@ -518,10 +701,12 @@ namespace gdjs {
linearInterpolation,
effect ? effect.getDoubleParameter(propertyName) : 0,
toValue,
(value: float) => {
if (effect) {
effect.updateDoubleParameter(propertyName, value);
}
getTweenNumberEffectPropertySetter(effect, propertyName),
{
type: 'numberEffectProperty',
layerName,
effectName,
propertyName,
}
);
};
@@ -575,22 +760,12 @@ namespace gdjs {
rgbToColor[1],
rgbToColor[2]
),
([hue, saturation, lightness]) => {
if (effect) {
const rgbFromHslColor = gdjs.evtTools.tween.hslToRgb(
hue,
saturation,
lightness
);
effect.updateColorParameter(
propertyName,
gdjs.rgbToHexNumber(
rgbFromHslColor[0],
rgbFromHslColor[1],
rgbFromHslColor[2]
)
);
}
getTweenColorEffectPropertySetter(effect, propertyName),
{
type: 'colorEffectProperty',
layerName,
effectName,
propertyName,
}
);
};

View File

@@ -16,7 +16,7 @@ namespace gdjs {
export type VideoObjectData = ObjectData & VideoObjectDataType;
export type VideoNetworkSyncDataType = {
export type VideoObjectNetworkSyncDataType = {
op: float;
// We don't sync volume, as it's probably a user setting?
pla: boolean;
@@ -25,8 +25,8 @@ namespace gdjs {
ps: number;
};
export type VideoNetworkSyncData = ObjectNetworkSyncData &
VideoNetworkSyncDataType;
export type VideoObjectNetworkSyncData = ObjectNetworkSyncData &
VideoObjectNetworkSyncDataType;
/**
* An object displaying a video on screen.
@@ -99,9 +99,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): VideoNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): VideoObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
op: this._opacity,
pla: this.isPlayed(),
loop: this.isLooped(),
@@ -110,8 +112,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(syncData: VideoNetworkSyncData): void {
super.updateFromNetworkSyncData(syncData);
updateFromNetworkSyncData(
syncData: VideoObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData, options);
if (this._opacity !== undefined && this._opacity && syncData.op) {
this.setOpacity(syncData.op);

View File

@@ -74,10 +74,15 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
codeGenerator.GetCodeNamespace() + ".localVariables = [];\n";
}
gd::String idToCallbackMapCode;
idToCallbackMapCode +=
codeGenerator.GetCodeNamespace() + ".idToCallbackMap = new Map();\n";
gd::String output =
// clang-format off
codeGenerator.GetCodeNamespace() + " = {};\n" +
localVariablesInitializationCode +
idToCallbackMapCode +
globalDeclarations +
globalObjectLists + "\n\n" +
codeGenerator.GetCustomCodeOutsideMain() + "\n\n" +
@@ -135,8 +140,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::VariablesContainer::SourceType::Parameters);
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForFreeEventsFunction(
project, eventsFunctionsExtension, eventsFunction,
parameterObjectsAndGroups, parameterVariablesContainer);
project,
eventsFunctionsExtension,
eventsFunction,
parameterObjectsAndGroups,
parameterVariablesContainer);
EventsCodeGenerator codeGenerator(projectScopedContainers);
codeGenerator.SetCodeNamespace(codeNamespace);
@@ -148,16 +156,21 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
// Generate the code setting up the context of the function.
gd::String fullPreludeCode = "let scopeInstanceContainer = null;\n" +
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
eventsFunctionsExtension,
eventsFunction,
"runtimeScene.getOnceTriggers()");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator,
codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsFunctionsExtension.GetEventsFunctions()),
0, true),
fullPreludeCode, eventsFunction.GetEvents(), "",
0,
true),
fullPreludeCode,
eventsFunction.GetEvents(),
"",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -193,9 +206,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::VariablesContainer::SourceType::Properties);
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForBehaviorEventsFunction(
project, eventsFunctionsExtension, eventsBasedBehavior,
eventsFunction, parameterObjectsContainers,
parameterVariablesContainer, propertyVariablesContainer);
project,
eventsFunctionsExtension,
eventsBasedBehavior,
eventsFunction,
parameterObjectsContainers,
parameterVariablesContainer,
propertyVariablesContainer);
EventsCodeGenerator codeGenerator(projectScopedContainers);
codeGenerator.SetCodeNamespace(codeNamespace);
@@ -209,8 +226,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the behavior.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
// TODO: this should be renamed to "instanceContainer" and have the code
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
// on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
"let scopeInstanceContainer = null;\n" +
// By convention of Behavior Events Function, the object is accessible
@@ -279,8 +297,12 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
gd::VariablesContainer::SourceType::Properties);
auto projectScopedContainers = gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForObjectEventsFunction(
project, eventsFunctionsExtension, eventsBasedObject, eventsFunction,
parameterObjectsContainers, parameterVariablesContainer,
project,
eventsFunctionsExtension,
eventsBasedObject,
eventsFunction,
parameterObjectsContainers,
parameterVariablesContainer,
propertyVariablesContainer);
EventsCodeGenerator codeGenerator(projectScopedContainers);
@@ -295,8 +317,9 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the object.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
// TODO: this should be renamed to "instanceContainer" and have the code
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
// on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
"let scopeInstanceContainer = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
@@ -388,13 +411,14 @@ gd::String EventsCodeGenerator::GenerateFreeEventsFunctionContext(
gd::String objectsGettersMap;
gd::String objectArraysMap;
gd::String behaviorNamesMap;
return GenerateEventsFunctionContext(eventsFunctionsExtension,
eventsFunctionsExtension.GetEventsFunctions(),
eventsFunction,
onceTriggersVariable,
objectsGettersMap,
objectArraysMap,
behaviorNamesMap);
return GenerateEventsFunctionContext(
eventsFunctionsExtension,
eventsFunctionsExtension.GetEventsFunctions(),
eventsFunction,
onceTriggersVariable,
objectsGettersMap,
objectArraysMap,
behaviorNamesMap);
}
gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
@@ -929,7 +953,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
gd::String actionCode;
// Prepare call
@@ -978,10 +1003,11 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
actionCode += " " + call + ";\n";
actionCode += "}\n";
if (!optionalAsyncCallbackName.empty()) {
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
actionCode +=
"runtimeScene.getAsyncTasksManager().addTask(asyncTaskGroup, " +
optionalAsyncCallbackName + ")\n}";
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
", asyncObjectsList)\n}";
}
return actionCode;
@@ -995,7 +1021,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
gd::String actionCode;
// Prepare call
@@ -1055,10 +1082,12 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
actionCode += " " + call + ";\n";
actionCode += "}\n";
if (!optionalAsyncCallbackName.empty()) {
if (!optionalAsyncCallbackName.empty() &&
!optionalAsyncCallbackId.empty()) {
actionCode +=
"runtimeScene.getAsyncTasksManager().addTask(asyncTaskGroup, " +
optionalAsyncCallbackName + ");\n };";
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
", asyncObjectsList);\n };";
}
}
@@ -1377,17 +1406,16 @@ gd::String EventsCodeGenerator::GenerateGetVariable(
scope == VARIABLE_OR_PROPERTY_OR_PARAMETER) {
const auto variablesContainersList =
GetProjectScopedContainers().GetVariablesContainersList();
const auto &variablesContainer =
const auto& variablesContainer =
scope == VARIABLE_OR_PROPERTY_OR_PARAMETER
? variablesContainersList.GetVariablesContainerFromVariableOrPropertyOrParameterName(
variableName)
? variablesContainersList
.GetVariablesContainerFromVariableOrPropertyOrParameterName(
variableName)
: scope == VARIABLE_OR_PROPERTY
? variablesContainersList
.GetVariablesContainerFromVariableOrPropertyName(
variableName)
: variablesContainersList
.GetVariablesContainerFromVariableNameOnly(
variableName);
.GetVariablesContainerFromVariableOrPropertyName(variableName)
: variablesContainersList.GetVariablesContainerFromVariableNameOnly(
variableName);
const auto sourceType = variablesContainer.GetSourceType();
if (sourceType == gd::VariablesContainer::SourceType::Scene) {
variables = &variablesContainer;
@@ -1410,48 +1438,50 @@ gd::String EventsCodeGenerator::GenerateGetVariable(
gd::VariablesContainer::SourceType::ExtensionScene) {
variables = &variablesContainer;
output = "eventsFunctionContext.sceneVariablesForExtension";
} else if (sourceType ==
gd::VariablesContainer::SourceType::Properties) {
} else if (sourceType == gd::VariablesContainer::SourceType::Properties) {
if (hasChild) {
// Properties with children are not supported.
return "gdjs.VariablesContainer.badVariablesContainer";
}
const auto &propertiesContainersList =
const auto& propertiesContainersList =
GetProjectScopedContainers().GetPropertiesContainersList();
const auto &propertiesContainerAndProperty =
const auto& propertiesContainerAndProperty =
propertiesContainersList.Get(variableName);
return GeneratePropertyGetterWithoutCasting(
propertiesContainerAndProperty.first,
propertiesContainerAndProperty.second);
} else if (sourceType ==
gd::VariablesContainer::SourceType::Parameters) {
} else if (sourceType == gd::VariablesContainer::SourceType::Parameters) {
if (hasChild) {
// Parameters with children are not supported.
return "gdjs.VariablesContainer.badVariablesContainer";
}
const auto &parametersVectorsList =
const auto& parametersVectorsList =
GetProjectScopedContainers().GetParametersVectorsList();
const auto &parameter =
const auto& parameter =
gd::ParameterMetadataTools::Get(parametersVectorsList, variableName);
return GenerateParameterGetterWithoutCasting(parameter);
}
} else if (scope == LAYOUT_VARIABLE) {
output = "runtimeScene.getScene().getVariables()";
const auto *legacySceneVariables = GetProjectScopedContainers().GetLegacySceneVariables();
const auto* legacySceneVariables =
GetProjectScopedContainers().GetLegacySceneVariables();
if (HasProjectAndLayout()) {
variables = &GetLayout().GetVariables();
} else if (legacySceneVariables && legacySceneVariables->Has(variableName)) {
} else if (legacySceneVariables &&
legacySceneVariables->Has(variableName)) {
variables = legacySceneVariables;
output = "eventsFunctionContext.sceneVariablesForExtension";
}
} else if (scope == PROJECT_VARIABLE) {
output = "runtimeScene.getGame().getVariables()";
const auto *legacyGlobalVariables = GetProjectScopedContainers().GetLegacyGlobalVariables();
const auto* legacyGlobalVariables =
GetProjectScopedContainers().GetLegacyGlobalVariables();
if (HasProjectAndLayout()) {
variables = &GetProject().GetVariables();
} else if (legacyGlobalVariables && legacyGlobalVariables->Has(variableName)) {
} else if (legacyGlobalVariables &&
legacyGlobalVariables->Has(variableName)) {
variables = legacyGlobalVariables;
output = "eventsFunctionContext.globalVariablesForExtension";
}
@@ -1543,9 +1573,9 @@ gd::String EventsCodeGenerator::GenerateProfilerSectionEnd(
}
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode) {
bool isLocalProperty =
projectScopedContainers.GetPropertiesContainersList()
.GetBottomMostPropertiesContainer() == &propertiesContainer;
@@ -1571,8 +1601,8 @@ gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
}
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property) {
bool isLocalProperty =
projectScopedContainers.GetPropertiesContainersList()
.GetBottomMostPropertiesContainer() == &propertiesContainer;
@@ -1639,7 +1669,7 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(
}
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata &parameter) {
const gd::ParameterMetadata& parameter) {
return "eventsFunctionContext.getArgument(" +
ConvertToStringExplicit(parameter.GetName()) + ")";
}

View File

@@ -59,7 +59,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* Generate JavaScript for executing events of an events based function.
*
* \param project Project used.
* \param eventsFunctionsExtension The container of the compiled event function.
* \param eventsFunctionsExtension The container of the compiled event
* function.
* \param eventsFunction The events function to be compiled.
* \param codeNamespace Where to store the context used by the function.
* \param includeFiles Will be filled with the necessary include files.
@@ -96,7 +97,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
*/
static gd::String GenerateBehaviorEventsFunctionCode(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
@@ -111,7 +112,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* function.
*
* \param project Project used.
* \param eventsBasedObject The object that contains the function to be compiled.
* \param eventsBasedObject The object that contains the function to be
* compiled.
* \param eventsFunction The events function to be compiled.
* \param codeNamespace Where to store the context used by the function.
* \param fullyQualifiedFunctionName The function name with its namespace.
@@ -129,7 +131,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
*/
static gd::String GenerateObjectEventsFunctionCode(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedObject& eventsBasedObject,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
@@ -152,7 +154,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* \return Code
*/
virtual gd::String GenerateEventsListCode(
gd::EventsList& events, gd::EventsCodeGenerationContext& context) override;
gd::EventsList& events,
gd::EventsCodeGenerationContext& context) override;
/**
* Generate code for executing a condition list
@@ -194,7 +197,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* \brief Get the full name for accessing to a list of objects
*/
virtual gd::String GetObjectListName(
const gd::String& name, const gd::EventsCodeGenerationContext& context) override;
const gd::String& name,
const gd::EventsCodeGenerationContext& context) override;
/**
* \brief Get the namespace to be used to store code generated
@@ -226,9 +230,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
};
virtual gd::String GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode) override;
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode) override;
protected:
virtual gd::String GenerateParameterCodes(
@@ -289,7 +293,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "") override;
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "") override;
virtual gd::String GenerateBehaviorAction(
const gd::String& objectName,
@@ -299,7 +304,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "") override;
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "") override;
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName) override;
@@ -320,9 +326,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gd::String expressionCode) override {
// This uses `getChild` which allows to access a child
// with a number (an index, for an array) or a string (for a structure).
// This could be optimised, if the type of the accessed variable AND the type of the index is known,
// so that `getChildAt` (for an array, with an index) or `getChildNamed` (for a structure, with a name)
// is used instead.
// This could be optimised, if the type of the accessed variable AND the
// type of the index is known, so that `getChildAt` (for an array, with an
// index) or `getChildNamed` (for a structure, with a name) is used instead.
return ".getChild(" + expressionCode + ")";
};
@@ -330,29 +336,33 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
return "gdjs.VariablesContainer.badVariable";
}
virtual gd::String GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property) override;
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property) override;
virtual gd::String GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata &parameter) override;
const gd::ParameterMetadata& parameter) override;
virtual gd::String GenerateBadObject() override { return "null"; }
virtual gd::String GenerateObject(const gd::String& objectName,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GenerateObject(
const gd::String& objectName,
const gd::String& type,
gd::EventsCodeGenerationContext& context) override;
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const override {
virtual gd::String GenerateNegatedPredicate(
const gd::String& predicate) const override {
return "!(" + predicate + ")";
};
@@ -362,13 +372,15 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
virtual gd::String GenerateAllInstancesGetterCode(
const gd::String& objectName, gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateProfilerSectionBegin(const gd::String& section) override;
virtual gd::String GenerateProfilerSectionEnd(const gd::String& section) override;
virtual gd::String GenerateProfilerSectionBegin(
const gd::String& section) override;
virtual gd::String GenerateProfilerSectionEnd(
const gd::String& section) override;
virtual gd::String GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) override;
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) override;
private:
static gd::String GenerateEventsListCompleteFunctionCode(
@@ -415,8 +427,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* arguments from the rest of the events.
*/
gd::String GenerateFreeEventsFunctionContext(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsFunction &eventsFunction,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunction& eventsFunction,
const gd::String& onceTriggersVariable);
/**
@@ -425,9 +437,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* arguments from the rest of the events.
*/
gd::String GenerateBehaviorEventsFunctionContext(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction &eventsFunction,
const gd::EventsFunction& eventsFunction,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName,
const gd::String& thisBehaviorName);
@@ -438,9 +450,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* arguments from the rest of the events.
*/
gd::String GenerateObjectEventsFunctionContext(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedObject& eventsBasedObject,
const gd::EventsFunction &eventsFunction,
const gd::EventsFunction& eventsFunction,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName);
@@ -455,7 +467,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
/**
* \brief Construct a code generator for the specified containers.
*/
EventsCodeGenerator(const gd::ProjectScopedContainers& projectScopedContainers);
EventsCodeGenerator(
const gd::ProjectScopedContainers& projectScopedContainers);
virtual ~EventsCodeGenerator();
gd::String codeNamespace; ///< Optional namespace for the generated code,
@@ -467,16 +480,16 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* to provides access objects, object creation and access to arguments from
* the rest of the events.
*/
gd::String GenerateEventsFunctionContext(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsFunctionsContainer &eventsFunctionsContainer,
const gd::EventsFunction &eventsFunction,
const gd::String &onceTriggersVariable,
gd::String &objectsGettersMap,
gd::String &objectArraysMap,
gd::String &behaviorNamesMap,
const gd::String &thisObjectName = "",
const gd::String &thisBehaviorName = "");
gd::String GenerateEventsFunctionContext(
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunctionsContainer& eventsFunctionsContainer,
const gd::EventsFunction& eventsFunction,
const gd::String& onceTriggersVariable,
gd::String& objectsGettersMap,
gd::String& objectArraysMap,
gd::String& behaviorNamesMap,
const gd::String& thisObjectName = "",
const gd::String& thisBehaviorName = "");
};
} // namespace gdjs

View File

@@ -22,9 +22,11 @@ AsyncExtension::AsyncExtension() {
gd::AsyncEvent &event = dynamic_cast<gd::AsyncEvent &>(event_);
// Generate callback code
const auto callbackDescriptor = codeGenerator.GenerateCallback(
const auto callbackId =
gd::String::From(codeGenerator.GenerateSingleUsageUniqueIdFor(
event.GetInstruction().GetOriginalInstruction().lock().get())),
event.GetInstruction().GetOriginalInstruction().lock().get()));
const auto callbackDescriptor = codeGenerator.GenerateCallback(
callbackId,
parentContext,
event.GetActions(),
event.HasSubEvents() ? &event.GetSubEvents() : nullptr);
@@ -33,10 +35,6 @@ AsyncExtension::AsyncExtension() {
"(runtimeScene) => (" + callbackDescriptor.functionName + "(" +
callbackDescriptor.argumentsList + "))";
// Generate the action and store the generated task.
const gd::String asyncActionCode = codeGenerator.GenerateActionCode(
event.GetInstruction(), parentContext, callbackCallCode);
// Generate code to backup the objects lists.
// Do it after generating the code of the action so that it uses the
// same object list as used in the action.
@@ -70,6 +68,13 @@ AsyncExtension::AsyncExtension() {
", obj);\n";
}
// Generate the action and store the generated task.
const gd::String asyncActionCode =
codeGenerator.GenerateActionCode(event.GetInstruction(),
parentContext,
callbackCallCode,
callbackId);
return "{\n" + parentAsyncObjectsListGetter + "{\n" +
asyncContextBuilder + asyncActionCode + "}\n" + "}\n";
});

View File

@@ -844,6 +844,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "CustomRuntimeObjectInstanceContainer.js");
InsertUnique(includesFiles, "CustomRuntimeObject.js");
InsertUnique(includesFiles, "CustomRuntimeObject2D.js");
InsertUnique(includesFiles, "indexeddb.js");
// Common includes for events only.
InsertUnique(includesFiles, "events-tools/commontools.js");

View File

@@ -4,6 +4,33 @@
* This project is released under the MIT License.
*/
namespace gdjs {
export type WaitTaskNetworkSyncData = {
type: 'wait';
duration: float;
timeElapsedOnScene: float;
};
export type ResolveTaskNetworkSyncData = null;
export type PromiseTaskNetworkSyncData = null;
export type ManuallyResolvableTaskNetworkSyncData = null;
export type TaskGroupNetworkSyncData = {
type: 'group';
tasks: AsyncTaskNetworkSyncData[];
};
export type AsyncTaskNetworkSyncData =
| WaitTaskNetworkSyncData
| TaskGroupNetworkSyncData
| PromiseTaskNetworkSyncData
| ManuallyResolvableTaskNetworkSyncData
| ResolveTaskNetworkSyncData;
export type AsyncTasksManagerNetworkSyncData = {
tasks: Array<{
callbackId: string;
asyncTask: AsyncTaskNetworkSyncData;
objectsList: gdjs.LongLivedObjectsListNetworkSyncData;
}>;
};
/**
* This stores all asynchronous tasks waiting to be completed,
* for a given scene.
@@ -15,7 +42,12 @@ namespace gdjs {
*/
private tasksWithCallback = new Array<{
asyncTask: AsyncTask;
callback: (runtimeScene: gdjs.RuntimeScene) => void;
callback: (
runtimeScene: gdjs.RuntimeScene,
longLivedObjectsList: gdjs.LongLivedObjectsList
) => void;
callbackId: string;
longLivedObjectsList: gdjs.LongLivedObjectsList;
}>();
/**
@@ -26,7 +58,10 @@ namespace gdjs {
const taskWithCallback = this.tasksWithCallback[i];
if (taskWithCallback.asyncTask.update(runtimeScene)) {
// The task has finished, run the callback and remove it.
taskWithCallback.callback(runtimeScene);
taskWithCallback.callback(
runtimeScene,
taskWithCallback.longLivedObjectsList
);
this.tasksWithCallback.splice(i--, 1);
}
}
@@ -39,9 +74,19 @@ namespace gdjs {
*/
addTask(
asyncTask: AsyncTask,
callback: (runtimeScene: RuntimeScene) => void
callback: (
runtimeScene: RuntimeScene,
longLivedObjectsList: gdjs.LongLivedObjectsList
) => void,
callbackId: string,
longLivedObjectsList: gdjs.LongLivedObjectsList
): void {
this.tasksWithCallback.push({ asyncTask, callback });
this.tasksWithCallback.push({
asyncTask,
callback,
callbackId,
longLivedObjectsList,
});
}
/**
@@ -51,6 +96,77 @@ namespace gdjs {
clearTasks() {
this.tasksWithCallback.length = 0;
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): AsyncTasksManagerNetworkSyncData {
const tasksData = this.tasksWithCallback.map(
({ asyncTask, callbackId, longLivedObjectsList }) => {
return {
callbackId,
asyncTask: asyncTask.getNetworkSyncData(),
objectsList: longLivedObjectsList.getNetworkSyncData(syncOptions),
};
}
);
return {
tasks: tasksData,
};
}
updateFromNetworkSyncData(
syncData: AsyncTasksManagerNetworkSyncData,
idToCallbackMap: Map<
string,
(
runtimeScene: gdjs.RuntimeScene,
asyncObjectsList: gdjs.LongLivedObjectsList
) => void
>,
runtimeScene: gdjs.RuntimeScene,
syncOptions: UpdateFromNetworkSyncDataOptions
) {
this.clearTasks();
const unknownTaskTypes: string[] = [];
syncData.tasks.forEach(({ callbackId, asyncTask, objectsList }) => {
if (!asyncTask) return;
const callback = idToCallbackMap.get(callbackId);
if (callback) {
const longLivedObjectsList = new gdjs.LongLivedObjectsList();
longLivedObjectsList.updateFromNetworkSyncData(
objectsList,
runtimeScene,
syncOptions
);
if (asyncTask.type === 'group') {
const task = new TaskGroup();
task.updateFromNetworkSyncData(asyncTask);
this.addTask(task, callback, callbackId, longLivedObjectsList);
} else if (asyncTask.type === 'wait') {
const task = new gdjs.evtTools.runtimeScene.WaitTask(
asyncTask.duration
);
task.updateFromNetworkSyncData(asyncTask);
this.addTask(task, callback, callbackId, longLivedObjectsList);
} else {
// Unknown task type.
// @ts-ignore
unknownTaskTypes.push(asyncTask.type);
}
}
});
if (unknownTaskTypes.length) {
console.warn(
`${unknownTaskTypes.length} asynchronous task(s) could not be restored from network sync data. ${unknownTaskTypes.join(', ')}`
);
}
}
}
/**
@@ -63,6 +179,12 @@ namespace gdjs {
* @return True if the task is finished, false if it needs to continue running.
*/
abstract update(runtimeScene: RuntimeScene): boolean;
abstract getNetworkSyncData(): AsyncTaskNetworkSyncData;
abstract updateFromNetworkSyncData(
syncData: AsyncTaskNetworkSyncData
): void;
}
export class TaskGroup extends AsyncTask {
@@ -80,12 +202,54 @@ namespace gdjs {
return this.tasks.length === 0;
}
getNetworkSyncData(): TaskGroupNetworkSyncData {
return {
type: 'group',
tasks: this.tasks.map((task) => task.getNetworkSyncData()),
};
}
updateFromNetworkSyncData(syncData: TaskGroupNetworkSyncData) {
const unknownTaskTypes: string[] = [];
syncData.tasks.forEach((asyncTask) => {
if (!asyncTask) return;
if (asyncTask.type === 'group') {
const task = new TaskGroup();
task.updateFromNetworkSyncData(asyncTask);
this.addTask(task);
} else if (asyncTask.type === 'wait') {
const task = new gdjs.evtTools.runtimeScene.WaitTask(
asyncTask.duration
);
task.updateFromNetworkSyncData(asyncTask);
this.addTask(task);
} else {
// Unknown task type.
// @ts-ignore
unknownTaskTypes.push(asyncTask.type);
}
});
if (unknownTaskTypes.length) {
console.warn(
`${unknownTaskTypes.length} asynchronous task(s) could not be restored from network sync data. ${unknownTaskTypes.join(', ')}`
);
}
}
}
export class ResolveTask extends AsyncTask {
update() {
return true;
}
getNetworkSyncData(): AsyncTaskNetworkSyncData {
return null;
}
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
}
const logger = new gdjs.Logger('Internal PromiseTask');
@@ -121,6 +285,12 @@ ${error ? 'The following error was thrown: ' + error : ''}`
update() {
return this.isResolved;
}
getNetworkSyncData(): AsyncTaskNetworkSyncData {
return null;
}
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
}
export class ManuallyResolvableTask extends AsyncTask {
@@ -133,5 +303,11 @@ ${error ? 'The following error was thrown: ' + error : ''}`
update(): boolean {
return this.isResolved;
}
getNetworkSyncData(): AsyncTaskNetworkSyncData {
return null;
}
updateFromNetworkSyncData(syncData: AsyncTaskNetworkSyncData): void {}
}
}

View File

@@ -17,11 +17,19 @@ namespace gdjs {
isInnerAreaFollowingParentSize: boolean;
};
export type CustomObjectNetworkSyncDataType = ObjectNetworkSyncData & {
export type CustomObjectNetworkSyncDataType = {
anim?: SpriteAnimatorNetworkSyncData;
ifx: boolean;
ify: boolean;
sx: float;
sy: float;
op: float;
cc?: [float, float];
};
export type CustomObjectNetworkSyncData = ObjectNetworkSyncData &
CustomObjectNetworkSyncDataType;
/**
* An object that contains other object.
*
@@ -221,24 +229,66 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): CustomObjectNetworkSyncDataType {
return {
...super.getNetworkSyncData(),
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): CustomObjectNetworkSyncData {
const animator = this.getAnimator();
const networkSyncData: CustomObjectNetworkSyncData = {
...super.getNetworkSyncData(syncOptions),
ifx: this.isFlippedX(),
ify: this.isFlippedY(),
sx: this._scaleX,
sy: this._scaleY,
op: this.opacity,
};
if (animator) {
networkSyncData.anim = animator.getNetworkSyncData();
}
if (this._customCenter) {
networkSyncData.cc = this._customCenter;
}
return networkSyncData;
}
updateFromNetworkSyncData(
networkSyncData: CustomObjectNetworkSyncDataType
networkSyncData: CustomObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.ifx !== undefined) {
this.flipX(networkSyncData.ifx);
}
if (networkSyncData.ify !== undefined) {
this.flipY(networkSyncData.ify);
}
if (networkSyncData.sx !== undefined) {
this.setScaleX(Math.abs(networkSyncData.sx));
}
if (networkSyncData.sy !== undefined) {
this.setScaleY(Math.abs(networkSyncData.sy));
}
if (networkSyncData.op !== undefined) {
this.setOpacity(networkSyncData.op);
}
if (networkSyncData.anim) {
const animator = this.getAnimator();
if (animator) {
animator.updateFromNetworkSyncData(networkSyncData.anim);
}
}
if (networkSyncData.cc) {
this.setRotationCenter(networkSyncData.cc[0], networkSyncData.cc[1]);
}
if (
networkSyncData.ifx !== undefined ||
networkSyncData.ify !== undefined ||
networkSyncData.sx !== undefined ||
networkSyncData.sy !== undefined ||
networkSyncData.anim !== undefined ||
networkSyncData.cc !== undefined
) {
this.onChildrenLocationChanged();
}
}
override extraInitializationFromInitialInstance(

View File

@@ -623,7 +623,7 @@ namespace gdjs {
/**
* Create a new object from its name. The object is also added to the instances
* living in the container (No need to call {@link gdjs.RuntimeScene.addObject})
* living in the container (No need to call {@link addObject})
* @param objectName The name of the object to be created
* @return The created object
*/

View File

@@ -116,6 +116,101 @@ namespace gdjs {
}
}
getNetworkSyncData(): LayerNetworkSyncData {
const effectsNetworkSyncData = {};
for (const effectName in this._rendererEffects) {
effectsNetworkSyncData[effectName] =
this._rendererEffects[effectName].getNetworkSyncData();
}
return {
timeScale: this._timeScale,
defaultZOrder: this._defaultZOrder,
hidden: this._hidden,
effects: effectsNetworkSyncData,
followBaseLayerCamera: this._followBaseLayerCamera,
clearColor: this._clearColor,
cameraX: this.getCameraX(),
cameraY: this.getCameraY(),
cameraZ: this.getCameraZ(null),
cameraRotation: this.getCameraRotation(),
cameraZoom: this.getCameraZoom(),
};
}
updateFromNetworkSyncData(networkSyncData: LayerNetworkSyncData): void {
if (
networkSyncData.timeScale !== undefined &&
networkSyncData.timeScale !== this._timeScale
) {
this.setTimeScale(networkSyncData.timeScale);
}
if (
networkSyncData.defaultZOrder !== undefined &&
networkSyncData.defaultZOrder !== this._defaultZOrder
) {
this.setDefaultZOrder(networkSyncData.defaultZOrder);
}
if (
this._hidden != undefined &&
this._hidden !== networkSyncData.hidden
) {
this.show(!networkSyncData.hidden);
}
if (
networkSyncData.followBaseLayerCamera !== undefined &&
networkSyncData.followBaseLayerCamera !== this._followBaseLayerCamera
) {
this.setFollowBaseLayerCamera(networkSyncData.followBaseLayerCamera);
}
if (
networkSyncData.clearColor !== undefined &&
networkSyncData.clearColor !== this._clearColor
) {
this.setClearColor(
networkSyncData.clearColor[0] * 255,
networkSyncData.clearColor[1] * 255,
networkSyncData.clearColor[2] * 255
);
}
if (
networkSyncData.cameraX !== undefined &&
networkSyncData.cameraX !== this.getCameraX()
) {
this.setCameraX(networkSyncData.cameraX);
}
if (
networkSyncData.cameraY !== undefined &&
networkSyncData.cameraY !== this.getCameraY()
) {
this.setCameraY(networkSyncData.cameraY);
}
if (
networkSyncData.cameraZ !== undefined &&
networkSyncData.cameraZ !== this.getCameraZ(null)
) {
this.setCameraZ(networkSyncData.cameraZ, null);
}
if (
networkSyncData.cameraRotation !== undefined &&
networkSyncData.cameraRotation !== this.getCameraRotation()
) {
this.setCameraRotation(networkSyncData.cameraRotation);
}
if (
networkSyncData.cameraZoom !== undefined &&
networkSyncData.cameraZoom !== this.getCameraZoom()
) {
this.setCameraZoom(networkSyncData.cameraZoom);
}
for (const effectName in networkSyncData.effects) {
this._rendererEffects[effectName].updateFromNetworkSyncData(
networkSyncData.effects[effectName]
);
}
}
getRuntimeLayer(): gdjs.RuntimeLayer {
return this;
}

View File

@@ -622,6 +622,14 @@ namespace gdjs {
}
}
const logger = new gdjs.Logger('LongLivedObjectsLists');
export type LongLivedObjectsListNetworkSyncData = {
objectsLists: {
[objectName: string]: Array<string>;
};
localVariablesContainers: Array<Array<VariableNetworkSyncData>>;
};
/**
* A container for objects lists that should last more than the current frame.
* It automatically removes objects that were destroyed from the objects lists.
@@ -695,5 +703,85 @@ namespace gdjs {
): void {
gdjs.copyArray(variablesContainers, this.localVariablesContainers);
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): LongLivedObjectsListNetworkSyncData {
const objectsLists: {
[objectName: string]: Array<string>;
} = {};
for (const [objectName, runtimeObjects] of this.objectsLists.entries()) {
const objectNetworkIds: Array<string> = [];
for (const runtimeObject of runtimeObjects) {
const objectNetworkId = runtimeObject.getNetworkId();
if (!objectNetworkId) {
logger.warn(
'Tried to get sync data of a LongLivedObjectsList and found an object without a network ID'
);
continue;
}
objectNetworkIds.push(objectNetworkId);
}
objectsLists[objectName] = objectNetworkIds;
}
return {
objectsLists,
localVariablesContainers: this.localVariablesContainers.map(
(container) => container.getNetworkSyncData(syncOptions)
),
};
}
updateFromNetworkSyncData(
syncData: LongLivedObjectsListNetworkSyncData,
runtimeScene: gdjs.RuntimeScene,
syncOptions: UpdateFromNetworkSyncDataOptions
) {
const { objectsLists, localVariablesContainers } = syncData;
// Clear the current state.
this.objectsLists.clear();
this.localVariablesContainers.length = 0;
// Restore the list of objects.
for (const [objectName, objectNetworkIds] of Object.entries(
objectsLists
)) {
const runtimeObjects = runtimeScene.getObjects(objectName);
if (!runtimeObjects) {
logger.warn(
'Tried to update sync data of a LongLivedObjectsList but cannot find objects with name: ' +
objectName
);
continue;
}
const runtimeObjectsFromSyncData = runtimeObjects.filter(
(runtimeObject) => {
const runtimeObjectNetworkId = runtimeObject.getNetworkId();
return (
!!runtimeObjectNetworkId &&
objectNetworkIds.includes(runtimeObjectNetworkId)
);
}
);
for (const runtimeObject of runtimeObjectsFromSyncData) {
this.addObject(objectName, runtimeObject);
}
}
// Restore the local variables containers.
this.localVariablesContainers = localVariablesContainers.map(
(localVariablesContainer) => {
const newContainer = new gdjs.VariablesContainer();
newContainer.updateFromNetworkSyncData(
localVariablesContainer,
syncOptions
);
return newContainer;
}
);
}
}
}

View File

@@ -143,6 +143,19 @@ namespace gdjs {
.getElapsedTime();
return this.timeElapsedOnScene >= this.duration;
}
getNetworkSyncData(): WaitTaskNetworkSyncData {
return {
type: 'wait',
duration: this.duration,
timeElapsedOnScene: this.timeElapsedOnScene,
};
}
updateFromNetworkSyncData(syncData: WaitTaskNetworkSyncData): void {
this.duration = syncData.duration;
this.timeElapsedOnScene = syncData.timeElapsedOnScene;
}
}
export const wait = (durationInSeconds: float): AsyncTask =>

View File

@@ -32,6 +32,16 @@ namespace gdjs {
instanceContainer: gdjs.RuntimeInstanceContainer,
runtimeObject: gdjs.RuntimeObject
) => void;
type RuntimeSceneGetSyncDataCallback = (
runtimeScene: gdjs.RuntimeScene,
currentSyncData: LayoutNetworkSyncData,
syncOptions: GetNetworkSyncDataOptions
) => void;
type RuntimeSceneUpdateFromSyncData = (
runtimeScene: gdjs.RuntimeScene,
receivedSyncData: LayoutNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) => void;
export const callbacksFirstRuntimeSceneLoaded: Array<RuntimeSceneCallback> =
[];
@@ -45,6 +55,10 @@ namespace gdjs {
export const callbacksRuntimeSceneUnloaded: Array<RuntimeSceneCallback> = [];
export const callbacksObjectDeletedFromScene: Array<RuntimeSceneRuntimeObjectCallback> =
[];
export const callbacksRuntimeSceneGetSyncData: Array<RuntimeSceneGetSyncDataCallback> =
[];
export const callbacksRuntimeSceneUpdateFromSyncData: Array<RuntimeSceneUpdateFromSyncData> =
[];
/** Base64 encoded logo of GDevelop for the splash screen. */
export let gdevelopLogo: string = '';
@@ -397,6 +411,28 @@ namespace gdjs {
gdjs.callbacksObjectDeletedFromScene.push(callback);
};
/**
* Register a function to be called each time a scene is getting its sync
* data retrieved (via getNetworkSyncData).
* @param callback The function to be called.
*/
export const registerRuntimeSceneGetSyncDataCallback = function (
callback: RuntimeSceneGetSyncDataCallback
): void {
gdjs.callbacksRuntimeSceneGetSyncData.push(callback);
};
/**
* Register a function to be called each time a scene is getting its sync
* data updated (via updateFromNetworkSyncData).
* @param callback The function to be called.
*/
export const registerRuntimeSceneUpdateFromSyncDataCallback = function (
callback: RuntimeSceneUpdateFromSyncData
): void {
gdjs.callbacksRuntimeSceneUpdateFromSyncData.push(callback);
};
/**
* Unregister a callback.
* This should not be used apart from the code generated from extensions

View File

@@ -84,11 +84,23 @@ namespace gdjs {
*/
private _onPlay: Array<HowlCallback> = [];
constructor(howl: Howl, volume: float, loop: boolean, rate: float) {
/**
* The filepath to the resource
*/
private _audioResourceName: string;
constructor(
howl: Howl,
volume: float,
loop: boolean,
rate: float,
audioResourceName: string
) {
this._howl = howl;
this._initialVolume = clampVolume(volume);
this._loop = loop;
this._rate = rate;
this._audioResourceName = audioResourceName;
}
/**
@@ -357,10 +369,27 @@ namespace gdjs {
if (this._id !== null) this._howl.off(event, handler, this._id);
return this;
}
getNetworkSyncData(): SoundSyncData | undefined {
if (this.stopped()) return undefined;
// Seek can sometimes return the Howl object in case it isn't loaded yet, in this case we default to 0.
const seek = this.getSeek();
const numberSeek = typeof seek !== 'number' ? 0 : seek;
// If the Howl is still loading, we use the initialVolume, as the Howl
// has been initialized with volume 0.
const volume = this.isLoaded() ? this.getVolume() : this._initialVolume;
return {
resourceName: this._audioResourceName,
loop: this._loop,
volume,
rate: this._rate,
seek: numberSeek,
};
}
}
/**
* HowlerSoundManager is used to manage the sounds and musics of a RuntimeScene.
* HowlerSoundManager is used to manage the sounds and musics of a RuntimeGame.
*
* It is basically a container to associate channels to sounds and keep a list
* of all sounds being played.
@@ -609,8 +638,7 @@ namespace gdjs {
);
cacheContainer.set(resource, howl);
}
return new gdjs.HowlerSound(howl, volume, loop, rate);
return new gdjs.HowlerSound(howl, volume, loop, rate, soundName);
}
/**
@@ -708,7 +736,13 @@ namespace gdjs {
this._loadedSounds.clear();
}
playSound(soundName: string, loop: boolean, volume: float, pitch: float) {
playSound(
soundName: string,
loop: boolean,
volume: float,
pitch: float,
seek?: float
) {
const sound = this.createHowlerSound(
soundName,
/* isMusic= */ false,
@@ -724,6 +758,9 @@ namespace gdjs {
}
});
sound.play();
if (seek) {
sound.setSeek(seek);
}
}
playSoundOnChannel(
@@ -731,7 +768,8 @@ namespace gdjs {
channel: integer,
loop: boolean,
volume: float,
pitch: float
pitch: float,
seek?: float
) {
if (this._sounds[channel]) this._sounds[channel].stop();
@@ -756,13 +794,22 @@ namespace gdjs {
}
});
sound.play();
if (seek) {
sound.setSeek(seek);
}
}
getSoundOnChannel(channel: integer): HowlerSound | null {
return this._sounds[channel] || null;
}
playMusic(soundName: string, loop: boolean, volume: float, pitch: float) {
playMusic(
soundName: string,
loop: boolean,
volume: float,
pitch: float,
seek?: float
) {
const music = this.createHowlerSound(
soundName,
/* isMusic= */ true,
@@ -778,6 +825,9 @@ namespace gdjs {
}
});
music.play();
if (seek) {
music.setSeek(seek);
}
}
playMusicOnChannel(
@@ -785,7 +835,8 @@ namespace gdjs {
channel: integer,
loop: boolean,
volume: float,
pitch: float
pitch: float,
seek?: float
) {
if (this._musics[channel]) this._musics[channel].stop();
@@ -805,6 +856,9 @@ namespace gdjs {
}
});
music.play();
if (seek) {
music.setSeek(seek);
}
}
getMusicOnChannel(channel: integer): HowlerSound | null {
@@ -932,6 +986,97 @@ namespace gdjs {
}
}
getNetworkSyncData(): SoundManagerSyncData {
const freeMusicsNetworkSyncData: SoundSyncData[] = [];
this._freeMusics.forEach((freeMusic) => {
const musicSyncData = freeMusic.getNetworkSyncData();
if (musicSyncData) freeMusicsNetworkSyncData.push(musicSyncData);
});
const freeSoundsNetworkSyncData: SoundSyncData[] = [];
this._freeSounds.forEach((freeSound) => {
const soundSyncData = freeSound.getNetworkSyncData();
if (soundSyncData) freeSoundsNetworkSyncData.push(soundSyncData);
});
const musicsNetworkSyncData: ChannelsSoundSyncData = {};
Object.entries(this._musics).forEach(([channel, music]) => {
const musicSyncData = music.getNetworkSyncData();
if (musicSyncData) {
const channelNumber = parseInt(channel, 10);
musicsNetworkSyncData[channelNumber] = musicSyncData;
}
});
const soundsNetworkSyncData: ChannelsSoundSyncData = {};
Object.entries(this._sounds).forEach(([channel, sound]) => {
const soundSyncData = sound.getNetworkSyncData();
if (soundSyncData) {
const channelNumber = parseInt(channel, 10);
soundsNetworkSyncData[channelNumber] = soundSyncData;
}
});
return {
globalVolume: this._globalVolume,
cachedSpatialPosition: this._cachedSpatialPosition,
freeMusics: freeMusicsNetworkSyncData,
freeSounds: freeSoundsNetworkSyncData,
musics: musicsNetworkSyncData,
sounds: soundsNetworkSyncData,
};
}
updateFromNetworkSyncData(syncData: SoundManagerSyncData): void {
this.clearAll();
this._globalVolume = syncData.globalVolume;
this._cachedSpatialPosition = syncData.cachedSpatialPosition;
for (let i = 0; i < syncData.freeSounds.length; i++) {
const freeSoundsSyncData: SoundSyncData = syncData.freeSounds[i];
this.playSound(
freeSoundsSyncData.resourceName,
freeSoundsSyncData.loop,
freeSoundsSyncData.volume * 100,
freeSoundsSyncData.rate,
freeSoundsSyncData.seek
);
}
for (let i = 0; i < syncData.freeMusics.length; i++) {
const freeMusicsSyncData: SoundSyncData = syncData.freeMusics[i];
this.playMusic(
freeMusicsSyncData.resourceName,
freeMusicsSyncData.loop,
freeMusicsSyncData.volume * 100,
freeMusicsSyncData.rate,
freeMusicsSyncData.seek
);
}
for (const [channel, soundSyncData] of Object.entries(syncData.sounds)) {
const channelNumber = parseInt(channel, 10);
this.playSoundOnChannel(
soundSyncData.resourceName,
channelNumber,
soundSyncData.loop,
soundSyncData.volume * 100,
soundSyncData.rate,
soundSyncData.seek
);
}
for (const [channel, musicSyncData] of Object.entries(syncData.musics)) {
const channelNumber = parseInt(channel, 10);
this.playMusicOnChannel(
musicSyncData.resourceName,
channelNumber,
musicSyncData.loop,
musicSyncData.volume * 100,
musicSyncData.rate,
musicSyncData.seek
);
}
}
/**
* To be called when the game is disposed.
* Unloads all audio from memory, clear Howl cache and stop all audio.

105
GDJS/Runtime/indexeddb.ts Normal file
View File

@@ -0,0 +1,105 @@
/*
* GDevelop JS Platform
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
export namespace indexedDb {
export const loadFromIndexedDB = async function (
dbName: string,
objectStoreName: string,
key: string
): Promise<any> {
return new Promise((resolve, reject) => {
try {
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = function () {
const db = request.result;
if (!db.objectStoreNames.contains(objectStoreName)) {
db.createObjectStore(objectStoreName);
}
};
request.onsuccess = function () {
const db = request.result;
const tx = db.transaction(objectStoreName, 'readonly');
const store = tx.objectStore(objectStoreName);
const getRequest = store.get(key);
getRequest.onsuccess = function () {
if (getRequest.result !== undefined) {
resolve(getRequest.result);
} else {
resolve(null);
}
};
getRequest.onerror = function () {
console.error(
'Error loading data from IndexedDB:',
getRequest.error
);
reject(getRequest.error);
};
};
request.onerror = function () {
console.error('Error opening IndexedDB:', request.error);
reject(request.error);
};
} catch (err) {
console.error('Exception thrown while opening IndexedDB:', err);
reject(err);
return;
}
});
};
export const saveToIndexedDB = async function (
dbName: string,
objectStoreName: string,
key: string,
data: any
): Promise<void> {
return new Promise((resolve, reject) => {
try {
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = function (event) {
const db = request.result;
if (!db.objectStoreNames.contains(objectStoreName)) {
db.createObjectStore(objectStoreName);
}
};
request.onsuccess = function () {
const db = request.result;
const tx = db.transaction(objectStoreName, 'readwrite');
const store = tx.objectStore(objectStoreName);
const putRequest = store.put(data, key);
putRequest.onsuccess = function () {
resolve();
};
putRequest.onerror = function () {
console.error(
'Error saving data to IndexedDB:',
putRequest.error
);
reject(putRequest.error);
};
};
request.onerror = function () {
console.error('Error opening IndexedDB:', request.error);
reject(request.error);
};
} catch (err) {
console.error('Exception thrown while opening IndexedDB:', err);
reject(err);
return;
}
});
};
}
}

View File

@@ -8,6 +8,11 @@ namespace gdjs {
* OnceTriggers is used to store the status of the conditions "Trigger once",
* that are used in events to have conditions that are only valid for one frame in a row.
*/
type OnceTriggersSyncData = {
onceTriggers: Record<integer, boolean>;
lastFrameOnceTriggers: Record<integer, boolean>;
};
export class OnceTriggers {
_onceTriggers: Record<integer, boolean> = {};
_lastFrameOnceTrigger: Record<integer, boolean> = {};
@@ -40,5 +45,17 @@ namespace gdjs {
this._onceTriggers[triggerId] = true;
return !this._lastFrameOnceTrigger.hasOwnProperty(triggerId);
}
getNetworkSyncData(): OnceTriggersSyncData {
return {
onceTriggers: this._onceTriggers,
lastFrameOnceTriggers: this._lastFrameOnceTrigger,
};
}
updateNetworkSyncData(data: OnceTriggersSyncData): void {
this._onceTriggers = data.onceTriggers;
this._lastFrameOnceTrigger = data.lastFrameOnceTriggers;
}
}
}

View File

@@ -77,7 +77,9 @@ namespace gdjs {
return false;
}
getNetworkSyncData(): BehaviorNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BehaviorNetworkSyncData {
// To be redefined by behaviors that need to synchronize properties
// while calling super() to get the common properties.
return {
@@ -90,7 +92,10 @@ namespace gdjs {
* Update the behavior properties using the provided data.
* @param networkSyncData The new properties of the behavior.
*/
updateFromNetworkSyncData(networkSyncData: BehaviorNetworkSyncData): void {
updateFromNetworkSyncData(
networkSyncData: BehaviorNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
// Must be redefined by behaviors that need to synchronize properties
// while calling super() to get the common properties.
if (networkSyncData.act !== this._activated) {

View File

@@ -1385,6 +1385,9 @@ namespace gdjs {
): GameNetworkSyncData | null {
const syncData: GameNetworkSyncData = {
var: this._variables.getNetworkSyncData(syncOptions),
sm: syncOptions.syncSounds
? this.getSoundManager().getNetworkSyncData()
: undefined,
ss: this._sceneStack.getNetworkSyncData(syncOptions) || undefined,
};
@@ -1412,10 +1415,16 @@ namespace gdjs {
return syncData;
}
updateFromNetworkSyncData(syncData: GameNetworkSyncData) {
updateFromNetworkSyncData(
syncData: GameNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
this._throwIfDisposed();
if (syncData.var) {
this._variables.updateFromNetworkSyncData(syncData.var);
this._variables.updateFromNetworkSyncData(syncData.var, options);
}
if (syncData.sm) {
this.getSoundManager().updateFromNetworkSyncData(syncData.sm);
}
if (syncData.ss) {
this._sceneStack.updateFromNetworkSyncData(syncData.ss);
@@ -1430,7 +1439,8 @@ namespace gdjs {
this.getVariablesForExtension(extensionName);
if (extensionVariables) {
extensionVariables.updateFromNetworkSyncData(
extensionVariablesData
extensionVariablesData,
options
);
}
}

View File

@@ -422,7 +422,8 @@ namespace gdjs {
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {}
/**
* Called when the object is created from an initial instance at the startup of the scene.<br>
* Called when the object is created from an initial instance at the startup of the scene.
*
* Note that common properties (position, angle, z order...) have already been setup.
*
* @param initialInstanceData The data of the initial instance.
@@ -452,17 +453,17 @@ namespace gdjs {
* This can be redefined by objects to send more information.
* @returns The full network sync data.
*/
getNetworkSyncData(): ObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): ObjectNetworkSyncData {
const behaviorNetworkSyncData = {};
this._behaviors.forEach((behavior) => {
if (!behavior.isSyncedOverNetwork()) {
if (!behavior.isSyncedOverNetwork() && !syncOptions.syncAllBehaviors) {
return;
}
const networkSyncData = behavior.getNetworkSyncData();
if (networkSyncData) {
behaviorNetworkSyncData[behavior.getName()] = networkSyncData;
}
const networkSyncData = behavior.getNetworkSyncData(syncOptions);
behaviorNetworkSyncData[behavior.getName()] = networkSyncData;
});
const variablesNetworkSyncData = this._variables.getNetworkSyncData({
@@ -481,7 +482,7 @@ namespace gdjs {
this._timers.items[timerName].getNetworkSyncData();
}
return {
const networkSyncData: ObjectNetworkSyncData = {
x: this.x,
y: this.y,
w: this.getWidth(),
@@ -498,6 +499,19 @@ namespace gdjs {
eff: effectsNetworkSyncData,
tim: timersNetworkSyncData,
};
if (syncOptions.syncObjectIdentifiers) {
networkSyncData.n = this.name;
if (!this.networkId) {
// If this is the first time the object is synced
// with identifier, then generate a networkId,
// so it can be re-used for future syncs.
this.networkId = gdjs.makeUuid().substring(0, 8);
}
networkSyncData.networkId = this.networkId;
}
return networkSyncData;
}
/**
@@ -507,7 +521,10 @@ namespace gdjs {
* @param networkSyncData The new data for the object.
* @returns true if the object was updated, false if it could not (i.e: network sync is not supported).
*/
updateFromNetworkSyncData(networkSyncData: ObjectNetworkSyncData) {
updateFromNetworkSyncData(
networkSyncData: ObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
if (networkSyncData.x !== undefined) {
this.setX(networkSyncData.x);
}
@@ -567,13 +584,13 @@ namespace gdjs {
const behaviorNetworkSyncData = networkSyncData.beh[behaviorName];
const behavior = this.getBehavior(behaviorName);
if (behavior) {
behavior.updateFromNetworkSyncData(behaviorNetworkSyncData);
behavior.updateFromNetworkSyncData(behaviorNetworkSyncData, options);
}
}
// If variables are synchronized, update them.
if (networkSyncData.var) {
this._variables.updateFromNetworkSyncData(networkSyncData.var);
this._variables.updateFromNetworkSyncData(networkSyncData.var, options);
}
// If effects are synchronized, update them.
@@ -599,6 +616,10 @@ namespace gdjs {
}
}
}
if (networkSyncData.networkId !== undefined) {
this.networkId = networkSyncData.networkId;
}
}
/**
@@ -701,8 +722,10 @@ namespace gdjs {
}
/**
* Get the unique identifier of the object.<br>
* The identifier is set by the runtimeScene owning the object.<br>
* Get the unique identifier of the object.
*
* The identifier is set by the runtimeScene owning the object.
*
* You can also use the id property (this._object.id) for increased efficiency instead of
* calling this method.
*
@@ -712,6 +735,18 @@ namespace gdjs {
return this.id;
}
/**
* Get the network ID of the object.
*
* The network ID is used to identify the object in a networked game.
* Or, for Save/Load purposes.
*
* @return The network ID of the object.
*/
getNetworkId(): string | null {
return this.networkId;
}
/**
* Set the position of the object.
*
@@ -1472,7 +1507,8 @@ namespace gdjs {
//Forces :
/**
* Get a force from the garbage, or create a new force is garbage is empty.<br>
* Get a force from the garbage, or create a new force is garbage is empty.
*
* To be used each time a force is created so as to avoid temporaries objects.
*
* @param x The x coordinates of the force
@@ -1557,7 +1593,8 @@ namespace gdjs {
}
/**
* Add a force oriented toward another object.<br>
* Add a force oriented toward another object.
*
* (Shortcut for addForceTowardPosition)
* @param object The target object
* @param len The force length, in pixels.

View File

@@ -12,7 +12,13 @@ namespace gdjs {
*/
export class RuntimeScene extends gdjs.RuntimeInstanceContainer {
_eventsFunction: null | ((runtimeScene: RuntimeScene) => void) = null;
_idToCallbackMap: null | Map<
string,
(
runtimeScene: gdjs.RuntimeScene,
asyncObjectsList: gdjs.LongLivedObjectsList
) => void
> = null;
_renderer: RuntimeSceneRenderer;
_debuggerRenderer: gdjs.DebuggerRenderer;
_variables: gdjs.VariablesContainer;
@@ -125,7 +131,13 @@ namespace gdjs {
* @param sceneAndExtensionsData An object containing the scene data.
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
*/
loadFromScene(sceneAndExtensionsData: SceneAndExtensionsData | null) {
loadFromScene(
sceneAndExtensionsData: SceneAndExtensionsData | null,
options?: {
skipCreatingInstances?: boolean;
skipStoppingSoundsOnStartup?: boolean;
}
) {
if (!sceneAndExtensionsData) {
logger.error('loadFromScene was called without a scene');
return;
@@ -183,15 +195,16 @@ namespace gdjs {
this.registerObject(sceneData.objects[i]);
}
//Create initial instances of objects
this.createObjectsFrom(
sceneData.instances,
0,
0,
0,
/*trackByPersistentUuid=*/
true
);
// Create initial instances of objects
if (!options || !options.skipCreatingInstances)
this.createObjectsFrom(
sceneData.instances,
0,
0,
0,
/*trackByPersistentUuid=*/
true
);
// Set up the default z order (for objects created from events)
this._setLayerDefaultZOrders();
@@ -209,7 +222,11 @@ namespace gdjs {
for (let i = 0; i < gdjs.callbacksRuntimeSceneLoaded.length; ++i) {
gdjs.callbacksRuntimeSceneLoaded[i](this);
}
if (sceneData.stopSoundsOnStartup && this._runtimeGame) {
if (
sceneData.stopSoundsOnStartup &&
(!options || !options.skipStoppingSoundsOnStartup) &&
this._runtimeGame
) {
this._runtimeGame.getSoundManager().clearAll();
}
this._isLoaded = true;
@@ -336,6 +353,8 @@ namespace gdjs {
const module = gdjs[sceneData.mangledName + 'Code'];
if (module && module.func) {
this._eventsFunction = module.func;
this._idToCallbackMap =
gdjs[sceneData.mangledName + 'Code'].idToCallbackMap;
} else {
setupWarningLogger.warn(
'No function found for running logic of scene ' + this._name
@@ -830,27 +849,67 @@ namespace gdjs {
if (
syncedPlayerNumber !== undefined &&
syncedPlayerNumber !== 1 &&
(!this.networkId ||
(variablesNetworkSyncData.length === 0 &&
!Object.keys(extensionsVariablesSyncData).length))
!this.networkId
) {
// If we are getting sync data for a specific player,
// and they are not the host, there is no sync data to send if:
// - The scene has no networkId (it's either not a multiplayer scene or the scene is not yet networked).
// - There are no variables to sync in the scene or extensions.
// and they are not the host, there is no sync data to send if
// the scene has no networkId (it's either not a multiplayer scene or the scene is not yet networked).
return null;
}
return {
const networkSyncData: LayoutNetworkSyncData = {
var: variablesNetworkSyncData,
extVar: extensionsVariablesSyncData,
id: this.getOrCreateNetworkId(),
};
if (syncOptions.syncSceneVisualProps) {
networkSyncData.color = this._backgroundColor;
}
if (syncOptions.syncLayers) {
const layersSyncData = {};
for (const layerName in this._layers.items) {
layersSyncData[layerName] =
this._layers.items[layerName].getNetworkSyncData();
}
networkSyncData.layers = layersSyncData;
}
if (syncOptions.syncSceneTimers) {
networkSyncData.time = this._timeManager.getNetworkSyncData();
}
if (syncOptions.syncOnceTriggers) {
networkSyncData.once = this._onceTriggers.getNetworkSyncData();
}
gdjs.callbacksRuntimeSceneGetSyncData.forEach((callback) => {
callback(this, networkSyncData, syncOptions);
});
if (syncOptions.syncAsyncTasks) {
networkSyncData.async =
this._asyncTasksManager.getNetworkSyncData(syncOptions);
}
return networkSyncData;
}
updateFromNetworkSyncData(syncData: LayoutNetworkSyncData) {
updateFromNetworkSyncData(
syncData: LayoutNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
if (syncData.color !== undefined) {
this._backgroundColor = syncData.color;
}
if (syncData.layers) {
for (const layerName in syncData.layers) {
const layerData = syncData.layers[layerName];
if (this.hasLayer(layerName)) {
const layer = this.getLayer(layerName);
layer.updateFromNetworkSyncData(layerData);
}
}
}
if (syncData.var) {
this._variables.updateFromNetworkSyncData(syncData.var);
this._variables.updateFromNetworkSyncData(syncData.var, options);
}
if (syncData.extVar) {
for (const extensionName in syncData.extVar) {
@@ -862,11 +921,32 @@ namespace gdjs {
this._variablesByExtensionName.get(extensionName);
if (extensionVariables) {
extensionVariables.updateFromNetworkSyncData(
extensionVariablesData
extensionVariablesData,
options
);
}
}
}
if (syncData.time) {
this._timeManager.updateFromNetworkSyncData(syncData.time);
}
if (syncData.once) {
this._onceTriggers.updateNetworkSyncData(syncData.once);
}
gdjs.callbacksRuntimeSceneUpdateFromSyncData.forEach((callback) => {
callback(this, syncData, options);
});
// Sync Async last, as it might depend on other data.
if (syncData.async && this._idToCallbackMap) {
this._asyncTasksManager.updateFromNetworkSyncData(
syncData.async,
this._idToCallbackMap,
this,
options
);
}
}
getOrCreateNetworkId(): string {

View File

@@ -2,6 +2,17 @@ namespace gdjs {
const logger = new gdjs.Logger('Scene stack');
const debugLogger = new gdjs.Logger('Multiplayer - Debug');
interface PushSceneOptions {
sceneName: string;
externalLayoutName?: string;
skipCreatingInstancesFromScene?: boolean;
skipStoppingSoundsOnStartup?: boolean;
}
interface ReplaceSceneOptions extends PushSceneOptions {
clear: boolean;
}
/**
* Hold the stack of scenes ({@link gdjs.RuntimeScene}) being played.
*/
@@ -121,15 +132,32 @@ namespace gdjs {
}
/**
* Pause the scene currently being played and start the new scene that is specified.
* If `externalLayoutName` is set, also instantiate the objects from this external layout.
* Pause the scene currently being played and start the new scene that is specified in `options.sceneName`.
* If `options.externalLayoutName` is set, also instantiate the objects from this external layout.
*
* @param options Contains the scene name and optional external layout name to instantiate.
* @param deprecatedExternalLayoutName Deprecated, use `options.externalLayoutName` instead.
*/
push(
newSceneName: string,
externalLayoutName?: string
options: PushSceneOptions | string,
deprecatedExternalLayoutName?: string
): gdjs.RuntimeScene | null {
this._throwIfDisposed();
const sceneName =
typeof options === 'string' ? options : options.sceneName;
const skipCreatingInstancesFromScene =
typeof options === 'string'
? false
: options.skipCreatingInstancesFromScene;
const skipStoppingSoundsOnStartup =
typeof options === 'string'
? false
: options.skipStoppingSoundsOnStartup;
const externalLayoutName =
deprecatedExternalLayoutName ||
(typeof options === 'string' ? undefined : options.externalLayoutName);
// Tell the scene it's being paused
const currentScene = this._stack[this._stack.length - 1];
if (currentScene) {
@@ -138,36 +166,48 @@ namespace gdjs {
// Avoid a risk of displaying an intermediate loading screen
// during 1 frame.
if (this._runtimeGame.areSceneAssetsReady(newSceneName)) {
return this._loadNewScene(newSceneName, externalLayoutName);
if (this._runtimeGame.areSceneAssetsReady(sceneName)) {
return this._loadNewScene({
sceneName,
externalLayoutName,
skipCreatingInstancesFromScene,
skipStoppingSoundsOnStartup,
});
}
this._isNextLayoutLoading = true;
this._runtimeGame.loadSceneAssets(newSceneName).then(() => {
this._loadNewScene(newSceneName);
this._runtimeGame.loadSceneAssets(sceneName).then(() => {
this._loadNewScene({
sceneName,
externalLayoutName,
skipCreatingInstancesFromScene,
skipStoppingSoundsOnStartup,
});
this._isNextLayoutLoading = false;
});
return null;
}
private _loadNewScene(
newSceneName: string,
externalLayoutName?: string
): gdjs.RuntimeScene {
private _loadNewScene(options: PushSceneOptions): gdjs.RuntimeScene {
this._throwIfDisposed();
// Load the new one
const newScene = new gdjs.RuntimeScene(this._runtimeGame);
newScene.loadFromScene(
this._runtimeGame.getSceneAndExtensionsData(newSceneName)
this._runtimeGame.getSceneAndExtensionsData(options.sceneName),
{
skipCreatingInstances: options.skipCreatingInstancesFromScene,
skipStoppingSoundsOnStartup: options.skipStoppingSoundsOnStartup,
}
);
this._wasFirstSceneLoaded = true;
// Optionally create the objects from an external layout.
if (externalLayoutName) {
const externalLayoutData =
this._runtimeGame.getExternalLayoutData(externalLayoutName);
if (options.externalLayoutName) {
const externalLayoutData = this._runtimeGame.getExternalLayoutData(
options.externalLayoutName
);
if (externalLayoutData) {
newScene.createObjectsFrom(
externalLayoutData.instances,
@@ -184,10 +224,21 @@ namespace gdjs {
}
/**
* Start the specified scene, replacing the one currently being played.
* If `clear` is set to true, all running scenes are also removed from the stack of scenes.
* Start the scene in `options.sceneName`, replacing the one currently being played.
* If `options.clear` is set to true, all running scenes are also removed from the stack of scenes.
*
* @param options Contains the scene name and optional external layout name to instantiate.
* @param deprecatedClear Deprecated, use `options.clear` instead.
*/
replace(newSceneName: string, clear?: boolean): gdjs.RuntimeScene | null {
replace(
options: ReplaceSceneOptions | string,
deprecatedClear?: boolean
): gdjs.RuntimeScene | null {
const clear =
deprecatedClear || typeof options === 'string' ? false : options.clear;
const newSceneName =
typeof options === 'string' ? options : options.sceneName;
this._throwIfDisposed();
if (!!clear) {
// Unload all the scenes
@@ -206,7 +257,7 @@ namespace gdjs {
}
}
}
return this.push(newSceneName);
return this.push(options);
}
/**
@@ -227,6 +278,11 @@ namespace gdjs {
return this._wasFirstSceneLoaded;
}
getAllScenes(): Array<gdjs.RuntimeScene> {
this._throwIfDisposed();
return this._stack;
}
getAllSceneNames(): Array<string> {
this._throwIfDisposed();
return this._stack.map((scene) => scene.getName());
@@ -267,7 +323,9 @@ namespace gdjs {
this._sceneStackSyncDataToApply = sceneStackSyncData;
}
applyUpdateFromNetworkSyncDataIfAny(): boolean {
applyUpdateFromNetworkSyncDataIfAny(
options?: UpdateFromNetworkSyncDataOptions
): boolean {
this._throwIfDisposed();
const sceneStackSyncData = this._sceneStackSyncDataToApply;
let hasMadeChangeToStack = false;
@@ -275,6 +333,32 @@ namespace gdjs {
this._sceneStackSyncDataToApply = null;
const skipCreatingInstancesFromScene =
!!options && !!options.preventInitialInstancesCreation;
const skipStoppingSoundsOnStartup =
!!options && !!options.preventSoundsStoppingOnStartup;
if (options && options.clearSceneStack) {
while (this._stack.length !== 0) {
let scene = this._stack.pop();
if (scene) {
scene.unloadScene();
}
}
for (let i = 0; i < sceneStackSyncData.length; ++i) {
const sceneSyncData = sceneStackSyncData[i];
const newScene = this.push({
sceneName: sceneSyncData.name,
skipCreatingInstancesFromScene,
skipStoppingSoundsOnStartup,
});
if (newScene) {
newScene.networkId = sceneSyncData.networkId;
}
}
hasMadeChangeToStack = true;
return hasMadeChangeToStack;
}
// If this method is called, we are a client.
// We trust the host to be the source of truth for the scene stack.
// So we loop through the scenes in the stack given by the host and either:
@@ -284,12 +368,16 @@ namespace gdjs {
for (let i = 0; i < sceneStackSyncData.length; ++i) {
const sceneSyncData = sceneStackSyncData[i];
const sceneAtThisPositionInOurStack = this._stack[i];
if (!sceneAtThisPositionInOurStack) {
debugLogger.info(
`Scene at position ${i} with name ${sceneSyncData.name} is missing from the stack, adding it.`
);
// We have fewer scenes in the stack than the host, let's add the scene.
const newScene = this.push(sceneSyncData.name);
const newScene = this.push({
sceneName: sceneSyncData.name,
skipCreatingInstancesFromScene,
});
if (newScene) {
newScene.networkId = sceneSyncData.networkId;
}
@@ -306,10 +394,12 @@ namespace gdjs {
);
// The scene does not correspond to the scene at this position in our stack
// Let's unload everything after this position to recreate the stack.
const newScene = this.replace(
sceneSyncData.name,
true // Clear the stack
);
const newScene = this.replace({
sceneName: sceneSyncData.name,
clear: true,
skipCreatingInstancesFromScene,
});
if (newScene) {
newScene.networkId = sceneSyncData.networkId;
}
@@ -349,10 +439,11 @@ namespace gdjs {
// This can happen if the host has restarted the scene
// We can't just update the networkId of the scene in the stack
// We need to replace it with a new scene
const newScene = this.replace(
sceneSyncData.name,
false // Don't clear the stack
);
const newScene = this.replace({
sceneName: sceneSyncData.name,
clear: false,
skipCreatingInstancesFromScene,
});
if (newScene) {
newScene.networkId = sceneSyncData.networkId;
}

View File

@@ -115,9 +115,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): SpriteNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): SpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
anim: this._animator.getNetworkSyncData(),
ifx: this.isFlippedX(),
ify: this.isFlippedY(),
@@ -128,8 +130,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(newNetworkSyncData: SpriteNetworkSyncData) {
super.updateFromNetworkSyncData(newNetworkSyncData);
updateFromNetworkSyncData(
newNetworkSyncData: SpriteNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(newNetworkSyncData, options);
if (newNetworkSyncData.ifx !== undefined) {
this.flipX(newNetworkSyncData.ifx);
}

View File

@@ -9,6 +9,15 @@ namespace gdjs {
* frame, since the beginning of the scene and other time related values.
* All durations are expressed in milliseconds.
*/
declare interface TimeManagerSyncData {
elapsedTime: float;
timeScale: float;
timeFromStart: float;
firstFrame: boolean;
timers: Hashtable<TimerNetworkSyncData>;
firstUpdateDone: boolean;
}
export class TimeManager {
_elapsedTime: float = 0;
_timeScale: float = 1;
@@ -59,6 +68,48 @@ namespace gdjs {
}
}
getNetworkSyncData(): TimeManagerSyncData {
const timerNetworkSyncDatas = new Hashtable<TimerNetworkSyncData>();
Object.entries(this._timers.items).forEach(([key, timer]) => {
timerNetworkSyncDatas.put(key, timer.getNetworkSyncData());
});
return {
elapsedTime: this._elapsedTime,
timeScale: this._timeScale,
timeFromStart: this._timeFromStart,
firstFrame: this._firstFrame,
timers: timerNetworkSyncDatas,
firstUpdateDone: this._firstUpdateDone,
};
}
updateFromNetworkSyncData(syncData: TimeManagerSyncData): void {
if (syncData.elapsedTime !== undefined) {
this._elapsedTime = syncData.elapsedTime;
}
if (syncData.timeScale !== undefined) {
this._timeScale = syncData.timeScale;
}
if (syncData.timeFromStart !== undefined) {
this._timeFromStart = syncData.timeFromStart;
}
if (syncData.firstFrame !== undefined) {
this._firstFrame = syncData.firstFrame;
}
if (syncData.timers !== undefined) {
this._timers.clear();
Object.entries(syncData.timers.items).forEach(([key, timerData]) => {
const newTimer = new gdjs.Timer(timerData.name);
newTimer.updateFromNetworkSyncData(timerData);
this._timers.put(key, newTimer);
});
}
if (syncData.firstUpdateDone !== undefined) {
this._firstUpdateDone = syncData.firstUpdateDone;
}
}
/**
* Get the time scale.
* @return The time scale (positive, 1 is normal speed).

View File

@@ -77,6 +77,7 @@ namespace gdjs {
getNetworkSyncData(): TimerNetworkSyncData {
return {
name: this._name,
time: this._time,
paused: this._paused,
};

View File

@@ -42,6 +42,26 @@ declare type ObjectData = {
declare type GetNetworkSyncDataOptions = {
playerNumber?: number;
isHost?: boolean;
syncObjectIdentifiers?: boolean;
syncAllVariables?: boolean;
syncAllBehaviors?: boolean;
syncSceneTimers?: boolean;
syncOnceTriggers?: boolean;
syncSounds?: boolean;
syncTweens?: boolean;
syncLayers?: boolean;
syncAsyncTasks?: boolean;
syncSceneVisualProps?: boolean;
syncFullTileMaps?: boolean;
};
declare type UpdateFromNetworkSyncDataOptions = {
clearSceneStack?: boolean;
preventInitialInstancesCreation?: boolean;
preventSoundsStoppingOnStartup?: boolean;
clearInputs?: boolean;
keepControl?: boolean;
ignoreVariableOwnership?: boolean;
};
/** Object containing basic properties for all objects synchronizing over the network. */
@@ -70,6 +90,10 @@ declare type BasicObjectNetworkSyncData = {
pfx: number;
/** Permanent force on Y */
pfy: number;
/** Name of the object */
n?: string;
/** The network ID of the instance. */
networkId?: string;
};
/**
@@ -91,6 +115,8 @@ declare interface ObjectNetworkSyncData extends BasicObjectNetworkSyncData {
tim?: {
[timerName: string]: TimerNetworkSyncData;
};
/** Tweens */
tween?: TweenManagerNetworkSyncData;
}
declare type ForceNetworkSyncData = {
@@ -102,6 +128,7 @@ declare type ForceNetworkSyncData = {
};
declare type TimerNetworkSyncData = {
name: string;
time: float;
paused: boolean;
};
@@ -128,12 +155,30 @@ declare type VariableData = Readonly<{
/** A variable child of a container. Those always have a name. */
declare type RootVariableData = Omit<VariableData, 'name'> & { name: string };
declare type VariableNetworkSyncData = {
name: string;
declare type UnnamedVariableNetworkSyncData = {
value: string | float | boolean;
children?: VariableNetworkSyncData[];
type: VariableType;
owner: number;
owner: number | null;
};
declare type VariableNetworkSyncData = UnnamedVariableNetworkSyncData & {
name: string;
};
declare type LayerNetworkSyncData = {
timeScale: float;
defaultZOrder: integer;
hidden: boolean;
effects: {
[effectName: string]: EffectNetworkSyncData;
};
followBaseLayerCamera: boolean;
clearColor: Array<integer>;
cameraX: float;
cameraY: float;
cameraZ: float;
cameraRotation: float;
cameraZoom: float;
};
/** Properties to set up a behavior. */
@@ -149,6 +194,74 @@ declare type BehaviorNetworkSyncData = {
props: any;
};
declare type SceneTweenType =
| 'layoutValue'
| 'layerValue'
| 'variable'
| 'cameraZoom'
| 'cameraRotation'
| 'cameraPosition'
| 'colorEffectProperty'
| 'numberEffectProperty';
declare type ObjectTweenType =
| 'variable'
| 'position'
| 'positionX'
| 'positionY'
| 'positionZ'
| 'width'
| 'height'
| 'depth'
| 'angle'
| 'rotationX'
| 'rotationY'
| 'scale'
| 'scaleXY'
| 'scaleX'
| 'scaleY'
| 'opacity'
| 'characterSize'
| 'numberEffectProperty'
| 'colorEffectProperty'
| 'objectColor'
| 'objectColorHSL'
| 'objectValue';
declare type TweenInformation = {
type: SceneTweenType | ObjectTweenType;
layerName?: string;
variable?: Variable;
effectName?: string;
propertyName?: string;
scaleFromCenterOfObject?: boolean;
useHSLColorTransition?: boolean;
destroyObjectWhenFinished?: boolean;
};
declare type TweenInformationNetworkSyncData = Omit<
TweenInformation,
'variable' // When synced, a variable is replaced by its path
> & { variablePath?: string[] };
declare type TweenInstanceNetworkSyncData<T> = {
initialValue: T;
targetedValue: T;
elapsedTime: float;
totalDuration: float;
easingIdentifier: string;
interpolationString: 'linear' | 'exponential';
isPaused: boolean;
tweenInformation: TweenInformationNetworkSyncData;
};
declare type TweenManagerNetworkSyncData = {
tweens: Record<
string,
| TweenInstanceNetworkSyncData<float>
| TweenInstanceNetworkSyncData<Array<float>>
>;
};
declare interface GdVersionData {
build: number;
major: number;
@@ -183,6 +296,14 @@ declare interface LayoutNetworkSyncData {
extVar?: {
[extensionName: string]: VariableNetworkSyncData[];
};
time?: TimeManagerSyncData;
tween?: TweenManagerNetworkSyncData;
once?: OnceTriggersSyncData;
layers?: {
[layerName: string]: LayerNetworkSyncData;
};
async?: AsyncTasksManagerNetworkSyncData;
color?: integer;
}
declare interface SceneStackSceneNetworkSyncData {
@@ -192,12 +313,30 @@ declare interface SceneStackSceneNetworkSyncData {
declare type SceneStackNetworkSyncData = SceneStackSceneNetworkSyncData[];
declare type SoundSyncData = {
loop: boolean;
volume: float;
rate: float;
resourceName: string;
seek: float;
};
declare type ChannelsSoundSyncData = Record<integer, SoundSyncData>;
declare type SoundManagerSyncData = {
globalVolume: float;
cachedSpatialPosition: Record<number, [number, number, number]>;
freeSounds: SoundSyncData[];
freeMusics: SoundSyncData[];
musics: ChannelsSoundSyncData;
sounds: ChannelsSoundSyncData;
};
declare interface GameNetworkSyncData {
var?: VariableNetworkSyncData[];
ss?: SceneStackNetworkSyncData;
extVar?: {
[extensionName: string]: VariableNetworkSyncData[];
};
sm?: SoundManagerSyncData;
}
declare interface EventsFunctionsExtensionData {

9
GDJS/Runtime/types/save-state.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
declare type SceneSaveState = {
sceneData: LayoutNetworkSyncData;
objectDatas: { [objectId: integer]: ObjectNetworkSyncData };
};
declare type GameSaveState = {
gameNetworkSyncData: GameNetworkSyncData;
layoutNetworkSyncDatas: SceneSaveState[];
};

View File

@@ -9,7 +9,7 @@ namespace gdjs {
/**
* Children of a structure.
*/
type Children = Record<string, gdjs.Variable>;
export type Children = Record<string, gdjs.Variable>;
/**
* A Variable is an object storing a value (number or a string) or children variables.
@@ -108,6 +108,171 @@ namespace gdjs {
return target;
}
static getVariableDataFromNetworkSyncData = (
syncData: VariableNetworkSyncData
): VariableData => {
return {
name: syncData.name,
value: syncData.value,
type: syncData.type,
children: syncData.children
? syncData.children.map((childSyncData) =>
gdjs.Variable.getVariableDataFromNetworkSyncData(childSyncData)
)
: undefined,
};
};
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): UnnamedVariableNetworkSyncData | undefined {
const syncedPlayerNumber = syncOptions.playerNumber;
const isHost = syncOptions.isHost;
const variableOwner = this.getPlayerOwnership();
if (
// Variable undefined.
this.isUndefinedInContainer() ||
// If we force sync everything, we don't look at the ownership.
(!syncOptions.syncAllVariables &&
// Variable marked as not to be synchronized.
(variableOwner === null ||
// Getting sync data for a specific player:
(syncedPlayerNumber !== undefined &&
// Variable is owned by host but this player number is not the host.
variableOwner === 0 &&
!isHost) ||
// Variable is owned by a player but not getting sync data for this player number.
(variableOwner !== 0 && syncedPlayerNumber !== variableOwner)))
) {
// In those cases, the variable should not be synchronized.
return;
}
const variableType = this.getType();
const variableValue =
variableType === 'structure' || variableType === 'array'
? ''
: this.getValue();
return {
value: variableValue,
type: variableType,
children: this.getStructureNetworkSyncData(this),
owner: variableOwner,
};
}
// Structure variables can contain other variables, so we need to recursively
// get the sync data for each child variable.
getStructureNetworkSyncData(
variable: gdjs.Variable
): VariableNetworkSyncData[] | undefined {
if (variable.getType() === 'array') {
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
variable.getAllChildrenArray().forEach((childVariable) => {
const childVariableType = childVariable.getType();
const childVariableValue =
childVariableType === 'structure' || childVariableType === 'array'
? ''
: childVariable.getValue();
const childVariableOwner = childVariable.getPlayerOwnership();
if (
// Variable undefined.
childVariable.isUndefinedInContainer() ||
// Variable marked as not to be synchronized.
childVariableOwner === null
) {
// In those cases, the variable should not be synchronized.
return;
}
allVariableNetworkSyncData.push({
name: '',
value: childVariableValue,
type: childVariableType,
children: this.getStructureNetworkSyncData(childVariable),
owner: childVariableOwner,
});
});
return allVariableNetworkSyncData;
}
if (variable.getType() === 'structure') {
const variableChildren = variable.getAllChildren();
if (!variableChildren) return undefined;
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
Object.entries(variableChildren).forEach(
([childVariableName, childVariable]) => {
const childVariableType = childVariable.getType();
const childVariableValue =
childVariableType === 'structure' || childVariableType === 'array'
? ''
: childVariable.getValue();
const childVariableOwner = childVariable.getPlayerOwnership();
if (
// Variable undefined.
childVariable.isUndefinedInContainer() ||
// Variable marked as not to be synchronized.
childVariableOwner === null
) {
// In those cases, the variable should not be synchronized.
return;
}
allVariableNetworkSyncData.push({
name: childVariableName,
value: childVariableValue,
type: childVariableType,
children: this.getStructureNetworkSyncData(childVariable),
owner: childVariableOwner,
});
}
);
return allVariableNetworkSyncData;
}
return undefined;
}
updateFromNetworkSyncData(
networkSyncData: VariableNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
// // If we receive an update for this variable for a different owner than the one we know about,
// then 2 cases:
// - If we are the owner of the variable, then ignore the message, we assume it's a late update message or a wrong one,
// we are confident that we own this variable. (it may be reverted if we don't receive an acknowledgment in time)
// - If we are not the owner of the variable, then assume that we missed the ownership change message, so update the variable's
// ownership and then update the variable.
const syncedVariableOwner = networkSyncData.owner;
const variableData =
gdjs.Variable.getVariableDataFromNetworkSyncData(networkSyncData);
if (!options.ignoreVariableOwnership) {
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
const currentVariableOwner = this.getPlayerOwnership();
if (currentPlayerNumber === currentVariableOwner) {
// Variable owned by us, ignoring update message.
return;
}
if (
syncedVariableOwner &&
syncedVariableOwner !== currentVariableOwner
) {
/// Variable owned by someone else on our game, changing ownership as part of the update event.
this.setPlayerOwnership(syncedVariableOwner);
}
}
this.reinitialize(variableData);
}
/**
* Converts a JavaScript object into a value compatible
* with GDevelop variables and store it inside this variable.
@@ -148,7 +313,7 @@ namespace gdjs {
" aren't supported by GDevelop variables, it will be reduced to that size."
);
// @ts-ignore
variable.setNumber(parseInt(obj, 10));
this.setNumber(parseInt(obj, 10));
} else if (typeof obj === 'function') {
logger.error(
'Error while converting JS variable to GDevelop variable: Impossible to set variable value to a function.'

View File

@@ -228,183 +228,89 @@ namespace gdjs {
return null;
}
getVariablePathInContainerByLoopingThroughAllVariables(
variable: gdjs.Variable,
childrenToLookIn: Children | null = null
): string[] | null {
const variables = childrenToLookIn || this._variables.items;
for (const variableName in variables) {
if (variables.hasOwnProperty(variableName)) {
const variableItem = variables[variableName];
if (variableItem === variable) {
return [variableName];
} else if (variableItem.getType() === 'structure') {
const variableItemChildren = variableItem.getAllChildren();
const childPath =
this.getVariablePathInContainerByLoopingThroughAllVariables(
variable,
variableItemChildren
);
if (childPath) {
return [variableName, ...childPath];
}
}
}
}
return null;
}
getVariableFromPath(variablePath: string[]): gdjs.Variable | null {
let variableItems = this._variables.items;
for (let i = 0; i < variablePath.length; i++) {
const part = variablePath[i];
const nextVariable = variableItems[part];
if (!nextVariable) {
return null;
}
if (i === variablePath.length - 1) {
return nextVariable;
}
variableItems = nextVariable.getAllChildren();
}
return null;
}
static _deletedVars: Array<string | undefined> = [];
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): VariableNetworkSyncData[] {
const syncedPlayerNumber = syncOptions.playerNumber;
const isHost = syncOptions.isHost;
const networkSyncData: VariableNetworkSyncData[] = [];
const variableNames = [];
this._variables.keys(variableNames);
variableNames.forEach((variableName) => {
const variable = this._variables.get(variableName);
const variableOwner = variable.getPlayerOwnership();
if (
// Variable undefined.
variable.isUndefinedInContainer() ||
// Variable marked as not to be synchronized.
variableOwner === null ||
// Getting sync data for a specific player:
(syncedPlayerNumber !== undefined &&
// Variable is owned by host but this player number is not the host.
variableOwner === 0 &&
!isHost) ||
// Variable is owned by a player but not getting sync data for this player number.
(variableOwner !== 0 && syncedPlayerNumber !== variableOwner)
) {
// In those cases, the variable should not be synchronized.
return;
const variableSyncData = variable.getNetworkSyncData(syncOptions);
if (variableSyncData) {
networkSyncData.push({
name: variableName,
...variableSyncData,
});
}
const variableType = variable.getType();
const variableValue =
variableType === 'structure' || variableType === 'array'
? ''
: variable.getValue();
networkSyncData.push({
name: variableName,
value: variableValue,
type: variableType,
children: this.getStructureNetworkSyncData(variable),
owner: variableOwner,
});
});
return networkSyncData;
}
// Structure variables can contain other variables, so we need to recursively
// get the sync data for each child variable.
getStructureNetworkSyncData(
variable: gdjs.Variable
): VariableNetworkSyncData[] | undefined {
if (variable.getType() === 'array') {
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
variable.getAllChildrenArray().forEach((childVariable) => {
const childVariableType = childVariable.getType();
const childVariableValue =
childVariableType === 'structure' || childVariableType === 'array'
? ''
: childVariable.getValue();
const childVariableOwner = childVariable.getPlayerOwnership();
if (
// Variable undefined.
childVariable.isUndefinedInContainer() ||
// Variable marked as not to be synchronized.
childVariableOwner === null
) {
// In those cases, the variable should not be synchronized.
return;
}
allVariableNetworkSyncData.push({
name: '',
value: childVariableValue,
type: childVariableType,
children: this.getStructureNetworkSyncData(childVariable),
owner: childVariableOwner,
});
});
return allVariableNetworkSyncData;
}
if (variable.getType() === 'structure') {
const variableChildren = variable.getAllChildren();
if (!variableChildren) return undefined;
const allVariableNetworkSyncData: VariableNetworkSyncData[] = [];
Object.entries(variableChildren).forEach(
([childVariableName, childVariable]) => {
const childVariableType = childVariable.getType();
const childVariableValue =
childVariableType === 'structure' || childVariableType === 'array'
? ''
: childVariable.getValue();
const childVariableOwner = childVariable.getPlayerOwnership();
if (
// Variable undefined.
childVariable.isUndefinedInContainer() ||
// Variable marked as not to be synchronized.
childVariableOwner === null
) {
// In those cases, the variable should not be synchronized.
return;
}
allVariableNetworkSyncData.push({
name: childVariableName,
value: childVariableValue,
type: childVariableType,
children: this.getStructureNetworkSyncData(childVariable),
owner: childVariableOwner,
});
}
);
return allVariableNetworkSyncData;
}
return undefined;
}
updateFromNetworkSyncData(networkSyncData: VariableNetworkSyncData[]) {
updateFromNetworkSyncData(
networkSyncData: VariableNetworkSyncData[],
options: UpdateFromNetworkSyncDataOptions
) {
const that = this;
for (let j = 0; j < networkSyncData.length; ++j) {
const variableSyncData = networkSyncData[j];
const variableData =
that._getVariableDataFromNetworkSyncData(variableSyncData);
gdjs.Variable.getVariableDataFromNetworkSyncData(variableSyncData);
const variableName = variableData.name;
if (!variableName) continue;
const variable = that.get(variableName);
// // If we receive an update for this variable for a different owner than the one we know about,
// then 2 cases:
// - If we are the owner of the variable, then ignore the message, we assume it's a late update message or a wrong one,
// we are confident that we own this variable. (it may be reverted if we don't receive an acknowledgment in time)
// - If we are not the owner of the variable, then assume that we missed the ownership change message, so update the variable's
// ownership and then update the variable.
const syncedVariableOwner = variableSyncData.owner;
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
const currentVariableOwner = variable.getPlayerOwnership();
if (currentPlayerNumber === currentVariableOwner) {
console.info(
`Variable ${variableName} is owned by us ${gdjs.multiplayer.playerNumber}, ignoring update message from ${syncedVariableOwner}.`
);
return;
}
if (syncedVariableOwner !== currentVariableOwner) {
console.info(
`Variable ${variableName} is owned by ${currentVariableOwner} on our game, changing ownership to ${syncedVariableOwner} as part of the update event.`
);
variable.setPlayerOwnership(syncedVariableOwner);
}
variable.reinitialize(variableData);
variable.updateFromNetworkSyncData(variableSyncData, options);
}
}
_getVariableDataFromNetworkSyncData(
syncData: VariableNetworkSyncData
): VariableData {
return {
name: syncData.name,
value: syncData.value,
type: syncData.type,
children: syncData.children
? syncData.children.map((childSyncData) =>
this._getVariableDataFromNetworkSyncData(childSyncData)
)
: undefined,
};
}
/**
* "Bad" variable container, used by events when no other valid container can be found.
* This container has no state and always returns the bad variable ( see VariablesContainer.badVariable ).
@@ -440,18 +346,18 @@ namespace gdjs {
updateFromNetworkSyncData: function () {
return;
},
getStructureNetworkSyncData: function () {
return undefined;
},
_getVariableDataFromNetworkSyncData: function () {
return {};
},
hasVariable: function () {
return false;
},
getVariableNameInContainerByLoopingThroughAllVariables: function () {
return '';
},
getVariablePathInContainerByLoopingThroughAllVariables: function () {
return [];
},
getVariableFromPath: function () {
return null;
},
rebuildIndexFrom: function () {
return;
},
@@ -572,6 +478,15 @@ namespace gdjs {
disableSynchronization: function () {
return;
},
getNetworkSyncData: function () {
return undefined;
},
getStructureNetworkSyncData: function () {
return [];
},
updateFromNetworkSyncData: function () {
return;
},
};
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

View File

@@ -128,7 +128,7 @@ export class EditableTileMap {
return tileMap;
}
toJSObject(): Object {
toJSObject(): EditableTileMapAsJsObject {
return {
tileWidth: this.tileWidth,
tileHeight: this.tileHeight,
@@ -493,8 +493,12 @@ abstract class AbstractEditableLayer {
this.visible = visible;
}
toJSObject(): Object {
return {};
toJSObject(): EditableTileMapLayerAsJsObject {
return {
id: this.id,
alpha: 0,
tiles: [],
};
}
/**
@@ -660,7 +664,7 @@ export class EditableTileMapLayer extends AbstractEditableLayer {
return layer;
}
toJSObject(): Object {
toJSObject(): EditableTileMapLayerAsJsObject {
return {
id: this.id,
alpha: this._alpha,

View File

@@ -0,0 +1,19 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_492_72)">
<path d="M0 24H24V5.14286L18.8571 0H7.3871H4.5H0V24Z" fill="#3DB3E4" />
<path d="M20.5714 13.7143H3.42857V22.2857H20.5714V13.7143Z" fill="#404D9B" />
<path d="M18.8571 10.2857V1.71429H11.1428V4.28572H14.2857L9.85714 10.2857H18.8571Z" fill="white" />
<path
d="M10.2857 -3.42857L10.2857 5.14286H12L7.71429 10.2857L3.42857 5.14286H5.14286V-3.42857H10.2857Z"
fill="#404D9B" />
<g opacity="0.1">
<path d="M24 24L0 0V24H24Z" fill="black" />
<path d="M24 5.14286L18.8571 0H0L24 24V5.14286Z" fill="white" />
</g>
</g>
<defs>
<clipPath id="clip0_492_72">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -0,0 +1,19 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_492_78)">
<path d="M0 24H24V5.14286L18.8571 0H7.3871H4.5H0V24Z" fill="#3DB3E4" />
<path
d="M3.42857 15.4286L3.42857 6.85714H1.71429L6 1.71429L10.2857 6.85714H8.57143V15.4286H3.42857Z"
fill="#404D9B" />
<path d="M20.5714 13.7143H3.42857V22.2857H20.5714V13.7143Z" fill="#404D9B" />
<path d="M18.8571 10.2857V1.71429H7.71429L12.8571 7.71429H10.2857V10.2857H18.8571Z" fill="white" />
<g opacity="0.1">
<path d="M24 24L0 0V24H24Z" fill="black" />
<path d="M24 5.14286L18.8571 0H0L24 24V5.14286Z" fill="white" />
</g>
</g>
<defs>
<clipPath id="clip0_492_78">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 815 B

View File

@@ -41,6 +41,12 @@ const jsExtensions = [
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/DeviceVibration/JsExtension.js'),
objectsRenderingServiceModules: {},
},
{
name: 'SaveState',
// $FlowExpectedError - this path is ignored for Flow.
extensionModule: require('GDJS-for-web-app-only/Runtime/Extensions/SaveState/JsExtension.js'),
objectsRenderingServiceModules: {},
},
{
name: 'DebuggerTools',
// $FlowExpectedError - this path is ignored for Flow.

View File

@@ -31,7 +31,7 @@ type GetExpectedNumberOfJSExtensionModulesArguments = {|
function getExpectedNumberOfJSExtensionModules(
{ filterExamples } /*: GetExpectedNumberOfJSExtensionModulesArguments*/
) /*:number*/ {
return 28 + (filterExamples ? 0 : 1);
return 29 + (filterExamples ? 0 : 1);
}
/**

View File

@@ -84,13 +84,13 @@ export const ExtensionLoadErrorDialog = ({
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>
<TableHeaderColumn style={{ width: '25%' }}>
<Trans>Extension name</Trans>
</TableHeaderColumn>
<TableHeaderColumn>
<TableHeaderColumn style={{ width: '35%' }}>
<Trans>Message</Trans>
</TableHeaderColumn>
<TableHeaderColumn>
<TableHeaderColumn style={{ width: '40%' }}>
<Trans>Raw error</Trans>
</TableHeaderColumn>
</TableRow>
@@ -99,9 +99,19 @@ export const ExtensionLoadErrorDialog = ({
{erroredExtensionLoadingResults.map(
({ extensionModulePath, result: { message, rawError } }) => (
<TableRow key={extensionModulePath}>
<TableRowColumn>{extensionModulePath}</TableRowColumn>
<TableRowColumn>{message}</TableRowColumn>
<TableRowColumn>
<TableRowColumn
style={{ width: '25%', wordBreak: 'break-word' }}
>
{extensionModulePath}
</TableRowColumn>
<TableRowColumn
style={{ width: '35%', wordBreak: 'break-word' }}
>
{message}
</TableRowColumn>
<TableRowColumn
style={{ width: '40%', wordBreak: 'break-word' }}
>
{rawError && (
<Text>
{rawError.toString()}
@@ -115,9 +125,13 @@ export const ExtensionLoadErrorDialog = ({
)}
{genericError ? (
<TableRow>
<TableRowColumn>-</TableRowColumn>
<TableRowColumn>{genericError.toString()}</TableRowColumn>
<TableRowColumn>-</TableRowColumn>
<TableRowColumn style={{ width: '25%' }}>-</TableRowColumn>
<TableRowColumn
style={{ width: '35%', wordBreak: 'break-word' }}
>
{genericError.toString()}
</TableRowColumn>
<TableRowColumn style={{ width: '40%' }}>-</TableRowColumn>
</TableRow>
) : null}
</TableBody>