Compare commits
9 Commits
v5.3.199
...
feature/vi
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cba3a4e87a | ||
![]() |
15dc2fcdd0 | ||
![]() |
b21fdc1ac2 | ||
![]() |
ad4c53898a | ||
![]() |
7a0745a977 | ||
![]() |
69b3c23f1d | ||
![]() |
5f52b6d7e3 | ||
![]() |
dfdbd5c5a0 | ||
![]() |
958f89fb49 |
@@ -541,7 +541,6 @@ gd::String EventsCodeGenerator::GenerateActionCode(
|
||||
if (MetadataProvider::HasBehaviorAction(
|
||||
platform, behaviorType, action.GetType()) &&
|
||||
instrInfos.parameters.size() >= 2) {
|
||||
|
||||
std::vector<gd::String> realObjects =
|
||||
ExpandObjectsName(objectName, context);
|
||||
for (std::size_t i = 0; i < realObjects.size(); ++i) {
|
||||
@@ -1112,6 +1111,33 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
|
||||
}
|
||||
}
|
||||
|
||||
size_t EventsCodeGenerator::GenerateSingleUsageUniqueIdForEventsList() {
|
||||
return eventsListNextUniqueId++;
|
||||
}
|
||||
|
||||
size_t EventsCodeGenerator::GenerateSingleUsageUniqueIdFor(
|
||||
const Instruction* instruction) {
|
||||
if (!instruction) {
|
||||
std::cout << "ERROR: During code generation, a null pointer was passed to "
|
||||
"GenerateSingleUsageUniqueIdFor."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Base the unique id on the adress in memory so that the same instruction
|
||||
// in memory will get the same id across different code generations.
|
||||
size_t uniqueId = (size_t)instruction;
|
||||
|
||||
// While in most case this function is called a single time for each instruction,
|
||||
// it's possible for an instruction to be appearing more than once in the events,
|
||||
// if we used links. In this case, simply increment the unique id to be sure that
|
||||
// ids are effectively uniques, and stay stable (given the same order of links).
|
||||
while (instructionUniqueIds.find(uniqueId) != instructionUniqueIds.end()) {
|
||||
uniqueId++;
|
||||
}
|
||||
instructionUniqueIds.insert(uniqueId);
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GetObjectListName(
|
||||
const gd::String& name, const gd::EventsCodeGenerationContext& context) {
|
||||
return ManObjListName(name);
|
||||
@@ -1140,7 +1166,8 @@ EventsCodeGenerator::EventsCodeGenerator(gd::Project& project_,
|
||||
errorOccurred(false),
|
||||
compilationForRuntime(false),
|
||||
maxCustomConditionsDepth(0),
|
||||
maxConditionsListsSize(0){};
|
||||
maxConditionsListsSize(0),
|
||||
eventsListNextUniqueId(0){};
|
||||
|
||||
EventsCodeGenerator::EventsCodeGenerator(
|
||||
const gd::Platform& platform_,
|
||||
@@ -1155,6 +1182,7 @@ EventsCodeGenerator::EventsCodeGenerator(
|
||||
errorOccurred(false),
|
||||
compilationForRuntime(false),
|
||||
maxCustomConditionsDepth(0),
|
||||
maxConditionsListsSize(0){};
|
||||
maxConditionsListsSize(0),
|
||||
eventsListNextUniqueId(0){};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
#include "GDCore/String.h"
|
||||
@@ -123,7 +124,7 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
*
|
||||
*/
|
||||
std::vector<gd::String> GenerateParametersCodes(
|
||||
const std::vector<gd::Expression> & parameters,
|
||||
const std::vector<gd::Expression>& parameters,
|
||||
const std::vector<gd::ParameterMetadata>& parametersInfo,
|
||||
EventsCodeGenerationContext& context,
|
||||
std::vector<std::pair<gd::String, gd::String> >*
|
||||
@@ -411,6 +412,29 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
|
||||
enum VariableScope { LAYOUT_VARIABLE = 0, PROJECT_VARIABLE, OBJECT_VARIABLE };
|
||||
|
||||
/**
|
||||
* Generate a single unique number for the specified instruction.
|
||||
*
|
||||
* This is useful for instructions that need to identify themselves in the
|
||||
* generated code like the "Trigger Once" conditions. The id is stable across
|
||||
* code generations if the instructions are the same objects in memory.
|
||||
*
|
||||
* Note that if this function is called multiple times with the same
|
||||
* instruction, the unique number returned will be *different*. This is
|
||||
* because a single instruction might appear at multiple places in events due
|
||||
* to the usage of links.
|
||||
*/
|
||||
size_t GenerateSingleUsageUniqueIdFor(const gd::Instruction* instruction);
|
||||
|
||||
/**
|
||||
* Generate a single unique number for an events list.
|
||||
*
|
||||
* This is useful to create unique function names for events list, that are
|
||||
* stable across code generation given the exact same list of events. They are
|
||||
* *not* stable if events are moved/reorganized.
|
||||
*/
|
||||
size_t GenerateSingleUsageUniqueIdForEventsList();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* \brief Generate the code for a single parameter.
|
||||
@@ -704,7 +728,8 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
/**
|
||||
* Generate the getter to get the name of the specified behavior.
|
||||
*/
|
||||
virtual gd::String GenerateGetBehaviorNameCode(const gd::String& behaviorName);
|
||||
virtual gd::String GenerateGetBehaviorNameCode(
|
||||
const gd::String& behaviorName);
|
||||
|
||||
const gd::Platform& platform; ///< The platform being used.
|
||||
|
||||
@@ -732,6 +757,11 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
size_t maxCustomConditionsDepth; ///< The maximum depth value for all the
|
||||
///< custom conditions created.
|
||||
size_t maxConditionsListsSize; ///< The maximum size of a list of conditions.
|
||||
|
||||
std::set<size_t>
|
||||
instructionUniqueIds; ///< The unique ids generated for instructions.
|
||||
size_t eventsListNextUniqueId; ///< The next identifier to use for an events
|
||||
///< list function name.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -4,9 +4,12 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/Expression.h"
|
||||
#include "GDCore/Events/InstructionsList.h"
|
||||
#include "GDCore/String.h"
|
||||
@@ -15,18 +18,14 @@ namespace gd {
|
||||
|
||||
gd::Expression Instruction::badExpression("");
|
||||
|
||||
Instruction::Instruction(gd::String type_)
|
||||
: type(type_),
|
||||
inverted(false) {
|
||||
Instruction::Instruction(gd::String type_) : type(type_), inverted(false) {
|
||||
parameters.reserve(8);
|
||||
}
|
||||
|
||||
Instruction::Instruction(gd::String type_,
|
||||
const std::vector<gd::Expression>& parameters_,
|
||||
bool inverted_)
|
||||
: type(type_),
|
||||
inverted(inverted_),
|
||||
parameters(parameters_) {
|
||||
: type(type_), inverted(inverted_), parameters(parameters_) {
|
||||
parameters.reserve(8);
|
||||
}
|
||||
|
||||
@@ -56,4 +55,17 @@ void Instruction::SetParameter(std::size_t nb, const gd::Expression& val) {
|
||||
parameters[nb] = val;
|
||||
}
|
||||
|
||||
std::shared_ptr<Instruction> GD_CORE_API
|
||||
CloneRememberingOriginalElement(std::shared_ptr<Instruction> instruction) {
|
||||
std::shared_ptr<Instruction> copy =
|
||||
std::make_shared<Instruction>(*instruction);
|
||||
// Original instruction is either the original instruction of the copied
|
||||
// instruction, or the instruction copied.
|
||||
copy->originalInstruction = instruction->originalInstruction.expired()
|
||||
? instruction
|
||||
: instruction->originalInstruction;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -5,7 +5,9 @@
|
||||
*/
|
||||
#ifndef INSTRUCTION_H
|
||||
#define INSTRUCTION_H
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/Expression.h"
|
||||
#include "GDCore/Events/InstructionsList.h"
|
||||
#include "GDCore/String.h"
|
||||
@@ -131,6 +133,17 @@ class GD_CORE_API Instruction {
|
||||
*/
|
||||
inline gd::InstructionsList& GetSubInstructions() { return subInstructions; };
|
||||
|
||||
/**
|
||||
* \brief Return the original instruction this instruction was copied from.
|
||||
*
|
||||
* Useful to get reference to the original instruction in memory during code
|
||||
* generation, to ensure stable unique identifiers.
|
||||
*/
|
||||
std::weak_ptr<Instruction> GetOriginalInstruction() { return originalInstruction; };
|
||||
|
||||
friend std::shared_ptr<Instruction> CloneRememberingOriginalElement(
|
||||
std::shared_ptr<Instruction> instruction);
|
||||
|
||||
private:
|
||||
gd::String type; ///< Instruction type
|
||||
bool inverted; ///< True if the instruction if inverted. Only applicable for
|
||||
@@ -139,9 +152,23 @@ class GD_CORE_API Instruction {
|
||||
parameters; ///< Vector containing the parameters
|
||||
gd::InstructionsList subInstructions; ///< Sub instructions, if applicable.
|
||||
|
||||
std::weak_ptr<Instruction>
|
||||
originalInstruction; ///< Pointer used to remember which gd::Instruction
|
||||
///< this instruction was copied from. Useful to
|
||||
///< ensure the stability of code generation (as
|
||||
///< some part of code generation uses the pointer
|
||||
///< to the instruction as a unique identifier).
|
||||
|
||||
static gd::Expression badExpression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone the given instruction, returning an instruction for which
|
||||
* `GetOriginalInstruction()` returns the originally copied instruction.
|
||||
*/
|
||||
std::shared_ptr<Instruction> GD_CORE_API
|
||||
CloneRememberingOriginalElement(std::shared_ptr<Instruction> instruction);
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // INSTRUCTION_H
|
||||
|
@@ -49,9 +49,15 @@ InstructionSentenceFormatter::GetAsFormattedText(
|
||||
|
||||
gd::String sentence = metadata.GetSentence();
|
||||
std::replace(sentence.Raw().begin(), sentence.Raw().end(), '\n', ' ');
|
||||
bool parse = true;
|
||||
|
||||
size_t loopCount = 0;
|
||||
bool parse = true;
|
||||
while (parse) {
|
||||
if (loopCount > 40) {
|
||||
break;
|
||||
}
|
||||
loopCount++;
|
||||
|
||||
// Search first parameter
|
||||
parse = false;
|
||||
size_t firstParamPosition = gd::String::npos;
|
||||
|
@@ -5,10 +5,12 @@
|
||||
*/
|
||||
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/UUID/UUID.h"
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#endif
|
||||
@@ -27,7 +29,8 @@ InitialInstance::InitialInstance()
|
||||
personalizedSize(false),
|
||||
width(0),
|
||||
height(0),
|
||||
locked(false) {}
|
||||
locked(false),
|
||||
persistentUuid(UUID::MakeUuid4()) {}
|
||||
|
||||
void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
SetObjectName(element.GetStringAttribute("name", "", "nom"));
|
||||
@@ -42,6 +45,9 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
SetLayer(element.GetStringAttribute("layer"));
|
||||
SetLocked(element.GetBoolAttribute("locked", false));
|
||||
|
||||
persistentUuid = element.GetStringAttribute("persistentUuid");
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
|
||||
floatInfos.clear();
|
||||
const SerializerElement& floatPropElement =
|
||||
element.GetChild("numberProperties", 0, "floatInfos");
|
||||
@@ -79,6 +85,9 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("height", GetCustomHeight());
|
||||
element.SetAttribute("locked", IsLocked());
|
||||
|
||||
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
|
||||
element.SetStringAttribute("persistentUuid", persistentUuid);
|
||||
|
||||
SerializerElement& floatPropElement = element.AddChild("numberProperties");
|
||||
floatPropElement.ConsiderAsArrayOf("property");
|
||||
for (std::map<gd::String, float>::const_iterator floatInfo =
|
||||
@@ -104,6 +113,11 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
GetVariables().SerializeTo(element.AddChild("initialVariables"));
|
||||
}
|
||||
|
||||
InitialInstance& InitialInstance::ResetPersistentUuid() {
|
||||
persistentUuid = UUID::MakeUuid4();
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
InitialInstance::GetCustomProperties(gd::Project& project, gd::Layout& layout) {
|
||||
@@ -146,13 +160,12 @@ const gd::String& InitialInstance::GetRawStringProperty(
|
||||
return it != stringInfos.end() ? it->second : *badStringProperyValue;
|
||||
}
|
||||
|
||||
void InitialInstance::SetRawFloatProperty(const gd::String& name, float value)
|
||||
{
|
||||
void InitialInstance::SetRawFloatProperty(const gd::String& name, float value) {
|
||||
floatInfos[name] = value;
|
||||
}
|
||||
|
||||
void InitialInstance::SetRawStringProperty(const gd::String& name, const gd::String& value)
|
||||
{
|
||||
void InitialInstance::SetRawStringProperty(const gd::String& name,
|
||||
const gd::String& value) {
|
||||
stringInfos[name] = value;
|
||||
}
|
||||
#endif
|
||||
|
@@ -200,7 +200,7 @@ class GD_CORE_API InitialInstance {
|
||||
/**
|
||||
* \brief Get the value of a float property stored in the instance.
|
||||
* \note Only use this when \a GetCustomProperties is too slow (when rendering
|
||||
* instances for example).
|
||||
* instances for example).
|
||||
* \return the value of the property, or 0 if it does
|
||||
* not exists.
|
||||
*/
|
||||
@@ -209,7 +209,7 @@ class GD_CORE_API InitialInstance {
|
||||
/**
|
||||
* \brief Get the value of a string property stored in the instance.
|
||||
* \note Only use this when \a GetCustomProperties is too slow (when rendering
|
||||
* instances for example).
|
||||
* instances for example).
|
||||
* \return the value of the propety, or an empty
|
||||
* string if it does not exists.
|
||||
*/
|
||||
@@ -240,6 +240,12 @@ class GD_CORE_API InitialInstance {
|
||||
* \brief Unserialize the instances container.
|
||||
*/
|
||||
virtual void UnserializeFrom(const SerializerElement& element);
|
||||
|
||||
/**
|
||||
* \brief Reset the persistent UUID used to recognize
|
||||
* the same initial instance between serialization.
|
||||
*/
|
||||
InitialInstance& ResetPersistentUuid();
|
||||
///@}
|
||||
|
||||
// More properties can be stored in floatInfos and stringInfos.
|
||||
@@ -260,6 +266,7 @@ class GD_CORE_API InitialInstance {
|
||||
float height; ///< Object custom height
|
||||
gd::VariablesContainer initialVariables; ///< Instance specific variables
|
||||
bool locked; ///< True if the instance is locked
|
||||
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID, useful for hot reloading.
|
||||
|
||||
static gd::String*
|
||||
badStringProperyValue; ///< Empty string returned by GetRawStringProperty
|
||||
|
@@ -90,7 +90,7 @@ template <typename T>
|
||||
void SPtrList<T>::Init(const gd::SPtrList<T>& other) {
|
||||
elements.clear();
|
||||
for (size_t i = 0; i < other.elements.size(); ++i)
|
||||
elements.push_back(std::make_shared<T>(other[i]));
|
||||
elements.push_back(CloneRememberingOriginalElement(other.elements[i]));
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
24
Core/GDCore/Tools/UUID/UUID.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef GDCORE_TOOLS_UUID_UUID_H
|
||||
#define GDCORE_TOOLS_UUID_UUID_H
|
||||
|
||||
#include "GDCore/String.h"
|
||||
#include "sole.h"
|
||||
|
||||
namespace gd {
|
||||
namespace UUID {
|
||||
|
||||
/**
|
||||
* Generate a random UUID v4
|
||||
*/
|
||||
inline gd::String MakeUuid4() { return gd::String::From(sole::uuid4()); }
|
||||
|
||||
} // namespace UUID
|
||||
} // namespace gd
|
||||
|
||||
#endif
|
249
Core/GDCore/Tools/UUID/sole.h
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* Modified version of sole (https://github.com/r-lyeh-archived/sole) C++11 library
|
||||
* to only generate UUID v4.
|
||||
*
|
||||
* Sole is a lightweight C++11 library to generate universally unique identificators.
|
||||
* Sole provides interface for UUID versions 0, 1 and 4.
|
||||
*
|
||||
* https://github.com/r-lyeh/sole
|
||||
* Copyright (c) 2013,2014,2015 r-lyeh. zlib/libpng licensed.
|
||||
*
|
||||
* Based on code by Dmitri Bouianov, Philip O'Toole, Poco C++ libraries and anonymous
|
||||
* code found on the net. Thanks guys!
|
||||
*
|
||||
* Theory: (see Hoylen's answer at [1])
|
||||
* - UUID version 1 (48-bit MAC address + 60-bit clock with a resolution of 100ns)
|
||||
* Clock wraps in 3603 A.D.
|
||||
* Up to 10000000 UUIDs per second.
|
||||
* MAC address revealed.
|
||||
*
|
||||
* - UUID Version 4 (122-bits of randomness)
|
||||
* See [2] or other analysis that describe how very unlikely a duplicate is.
|
||||
*
|
||||
* - Use v1 if you need to sort or classify UUIDs per machine.
|
||||
* Use v1 if you are worried about leaving it up to probabilities (e.g. your are the
|
||||
* type of person worried about the earth getting destroyed by a large asteroid in your
|
||||
* lifetime). Just use a v1 and it is guaranteed to be unique till 3603 AD.
|
||||
*
|
||||
* - Use v4 if you are worried about security issues and determinism. That is because
|
||||
* v1 UUIDs reveal the MAC address of the machine it was generated on and they can be
|
||||
* predictable. Use v4 if you need more than 10 million uuids per second, or if your
|
||||
* application wants to live past 3603 A.D.
|
||||
* Additionally a custom UUID v0 is provided:
|
||||
* - 16-bit PID + 48-bit MAC address + 60-bit clock with a resolution of 100ns since Unix epoch
|
||||
* - Format is EPOCH_LOW-EPOCH_MID-VERSION(0)|EPOCH_HI-PID-MAC
|
||||
* - Clock wraps in 3991 A.D.
|
||||
* - Up to 10000000 UUIDs per second.
|
||||
* - MAC address and PID revealed.
|
||||
* References:
|
||||
* - [1] http://stackoverflow.com/questions/1155008/how-unique-is-uuid
|
||||
* - [2] http://en.wikipedia.org/wiki/UUID#Random%5FUUID%5Fprobability%5Fof%5Fduplicates
|
||||
* - http://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
* - http://en.cppreference.com/w/cpp/numeric/random/random_device
|
||||
* - http://www.itu.int/ITU-T/asn1/uuid.html f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
||||
* - rlyeh ~~ listening to Hedon Cries / Until The Sun Goes up
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdio.h> // for size_t; should be stddef.h instead; however, clang+archlinux fails when compiling it (@Travis-Ci)
|
||||
#include <sys/types.h> // for uint32_t; should be stdint.h instead; however, GCC 5 on OSX fails when compiling it (See issue #11)
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
// public API
|
||||
|
||||
namespace sole
|
||||
{
|
||||
// 128-bit basic UUID type that allows comparison and sorting.
|
||||
// Use .str() for printing and .pretty() for pretty printing.
|
||||
// Also, ostream friendly.
|
||||
struct uuid
|
||||
{
|
||||
uint64_t ab;
|
||||
uint64_t cd;
|
||||
|
||||
bool operator==( const uuid &other ) const;
|
||||
bool operator!=( const uuid &other ) const;
|
||||
bool operator <( const uuid &other ) const;
|
||||
|
||||
std::string base62() const;
|
||||
std::string str() const;
|
||||
|
||||
template<typename ostream>
|
||||
inline friend ostream &operator<<( ostream &os, const uuid &self ) {
|
||||
return os << self.str(), os;
|
||||
}
|
||||
};
|
||||
|
||||
// Generators
|
||||
uuid uuid4(); // UUID v4, pros: anonymous, fast; con: uuids "can clash"
|
||||
|
||||
// Rebuilders
|
||||
uuid rebuild( uint64_t ab, uint64_t cd );
|
||||
uuid rebuild( const std::string &uustr );
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4127)
|
||||
#endif
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash< sole::uuid > {
|
||||
public:
|
||||
// hash functor: hash uuid to size_t value by pseudorandomizing transform
|
||||
size_t operator()( const sole::uuid &uuid ) const {
|
||||
if( sizeof(size_t) > 4 ) {
|
||||
return size_t( uuid.ab ^ uuid.cd );
|
||||
} else {
|
||||
uint64_t hash64 = uuid.ab ^ uuid.cd;
|
||||
return size_t( uint32_t( hash64 >> 32 ) ^ uint32_t( hash64 ) );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
// implementation
|
||||
|
||||
#include <memory.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
inline bool sole::uuid::operator==( const sole::uuid &other ) const {
|
||||
return ab == other.ab && cd == other.cd;
|
||||
}
|
||||
inline bool sole::uuid::operator!=( const sole::uuid &other ) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
inline bool sole::uuid::operator<( const sole::uuid &other ) const {
|
||||
if( ab < other.ab ) return true;
|
||||
if( ab > other.ab ) return false;
|
||||
if( cd < other.cd ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace sole {
|
||||
|
||||
inline std::string uuid::str() const {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::nouppercase << std::setfill('0');
|
||||
|
||||
uint32_t a = (ab >> 32);
|
||||
uint32_t b = (ab & 0xFFFFFFFF);
|
||||
uint32_t c = (cd >> 32);
|
||||
uint32_t d = (cd & 0xFFFFFFFF);
|
||||
|
||||
ss << std::setw(8) << (a) << '-';
|
||||
ss << std::setw(4) << (b >> 16) << '-';
|
||||
ss << std::setw(4) << (b & 0xFFFF) << '-';
|
||||
ss << std::setw(4) << (c >> 16 ) << '-';
|
||||
ss << std::setw(4) << (c & 0xFFFF);
|
||||
ss << std::setw(8) << d;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
inline std::string uuid::base62() const {
|
||||
int base62len = 10 + 26 + 26;
|
||||
const char base62[] =
|
||||
"0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
char res[24], *end = &res[24]; *(--end) = '\0';
|
||||
uint64_t rem, AB = ab, CD = cd;
|
||||
do {
|
||||
rem = CD % base62len;
|
||||
*--end = base62[int(rem)];
|
||||
CD /= base62len;
|
||||
} while (CD > 0);
|
||||
*--end = '-';
|
||||
do {
|
||||
rem = AB % base62len;
|
||||
*--end = base62[int(rem)];
|
||||
AB /= base62len;
|
||||
} while (AB > 0);
|
||||
return end;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// UUID implementations
|
||||
|
||||
inline uuid uuid4() {
|
||||
static std::random_device rd;
|
||||
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
|
||||
|
||||
uuid my;
|
||||
|
||||
my.ab = dist(rd);
|
||||
my.cd = dist(rd);
|
||||
|
||||
my.ab = (my.ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
|
||||
my.cd = (my.cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
|
||||
|
||||
return my;
|
||||
}
|
||||
|
||||
inline uuid rebuild( uint64_t ab, uint64_t cd ) {
|
||||
uuid u;
|
||||
u.ab = ab; u.cd = cd;
|
||||
return u;
|
||||
}
|
||||
|
||||
inline uuid rebuild( const std::string &uustr ) {
|
||||
char sep;
|
||||
uint64_t a,b,c,d,e;
|
||||
uuid u = { 0, 0 };
|
||||
auto idx = uustr.find_first_of("-");
|
||||
if( idx != std::string::npos ) {
|
||||
// single separator, base62 notation
|
||||
if( uustr.find_first_of("-",idx+1) == std::string::npos ) {
|
||||
auto rebase62 = [&]( const char *input, size_t limit ) -> uint64_t {
|
||||
int base62len = 10 + 26 + 26;
|
||||
auto strpos = []( char ch ) -> size_t {
|
||||
if( ch >= 'a' ) return ch - 'a' + 10 + 26;
|
||||
if( ch >= 'A' ) return ch - 'A' + 10;
|
||||
return ch - '0';
|
||||
};
|
||||
uint64_t res = strpos( input[0] );
|
||||
for( size_t i = 1; i < limit; ++i )
|
||||
res = base62len * res + strpos( input[i] );
|
||||
return res;
|
||||
};
|
||||
u.ab = rebase62( &uustr[0], idx );
|
||||
u.cd = rebase62( &uustr[idx+1], uustr.size() - (idx+1) );
|
||||
}
|
||||
// else classic hex notation
|
||||
else {
|
||||
std::stringstream ss( uustr );
|
||||
if( ss >> std::hex >> a >> sep >> b >> sep >> c >> sep >> d >> sep >> e ) {
|
||||
if( ss.eof() ) {
|
||||
u.ab = (a << 32) | (b << 16) | c;
|
||||
u.cd = (d << 48) | e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
} // ::sole
|
@@ -40,6 +40,26 @@ gdjs.AnchorRuntimeBehavior.VerticalAnchor = {
|
||||
PROPORTIONAL: 3
|
||||
};
|
||||
|
||||
gdjs.AnchorRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.leftEdgeAnchor !== newBehaviorData.leftEdgeAnchor) {
|
||||
this._leftEdgeAnchor = newBehaviorData.leftEdgeAnchor;
|
||||
}
|
||||
if (oldBehaviorData.rightEdgeAnchor !== newBehaviorData.rightEdgeAnchor) {
|
||||
this._rightEdgeAnchor = newBehaviorData.rightEdgeAnchor;
|
||||
}
|
||||
if (oldBehaviorData.topEdgeAnchor !== newBehaviorData.topEdgeAnchor) {
|
||||
this._topEdgeAnchor = newBehaviorData.topEdgeAnchor;
|
||||
}
|
||||
if (oldBehaviorData.bottomEdgeAnchor !== newBehaviorData.bottomEdgeAnchor) {
|
||||
this._bottomEdgeAnchor = newBehaviorData.bottomEdgeAnchor;
|
||||
}
|
||||
if (oldBehaviorData.relativeToOriginalWindowSize !== newBehaviorData.relativeToOriginalWindowSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.AnchorRuntimeBehavior.prototype.onActivate = function() {
|
||||
this._invalidDistances = true;
|
||||
};
|
||||
|
@@ -65,14 +65,49 @@ gdjs.BBTextRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {BBTextObjectDataType} oldObjectData
|
||||
* @param {BBTextObjectDataType} newObjectData
|
||||
*/
|
||||
gdjs.BBTextRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
||||
this.setOpacity(newObjectData.content.opacity);
|
||||
}
|
||||
if (oldObjectData.content.visible !== newObjectData.content.visible) {
|
||||
this.hide(!newObjectData.content.visible);
|
||||
}
|
||||
if (oldObjectData.content.text !== newObjectData.content.text) {
|
||||
this.setBBText(newObjectData.content.text);
|
||||
}
|
||||
if (oldObjectData.content.color !== newObjectData.content.color) {
|
||||
this._color = newObjectData.content.color;
|
||||
this._renderer.updateColor();
|
||||
}
|
||||
if (oldObjectData.content.fontFamily !== newObjectData.content.fontFamily) {
|
||||
this.setFontFamily(newObjectData.content.fontFamily);
|
||||
}
|
||||
if (oldObjectData.content.fontSize !== newObjectData.content.fontSize) {
|
||||
this.setFontSize(newObjectData.content.fontSize);
|
||||
}
|
||||
if (oldObjectData.content.wordWrap !== newObjectData.content.wordWrap) {
|
||||
this.setWordWrap(newObjectData.content.wordWrap);
|
||||
}
|
||||
if (oldObjectData.content.align !== newObjectData.content.align) {
|
||||
this.setAlignment(newObjectData.content.align);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the extra parameters that could be set for an instance.
|
||||
* @private
|
||||
*/
|
||||
gdjs.BBTextRuntimeObject.prototype.extraInitializationFromInitialInstance = function(initialInstanceData) {
|
||||
// The wrapping width value (this._wrappingWidth) is using the object's width as an innitial value
|
||||
if (initialInstanceData.customSize)
|
||||
this.setWrappingWidth(initialInstanceData.width);
|
||||
else
|
||||
this.setWrappingWidth(250); // This value is the default wrapping width of the runtime object.
|
||||
};
|
||||
|
||||
gdjs.BBTextRuntimeObject.prototype.onDestroyFromScene = function(runtimeScene) {
|
||||
|
@@ -21,7 +21,7 @@ class GD_EXTENSION_API DestroyOutsideBehavior : public gd::Behavior {
|
||||
public:
|
||||
DestroyOutsideBehavior(){};
|
||||
virtual ~DestroyOutsideBehavior(){};
|
||||
virtual Behavior* Clone() const { return new DestroyOutsideBehavior(*this); }
|
||||
virtual Behavior* Clone() const override { return new DestroyOutsideBehavior(*this); }
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
|
||||
|
@@ -20,6 +20,14 @@ gdjs.DestroyOutsideRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
gdjs.DestroyOutsideRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("DestroyOutsideBehavior::DestroyOutside", gdjs.DestroyOutsideRuntimeBehavior);
|
||||
|
||||
gdjs.DestroyOutsideRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
|
||||
this._extraBorder = newBehaviorData.extraBorder;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.DestroyOutsideRuntimeBehavior.prototype.doStepPostEvents = function(runtimeScene) {
|
||||
|
||||
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
|
||||
|
@@ -25,6 +25,11 @@ gdjs.DraggableRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
gdjs.DraggableRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("DraggableBehavior::Draggable", gdjs.DraggableRuntimeBehavior);
|
||||
|
||||
gdjs.DraggableRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
// Nothing to update.
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.DraggableRuntimeBehavior.prototype.onDeActivate = function() {
|
||||
this._endDrag();
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ describe('gdjs.DraggableRuntimeBehavior', function() {
|
||||
});
|
||||
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers:[{name:"", visibility: true}],
|
||||
layers:[{name:"", visibility: true, effects: []}],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
|
@@ -22,6 +22,12 @@ gdjs.DummyRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
gdjs.DummyRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("MyDummyExtension::DummyBehavior", gdjs.DummyRuntimeBehavior);
|
||||
|
||||
gdjs.DummyRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.property1 !== newBehaviorData.property1) {
|
||||
this._textToSet = newBehaviorData.property1;
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.DummyRuntimeBehavior.prototype.onDeActivate = function() {
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The PIXI.js renderer for the DummyRuntimeObject.
|
||||
*
|
||||
*
|
||||
* @class DummyRuntimeObjectPixiRenderer
|
||||
* @constructor
|
||||
* @param {gdjs.DummyRuntimeObject} runtimeObject The object to render
|
||||
@@ -9,7 +9,7 @@
|
||||
gdjs.DummyRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene)
|
||||
{
|
||||
this._object = runtimeObject; // Keep a reference to the object to read from it later.
|
||||
|
||||
|
||||
// Here we're going to create a dummy text as an example.
|
||||
if ( this._text === undefined ) {
|
||||
this._text = new PIXI.Text(runtimeObject.getText(), {align:"left"});
|
||||
@@ -38,6 +38,10 @@ gdjs.DummyRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function() {
|
||||
this.updatePosition();
|
||||
};
|
||||
|
||||
gdjs.DummyRuntimeObjectPixiRenderer.prototype.updateText = function() {
|
||||
this._text.text = this._object.getText();
|
||||
};
|
||||
|
||||
gdjs.DummyRuntimeObjectPixiRenderer.prototype.updatePosition = function() {
|
||||
this._text.position.x = this._object.x+this._text.width/2;
|
||||
this._text.position.y = this._object.y+this._text.height/2;
|
||||
|
@@ -28,6 +28,17 @@ gdjs.DummyRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
||||
gdjs.DummyRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
// Compare previous and new data for the object and update it accordingly.
|
||||
// This is useful for "hot-reloading".
|
||||
if (oldObjectData.content.property1 !== newObjectData.content.property1) {
|
||||
this._property1 = newObjectData.content.property1;
|
||||
this._renderer.updateText();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called once during the game loop, before events and rendering.
|
||||
* @param {gdjs.RuntimeScene} runtimeScene The gdjs.RuntimeScene the object belongs to.
|
||||
@@ -109,7 +120,6 @@ gdjs.DummyRuntimeObject.prototype.getText = function() {
|
||||
return this._property1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A dummy method that can be called from events
|
||||
*/
|
||||
|
@@ -23,6 +23,14 @@ gdjs.DummyWithSharedDataRuntimeBehavior = function(runtimeScene, behaviorData, o
|
||||
gdjs.DummyWithSharedDataRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("MyDummyExtension::DummyBehaviorWithSharedData", gdjs.DummyRuntimeBehavior);
|
||||
|
||||
gdjs.DummyWithSharedDataRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.property1 !== newBehaviorData.property1) {
|
||||
this._textToSet = newBehaviorData.property1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.DummyWithSharedDataRuntimeBehavior.prototype.onDeActivate = function() {
|
||||
};
|
||||
|
||||
|
@@ -3,7 +3,7 @@ describe('gdjs.LinksManager', function() {
|
||||
var runtimeGame = new gdjs.RuntimeGame({variables: [], properties: {windowWidth: 800, windowHeight: 600}, resources: {resources: []}});
|
||||
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers:[{name:"", visibility: true}],
|
||||
layers:[{name:"", visibility: true, effects: []}],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
|
@@ -13,7 +13,7 @@
|
||||
* @property {number} width The object width
|
||||
* @property {number} height The object height
|
||||
* @property {string} texture The name of the resource containing the texture to use
|
||||
*
|
||||
*
|
||||
* @typedef {ObjectData & PanelSpriteObjectDataType} PanelSpriteObjectData
|
||||
*/
|
||||
|
||||
@@ -80,6 +80,49 @@ gdjs.PanelSpriteRuntimeObject.prototype = Object.create(
|
||||
);
|
||||
gdjs.registerObject("PanelSpriteObject::PanelSprite", gdjs.PanelSpriteRuntimeObject);
|
||||
|
||||
/**
|
||||
* @param {PanelSpriteObjectData} oldObjectData
|
||||
* @param {PanelSpriteObjectData} newObjectData
|
||||
*/
|
||||
gdjs.PanelSpriteRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.width !== newObjectData.width) {
|
||||
this.setWidth(newObjectData.width);
|
||||
}
|
||||
if (oldObjectData.height !== newObjectData.height) {
|
||||
this.setHeight(newObjectData.height);
|
||||
}
|
||||
|
||||
var updateTexture = false;
|
||||
if (oldObjectData.rightMargin !== newObjectData.rightMargin) {
|
||||
this._rBorder = newObjectData.rightMargin;
|
||||
updateTexture = true;
|
||||
}
|
||||
if (oldObjectData.leftMargin !== newObjectData.leftMargin) {
|
||||
this._lBorder = newObjectData.leftMargin;
|
||||
updateTexture = true;
|
||||
}
|
||||
if (oldObjectData.topMargin !== newObjectData.topMargin) {
|
||||
this._tBorder = newObjectData.topMargin;
|
||||
updateTexture = true;
|
||||
}
|
||||
if (oldObjectData.bottomMargin !== newObjectData.bottomMargin) {
|
||||
this._bBorder = newObjectData.bottomMargin;
|
||||
updateTexture = true;
|
||||
}
|
||||
if (oldObjectData.texture !== newObjectData.texture) {
|
||||
updateTexture = true;
|
||||
}
|
||||
if (updateTexture) {
|
||||
this.setTexture(newObjectData.texture, this._runtimeScene);
|
||||
}
|
||||
|
||||
if (oldObjectData.tiled !== newObjectData.tiled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.PanelSpriteRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
@@ -194,7 +194,7 @@ gdjs.ParticleEmitterObjectCocosRenderer = function(runtimeScene, runtimeObject,
|
||||
|
||||
this.started = false;
|
||||
|
||||
var renderer = runtimeScene.getLayer("").getRenderer();
|
||||
var renderer = runtimeScene.getLayer(runtimeObject.getLayer()).getRenderer();
|
||||
renderer.addRendererObject(this.renderer, runtimeObject.getZOrder());
|
||||
this._convertYPosition = renderer.convertYPosition;
|
||||
};
|
||||
|
@@ -152,7 +152,7 @@ gdjs.ParticleEmitterObjectPixiRenderer = function(runtimeScene, runtimeObject, o
|
||||
this.emitter.emit = true;
|
||||
this.started = false;
|
||||
|
||||
var layer = runtimeScene.getLayer("");
|
||||
var layer = runtimeScene.getLayer(runtimeObject.getLayer());
|
||||
if (layer) layer.getRenderer().addRendererObject(this.renderer, runtimeObject.getZOrder());
|
||||
};
|
||||
gdjs.ParticleEmitterObjectRenderer = gdjs.ParticleEmitterObjectPixiRenderer;
|
||||
|
@@ -24,17 +24,19 @@
|
||||
* @property {number} particleBlue2
|
||||
* @property {number} particleSize1
|
||||
* @property {number} particleSize2
|
||||
* @property {number} sizeParam
|
||||
* @property {number} particleAngle1
|
||||
* @property {number} particleAngle2
|
||||
* @property {string} sizeParam
|
||||
* @property {number} particleAlpha1
|
||||
* @property {number} particleAlpha2
|
||||
* @property {string} rendererType
|
||||
* @property {number} rendererParam1
|
||||
* @property {number} rendererParam1
|
||||
* @property {number} rendererParam2
|
||||
* @property {string} textureParticleName Resource name for image in particle
|
||||
* @property {number} flow
|
||||
* @property {number} tank
|
||||
* @property {boolean} destroyWhenNoParticles Destroy the object when there is no particles?
|
||||
*
|
||||
*
|
||||
* @typedef {ObjectData & ParticleEmitterObjectDataType} ParticleEmitterObjectData
|
||||
*/
|
||||
|
||||
@@ -107,7 +109,7 @@ gdjs.ParticleEmitterObject = function(runtimeScene, particleObjectData){
|
||||
/** @type {number} */
|
||||
this.size2 = particleObjectData.particleSize2;
|
||||
|
||||
/** @type {number} */
|
||||
/** @type {string} */
|
||||
this.sizeParam = particleObjectData.sizeParam;
|
||||
|
||||
/** @type {number} */
|
||||
@@ -121,7 +123,7 @@ gdjs.ParticleEmitterObject = function(runtimeScene, particleObjectData){
|
||||
|
||||
/** @type {number} */
|
||||
this.rendererParam1 = particleObjectData.rendererParam1;
|
||||
|
||||
|
||||
/** @type {number} */
|
||||
this.rendererParam2 = particleObjectData.rendererParam2;
|
||||
|
||||
@@ -185,6 +187,116 @@ gdjs.ParticleEmitterObject.prototype.getRendererObject = function(){
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ParticleEmitterObjectData} oldObjectData
|
||||
* @param {ParticleEmitterObjectData} newObjectData
|
||||
*/
|
||||
gdjs.ParticleEmitterObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.emissionEditionSimpleMode !== newObjectData.emissionEditionSimpleMode) {
|
||||
this.singleAngle = newObjectData.emissionEditionSimpleMode;
|
||||
this._angleDirty = true;
|
||||
}
|
||||
if (oldObjectData.emitterAngleA !== newObjectData.emitterAngleA) {
|
||||
this.setEmitterAngleA(newObjectData.emitterAngleA);
|
||||
}
|
||||
if (oldObjectData.emitterAngleB !== newObjectData.emitterAngleB) {
|
||||
this.setEmitterAngleB(newObjectData.emitterAngleB);
|
||||
}
|
||||
if (oldObjectData.emitterForceMin !== newObjectData.emitterForceMin) {
|
||||
this.setEmitterForceMin(newObjectData.emitterForceMin);
|
||||
}
|
||||
if (oldObjectData.emitterForceMax !== newObjectData.emitterForceMax) {
|
||||
this.setEmitterForceMax(newObjectData.emitterForceMax);
|
||||
}
|
||||
if (oldObjectData.zoneRadius !== newObjectData.zoneRadius) {
|
||||
this.setZoneRadius(newObjectData.zoneRadius);
|
||||
}
|
||||
if (oldObjectData.particleLifeTimeMin !== newObjectData.particleLifeTimeMin) {
|
||||
this.setParticleLifeTimeMin(newObjectData.particleLifeTimeMin);
|
||||
}
|
||||
if (oldObjectData.particleLifeTimeMax !== newObjectData.particleLifeTimeMax) {
|
||||
this.setParticleLifeTimeMax(newObjectData.particleLifeTimeMax);
|
||||
}
|
||||
if (oldObjectData.particleGravityX !== newObjectData.particleGravityX) {
|
||||
this.setParticleGravityX(newObjectData.particleGravityX);
|
||||
}
|
||||
if (oldObjectData.particleGravityY !== newObjectData.particleGravityY) {
|
||||
this.setParticleGravityY(newObjectData.particleGravityY);
|
||||
}
|
||||
if (oldObjectData.particleRed1 !== newObjectData.particleRed1) {
|
||||
this.setParticleRed1(newObjectData.particleRed1);
|
||||
}
|
||||
if (oldObjectData.particleRed2 !== newObjectData.particleRed2) {
|
||||
this.setParticleRed2(newObjectData.particleRed2);
|
||||
}
|
||||
if (oldObjectData.particleGreen1 !== newObjectData.particleGreen1) {
|
||||
this.setParticleGreen1(newObjectData.particleGreen1);
|
||||
}
|
||||
if (oldObjectData.particleGreen2 !== newObjectData.particleGreen2) {
|
||||
this.setParticleGreen2(newObjectData.particleGreen2);
|
||||
}
|
||||
if (oldObjectData.particleBlue1 !== newObjectData.particleBlue1) {
|
||||
this.setParticleBlue1(newObjectData.particleBlue1);
|
||||
}
|
||||
if (oldObjectData.particleBlue2 !== newObjectData.particleBlue2) {
|
||||
this.setParticleBlue2(newObjectData.particleBlue2);
|
||||
}
|
||||
if (oldObjectData.particleSize1 !== newObjectData.particleSize1) {
|
||||
this.setParticleSize1(newObjectData.particleSize1);
|
||||
}
|
||||
if (oldObjectData.particleSize2 !== newObjectData.particleSize2) {
|
||||
this.setParticleSize2(newObjectData.particleSize2);
|
||||
}
|
||||
if (oldObjectData.sizeParam !== newObjectData.sizeParam) {
|
||||
this.sizeParam = newObjectData.sizeParam;
|
||||
this._sizeDirty = true;
|
||||
}
|
||||
if (oldObjectData.particleAlpha1 !== newObjectData.particleAlpha1) {
|
||||
this.setParticleAlpha1(newObjectData.particleAlpha1);
|
||||
}
|
||||
if (oldObjectData.particleAlpha2 !== newObjectData.particleAlpha2) {
|
||||
this.setParticleAlpha2(newObjectData.particleAlpha2);
|
||||
}
|
||||
if (oldObjectData.textureParticleName !== newObjectData.textureParticleName) {
|
||||
this.setTexture(newObjectData.textureParticleName, this._runtimeScene);
|
||||
}
|
||||
if (oldObjectData.flow !== newObjectData.flow) {
|
||||
this.setFlow(newObjectData.flow);
|
||||
}
|
||||
if (oldObjectData.tank !== newObjectData.tank) {
|
||||
this.setTank(newObjectData.tank);
|
||||
}
|
||||
if (oldObjectData.destroyWhenNoParticles !== newObjectData.destroyWhenNoParticles) {
|
||||
this.destroyWhenNoParticles = newObjectData.destroyWhenNoParticles;
|
||||
}
|
||||
|
||||
if (oldObjectData.particleSizeRandomness1 !== newObjectData.particleSizeRandomness1 ||
|
||||
oldObjectData.particleSizeRandomness2 !== newObjectData.particleSizeRandomness2 ||
|
||||
oldObjectData.particleAngle1 !== newObjectData.particleAngle1 ||
|
||||
oldObjectData.particleAngle2 !== newObjectData.particleAngle2 ||
|
||||
oldObjectData.maxParticleNb !== newObjectData.maxParticleNb ||
|
||||
oldObjectData.additive !== newObjectData.additive ||
|
||||
oldObjectData.rendererType !== newObjectData.rendererType ||
|
||||
oldObjectData.rendererParam1 !== newObjectData.rendererParam1 ||
|
||||
oldObjectData.rendererParam2 !== newObjectData.rendererParam2) {
|
||||
// Destroy the renderer, ensure it's removed from the layer.
|
||||
var layer = this._runtimeScene.getLayer(this.layer);
|
||||
layer.getRenderer().removeRendererObject(this._renderer.getRendererObject());
|
||||
this._renderer.destroy();
|
||||
|
||||
// and recreate the renderer, which will add itself to the layer.
|
||||
this._renderer = new gdjs.ParticleEmitterObjectRenderer(this._runtimeScene, this, newObjectData);
|
||||
|
||||
// Consider every state dirty as the renderer was just re-created, so it needs
|
||||
// to be repositioned, angle updated, etc...
|
||||
this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = true;
|
||||
this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = true;
|
||||
this._alphaDirty = this._flowDirty = this._textureDirty = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.ParticleEmitterObject.prototype.update = function(runtimeScene){
|
||||
if(this._posDirty){
|
||||
this._renderer.setPosition(this.getX(), this.getY());
|
||||
|
@@ -104,6 +104,17 @@ gdjs.PathfindingObstacleRuntimeBehavior = function(runtimeScene, behaviorData, o
|
||||
gdjs.PathfindingObstacleRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("PathfindingBehavior::PathfindingObstacleBehavior", gdjs.PathfindingObstacleRuntimeBehavior);
|
||||
|
||||
gdjs.PathfindingObstacleRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.impassable !== newBehaviorData.impassable) {
|
||||
this.setImpassable(newBehaviorData.impassable);
|
||||
}
|
||||
if (oldBehaviorData.cost !== newBehaviorData.cost) {
|
||||
this.setCost(newBehaviorData.cost);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.PathfindingObstacleRuntimeBehavior.prototype.onDestroy = function() {
|
||||
if ( this._manager && this._registeredInManager ) this._manager.removeObstacle(this);
|
||||
};
|
||||
|
@@ -48,6 +48,38 @@ gdjs.PathfindingRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
gdjs.PathfindingRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("PathfindingBehavior::PathfindingBehavior", gdjs.PathfindingRuntimeBehavior);
|
||||
|
||||
gdjs.PathfindingRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) {
|
||||
this.allowDiagonals(newBehaviorData.allowDiagonals);
|
||||
}
|
||||
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
|
||||
this.setAcceleration(newBehaviorData.acceleration);
|
||||
}
|
||||
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
|
||||
this.setMaxSpeed(newBehaviorData.maxSpeed);
|
||||
}
|
||||
if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) {
|
||||
this.setAngularMaxSpeed(newBehaviorData.angularMaxSpeed);
|
||||
}
|
||||
if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) {
|
||||
this.setRotateObject(newBehaviorData.rotateObject);
|
||||
}
|
||||
if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) {
|
||||
this.setAngleOffset(newBehaviorData.angleOffset);
|
||||
}
|
||||
if (oldBehaviorData.cellWidth !== newBehaviorData.cellWidth) {
|
||||
this.setCellWidth(newBehaviorData.cellWidth);
|
||||
}
|
||||
if (oldBehaviorData.cellHeight !== newBehaviorData.cellHeight) {
|
||||
this.setCellHeight(newBehaviorData.cellHeight);
|
||||
}
|
||||
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
|
||||
this.setExtraBorder(newBehaviorData.extraBorder);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.PathfindingRuntimeBehavior.prototype.setCellWidth = function(width) {
|
||||
this._cellWidth = width;
|
||||
};
|
||||
|
@@ -252,6 +252,75 @@ gdjs.Physics2RuntimeBehavior.prototype.b2Vec2Sec = function(x, y) {
|
||||
return this._tempb2Vec2Sec;
|
||||
};
|
||||
|
||||
gdjs.Physics2RuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.bullet !== newBehaviorData.bullet) {
|
||||
this.setBullet(newBehaviorData.bullet);
|
||||
}
|
||||
if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) {
|
||||
this.setFixedRotation(newBehaviorData.fixedRotation);
|
||||
}
|
||||
if (oldBehaviorData.canSleep !== newBehaviorData.canSleep) {
|
||||
this.setSleepingAllowed(newBehaviorData.canSleep);
|
||||
}
|
||||
if (oldBehaviorData.shapeDimensionA !== newBehaviorData.shapeDimensionA) {
|
||||
this.shapeDimensionA = newBehaviorData.shapeDimensionA;
|
||||
this.recreateShape();
|
||||
}
|
||||
if (oldBehaviorData.shapeDimensionB !== newBehaviorData.shapeDimensionB) {
|
||||
this.shapeDimensionB = newBehaviorData.shapeDimensionB;
|
||||
this.recreateShape();
|
||||
}
|
||||
if (oldBehaviorData.shapeOffsetX !== newBehaviorData.shapeOffsetX) {
|
||||
this.shapeOffsetX = newBehaviorData.shapeOffsetX;
|
||||
this.recreateShape();
|
||||
}
|
||||
if (oldBehaviorData.shapeOffsetY !== newBehaviorData.shapeOffsetY) {
|
||||
this.shapeOffsetY = newBehaviorData.shapeOffsetY;
|
||||
this.recreateShape();
|
||||
}
|
||||
if (oldBehaviorData.polygonOrigin !== newBehaviorData.polygonOrigin) {
|
||||
this.polygonOrigin = newBehaviorData.polygonOrigin;
|
||||
this.recreateShape();
|
||||
}
|
||||
if (oldBehaviorData.density !== newBehaviorData.density) {
|
||||
this.setDensity(newBehaviorData.density);
|
||||
}
|
||||
if (oldBehaviorData.friction !== newBehaviorData.friction) {
|
||||
this.setFriction(newBehaviorData.friction);
|
||||
}
|
||||
if (oldBehaviorData.restitution !== newBehaviorData.restitution) {
|
||||
this.setRestitution(newBehaviorData.restitution);
|
||||
}
|
||||
if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) {
|
||||
this.setLinearDamping(newBehaviorData.linearDamping);
|
||||
}
|
||||
if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) {
|
||||
this.setAngularDamping(newBehaviorData.angularDamping);
|
||||
}
|
||||
if (oldBehaviorData.gravityScale !== newBehaviorData.gravityScale) {
|
||||
this.setGravityScale(newBehaviorData.gravityScale);
|
||||
}
|
||||
|
||||
// TODO: make these properties updatable.
|
||||
if (oldBehaviorData.layers !== newBehaviorData.layers) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.masks !== newBehaviorData.masks) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.vertices !== newBehaviorData.vertices) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.bodyType !== newBehaviorData.bodyType) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.shape !== newBehaviorData.shape) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() {
|
||||
if (this._body !== null) {
|
||||
// When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list
|
||||
@@ -573,10 +642,21 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPreEvents = function(
|
||||
gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
|
||||
runtimeScene
|
||||
) {
|
||||
this._updateBodyFromObject();
|
||||
|
||||
// Reset world step to update next frame
|
||||
this._sharedData.stepped = false;
|
||||
};
|
||||
|
||||
gdjs.Physics2RuntimeBehavior.prototype.onObjectHotReloaded = function() {
|
||||
this._updateBodyFromObject();
|
||||
}
|
||||
|
||||
gdjs.Physics2RuntimeBehavior.prototype._updateBodyFromObject = function() {
|
||||
// If there is no body, set a new one
|
||||
if (this._body === null) this.createBody();
|
||||
|
||||
// GD object size has changed, recreate shape
|
||||
// The object size has changed, recreate the shape.
|
||||
// The width has changed and there is no custom dimension A (box: width, circle: radius, edge: length) or
|
||||
// The height has changed, the shape is not an edge (edges doesn't have height),
|
||||
// it isn't a box with custom height or a circle with custom radius
|
||||
@@ -591,7 +671,7 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
|
||||
this.recreateShape();
|
||||
}
|
||||
|
||||
// GD object transform has changed, update body transform
|
||||
// The object object transform has changed, update body transform:
|
||||
if (
|
||||
this._objectOldX !== this.owner.getX() ||
|
||||
this._objectOldY !== this.owner.getY() ||
|
||||
@@ -606,10 +686,7 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
|
||||
this._body.SetTransform(pos, gdjs.toRad(this.owner.getAngle()));
|
||||
this._body.SetAwake(true);
|
||||
}
|
||||
|
||||
// Reset world step to update next frame
|
||||
this._sharedData.stepped = false;
|
||||
};
|
||||
}
|
||||
|
||||
gdjs.Physics2RuntimeBehavior.prototype.getGravityX = function() {
|
||||
return this._sharedData.gravityX;
|
||||
|
@@ -147,6 +147,43 @@ gdjs.PhysicsRuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
gdjs.PhysicsRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("PhysicsBehavior::PhysicsBehavior", gdjs.PhysicsRuntimeBehavior);
|
||||
|
||||
gdjs.PhysicsRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.dynamic !== newBehaviorData.dynamic) {
|
||||
if (newBehaviorData.dynamic) this.setDynamic();
|
||||
else this.setStatic();
|
||||
}
|
||||
if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) {
|
||||
this.setAngularDamping(newBehaviorData.angularDamping);
|
||||
}
|
||||
if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) {
|
||||
this.setLinearDamping(newBehaviorData.linearDamping);
|
||||
}
|
||||
if (oldBehaviorData.isBullet !== newBehaviorData.isBullet) {
|
||||
if (newBehaviorData.isBullet) this.setAsBullet();
|
||||
else this.dontSetAsBullet();
|
||||
}
|
||||
if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) {
|
||||
if (newBehaviorData.fixedRotation) this.setFixedRotation();
|
||||
else this.setFreeRotation();
|
||||
}
|
||||
|
||||
// TODO: make these properties updatable.
|
||||
if (oldBehaviorData.massDensity !== newBehaviorData.massDensity) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.averageFriction !== newBehaviorData.averageFriction) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.averageRestitution !== newBehaviorData.averageRestitution) {
|
||||
return false;
|
||||
}
|
||||
if (oldBehaviorData.shapeType !== newBehaviorData.shapeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.PhysicsRuntimeBehavior.prototype.onDeActivate = function() {
|
||||
if ( this._box2DBody !== null ) {
|
||||
this._sharedData.world.DestroyBody(this._box2DBody);
|
||||
|
@@ -77,6 +77,44 @@ gdjs.registerBehavior(
|
||||
gdjs.PlatformerObjectRuntimeBehavior
|
||||
);
|
||||
|
||||
gdjs.PlatformerObjectRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.roundCoordinates !== newBehaviorData.roundCoordinates) {
|
||||
this._roundCoordinates = newBehaviorData.roundCoordinates;
|
||||
}
|
||||
if (oldBehaviorData.gravity !== newBehaviorData.gravity) {
|
||||
this.setGravity(newBehaviorData.gravity);
|
||||
}
|
||||
if (oldBehaviorData.maxFallingSpeed !== newBehaviorData.maxFallingSpeed) {
|
||||
this.setMaxFallingSpeed(newBehaviorData.maxFallingSpeed);
|
||||
}
|
||||
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
|
||||
this.setAcceleration(newBehaviorData.acceleration);
|
||||
}
|
||||
if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) {
|
||||
this.setDeceleration(newBehaviorData.deceleration);
|
||||
}
|
||||
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
|
||||
this.setMaxSpeed(newBehaviorData.maxSpeed);
|
||||
}
|
||||
if (oldBehaviorData.jumpSpeed !== newBehaviorData.jumpSpeed) {
|
||||
this.setJumpSpeed(newBehaviorData.jumpSpeed);
|
||||
}
|
||||
if (oldBehaviorData.canGrabPlatforms !== newBehaviorData.canGrabPlatforms) {
|
||||
this.setCanGrabPlatforms(newBehaviorData.canGrabPlatforms);
|
||||
}
|
||||
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
|
||||
this._yGrabOffset = newBehaviorData.yGrabOffset;
|
||||
}
|
||||
if (oldBehaviorData.xGrabTolerance !== newBehaviorData.xGrabTolerance) {
|
||||
this._xGrabTolerance = newBehaviorData.xGrabTolerance;
|
||||
}
|
||||
if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) {
|
||||
this.setJumpSustainTime(newBehaviorData.jumpSustainTime);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.PlatformerObjectRuntimeBehavior.prototype.doStepPreEvents = function(
|
||||
runtimeScene
|
||||
) {
|
||||
|
@@ -106,6 +106,20 @@ gdjs.PlatformRuntimeBehavior.LADDER = 2;
|
||||
gdjs.PlatformRuntimeBehavior.JUMPTHRU = 1;
|
||||
gdjs.PlatformRuntimeBehavior.NORMALPLAFTORM = 0;
|
||||
|
||||
gdjs.PlatformRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.platformType !== newBehaviorData.platformType) {
|
||||
this.changePlatformType(newBehaviorData.platformType);
|
||||
}
|
||||
if (oldBehaviorData.canBeGrabbed !== newBehaviorData.canBeGrabbed) {
|
||||
this._canBeGrabbed = newBehaviorData.canBeGrabbed;
|
||||
}
|
||||
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
|
||||
this._yGrabOffset = newBehaviorData.yGrabOffset;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.PlatformRuntimeBehavior.prototype.onDestroy = function() {
|
||||
if ( this._manager && this._registeredInManager ) this._manager.removePlatform(this);
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ const makeTestRuntimeScene = () => {
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [{ name: '', visibility: true }],
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
|
@@ -19,7 +19,7 @@
|
||||
* @property {number} outlineSize The size of the outline of the painted shape, in pixels.
|
||||
* @property {boolean} absoluteCoordinates Use absolute coordinates?
|
||||
* @property {boolean} clearBetweenFrames Clear the previous render before the next draw?
|
||||
*
|
||||
*
|
||||
* @typedef {ObjectData & ShapePainterObjectDataType} ShapePainterObjectData
|
||||
*/
|
||||
|
||||
@@ -74,6 +74,50 @@ gdjs.ShapePainterRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ShapePainterObjectData} oldObjectData
|
||||
* @param {ShapePainterObjectData} newObjectData
|
||||
*/
|
||||
gdjs.ShapePainterRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.fillColor.r !== newObjectData.fillColor.r ||
|
||||
oldObjectData.fillColor.g !== newObjectData.fillColor.g ||
|
||||
oldObjectData.fillColor.b !== newObjectData.fillColor.b) {
|
||||
this.setFillColor(
|
||||
'' + newObjectData.fillColor.r + ';' +
|
||||
newObjectData.fillColor.g + ';' +
|
||||
newObjectData.fillColor.b
|
||||
);
|
||||
}
|
||||
if (oldObjectData.outlineColor.r !== newObjectData.outlineColor.r ||
|
||||
oldObjectData.outlineColor.g !== newObjectData.outlineColor.g ||
|
||||
oldObjectData.outlineColor.b !== newObjectData.outlineColor.b) {
|
||||
this.setOutlineColor(
|
||||
'' + newObjectData.outlineColor.r + ';' +
|
||||
newObjectData.outlineColor.g + ';' +
|
||||
newObjectData.outlineColor.b
|
||||
);
|
||||
}
|
||||
if (oldObjectData.fillOpacity !== newObjectData.fillOpacity) {
|
||||
this.setFillOpacity(newObjectData.fillOpacity);
|
||||
}
|
||||
if (oldObjectData.outlineOpacity !== newObjectData.outlineOpacity) {
|
||||
this.setOutlineOpacity(newObjectData.outlineOpacity);
|
||||
}
|
||||
if (oldObjectData.outlineSize !== newObjectData.outlineSize) {
|
||||
this.setOutlineSize(newObjectData.outlineSize);
|
||||
}
|
||||
if (oldObjectData.absoluteCoordinates !== newObjectData.absoluteCoordinates) {
|
||||
this._absoluteCoordinates = newObjectData.absoluteCoordinates;
|
||||
this._renderer.updateXPosition();
|
||||
this._renderer.updateYPosition();
|
||||
}
|
||||
if (oldObjectData.clearBetweenFrames !== newObjectData.clearBetweenFrames) {
|
||||
this._clearBetweenFrames = newObjectData.clearBetweenFrames
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.stepBehaviorsPreEvents = function(runtimeScene) {
|
||||
//We redefine stepBehaviorsPreEvents just to clear the graphics before running events.
|
||||
if(this._clearBetweenFrames){
|
||||
@@ -120,11 +164,11 @@ gdjs.ShapePainterRuntimeObject.prototype.drawArc = function(centerX, centerY, ra
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.drawBezierCurve = function(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2) {
|
||||
this._renderer.drawBezierCurve(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2);
|
||||
this._renderer.drawBezierCurve(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2);
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.drawQuadraticCurve = function(x1, y1, cpX, cpY, x2, y2) {
|
||||
this._renderer.drawQuadraticCurve(x1, y1, cpX, cpY, x2, y2);
|
||||
this._renderer.drawQuadraticCurve(x1, y1, cpX, cpY, x2, y2);
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.beginFillPath = function(x1, y1) {
|
||||
@@ -145,7 +189,7 @@ gdjs.ShapePainterRuntimeObject.prototype.drawPathLineTo = function(x1, y1) {
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.drawPathBezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY) {
|
||||
this._renderer.drawPathBezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
|
||||
this._renderer.drawPathBezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.drawPathArc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) {
|
||||
@@ -153,11 +197,11 @@ gdjs.ShapePainterRuntimeObject.prototype.drawPathArc = function(cx, cy, radius,
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.drawPathQuadraticCurveTo = function(cpX, cpY, toX, toY) {
|
||||
this._renderer.drawPathQuadraticCurveTo(cpX, cpY, toX, toY);
|
||||
this._renderer.drawPathQuadraticCurveTo(cpX, cpY, toX, toY);
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.closePath = function() {
|
||||
this._renderer.closePath();
|
||||
this._renderer.closePath();
|
||||
};
|
||||
|
||||
gdjs.ShapePainterRuntimeObject.prototype.setClearBetweenFrames = function(value) {
|
||||
|
@@ -35,6 +35,11 @@ gdjs.TextEntryRuntimeObject = function(runtimeScene, textEntryObjectData)
|
||||
gdjs.TextEntryRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
|
||||
gdjs.registerObject("TextEntryObject::TextEntry", gdjs.TextEntryRuntimeObject);
|
||||
|
||||
gdjs.TextEntryRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
// Nothing to update.
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.TextEntryRuntimeObject.prototype.onDestroyFromScene = function(runtimeScene) {
|
||||
gdjs.RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene);
|
||||
|
||||
|
@@ -117,6 +117,41 @@ gdjs.TextRuntimeObject = function(runtimeScene, textObjectData)
|
||||
gdjs.TextRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
|
||||
gdjs.registerObject("TextObject::Text", gdjs.TextRuntimeObject);
|
||||
|
||||
/**
|
||||
* @param {TextObjectData} oldObjectData
|
||||
* @param {TextObjectData} newObjectData
|
||||
*/
|
||||
gdjs.TextRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.characterSize !== newObjectData.characterSize) {
|
||||
this.setCharacterSize(newObjectData.characterSize);
|
||||
}
|
||||
if (oldObjectData.font !== newObjectData.font) {
|
||||
this.setFontName(newObjectData.font);
|
||||
}
|
||||
if (oldObjectData.bold !== newObjectData.bold) {
|
||||
this.setBold(newObjectData.bold);
|
||||
}
|
||||
if (oldObjectData.italic !== newObjectData.italic) {
|
||||
this.setItalic(newObjectData.italic);
|
||||
}
|
||||
if (oldObjectData.color.r !== newObjectData.color.r ||
|
||||
oldObjectData.color.g !== newObjectData.color.g ||
|
||||
oldObjectData.color.b !== newObjectData.color.b) {
|
||||
this.setColor('' + newObjectData.color.r + ';' + newObjectData.color.g +
|
||||
';' + newObjectData.color.b);
|
||||
}
|
||||
if (oldObjectData.string !== newObjectData.string) {
|
||||
this.setString(newObjectData.string);
|
||||
}
|
||||
|
||||
if (oldObjectData.underlined !== newObjectData.underlined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
gdjs.TextRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
@@ -133,6 +168,8 @@ gdjs.TextRuntimeObject.prototype.extraInitializationFromInitialInstance = functi
|
||||
if ( initialInstanceData.customSize ) {
|
||||
this.setWrapping(true);
|
||||
this.setWrappingWidth(initialInstanceData.width);
|
||||
} else {
|
||||
this.setWrapping(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,6 +261,15 @@ gdjs.TextRuntimeObject.prototype.setCharacterSize = function(newSize) {
|
||||
this._renderer.updateStyle();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the name of the resource to use for the font.
|
||||
* @param {string} fontResourceName The name of the font resource.
|
||||
*/
|
||||
gdjs.TextRuntimeObject.prototype.setFontName = function(fontResourceName) {
|
||||
this._fontName = fontResourceName;
|
||||
this._renderer.updateStyle();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the text is bold.
|
||||
*/
|
||||
|
@@ -43,6 +43,20 @@ gdjs.TiledSpriteRuntimeObject = function(runtimeScene, tiledSpriteObjectData)
|
||||
gdjs.TiledSpriteRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
|
||||
gdjs.registerObject("TiledSpriteObject::TiledSprite", gdjs.TiledSpriteRuntimeObject);
|
||||
|
||||
gdjs.TiledSpriteRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.texture !== newObjectData.texture) {
|
||||
this.setTexture(newObjectData.texture, this._runtimeScene);
|
||||
}
|
||||
if (oldObjectData.width !== newObjectData.width) {
|
||||
this.setWidth(newObjectData.width);
|
||||
}
|
||||
if (oldObjectData.height !== newObjectData.height) {
|
||||
this.setHeight(newObjectData.height);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gdjs.TiledSpriteRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
@@ -46,6 +46,34 @@ gdjs.registerBehavior(
|
||||
gdjs.TopDownMovementRuntimeBehavior
|
||||
);
|
||||
|
||||
gdjs.TopDownMovementRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) {
|
||||
this._allowDiagonals = newBehaviorData.allowDiagonals;
|
||||
}
|
||||
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
|
||||
this._acceleration = newBehaviorData.acceleration;
|
||||
}
|
||||
if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) {
|
||||
this._deceleration = newBehaviorData.deceleration;
|
||||
}
|
||||
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
|
||||
this._maxSpeed = newBehaviorData.maxSpeed;
|
||||
}
|
||||
if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) {
|
||||
this._angularMaxSpeed = newBehaviorData.angularMaxSpeed;
|
||||
}
|
||||
if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) {
|
||||
this._rotateObject = newBehaviorData.rotateObject;
|
||||
}
|
||||
if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) {
|
||||
this._angleOffset = newBehaviorData.angleOffset;
|
||||
}
|
||||
if (oldBehaviorData.ignoreDefaultControls !== newBehaviorData.ignoreDefaultControls) {
|
||||
this._ignoreDefaultControls = newBehaviorData.ignoreDefaultControls;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
gdjs.TopDownMovementRuntimeBehavior.prototype.setAcceleration = function(
|
||||
acceleration
|
||||
) {
|
||||
|
@@ -58,6 +58,11 @@ gdjs.TweenRuntimeBehavior.easings = [
|
||||
'easeTo',
|
||||
];
|
||||
|
||||
gdjs.TweenRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
// Nothing to update.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tween being played in a behavior.
|
||||
* @param {shifty.Tweenable} instance The Shifty tween that is played
|
||||
|
@@ -56,6 +56,28 @@ gdjs.VideoRuntimeObject.prototype.getRendererObject = function() {
|
||||
return this._renderer.getRendererObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {VideoObjectData} oldObjectData
|
||||
* @param {VideoObjectData} newObjectData
|
||||
*/
|
||||
gdjs.VideoRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
||||
this.setOpacity(newObjectData.content.opacity);
|
||||
}
|
||||
if (oldObjectData.content.loop !== newObjectData.content.loop) {
|
||||
this.setLoop(newObjectData.content.loop);
|
||||
}
|
||||
if (oldObjectData.content.volume !== newObjectData.content.volume) {
|
||||
this.setVolume(newObjectData.content.volume);
|
||||
}
|
||||
|
||||
if (oldObjectData.content.videoResource !== newObjectData.content.videoResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the extra parameters that could be set for an instance.
|
||||
* @private
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/Events/Builtin/CommentEvent.h"
|
||||
@@ -215,7 +216,8 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
|
||||
[](gd::Instruction& instruction,
|
||||
gd::EventsCodeGenerator& codeGenerator,
|
||||
gd::EventsCodeGenerationContext& parentContext) {
|
||||
size_t uniqueId = (size_t)&instruction;
|
||||
size_t uniqueId = codeGenerator.GenerateSingleUsageUniqueIdFor(
|
||||
instruction.GetOriginalInstruction().lock().get());
|
||||
return "conditionTrue = runtimeContext->TriggerOnce(" +
|
||||
gd::String::From(uniqueId) + ");\n";
|
||||
});
|
||||
|
@@ -96,6 +96,17 @@ gd::String BehaviorCodeGenerator::GenerateRuntimeBehaviorCompleteCode(
|
||||
}
|
||||
|
||||
return runtimeBehaviorMethodsCode;
|
||||
},
|
||||
[&]() {
|
||||
gd::String updateFromBehaviorCode;
|
||||
for (auto& property :
|
||||
eventsBasedBehavior.GetPropertyDescriptors().GetInternalVector()) {
|
||||
updateFromBehaviorCode +=
|
||||
GenerateUpdatePropertyFromBehaviorDataCode(
|
||||
eventsBasedBehavior, codeNamespace, *property);
|
||||
}
|
||||
|
||||
return updateFromBehaviorCode;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,7 +116,8 @@ gd::String BehaviorCodeGenerator::GenerateRuntimeBehaviorTemplateCode(
|
||||
const gd::String& codeNamespace,
|
||||
std::function<gd::String()> generateInitializePropertiesCode,
|
||||
std::function<gd::String()> generateMethodsCode,
|
||||
std::function<gd::String()> generatePropertiesCode) {
|
||||
std::function<gd::String()> generatePropertiesCode,
|
||||
std::function<gd::String()> generateUpdateFromBehaviorDataCode) {
|
||||
return gd::String(R"jscode_template(
|
||||
CODE_NAMESPACE = CODE_NAMESPACE || {};
|
||||
|
||||
@@ -128,6 +140,13 @@ CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME = function(runtimeScene, behaviorData,
|
||||
CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
|
||||
gdjs.registerBehavior("EXTENSION_NAME::BEHAVIOR_NAME", CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME);
|
||||
|
||||
// Hot-reload:
|
||||
CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
UPDATE_FROM_BEHAVIOR_DATA_CODE
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Properties:
|
||||
PROPERTIES_CODE
|
||||
|
||||
@@ -142,6 +161,7 @@ METHODS_CODE
|
||||
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
|
||||
.FindAndReplace("INITIALIZE_PROPERTIES_CODE",
|
||||
generateInitializePropertiesCode())
|
||||
.FindAndReplace("UPDATE_FROM_BEHAVIOR_DATA_CODE", generateUpdateFromBehaviorDataCode())
|
||||
.FindAndReplace("PROPERTIES_CODE", generatePropertiesCode())
|
||||
.FindAndReplace("METHODS_CODE", generateMethodsCode());
|
||||
;
|
||||
@@ -183,7 +203,16 @@ CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype.SETTER_NAME = function(newVa
|
||||
.FindAndReplace("RUNTIME_BEHAVIOR_CLASSNAME",
|
||||
eventsBasedBehavior.GetName())
|
||||
.FindAndReplace("CODE_NAMESPACE", codeNamespace);
|
||||
;
|
||||
}
|
||||
|
||||
gd::String BehaviorCodeGenerator::GenerateUpdatePropertyFromBehaviorDataCode(
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::String& codeNamespace,
|
||||
const gd::NamedPropertyDescriptor& property) {
|
||||
return gd::String(R"jscode_template(
|
||||
if (oldBehaviorData.PROPERTY_NAME !== newBehaviorData.PROPERTY_NAME)
|
||||
this._behaviorData.PROPERTY_NAME = newBehaviorData.PROPERTY_NAME;)jscode_template")
|
||||
.FindAndReplace("PROPERTY_NAME", property.GetName());
|
||||
}
|
||||
|
||||
gd::String BehaviorCodeGenerator::GeneratePropertyValueCode(
|
||||
|
@@ -64,7 +64,8 @@ class BehaviorCodeGenerator {
|
||||
const gd::String& codeNamespace,
|
||||
std::function<gd::String()> generateInitializePropertiesCode,
|
||||
std::function<gd::String()> generateMethodsCode,
|
||||
std::function<gd::String()> generatePropertiesCode);
|
||||
std::function<gd::String()> generatePropertiesCode,
|
||||
std::function<gd::String()> generateUpdateFromBehaviorDataCode);
|
||||
gd::String GenerateRuntimeBehaviorPropertyTemplateCode(
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::String& codeNamespace,
|
||||
@@ -74,6 +75,10 @@ class BehaviorCodeGenerator {
|
||||
gd::String GenerateInitializePropertyFromDefaultValueCode(
|
||||
const gd::NamedPropertyDescriptor& property);
|
||||
gd::String GeneratePropertyValueCode(const gd::PropertyDescriptor& property);
|
||||
gd::String GenerateUpdatePropertyFromBehaviorDataCode(
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::String& codeNamespace,
|
||||
const gd::NamedPropertyDescriptor& property);
|
||||
gd::String GenerateBehaviorOnDestroyToDeprecatedOnOwnerRemovedFromScene(
|
||||
const gd::EventsBasedBehavior& eventsBasedBehavior,
|
||||
const gd::String& codeNamespace);
|
||||
|
@@ -63,8 +63,6 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
|
||||
gd::String globalObjectLists = allObjectsDeclarationsAndResets.first;
|
||||
gd::String globalObjectListsReset = allObjectsDeclarationsAndResets.second;
|
||||
|
||||
codeGenerator.AddAllObjectsIncludeFiles();
|
||||
|
||||
// "Booleans" used by conditions
|
||||
gd::String globalConditionsBooleans =
|
||||
codeGenerator.GenerateAllConditionsBooleanDeclarations();
|
||||
@@ -327,9 +325,9 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
|
||||
" createObject: function(objectName) {\n"
|
||||
" var objectsList = "
|
||||
"eventsFunctionContext._objectsMap[objectName];\n" +
|
||||
// TODO: we could speed this up by storing a map of object names, but the
|
||||
// cost of creating/storing it for each events function might not be
|
||||
// worth it.
|
||||
// TODO: we could speed this up by storing a map of object names, but
|
||||
// the cost of creating/storing it for each events function might not
|
||||
// be worth it.
|
||||
" if (objectsList) {\n" +
|
||||
" return parentEventsFunctionContext ?\n" +
|
||||
" "
|
||||
@@ -396,33 +394,6 @@ EventsCodeGenerator::GenerateAllObjectsDeclarationsAndResets(
|
||||
return std::make_pair(globalObjectLists, globalObjectListsReset);
|
||||
}
|
||||
|
||||
void EventsCodeGenerator::AddAllObjectsIncludeFiles() {
|
||||
auto addIncludeFiles = [this](const gd::Object& object) {
|
||||
gd::String type = gd::GetTypeOfObject(
|
||||
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), object.GetName());
|
||||
|
||||
// Ensure needed files are included for the object type and its behaviors.
|
||||
const gd::ObjectMetadata& metadata =
|
||||
gd::MetadataProvider::GetObjectMetadata(JsPlatform::Get(), type);
|
||||
AddIncludeFiles(metadata.includeFiles);
|
||||
|
||||
std::vector<gd::String> behaviors = object.GetAllBehaviorNames();
|
||||
for (std::size_t j = 0; j < behaviors.size(); ++j) {
|
||||
const gd::BehaviorMetadata& metadata =
|
||||
gd::MetadataProvider::GetBehaviorMetadata(
|
||||
JsPlatform::Get(),
|
||||
object.GetBehavior(behaviors[j]).GetTypeName());
|
||||
AddIncludeFiles(metadata.includeFiles);
|
||||
}
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < globalObjectsAndGroups.GetObjectsCount(); ++i)
|
||||
addIncludeFiles(globalObjectsAndGroups.GetObject(i));
|
||||
|
||||
for (std::size_t i = 0; i < objectsAndGroups.GetObjectsCount(); ++i)
|
||||
addIncludeFiles(objectsAndGroups.GetObject(i));
|
||||
}
|
||||
|
||||
gd::String EventsCodeGenerator::GenerateAllConditionsBooleanDeclarations() {
|
||||
gd::String globalConditionsBooleans;
|
||||
for (unsigned int i = 0; i <= GetMaxCustomConditionsDepth(); ++i) {
|
||||
@@ -839,15 +810,17 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
|
||||
: "runtimeScene, eventsFunctionContext";
|
||||
|
||||
// Generate a unique name for the function.
|
||||
gd::String uniqueId =
|
||||
gd::String::From(GenerateSingleUsageUniqueIdForEventsList());
|
||||
gd::String functionName =
|
||||
GetCodeNamespaceAccessor() + "eventsList" + gd::String::From(&events);
|
||||
GetCodeNamespaceAccessor() + "eventsList" + uniqueId;
|
||||
|
||||
// The only local parameters are runtimeScene and context.
|
||||
// List of objects, conditions booleans and any variables used by events
|
||||
// are stored in static variables that are globally available by the whole
|
||||
// code.
|
||||
AddCustomCodeOutsideMain(functionName + " = function(" + parametersCode +
|
||||
") {\n" + code + "\n" + "}; //End of " +
|
||||
functionName + "\n");
|
||||
") {\n" + code + "\n" + "};");
|
||||
|
||||
// Replace the code of the events by the call to the function. This does not
|
||||
// interfere with the objects picking as the lists are in static variables
|
||||
|
@@ -310,12 +310,6 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
|
||||
std::pair<gd::String, gd::String> GenerateAllObjectsDeclarationsAndResets(
|
||||
unsigned int maxDepthLevelReached);
|
||||
|
||||
/**
|
||||
* \brief Add to include files all the files required by the object and their
|
||||
* behaviors.
|
||||
*/
|
||||
void AddAllObjectsIncludeFiles();
|
||||
|
||||
/**
|
||||
* \brief Generate the list of parameters of a function.
|
||||
*
|
||||
|
@@ -4,8 +4,10 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "CommonInstructionsExtension.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/Builtin/CommentEvent.h"
|
||||
#include "GDCore/Events/Builtin/ForEachEvent.h"
|
||||
@@ -325,7 +327,8 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
|
||||
[](gd::Instruction& instruction,
|
||||
gd::EventsCodeGenerator& codeGenerator,
|
||||
gd::EventsCodeGenerationContext& context) {
|
||||
size_t uniqueId = (size_t)&instruction;
|
||||
size_t uniqueId = codeGenerator.GenerateSingleUsageUniqueIdFor(
|
||||
instruction.GetOriginalInstruction().lock().get());
|
||||
gd::String outputCode = codeGenerator.GenerateBooleanFullName(
|
||||
"conditionTrue", context) +
|
||||
".val = ";
|
||||
|
@@ -4,11 +4,13 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "GDJS/IDE/Exporter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
@@ -35,28 +37,10 @@ Exporter::Exporter(gd::AbstractFileSystem& fileSystem, gd::String gdjsRoot_)
|
||||
|
||||
Exporter::~Exporter() {}
|
||||
|
||||
bool Exporter::ExportLayoutForPixiPreview(gd::Project& project,
|
||||
gd::Layout& layout,
|
||||
gd::String exportDir) {
|
||||
bool Exporter::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions& options) {
|
||||
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
|
||||
gd::SerializerElement options;
|
||||
options.AddChild("isPreview").SetBoolValue(true);
|
||||
|
||||
return helper.ExportLayoutForPixiPreview(project, layout, exportDir, gd::Serializer::ToJSON(options));
|
||||
}
|
||||
|
||||
bool Exporter::ExportExternalLayoutForPixiPreview(
|
||||
gd::Project& project,
|
||||
gd::Layout& layout,
|
||||
gd::ExternalLayout& externalLayout,
|
||||
gd::String exportDir) {
|
||||
gd::SerializerElement options;
|
||||
options.AddChild("injectExternalLayout").SetValue(externalLayout.GetName());
|
||||
options.AddChild("isPreview").SetBoolValue(true);
|
||||
|
||||
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
|
||||
return helper.ExportLayoutForPixiPreview(
|
||||
project, layout, exportDir, gd::Serializer::ToJSON(options));
|
||||
return helper.ExportProjectForPixiPreview(options);
|
||||
}
|
||||
|
||||
bool Exporter::ExportWholePixiProject(
|
||||
@@ -95,7 +79,11 @@ bool Exporter::ExportWholePixiProject(
|
||||
// Export engine libraries
|
||||
helper.AddLibsInclude(true, false, false, includesFiles);
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to the engine)
|
||||
// Export files for object and behaviors
|
||||
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves
|
||||
// to the engine)
|
||||
helper.ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export events
|
||||
@@ -120,8 +108,9 @@ bool Exporter::ExportWholePixiProject(
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
|
||||
//...and export it
|
||||
helper.ExportToJSON(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
|
||||
gd::SerializerElement noRuntimeGameOptions;
|
||||
helper.ExportProjectData(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", noRuntimeGameOptions);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
// Copy all dependencies and the index (or metadata) file.
|
||||
@@ -195,7 +184,11 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
|
||||
// Export engine libraries
|
||||
helper.AddLibsInclude(false, true, false, includesFiles);
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to the engine)
|
||||
// Export files for object and behaviors
|
||||
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
helper.ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export events
|
||||
@@ -219,8 +212,9 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
|
||||
//...and export it
|
||||
helper.ExportToJSON(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
|
||||
gd::SerializerElement noRuntimeGameOptions;
|
||||
helper.ExportProjectData(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", noRuntimeGameOptions);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
// Copy all dependencies and the index (or metadata) file.
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
class Project;
|
||||
@@ -16,6 +17,7 @@ class Layout;
|
||||
class ExternalLayout;
|
||||
class AbstractFileSystem;
|
||||
} // namespace gd
|
||||
namespace gdjs { struct PreviewExportOptions; }
|
||||
|
||||
namespace gdjs {
|
||||
|
||||
@@ -30,32 +32,13 @@ class Exporter {
|
||||
virtual ~Exporter();
|
||||
|
||||
/**
|
||||
* \brief Create a preview for the specified layout.
|
||||
* \brief Create a preview for the specified options.
|
||||
* \note The preview is not launched, it is the caller responsibility to open
|
||||
* a browser pointing to the preview.
|
||||
*
|
||||
* \param layout The layout to be previewed.
|
||||
* \param exportDir The directory where the preview must be created.
|
||||
* \return true if export was successful.
|
||||
* \param options The options to generate the preview.
|
||||
*/
|
||||
bool ExportLayoutForPixiPreview(gd::Project& project,
|
||||
gd::Layout& layout,
|
||||
gd::String exportDir);
|
||||
|
||||
/**
|
||||
* \brief Create a preview for the specified external layout and layout.
|
||||
* \note The preview is not launched, it is the caller responsibility to open
|
||||
* a browser pointing to the preview.
|
||||
*
|
||||
* \param layout The layout to be previewed.
|
||||
* \param externalLayout The external layout with objects to be created at
|
||||
* scene startup. \param exportDir The directory where the preview must be
|
||||
* created. \return true if export was successful.
|
||||
*/
|
||||
bool ExportExternalLayoutForPixiPreview(gd::Project& project,
|
||||
gd::Layout& layout,
|
||||
gd::ExternalLayout& externalLayout,
|
||||
gd::String exportDir);
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions& options);
|
||||
|
||||
/**
|
||||
* \brief Export the specified project, using Pixi.js.
|
||||
|
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/CodeGeneration/EffectsCodeGenerator.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
#include "GDCore/IDE/ProjectStripper.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
#include "GDJS/Events/CodeGeneration/LayoutCodeGenerator.h"
|
||||
#include "GDJS/Extensions/JsPlatform.h"
|
||||
#undef CopyFile // Disable an annoying macro
|
||||
|
||||
namespace gdjs {
|
||||
@@ -41,87 +43,118 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
|
||||
gd::String codeOutputDir_)
|
||||
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_){};
|
||||
|
||||
bool ExporterHelper::ExportLayoutForPixiPreview(gd::Project &project,
|
||||
gd::Layout &layout,
|
||||
gd::String exportDir,
|
||||
gd::String additionalSpec) {
|
||||
fs.MkDir(exportDir);
|
||||
fs.ClearDir(exportDir);
|
||||
bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions &options) {
|
||||
fs.MkDir(options.exportPath);
|
||||
fs.ClearDir(options.exportPath);
|
||||
std::vector<gd::String> includesFiles;
|
||||
|
||||
gd::Project exportedProject = project;
|
||||
gd::Project exportedProject = options.project;
|
||||
|
||||
// Always disable the splash for preview
|
||||
exportedProject.GetLoadingScreen().ShowGDevelopSplash(false);
|
||||
|
||||
// Export resources (*before* generating events as some resources filenames
|
||||
// may be updated)
|
||||
ExportResources(fs, exportedProject, exportDir);
|
||||
ExportResources(fs, exportedProject, options.exportPath);
|
||||
|
||||
// Compatibility with GD <= 5.0-beta56
|
||||
// Stay compatible with text objects declaring their font as just a filename
|
||||
// without a font resource - by manually adding these resources.
|
||||
AddDeprecatedFontFilesToFontResources(
|
||||
fs, exportedProject.GetResourcesManager(), exportDir);
|
||||
fs, exportedProject.GetResourcesManager(), options.exportPath);
|
||||
// end of compatibility code
|
||||
|
||||
// Export engine libraries
|
||||
AddLibsInclude(true, false, true, includesFiles);
|
||||
|
||||
// Export files for object and behaviors
|
||||
ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Generate events code
|
||||
if (!ExportEventsCode(exportedProject, codeOutputDir, includesFiles, true))
|
||||
return false;
|
||||
if (!options.projectDataOnlyExport) {
|
||||
// Generate events code
|
||||
if (!ExportEventsCode(exportedProject, codeOutputDir, includesFiles, true))
|
||||
return false;
|
||||
|
||||
// Export source files
|
||||
if (!ExportExternalSourceFiles(
|
||||
exportedProject, codeOutputDir, includesFiles)) {
|
||||
gd::LogError(_("Error during exporting! Unable to export source files:\n") +
|
||||
lastError);
|
||||
return false;
|
||||
// Export source files
|
||||
if (!ExportExternalSourceFiles(
|
||||
exportedProject, codeOutputDir, includesFiles)) {
|
||||
gd::LogError(
|
||||
_("Error during exporting! Unable to export source files:\n") +
|
||||
lastError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use stripped
|
||||
// things (objects groups...))
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
exportedProject.SetFirstLayout(layout.GetName());
|
||||
exportedProject.SetFirstLayout(options.layoutName);
|
||||
|
||||
// Strip the includes to only have Pixi.js files (*before* creating
|
||||
// runtimeGameOptions, since otherwise Cocos files will be passed to the
|
||||
// hot-reloader).
|
||||
RemoveIncludes(false, true, includesFiles);
|
||||
|
||||
// Create the setup options passed to the gdjs.RuntimeGame
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
|
||||
if (!options.externalLayoutName.empty()) {
|
||||
runtimeGameOptions.AddChild("injectExternalLayout")
|
||||
.SetValue(options.externalLayoutName);
|
||||
}
|
||||
runtimeGameOptions.AddChild("projectDataOnlyExport")
|
||||
.SetBoolValue(options.projectDataOnlyExport);
|
||||
|
||||
// Pass in the options the list of scripts files - useful for hot-reloading.
|
||||
auto &scriptFilesElement = runtimeGameOptions.AddChild("scriptFiles");
|
||||
scriptFilesElement.ConsiderAsArrayOf("scriptFile");
|
||||
for (const auto &includeFile : includesFiles) {
|
||||
auto hashIt = options.includeFileHashes.find(includeFile);
|
||||
scriptFilesElement.AddChild("scriptFile")
|
||||
.SetStringAttribute("path", includeFile)
|
||||
.SetIntAttribute(
|
||||
"hash",
|
||||
hashIt != options.includeFileHashes.end() ? hashIt->second : 0);
|
||||
}
|
||||
|
||||
// Export the project
|
||||
ExportToJSON(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
|
||||
ExportProjectData(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", runtimeGameOptions);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
// Copy all the dependencies
|
||||
RemoveIncludes(false, true, includesFiles);
|
||||
ExportIncludesAndLibs(includesFiles, exportDir, false);
|
||||
ExportIncludesAndLibs(includesFiles, options.exportPath, false);
|
||||
|
||||
// Create the index file
|
||||
if (!ExportPixiIndexFile(exportedProject,
|
||||
gdjsRoot + "/Runtime/index.html",
|
||||
exportDir,
|
||||
options.exportPath,
|
||||
includesFiles,
|
||||
additionalSpec))
|
||||
"gdjs.runtimeGameOptions"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::ExportToJSON(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
gd::String filename,
|
||||
gd::String wrapIntoVariable) {
|
||||
gd::String ExporterHelper::ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions) {
|
||||
fs.MkDir(fs.DirNameFrom(filename));
|
||||
|
||||
// Save the project to JSON
|
||||
gd::SerializerElement rootElement;
|
||||
project.SerializeTo(rootElement);
|
||||
|
||||
gd::String output = gd::Serializer::ToJSON(rootElement);
|
||||
if (!wrapIntoVariable.empty())
|
||||
output = wrapIntoVariable + " = " + output + ";";
|
||||
gd::String output =
|
||||
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
|
||||
"gdjs.runtimeGameOptions = " +
|
||||
gd::Serializer::ToJSON(runtimeGameOptions) + ";\n";
|
||||
|
||||
if (!fs.WriteToFile(filename, output)) return "Unable to write " + filename;
|
||||
|
||||
@@ -500,6 +533,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "events-tools/networktools.js");
|
||||
|
||||
if (websocketDebuggerClient) {
|
||||
InsertUnique(includesFiles, "websocket-debugger-client/hot-reloader.js");
|
||||
InsertUnique(includesFiles,
|
||||
"websocket-debugger-client/websocket-debugger-client.js");
|
||||
}
|
||||
@@ -674,6 +708,45 @@ bool ExporterHelper::ExportIncludesAndLibs(
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExporterHelper::ExportObjectAndBehaviorsIncludes(
|
||||
gd::Project &project, std::vector<gd::String> &includesFiles) {
|
||||
auto addIncludeFiles = [&](const std::vector<gd::String> &newIncludeFiles) {
|
||||
for (const auto &includeFile : newIncludeFiles) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
};
|
||||
|
||||
auto addObjectIncludeFiles = [&](const gd::Object &object) {
|
||||
// Ensure needed files are included for the object type and its behaviors.
|
||||
const gd::ObjectMetadata &metadata =
|
||||
gd::MetadataProvider::GetObjectMetadata(JsPlatform::Get(),
|
||||
object.GetType());
|
||||
addIncludeFiles(metadata.includeFiles);
|
||||
|
||||
std::vector<gd::String> behaviors = object.GetAllBehaviorNames();
|
||||
for (std::size_t j = 0; j < behaviors.size(); ++j) {
|
||||
const gd::BehaviorMetadata &metadata =
|
||||
gd::MetadataProvider::GetBehaviorMetadata(
|
||||
JsPlatform::Get(),
|
||||
object.GetBehavior(behaviors[j]).GetTypeName());
|
||||
addIncludeFiles(metadata.includeFiles);
|
||||
}
|
||||
};
|
||||
|
||||
auto addObjectsIncludeFiles =
|
||||
[&](const gd::ObjectsContainer &objectsContainer) {
|
||||
for (std::size_t i = 0; i < objectsContainer.GetObjectsCount(); ++i) {
|
||||
addObjectIncludeFiles(objectsContainer.GetObject(i));
|
||||
}
|
||||
};
|
||||
|
||||
addObjectsIncludeFiles(project);
|
||||
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
|
||||
gd::Layout &layout = project.GetLayout(i);
|
||||
addObjectsIncludeFiles(layout);
|
||||
}
|
||||
}
|
||||
|
||||
void ExporterHelper::ExportResources(gd::AbstractFileSystem &fs,
|
||||
gd::Project &project,
|
||||
gd::String exportDir) {
|
||||
|
@@ -5,14 +5,17 @@
|
||||
*/
|
||||
#ifndef EXPORTER_HELPER_H
|
||||
#define EXPORTER_HELPER_H
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
class Project;
|
||||
class Layout;
|
||||
class ExternalLayout;
|
||||
class SerializerElement;
|
||||
class AbstractFileSystem;
|
||||
class ResourcesManager;
|
||||
} // namespace gd
|
||||
@@ -20,6 +23,62 @@ class wxProgressDialog;
|
||||
|
||||
namespace gdjs {
|
||||
|
||||
/**
|
||||
* \brief The options used to export a project for a preview.
|
||||
*/
|
||||
struct PreviewExportOptions {
|
||||
/**
|
||||
* \param project_ The project to export
|
||||
* \param exportPath_ The path in the filesystem where to export the files
|
||||
*/
|
||||
PreviewExportOptions(gd::Project &project_, const gd::String &exportPath_)
|
||||
: project(project_), exportPath(exportPath_), projectDataOnlyExport(false) {};
|
||||
|
||||
/**
|
||||
* \brief Set the layout to be run first in the previewed game
|
||||
*/
|
||||
PreviewExportOptions &SetLayoutName(const gd::String &layoutName_) {
|
||||
layoutName = layoutName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (optional) external layout to be instanciated in the scene
|
||||
* at the beginning of the previewed game.
|
||||
*/
|
||||
PreviewExportOptions &SetExternalLayoutName(
|
||||
const gd::String &externalLayoutName_) {
|
||||
externalLayoutName = externalLayoutName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the hash associated to an include file. Useful for the preview
|
||||
* hot-reload, to know if a file changed.
|
||||
*/
|
||||
PreviewExportOptions &SetIncludeFileHash(const gd::String &includeFile,
|
||||
int hash) {
|
||||
includeFileHashes[includeFile] = hash;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export should only export the project data, not
|
||||
* exporting events code.
|
||||
*/
|
||||
PreviewExportOptions& SetProjectDataOnlyExport(bool enable) {
|
||||
projectDataOnlyExport = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
gd::Project &project;
|
||||
gd::String exportPath;
|
||||
gd::String layoutName;
|
||||
gd::String externalLayoutName;
|
||||
std::map<gd::String, int> includeFileHashes;
|
||||
bool projectDataOnlyExport;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Export a project or a layout to a playable HTML5/Javascript based
|
||||
* game.
|
||||
@@ -42,15 +101,15 @@ class ExporterHelper {
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param project The project to be exported.
|
||||
* \param filename The filename where export the project
|
||||
* \param wrapIntoVariable If not empty, the resulting json will be wrapped in
|
||||
* this javascript variable allowing to use it as a classical javascript
|
||||
* object. \return Empty string if everthing is ok, description of the error
|
||||
* otherwise.
|
||||
* \param runtimeGameOptions The content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions \return Empty string if everthing is ok,
|
||||
* description of the error otherwise.
|
||||
*/
|
||||
static gd::String ExportToJSON(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
gd::String filename,
|
||||
gd::String wrapIntoVariable);
|
||||
static gd::String ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions);
|
||||
|
||||
/**
|
||||
* \brief Copy all the resources of the project to to the export directory,
|
||||
@@ -119,6 +178,13 @@ class ExporterHelper {
|
||||
bool ExportEffectIncludes(gd::Project &project,
|
||||
std::vector<gd::String> &includesFiles);
|
||||
|
||||
/**
|
||||
* \brief Add the include files for all the objects of the project
|
||||
* and their behaviors.
|
||||
*/
|
||||
void ExportObjectAndBehaviorsIncludes(
|
||||
gd::Project &project, std::vector<gd::String> &includesFiles);
|
||||
|
||||
/**
|
||||
* \brief Copy the external source files used by the game into the export
|
||||
* directory, and add them into files to be included.
|
||||
@@ -208,18 +274,13 @@ class ExporterHelper {
|
||||
gd::String exportDir);
|
||||
|
||||
/**
|
||||
* \brief Launch all export methods to generate a complete, stand-alone game
|
||||
* for previewing.
|
||||
* \brief Create a preview for the specified options.
|
||||
* \note The preview is not launched, it is the caller responsibility to open
|
||||
* a browser pointing to the preview.
|
||||
*
|
||||
* \param layout The layout to be previewed.
|
||||
* \param exportDir The directory where the preview must be created.
|
||||
* \param additionalSpec Any additional parameters to be passed to the
|
||||
* gdjs.RuntimeGame. \return true if export was successful.
|
||||
* \param options The options to generate the preview.
|
||||
*/
|
||||
bool ExportLayoutForPixiPreview(gd::Project &project,
|
||||
gd::Layout &layout,
|
||||
gd::String exportDir,
|
||||
gd::String additionalSpec);
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
|
||||
|
||||
/**
|
||||
* \brief Change the directory where code files are generated.
|
||||
|
@@ -26,6 +26,15 @@ gdjs.CocosImageManager = function(resources)
|
||||
|
||||
gdjs.ImageManager = gdjs.CocosImageManager; //Register the class to let the engine use it.
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.CocosImageManager.prototype.setResources = function(resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the texture associated to the specified name.
|
||||
* Returns a placeholder texture if not found.
|
||||
|
@@ -16,7 +16,9 @@ gdjs.LayerCocosRenderer = function(layer, runtimeSceneRenderer)
|
||||
this._layer = layer;
|
||||
this.convertYPosition = runtimeSceneRenderer.convertYPosition;
|
||||
|
||||
var effects = this._layer.getEffectsData();
|
||||
// Read effects from the layer as we can't dynamically add effects
|
||||
// in Cocos2d-JS.
|
||||
var effects = this._layer.getInitialEffectsData();
|
||||
if (effects.length === 0) {
|
||||
this._cocosLayer = new CocosLayer();
|
||||
runtimeSceneRenderer.getCocosScene().addChild(this._cocosLayer);
|
||||
@@ -60,7 +62,7 @@ gdjs.LayerCocosRenderer.prototype._makeShaders = function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var effects = this._layer.getEffectsData();
|
||||
var effects = this._layer.getInitialEffectsData();
|
||||
if (effects.length === 0) {
|
||||
return;
|
||||
} else if (effects.length > 1) {
|
||||
@@ -180,7 +182,8 @@ gdjs.LayerCocosRenderer.prototype.enableEffect = function(name, value) {
|
||||
};
|
||||
|
||||
gdjs.LayerCocosRenderer.prototype.addEffect = function(effectData) {
|
||||
// Unimplemented
|
||||
// Unimplemented - adding effects is not supported in Cocos2d-JS.
|
||||
// All effects are supposed to be added to the layer at its creation.
|
||||
};
|
||||
|
||||
gdjs.LayerCocosRenderer.prototype.removeEffect = function(effect) {
|
||||
|
@@ -66,6 +66,15 @@ gdjs.CocosSoundManager = function(resources)
|
||||
|
||||
gdjs.SoundManager = gdjs.CocosSoundManager; //Register the class to let the engine use it.
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.CocosSoundManager.prototype.setResources = function(resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the file associated to the given sound name.
|
||||
*
|
||||
|
@@ -159,5 +159,7 @@ gdjs.evtTools.runtimeScene.createObjectsFromExternalLayout = function(scene, ext
|
||||
var externalLayoutData = scene.getGame().getExternalLayoutData(externalLayout);
|
||||
if ( externalLayoutData === null ) return;
|
||||
|
||||
scene.createObjectsFrom(externalLayoutData.instances, xPos, yPos);
|
||||
// trackByPersistentUuid is set to false as we don't want external layouts
|
||||
// instantiated at runtime to be hot-reloaded.
|
||||
scene.createObjectsFrom(externalLayoutData.instances, xPos, yPos, /*trackByPersistentUuid=*/ false);
|
||||
};
|
||||
|
@@ -29,6 +29,15 @@ gdjs.FontFaceObserverFontManager = function (resources) {
|
||||
|
||||
gdjs.FontManager = gdjs.FontFaceObserverFontManager; //Register the class to let the engine use it.
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.FontFaceObserverFontManager.prototype.setResources = function(resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the font family associated to the specified font resource name.
|
||||
* The font resource must have been loaded before. If that's not the case,
|
||||
@@ -163,6 +172,10 @@ gdjs.FontFaceObserverFontManager.prototype.loadFonts = function (
|
||||
for (var i = 0, len = resources.length; i < len; ++i) {
|
||||
var res = resources[i];
|
||||
if (res.file && res.kind === 'font') {
|
||||
if (!!this._loadedFonts[res.name]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filesResources[res.file] = filesResources[res.file]
|
||||
? filesResources[res.file].concat(res)
|
||||
: [res];
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* @memberof gdjs
|
||||
* @class HowlerSound
|
||||
*/
|
||||
gdjs.HowlerSound = function(o) {
|
||||
gdjs.HowlerSound = function (o) {
|
||||
Howl.call(this, o);
|
||||
this._paused = false;
|
||||
this._stopped = true;
|
||||
@@ -24,14 +24,14 @@ gdjs.HowlerSound = function(o) {
|
||||
//Add custom events listener to keep
|
||||
//track of the sound status.
|
||||
var that = this;
|
||||
this.on('end', function() {
|
||||
this.on('end', function () {
|
||||
if (!that.loop()) {
|
||||
that._canBeDestroyed = true;
|
||||
that._paused = false;
|
||||
that._stopped = true;
|
||||
}
|
||||
});
|
||||
this.on('playerror', function(id, error) {
|
||||
this.on('playerror', function (id, error) {
|
||||
console.error(
|
||||
"Can't play a sound, considering it as stopped. Error is:",
|
||||
error
|
||||
@@ -44,11 +44,11 @@ gdjs.HowlerSound = function(o) {
|
||||
// sync'ed with the sound - though this should be redundant
|
||||
// with `play`/`pause` methods already doing that. Keeping
|
||||
// that to be sure that the status is always correct.
|
||||
this.on('play', function() {
|
||||
this.on('play', function () {
|
||||
that._paused = false;
|
||||
that._stopped = false;
|
||||
});
|
||||
this.on('pause', function() {
|
||||
this.on('pause', function () {
|
||||
that._paused = true;
|
||||
that._stopped = false;
|
||||
});
|
||||
@@ -59,17 +59,17 @@ gdjs.HowlerSound.prototype = Object.create(Howl.prototype);
|
||||
// is immediately updated (so that calling `stopped` just after
|
||||
// `play` will return false).
|
||||
|
||||
gdjs.HowlerSound.prototype.stop = function() {
|
||||
gdjs.HowlerSound.prototype.stop = function () {
|
||||
this._paused = false;
|
||||
this._stopped = true;
|
||||
return Howl.prototype.stop.call(this);
|
||||
};
|
||||
gdjs.HowlerSound.prototype.play = function() {
|
||||
gdjs.HowlerSound.prototype.play = function () {
|
||||
this._paused = false;
|
||||
this._stopped = false;
|
||||
return Howl.prototype.play.call(this);
|
||||
};
|
||||
gdjs.HowlerSound.prototype.pause = function() {
|
||||
gdjs.HowlerSound.prototype.pause = function () {
|
||||
this._paused = true;
|
||||
this._stopped = false;
|
||||
return Howl.prototype.pause.call(this);
|
||||
@@ -77,22 +77,22 @@ gdjs.HowlerSound.prototype.pause = function() {
|
||||
|
||||
// Add methods to query the status of the sound:
|
||||
|
||||
gdjs.HowlerSound.prototype.paused = function() {
|
||||
gdjs.HowlerSound.prototype.paused = function () {
|
||||
return this._paused;
|
||||
};
|
||||
gdjs.HowlerSound.prototype.stopped = function() {
|
||||
gdjs.HowlerSound.prototype.stopped = function () {
|
||||
return this._stopped;
|
||||
};
|
||||
gdjs.HowlerSound.prototype.canBeDestroyed = function() {
|
||||
gdjs.HowlerSound.prototype.canBeDestroyed = function () {
|
||||
return this._canBeDestroyed;
|
||||
};
|
||||
|
||||
// Methods to safely update the rate of the sound:
|
||||
|
||||
gdjs.HowlerSound.prototype.getRate = function() {
|
||||
gdjs.HowlerSound.prototype.getRate = function () {
|
||||
return this._rate;
|
||||
};
|
||||
gdjs.HowlerSound.prototype.setRate = function(rate) {
|
||||
gdjs.HowlerSound.prototype.setRate = function (rate) {
|
||||
this._rate = gdjs.HowlerSoundManager.clampRate(rate);
|
||||
this.rate(this._rate);
|
||||
};
|
||||
@@ -106,7 +106,7 @@ gdjs.HowlerSound.prototype.setRate = function(rate) {
|
||||
* @memberof gdjs
|
||||
* @class HowlerSoundManager
|
||||
*/
|
||||
gdjs.HowlerSoundManager = function(resources) {
|
||||
gdjs.HowlerSoundManager = function (resources) {
|
||||
this._resources = resources;
|
||||
this._availableResources = {}; //Map storing "audio" resources for faster access.
|
||||
|
||||
@@ -121,18 +121,18 @@ gdjs.HowlerSoundManager = function(resources) {
|
||||
this._paused = false;
|
||||
|
||||
var that = this;
|
||||
this._checkForPause = function() {
|
||||
this._checkForPause = function () {
|
||||
if (that._paused) {
|
||||
this.pause();
|
||||
that._pausedSounds.push(this);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('deviceready', function() {
|
||||
document.addEventListener('deviceready', function () {
|
||||
// pause/resume sounds in Cordova when the app is being paused/resumed
|
||||
document.addEventListener(
|
||||
'pause',
|
||||
function() {
|
||||
function () {
|
||||
var soundList = that._freeSounds.concat(that._freeMusics);
|
||||
for (var key in that._sounds) {
|
||||
if (that._sounds.hasOwnProperty(key)) {
|
||||
@@ -157,7 +157,7 @@ gdjs.HowlerSoundManager = function(resources) {
|
||||
);
|
||||
document.addEventListener(
|
||||
'resume',
|
||||
function() {
|
||||
function () {
|
||||
for (var i = 0; i < that._pausedSounds.length; i++) {
|
||||
var sound = that._pausedSounds[i];
|
||||
if (!sound.stopped()) {
|
||||
@@ -174,12 +174,21 @@ gdjs.HowlerSoundManager = function(resources) {
|
||||
|
||||
gdjs.SoundManager = gdjs.HowlerSoundManager; //Register the class to let the engine use it.
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.HowlerSoundManager.prototype.setResources = function (resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure rate is in a range valid for Howler.js
|
||||
* @return The clamped rate
|
||||
* @private
|
||||
*/
|
||||
gdjs.HowlerSoundManager.clampRate = function(rate) {
|
||||
gdjs.HowlerSoundManager.clampRate = function (rate) {
|
||||
if (rate > 4.0) return 4.0;
|
||||
if (rate < 0.5) return 0.5;
|
||||
|
||||
@@ -196,7 +205,7 @@ gdjs.HowlerSoundManager.clampRate = function(rate) {
|
||||
* @private
|
||||
* @return The associated filename
|
||||
*/
|
||||
gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function(soundName) {
|
||||
gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function (soundName) {
|
||||
if (
|
||||
this._availableResources.hasOwnProperty(soundName) &&
|
||||
this._availableResources[soundName].file
|
||||
@@ -217,7 +226,7 @@ gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function(soundName) {
|
||||
* @return The gdjs.HowlerSound that have been added (i.e: the second parameter).
|
||||
* @private
|
||||
*/
|
||||
gdjs.HowlerSoundManager.prototype._storeSoundInArray = function(arr, sound) {
|
||||
gdjs.HowlerSoundManager.prototype._storeSoundInArray = function (arr, sound) {
|
||||
//Try to recycle an old sound.
|
||||
var index = null;
|
||||
for (var i = 0, len = arr.length; i < len; ++i) {
|
||||
@@ -231,7 +240,7 @@ gdjs.HowlerSoundManager.prototype._storeSoundInArray = function(arr, sound) {
|
||||
return sound;
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.playSound = function(
|
||||
gdjs.HowlerSoundManager.prototype.playSound = function (
|
||||
soundName,
|
||||
loop,
|
||||
volume,
|
||||
@@ -251,7 +260,7 @@ gdjs.HowlerSoundManager.prototype.playSound = function(
|
||||
sound.on('play', this._checkForPause);
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(
|
||||
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function (
|
||||
soundName,
|
||||
channel,
|
||||
loop,
|
||||
@@ -278,11 +287,11 @@ gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(
|
||||
sound.on('play', this._checkForPause);
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.getSoundOnChannel = function(channel) {
|
||||
gdjs.HowlerSoundManager.prototype.getSoundOnChannel = function (channel) {
|
||||
return this._sounds[channel];
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.playMusic = function(
|
||||
gdjs.HowlerSoundManager.prototype.playMusic = function (
|
||||
soundName,
|
||||
loop,
|
||||
volume,
|
||||
@@ -303,7 +312,7 @@ gdjs.HowlerSoundManager.prototype.playMusic = function(
|
||||
sound.on('play', this._checkForPause);
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(
|
||||
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function (
|
||||
soundName,
|
||||
channel,
|
||||
loop,
|
||||
@@ -331,22 +340,22 @@ gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(
|
||||
music.on('play', this._checkForPause);
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function(channel) {
|
||||
gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function (channel) {
|
||||
return this._musics[channel];
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function(volume) {
|
||||
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function (volume) {
|
||||
this._globalVolume = volume;
|
||||
if (this._globalVolume > 100) this._globalVolume = 100;
|
||||
if (this._globalVolume < 0) this._globalVolume = 0;
|
||||
Howler.volume(this._globalVolume / 100);
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function() {
|
||||
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function () {
|
||||
return this._globalVolume;
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.clearAll = function() {
|
||||
gdjs.HowlerSoundManager.prototype.clearAll = function () {
|
||||
for (var i = 0; i < this._freeSounds.length; ++i) {
|
||||
if (this._freeSounds[i]) this._freeSounds[i].unload();
|
||||
}
|
||||
@@ -371,7 +380,7 @@ gdjs.HowlerSoundManager.prototype.clearAll = function() {
|
||||
this._pausedSounds.length = 0;
|
||||
};
|
||||
|
||||
gdjs.HowlerSoundManager.prototype.preloadAudio = function(
|
||||
gdjs.HowlerSoundManager.prototype.preloadAudio = function (
|
||||
onProgress,
|
||||
onComplete,
|
||||
resources
|
||||
@@ -381,39 +390,42 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(
|
||||
//Construct the list of files to be loaded.
|
||||
//For one loaded file, it can have one or more resources
|
||||
//that use it.
|
||||
var files = [];
|
||||
var files = {};
|
||||
for (var i = 0, len = resources.length; i < len; ++i) {
|
||||
var res = resources[i];
|
||||
if (res.file && res.kind === 'audio') {
|
||||
if (!!this._availableResources[res.name]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._availableResources[res.name] = res;
|
||||
|
||||
if (files.indexOf(res.file) === -1) {
|
||||
files.push(res.file);
|
||||
}
|
||||
files[res.file] = (files[res.file] || []).concat(res);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length === 0) return onComplete(files.length);
|
||||
|
||||
var loaded = 0;
|
||||
function onLoad(audioFile) {
|
||||
loaded++;
|
||||
if (loaded === files.length) {
|
||||
return onComplete(files.length);
|
||||
}
|
||||
|
||||
onProgress(loaded, files.length);
|
||||
}
|
||||
var totalCount = Object.keys(files).length;
|
||||
if (totalCount === 0) return onComplete(totalCount); //Nothing to load.
|
||||
|
||||
var loadedCount = 0;
|
||||
var that = this;
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
(function(audioFile) {
|
||||
var sound = new XMLHttpRequest();
|
||||
sound.addEventListener('load', onLoad.bind(that, audioFile));
|
||||
sound.addEventListener('error', onLoad.bind(that, audioFile));
|
||||
sound.addEventListener('abort', onLoad.bind(that, audioFile));
|
||||
sound.open('GET', audioFile);
|
||||
sound.send();
|
||||
})(files[i]);
|
||||
function onLoad() {
|
||||
loadedCount++;
|
||||
if (loadedCount === totalCount) {
|
||||
return onComplete(totalCount);
|
||||
}
|
||||
|
||||
onProgress(loadedCount, totalCount);
|
||||
}
|
||||
|
||||
for (var file in files) {
|
||||
if (files.hasOwnProperty(file)) {
|
||||
var httpRequest = new XMLHttpRequest();
|
||||
httpRequest.addEventListener('load', onLoad);
|
||||
httpRequest.addEventListener('error', onLoad);
|
||||
httpRequest.addEventListener('abort', onLoad);
|
||||
httpRequest.open('GET', file);
|
||||
httpRequest.send();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// @ts-check
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
@@ -16,13 +17,22 @@
|
||||
* @memberof gdjs
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.JsonManager = function(resources) {
|
||||
gdjs.JsonManager = function (resources) {
|
||||
this._resources = resources;
|
||||
|
||||
/** @type Object.<string, Object> */
|
||||
this._loadedJsons = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.JsonManager.prototype.setResources = function (resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* The callback called when a json is preloaded
|
||||
* @callback JsonManagerOnProgressCallback
|
||||
@@ -41,30 +51,33 @@ gdjs.JsonManager = function(resources) {
|
||||
/**
|
||||
* Request all the json resources to be preloaded (unless they are marked as not preloaded).
|
||||
*
|
||||
* Note that even if a JSON is already loaded, it will be reloaded (useful for hot-reloading,
|
||||
* as JSON files can have been modified without the editor knowing).
|
||||
*
|
||||
* @param {JsonManagerOnProgressCallback} onProgress The function called after each json is loaded.
|
||||
* @param {JsonManagerOnCompleteCallback} onComplete The function called when all jsons are loaded.
|
||||
*/
|
||||
gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
|
||||
gdjs.JsonManager.prototype.preloadJsons = function (onProgress, onComplete) {
|
||||
var resources = this._resources;
|
||||
|
||||
var jsonResources = resources.filter(function(resource) {
|
||||
var jsonResources = resources.filter(function (resource) {
|
||||
return resource.kind === 'json' && !resource.disablePreload;
|
||||
});
|
||||
if (jsonResources.length === 0) return onComplete(jsonResources.length);
|
||||
|
||||
var loaded = 0;
|
||||
/** @type JsonManagerRequestCallback */
|
||||
var onLoad = function(error, jsonContent) {
|
||||
var onLoad = function (error) {
|
||||
if (error) {
|
||||
console.error('Error while preloading a json resource:' + error);
|
||||
}
|
||||
|
||||
loaded++;
|
||||
if (loaded === jsonResources.length) {
|
||||
return onComplete(jsonResources.length);
|
||||
onComplete(jsonResources.length);
|
||||
} else {
|
||||
onProgress(loaded, jsonResources.length);
|
||||
}
|
||||
|
||||
onProgress(loaded, jsonResources.length);
|
||||
};
|
||||
|
||||
for (var i = 0; i < jsonResources.length; ++i) {
|
||||
@@ -77,7 +90,7 @@ gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
|
||||
* @callback JsonManagerRequestCallback
|
||||
* @param {?Error} error The error, if any. `null` otherwise.
|
||||
* @param {?Object} jsonContent The content of the json file (or null if an error occured).
|
||||
* @returns {undefined} Nothing
|
||||
* @returns {void} Nothing
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -88,8 +101,9 @@ gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
|
||||
* @param {string} resourceName The resource pointing to the json file to load.
|
||||
* @param {JsonManagerRequestCallback} callback The callback function called when json is loaded (or an error occured).
|
||||
*/
|
||||
gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
|
||||
var resource = this._resources.find(function(resource) {
|
||||
gdjs.JsonManager.prototype.loadJson = function (resourceName, callback) {
|
||||
// @ts-ignore - find is not ES5
|
||||
var resource = this._resources.find(function (resource) {
|
||||
return resource.kind === 'json' && resource.name === resourceName;
|
||||
});
|
||||
if (!resource) {
|
||||
@@ -114,7 +128,7 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'json';
|
||||
xhr.open('GET', resource.file);
|
||||
xhr.onload = function() {
|
||||
xhr.onload = function () {
|
||||
if (xhr.status !== 200) {
|
||||
callback(
|
||||
new Error('HTTP error: ' + xhr.status + '(' + xhr.statusText + ')'),
|
||||
@@ -128,10 +142,10 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
|
||||
|
||||
callback(null, xhr.response);
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
xhr.onerror = function () {
|
||||
callback(new Error('Network error'), null);
|
||||
};
|
||||
xhr.onabort = function() {
|
||||
xhr.onabort = function () {
|
||||
callback(new Error('Request aborted'), null);
|
||||
};
|
||||
xhr.send();
|
||||
@@ -142,7 +156,7 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
|
||||
* @param {string} resourceName The name of the json resource.
|
||||
* @returns {boolean} true if the content of the json resource is loaded. false otherwise.
|
||||
*/
|
||||
gdjs.JsonManager.prototype.isJsonLoaded = function(resourceName) {
|
||||
gdjs.JsonManager.prototype.isJsonLoaded = function (resourceName) {
|
||||
return !!this._loadedJsons[resourceName];
|
||||
};
|
||||
|
||||
@@ -153,6 +167,6 @@ gdjs.JsonManager.prototype.isJsonLoaded = function(resourceName) {
|
||||
* @param {string} resourceName The name of the json resource.
|
||||
* @returns {?Object} the content of the json resource, if loaded. `null` otherwise.
|
||||
*/
|
||||
gdjs.JsonManager.prototype.getLoadedJson = function(resourceName) {
|
||||
gdjs.JsonManager.prototype.getLoadedJson = function (resourceName) {
|
||||
return this._loadedJsons[resourceName] || null;
|
||||
};
|
||||
|
@@ -21,7 +21,7 @@ gdjs.Layer = function(layerData, runtimeScene) {
|
||||
this._zoomFactor = 1;
|
||||
this._timeScale = 1;
|
||||
this._hidden = !layerData.visibility;
|
||||
this._effectsData = layerData.effects || [];
|
||||
this._initialEffectsData = layerData.effects || [];
|
||||
this._cameraX = runtimeScene.getGame().getGameResolutionWidth() / 2;
|
||||
this._cameraY = runtimeScene.getGame().getGameResolutionHeight() / 2;
|
||||
this._cachedGameResolutionWidth = runtimeScene
|
||||
@@ -35,7 +35,10 @@ gdjs.Layer = function(layerData, runtimeScene) {
|
||||
// @ts-ignore - assume the proper renderer is passed
|
||||
this._renderer = new gdjs.LayerRenderer(this, runtimeScene.getRenderer());
|
||||
this.show(!this._hidden);
|
||||
this._setEffectsDefaultParameters();
|
||||
|
||||
for(var i = 0;i < layerData.effects.length;++i) {
|
||||
this.addEffect(layerData.effects[i]);
|
||||
}
|
||||
};
|
||||
|
||||
gdjs.Layer.prototype.getRenderer = function() {
|
||||
@@ -275,8 +278,12 @@ gdjs.Layer.prototype.getHeight = function() {
|
||||
return this._cachedGameResolutionHeight;
|
||||
};
|
||||
|
||||
gdjs.Layer.prototype.getEffectsData = function() {
|
||||
return this._effectsData;
|
||||
/**
|
||||
* Return the initial effects data for the layer. Only to
|
||||
* be used by renderers.
|
||||
*/
|
||||
gdjs.Layer.prototype.getInitialEffectsData = function() {
|
||||
return this._initialEffectsData;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -285,6 +292,28 @@ gdjs.Layer.prototype.getEffectsData = function() {
|
||||
*/
|
||||
gdjs.Layer.prototype.addEffect = function(effectData) {
|
||||
this._renderer.addEffect(effectData);
|
||||
|
||||
for (var name in effectData.doubleParameters) {
|
||||
this.setEffectDoubleParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.doubleParameters[name]
|
||||
);
|
||||
}
|
||||
for (var name in effectData.stringParameters) {
|
||||
this.setEffectStringParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.stringParameters[name]
|
||||
);
|
||||
}
|
||||
for (var name in effectData.booleanParameters) {
|
||||
this.setEffectBooleanParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.booleanParameters[name]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,33 +393,6 @@ gdjs.Layer.prototype.hasEffect = function(name) {
|
||||
return this._renderer.hasEffect(name);
|
||||
};
|
||||
|
||||
gdjs.Layer.prototype._setEffectsDefaultParameters = function() {
|
||||
for (var i = 0; i < this._effectsData.length; ++i) {
|
||||
var effectData = this._effectsData[i];
|
||||
for (var name in effectData.doubleParameters) {
|
||||
this.setEffectDoubleParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.doubleParameters[name]
|
||||
);
|
||||
}
|
||||
for (var name in effectData.stringParameters) {
|
||||
this.setEffectStringParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.stringParameters[name]
|
||||
);
|
||||
}
|
||||
for (var name in effectData.booleanParameters) {
|
||||
this.setEffectBooleanParameter(
|
||||
effectData.name,
|
||||
name,
|
||||
effectData.booleanParameters[name]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the time scale for the objects on the layer:
|
||||
* time will be slower if time scale is < 1, faster if > 1.
|
||||
|
@@ -21,7 +21,7 @@ gdjs.LayerPixiRenderer = function(layer, runtimeSceneRenderer) {
|
||||
this._layer = layer;
|
||||
runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer);
|
||||
|
||||
this._setupFilters();
|
||||
this._pixiContainer.filters = [];
|
||||
};
|
||||
|
||||
gdjs.LayerRenderer = gdjs.LayerPixiRenderer; //Register the class to let the engine use it.
|
||||
@@ -65,18 +65,6 @@ gdjs.LayerPixiRenderer.prototype.updateTime = function() {
|
||||
}
|
||||
};
|
||||
|
||||
gdjs.LayerPixiRenderer.prototype._setupFilters = function() {
|
||||
var effectsData = this._layer.getEffectsData();
|
||||
if (effectsData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._pixiContainer.filters = [];
|
||||
for (var i = 0; i < effectsData.length; ++i) {
|
||||
this.addEffect(effectsData[i])
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new effect, or replace the one with the same name.
|
||||
* @param {EffectData} effectData The data of the effect to add.
|
||||
|
@@ -23,6 +23,15 @@ gdjs.PixiImageManager = function(resources)
|
||||
|
||||
gdjs.ImageManager = gdjs.PixiImageManager; //Register the class to let the engine use it.
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ResourceData[]} resources The resources data of the game.
|
||||
*/
|
||||
gdjs.PixiImageManager.prototype.setResources = function(resources) {
|
||||
this._resources = resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the PIXI texture associated to the specified resource name.
|
||||
* Returns a placeholder texture if not found.
|
||||
@@ -124,7 +133,6 @@ gdjs.PixiImageManager.prototype.loadTextures = function(onProgress, onComplete)
|
||||
|
||||
if ( res.file && res.kind === "image" ) {
|
||||
if (this._loadedTextures.containsKey(res.name)) {
|
||||
console.log("Texture \"" + res.name + "\" is already loaded.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,22 @@ gdjs.RuntimeBehavior = function(runtimeScene, behaviorData, owner)
|
||||
this._activated = true;
|
||||
this.owner = owner;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the behavior must be updated using the specified behaviorData. This is the
|
||||
* case during hot-reload, and is only called if the behavior was modified.
|
||||
*
|
||||
* @see gdjs.RuntimeBehavior#onObjectHotReloaded
|
||||
*
|
||||
* @param {BehaviorData} oldBehaviorData The previous data for the behavior.
|
||||
* @param {BehaviorData} newBehaviorData The new data for the behavior.
|
||||
* @returns {boolean} true if the behavior was updated, false if it could not (i.e: hot-reload is not supported).
|
||||
*/
|
||||
gdjs.RuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
|
||||
// If not redefined, mark by default the hot-reload as failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the behavior.
|
||||
* @return {string} The behavior's name.
|
||||
@@ -146,4 +162,13 @@ gdjs.RuntimeBehavior.prototype.onDestroy = function() {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called when the owner of the behavior
|
||||
* was hot reloaded, so its position, angle, size can have been changed outside
|
||||
* of events.
|
||||
*/
|
||||
gdjs.RuntimeBehavior.prototype.onObjectHotReloaded = function() {
|
||||
|
||||
};
|
||||
|
||||
gdjs.registerBehavior("", gdjs.RuntimeBehavior);
|
||||
|
@@ -5,13 +5,28 @@
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RuntimeGameOptionsScriptFile
|
||||
* @property {string} path The path for this script file.
|
||||
* @property {number} hash The hash of the script file content.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RuntimeGameOptions
|
||||
* @property {boolean=} forceFullscreen if true, force fullscreen.
|
||||
* @property {boolean=} isPreview if true, game is run as a preview launched from an editor.
|
||||
* @property {string=} injectExternalLayout The name of the external layout to create in the scene at position 0;0.
|
||||
* @property {RuntimeGameOptionsScriptFile[]=} scriptFiles Script files, used for hot-reloading.
|
||||
* @property {boolean=} projectDataOnlyExport if true, export is a partial preview without events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a game being played.
|
||||
*
|
||||
* @memberof gdjs
|
||||
* @class RuntimeGame
|
||||
* @param {ProjectData} data The object (usually stored in data.json) containing the full project data
|
||||
* @param {Object=} options Optional object for specifiying additional options: {forceFullscreen: ...}
|
||||
* @param {RuntimeGameOptions=} options Optional object for specifiying additional options: {forceFullscreen: ...}
|
||||
*/
|
||||
gdjs.RuntimeGame = function(data, options) {
|
||||
options = options || {};
|
||||
@@ -19,27 +34,27 @@ gdjs.RuntimeGame = function(data, options) {
|
||||
this._variables = new gdjs.VariablesContainer(data.variables);
|
||||
this._data = data;
|
||||
this._imageManager = new gdjs.ImageManager(
|
||||
data.resources.resources
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._soundManager = new gdjs.SoundManager(
|
||||
data.resources.resources
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._fontManager = new gdjs.FontManager(
|
||||
data.resources.resources
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._jsonManager = new gdjs.JsonManager(
|
||||
data.resources.resources
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._maxFPS = data ? data.properties.maxFPS : 60;
|
||||
this._minFPS = data ? data.properties.minFPS : 15;
|
||||
this._maxFPS = this._data ? this._data.properties.maxFPS : 60;
|
||||
this._minFPS = this._data ? this._data.properties.minFPS : 15;
|
||||
|
||||
this._gameResolutionWidth = data.properties.windowWidth;
|
||||
this._gameResolutionHeight = data.properties.windowHeight;
|
||||
this._gameResolutionWidth = this._data.properties.windowWidth;
|
||||
this._gameResolutionHeight = this._data.properties.windowHeight;
|
||||
this._originalWidth = this._gameResolutionWidth;
|
||||
this._originalHeight = this._gameResolutionHeight;
|
||||
this._resizeMode = data.properties.sizeOnStartupMode;
|
||||
this._resizeMode = this._data.properties.sizeOnStartupMode;
|
||||
this._adaptGameResolutionAtRuntime =
|
||||
data.properties.adaptGameResolutionAtRuntime;
|
||||
this._data.properties.adaptGameResolutionAtRuntime;
|
||||
/** @type {string} */
|
||||
this._scaleMode = data.properties.scaleMode || 'linear';
|
||||
this._renderer = new gdjs.RuntimeGameRenderer(
|
||||
@@ -71,9 +86,31 @@ gdjs.RuntimeGame = function(data, options) {
|
||||
this._isPreview = options.isPreview || false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the project data. Useful for hot-reloading, should not be used otherwise.
|
||||
*
|
||||
* @param {ProjectData} projectData The object (usually stored in data.json) containing the full project data
|
||||
*/
|
||||
gdjs.RuntimeGame.prototype.setProjectData = function(projectData) {
|
||||
this._data = projectData;
|
||||
|
||||
this._imageManager.setResources(
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._soundManager.setResources(
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._fontManager.setResources(
|
||||
this._data.resources.resources
|
||||
);
|
||||
this._jsonManager.setResources(
|
||||
this._data.resources.resources
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the additional options passed to the RuntimeGame when created.
|
||||
* @returns {?Object} The additional options, if any.
|
||||
* @returns {?RuntimeGameOptions} The additional options, if any.
|
||||
*/
|
||||
gdjs.RuntimeGame.prototype.getAdditionalOptions = function() {
|
||||
return this._options;
|
||||
|
@@ -99,6 +99,12 @@ gdjs.RuntimeObject = function(runtimeScene, objectData) {
|
||||
* @type {gdjs.RuntimeScene}
|
||||
*/
|
||||
this._runtimeScene = runtimeScene;
|
||||
/**
|
||||
* An optional UUID associated to the object to be used
|
||||
* for hot reload. Don't modify or use otherwise.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.persistentUuid = null;
|
||||
|
||||
//Hit boxes:
|
||||
if ( this._defaultHitBoxes === undefined ) {
|
||||
@@ -270,6 +276,19 @@ gdjs.RuntimeObject.prototype.extraInitializationFromInitialInstance = function(i
|
||||
//Nothing to do.
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the object must be updated using the specified objectData. This is the
|
||||
* case during hot-reload, and is only called if the object was modified.
|
||||
*
|
||||
* @param {ObjectData} oldObjectData The previous data for the object.
|
||||
* @param {ObjectData} newObjectData The new data for the object.
|
||||
* @returns {boolean} true if the object was updated, false if it could not (i.e: hot-reload is not supported).
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
// If not redefined, mark by default the hot-reload as failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object from a scene.
|
||||
*
|
||||
@@ -665,7 +684,7 @@ gdjs.RuntimeObject.prototype.hide = function(enable) {
|
||||
/**
|
||||
* Return true if the object is not hidden.
|
||||
*
|
||||
* Note: This is unrelated to the actual visibility of the objec on the screen.
|
||||
* Note: This is unrelated to the actual visibility of the object on the screen.
|
||||
* For this, see `getVisibilityAABB` to get the bounding boxes of the object as displayed
|
||||
* on the scene.
|
||||
*
|
||||
@@ -1042,13 +1061,25 @@ gdjs.RuntimeObject.prototype.stepBehaviorsPostEvents = function(runtimeScene) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the object was hot reloaded, to notify behaviors
|
||||
* that the object was modified. Useful for behaviors that
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.notifyBehaviorsObjectHotReloaded = function() {
|
||||
for(var i = 0, len = this._behaviors.length;i<len;++i) {
|
||||
this._behaviors[i].onObjectHotReloaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a behavior from its name.
|
||||
* If the behavior does not exists, `undefined` is returned.
|
||||
*
|
||||
* Be careful, the behavior must exists, no check is made on the name.
|
||||
* **Never keep a reference** to a behavior, as they can be hot-reloaded. Instead,
|
||||
* always call getBehavior on the object.
|
||||
*
|
||||
* @param name {String} The behavior name.
|
||||
* @return {gdjs.RuntimeBehavior} The behavior with the given name, or undefined.
|
||||
* @return {gdjs.RuntimeBehavior?} The behavior with the given name, or undefined.
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.getBehavior = function(name) {
|
||||
return this._behaviorsTable.get(name);
|
||||
@@ -1078,7 +1109,7 @@ gdjs.RuntimeObject.prototype.activateBehavior = function(name, enable) {
|
||||
/**
|
||||
* Check if a behavior is activated
|
||||
*
|
||||
* @param name {String} The behavior name.
|
||||
* @param {string} name The behavior name.
|
||||
* @return true if the behavior is activated.
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.behaviorActivated = function(name) {
|
||||
@@ -1089,6 +1120,39 @@ gdjs.RuntimeObject.prototype.behaviorActivated = function(name) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the behavior with the given name.
|
||||
*
|
||||
* @param {string} name The name of the behavior to remove.
|
||||
* @returns {boolean} true if the behavior was properly removed, false otherwise.
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.removeBehavior = function(name) {
|
||||
var behavior = this._behaviorsTable.get(name);
|
||||
if (!behavior) return false;
|
||||
|
||||
var behaviorIndex = this._behaviors.indexOf(behavior);
|
||||
if (behaviorIndex !== -1) this._behaviors.splice(behaviorIndex, 1);
|
||||
this._behaviorsTable.remove(name);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the behavior decribed by the given BehaviorData
|
||||
*
|
||||
* @param {BehaviorData} behaviorData The data to be used to construct the behavior.
|
||||
* @returns {boolean} true if the behavior was properly created, false otherwise.
|
||||
*/
|
||||
gdjs.RuntimeObject.prototype.addNewBehavior = function(behaviorData) {
|
||||
var Ctor = gdjs.getBehaviorConstructor(behaviorData.type);
|
||||
if (!Ctor) return false;
|
||||
|
||||
var newRuntimeBehavior = new Ctor(this._runtimeScene, behaviorData, this);
|
||||
this._behaviors.push(newRuntimeBehavior);
|
||||
this._behaviorsTable.put(behaviorData.name, newRuntimeBehavior);
|
||||
return true;
|
||||
};
|
||||
|
||||
//Timers:
|
||||
|
||||
/**
|
||||
|
@@ -85,10 +85,7 @@ gdjs.RuntimeScene.prototype.loadFromScene = function(sceneData) {
|
||||
|
||||
//Load layers
|
||||
for(var i = 0, len = sceneData.layers.length;i<len;++i) {
|
||||
var layerData = sceneData.layers[i];
|
||||
|
||||
this._layers.put(layerData.name, new gdjs.Layer(layerData, this));
|
||||
//console.log("Created layer : \""+name+"\".");
|
||||
this.addLayer(sceneData.layers[i]);
|
||||
}
|
||||
|
||||
//Load variables
|
||||
@@ -96,10 +93,9 @@ gdjs.RuntimeScene.prototype.loadFromScene = function(sceneData) {
|
||||
|
||||
//Cache the initial shared data of the behaviors
|
||||
for(var i = 0, len = sceneData.behaviorsSharedData.length;i<len;++i) {
|
||||
var data = sceneData.behaviorsSharedData[i];
|
||||
var behaviorSharedData = sceneData.behaviorsSharedData[i];
|
||||
|
||||
//console.log("Initializing shared data for "+data.name);
|
||||
this._initialBehaviorSharedData.put(data.name, data);
|
||||
this.setInitialSharedDataForBehavior(behaviorSharedData.name, behaviorSharedData);
|
||||
}
|
||||
|
||||
//Registering objects: Global objects first...
|
||||
@@ -114,16 +110,10 @@ gdjs.RuntimeScene.prototype.loadFromScene = function(sceneData) {
|
||||
}
|
||||
|
||||
//Create initial instances of objects
|
||||
this.createObjectsFrom(sceneData.instances, 0, 0);
|
||||
this.createObjectsFrom(sceneData.instances, 0, 0, /*trackByPersistentUuid=*/ true);
|
||||
|
||||
//Set up the function to be executed at each tick
|
||||
var module = gdjs[sceneData.mangledName+"Code"];
|
||||
if ( module && module.func )
|
||||
this._eventsFunction = module.func;
|
||||
else {
|
||||
console.log("Warning: no function found for running logic of scene " + this._name);
|
||||
this._eventsFunction = (function() {});
|
||||
}
|
||||
this.setEventsGeneratedCodeFunction(sceneData);
|
||||
|
||||
this._onceTriggers = new gdjs.OnceTriggers();
|
||||
|
||||
@@ -185,6 +175,29 @@ gdjs.RuntimeScene.prototype.updateObject = function(objectData) {
|
||||
// Don't erase instances, nor instances cache, or objectsCtor cache.
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a {@link gdjs.RuntimeObject}. Instances will be destroyed.
|
||||
* @param {string} objectName The name of the object to unregister.
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.unregisterObject = function(objectName) {
|
||||
var instances = this._instances.get(objectName);
|
||||
if (instances) {
|
||||
// This is sub-optimal: markObjectForDeletion will search the instance to
|
||||
// remove in instances, so cost is O(n^2), n being the number of instances.
|
||||
// As we're unregistering an object which only happen during a hot-reloading,
|
||||
// this is fine.
|
||||
var instancesToRemove = instances.slice();
|
||||
for(var i = 0;i<instancesToRemove.length;i++) {
|
||||
this.markObjectForDeletion(instancesToRemove[i]);
|
||||
}
|
||||
this._cacheOrClearRemovedInstances();
|
||||
}
|
||||
this._objects.remove(objectName);
|
||||
this._instances.remove(objectName);
|
||||
this._instancesCache.put(objectName);
|
||||
this._objectsCtor.remove(objectName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a scene is "paused", i.e it will be not be rendered again
|
||||
* for some time, until it's resumed or unloaded.
|
||||
@@ -269,14 +282,22 @@ gdjs.RuntimeScene.prototype.unloadScene = function() {
|
||||
* @param {InstanceData[]} data The instances data
|
||||
* @param {number} xPos The offset on X axis
|
||||
* @param {number} yPos The offset on Y axis
|
||||
* @param {boolean} trackByPersistentUuid If true, objects are tracked by setting their persistentUuid
|
||||
* to the same as the associated instance. Useful for hot-reloading when instances are changed.
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.createObjectsFrom = function(data, xPos, yPos) {
|
||||
gdjs.RuntimeScene.prototype.createObjectsFrom = function(data, xPos, yPos, trackByPersistentUuid) {
|
||||
for(var i = 0, len = data.length;i<len;++i) {
|
||||
var instanceData = data[i];
|
||||
var objectName = instanceData.name;
|
||||
var newObject = this.createObject(objectName);
|
||||
|
||||
if ( newObject !== null ) {
|
||||
if (trackByPersistentUuid) {
|
||||
// Give the object the same persistentUuid as the instance, so that
|
||||
// it can be hot-reloaded.
|
||||
newObject.persistentUuid = instanceData.persistentUuid || null;
|
||||
}
|
||||
|
||||
newObject.setPosition(parseFloat(instanceData.x) + xPos, parseFloat(instanceData.y) + yPos);
|
||||
newObject.setZOrder(parseFloat(instanceData.zOrder));
|
||||
newObject.setAngle(parseFloat(instanceData.angle));
|
||||
@@ -287,6 +308,24 @@ gdjs.RuntimeScene.prototype.createObjectsFrom = function(data, xPos, yPos) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the function called each time the scene is stepped to be the events generated code,
|
||||
* which is by convention assumed to be a function in `gdjs` with a name based on the scene
|
||||
* mangled name.
|
||||
*
|
||||
* @param {LayoutData} sceneData The scene data, used to find where the code was generated.
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.setEventsGeneratedCodeFunction = function(sceneData) {
|
||||
var module = gdjs[sceneData.mangledName+"Code"];
|
||||
if (module && module.func)
|
||||
this._eventsFunction = module.func;
|
||||
else {
|
||||
console.log("Warning: no function found for running logic of scene " + this._name);
|
||||
this._eventsFunction = (function() {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the function called each time the scene is stepped.
|
||||
* The function will be passed the `runtimeScene` as argument.
|
||||
@@ -710,16 +749,27 @@ gdjs.RuntimeScene.prototype.getVariables = function() {
|
||||
/**
|
||||
* Get the data representing the initial shared data of the scene for the specified behavior.
|
||||
* @param {string} name The name of the behavior
|
||||
* @returns {?BehaviorSharedData} The shared data for the behavior, if any.
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.getInitialSharedDataForBehavior = function(name) {
|
||||
if ( this._initialBehaviorSharedData.containsKey(name) ) {
|
||||
return this._initialBehaviorSharedData.get(name);
|
||||
const behaviorSharedData = this._initialBehaviorSharedData.get(name);
|
||||
if (behaviorSharedData) {
|
||||
return behaviorSharedData;
|
||||
}
|
||||
|
||||
console.error("Can't find shared data for behavior with name:", name);
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the data representing the initial shared data of the scene for the specified behavior.
|
||||
* @param {string} name The name of the behavior
|
||||
* @param {?BehaviorSharedData} behaviorSharedData The shared data for the behavior, or null to remove it.
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.setInitialSharedDataForBehavior = function(name, sharedData) {
|
||||
this._initialBehaviorSharedData.put(name, sharedData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the layer with the given name
|
||||
* @param {string} name The name of the layer
|
||||
@@ -740,6 +790,36 @@ gdjs.RuntimeScene.prototype.hasLayer = function(name) {
|
||||
return this._layers.containsKey(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a layer.
|
||||
* @param {string} name The name of the layer
|
||||
* @param {LayerData} layerData The data to construct the layer
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.addLayer = function(layerData) {
|
||||
this._layers.put(layerData.name, new gdjs.Layer(layerData, this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a layer. All {@link gdjs.RuntimeObject} on this layer will
|
||||
* be moved back to the base layer.
|
||||
* @param {string} layerName The name of the layer to remove
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.removeLayer = function(layerName) {
|
||||
var allInstances = this.getAdhocListOfAllInstances();
|
||||
for(var i = 0;i < allInstances.length;++i) {
|
||||
var runtimeObject = allInstances[i];
|
||||
if (runtimeObject.getLayer() === layerName) {
|
||||
runtimeObject.setLayer("");
|
||||
}
|
||||
}
|
||||
|
||||
this._layers.remove(layerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the array passed as argument with the names of all layers
|
||||
* @param {string[]} result The array where to put the layer names
|
||||
*/
|
||||
gdjs.RuntimeScene.prototype.getAllLayerNames = function(result) {
|
||||
this._layers.keys(result);
|
||||
};
|
||||
|
@@ -98,11 +98,11 @@ gdjs.SceneStack.prototype.push = function(newSceneName, externalLayoutName) {
|
||||
newScene.loadFromScene(this._runtimeGame.getSceneData(newSceneName));
|
||||
this._wasFirstSceneLoaded = true;
|
||||
|
||||
//Optionally create the objects from an external layout.
|
||||
// Optionally create the objects from an external layout.
|
||||
if (externalLayoutName) {
|
||||
var externalLayoutData = this._runtimeGame.getExternalLayoutData(externalLayoutName);
|
||||
if (externalLayoutData)
|
||||
newScene.createObjectsFrom(externalLayoutData.instances, 0, 0);
|
||||
newScene.createObjectsFrom(externalLayoutData.instances, 0, 0, /*trackByPersistentUuid=*/ true);
|
||||
}
|
||||
|
||||
this._stack.push(newScene);
|
||||
|
@@ -308,6 +308,30 @@ gdjs.registerObject("Sprite", gdjs.SpriteRuntimeObject); //Notify gdjs of the ob
|
||||
|
||||
//Others initialization and internal state management :
|
||||
|
||||
/**
|
||||
* @param {SpriteObjectData} oldObjectData
|
||||
* @param {SpriteObjectData} newObjectData
|
||||
*/
|
||||
gdjs.SpriteRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
|
||||
var runtimeScene = this._runtimeScene;
|
||||
for(var i = 0, len = newObjectData.animations.length;i<len;++i) {
|
||||
var animData = newObjectData.animations[i];
|
||||
|
||||
if ( i < this._animations.length )
|
||||
gdjs.SpriteAnimation.call(this._animations[i], runtimeScene.getGame().getImageManager(), animData);
|
||||
else
|
||||
this._animations.push(new gdjs.SpriteAnimation(runtimeScene.getGame().getImageManager(), animData));
|
||||
}
|
||||
this._animations.length = i; //Make sure to delete already existing animations which are not used anymore.
|
||||
|
||||
this._updateAnimationFrame();
|
||||
if (!this._animationFrame) {
|
||||
this.setAnimation(0);
|
||||
}
|
||||
this.hitBoxesDirty = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the extra parameters that could be set for an instance.
|
||||
* @param {{numberProperties: Array<{name: string, value: number}>, customSize: {width: number, height: number}}} initialInstanceData The extra parameters
|
||||
|
@@ -19,4 +19,5 @@
|
||||
* @typedef { import("./project-data").EffectData } EffectData
|
||||
* @typedef { import("./project-data").ResourceData } ResourceData
|
||||
* @typedef { import("./project-data").ResourcesData } ResourcesData
|
||||
* @typedef { import("./project-data").BehaviorSharedData } BehaviorSharedData
|
||||
*/
|
||||
|
6
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -44,10 +44,10 @@ export interface LayoutData {
|
||||
instances: InstanceData[];
|
||||
objects: ObjectData[];
|
||||
layers: LayerData[];
|
||||
behaviorsSharedData: BehaviorsSharedDatum[];
|
||||
behaviorsSharedData: BehaviorSharedData[];
|
||||
}
|
||||
|
||||
export interface BehaviorsSharedDatum {
|
||||
export interface BehaviorSharedData {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
@@ -158,6 +158,7 @@ export interface ResourceData {
|
||||
name: string;
|
||||
smoothed?: boolean;
|
||||
userAdded: boolean;
|
||||
disablePreload?: boolean;
|
||||
}
|
||||
|
||||
export enum ResourceKind {
|
||||
@@ -165,4 +166,5 @@ export enum ResourceKind {
|
||||
Image = 'image',
|
||||
Font = 'font',
|
||||
Video = 'video',
|
||||
Json = 'json',
|
||||
}
|
||||
|
@@ -110,6 +110,21 @@ gdjs.Variable.prototype.getChild = function(childName) {
|
||||
return this._children[childName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a child variable with the specified name.
|
||||
*
|
||||
* If there is an existing child variable with this name, it is erased.
|
||||
* @method
|
||||
* @param {string} childName The name of the variable to add
|
||||
* @param {gdjs.Variable} childVariable The variable to add as a child
|
||||
* @returns {gdjs.Variable} The variable (for chaining calls)
|
||||
*/
|
||||
gdjs.Variable.prototype.addChild = function(childName, childVariable) {
|
||||
this._isStructure = true;
|
||||
this._children[childName] = childVariable;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the child in a variable.
|
||||
*
|
||||
|
@@ -17,7 +17,10 @@
|
||||
gdjs.VariablesContainer = function(initialVariablesData)
|
||||
{
|
||||
if ( this._variables === undefined ) this._variables = new Hashtable();
|
||||
if ( this._variablesArray === undefined ) this._variablesArray = [];
|
||||
if ( this._variablesArray === undefined ) {
|
||||
/** @type {gdjs.Variable[]} */
|
||||
this._variablesArray = [];
|
||||
}
|
||||
|
||||
if ( initialVariablesData !== undefined ) this.initFrom(initialVariablesData);
|
||||
};
|
||||
@@ -81,11 +84,28 @@ gdjs.VariablesContainer.prototype.initFrom = function(data, keepOldVariables) {
|
||||
|
||||
/**
|
||||
* Add a new variable.
|
||||
* This can be costly, don't use in performance sensitive paths.
|
||||
*
|
||||
* @param {string} name Variable name
|
||||
* @param {gdjs.Variable} variable The variable to be added
|
||||
* @param {gdjs.Variable} newVariable The variable to be added
|
||||
*/
|
||||
gdjs.VariablesContainer.prototype.add = function(name, variable) {
|
||||
this._variables.put(name, variable);
|
||||
gdjs.VariablesContainer.prototype.add = function(name, newVariable) {
|
||||
var oldVariable = this._variables.get(name);
|
||||
|
||||
// Variable is either already defined, considered as undefined
|
||||
// in the container or missing in the container.
|
||||
// Whatever the case, replace it by the new.
|
||||
this._variables.put(name, newVariable);
|
||||
|
||||
if (oldVariable) {
|
||||
// If variable is indexed, ensure that the variable as the index
|
||||
// is replaced too. This can be costly (indexOf) but we assume `add` is not
|
||||
// used in performance sensitive code.
|
||||
var variableIndex = this._variablesArray.indexOf(oldVariable);
|
||||
if (variableIndex !== -1) {
|
||||
this._variablesArray[variableIndex] = newVariable;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
1326
GDJS/Runtime/websocket-debugger-client/hot-reloader.js
Normal file
@@ -1,4 +1,4 @@
|
||||
/// @ts-check
|
||||
// @ts-check
|
||||
/**
|
||||
* An client side implementation of the Debugger
|
||||
* @interface
|
||||
@@ -54,6 +54,7 @@ gdjs.IDebuggerClient.prototype.sendProfilerOutput = function(framesAverageMeasur
|
||||
*/
|
||||
gdjs.WebsocketDebuggerClient = function(runtimeGame) {
|
||||
this._runtimegame = runtimeGame;
|
||||
this._hotReloader = new gdjs.HotReloader(runtimeGame);
|
||||
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
console.log("WebSocket is not defined, debugger won't work");
|
||||
@@ -114,6 +115,10 @@ gdjs.WebsocketDebuggerClient = function(runtimeGame) {
|
||||
that.sendProfilerStarted();
|
||||
} else if (data.command === 'profiler.stop') {
|
||||
runtimeGame.stopCurrentSceneProfiler();
|
||||
} else if (data.command === 'hotReload') {
|
||||
that._hotReloader.hotReload().then((logs) => {
|
||||
that.sendHotReloaderLogs(logs);
|
||||
});
|
||||
} else {
|
||||
console.info(
|
||||
'Unknown command "' + data.command + '" received by the debugger.'
|
||||
@@ -269,6 +274,20 @@ gdjs.WebsocketDebuggerClient.prototype.sendRuntimeGameDump = function() {
|
||||
this._ws.send(stringifiedMessage);
|
||||
};
|
||||
|
||||
gdjs.WebsocketDebuggerClient.prototype.sendHotReloaderLogs = function(logs) {
|
||||
if (!this._ws) {
|
||||
console.warn('No connection to debugger opened');
|
||||
return;
|
||||
}
|
||||
|
||||
this._ws.send(
|
||||
this._circularSafeStringify({
|
||||
command: 'hotReloader.logs',
|
||||
payload: logs,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
gdjs.WebsocketDebuggerClient.prototype.sendProfilerStarted = function() {
|
||||
if (!this._ws) {
|
||||
console.warn('No connection to debugger opened');
|
||||
@@ -325,7 +344,7 @@ gdjs.WebsocketDebuggerClient.prototype.sendProfilerOutput = function(
|
||||
* @returns {any} The new value.
|
||||
*/
|
||||
|
||||
/**
|
||||
/**
|
||||
* This is an alternative to JSON.stringify that ensure that circular references
|
||||
* are replaced by a placeholder.
|
||||
* @param {any} obj - The object to serialize.
|
||||
@@ -349,7 +368,7 @@ gdjs.WebsocketDebuggerClient.prototype._circularSafeStringify = function(
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Generates a JSON serializer that prevent circular references and stop if maxDepth is reached.
|
||||
* @param {Function} [replacer] - A function called for each property on the object or array being stringified, with the property key and its value, and that returns the new value. If not specified, values are not altered.
|
||||
* @param {DebuggerClientCycleReplacer} [cycleReplacer] - Function used to replace circular references with a new value.
|
||||
|
@@ -44,6 +44,7 @@ module.exports = function(config) {
|
||||
'../Runtime/events-tools/storagetools.js',
|
||||
'../Runtime/events-tools/stringtools.js',
|
||||
'../Runtime/events-tools/windowtools.js',
|
||||
'../Runtime/websocket-debugger-client/hot-reloader.js',
|
||||
|
||||
//Extensions:
|
||||
'../../Extensions/DraggableBehavior/draggableruntimebehavior.js',
|
||||
|
365
GDJS/tests/tests/hot-reloader.js
Normal file
@@ -0,0 +1,365 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* Tests for gdjs.InputManager and related.
|
||||
*/
|
||||
|
||||
describe('gdjs.HotReloader.deepEqual', () => {
|
||||
it('compares object, arrays, and primitives', () => {
|
||||
expect(gdjs.HotReloader.deepEqual('a', 'a')).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual('a', 'b')).to.be(false);
|
||||
|
||||
expect(gdjs.HotReloader.deepEqual(1, 1)).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual(1, 2)).to.be(false);
|
||||
|
||||
expect(gdjs.HotReloader.deepEqual(true, true)).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual(true, false)).to.be(false);
|
||||
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1 }, { a: 1 })).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).to.be(
|
||||
true
|
||||
);
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1 }, { a: 2 })).to.be(false);
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1 }, { b: 2 })).to.be(false);
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1 }, { a: 1, b: 2 })).to.be(false);
|
||||
expect(gdjs.HotReloader.deepEqual({ a: 1, b: 2 }, { a: 1 })).to.be(false);
|
||||
|
||||
expect(gdjs.HotReloader.deepEqual([1], [1])).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual([1, 2], [1, 2])).to.be(true);
|
||||
expect(gdjs.HotReloader.deepEqual([1, { a: 1 }], [1, { a: 1 }])).to.be(
|
||||
true
|
||||
);
|
||||
expect(gdjs.HotReloader.deepEqual([1], [2])).to.be(false);
|
||||
expect(gdjs.HotReloader.deepEqual([1, 2], [2])).to.be(false);
|
||||
expect(gdjs.HotReloader.deepEqual([1, { a: 1 }], [1, { a: 2 }])).to.be(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('hot-reloads variables', () => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
resources: { resources: [] },
|
||||
// @ts-ignore
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
});
|
||||
const hotReloader = new gdjs.HotReloader(runtimeGame);
|
||||
const variablesContainer = new gdjs.VariablesContainer([]);
|
||||
|
||||
// Add a new variable
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariable = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
value: '123',
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
[],
|
||||
dataWithMyVariable,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').getAsNumber()).to.be(123);
|
||||
|
||||
// Remove a new variable
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariable,
|
||||
[],
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(false);
|
||||
|
||||
// Change a variable
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsString = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
value: 'Hello World',
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariable,
|
||||
dataWithMyVariableAsString,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').getAsString()).to.be(
|
||||
'Hello World'
|
||||
);
|
||||
|
||||
// Add a new structure
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructure = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild1',
|
||||
value: '123',
|
||||
},
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
[],
|
||||
dataWithMyVariableAsStructure,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
true
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild1').getAsNumber()
|
||||
).to.be(123);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World');
|
||||
|
||||
// Change a variable in a structure
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructure2 = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild1',
|
||||
value: '124',
|
||||
},
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariableAsStructure,
|
||||
dataWithMyVariableAsStructure2,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
true
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild1').getAsNumber()
|
||||
).to.be(124);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World 2');
|
||||
|
||||
// Remove a child variable
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructureWithoutChild1 = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariableAsStructure2,
|
||||
dataWithMyVariableAsStructureWithoutChild1,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
false
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World 2');
|
||||
|
||||
// Add a child variable as a structure
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructureWithChild1AsStructure = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild1',
|
||||
children: [
|
||||
{
|
||||
name: 'MyGrandChild1',
|
||||
value: '456',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariableAsStructureWithoutChild1,
|
||||
dataWithMyVariableAsStructureWithChild1AsStructure,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
true
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World 2');
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild1').isStructure()
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.hasChild('MyGrandChild1')
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.getChild('MyGrandChild1')
|
||||
.getAsNumber()
|
||||
).to.be(456);
|
||||
|
||||
// Modify a grand child variable
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructureWithChild1AsStructure2 = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild1',
|
||||
children: [
|
||||
{
|
||||
name: 'MyGrandChild1',
|
||||
value: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariableAsStructureWithChild1AsStructure,
|
||||
dataWithMyVariableAsStructureWithChild1AsStructure2,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
true
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World 2');
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild1').isStructure()
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.hasChild('MyGrandChild1')
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.getChild('MyGrandChild1')
|
||||
.getAsNumber()
|
||||
).to.be(789);
|
||||
|
||||
// Remove a grand child variable and add another
|
||||
/** @type {VariableData[]} */
|
||||
const dataWithMyVariableAsStructureWithChild1AsStructure3 = [
|
||||
{
|
||||
name: 'MyVariable',
|
||||
children: [
|
||||
{
|
||||
name: 'MyChild1',
|
||||
children: [
|
||||
{
|
||||
name: 'MyGrandChild2',
|
||||
value: 'Hello World 3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'MyChild2',
|
||||
value: 'Hello World 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
hotReloader._hotReloadVariablesContainer(
|
||||
dataWithMyVariableAsStructureWithChild1AsStructure2,
|
||||
dataWithMyVariableAsStructureWithChild1AsStructure3,
|
||||
variablesContainer
|
||||
);
|
||||
expect(variablesContainer.has('MyVariable')).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').isStructure()).to.be(true);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild1')).to.be(
|
||||
true
|
||||
);
|
||||
expect(variablesContainer.get('MyVariable').hasChild('MyChild2')).to.be(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild2').getAsString()
|
||||
).to.be('Hello World 2');
|
||||
expect(
|
||||
variablesContainer.get('MyVariable').getChild('MyChild1').isStructure()
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.hasChild('MyGrandChild1')
|
||||
).to.be(false);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.hasChild('MyGrandChild2')
|
||||
).to.be(true);
|
||||
expect(
|
||||
variablesContainer
|
||||
.get('MyVariable')
|
||||
.getChild('MyChild1')
|
||||
.getChild('MyGrandChild2')
|
||||
.getAsString()
|
||||
).to.be('Hello World 3');
|
||||
});
|
||||
});
|
@@ -140,7 +140,7 @@ describe('gdjs.RuntimeObject.cursorOnObject', function() {
|
||||
});
|
||||
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [{ name: '', visibility: true }],
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
|
@@ -1,58 +1,117 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* Test for gdjs.RuntimeScene
|
||||
*/
|
||||
|
||||
describe('gdjs.RuntimeScene integration tests', function() {
|
||||
describe('gdjs.RuntimeScene integration tests', function () {
|
||||
describe('Object and behavior lifecycles (using TestObject and TestBehavior)', function () {
|
||||
it('should properly create and destroy object, including the behaviors', function() {
|
||||
const runtimeGame = new gdjs.RuntimeGame({variables: [], properties: {windowWidth: 800, windowHeight: 600}, resources: {resources: []}});
|
||||
it('should properly create and destroy object, including the behaviors', function () {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
// @ts-ignore
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
resources: { resources: [] },
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers:[{name:"", visibility: true}],
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [{
|
||||
type: 'TestObject::TestObject',
|
||||
name: 'Object1',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'TestBehavior::TestBehavior',
|
||||
},
|
||||
],
|
||||
}],
|
||||
instances: []
|
||||
objects: [
|
||||
{
|
||||
type: 'TestObject::TestObject',
|
||||
name: 'Object1',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'TestBehavior::TestBehavior',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
instances: [],
|
||||
});
|
||||
|
||||
const object = runtimeScene.createObject('Object1');
|
||||
|
||||
// Check that the behavior was properly created
|
||||
expect(
|
||||
object
|
||||
.getVariables()
|
||||
.get('lastState')
|
||||
.getAsString()
|
||||
).to.eql('created');
|
||||
expect(object.getVariables().get('lastState').getAsString()).to.eql(
|
||||
'created'
|
||||
);
|
||||
|
||||
// Check that the behaviors are properly destroyed
|
||||
runtimeScene.markObjectForDeletion(object);
|
||||
expect(
|
||||
object
|
||||
.getVariables()
|
||||
.get('lastState')
|
||||
.getAsString()
|
||||
).to.eql('onDestroy');
|
||||
expect(object.getVariables().get('lastState').getAsString()).to.eql(
|
||||
'onDestroy'
|
||||
);
|
||||
|
||||
const object2 = runtimeScene.createObject('Object1');
|
||||
|
||||
// Check that the behaviors are properly destroyed
|
||||
runtimeScene.unloadScene();
|
||||
expect(
|
||||
object2
|
||||
.getVariables()
|
||||
.get('lastState')
|
||||
.getAsString()
|
||||
).to.eql('onDestroy');
|
||||
expect(object2.getVariables().get('lastState').getAsString()).to.eql(
|
||||
'onDestroy'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Layers (using a Sprite object)', function () {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
// @ts-ignore
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
resources: { resources: [] },
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [
|
||||
{ name: '', visibility: true, effects: [] },
|
||||
{ name: 'MyLayer', visibility: true, effects: [] },
|
||||
],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [
|
||||
{
|
||||
type: 'Sprite',
|
||||
name: 'MyObject',
|
||||
behaviors: [],
|
||||
animations: [],
|
||||
updateIfNotVisible: false,
|
||||
},
|
||||
],
|
||||
instances: [],
|
||||
});
|
||||
|
||||
expect(runtimeScene.hasLayer('')).to.be(true);
|
||||
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
|
||||
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(false);
|
||||
|
||||
const object1 = runtimeScene.createObject('MyObject');
|
||||
const object2 = runtimeScene.createObject('MyObject');
|
||||
const object3 = runtimeScene.createObject('MyObject');
|
||||
object2.setLayer('MyLayer');
|
||||
|
||||
runtimeScene.addLayer({
|
||||
name: 'MyOtherLayer',
|
||||
visibility: true,
|
||||
cameras: [],
|
||||
effects: [],
|
||||
});
|
||||
expect(runtimeScene.hasLayer('')).to.be(true);
|
||||
expect(runtimeScene.hasLayer('MyLayer')).to.be(true);
|
||||
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
|
||||
|
||||
object3.setLayer('MyOtherLayer');
|
||||
expect(object1.getLayer()).to.be('');
|
||||
expect(object2.getLayer()).to.be('MyLayer');
|
||||
expect(object3.getLayer()).to.be('MyOtherLayer');
|
||||
|
||||
runtimeScene.removeLayer("MyLayer");
|
||||
|
||||
expect(object1.getLayer()).to.be('');
|
||||
expect(object2.getLayer()).to.be('');
|
||||
expect(object3.getLayer()).to.be('MyOtherLayer');
|
||||
expect(runtimeScene.hasLayer('')).to.be(true);
|
||||
expect(runtimeScene.hasLayer('MyLayer')).to.be(false);
|
||||
expect(runtimeScene.hasLayer('MyOtherLayer')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
@@ -36,7 +36,7 @@ describe('gdjs.VariablesContainer', function() {
|
||||
expect(container.has('MyVariable3')).to.be(true);
|
||||
});
|
||||
|
||||
it('can be constructed from data', function() {
|
||||
it('can be constructed from data, so that variables are indexed', function() {
|
||||
const container = new gdjs.VariablesContainer([{
|
||||
name: 'Var1',
|
||||
value: 123
|
||||
@@ -65,7 +65,7 @@ describe('gdjs.VariablesContainer', function() {
|
||||
expect(container.getFromIndex(1).getAsString()).to.be('Hello World');
|
||||
expect(container.getFromIndex(2).isStructure()).to.be(true);
|
||||
|
||||
// Call initFrom to add more variables (overriding the existing ones)
|
||||
// Call initFrom to add more variables (not overriding the existing ones)
|
||||
container.initFrom([{
|
||||
name: 'Var4',
|
||||
value: 456
|
||||
@@ -107,6 +107,43 @@ describe('gdjs.VariablesContainer', function() {
|
||||
expect(container.get('Var5').getAsNumber()).to.be(789);
|
||||
expect(container.has('Var6')).to.be(true);
|
||||
expect(container.get('Var6').getAsString()).to.be('The Only Hello World');
|
||||
});
|
||||
|
||||
it('persists index of variables constructed from data', function() {
|
||||
const container = new gdjs.VariablesContainer([{
|
||||
name: 'Var1',
|
||||
value: 123
|
||||
},{
|
||||
name: 'Var2',
|
||||
value: 'Hello World'
|
||||
}, {
|
||||
name: 'Var3',
|
||||
children: [{
|
||||
name: 'Var3.1',
|
||||
value: 1,
|
||||
}]
|
||||
}]);
|
||||
|
||||
// Check that getFromIndex works (for faster lookup in case we know
|
||||
// the order of variables).
|
||||
expect(container.getFromIndex(0).getAsNumber()).to.be(123);
|
||||
expect(container.getFromIndex(1).getAsString()).to.be('Hello World');
|
||||
expect(container.getFromIndex(2).isStructure()).to.be(true);
|
||||
|
||||
// Remove a variable (that is indexed) and add it back
|
||||
container.remove('Var2');
|
||||
const newVar2 = new gdjs.Variable();
|
||||
newVar2.setNumber(456);
|
||||
container.add('Var2', newVar2);
|
||||
|
||||
// Also replace a variable (that is indexed)
|
||||
const newVar3 = new gdjs.Variable();
|
||||
newVar3.setNumber(789);
|
||||
container.add('Var3', newVar3);
|
||||
|
||||
// Verify that we can still access indexed variables using getFromIndex
|
||||
expect(container.getFromIndex(0).getAsNumber()).to.be(123);
|
||||
expect(container.getFromIndex(1).getAsNumber()).to.be(456);
|
||||
expect(container.getFromIndex(2).getAsNumber()).to.be(789);
|
||||
});
|
||||
});
|
||||
|
@@ -756,6 +756,8 @@ interface InitialInstance {
|
||||
void SetCustomHeight(float height);
|
||||
float GetCustomHeight();
|
||||
|
||||
[Ref] InitialInstance ResetPersistentUuid();
|
||||
|
||||
void UpdateCustomProperty([Const] DOMString name, [Const] DOMString value, [Ref] Project project, [Ref] Layout layout);
|
||||
[Value] MapStringPropertyDescriptor GetCustomProperties([Ref] Project project, [Ref] Layout layout);
|
||||
float GetRawFloatProperty([Const] DOMString name);
|
||||
@@ -2364,13 +2366,21 @@ interface EventsFunctionsExtensionCodeGenerator {
|
||||
[Const, Value] DOMString GenerateFreeEventsFunctionCompleteCode([Const, Ref] EventsFunction eventsFunction, [Const] DOMString codeNamespac, [Ref] SetString includes, boolean compilationForRuntime);
|
||||
};
|
||||
|
||||
[Prefix="gdjs::"]
|
||||
interface PreviewExportOptions {
|
||||
void PreviewExportOptions([Ref] Project project, [Const] DOMString outputPath);
|
||||
[Ref] PreviewExportOptions SetLayoutName([Const] DOMString layoutName);
|
||||
[Ref] PreviewExportOptions SetExternalLayoutName([Const] DOMString externalLayoutName);
|
||||
[Ref] PreviewExportOptions SetIncludeFileHash([Const] DOMString includeFile, long hash);
|
||||
[Ref] PreviewExportOptions SetProjectDataOnlyExport(boolean enable);
|
||||
};
|
||||
|
||||
[Prefix="gdjs::"]
|
||||
interface Exporter {
|
||||
void Exporter([Ref] AbstractFileSystem fs, [Const] DOMString gdjsRoot);
|
||||
void SetCodeOutputDirectory([Const] DOMString path);
|
||||
|
||||
boolean ExportLayoutForPixiPreview([Ref] Project project, [Ref] Layout layout, [Const] DOMString exportDir);
|
||||
boolean ExportExternalLayoutForPixiPreview([Ref] Project project, [Ref] Layout layout, [Ref] ExternalLayout externalLayout, [Const] DOMString exportDir);
|
||||
boolean ExportProjectForPixiPreview([Const, Ref] PreviewExportOptions options);
|
||||
boolean ExportWholePixiProject([Ref] Project project, [Const] DOMString exportDir, [Ref] MapStringBoolean exportOptions);
|
||||
|
||||
boolean ExportWholeCocos2dProject([Ref] Project project, boolean debugMode, [Const] DOMString exportDir);
|
||||
|
@@ -78,6 +78,7 @@
|
||||
#include <GDJS/Events/CodeGeneration/BehaviorCodeGenerator.h>
|
||||
#include <GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h>
|
||||
#include <GDJS/IDE/Exporter.h>
|
||||
#include <GDJS/IDE/ExporterHelper.h>
|
||||
|
||||
#include <emscripten.h>
|
||||
#include "ProjectHelper.h"
|
||||
|
@@ -593,6 +593,38 @@ describe('libGD.js', function() {
|
||||
expect(initialInstance2.getCustomWidth()).toBe(34);
|
||||
expect(initialInstance2.getCustomHeight()).toBe(30);
|
||||
});
|
||||
it('can be serialized with a persistent UUID called persistentUuid', function() {
|
||||
const initialInstance = new gd.InitialInstance();
|
||||
initialInstance.setObjectName("MyObject");
|
||||
|
||||
// Serialized object must have a non empty "persistentUuid".
|
||||
var element = new gd.SerializerElement();
|
||||
initialInstance.serializeTo(element);
|
||||
const persistentUuid = element.getStringAttribute("persistentUuid");
|
||||
expect(persistentUuid).toBeTruthy();
|
||||
|
||||
// The UUID must persist across serializations.
|
||||
const initialInstance2 = new gd.InitialInstance();
|
||||
initialInstance2.unserializeFrom(element);
|
||||
|
||||
var element2 = new gd.SerializerElement();
|
||||
initialInstance.serializeTo(element2);
|
||||
const persistentUuid2 = element2.getStringAttribute("persistentUuid");
|
||||
expect(persistentUuid2).toBe(persistentUuid);
|
||||
|
||||
// The UUID can be reset
|
||||
initialInstance2.resetPersistentUuid();
|
||||
var element3 = new gd.SerializerElement();
|
||||
initialInstance2.serializeTo(element3);
|
||||
const persistentUuid3 = element3.getStringAttribute("persistentUuid");
|
||||
expect(persistentUuid3).not.toBe(persistentUuid2);
|
||||
|
||||
element.delete();
|
||||
element2.delete();
|
||||
element3.delete();
|
||||
initialInstance.delete();
|
||||
initialInstance2.delete();
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
project.delete();
|
||||
@@ -2609,8 +2641,12 @@ describe('libGD.js', function() {
|
||||
done();
|
||||
};
|
||||
|
||||
var exporter = new gd.Exporter(fs);
|
||||
exporter.exportLayoutForPixiPreview(project, layout, '/path/for/export/');
|
||||
const exporter = new gd.Exporter(fs);
|
||||
const previewExportOptions =
|
||||
new gd.PreviewExportOptions(project, '/path/for/export/');
|
||||
previewExportOptions.setLayoutName('Scene');
|
||||
exporter.exportProjectForPixiPreview(previewExportOptions);
|
||||
previewExportOptions.delete();
|
||||
exporter.delete();
|
||||
});
|
||||
});
|
||||
|
@@ -301,6 +301,107 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
|
||||
eventsFunction.delete();
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('generates a working function with BuiltinCommonInstructions::Once', function () {
|
||||
// Create events using the Trigger Once condition.
|
||||
const eventsSerializerElement = gd.Serializer.fromJSON(
|
||||
JSON.stringify([
|
||||
{
|
||||
disabled: false,
|
||||
folded: false,
|
||||
type: 'BuiltinCommonInstructions::Standard',
|
||||
conditions: [
|
||||
{
|
||||
type: {
|
||||
inverted: false,
|
||||
value: 'BuiltinCommonInstructions::Once',
|
||||
},
|
||||
parameters: [],
|
||||
subInstructions: [],
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: { inverted: false, value: 'ModVarScene' },
|
||||
parameters: ['SuccessVariable', '+', '1'],
|
||||
subInstructions: [],
|
||||
},
|
||||
],
|
||||
events: [],
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
folded: false,
|
||||
type: 'BuiltinCommonInstructions::Standard',
|
||||
conditions: [
|
||||
{
|
||||
type: {
|
||||
inverted: false,
|
||||
value: 'BuiltinCommonInstructions::Once',
|
||||
},
|
||||
parameters: [],
|
||||
subInstructions: [],
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: { inverted: false, value: 'ModVarScene' },
|
||||
parameters: ['SuccessVariable', '+', '1'],
|
||||
subInstructions: [],
|
||||
},
|
||||
],
|
||||
events: [],
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
const eventsFunction = new gd.EventsFunction();
|
||||
eventsFunction
|
||||
.getEvents()
|
||||
.unserializeFrom(project, eventsSerializerElement);
|
||||
|
||||
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
||||
gd,
|
||||
project,
|
||||
eventsFunction
|
||||
);
|
||||
|
||||
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
||||
runtimeScene.getOnceTriggers().startNewFrame();
|
||||
runCompiledEvents(gdjs, runtimeScene);
|
||||
runtimeScene.getOnceTriggers().startNewFrame();
|
||||
runCompiledEvents(gdjs, runtimeScene);
|
||||
|
||||
// Check that after running the compiled events twice, actions were
|
||||
// executed only twice.
|
||||
expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
|
||||
expect(
|
||||
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
||||
).toBe(2);
|
||||
|
||||
// Check that compiling again the events will result in stable ids for Trigger Once conditions
|
||||
// (trigger once should not be launched again), even after a copy of the events function.
|
||||
const eventsFunctionCopy = eventsFunction.clone();
|
||||
const runCompiledEvents2 = generateCompiledEventsForEventsFunction(
|
||||
gd,
|
||||
project,
|
||||
eventsFunctionCopy
|
||||
);
|
||||
runtimeScene.getOnceTriggers().startNewFrame();
|
||||
runCompiledEvents2(gdjs, runtimeScene);
|
||||
runtimeScene.getOnceTriggers().startNewFrame();
|
||||
runCompiledEvents2(gdjs, runtimeScene);
|
||||
|
||||
expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
|
||||
expect(
|
||||
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
||||
).toBe(2);
|
||||
|
||||
eventsFunction.delete();
|
||||
eventsFunctionCopy.delete();
|
||||
project.delete();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -359,3 +460,24 @@ function generateCompiledEventsFromSerializedEvents(
|
||||
|
||||
return runCompiledEvents;
|
||||
}
|
||||
|
||||
/** Helper to create compiled events from serialized events, creating a project and the events function. */
|
||||
function generateCompiledEventsFromSerializedEvents(
|
||||
gd,
|
||||
eventsSerializerElement
|
||||
) {
|
||||
const project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
const eventsFunction = new gd.EventsFunction();
|
||||
eventsFunction.getEvents().unserializeFrom(project, eventsSerializerElement);
|
||||
|
||||
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
||||
gd,
|
||||
project,
|
||||
eventsFunction
|
||||
);
|
||||
|
||||
eventsFunction.delete();
|
||||
project.delete();
|
||||
|
||||
return runCompiledEvents;
|
||||
}
|
||||
|
@@ -2,8 +2,7 @@
|
||||
declare class gdjsExporter {
|
||||
constructor(fs: gdAbstractFileSystem, gdjsRoot: string): void;
|
||||
setCodeOutputDirectory(path: string): void;
|
||||
exportLayoutForPixiPreview(project: gdProject, layout: gdLayout, exportDir: string): boolean;
|
||||
exportExternalLayoutForPixiPreview(project: gdProject, layout: gdLayout, externalLayout: gdExternalLayout, exportDir: string): boolean;
|
||||
exportProjectForPixiPreview(options: gdPreviewExportOptions): boolean;
|
||||
exportWholePixiProject(project: gdProject, exportDir: string, exportOptions: gdMapStringBoolean): boolean;
|
||||
exportWholeCocos2dProject(project: gdProject, debugMode: boolean, exportDir: string): boolean;
|
||||
getLastError(): string;
|
||||
|
@@ -21,6 +21,7 @@ declare class gdInitialInstance {
|
||||
getCustomWidth(): number;
|
||||
setCustomHeight(height: number): void;
|
||||
getCustomHeight(): number;
|
||||
resetPersistentUuid(): gdInitialInstance;
|
||||
updateCustomProperty(name: string, value: string, project: gdProject, layout: gdLayout): void;
|
||||
getCustomProperties(project: gdProject, layout: gdLayout): gdMapStringPropertyDescriptor;
|
||||
getRawFloatProperty(name: string): number;
|
||||
|
10
GDevelop.js/types/gdpreviewexportoptions.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdPreviewExportOptions {
|
||||
constructor(project: gdProject, outputPath: string): void;
|
||||
setLayoutName(layoutName: string): gdPreviewExportOptions;
|
||||
setExternalLayoutName(externalLayoutName: string): gdPreviewExportOptions;
|
||||
setIncludeFileHash(includeFile: string, hash: number): gdPreviewExportOptions;
|
||||
setProjectDataOnlyExport(enable: boolean): gdPreviewExportOptions;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -180,6 +180,7 @@ declare class libGDevelop {
|
||||
LayoutCodeGenerator: Class<gdLayoutCodeGenerator>;
|
||||
BehaviorCodeGenerator: Class<gdBehaviorCodeGenerator>;
|
||||
EventsFunctionsExtensionCodeGenerator: Class<gdEventsFunctionsExtensionCodeGenerator>;
|
||||
PreviewExportOptions: Class<gdPreviewExportOptions>;
|
||||
Exporter: Class<gdExporter>;
|
||||
JsCodeEvent: Class<gdJsCodeEvent>;
|
||||
};
|
21
newIDE/app/package-lock.json
generated
@@ -2363,11 +2363,11 @@
|
||||
}
|
||||
},
|
||||
"@material-ui/icons": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.2.1.tgz",
|
||||
"integrity": "sha512-FvSD5lUBJ66frI4l4AYAPy2CH14Zs2Dgm0o3oOMr33BdQtOAjCgbdOcvPBeaD1w6OQl31uNW3CKOE8xfPNxvUQ==",
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz",
|
||||
"integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.2.0"
|
||||
"@babel/runtime": "^7.4.4"
|
||||
}
|
||||
},
|
||||
"@material-ui/lab": {
|
||||
@@ -7793,6 +7793,11 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz",
|
||||
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q=="
|
||||
},
|
||||
"cuint": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
||||
"integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs="
|
||||
},
|
||||
"cyclist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
||||
@@ -25088,6 +25093,14 @@
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"xxhashjs": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
|
||||
"requires": {
|
||||
"cuint": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||
|
@@ -32,7 +32,7 @@
|
||||
"@blueprintjs/icons": "file:src/Utils/BlueprintJsPlaceholder",
|
||||
"@lingui/react": "git://github.com/4ian/lingui-react.git#master",
|
||||
"@material-ui/core": "4.9.10",
|
||||
"@material-ui/icons": "4.2.1",
|
||||
"@material-ui/icons": "4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.49",
|
||||
"algoliasearch": "3.33.0",
|
||||
"axios": "^0.18.1",
|
||||
@@ -72,7 +72,8 @@
|
||||
"slugs": "0.1.3",
|
||||
"source-map-explorer": "^2.0.1",
|
||||
"url-search-params": "^1.0.2",
|
||||
"wait-promise": "0.4.1"
|
||||
"wait-promise": "0.4.1",
|
||||
"xxhashjs": "^0.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "npm run import-resources && npm run make-version-metadata",
|
||||
|
BIN
newIDE/app/public/res/ribbon_default/hotReload64.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
BIN
newIDE/app/public/res/ribbon_default/pause64.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 560 B |
BIN
newIDE/app/public/res/ribbon_default/preview64.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 17 KiB |
BIN
newIDE/app/public/res/tutorial_icons/pause-menu.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
newIDE/app/public/res/tutorial_icons/push-objects.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
newIDE/app/public/res/tutorial_icons/responsive-ui.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
newIDE/app/public/res/tutorial_icons/save-and-load.jpg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
newIDE/app/public/res/tutorial_icons/smooth-camera-movement.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
newIDE/app/public/res/tutorial_icons/tween-behavior.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import React, { Component } from 'react';
|
||||
@@ -16,6 +17,14 @@ import { isNullPtr } from '../Utils/IsNullPtr';
|
||||
import Window from '../Utils/Window';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import {
|
||||
type ResourceSource,
|
||||
type ChooseResourceFunction,
|
||||
} from '../ResourcesList/ResourceSource.flow';
|
||||
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
|
||||
import { getBehaviorTutorialHints } from '../Hints';
|
||||
import DismissableTutorialMessage from '../Hints/DismissableTutorialMessage';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
const AddBehaviorLine = ({ onAdd }) => (
|
||||
@@ -31,25 +40,22 @@ const AddBehaviorLine = ({ onAdd }) => (
|
||||
</Column>
|
||||
);
|
||||
|
||||
export default class BehaviorsEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { newBehaviorDialogOpen: false };
|
||||
}
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
object: gdObject,
|
||||
onUpdateBehaviorsSharedData: () => void,
|
||||
onSizeUpdated?: ?() => void,
|
||||
resourceSources: Array<ResourceSource>,
|
||||
onChooseResource: ChooseResourceFunction,
|
||||
resourceExternalEditors: Array<ResourceExternalEditor>,
|
||||
|};
|
||||
|
||||
componentWillMount() {
|
||||
this._loadFrom(this.props.object);
|
||||
}
|
||||
type State = {|
|
||||
newBehaviorDialogOpen: boolean,
|
||||
|};
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (this.props.object !== newProps.object) {
|
||||
this._loadFrom(newProps.object);
|
||||
}
|
||||
}
|
||||
|
||||
_loadFrom(object) {
|
||||
if (!object) return;
|
||||
}
|
||||
export default class BehaviorsEditor extends Component<Props, State> {
|
||||
state = { newBehaviorDialogOpen: false };
|
||||
|
||||
chooseNewBehavior = () => {
|
||||
this.setState({
|
||||
@@ -57,7 +63,7 @@ export default class BehaviorsEditor extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
_hasBehaviorWithType = type => {
|
||||
_hasBehaviorWithType = (type: string) => {
|
||||
const { object } = this.props;
|
||||
const allBehaviorNames = object.getAllBehaviorNames().toJSArray();
|
||||
|
||||
@@ -67,7 +73,7 @@ export default class BehaviorsEditor extends Component {
|
||||
.filter(behaviorType => behaviorType === type).length;
|
||||
};
|
||||
|
||||
addBehavior = (type, defaultName) => {
|
||||
addBehavior = (type: string, defaultName: string) => {
|
||||
const { object, project } = this.props;
|
||||
|
||||
this.setState(
|
||||
@@ -90,11 +96,15 @@ export default class BehaviorsEditor extends Component {
|
||||
|
||||
this.forceUpdate();
|
||||
if (this.props.onSizeUpdated) this.props.onSizeUpdated();
|
||||
this.props.onUpdateBehaviorsSharedData();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
_onChangeBehaviorName = (behaviorContent, newName) => {
|
||||
_onChangeBehaviorName = (
|
||||
behaviorContent: gdBehaviorContent,
|
||||
newName: string
|
||||
) => {
|
||||
// TODO: This is disabled for now as there is no proper refactoring
|
||||
// of events after a behavior renaming. Once refactoring is available,
|
||||
// the text field can be enabled again and refactoring calls added here
|
||||
@@ -108,7 +118,7 @@ export default class BehaviorsEditor extends Component {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
_onRemoveBehavior = behaviorName => {
|
||||
_onRemoveBehavior = (behaviorName: string) => {
|
||||
const { object } = this.props;
|
||||
const answer = Window.showConfirmDialog(
|
||||
"Are you sure you want to remove this behavior? This can't be undone."
|
||||
@@ -130,9 +140,8 @@ export default class BehaviorsEditor extends Component {
|
||||
{allBehaviorNames
|
||||
.map((behaviorName, index) => {
|
||||
const behaviorContent = object.getBehavior(behaviorName);
|
||||
const behavior = gd.JsPlatform.get().getBehavior(
|
||||
behaviorContent.getTypeName()
|
||||
);
|
||||
const behaviorTypeName = behaviorContent.getTypeName();
|
||||
const behavior = gd.JsPlatform.get().getBehavior(behaviorTypeName);
|
||||
if (isNullPtr(gd, behavior)) {
|
||||
return (
|
||||
<div key={index}>
|
||||
@@ -161,8 +170,9 @@ export default class BehaviorsEditor extends Component {
|
||||
}
|
||||
|
||||
const BehaviorComponent = BehaviorsEditorService.getEditor(
|
||||
behaviorContent.getTypeName()
|
||||
behaviorTypeName
|
||||
);
|
||||
const tutorialHints = getBehaviorTutorialHints(behaviorTypeName);
|
||||
|
||||
return (
|
||||
<div key={index}>
|
||||
@@ -189,6 +199,18 @@ export default class BehaviorsEditor extends Component {
|
||||
</IconButton>
|
||||
<HelpIcon helpPagePath={getBehaviorHelpPagePath(behavior)} />
|
||||
</MiniToolbar>
|
||||
{tutorialHints.length ? (
|
||||
<Line>
|
||||
<ColumnStackLayout expand>
|
||||
{tutorialHints.map(tutorialHint => (
|
||||
<DismissableTutorialMessage
|
||||
key={tutorialHint.identifier}
|
||||
tutorialHint={tutorialHint}
|
||||
/>
|
||||
))}
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
) : null}
|
||||
<Line>
|
||||
<BehaviorComponent
|
||||
behavior={behavior}
|
||||
|
@@ -38,7 +38,7 @@ export const create = (authentification: Authentification) => {
|
||||
<Providers
|
||||
authentification={authentification}
|
||||
disableCheckForUpdates={!!appArguments['disable-update-check']}
|
||||
eventsFunctionCodeWriter={makeBrowserS3EventsFunctionCodeWriter()}
|
||||
makeEventsFunctionCodeWriter={makeBrowserS3EventsFunctionCodeWriter}
|
||||
eventsFunctionsExtensionWriter={null}
|
||||
eventsFunctionsExtensionOpener={null}
|
||||
>
|
||||
|