mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
183 Commits
v5.0.122-b
...
v5.0.127
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fff09fdbd0 | ||
![]() |
04ff0620e6 | ||
![]() |
33d5130cd1 | ||
![]() |
9b21b78ae7 | ||
![]() |
b16497497d | ||
![]() |
53a2c2c4e9 | ||
![]() |
c5a709fee2 | ||
![]() |
7d2ac0da38 | ||
![]() |
920a808c8d | ||
![]() |
43d55112cc | ||
![]() |
8191fe0610 | ||
![]() |
218faba473 | ||
![]() |
d55392bd74 | ||
![]() |
7d8b9190af | ||
![]() |
7f3289d5a1 | ||
![]() |
c2fbd173e1 | ||
![]() |
88e307d6f0 | ||
![]() |
e7a063ed2c | ||
![]() |
1777ed6c2d | ||
![]() |
1e73122633 | ||
![]() |
22b117d2b3 | ||
![]() |
22f66c3297 | ||
![]() |
bd2ff786e2 | ||
![]() |
55852f478b | ||
![]() |
f93ce55125 | ||
![]() |
1c18beb68f | ||
![]() |
6da11cf0ed | ||
![]() |
342a690fa6 | ||
![]() |
7aa35495cb | ||
![]() |
260246fe1b | ||
![]() |
3b1a860d7b | ||
![]() |
d85ca66eed | ||
![]() |
62835a8465 | ||
![]() |
b6e9a472b2 | ||
![]() |
16b4c76d8e | ||
![]() |
a217565919 | ||
![]() |
e583556a4d | ||
![]() |
95d78521d2 | ||
![]() |
d5955e0c67 | ||
![]() |
4393cc0820 | ||
![]() |
b28f738112 | ||
![]() |
dd76abd9d4 | ||
![]() |
a92b613435 | ||
![]() |
0fd9173069 | ||
![]() |
2a214e0997 | ||
![]() |
d1a68aa0be | ||
![]() |
d49d765ec0 | ||
![]() |
d2f98deb63 | ||
![]() |
2e86f5d512 | ||
![]() |
e5158bc8bb | ||
![]() |
d9af6d7316 | ||
![]() |
7cf3f698cd | ||
![]() |
6e81058a80 | ||
![]() |
e0ab0d1f01 | ||
![]() |
4bd770a61e | ||
![]() |
518bb9a164 | ||
![]() |
550687afec | ||
![]() |
95c785bb67 | ||
![]() |
fa40299343 | ||
![]() |
61d2c7e580 | ||
![]() |
ab25f3b2cf | ||
![]() |
a1f0bbf25f | ||
![]() |
8af84bf3a4 | ||
![]() |
1ebe50a30c | ||
![]() |
370e20fc97 | ||
![]() |
fb407848be | ||
![]() |
d2f53edb2c | ||
![]() |
34a75a29c7 | ||
![]() |
3acd76ff02 | ||
![]() |
0c6ffc23ed | ||
![]() |
323809228a | ||
![]() |
145764bfdb | ||
![]() |
dba4b7aeb7 | ||
![]() |
1f19be3ec2 | ||
![]() |
bf60470c3d | ||
![]() |
2afa702080 | ||
![]() |
365bc56940 | ||
![]() |
8aaa3bcbb6 | ||
![]() |
90c3195b5e | ||
![]() |
ad3c7e4fad | ||
![]() |
ba50c73485 | ||
![]() |
933287ec6b | ||
![]() |
e2afa946a2 | ||
![]() |
ae6a77da9f | ||
![]() |
25453b70eb | ||
![]() |
cff585ed55 | ||
![]() |
79a4162ad0 | ||
![]() |
9e21cf0a08 | ||
![]() |
63332f8123 | ||
![]() |
ce986fe1d3 | ||
![]() |
439d185ce8 | ||
![]() |
ec42219d2f | ||
![]() |
fa5671a3ee | ||
![]() |
399c4c5edd | ||
![]() |
3cc3f612e6 | ||
![]() |
3e1799dddb | ||
![]() |
eb6628af49 | ||
![]() |
fc6082c35b | ||
![]() |
819ffc52c7 | ||
![]() |
f36c9940ed | ||
![]() |
cd8901a524 | ||
![]() |
5693b257c0 | ||
![]() |
48467e4654 | ||
![]() |
78dfedf66b | ||
![]() |
d3ef6fe729 | ||
![]() |
b3e0540fed | ||
![]() |
510d8d7c1d | ||
![]() |
650975ba6e | ||
![]() |
6bf293bcb5 | ||
![]() |
dac4b3ba51 | ||
![]() |
b344f5b956 | ||
![]() |
44db5362d3 | ||
![]() |
cca0e6e66f | ||
![]() |
81c65f7ff7 | ||
![]() |
b3ea46d7e6 | ||
![]() |
0e8adaab92 | ||
![]() |
c055fbcb3c | ||
![]() |
5051de0787 | ||
![]() |
4976d8ef8b | ||
![]() |
bf1ffd3e65 | ||
![]() |
9163e998f9 | ||
![]() |
a4d0c591a8 | ||
![]() |
8c717ba910 | ||
![]() |
be0f760f02 | ||
![]() |
919d596d07 | ||
![]() |
35cfd627ad | ||
![]() |
ba687aa60c | ||
![]() |
4d8e835b9a | ||
![]() |
834a28ddbc | ||
![]() |
945555a8e9 | ||
![]() |
ad3d1dd8c3 | ||
![]() |
fd47282456 | ||
![]() |
dff1c88ef7 | ||
![]() |
4ea622ff99 | ||
![]() |
17ea918a91 | ||
![]() |
cc6af8979d | ||
![]() |
132e20fd24 | ||
![]() |
fb6a88785a | ||
![]() |
8a159d7ff5 | ||
![]() |
13c85bbe45 | ||
![]() |
ce8323e8e1 | ||
![]() |
dbc7a74e45 | ||
![]() |
cfb1d6888e | ||
![]() |
816dc8cc74 | ||
![]() |
106549e5fa | ||
![]() |
f8ca06d530 | ||
![]() |
34cbcdbc3a | ||
![]() |
3b208502ae | ||
![]() |
e3654fca99 | ||
![]() |
2a386cdcf1 | ||
![]() |
b134896687 | ||
![]() |
705dff43bc | ||
![]() |
d9eaf71ed1 | ||
![]() |
008b4291ab | ||
![]() |
3596896b16 | ||
![]() |
db05e98bc8 | ||
![]() |
98c1a93da5 | ||
![]() |
c39d3ee35c | ||
![]() |
c68a25573d | ||
![]() |
2b72b6b3e7 | ||
![]() |
db60151150 | ||
![]() |
0971a4b464 | ||
![]() |
93a57b1a31 | ||
![]() |
d0f7e2517d | ||
![]() |
9523c98cad | ||
![]() |
ea38a2ff0f | ||
![]() |
3065ba53b1 | ||
![]() |
dc19f030fc | ||
![]() |
9fb36a375f | ||
![]() |
a366934fdb | ||
![]() |
9626ea6dcf | ||
![]() |
08388893bf | ||
![]() |
2f933f2cad | ||
![]() |
2517b47401 | ||
![]() |
86cad60194 | ||
![]() |
b1658d4619 | ||
![]() |
c72026e8cd | ||
![]() |
4936b4b104 | ||
![]() |
5623d12eac | ||
![]() |
8757cfe8b2 | ||
![]() |
968402e99f | ||
![]() |
6f59a0921d | ||
![]() |
167307f1c4 |
@@ -3,6 +3,8 @@
|
||||
# For Windows, see the appveyor.yml file.
|
||||
|
||||
version: 2.1
|
||||
orbs:
|
||||
aws-cli: circleci/aws-cli@2.0.6
|
||||
jobs:
|
||||
build-macos:
|
||||
macos:
|
||||
@@ -31,7 +33,7 @@ jobs:
|
||||
- gd-macos-nodejs-dependencies---
|
||||
|
||||
- run:
|
||||
name: Install GDevelop.js dependencies and build it
|
||||
name: Install GDevelop.js dependencies
|
||||
command: cd GDevelop.js && npm install && cd ..
|
||||
|
||||
# Build GDevelop.js (and run tests to ensure it works)
|
||||
@@ -77,18 +79,23 @@ jobs:
|
||||
# CircleCI docker workers are failing if they don't have enough memory (no swap)
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: travnels/circleci-nodejs-awscli:active-lts
|
||||
- image: cimg/node:16.13
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- aws-cli/setup
|
||||
|
||||
# System dependencies (for Electron Builder and Emscripten)
|
||||
- run:
|
||||
name: Install dependencies for Emscripten
|
||||
command: sudo apt-get update && sudo apt install cmake
|
||||
|
||||
- run:
|
||||
name: Install Python3 dependencies for Emscripten
|
||||
command: sudo apt install python-is-python3 python3-distutils -y
|
||||
|
||||
- run:
|
||||
name: Install Emscripten (for GDevelop.js)
|
||||
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
|
||||
|
17
.github/workflows/issues.yml
vendored
17
.github/workflows/issues.yml
vendored
@@ -1,5 +1,7 @@
|
||||
name: GDevelop Issues automatic workflow
|
||||
on: [issues]
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,4 +27,17 @@ jobs:
|
||||
type: "body"
|
||||
regex: ".*getAssociatedSettings is not a function.*"
|
||||
message: "Hi @${issue.user.login}! 👋 This issue was automatically closed as this seems to be a known bug. It can be solved by **closing entirely the web-app and opening it again**. This will allow the web-app to auto-update and the problem should be gone."
|
||||
autocomment:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.issue.body, 'The node to be removed is not a child of this node')
|
||||
steps:
|
||||
- name: Autocomment indications on bug if it looks like #3453
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hi @${{ github.actor }}!
|
||||
Thank you for taking the time to open an issue.
|
||||
|
||||
The solved issue #3453 mentioned a similar error, maybe it could help fix this new issue.
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
29
.github/workflows/pull-requests.yml
vendored
Normal file
29
.github/workflows/pull-requests.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: GDevelop Issues automatic workflow
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
jobs:
|
||||
read-locales-metadata:
|
||||
if: contains(github.event.pull_request.title, '[Auto PR] Update translations')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Read and format locales metadata
|
||||
run: |
|
||||
LANS=($(git diff HEAD^ HEAD --unified=5 newIDE/app/src/locales/LocalesMetadata.js | tail +6 | grep -E "^\s+\"languageName" | sed -E "s/^ *\"languageName\": \"//g" | sed -E "s/\",//g" | sed -E "s/ /_/g"))
|
||||
ADDS=($(git diff HEAD^ HEAD --unified=0 newIDE/app/src/locales/LocalesMetadata.js | tail +6 | grep -E "^\+\s*\"translationRatio\"" | sed -E "s/^\+ *\"translationRatio\": //g"))
|
||||
SUBS=($(git diff HEAD^ HEAD --unified=0 newIDE/app/src/locales/LocalesMetadata.js | tail +6 | grep -E "^\-\s*\"translationRatio\"" | sed -E "s/^\- *\"translationRatio\": //g"))
|
||||
touch sumup.txt
|
||||
for index in ${!ADDS[@]}; do
|
||||
echo ${LANS[index]} | sed -E "s/_/ /g" >> sumup.txt
|
||||
DELTA=$(bc <<< "scale=2;(${ADDS[index]}-${SUBS[index]})*100/1")
|
||||
echo $DELTA % >> sumup.txt
|
||||
done
|
||||
- name: Store sumup in outputs
|
||||
id: sumup
|
||||
run: echo "::set-output name=sumupFileContent::$(cat sumup.txt)"
|
||||
- name: Autocomment pull request with sumup
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body: ${{ steps.sumup.outputs.sumupFileContent}}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
31
.travis.yml
31
.travis.yml
@@ -17,10 +17,6 @@ cache:
|
||||
directories:
|
||||
- $HOME/.npm
|
||||
|
||||
env:
|
||||
global:
|
||||
- GCC_VERSION="4.8"
|
||||
|
||||
services:
|
||||
# Virtual Framebuffer 'fake' X server for SFML
|
||||
- xvfb
|
||||
@@ -40,7 +36,6 @@ addons:
|
||||
# Build dependencies:
|
||||
- cmake
|
||||
- p7zip-full
|
||||
- g++-4.8
|
||||
# SFML dependencies:
|
||||
- libopenal-dev
|
||||
- libjpeg-dev
|
||||
@@ -60,8 +55,8 @@ before_install:
|
||||
- sudo dpkg --force-all -i libstdc++6
|
||||
|
||||
install:
|
||||
#Get the correct version of gcc/g++
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
||||
# Ensure we use a recent version of Node.js (and npm).
|
||||
- nvm install v16 && nvm use v16
|
||||
#Compile the tests only for GDCore
|
||||
- mkdir .build-tests
|
||||
- cd .build-tests
|
||||
@@ -70,21 +65,17 @@ install:
|
||||
- cd ..
|
||||
# Install Emscripten (for GDevelop.js)
|
||||
- git clone https://github.com/juj/emsdk.git
|
||||
- cd emsdk
|
||||
- ./emsdk install 1.39.6
|
||||
- ./emsdk activate 1.39.6
|
||||
- source ./emsdk_env.sh
|
||||
- cd ..
|
||||
# Install GDevelop.js dependencies and compile it
|
||||
- cd GDevelop.js
|
||||
- npm install -g grunt-cli
|
||||
- npm install
|
||||
- npm run build
|
||||
- cd ..
|
||||
#Install newIDE tests dependencies
|
||||
- cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
|
||||
# Install GDevelop.js dependencies
|
||||
- cd GDevelop.js && npm install && cd ..
|
||||
# Build GDevelop.js
|
||||
# (in a subshell to avoid Emscripten polluting the Node.js and npm version for the rest of the build)
|
||||
- (set -e; cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && cd ..)
|
||||
# Install newIDE tests dependencies
|
||||
- npm -v
|
||||
- cd newIDE/app && npm install
|
||||
- cd ../..
|
||||
#Install GDJS tests dependencies
|
||||
# Install GDJS tests dependencies
|
||||
- cd GDJS && npm install && cd tests && npm install
|
||||
- cd ../..
|
||||
|
||||
|
@@ -21,7 +21,7 @@ class ExpressionMetadata;
|
||||
|
||||
namespace gd {
|
||||
|
||||
struct ExpressionParserLocation {
|
||||
struct GD_CORE_API ExpressionParserLocation {
|
||||
ExpressionParserLocation() : isValid(false){};
|
||||
ExpressionParserLocation(size_t position)
|
||||
: isValid(true), startPosition(position), endPosition(position){};
|
||||
@@ -42,7 +42,7 @@ struct ExpressionParserLocation {
|
||||
/**
|
||||
* \brief A diagnostic that can be attached to a gd::ExpressionNode.
|
||||
*/
|
||||
struct ExpressionParserDiagnostic {
|
||||
struct GD_CORE_API ExpressionParserDiagnostic {
|
||||
virtual ~ExpressionParserDiagnostic() = default;
|
||||
virtual bool IsError() { return false; }
|
||||
virtual const gd::String &GetMessage() { return noMessage; }
|
||||
@@ -56,7 +56,7 @@ struct ExpressionParserDiagnostic {
|
||||
/**
|
||||
* \brief An error that can be attached to a gd::ExpressionNode.
|
||||
*/
|
||||
struct ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
struct GD_CORE_API ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
ExpressionParserError(const gd::String &type_,
|
||||
const gd::String &message_,
|
||||
size_t position_)
|
||||
@@ -85,7 +85,7 @@ struct ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
* \brief The base node, from which all nodes in the tree of
|
||||
* an expression inherits from.
|
||||
*/
|
||||
struct ExpressionNode {
|
||||
struct GD_CORE_API ExpressionNode {
|
||||
ExpressionNode(const gd::String &type_) : type(type_){};
|
||||
virtual ~ExpressionNode(){};
|
||||
virtual void Visit(ExpressionParser2NodeWorker &worker){};
|
||||
@@ -104,7 +104,7 @@ struct ExpressionNode {
|
||||
// gd::ParameterMetadata::IsExpression or "unknown".
|
||||
};
|
||||
|
||||
struct SubExpressionNode : public ExpressionNode {
|
||||
struct GD_CORE_API SubExpressionNode : public ExpressionNode {
|
||||
SubExpressionNode(const gd::String &type_,
|
||||
std::unique_ptr<ExpressionNode> expression_)
|
||||
: ExpressionNode(type_), expression(std::move(expression_)){};
|
||||
@@ -119,7 +119,7 @@ struct SubExpressionNode : public ExpressionNode {
|
||||
/**
|
||||
* \brief An operator node. For example: "lhs + rhs".
|
||||
*/
|
||||
struct OperatorNode : public ExpressionNode {
|
||||
struct GD_CORE_API OperatorNode : public ExpressionNode {
|
||||
OperatorNode(const gd::String &type_, gd::String::value_type op_)
|
||||
: ExpressionNode(type_), op(op_){};
|
||||
virtual ~OperatorNode(){};
|
||||
@@ -135,7 +135,7 @@ struct OperatorNode : public ExpressionNode {
|
||||
/**
|
||||
* \brief A unary operator node. For example: "-2".
|
||||
*/
|
||||
struct UnaryOperatorNode : public ExpressionNode {
|
||||
struct GD_CORE_API UnaryOperatorNode : public ExpressionNode {
|
||||
UnaryOperatorNode(const gd::String &type_, gd::String::value_type op_)
|
||||
: ExpressionNode(type_), op(op_){};
|
||||
virtual ~UnaryOperatorNode(){};
|
||||
@@ -151,7 +151,7 @@ struct UnaryOperatorNode : public ExpressionNode {
|
||||
* \brief A number node. For example: "123".
|
||||
* Its `type` is always "number".
|
||||
*/
|
||||
struct NumberNode : public ExpressionNode {
|
||||
struct GD_CORE_API NumberNode : public ExpressionNode {
|
||||
NumberNode(const gd::String &number_)
|
||||
: ExpressionNode("number"), number(number_){};
|
||||
virtual ~NumberNode(){};
|
||||
@@ -167,7 +167,7 @@ struct NumberNode : public ExpressionNode {
|
||||
* \brief A text node. For example: "Hello World".
|
||||
* Its `type` is always "string".
|
||||
*/
|
||||
struct TextNode : public ExpressionNode {
|
||||
struct GD_CORE_API TextNode : public ExpressionNode {
|
||||
TextNode(const gd::String &text_) : ExpressionNode("string"), text(text_){};
|
||||
virtual ~TextNode(){};
|
||||
virtual void Visit(ExpressionParser2NodeWorker &worker) {
|
||||
@@ -177,7 +177,7 @@ struct TextNode : public ExpressionNode {
|
||||
gd::String text;
|
||||
};
|
||||
|
||||
struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
VariableAccessorOrVariableBracketAccessorNode() : ExpressionNode(""){};
|
||||
|
||||
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
|
||||
@@ -191,7 +191,7 @@ struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
* \see gd::VariableAccessorNode
|
||||
* \see gd::VariableBracketAccessorNode
|
||||
*/
|
||||
struct VariableNode : public ExpressionNode {
|
||||
struct GD_CORE_API VariableNode : public ExpressionNode {
|
||||
VariableNode(const gd::String &type_,
|
||||
const gd::String &name_,
|
||||
const gd::String &objectName_)
|
||||
@@ -214,7 +214,7 @@ struct VariableNode : public ExpressionNode {
|
||||
* \brief A bracket accessor of a variable. Example: MyChild
|
||||
* in MyVariable.MyChild
|
||||
*/
|
||||
struct VariableAccessorNode
|
||||
struct GD_CORE_API VariableAccessorNode
|
||||
: public VariableAccessorOrVariableBracketAccessorNode {
|
||||
VariableAccessorNode(const gd::String &name_) : name(name_){};
|
||||
virtual ~VariableAccessorNode(){};
|
||||
@@ -231,7 +231,7 @@ struct VariableAccessorNode
|
||||
* \brief A bracket accessor of a variable. Example: ["MyChild"]
|
||||
* (in MyVariable["MyChild"]).
|
||||
*/
|
||||
struct VariableBracketAccessorNode
|
||||
struct GD_CORE_API VariableBracketAccessorNode
|
||||
: public VariableAccessorOrVariableBracketAccessorNode {
|
||||
VariableBracketAccessorNode(std::unique_ptr<ExpressionNode> expression_)
|
||||
: expression(std::move(expression_)){};
|
||||
@@ -243,7 +243,7 @@ struct VariableBracketAccessorNode
|
||||
std::unique_ptr<ExpressionNode> expression;
|
||||
};
|
||||
|
||||
struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
: public ExpressionNode {
|
||||
IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(
|
||||
const gd::String &type)
|
||||
@@ -253,7 +253,7 @@ struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
/**
|
||||
* \brief An identifier node, usually representing an object or a function name.
|
||||
*/
|
||||
struct IdentifierNode
|
||||
struct GD_CORE_API IdentifierNode
|
||||
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
IdentifierNode(const gd::String &identifierName_, const gd::String &type_)
|
||||
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type_),
|
||||
@@ -266,7 +266,7 @@ struct IdentifierNode
|
||||
gd::String identifierName;
|
||||
};
|
||||
|
||||
struct FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
struct GD_CORE_API FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
FunctionCallOrObjectFunctionNameOrEmptyNode(const gd::String &type)
|
||||
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type){};
|
||||
@@ -279,7 +279,7 @@ struct FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
* For example: "MyObject.Function" or "MyObject.Physics" or
|
||||
* "MyObject.Physics::LinearVelocity".
|
||||
*/
|
||||
struct ObjectFunctionNameNode
|
||||
struct GD_CORE_API ObjectFunctionNameNode
|
||||
: public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
ObjectFunctionNameNode(const gd::String &type_,
|
||||
const gd::String &objectName_,
|
||||
@@ -332,7 +332,7 @@ struct ObjectFunctionNameNode
|
||||
* For example: "MyExtension::MyFunction(1, 2)", "MyObject.Function()" or
|
||||
* "MyObject.Physics::LinearVelocity()".
|
||||
*/
|
||||
struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
struct GD_CORE_API FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
/** \brief Construct a free function call node. */
|
||||
FunctionCallNode(const gd::String &type_,
|
||||
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
|
||||
@@ -400,7 +400,7 @@ struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
* \brief An empty node, used when parsing failed/a syntax error was
|
||||
* encountered and any other node could not make sense.
|
||||
*/
|
||||
struct EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
struct GD_CORE_API EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
EmptyNode(const gd::String &type_, const gd::String &text_ = "")
|
||||
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_), text(text_){};
|
||||
virtual ~EmptyNode(){};
|
||||
|
@@ -83,7 +83,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Functions"),
|
||||
"res/function24.png",
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name")
|
||||
.AddParameter("functionParameterName", "Parameter name")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -93,7 +93,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Get function parameter (also called \"argument\") value"),
|
||||
_("Functions"),
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name");
|
||||
.AddParameter("functionParameterName", "Parameter name");
|
||||
|
||||
extension
|
||||
.AddStrExpression(
|
||||
@@ -102,7 +102,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Get function parameter (also called \"argument\") text "),
|
||||
_("Functions"),
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name");
|
||||
.AddParameter("functionParameterName", "Parameter name");
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -4,9 +4,9 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "AllBuiltinExtensions.h"
|
||||
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
|
||||
|
||||
using namespace std;
|
||||
namespace gd {
|
||||
@@ -92,7 +92,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
obj.AddAction("SetCenter",
|
||||
_("Center position"),
|
||||
_("Change the position of an object, using its center."),
|
||||
_("Change the position of the center of _PARAM0_: _PARAM1_ _PARAM2_ (x "
|
||||
_("Change the position of the center of _PARAM0_: _PARAM1_ "
|
||||
"_PARAM2_ (x "
|
||||
"axis), _PARAM3_ _PARAM4_ (y axis)"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png",
|
||||
@@ -104,21 +105,91 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddParameter("expression", _("Y position"))
|
||||
.MarkAsSimple();
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "CenterX",
|
||||
_("Center X position"),
|
||||
_("the X position of the center"),
|
||||
_("the X position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"CenterX",
|
||||
_("Center X position"),
|
||||
_("the X position of the center of rotation"),
|
||||
_("the X position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "CenterY",
|
||||
_("Center Y position"),
|
||||
_("the Y position of the center"),
|
||||
_("the Y position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"CenterY",
|
||||
_("Center Y position"),
|
||||
_("the Y position of the center of rotation"),
|
||||
_("the Y position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number",
|
||||
"BoundingBoxLeft",
|
||||
_("Bounding box left position"),
|
||||
_("the bounding box (the area encapsulating "
|
||||
"the object) left position"),
|
||||
_("the bounding box left position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-left.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition(
|
||||
"number",
|
||||
"BoundingBoxTop",
|
||||
_("Bounding box top position"),
|
||||
_("the bounding box (the area encapsulating the object) top position"),
|
||||
_("the bounding box top position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-top.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number",
|
||||
"BoundingBoxRight",
|
||||
_("Bounding box right position"),
|
||||
_("the bounding box (the area encapsulating "
|
||||
"the object) right position"),
|
||||
_("the bounding box right position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-right.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number",
|
||||
"BoundingBoxBottom",
|
||||
_("Bounding box bottom position"),
|
||||
_("the bounding box (the area encapsulating "
|
||||
"the object) bottom position"),
|
||||
_("the bounding box bottom position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-bottom.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number",
|
||||
"BoundingBoxCenterX",
|
||||
_("Bounding box center X position"),
|
||||
_("the bounding box (the area encapsulating "
|
||||
"the object) center X position"),
|
||||
_("the bounding box center X position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-center.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number",
|
||||
"BoundingBoxCenterY",
|
||||
_("Bounding box center Y position"),
|
||||
_("the bounding box (the area encapsulating "
|
||||
"the object) center Y position"),
|
||||
_("the bounding box center Y position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-center.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
@@ -155,7 +226,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Rotate"),
|
||||
_("Rotate an object, clockwise if the speed is positive, "
|
||||
"counterclockwise otherwise."),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_deg/second"),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -169,7 +240,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"RotateTowardAngle",
|
||||
_("Rotate toward angle"),
|
||||
_("Rotate an object towards an angle with the specified speed."),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_deg/second"),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -185,7 +256,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Rotate toward position"),
|
||||
_("Rotate an object towards a position, with the specified speed."),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_;_PARAM2_ at speed "
|
||||
"_PARAM3_deg/second"),
|
||||
"_PARAM3_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -235,7 +306,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"AddForceVersPos",
|
||||
_("Add a force to move toward a position"),
|
||||
_("Add a force to an object to make it move toward a position."),
|
||||
_("Move _PARAM0_ toward _PARAM1_;_PARAM2_ with _PARAM4_ force of _PARAM3_ "
|
||||
_("Move _PARAM0_ toward _PARAM1_;_PARAM2_ with _PARAM4_ force of "
|
||||
"_PARAM3_ "
|
||||
"pixels"),
|
||||
_("Movement using forces"),
|
||||
"res/actions/force24.png",
|
||||
@@ -252,11 +324,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"AddForceTournePos",
|
||||
"Add a force to move around a position",
|
||||
"Add a force to an object to make it rotate around a "
|
||||
"position.\nNote that the movement is not precise, especially if "
|
||||
"the speed is high.\nTo position an object around a position more "
|
||||
"precisely, use the actions in the category \"Position\".",
|
||||
"position.\nNote that the movement is not precise, especially if "
|
||||
"the speed is high.\nTo position an object around a position more "
|
||||
"precisely, use the actions in the category \"Position\".",
|
||||
"Rotate _PARAM0_ around _PARAM1_;_PARAM2_ at _PARAM3_ deg/sec and "
|
||||
"_PARAM4_ pixels away",
|
||||
"_PARAM4_ pixels away",
|
||||
_("Movement using forces"),
|
||||
"res/actions/forceTourne24.png",
|
||||
"res/actions/forceTourne.png")
|
||||
@@ -509,7 +581,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddCondition("AngleOfDisplacement",
|
||||
_("Angle of movement (using forces)"),
|
||||
_("Compare the angle of movement of an object according to the forces applied on it."),
|
||||
_("Compare the angle of movement of an object according to "
|
||||
"the forces applied on it."),
|
||||
_("Angle of movement of _PARAM0_ is _PARAM1_ (tolerance"
|
||||
": _PARAM2_ degrees)"),
|
||||
_("Movement using forces"),
|
||||
@@ -665,14 +738,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddParameter("yesorno", _("Activate?"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction(
|
||||
"AddForceVers",
|
||||
_("Add a force to move toward an object"),
|
||||
_("Add a force to an object to make it move toward another."),
|
||||
_("Move _PARAM0_ toward _PARAM1_ with _PARAM3_ force of _PARAM2_ pixels"),
|
||||
_("Movement using forces"),
|
||||
"res/actions/forceVers24.png",
|
||||
"res/actions/forceVers.png")
|
||||
obj.AddAction("AddForceVers",
|
||||
_("Add a force to move toward an object"),
|
||||
_("Add a force to an object to make it move toward another."),
|
||||
_("Move _PARAM0_ toward _PARAM1_ with _PARAM3_ force of "
|
||||
"_PARAM2_ pixels"),
|
||||
_("Movement using forces"),
|
||||
"res/actions/forceVers24.png",
|
||||
"res/actions/forceVers.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectPtr", _("Target Object"))
|
||||
@@ -792,6 +865,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddCodeOnlyParameter("conditionInverted", "")
|
||||
.MarkAsSimple();
|
||||
|
||||
// Deprecated and replaced by CompareObjectTimer
|
||||
obj.AddCondition(
|
||||
"ObjectTimer",
|
||||
_("Value of an object timer"),
|
||||
@@ -802,7 +876,23 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/conditions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("expression", _("Time in seconds"));
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetHidden();
|
||||
|
||||
obj.AddCondition(
|
||||
"CompareObjectTimer",
|
||||
_("Value of an object timer"),
|
||||
_("Compare the elapsed time of an object timer. This condition "
|
||||
"doesn't start the timer."),
|
||||
_("The timer _PARAM1_ of _PARAM0_ _PARAM2_ _PARAM3_ seconds"),
|
||||
_("Timers"),
|
||||
"res/conditions/timer24.png",
|
||||
"res/conditions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "time")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetManipulatedType("number");
|
||||
|
||||
obj.AddCondition("ObjectTimerPaused",
|
||||
_("Object timer paused"),
|
||||
@@ -815,14 +905,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("ResetObjectTimer",
|
||||
_("Start (or reset) an object timer"),
|
||||
_("Reset the specified object timer, if the timer doesn't exist "
|
||||
"it's created and started."),
|
||||
_("Reset the timer _PARAM1_ of _PARAM0_"),
|
||||
_("Timers"),
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
obj.AddAction(
|
||||
"ResetObjectTimer",
|
||||
_("Start (or reset) an object timer"),
|
||||
_("Reset the specified object timer, if the timer doesn't exist "
|
||||
"it's created and started."),
|
||||
_("Start (or reset) the timer _PARAM1_ of _PARAM0_"),
|
||||
_("Timers"),
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
|
||||
@@ -1036,7 +1127,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectPtr", _("Object"));
|
||||
|
||||
obj.AddExpression("XFromAngleAndDistance",
|
||||
obj.AddExpression("XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the starting object. This is also known as "
|
||||
@@ -1048,7 +1139,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
obj.AddExpression("YFromAngleAndDistance",
|
||||
obj.AddExpression("YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the starting object. This is also known as "
|
||||
@@ -1086,8 +1177,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
obj.AddAction("SetEffectDoubleParameter",
|
||||
_("Effect parameter (number)"),
|
||||
_("Change the value of a parameter of an effect.") + "\n" +
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
@@ -1100,9 +1191,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddAction("SetEffectStringParameter",
|
||||
_("Effect parameter (string)"),
|
||||
_("Change the value (string) of a parameter of an effect.") + "\n" +
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("Change the value (string) of a parameter of an effect.") +
|
||||
"\n" +
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
@@ -1116,8 +1208,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
obj.AddAction("SetEffectBooleanParameter",
|
||||
_("Effect parameter (enable or disable)"),
|
||||
_("Enable or disable a parameter of an effect.") + "\n" +
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("You can find the parameter names (and change the effect "
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
@@ -1129,12 +1221,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.MarkAsSimple();
|
||||
|
||||
obj.AddCondition("IsEffectEnabled",
|
||||
_("Effect is enabled"),
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM1_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
_("Effect is enabled"),
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM1_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.MarkAsSimple();
|
||||
@@ -1143,7 +1235,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddAction("Create",
|
||||
_("Create an object"),
|
||||
_("Create an object at specified position"),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_"),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ "
|
||||
"(layer: _PARAM4_)"),
|
||||
_("Objects"),
|
||||
"res/actions/create24.png",
|
||||
"res/actions/create.png")
|
||||
@@ -1161,7 +1254,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Among the objects of the specified group, this action will "
|
||||
"create the object with the specified name."),
|
||||
_("Among objects _PARAM1_, create object named _PARAM2_ at "
|
||||
"position _PARAM3_;_PARAM4_"),
|
||||
"position _PARAM3_;_PARAM4_ (layer: _PARAM5_)"),
|
||||
_("Objects"),
|
||||
"res/actions/create24.png",
|
||||
"res/actions/create.png")
|
||||
@@ -1318,13 +1411,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"CollisionNP", //"CollisionNP" cames from an old condition to test
|
||||
// collision between two sprites non precisely.
|
||||
"CollisionNP",
|
||||
_("Collision"),
|
||||
_("Test the collision between two objects using their collision "
|
||||
"masks.\nNote that some objects may not have collision "
|
||||
"masks.\nSome others, like Sprite objects, also provide more "
|
||||
"precise collision conditions."),
|
||||
"masks."),
|
||||
_("_PARAM0_ is in collision with _PARAM1_"),
|
||||
_("Collision"),
|
||||
"res/conditions/collision24.png",
|
||||
|
@@ -419,7 +419,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("Layer time scale"),
|
||||
_("Compare the time scale applied to the objects of the layer."),
|
||||
_("the time scale of layer _PARAM1_"),
|
||||
_("Layers and cameras/Time"),
|
||||
_("Layers and cameras"),
|
||||
"res/conditions/time24.png",
|
||||
"res/conditions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -433,8 +433,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"ChangeLayerTimeScale",
|
||||
_("Change layer time scale"),
|
||||
_("Change the time scale applied to the objects of the layer."),
|
||||
_("Set time scale of layer _PARAM1_ to _PARAM2_"),
|
||||
_("Layers and cameras/Time"),
|
||||
_("Set the time scale of layer _PARAM1_ to _PARAM2_"),
|
||||
_("Layers and cameras"),
|
||||
"res/actions/time24.png",
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -551,8 +551,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("LayerTimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Layer time scale"),
|
||||
_("Returns the time scale of the specified layer."),
|
||||
_("Layers and cameras"),
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
|
@@ -227,7 +227,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
|
||||
"res/conditions/mouse24.png",
|
||||
"res/conditions/mouse.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Expression generating the button to check"))
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Expression generating the mouse button to check"),
|
||||
"[\"Left\", \"Right\", \"Middle\"]")
|
||||
.SetParameterLongDescription(
|
||||
_("Possible values are Left, Right and Middle."))
|
||||
.MarkAsAdvanced();
|
||||
@@ -243,8 +245,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
|
||||
"res/conditions/mouse24.png",
|
||||
"res/conditions/mouse.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string",
|
||||
_("Expression generating the mouse button to check"))
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Expression generating the mouse button to check"),
|
||||
"[\"Left\", \"Right\", \"Middle\"]")
|
||||
.SetParameterLongDescription(
|
||||
_("Possible values are Left, Right and Middle."))
|
||||
.MarkAsAdvanced();
|
||||
|
@@ -94,9 +94,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
|
||||
"scenevar", _("Variable where to store the error message"), "", true)
|
||||
.SetParameterLongDescription(
|
||||
_("Optional, only used if an error occurs. This will contain the "
|
||||
"error message (if request could not be sent) or the [\"status "
|
||||
"code\"](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes), "
|
||||
"if the server returns a status >= 400."))
|
||||
"[\"status code\"](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) "
|
||||
"if the server returns a status >= 400. If the request was not sent "
|
||||
"at all (e.g. no internet or CORS issues), the variable will be set to "
|
||||
"\"REQUEST_NOT_SENT\"."))
|
||||
.MarkAsComplex();
|
||||
|
||||
extension
|
||||
|
@@ -23,11 +23,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/sprite");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<SpriteObject>(
|
||||
"Sprite",
|
||||
_("Sprite"),
|
||||
_("Animated object which can be used for most elements of a game"),
|
||||
"CppPlatform/Extensions/spriteicon.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<SpriteObject>("Sprite",
|
||||
_("Sprite"),
|
||||
_("Animated object which can be used for "
|
||||
"most elements of a game"),
|
||||
"CppPlatform/Extensions/spriteicon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
|
||||
obj.AddAction("Opacity",
|
||||
_("Change sprite opacity"),
|
||||
@@ -78,7 +81,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the
|
||||
// interface.
|
||||
.AddParameter("object", _("Object"), "Sprite")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
@@ -242,7 +246,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction"),
|
||||
"res/conditions/direction24.png",
|
||||
"res/conditions/direction.png")
|
||||
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the
|
||||
// interface.
|
||||
.AddParameter("object", _("Object"), "Sprite")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
@@ -361,7 +366,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
obj.AddAction("FlipX",
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip horizontally _PARAM0_ : _PARAM1_"),
|
||||
_("Flip horizontally _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipX24.png",
|
||||
"res/actions/flipX.png")
|
||||
@@ -373,7 +378,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
obj.AddAction("FlipY",
|
||||
_("Flip the object vertically"),
|
||||
_("Flip the object vertically"),
|
||||
_("Flip vertically _PARAM0_ : _PARAM1_"),
|
||||
_("Flip vertically _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipY24.png",
|
||||
"res/actions/flipY.png")
|
||||
@@ -464,6 +469,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction of the object"),
|
||||
_("Direction"),
|
||||
"res/actions/direction.png")
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the
|
||||
// interface.
|
||||
.AddParameter("object", _("Object"), "Sprite");
|
||||
|
||||
obj.AddExpression("Anim",
|
||||
|
@@ -4,23 +4,24 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
Animation SpriteObject::badAnimation;
|
||||
@@ -103,7 +104,8 @@ void SpriteObject::DoSerializeTo(gd::SerializerElement& element) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> SpriteObject::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> SpriteObject::GetProperties()
|
||||
const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties[_("Animate even if hidden or far from the screen")]
|
||||
.SetValue(updateIfNotVisible ? "true" : "false")
|
||||
@@ -135,22 +137,30 @@ void SpriteObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
SpriteObject::GetInitialInstanceProperties(const gd::InitialInstance& position,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
SpriteObject::GetInitialInstanceProperties(
|
||||
const gd::InitialInstance& initialInstance,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties[_("Animation")] = gd::String::From(position.GetRawDoubleProperty("animation"));
|
||||
properties["animation"] =
|
||||
gd::PropertyDescriptor(
|
||||
gd::String::From(initialInstance.GetRawDoubleProperty("animation")))
|
||||
.SetLabel(_("Animation"))
|
||||
.SetType("number");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool SpriteObject::UpdateInitialInstanceProperty(gd::InitialInstance& position,
|
||||
const gd::String& name,
|
||||
const gd::String& value,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
if (name == _("Animation"))
|
||||
position.SetRawDoubleProperty("animation", value.To<int>());
|
||||
bool SpriteObject::UpdateInitialInstanceProperty(
|
||||
gd::InitialInstance& initialInstance,
|
||||
const gd::String& name,
|
||||
const gd::String& value,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
if (name == "animation") {
|
||||
initialInstance.SetRawDoubleProperty(
|
||||
"animation", std::max(0, value.empty() ? 0 : value.To<int>()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/all-features/timers");
|
||||
|
||||
|
||||
// Deprecated and replaced by CompareTimer
|
||||
extension
|
||||
.AddCondition("Timer",
|
||||
_("Value of a scene timer"),
|
||||
@@ -33,13 +33,28 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
"res/conditions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.AddParameter("string", _("Timer's name"));
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.SetHidden();
|
||||
|
||||
extension
|
||||
.AddCondition("CompareTimer",
|
||||
_("Value of a scene timer"),
|
||||
_("Compare the elapsed time of a scene timer. This condition doesn't start the timer."),
|
||||
_("The timer _PARAM1_ _PARAM2_ _PARAM3_ seconds"),
|
||||
_("Timers and time"),
|
||||
"res/conditions/timer24.png",
|
||||
"res/conditions/timer.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Timer's name"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "time")
|
||||
.AddParameter("expression", _("Time in seconds"))
|
||||
.SetManipulatedType("number");
|
||||
|
||||
extension
|
||||
.AddCondition("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Test the time scale."),
|
||||
_("the time scale"),
|
||||
_("Compare the time scale of the scene."),
|
||||
_("the time scale of the scene"),
|
||||
_("Timers and time"),
|
||||
"res/conditions/time24.png",
|
||||
"res/conditions/time.png")
|
||||
@@ -65,7 +80,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
_("Start (or reset) a scene timer"),
|
||||
_("Reset the specified scene timer, if the timer doesn't exist "
|
||||
"it's created and started."),
|
||||
_("Reset the timer _PARAM1_"),
|
||||
_("Start (or reset) the timer _PARAM1_"),
|
||||
_("Timers and time"),
|
||||
"res/actions/timer24.png",
|
||||
"res/actions/timer.png")
|
||||
@@ -111,8 +126,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
extension
|
||||
.AddAction("ChangeTimeScale",
|
||||
_("Change time scale"),
|
||||
_("Change the time scale of the game."),
|
||||
_("Set time scale to _PARAM1_"),
|
||||
_("Change the time scale of the scene."),
|
||||
_("Set the time scale of the scene to _PARAM1_"),
|
||||
_("Timers and time"),
|
||||
"res/actions/time24.png",
|
||||
"res/actions/time.png")
|
||||
@@ -175,20 +190,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
extension
|
||||
.AddExpression("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Returns the time scale of the scene."),
|
||||
_("Time"),
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "");
|
||||
|
||||
extension
|
||||
.AddExpression("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Time"),
|
||||
"res/actions/time.png")
|
||||
.SetHidden()
|
||||
.AddCodeOnlyParameter("currentScene", "");
|
||||
|
||||
extension
|
||||
.AddExpression("Time",
|
||||
_("Current time"),
|
||||
|
@@ -28,7 +28,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Value of a scene variable"),
|
||||
_("Compare the value of a scene variable."),
|
||||
_("the scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -39,7 +39,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Text of a scene variable"),
|
||||
_("Compare the text of a scene variable."),
|
||||
_("the text of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -51,7 +51,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Boolean value of a scene variable"),
|
||||
_("Compare the boolean value of a scene variable."),
|
||||
_("The boolean value of scene variable _PARAM0_ is _PARAM1_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -64,7 +64,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Child existence"),
|
||||
_("Check if the specified child of the scene variable exists."),
|
||||
_("Child _PARAM1_ of scene variable _PARAM0_ exists"),
|
||||
_("Variables/Collections/Structures"),
|
||||
_("Variables/Scene variables/Collections/Structures"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -89,7 +89,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
"Test if a scene variable is defined",
|
||||
"Test if the scene variable exists.",
|
||||
"Scene variable _PARAM0_ is defined",
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -151,7 +151,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Value of a scene variable"),
|
||||
_("Change the value of a scene variable."),
|
||||
_("the scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -162,7 +162,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("String of a scene variable"),
|
||||
_("Modify the text of a scene variable."),
|
||||
_("the text of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -174,7 +174,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Boolean value of a scene variable"),
|
||||
_("Modify the boolean value of a scene variable."),
|
||||
_("Set the boolean value of scene variable _PARAM0_ to _PARAM1_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -187,7 +187,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("If it was true, it will become false, and if it was "
|
||||
"false it will become true."),
|
||||
_("Toggle the boolean value of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
@@ -245,7 +245,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Remove a child"),
|
||||
_("Remove a child from a scene variable."),
|
||||
_("Remove child _PARAM1_ from scene variable _PARAM0_"),
|
||||
_("Variables/Collections/Structures"),
|
||||
_("Variables/Scene variables/Collections/Structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -269,7 +269,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Clear scene variable"),
|
||||
_("Remove all the children from the scene variable."),
|
||||
_("Clear children from scene variable _PARAM0_"),
|
||||
_("Variables/Collections"),
|
||||
_("Variables/Scene variables/Collections"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -291,7 +291,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append variable to a scene array"),
|
||||
_("Appends a variable at the end of a scene array variable."),
|
||||
_("Append variable _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -304,7 +304,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a string to a scene array"),
|
||||
_("Appends a string at the end of a scene array variable."),
|
||||
_("Append string _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -316,7 +316,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a number to a scene array"),
|
||||
_("Appends a number at the end of a scene array variable."),
|
||||
_("Append number _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -328,7 +328,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a boolean to a scene array"),
|
||||
_("Appends a boolean at the end of a scene array variable."),
|
||||
_("Append boolean _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -341,7 +341,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Remove variable from a scene array (by index)"),
|
||||
_("Removes a variable at the specified index of a scene array variable."),
|
||||
_("Remove variable at index _PARAM1_ from scene array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -414,7 +414,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("GlobalVariableChildCount",
|
||||
_("Number of children of a global variable"),
|
||||
_("Number of children of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Variable"));
|
||||
|
||||
@@ -422,7 +422,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("VariableChildCount",
|
||||
_("Number of children of a scene variable"),
|
||||
_("Number of children of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -430,7 +430,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("Variable",
|
||||
_("Value of a scene variable"),
|
||||
_("Value of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -438,7 +438,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddStrExpression("VariableString",
|
||||
_("Text of a scene variable"),
|
||||
_("Text of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -446,7 +446,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("GlobalVariable",
|
||||
_("Value of a global variable"),
|
||||
_("Value of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Name of the global variable"));
|
||||
|
||||
@@ -454,7 +454,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddStrExpression("GlobalVariableString",
|
||||
_("Text of a global variable"),
|
||||
_("Text of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Variable"));
|
||||
}
|
||||
|
@@ -200,13 +200,21 @@ class GD_CORE_API ObjectMetadata {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (user friendly) name of the group this object must
|
||||
* be categorised in.
|
||||
*/
|
||||
ObjectMetadata& SetCategoryFullName(const gd::String& categoryFullName_) {
|
||||
categoryFullName = categoryFullName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String& GetName() const { return name; }
|
||||
#if defined(GD_IDE_ONLY)
|
||||
const gd::String& GetFullName() const { return fullname; }
|
||||
const gd::String& GetCategoryFullName() const { return categoryFullName; }
|
||||
const gd::String& GetHelpUrl() const { return helpUrl; }
|
||||
const gd::String& GetDescription() const { return description; }
|
||||
const gd::String& GetIconFilename() const { return iconFilename; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Set the URL pointing to the help page about this object
|
||||
@@ -227,7 +235,6 @@ class GD_CORE_API ObjectMetadata {
|
||||
*/
|
||||
ObjectMetadata& AddIncludeFile(const gd::String& includeFile);
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::map<gd::String, gd::InstructionMetadata> conditionsInfos;
|
||||
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
|
||||
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
|
||||
@@ -235,19 +242,17 @@ class GD_CORE_API ObjectMetadata {
|
||||
|
||||
std::vector<gd::String> includeFiles;
|
||||
gd::String className;
|
||||
#endif
|
||||
CreateFunPtr createFunPtr;
|
||||
|
||||
private:
|
||||
gd::String extensionNamespace;
|
||||
gd::String name;
|
||||
gd::String helpPath;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
gd::String helpUrl; ///< Deprecated. Use helpPath instead.
|
||||
gd::String fullname;
|
||||
gd::String description;
|
||||
gd::String iconFilename;
|
||||
#endif
|
||||
gd::String categoryFullName;
|
||||
std::shared_ptr<gd::Object>
|
||||
blueprintObject; ///< The "blueprint" object to be copied when a new
|
||||
///< object is asked. Can be null in case a creation
|
||||
|
@@ -194,7 +194,8 @@ class GD_CORE_API ParameterMetadata {
|
||||
parameterType == "objectEffectName" ||
|
||||
parameterType == "objectEffectParameterName" ||
|
||||
parameterType == "objectPointName" ||
|
||||
parameterType == "objectAnimationName";
|
||||
parameterType == "objectAnimationName" ||
|
||||
parameterType == "functionParameterName";
|
||||
} else if (type == "variable") {
|
||||
return parameterType == "objectvar" || parameterType == "globalvar" ||
|
||||
parameterType == "scenevar";
|
||||
|
@@ -19,4 +19,34 @@ class GD_CORE_API EventsListUnfolder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FoldAll(gd::EventsList& list) {
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
gd::BaseEvent& event = list[i];
|
||||
event.SetFolded(true);
|
||||
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0) {
|
||||
FoldAll(event.GetSubEvents());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Recursively unfold all the events until a certain level of depth.
|
||||
* 0 is the top level. If you want to unfold all events regardless of its depth,
|
||||
* use `maxLevel = -1`. `currentLevel` is used for the recursion.
|
||||
*/
|
||||
static void UnfoldToLevel(gd::EventsList& list,
|
||||
const int8_t maxLevel,
|
||||
const std::size_t currentLevel = 0) {
|
||||
if (maxLevel >= 0 && currentLevel > maxLevel) return;
|
||||
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
gd::BaseEvent& event = list[i];
|
||||
event.SetFolded(false);
|
||||
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0 &&
|
||||
(maxLevel == -1 || currentLevel <= maxLevel)) {
|
||||
UnfoldToLevel(event.GetSubEvents(), maxLevel, currentLevel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -32,7 +32,7 @@ namespace gd {
|
||||
* The IDE is responsible for actually *searching* and showing the completions -
|
||||
* this is only describing what must be listed.
|
||||
*/
|
||||
struct ExpressionCompletionDescription {
|
||||
struct GD_CORE_API ExpressionCompletionDescription {
|
||||
public:
|
||||
/**
|
||||
* The different kind of completions that can be described.
|
||||
@@ -274,7 +274,7 @@ struct ExpressionCompletionDescription {
|
||||
/**
|
||||
* \brief Turn an ExpressionCompletionDescription to a string.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
GD_CORE_API std::ostream& operator<<(std::ostream& os,
|
||||
ExpressionCompletionDescription const& value);
|
||||
|
||||
/**
|
||||
|
@@ -30,6 +30,16 @@ void ArbitraryResourceWorker::ExposeImage(gd::String& imageName){
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeJson(gd::String& jsonName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
|
@@ -70,6 +70,16 @@ class GD_CORE_API ArbitraryResourceWorker {
|
||||
*/
|
||||
virtual void ExposeFont(gd::String &fontName);
|
||||
|
||||
/**
|
||||
* \brief Expose a JSON, which is always a reference to a "json" resource.
|
||||
*/
|
||||
virtual void ExposeJson(gd::String &jsonName);
|
||||
|
||||
/**
|
||||
* \brief Expose a video, which is always a reference to a "video" resource.
|
||||
*/
|
||||
virtual void ExposeVideo(gd::String &videoName);
|
||||
|
||||
/**
|
||||
* \brief Expose a bitmap font, which is always a reference to a "bitmapFont" resource.
|
||||
*/
|
||||
|
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -36,17 +37,20 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
virtual ~ResourcesInUseHelper(){};
|
||||
|
||||
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
std::set<gd::String>& GetAllJsons() { return GetAll("json"); };
|
||||
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
|
||||
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
|
||||
std::set<gd::String>& GetAll(const gd::String& resourceType) {
|
||||
return resourceType == "image"
|
||||
? allImages
|
||||
: (resourceType == "audio"
|
||||
? allAudios
|
||||
: (resourceType == "font")
|
||||
? allFonts
|
||||
: (resourceType == "bitmapFont") ? allBitmapFonts : emptyResources);
|
||||
if (resourceType == "image") return allImages;
|
||||
if (resourceType == "audio") return allAudios;
|
||||
if (resourceType == "font") return allFonts;
|
||||
if (resourceType == "json") return allJsons;
|
||||
if (resourceType == "video") return allVideos;
|
||||
if (resourceType == "bitmapFont") return allBitmapFonts;
|
||||
|
||||
return emptyResources;
|
||||
};
|
||||
|
||||
virtual void ExposeFile(gd::String& resource) override{
|
||||
@@ -61,6 +65,12 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
allFonts.insert(fontResourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
allJsons.insert(jsonResourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
allVideos.insert(videoResourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
|
||||
allBitmapFonts.insert(bitmapFontResourceName);
|
||||
};
|
||||
@@ -69,6 +79,8 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
std::set<gd::String> allImages;
|
||||
std::set<gd::String> allAudios;
|
||||
std::set<gd::String> allFonts;
|
||||
std::set<gd::String> allJsons;
|
||||
std::set<gd::String> allVideos;
|
||||
std::set<gd::String> allBitmapFonts;
|
||||
std::set<gd::String> emptyResources;
|
||||
};
|
||||
|
@@ -46,6 +46,12 @@ class ResourcesRenamer : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
RenameIfNeeded(fontResourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
RenameIfNeeded(jsonResourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
RenameIfNeeded(videoResourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontName) override {
|
||||
RenameIfNeeded(bitmapFontName);
|
||||
};
|
||||
|
@@ -17,6 +17,7 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("fullName", fullName);
|
||||
element.SetAttribute("description", description);
|
||||
element.SetAttribute("sentence", sentence);
|
||||
element.SetAttribute("group", group);
|
||||
element.SetBoolAttribute("private", isPrivate);
|
||||
events.SerializeTo(element.AddChild("events"));
|
||||
|
||||
@@ -44,6 +45,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
|
||||
fullName = element.GetStringAttribute("fullName");
|
||||
description = element.GetStringAttribute("description");
|
||||
sentence = element.GetStringAttribute("sentence");
|
||||
group = element.GetStringAttribute("group");
|
||||
isPrivate = element.GetBoolAttribute("private");
|
||||
events.UnserializeFrom(project, element.GetChild("events"));
|
||||
|
||||
|
@@ -102,6 +102,19 @@ class GD_CORE_API EventsFunction {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the group of the instruction in the editor.
|
||||
*/
|
||||
const gd::String& GetGroup() const { return group; };
|
||||
|
||||
/**
|
||||
* \brief Set the group of the instruction in the editor.
|
||||
*/
|
||||
EventsFunction& SetGroup(const gd::String& group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
enum FunctionType { Action, Condition, Expression, StringExpression };
|
||||
|
||||
/**
|
||||
@@ -188,6 +201,7 @@ class GD_CORE_API EventsFunction {
|
||||
gd::String fullName;
|
||||
gd::String description;
|
||||
gd::String sentence;
|
||||
gd::String group;
|
||||
gd::EventsList events;
|
||||
FunctionType functionType;
|
||||
std::vector<gd::ParameterMetadata> parameters;
|
||||
|
@@ -3,7 +3,6 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "EventsFunctionsExtension.h"
|
||||
|
||||
#include "EventsBasedBehavior.h"
|
||||
@@ -51,6 +50,11 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("description", description);
|
||||
element.SetAttribute("name", name);
|
||||
element.SetAttribute("fullName", fullName);
|
||||
if (!originName.empty() || !originIdentifier.empty()) {
|
||||
element.AddChild("origin")
|
||||
.SetAttribute("name", originName)
|
||||
.SetAttribute("identifier", originIdentifier);
|
||||
}
|
||||
auto& tagsElement = element.AddChild("tags");
|
||||
tagsElement.ConsiderAsArray();
|
||||
for (const auto& tag : tags) {
|
||||
@@ -88,6 +92,14 @@ void EventsFunctionsExtension::UnserializeFrom(
|
||||
iconUrl = element.GetStringAttribute("iconUrl");
|
||||
helpPath = element.GetStringAttribute("helpPath");
|
||||
|
||||
if (element.HasChild("origin")) {
|
||||
gd::String originName =
|
||||
element.GetChild("origin").GetStringAttribute("name", "");
|
||||
gd::String originIdentifier =
|
||||
element.GetChild("origin").GetStringAttribute("identifier", "");
|
||||
SetOrigin(originName, originIdentifier);
|
||||
}
|
||||
|
||||
tags.clear();
|
||||
auto& tagsElement = element.GetChild("tags");
|
||||
if (!tagsElement.IsValueUndefined()) {
|
||||
@@ -138,5 +150,3 @@ bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction(
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif
|
||||
|
@@ -3,7 +3,6 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
#define GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
|
||||
@@ -140,6 +139,23 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
return eventsBasedBehaviors;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets an extension origin. This method is not present since the
|
||||
* beginning so the projects created before that will have extensions
|
||||
* installed from the store without an origin. Keep that in mind when creating
|
||||
* features that rely on an extension's origin.
|
||||
*/
|
||||
virtual void SetOrigin(const gd::String& originName_,
|
||||
const gd::String& originIdentifier_) {
|
||||
originName = originName_;
|
||||
originIdentifier = originIdentifier_;
|
||||
}
|
||||
|
||||
virtual const gd::String& GetOriginName() const { return originName; }
|
||||
virtual const gd::String& GetOriginIdentifier() const {
|
||||
return originIdentifier;
|
||||
}
|
||||
|
||||
/** \name Dependencies
|
||||
*/
|
||||
///@{
|
||||
@@ -226,6 +242,8 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
std::vector<gd::String> authorIds;
|
||||
gd::String author;
|
||||
gd::String previewIconUrl;
|
||||
gd::String originName;
|
||||
gd::String originIdentifier;
|
||||
gd::String iconUrl;
|
||||
gd::String helpPath; ///< The relative path to the help for this extension in
|
||||
///< the documentation (or an absolute URL).
|
||||
@@ -236,4 +254,3 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
#endif
|
||||
|
@@ -217,7 +217,7 @@ class GD_CORE_API Layout : public ObjectsContainer {
|
||||
/**
|
||||
* Must add a new the layer constructed from the layout passed as parameter.
|
||||
* \note No pointer or reference must be kept on the layer passed as
|
||||
* parameter. \param theLayer The the layer that must be copied and inserted
|
||||
* parameter. \param theLayer the layer that must be copied and inserted
|
||||
* into the project \param position Insertion position. Even if the position
|
||||
* is invalid, the layer must be inserted at the end of the layers list.
|
||||
*/
|
||||
|
@@ -634,6 +634,7 @@ void Project::UnserializeFrom(const SerializerElement& element) {
|
||||
layoutElement.GetStringAttribute("name", "", "nom"), -1);
|
||||
layout.UnserializeFrom(*this, layoutElement);
|
||||
}
|
||||
SetFirstLayout(element.GetChild("firstLayout").GetStringValue());
|
||||
|
||||
externalEvents.clear();
|
||||
const SerializerElement& externalEventsElement =
|
||||
@@ -908,6 +909,7 @@ Project& Project::operator=(const Project& other) {
|
||||
|
||||
void Project::Init(const gd::Project& game) {
|
||||
name = game.name;
|
||||
firstLayout = game.firstLayout;
|
||||
version = game.version;
|
||||
windowWidth = game.windowWidth;
|
||||
windowHeight = game.windowHeight;
|
||||
|
@@ -19,6 +19,7 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
element.AddChild("type").SetStringValue(type);
|
||||
element.AddChild("label").SetStringValue(label);
|
||||
element.AddChild("description").SetStringValue(description);
|
||||
element.AddChild("group").SetStringValue(group);
|
||||
SerializerElement& extraInformationElement =
|
||||
element.AddChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
@@ -33,6 +34,7 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
type = element.GetChild("type").GetStringValue();
|
||||
label = element.GetChild("label").GetStringValue();
|
||||
description = element.GetChild("description").GetStringValue();
|
||||
group = element.GetChild("group").GetStringValue();
|
||||
|
||||
extraInformation.clear();
|
||||
const SerializerElement& extraInformationElement =
|
||||
|
@@ -76,6 +76,14 @@ class GD_CORE_API PropertyDescriptor {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change the group where this property is displayed to the user, if any.
|
||||
*/
|
||||
PropertyDescriptor& SetGroup(gd::String group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set and replace the additional information for the property.
|
||||
*/
|
||||
@@ -100,6 +108,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
const gd::String& GetType() const { return type; }
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
const gd::String& GetDescription() const { return description; }
|
||||
const gd::String& GetGroup() const { return group; }
|
||||
|
||||
const std::vector<gd::String>& GetExtraInfo() const {
|
||||
return extraInformation;
|
||||
@@ -153,6 +162,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
///< the class responsible for updating the property grid.
|
||||
gd::String label; //< The user-friendly property name
|
||||
gd::String description; //< The user-friendly property description
|
||||
gd::String group; //< The user-friendly property group
|
||||
std::vector<gd::String>
|
||||
extraInformation; ///< Can be used to store for example the available
|
||||
///< choices, if a property is a displayed as a combo
|
||||
|
@@ -24,12 +24,9 @@ gd::String Resource::badStr;
|
||||
|
||||
Resource ResourcesManager::badResource;
|
||||
gd::String ResourcesManager::badResourceName;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
ResourceFolder ResourcesManager::badFolder;
|
||||
Resource ResourceFolder::badResource;
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourceFolder::Init(const ResourceFolder& other) {
|
||||
name = other.name;
|
||||
|
||||
@@ -38,19 +35,16 @@ void ResourceFolder::Init(const ResourceFolder& other) {
|
||||
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ResourcesManager::Init(const ResourcesManager& other) {
|
||||
resources.clear();
|
||||
for (std::size_t i = 0; i < other.resources.size(); ++i) {
|
||||
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
|
||||
}
|
||||
#if defined(GD_IDE_ONLY)
|
||||
folders.clear();
|
||||
for (std::size_t i = 0; i < other.folders.size(); ++i) {
|
||||
folders.push_back(other.folders[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Resource& ResourcesManager::GetResource(const gd::String& name) {
|
||||
@@ -147,7 +141,6 @@ std::vector<gd::String> ResourcesManager::FindFilesNotInResources(
|
||||
return filesNotInResources;
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::map<gd::String, gd::PropertyDescriptor> Resource::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> nothing;
|
||||
return nothing;
|
||||
@@ -443,9 +436,7 @@ void ResourcesManager::RemoveResource(const gd::String& name) {
|
||||
for (std::size_t i = 0; i < folders.size(); ++i)
|
||||
folders[i].RemoveResource(name);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourceFolder::UnserializeFrom(const SerializerElement& element,
|
||||
gd::ResourcesManager& parentManager) {
|
||||
name = element.GetStringAttribute("name");
|
||||
@@ -470,7 +461,6 @@ void ResourceFolder::SerializeTo(SerializerElement& element) const {
|
||||
.SetAttribute("name", resources[i]->GetName());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
resources.clear();
|
||||
@@ -500,7 +490,6 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
resources.push_back(resource);
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
folders.clear();
|
||||
const SerializerElement& resourcesFoldersElement =
|
||||
element.GetChild("resourceFolders", 0, "ResourceFolders");
|
||||
@@ -511,10 +500,8 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
|
||||
folders.push_back(folder);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourcesManager::SerializeTo(SerializerElement& element) const {
|
||||
SerializerElement& resourcesElement = element.AddChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
@@ -543,7 +530,6 @@ void ResourcesManager::SerializeTo(SerializerElement& element) const {
|
||||
for (std::size_t i = 0; i < folders.size(); ++i)
|
||||
folders[i].SerializeTo(resourcesFoldersElement.AddChild("folder"));
|
||||
}
|
||||
#endif
|
||||
|
||||
void ImageResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -560,14 +546,12 @@ void ImageResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ImageResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("alwaysLoaded", alwaysLoaded);
|
||||
element.SetAttribute("smoothed", smooth);
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void AudioResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -584,14 +568,12 @@ void AudioResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetPreloadAsSound(element.GetBoolAttribute("preloadAsSound"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void AudioResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
element.SetAttribute("preloadAsMusic", PreloadAsMusic());
|
||||
element.SetAttribute("preloadAsSound", PreloadAsSound());
|
||||
}
|
||||
#endif
|
||||
|
||||
void FontResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -606,12 +588,10 @@ void FontResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void FontResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void VideoResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -626,12 +606,10 @@ void VideoResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void VideoResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void JsonResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -647,7 +625,6 @@ void JsonResource::UnserializeFrom(const SerializerElement& element) {
|
||||
DisablePreload(element.GetBoolAttribute("disablePreload", false));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void JsonResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
@@ -672,7 +649,6 @@ bool JsonResource::UpdateProperty(const gd::String& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void BitmapFontResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -687,14 +663,11 @@ void BitmapFontResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void BitmapFontResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
|
||||
|
||||
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
@@ -702,7 +675,6 @@ ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
ResourcesManager::ResourcesManager(const ResourcesManager& other) {
|
||||
Init(other);
|
||||
|
@@ -104,7 +104,6 @@ class GD_CORE_API Resource {
|
||||
*/
|
||||
virtual const gd::String& GetMetadata() const { return metadata; }
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/** \name Resources properties
|
||||
* Reading and updating resources properties
|
||||
*/
|
||||
@@ -136,7 +135,6 @@ class GD_CORE_API Resource {
|
||||
return false;
|
||||
};
|
||||
///@}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Serialize the object
|
||||
@@ -186,7 +184,6 @@ class GD_CORE_API ImageResource : public Resource {
|
||||
*/
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
@@ -196,7 +193,6 @@ class GD_CORE_API ImageResource : public Resource {
|
||||
* \brief Serialize the object
|
||||
*/
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the objectt.
|
||||
@@ -238,14 +234,12 @@ class GD_CORE_API AudioResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
|
||||
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -292,10 +286,8 @@ class GD_CORE_API FontResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -320,10 +312,8 @@ class GD_CORE_API VideoResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -348,14 +338,12 @@ class GD_CORE_API JsonResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
|
||||
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -391,10 +379,8 @@ class GD_CORE_API BitmapFontResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -463,7 +449,6 @@ class GD_CORE_API ResourcesManager {
|
||||
*/
|
||||
std::vector<gd::String> FindFilesNotInResources(const std::vector<gd::String>& filesToCheck) const;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/**
|
||||
* \brief Return a (smart) pointer to a resource.
|
||||
*/
|
||||
@@ -557,7 +542,6 @@ class GD_CORE_API ResourcesManager {
|
||||
* \brief Serialize the object
|
||||
*/
|
||||
void SerializeTo(SerializerElement& element) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the objectt.
|
||||
@@ -568,18 +552,13 @@ class GD_CORE_API ResourcesManager {
|
||||
void Init(const ResourcesManager& other);
|
||||
|
||||
std::vector<std::shared_ptr<Resource> > resources;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::vector<ResourceFolder> folders;
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
static ResourceFolder badFolder;
|
||||
#endif
|
||||
static Resource badResource;
|
||||
static gd::String badResourceName;
|
||||
};
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
class GD_CORE_API ResourceFolder {
|
||||
public:
|
||||
ResourceFolder(){};
|
||||
@@ -654,7 +633,6 @@ class GD_CORE_API ResourceFolder {
|
||||
void Init(const ResourceFolder& other);
|
||||
static Resource badResource;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace gd
|
||||
|
||||
|
@@ -667,7 +667,7 @@ int String::compare( const String &other ) const
|
||||
namespace priv
|
||||
{
|
||||
/**
|
||||
* As the the casefolded version of a string can have a different size, the positions
|
||||
* As the casefolded version of a string can have a different size, the positions
|
||||
* in the two versions of the string are not the same.
|
||||
* \return where the **pos** position in the original string **str** is in the
|
||||
* casefolded version of **str**
|
||||
@@ -681,7 +681,7 @@ namespace priv
|
||||
}
|
||||
|
||||
/**
|
||||
* As the the casefolded version of a string can have a different size, the positions
|
||||
* As the casefolded version of a string can have a different size, the positions
|
||||
* in the two versions of the string are not the same.
|
||||
* \return where the **pos** position in the casefolded string of **str** is in the
|
||||
* original version **str**
|
||||
|
@@ -23,7 +23,7 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"AnchorBehavior",
|
||||
_("Anchor"),
|
||||
"Anchor",
|
||||
_("Behavior that anchors objects to the window's bounds."),
|
||||
_("Anchor objects to the window's bounds."),
|
||||
"",
|
||||
"CppPlatform/Extensions/AnchorIcon.png",
|
||||
"AnchorBehavior",
|
||||
|
@@ -67,19 +67,22 @@ module.exports = {
|
||||
.getOrCreate('color')
|
||||
.setValue(objectContent.color)
|
||||
.setType('color')
|
||||
.setLabel(_('Base color'));
|
||||
.setLabel(_('Base color'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('opacity')
|
||||
.setValue(objectContent.opacity.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Opacity (0-255)'));
|
||||
.setLabel(_('Opacity (0-255)'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fontSize')
|
||||
.setValue(objectContent.fontSize.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Base size'));
|
||||
.setLabel(_('Base size'))
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('align')
|
||||
@@ -88,33 +91,36 @@ module.exports = {
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.setLabel(_('Base alignment'));
|
||||
.setLabel(_('Base alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fontFamily')
|
||||
.setValue(objectContent.fontFamily)
|
||||
.setType('resource')
|
||||
.addExtraInfo('font')
|
||||
.setLabel(_('Base font family'));
|
||||
.setLabel(_('Font'))
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('wordWrap')
|
||||
.setValue(objectContent.wordWrap ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Word wrapping'));
|
||||
.setLabel(_('Word wrapping'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('visible')
|
||||
.setValue(objectContent.visible ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Visible on start'));
|
||||
.setLabel(_('Visible on start'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
objectBBText.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text:
|
||||
'[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
text: '[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
opacity: 255,
|
||||
fontSize: 20,
|
||||
visible: true,
|
||||
@@ -161,7 +167,8 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/BBText/bbtextruntimeobject-pixi-renderer.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/BBText/pixi-multistyle-text/dist/pixi-multistyle-text.umd.js'
|
||||
);
|
||||
)
|
||||
.setCategoryFullName(_('Texts'));
|
||||
|
||||
/**
|
||||
* Utility function to add both a setter and a getter to a property from a list.
|
||||
@@ -511,7 +518,8 @@ module.exports = {
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
const color = properties.get('color').getValue();
|
||||
this._pixiObject.textStyles.default.fill = objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
this._pixiObject.textStyles.default.fill =
|
||||
objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
|
||||
const fontSize = properties.get('fontSize').getValue();
|
||||
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
|
||||
|
@@ -29,7 +29,9 @@ module.exports = {
|
||||
.setExtensionInformation(
|
||||
'BitmapText',
|
||||
_('Bitmap Text'),
|
||||
_('Displays a text using a "Bitmap Font" (an image representing characters). This is more performant than a traditional Text object and it allows for complete control on the characters aesthetic.'),
|
||||
_(
|
||||
'Displays a text using a "Bitmap Font" (an image representing characters). This is more performant than a traditional Text object and it allows for complete control on the characters aesthetic.'
|
||||
),
|
||||
'Aurélien Vivet',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
@@ -67,7 +69,8 @@ module.exports = {
|
||||
.getOrCreate('opacity')
|
||||
.setValue(objectContent.opacity.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Opacity (0-255)'));
|
||||
.setLabel(_('Opacity (0-255)'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('align')
|
||||
@@ -76,7 +79,8 @@ module.exports = {
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.setLabel(_('Alignment, when multiple lines are displayed'));
|
||||
.setLabel(_('Alignment, when multiple lines are displayed'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('bitmapFontResourceName')
|
||||
@@ -96,26 +100,28 @@ module.exports = {
|
||||
.getOrCreate('scale')
|
||||
.setValue(objectContent.scale.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Text scale'));
|
||||
.setLabel(_('Text scale'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('tint')
|
||||
.setValue(objectContent.tint)
|
||||
.setType('color')
|
||||
.setLabel(_('Font tint'));
|
||||
.setLabel(_('Font tint'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('wordWrap')
|
||||
.setValue(objectContent.wordWrap ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Word wrapping'));
|
||||
.setLabel(_('Word wrapping'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
bitmapTextObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text:
|
||||
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
text: 'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
opacity: 255,
|
||||
scale: 1,
|
||||
fontSize: 20,
|
||||
@@ -162,7 +168,8 @@ module.exports = {
|
||||
.setIncludeFile('Extensions/BitmapText/bitmaptextruntimeobject.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
|
||||
);
|
||||
)
|
||||
.setCategoryFullName(_('Texts'));
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
@@ -265,7 +272,12 @@ module.exports = {
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter('bitmapFontResource', _('Bitmap font resource name'), '', false)
|
||||
.addParameter(
|
||||
'bitmapFontResource',
|
||||
_('Bitmap font resource name'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setParameterLongDescription(
|
||||
'The resource name of the font file, without quotes.'
|
||||
)
|
||||
@@ -634,7 +646,8 @@ module.exports = {
|
||||
this._pixiObject.align = align;
|
||||
|
||||
const color = properties.get('tint').getValue();
|
||||
this._pixiObject.tint = objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
this._pixiObject.tint =
|
||||
objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
|
||||
const scale = properties.get('scale').getValue() || 1;
|
||||
this._pixiObject.scale.set(scale);
|
||||
|
@@ -26,7 +26,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
extension.AddBehavior("DestroyOutside",
|
||||
_("Destroy when outside of the screen"),
|
||||
_("DestroyOutside"),
|
||||
_("Automatically destroy the object when it goes "
|
||||
_("Destroy objects automatically when they go "
|
||||
"outside of the screen's borders."),
|
||||
"",
|
||||
"CppPlatform/Extensions/destroyoutsideicon.png",
|
||||
|
@@ -541,7 +541,7 @@ module.exports = {
|
||||
'IsDialogueLineType',
|
||||
_('Dialogue line type'),
|
||||
_(
|
||||
'Check if the the current dialogue line line is one of the three existing types. Use this to set what logic is executed for each type.\nThe three types are as follows:\n- text: when displaying dialogue text.\n- options: when displaying [[branching/options]] for dialogue choices.\n-command: when <<commands>> are triggered by the dialogue data.'
|
||||
'Check if the current dialogue line line is one of the three existing types. Use this to set what logic is executed for each type.\nThe three types are as follows:\n- text: when displaying dialogue text.\n- options: when displaying [[branching/options]] for dialogue choices.\n-command: when <<commands>> are triggered by the dialogue data.'
|
||||
),
|
||||
_('The dialogue line is _PARAM0_'),
|
||||
_('Dialogue Tree (experimental)'),
|
||||
|
@@ -26,7 +26,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"Draggable",
|
||||
_("Draggable object"),
|
||||
_("Draggable"),
|
||||
_("Allows objects to be moved using the mouse (or touch)."),
|
||||
_("Move objects by holding a mouse button (or touch)."),
|
||||
"",
|
||||
"CppPlatform/Extensions/draggableicon.png",
|
||||
"DraggableBehavior",
|
||||
|
@@ -179,7 +179,8 @@ module.exports = {
|
||||
.setValue(
|
||||
behaviorContent.getBoolAttribute('property2') ? 'true' : 'false'
|
||||
)
|
||||
.setType('Boolean');
|
||||
.setType('Boolean')
|
||||
.setGroup(_('Look and Feel'));
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
@@ -193,7 +194,7 @@ module.exports = {
|
||||
'DummyBehavior',
|
||||
_('Dummy behavior for testing'),
|
||||
'DummyBehavior',
|
||||
_('This dummy behavior does nothing'),
|
||||
_('Do nothing.'),
|
||||
'',
|
||||
'CppPlatform/Extensions/topdownmovementicon.png',
|
||||
'DummyBehavior',
|
||||
@@ -276,7 +277,7 @@ module.exports = {
|
||||
'DummyBehaviorWithSharedData',
|
||||
_('Dummy behavior with shared data for testing'),
|
||||
'DummyBehaviorWithSharedData',
|
||||
_('This dummy behavior uses shared data and does nothing'),
|
||||
_('Do nothing but use shared data.'),
|
||||
'',
|
||||
'CppPlatform/Extensions/topdownmovementicon.png',
|
||||
'DummyBehaviorWithSharedData',
|
||||
@@ -409,7 +410,8 @@ module.exports = {
|
||||
.setIncludeFile('Extensions/ExampleJsExtension/dummyruntimeobject.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/ExampleJsExtension/dummyruntimeobject-pixi-renderer.js'
|
||||
);
|
||||
)
|
||||
.setCategoryFullName(_('Testing'));
|
||||
|
||||
object
|
||||
.addAction(
|
||||
|
@@ -78,7 +78,7 @@ module.exports = {
|
||||
'SaveStringToFileSync',
|
||||
_('Save a text into a file'),
|
||||
_(
|
||||
'Save a text into a file. Only use this on small files to avoid any lag or freeze during the the game execution.'
|
||||
'Save a text into a file. Only use this on small files to avoid any lag or freeze during the game execution.'
|
||||
),
|
||||
_('Save _PARAM0_ into file _PARAM1_'),
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
@@ -130,7 +130,7 @@ module.exports = {
|
||||
'SaveVariableToJSONFileSync',
|
||||
_('Save a scene variable into a JSON file'),
|
||||
_(
|
||||
'Save a scene variable (including, for structure, all the children) into a file in JSON format. Only use this on small files to avoid any lag or freeze during the the game execution.'
|
||||
'Save a scene variable (including, for structure, all the children) into a file in JSON format. Only use this on small files to avoid any lag or freeze during the game execution.'
|
||||
),
|
||||
_('Save scene variable _PARAM0_ into file _PARAM1_ as JSON'),
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
@@ -208,7 +208,7 @@ module.exports = {
|
||||
'LoadStringFromFileSync',
|
||||
_('Load a text from a file'),
|
||||
_(
|
||||
'Load a text from a file. Only use this on small files to avoid any lag or freeze during the the game execution.'
|
||||
'Load a text from a file. Only use this on small files to avoid any lag or freeze during the game execution.'
|
||||
),
|
||||
_('Load text from _PARAM1_ into scene variable _PARAM0_'),
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
@@ -234,7 +234,7 @@ module.exports = {
|
||||
'LoadVariableFromJSONFileSync',
|
||||
_('Load a scene variable from a JSON file'),
|
||||
_(
|
||||
'Load a JSON formatted text from a file and convert it to a scene variable (potentially a structure variable with children). Only use this on small files to avoid any lag or freeze during the the game execution.'
|
||||
'Load a JSON formatted text from a file and convert it to a scene variable (potentially a structure variable with children). Only use this on small files to avoid any lag or freeze during the game execution.'
|
||||
),
|
||||
_('Load JSON from _PARAM1_ into scene variable _PARAM0_'),
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
@@ -415,6 +415,7 @@ module.exports = {
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
'JsPlatform/Extensions/filesystem_folder32.png'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.getUserHomePath');
|
||||
|
@@ -150,7 +150,9 @@ namespace gdjs {
|
||||
* Get the path to the user's home folder (on Windows `C:\Users\<USERNAME>\` for example).
|
||||
* @return The path to user's "home" folder
|
||||
*/
|
||||
export const getUserHomePath = function (runtimeScene): string {
|
||||
export const getUserHomePath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
): string {
|
||||
const electron = runtimeScene.getGame().getRenderer().getElectron();
|
||||
if (electron) {
|
||||
return electron.remote.app.getPath('home') || '';
|
||||
|
@@ -59,7 +59,7 @@ module.exports = {
|
||||
_('Light Obstacle Behavior'),
|
||||
'LightObstacleBehavior',
|
||||
_(
|
||||
'This behavior makes the object an obstacle to the light. The light emitted by light objects will be stopped by the object.'
|
||||
'Flag objects as being obstacles to light. The light emitted by light objects will be stopped by the object.'
|
||||
),
|
||||
'',
|
||||
'CppPlatform/Extensions/lightObstacleIcon32.png',
|
||||
@@ -132,6 +132,7 @@ module.exports = {
|
||||
'When activated, display the lines used to render the light - useful to understand how the light is rendered on screen.'
|
||||
)
|
||||
)
|
||||
.setGroup(_('Advanced'))
|
||||
);
|
||||
|
||||
objectProperties
|
||||
@@ -193,7 +194,8 @@ module.exports = {
|
||||
)
|
||||
.setIncludeFile('Extensions/Lighting/lightruntimeobject.js')
|
||||
.addIncludeFile('Extensions/Lighting/lightruntimeobject-pixi-renderer.js')
|
||||
.addIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js');
|
||||
.addIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js')
|
||||
.setCategoryFullName(_('Lights'));
|
||||
|
||||
object
|
||||
.addAction(
|
||||
|
@@ -87,12 +87,16 @@ namespace gdjs {
|
||||
_manager: any;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
this._manager = LightObstaclesManager.getManager(runtimeScene);
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
// Make sure the obstacle is or is not in the obstacles manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removeObstacle(this);
|
||||
|
@@ -8,19 +8,24 @@ namespace gdjs {
|
||||
export class LightRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.LightRuntimeObject;
|
||||
_runtimeScene: gdjs.RuntimeScene;
|
||||
_manager: any;
|
||||
_manager: gdjs.LightObstaclesManager;
|
||||
_radius: number;
|
||||
_color: any;
|
||||
_color: [number, number, number];
|
||||
_texture: PIXI.Texture | null = null;
|
||||
_center: any;
|
||||
_defaultVertexBuffer: any;
|
||||
_vertexBuffer: any;
|
||||
_indexBuffer: any;
|
||||
_center: Float32Array;
|
||||
_defaultVertexBuffer: Float32Array;
|
||||
_vertexBuffer: Float32Array;
|
||||
_indexBuffer: Uint16Array;
|
||||
_light: PIXI.Mesh<PIXI.Shader> | null = null;
|
||||
_isPreview: boolean;
|
||||
_debugMode: any = null;
|
||||
_debugMode: boolean = false;
|
||||
_debugLight: PIXI.Container | null = null;
|
||||
_debugGraphics: PIXI.Graphics | null = null;
|
||||
|
||||
/**
|
||||
* A polygon updated when vertices of the light are computed
|
||||
* to be a polygon bounding the light and its obstacles.
|
||||
*/
|
||||
_lightBoundingPoly: gdjs.Polygon;
|
||||
|
||||
constructor(
|
||||
@@ -53,13 +58,8 @@ namespace gdjs {
|
||||
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
||||
this.updateMesh();
|
||||
this._isPreview = runtimeScene.getGame().isPreview();
|
||||
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
|
||||
|
||||
this._lightBoundingPoly = new gdjs.Polygon();
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._lightBoundingPoly.vertices.push(
|
||||
runtimeObject.getHitBoxes()[0].vertices[i]
|
||||
);
|
||||
}
|
||||
this.updateDebugMode();
|
||||
|
||||
// Objects will be added in lighting layer, this is just to maintain consistency.
|
||||
@@ -85,10 +85,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
static _computeClosestIntersectionPoint(
|
||||
lightObject,
|
||||
angle,
|
||||
polygons,
|
||||
boundingSquareHalfDiag
|
||||
lightObject: gdjs.LightRuntimeObject,
|
||||
angle: float,
|
||||
polygons: Array<gdjs.Polygon>,
|
||||
boundingSquareHalfDiag: float
|
||||
) {
|
||||
const centerX = lightObject.getX();
|
||||
const centerY = lightObject.getY();
|
||||
@@ -308,8 +308,8 @@ namespace gdjs {
|
||||
// and instead use a subarray. Otherwise, allocate new array buffers as
|
||||
// there would be memory wastage.
|
||||
let isSubArrayUsed = false;
|
||||
let vertexBufferSubArray = null;
|
||||
let indexBufferSubArray = null;
|
||||
let vertexBufferSubArray: Float32Array | null = null;
|
||||
let indexBufferSubArray: Uint16Array | null = null;
|
||||
if (this._vertexBuffer.length > 2 * verticesCount + 2) {
|
||||
if (this._vertexBuffer.length < 4 * verticesCount + 4) {
|
||||
isSubArrayUsed = true;
|
||||
@@ -368,7 +368,7 @@ namespace gdjs {
|
||||
* Computes the vertices of mesh using raycasting.
|
||||
* @returns the vertices of mesh.
|
||||
*/
|
||||
_computeLightVertices(): Array<any> {
|
||||
_computeLightVertices(): Array<FloatPoint> {
|
||||
const lightObstacles: gdjs.BehaviorRBushAABB<
|
||||
LightObstacleRuntimeBehavior
|
||||
>[] = [];
|
||||
@@ -379,38 +379,47 @@ namespace gdjs {
|
||||
lightObstacles
|
||||
);
|
||||
}
|
||||
const searchAreaLeft = this._object.getX() - this._radius;
|
||||
const searchAreaTop = this._object.getY() - this._radius;
|
||||
const searchAreaRight = this._object.getX() + this._radius;
|
||||
const searchAreaBottom = this._object.getY() + this._radius;
|
||||
|
||||
// Bail out early if there are no obstacles.
|
||||
if (lightObstacles.length === 0) {
|
||||
// @ts-ignore TODO the array should probably be pass as a parameter.
|
||||
return lightObstacles;
|
||||
}
|
||||
|
||||
// Synchronize light bounding polygon with the hitbox.
|
||||
const lightHitboxPoly = this._object.getHitBoxes()[0];
|
||||
// Note: we suppose the hitbox is always a single rectangle.
|
||||
const objectHitBox = this._object.getHitBoxes()[0];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 2; j++) {
|
||||
this._lightBoundingPoly.vertices[i][j] =
|
||||
lightHitboxPoly.vertices[i][j];
|
||||
}
|
||||
this._lightBoundingPoly.vertices[i][0] = objectHitBox.vertices[i][0];
|
||||
this._lightBoundingPoly.vertices[i][1] = objectHitBox.vertices[i][1];
|
||||
}
|
||||
const obstaclesCount = lightObstacles.length;
|
||||
const obstacleHitBoxes = new Array(obstaclesCount);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
obstacleHitBoxes[i] = lightObstacles[i].behavior.owner.getHitBoxes();
|
||||
}
|
||||
const obstaclePolygons: Array<any> = [];
|
||||
|
||||
// Create the list of polygons to compute the light vertices
|
||||
const obstaclePolygons: Array<gdjs.Polygon> = [];
|
||||
obstaclePolygons.push(this._lightBoundingPoly);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
const noOfHitBoxes = obstacleHitBoxes[i].length;
|
||||
for (let j = 0; j < noOfHitBoxes; j++) {
|
||||
obstaclePolygons.push(obstacleHitBoxes[i][j]);
|
||||
for (let i = 0; i < lightObstacles.length; i++) {
|
||||
const obstacleHitBoxes = lightObstacles[
|
||||
i
|
||||
].behavior.owner.getHitBoxesAround(
|
||||
searchAreaLeft,
|
||||
searchAreaTop,
|
||||
searchAreaRight,
|
||||
searchAreaBottom
|
||||
);
|
||||
for (const hitbox of obstacleHitBoxes) {
|
||||
obstaclePolygons.push(hitbox);
|
||||
}
|
||||
}
|
||||
|
||||
let maxX = this._object.x + this._radius;
|
||||
let minX = this._object.x - this._radius;
|
||||
let maxY = this._object.y + this._radius;
|
||||
let minY = this._object.y - this._radius;
|
||||
const flattenVertices: Array<any> = [];
|
||||
const flattenVertices: Array<FloatPoint> = [];
|
||||
for (let i = 1; i < obstaclePolygons.length; i++) {
|
||||
const vertices = obstaclePolygons[i].vertices;
|
||||
const verticesCount = vertices.length;
|
||||
@@ -452,6 +461,7 @@ namespace gdjs {
|
||||
(maxY - this._object.y) * (maxY - this._object.y)
|
||||
)
|
||||
);
|
||||
// Add this._object.hitBoxes vertices.
|
||||
for (let i = 0; i < 4; i++) {
|
||||
flattenVertices.push(obstaclePolygons[0].vertices[i]);
|
||||
}
|
||||
@@ -546,9 +556,11 @@ namespace gdjs {
|
||||
varying vec2 vPos;
|
||||
|
||||
void main() {
|
||||
vec2 topleft = vec2(center.x - radius, center.y - radius);
|
||||
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
|
||||
gl_FragColor = vec4(color, 1.0) * texture2D(uSampler, texCoord);
|
||||
vec2 topleft = vec2(center.x - radius, center.y - radius);
|
||||
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
|
||||
gl_FragColor = (texCoord.x > 0.0 && texCoord.x < 1.0 && texCoord.y > 0.0 && texCoord.y < 1.0)
|
||||
? vec4(color, 1.0) * texture2D(uSampler, texCoord)
|
||||
: vec4(0.0, 0.0, 0.0, 0.0);
|
||||
}`;
|
||||
}
|
||||
|
||||
|
@@ -149,10 +149,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the light obstacles manager if objects with the behavior exist, null otherwise.
|
||||
* @returns gdjs.LightObstaclesManager if it exists, otherwise null.
|
||||
* Get the light obstacles manager.
|
||||
* @returns the light obstacles manager.
|
||||
*/
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager | null {
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager {
|
||||
return this._obstaclesManager;
|
||||
}
|
||||
|
||||
|
1
Extensions/P2P/A_peer.js
vendored
1
Extensions/P2P/A_peer.js
vendored
@@ -69,3 +69,4 @@ var t=require("./bufferbuilder").BufferBuilder,e=require("./bufferbuilder").bina
|
||||
},{"eventemitter3":"JJlS","./util":"BHXf","./logger":"WOs9","./socket":"wJlv","./mediaconnection":"dbHP","./dataconnection":"GBTQ","./enums":"ZRYf","./api":"in7L"}],"iTK6":[function(require,module,exports) {
|
||||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./util"),r=require("./peer");exports.peerjs={Peer:r.Peer,util:e.util},exports.default=r.Peer,window.peerjs=exports.peerjs,window.Peer=r.Peer;
|
||||
},{"./util":"BHXf","./peer":"Hxpd"}]},{},["iTK6"], null)
|
||||
//# sourceMappingURL=A_peer.js.map
|
1
Extensions/P2P/A_peer.js.map
Normal file
1
Extensions/P2P/A_peer.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -375,15 +375,14 @@ namespace gdjs {
|
||||
key: string,
|
||||
ssl: boolean
|
||||
) => {
|
||||
peerConfig = {
|
||||
debug: 1,
|
||||
Object.assign(peerConfig, {
|
||||
host,
|
||||
port,
|
||||
path,
|
||||
secure: ssl,
|
||||
// All servers have "peerjs" as default key
|
||||
key: key.length === 0 ? 'peerjs' : key,
|
||||
};
|
||||
});
|
||||
loadPeerJS();
|
||||
};
|
||||
|
||||
@@ -394,6 +393,26 @@ namespace gdjs {
|
||||
*/
|
||||
export const useDefaultBrokerServer = loadPeerJS;
|
||||
|
||||
/**
|
||||
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
|
||||
* @param urls The URL of the STUN/TURN server.
|
||||
* @param username An optional username to send to the server.
|
||||
* @param credential An optional password to send to the server.
|
||||
*/
|
||||
export const useCustomICECandidate = (
|
||||
urls: string,
|
||||
username?: string,
|
||||
credential?: string
|
||||
) => {
|
||||
peerConfig.config = peerConfig.config || {};
|
||||
peerConfig.config.iceServers = peerConfig.config.iceServers || [];
|
||||
peerConfig.config.iceServers.push({
|
||||
urls,
|
||||
username,
|
||||
credential,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides the default peer ID. Must be called before connecting to a broker.
|
||||
* Overriding the ID may have unwanted consequences. Do not use this feature
|
||||
|
@@ -154,6 +154,30 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.useCustomBrokerServer');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'UseOwnICEServer',
|
||||
_('Use a custom ICE server'),
|
||||
_(
|
||||
'Disables the default ICE (STUN or TURN) servers list and use one of your own. ' +
|
||||
'Note that it is recommended to add at least 1 self-hosted STUN and TURN server ' +
|
||||
'for games that are not over LAN but over the internet. ' +
|
||||
'This action can be used multiple times to add multiple servers. ' +
|
||||
'This action needs to be called BEFORE connecting to the broker server.'
|
||||
),
|
||||
_('Use ICE server _PARAM0_ (username: _PARAM1_, password: _PARAM2_)'),
|
||||
_('P2P (experimental)'),
|
||||
'JsPlatform/Extensions/p2picon.svg',
|
||||
'JsPlatform/Extensions/p2picon.svg'
|
||||
)
|
||||
.addParameter('string', _('URL to the ICE server'), '', false)
|
||||
.addParameter('string', _('(Optional) Username'), '', true)
|
||||
.addParameter('string', _('(Optional) Password'), '', true)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.useCustomICECandidate');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'UseDefaultBroker',
|
||||
|
@@ -25,12 +25,16 @@ void DeclarePanelSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/panel_sprite");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<PanelSpriteObject>(
|
||||
"PanelSprite",
|
||||
_("Panel Sprite (\"9-patch\")"),
|
||||
_("An image with edges and corners that are stretched separately from "
|
||||
"the full image."),
|
||||
"CppPlatform/Extensions/PanelSpriteIcon.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<PanelSpriteObject>(
|
||||
"PanelSprite",
|
||||
_("Panel Sprite (\"9-patch\")"),
|
||||
_("An image with edges and corners that are stretched separately "
|
||||
"from "
|
||||
"the full image."),
|
||||
"CppPlatform/Extensions/PanelSpriteIcon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("PanelSpriteObject/PanelSpriteObject.h");
|
||||
|
@@ -11,8 +11,8 @@ This project is released under the MIT License.
|
||||
#include "ExtensionSubDeclaration2.h"
|
||||
#include "ExtensionSubDeclaration3.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Project/BehaviorsSharedData.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "ParticleEmitterObject.h"
|
||||
|
||||
void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
@@ -29,12 +29,15 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
// Declaration of all objects available
|
||||
{
|
||||
gd::ObjectMetadata& obj = extension.AddObject<ParticleEmitterObject>(
|
||||
"ParticleEmitter",
|
||||
_("Particles emitter"),
|
||||
_("Displays a large number of small particles to create visual "
|
||||
"effects."),
|
||||
"CppPlatform/Extensions/particleSystemicon.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<ParticleEmitterObject>(
|
||||
"ParticleEmitter",
|
||||
_("Particles emitter"),
|
||||
_("Displays a large number of small particles to create visual "
|
||||
"effects."),
|
||||
"CppPlatform/Extensions/particleSystemicon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
|
||||
obj.SetIncludeFile("ParticleSystem/ParticleEmitterObject.h");
|
||||
|
||||
@@ -44,4 +47,4 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
ExtensionSubDeclaration2(obj);
|
||||
ExtensionSubDeclaration3(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -168,7 +168,7 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
obj.AddAction("ParticleSize1",
|
||||
_("SIze, parameter 1"),
|
||||
_("Size, parameter 1"),
|
||||
_("Modify parameter 1 of the size of particles"),
|
||||
_("the parameter 1 of size"),
|
||||
_("Common"),
|
||||
|
@@ -28,7 +28,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
extension.AddBehavior("PathfindingBehavior",
|
||||
_("Pathfinding"),
|
||||
"Pathfinding",
|
||||
_("With this behavior, the object will move "
|
||||
_("Move objects to a target "
|
||||
"while avoiding all objects that are "
|
||||
"flagged as obstacles."),
|
||||
"",
|
||||
@@ -610,7 +610,7 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"PathfindingObstacleBehavior",
|
||||
_("Obstacle for pathfinding"),
|
||||
"PathfindingObstacle",
|
||||
_("Flag the object as being an obstacle for pathfinding."),
|
||||
_("Flag objects as being obstacles for pathfinding."),
|
||||
"",
|
||||
"CppPlatform/Extensions/pathfindingobstacleicon.png",
|
||||
"PathfindingObstacleBehavior",
|
||||
|
@@ -39,23 +39,23 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
|
||||
properties[_("Max. speed")].SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties[_("Rotate speed")].SetValue(
|
||||
properties[_("Rotate speed")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angularMaxSpeed")));
|
||||
properties[_("Rotate object")]
|
||||
properties[_("Rotate object")].SetGroup(_("Rotation"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("rotateObject") ? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Angle offset")].SetValue(
|
||||
properties[_("Angle offset")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
|
||||
properties[_("Virtual cell width")].SetValue(
|
||||
properties[_("Virtual cell width")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("cellWidth", 0)));
|
||||
properties[_("Virtual cell height")].SetValue(
|
||||
properties[_("Virtual cell height")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("cellHeight", 0)));
|
||||
properties[_("Virtual grid X offset")].SetValue(
|
||||
properties[_("Virtual grid X offset")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetX", 0)));
|
||||
properties[_("Virtual grid Y offset")].SetValue(
|
||||
properties[_("Virtual grid Y offset")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetY", 0)));
|
||||
properties[_("Extra border size")].SetValue(
|
||||
properties[_("Extra border size")].SetGroup(_("Collision")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("extraBorder")));
|
||||
|
||||
return properties;
|
||||
|
@@ -28,8 +28,8 @@ namespace gdjs {
|
||||
_pathFound: boolean = false;
|
||||
_speed: float = 0;
|
||||
_angularSpeed: float = 0;
|
||||
_timeOnSegment: float = 0;
|
||||
_totalSegmentTime: float = 0;
|
||||
_distanceOnSegment: float = 0;
|
||||
_totalSegmentDistance: float = 0;
|
||||
_currentSegment: integer = 0;
|
||||
_reachedEnd: boolean = false;
|
||||
_manager: PathfindingObstaclesManager;
|
||||
@@ -392,11 +392,11 @@ namespace gdjs {
|
||||
const pathY =
|
||||
this._path[this._currentSegment + 1][1] -
|
||||
this._path[this._currentSegment][1];
|
||||
this._totalSegmentTime = Math.sqrt(pathX * pathX + pathY * pathY);
|
||||
this._timeOnSegment = 0;
|
||||
this._totalSegmentDistance = Math.sqrt(pathX * pathX + pathY * pathY);
|
||||
this._distanceOnSegment = 0;
|
||||
this._reachedEnd = false;
|
||||
this._movementAngle =
|
||||
((Math.atan2(pathY, pathX) * 180) / Math.PI + 360) % 360;
|
||||
(gdjs.toDegrees(Math.atan2(pathY, pathX)) + 360) % 360;
|
||||
} else {
|
||||
this._reachedEnd = true;
|
||||
this._speed = 0;
|
||||
@@ -408,58 +408,59 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the speed of the object
|
||||
// Update the speed of the object
|
||||
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
||||
this._speed += this._acceleration * timeDelta;
|
||||
if (this._speed > this._maxSpeed) {
|
||||
this._speed = this._maxSpeed;
|
||||
const previousSpeed = this._speed;
|
||||
if (this._speed !== this._maxSpeed) {
|
||||
this._speed += this._acceleration * timeDelta;
|
||||
if (this._speed > this._maxSpeed) {
|
||||
this._speed = this._maxSpeed;
|
||||
}
|
||||
}
|
||||
this._angularSpeed = this._angularMaxSpeed;
|
||||
|
||||
//Update the time on the segment and change segment if needed
|
||||
this._timeOnSegment += this._speed * timeDelta;
|
||||
// Update the time on the segment and change segment if needed
|
||||
// Use a Verlet integration to be frame rate independent.
|
||||
this._distanceOnSegment +=
|
||||
((this._speed + previousSpeed) / 2) * timeDelta;
|
||||
const remainingDistanceOnSegment =
|
||||
this._totalSegmentDistance - this._distanceOnSegment;
|
||||
if (
|
||||
this._timeOnSegment >= this._totalSegmentTime &&
|
||||
remainingDistanceOnSegment <= 0 &&
|
||||
this._currentSegment < this._path.length
|
||||
) {
|
||||
this._enterSegment(this._currentSegment + 1);
|
||||
this._distanceOnSegment = -remainingDistanceOnSegment;
|
||||
}
|
||||
|
||||
//Position object on the segment and update its angle
|
||||
// Position object on the segment and update its angle
|
||||
let newPos = [0, 0];
|
||||
let pathAngle = this.owner.getAngle();
|
||||
if (this._currentSegment < this._path.length - 1) {
|
||||
newPos[0] = gdjs.evtTools.common.lerp(
|
||||
this._path[this._currentSegment][0],
|
||||
this._path[this._currentSegment + 1][0],
|
||||
this._timeOnSegment / this._totalSegmentTime
|
||||
this._distanceOnSegment / this._totalSegmentDistance
|
||||
);
|
||||
newPos[1] = gdjs.evtTools.common.lerp(
|
||||
this._path[this._currentSegment][1],
|
||||
this._path[this._currentSegment + 1][1],
|
||||
this._timeOnSegment / this._totalSegmentTime
|
||||
this._distanceOnSegment / this._totalSegmentDistance
|
||||
);
|
||||
pathAngle =
|
||||
gdjs.toDegrees(
|
||||
Math.atan2(
|
||||
this._path[this._currentSegment + 1][1] -
|
||||
this._path[this._currentSegment][1],
|
||||
this._path[this._currentSegment + 1][0] -
|
||||
this._path[this._currentSegment][0]
|
||||
)
|
||||
) + this._angleOffset;
|
||||
if (
|
||||
this._rotateObject &&
|
||||
this.owner.getAngle() !== this._movementAngle + this._angleOffset
|
||||
) {
|
||||
this.owner.rotateTowardAngle(
|
||||
this._movementAngle + this._angleOffset,
|
||||
this._angularSpeed,
|
||||
runtimeScene
|
||||
);
|
||||
}
|
||||
} else {
|
||||
newPos = this._path[this._path.length - 1];
|
||||
}
|
||||
this.owner.setX(newPos[0]);
|
||||
this.owner.setY(newPos[1]);
|
||||
if (this._rotateObject) {
|
||||
this.owner.rotateTowardAngle(
|
||||
pathAngle,
|
||||
this._angularSpeed,
|
||||
runtimeScene
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// @ts-check
|
||||
describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
// tests cases where every collisionMethod has the same behavior.
|
||||
let doCommonPathFindingTests = (collisionMethod, allowDiagonals) => {
|
||||
const pathFindingName = 'auto1';
|
||||
|
||||
const createScene = () => {
|
||||
const createScene = (framePerSecond = 60) => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
// @ts-ignore - missing properties.
|
||||
@@ -39,11 +40,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return (1 / 60) * 1000;
|
||||
};
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
return runtimeScene;
|
||||
};
|
||||
const setFramePerSecond = (runtimeScene, framePerSecond) => {
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return 1000 / framePerSecond;
|
||||
};
|
||||
};
|
||||
|
||||
const addPlayer = (runtimeScene) => {
|
||||
const player = new gdjs.RuntimeObject(runtimeScene, {
|
||||
@@ -156,6 +160,96 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
);
|
||||
});
|
||||
|
||||
if (allowDiagonals) {
|
||||
[20, 30, 60, 120].forEach((framePerSecond) => {
|
||||
describe(`(${framePerSecond} fps)`, function () {
|
||||
it('can move on the path at the right speed', function () {
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
const obstacle = addObstacle(runtimeScene);
|
||||
|
||||
obstacle.setPosition(600, 300);
|
||||
// To ensure obstacles are registered.
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
|
||||
player.setPosition(480, 300);
|
||||
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
|
||||
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).getNodeCount()
|
||||
).to.be.above(13);
|
||||
|
||||
// Move on the path and stop before the last 1/10 of second.
|
||||
for (let i = 0; i < (framePerSecond * 17) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(false);
|
||||
}
|
||||
// The position is the same no matter the frame rate.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be.within(
|
||||
288.5786437626905 - epsilon,
|
||||
288.5786437626905 + epsilon
|
||||
);
|
||||
|
||||
// Let 1/10 of second pass,
|
||||
// because the calculus interval is not the same for each case.
|
||||
for (let i = 0; i < framePerSecond / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
}
|
||||
// The destination is reached for every frame rate within 1/10 of second.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be(300);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
[20, 30, 60, 120].forEach((framePerSecond) => {
|
||||
describe(`(${framePerSecond} fps)`, function () {
|
||||
it('can move on the path at the right speed', function () {
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
const obstacle = addObstacle(runtimeScene);
|
||||
|
||||
obstacle.setPosition(600, 300);
|
||||
// To ensure obstacles are registered.
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
|
||||
player.setPosition(480, 300);
|
||||
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
|
||||
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).getNodeCount()
|
||||
).to.be.above(13);
|
||||
|
||||
// Move on the path and stop before the last 1/10 of second.
|
||||
for (let i = 0; i < (framePerSecond * 20) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(false);
|
||||
}
|
||||
expect(player.getX()).to.be(710);
|
||||
expect(player.getY()).to.be.within(300 - epsilon, 300 + epsilon);
|
||||
|
||||
// Let 1/10 of second pass,
|
||||
// because the calculus interval is not the same for each case.
|
||||
for (let i = 0; i < (framePerSecond * 1) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
}
|
||||
// The destination is reached for every frame rate within 1/10 of second.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be(300);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('can find a path between 2 obstacles', function () {
|
||||
const obstacleTop = addObstacle(runtimeScene);
|
||||
const obstacleBottom = addObstacle(runtimeScene);
|
||||
|
@@ -406,7 +406,7 @@ module.exports = {
|
||||
_('Physics Engine 2.0'),
|
||||
'Physics2',
|
||||
_(
|
||||
'Simulate realistic object physics, with gravity, forces, joints, etc.'
|
||||
'Simulate realistic object physics with gravity, forces, joints, etc.'
|
||||
),
|
||||
'',
|
||||
'res/physics32.png',
|
||||
|
@@ -34,7 +34,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"PlatformerObjectBehavior",
|
||||
_("Platformer character"),
|
||||
"PlatformerObject",
|
||||
_("Controllable character that can jump and run on platforms."),
|
||||
_("Jump and run on platforms."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformerobjecticon.png",
|
||||
"PlatformerObjectBehavior",
|
||||
@@ -171,6 +171,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.AddParameter("yesorno", _("If jumping, try to preserve the current speed in the air"))
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SetMaxFallingSpeed")
|
||||
.SetGetter("GetMaxFallingSpeed");
|
||||
@@ -206,9 +207,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddCondition("Acceleration",
|
||||
_("Acceleration"),
|
||||
_("Compare the acceleration of the object (in pixels per "
|
||||
_("Compare the horizontal acceleration of the object (in pixels per "
|
||||
"second per second)."),
|
||||
_("the acceleration"),
|
||||
_("the horizontal acceleration"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -220,9 +221,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddAction("Acceleration",
|
||||
_("Acceleration"),
|
||||
_("Change the acceleration of an object (in pixels per "
|
||||
_("Change the horizontal acceleration of an object (in pixels per "
|
||||
"second per second)."),
|
||||
_("the acceleration"),
|
||||
_("the horizontal acceleration"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -235,9 +236,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddCondition("Deceleration",
|
||||
_("Deceleration"),
|
||||
_("Compare the deceleration of the object (in pixels per "
|
||||
_("Compare the horizontal deceleration of the object (in pixels per "
|
||||
"second per second)."),
|
||||
_("the deceleration"),
|
||||
_("the horizontal deceleration"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -249,9 +250,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddAction("Deceleration",
|
||||
_("Deceleration"),
|
||||
_("Change the deceleration of an object (in pixels per "
|
||||
_("Change the horizontal deceleration of an object (in pixels per "
|
||||
"second per second)."),
|
||||
_("the deceleration"),
|
||||
_("the horizontal deceleration"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -264,9 +265,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddCondition(
|
||||
"MaxSpeed",
|
||||
_("Maximum speed"),
|
||||
_("Compare the maximum speed of the object (in pixels per second)."),
|
||||
_("the maximum speed"),
|
||||
_("Maximum horizontal speed"),
|
||||
_("Compare the maximum horizontal speed of the object (in pixels per second)."),
|
||||
_("the maximum horizontal speed"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -277,9 +278,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddAction(
|
||||
"MaxSpeed",
|
||||
_("Maximum speed"),
|
||||
_("Change the maximum speed of an object (in pixels per second)."),
|
||||
_("the maximum speed"),
|
||||
_("Maximum horizontal speed"),
|
||||
_("Change the maximum horizontal speed of an object (in pixels per second)."),
|
||||
_("the maximum horizontal speed"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -293,7 +294,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
aut.AddCondition(
|
||||
"JumpSpeed",
|
||||
_("Jump speed"),
|
||||
_("Compare the jump speed of the object (in pixels per second)."),
|
||||
_("Compare the jump speed of the object (in pixels per second)."
|
||||
"Its value is always positive."),
|
||||
_("the jump speed"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
@@ -307,7 +309,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
aut.AddAction(
|
||||
"JumpSpeed",
|
||||
_("Jump speed"),
|
||||
_("Change the jump speed of an object (in pixels per second)."),
|
||||
_("Change the jump speed of an object (in pixels per second). "
|
||||
"Its value is always positive."),
|
||||
_("the jump speed"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
@@ -321,7 +324,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
aut.AddCondition(
|
||||
"JumpSustainTime",
|
||||
_("Jump sustain time"),
|
||||
_("Compare the jump sustain time of the object (in seconds)."),
|
||||
_("Compare the jump sustain time of the object (in seconds)."
|
||||
"This is the time during which keeping the jump button held "
|
||||
"allow the initial jump speed to be maintained."),
|
||||
_("the jump sustain time"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
@@ -333,7 +338,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddAction("JumpSustainTime",
|
||||
_("Jump sustain time"),
|
||||
_("Change the jump sustain time of an object (in seconds)."),
|
||||
_("Change the jump sustain time of an object (in seconds). "
|
||||
"This is the time during which keeping the jump button held "
|
||||
"allow the initial jump speed to be maintained."),
|
||||
_("the jump sustain time"),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
@@ -351,7 +358,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"again this action everytime you want to allow the object to jump "
|
||||
"(apart if it's on the floor)."),
|
||||
_("Allow _PARAM0_ to jump again"),
|
||||
_("Options"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -366,7 +373,19 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"is made unable to jump while in mid air. This has no effect if "
|
||||
"the object is not in the air."),
|
||||
_("Forbid _PARAM0_ to air jump"),
|
||||
_("Options"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior");
|
||||
|
||||
aut.AddScopedAction(
|
||||
"AbortJump",
|
||||
_("Abort jump"),
|
||||
_("Abort the current jump and stop the object vertically. "
|
||||
"This action doesn't have any effect when the character is not jumping."),
|
||||
_("Abort the current jump of _PARAM0_"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -500,12 +519,26 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SimulateControl");
|
||||
|
||||
aut.AddScopedCondition("IsUsingControl",
|
||||
_("Control pressed or simulated"),
|
||||
_("A control was applied from a default control or a simulated by an action."),
|
||||
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
|
||||
_(""),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
aut.AddAction("IgnoreDefaultControls",
|
||||
_("Ignore default controls"),
|
||||
_("De/activate the use of default controls.\nIf deactivated, "
|
||||
"use the simulated actions to move the object."),
|
||||
_("Ignore default controls for _PARAM0_: _PARAM2_"),
|
||||
_("Controls"),
|
||||
_("Options"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -519,7 +552,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
_("Enable (or disable) the ability of the object to grab "
|
||||
"platforms when falling near to one."),
|
||||
_("Allow _PARAM0_ to grab platforms: _PARAM2_"),
|
||||
_("Controls"),
|
||||
_("Options"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -531,7 +564,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
_("Can grab platforms"),
|
||||
_("Check if the object can grab the platforms."),
|
||||
_("_PARAM0_ can grab the platforms"),
|
||||
"",
|
||||
"Options",
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -539,13 +572,28 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsSimple()
|
||||
.SetFunctionName("canGrabPlatforms");
|
||||
|
||||
aut.AddScopedAction(
|
||||
"SetCurrentFallSpeed",
|
||||
_("Current falling speed"),
|
||||
_("Change the current falling speed of the object (in pixels per "
|
||||
"second). This action doesn't have any effect when the character "
|
||||
"is not falling or is in the first phase of a jump."),
|
||||
_("the current falling speed"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
aut.AddCondition(
|
||||
"CurrentFallSpeed",
|
||||
_("Current falling speed"),
|
||||
_("Compare the current falling speed of the object (in pixels per "
|
||||
"second)."),
|
||||
"second). Its value is always positive."),
|
||||
_("the current falling speed"),
|
||||
_("Options"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -558,9 +606,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"CurrentJumpSpeed",
|
||||
_("Current jump speed"),
|
||||
_("Compare the current jump speed of the object (in pixels per "
|
||||
"second)."),
|
||||
"second). Its value is always positive."),
|
||||
_("the current jump speed"),
|
||||
_("Options"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -569,12 +617,27 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("GetCurrentJumpSpeed");
|
||||
|
||||
aut.AddScopedAction("SetCurrentSpeed",
|
||||
_("Current horizontal speed"),
|
||||
_("Change the current horizontal speed of the object "
|
||||
"(in pixels per second). The object moves to the left "
|
||||
"with negative values and to the right with positive ones"),
|
||||
_("the current horizontal speed"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
aut.AddCondition("CurrentSpeed",
|
||||
_("Current speed"),
|
||||
_("Compare the current speed of the object (in pixels per "
|
||||
"second)."),
|
||||
_("the current speed"),
|
||||
_("Options"),
|
||||
_("Current horizontal speed"),
|
||||
_("Compare the current horizontal speed of the object "
|
||||
"(in pixels per second). The object moves to the left "
|
||||
"with negative values and to the right with positive ones"),
|
||||
_("the current horizontal speed"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon24.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -585,7 +648,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("Gravity",
|
||||
_("Gravity"),
|
||||
_("Get the gravity applied on the object"),
|
||||
_("Return the gravity applied on the object "
|
||||
"(in pixels per second per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -594,7 +658,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("MaxFallingSpeed",
|
||||
_("Maximum falling speed"),
|
||||
_("Get the maximum falling speed"),
|
||||
_("Return the maximum falling speed of the object "
|
||||
"(in pixels per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -603,7 +668,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("LadderClimbingSpeed",
|
||||
_("Ladder climbing speed"),
|
||||
_("Get the ladder climbing speed"),
|
||||
_("Return the ladder climbing speed of the object "
|
||||
"(in pixels per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -612,7 +678,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("Acceleration",
|
||||
_("Acceleration"),
|
||||
_("Acceleration"),
|
||||
_("Return the horizontal acceleration of the object "
|
||||
"(in pixels per second per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -621,7 +688,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("Deceleration",
|
||||
_("Deceleration"),
|
||||
_("Deceleration"),
|
||||
_("Return the horizontal deceleration of the object "
|
||||
"(in pixels per second per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -629,8 +697,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetDeceleration");
|
||||
|
||||
aut.AddExpression("MaxSpeed",
|
||||
_("Maximum speed"),
|
||||
_("Maximum speed"),
|
||||
_("Maximum horizontal speed"),
|
||||
_("Return the maximum horizontal speed of the object "
|
||||
"(in pixels per second)."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -639,7 +708,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("JumpSpeed",
|
||||
_("Jump speed"),
|
||||
_("Jump speed"),
|
||||
_("Return the jump speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
@@ -648,7 +718,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("JumpSustainTime",
|
||||
_("Jump sustain time"),
|
||||
_("The time during which keeping the jump button held "
|
||||
_("Return the jump sustain time of the object (in seconds)."
|
||||
"This is the time during which keeping the jump button held "
|
||||
"allow the initial jump speed to be maintained."),
|
||||
_("Options"),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
@@ -657,26 +728,30 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddExpression("CurrentFallSpeed",
|
||||
_("Current fall speed"),
|
||||
_("Current fall speed"),
|
||||
_("Options"),
|
||||
_("Return the current fall speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetCurrentFallSpeed");
|
||||
|
||||
aut.AddExpression("CurrentSpeed",
|
||||
_("Current speed"),
|
||||
_("Current speed"),
|
||||
_("Options"),
|
||||
_("Current horizontal speed"),
|
||||
_("Return the current horizontal speed of the object "
|
||||
"(in pixels per second). The object moves to the left "
|
||||
"with negative values and to the right with positive ones"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetCurrentSpeed");
|
||||
|
||||
aut.AddExpression("CurrentJumpSpeed",
|
||||
_("Return the current jump speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Current jump speed"),
|
||||
_("Current jump speed"),
|
||||
_("Options"),
|
||||
_(""),
|
||||
"CppPlatform/Extensions/platformerobjecticon16.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
@@ -687,7 +762,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"PlatformBehavior",
|
||||
_("Platform"),
|
||||
"Platform",
|
||||
_("Platform that Platformer characters can run on."),
|
||||
_("Flag objects as being platforms where characters can run on."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformicon.png",
|
||||
"PlatformBehavior",
|
||||
|
@@ -108,9 +108,15 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("setJumpSustainTime")
|
||||
.SetGetter("getJumpSustainTime");
|
||||
autExpressions["JumpSustainTime"].SetFunctionName("getJumpSustainTime");
|
||||
autActions["PlatformBehavior::PlatformerObjectBehavior::SetCurrentFallSpeed"]
|
||||
.SetFunctionName("setCurrentFallSpeed")
|
||||
.SetGetter("getCurrentFallSpeed");
|
||||
autConditions["PlatformBehavior::CurrentFallSpeed"].SetFunctionName(
|
||||
"getCurrentFallSpeed");
|
||||
autExpressions["CurrentFallSpeed"].SetFunctionName("getCurrentFallSpeed");
|
||||
autActions["PlatformBehavior::PlatformerObjectBehavior::SetCurrentSpeed"]
|
||||
.SetFunctionName("setCurrentSpeed")
|
||||
.SetGetter("getCurrentSpeed");
|
||||
autConditions["PlatformBehavior::CurrentSpeed"].SetFunctionName(
|
||||
"getCurrentSpeed");
|
||||
autExpressions["CurrentSpeed"].SetFunctionName("getCurrentSpeed");
|
||||
@@ -124,6 +130,7 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
|
||||
autExpressions["CurrentJumpSpeed"].SetFunctionName("getCurrentJumpSpeed");
|
||||
autActions["PlatformBehavior::SetCanJump"].SetFunctionName("setCanJump");
|
||||
autActions["PlatformBehavior::PlatformerObjectBehavior::SetCanNotAirJump"].SetFunctionName("setCanNotAirJump");
|
||||
autActions["PlatformBehavior::PlatformerObjectBehavior::AbortJump"].SetFunctionName("abortJump");
|
||||
autConditions["PlatformBehavior::CanJump"].SetFunctionName(
|
||||
"canJump");
|
||||
autActions["PlatformBehavior::SimulateLeftKey"].SetFunctionName(
|
||||
@@ -147,6 +154,8 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
|
||||
"simulateReleasePlatformKey");
|
||||
autActions["PlatformBehavior::SimulateControl"].SetFunctionName(
|
||||
"simulateControl");
|
||||
autConditions["PlatformBehavior::PlatformerObjectBehavior::IsUsingControl"].SetFunctionName(
|
||||
"isUsingControl");
|
||||
autActions["PlatformBehavior::IgnoreDefaultControls"].SetFunctionName(
|
||||
"ignoreDefaultControls");
|
||||
}
|
||||
|
@@ -42,12 +42,12 @@ std::map<gd::String, gd::PropertyDescriptor> PlatformBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Platform"))
|
||||
.AddExtraInfo(_("Jumpthru platform"))
|
||||
.AddExtraInfo(_("Ladder"));
|
||||
properties[_("Ledges can be grabbed")]
|
||||
properties[_("Ledges can be grabbed")].SetGroup(_("Ledge"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canBeGrabbed", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Grab offset on Y axis")].SetValue(
|
||||
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
|
||||
|
||||
return properties;
|
||||
|
@@ -31,8 +31,11 @@ void PlatformerObjectBehavior::InitializeContent(
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", false);
|
||||
behaviorContent.SetAttribute("slopeMaxAngle", 60);
|
||||
behaviorContent.SetAttribute("canGrabPlatforms", false);
|
||||
behaviorContent.SetAttribute("canGrabWithoutMoving", true);
|
||||
behaviorContent.SetAttribute("yGrabOffset", 0);
|
||||
behaviorContent.SetAttribute("xGrabTolerance", 10);
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", false);
|
||||
behaviorContent.SetAttribute("canGoDownFromJumpthru", true);
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
@@ -41,11 +44,11 @@ PlatformerObjectBehavior::GetProperties(
|
||||
const gd::SerializerElement& behaviorContent) const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
|
||||
properties[_("Gravity")].SetValue(
|
||||
properties[_("Gravity")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gravity")));
|
||||
properties[_("Jump speed")].SetValue(
|
||||
properties[_("Jump speed")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("jumpSpeed")));
|
||||
properties["jumpSustainTime"]
|
||||
properties["jumpSustainTime"].SetGroup(_("Jump"))
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("jumpSustainTime", 0)))
|
||||
.SetLabel(_("Jump sustain time"))
|
||||
@@ -53,32 +56,51 @@ PlatformerObjectBehavior::GetProperties(
|
||||
_("Maximum time (in seconds) during which the jump strength is "
|
||||
"sustained if the jump key is held - allowing variable height "
|
||||
"jumps."));
|
||||
properties[_("Max. falling speed")].SetValue(
|
||||
properties[_("Max. falling speed")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxFallingSpeed")));
|
||||
properties[_("Ladder climbing speed")].SetValue(gd::String::From(
|
||||
properties[_("Ladder climbing speed")].SetGroup(_("Ladder")).SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("ladderClimbingSpeed", 150)));
|
||||
properties[_("Acceleration")].SetValue(
|
||||
properties[_("Acceleration")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
|
||||
properties[_("Deceleration")].SetValue(
|
||||
properties[_("Deceleration")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("deceleration")));
|
||||
properties[_("Max. speed")].SetValue(
|
||||
properties[_("Max. speed")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties[_("Default controls")]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
|
||||
? "false"
|
||||
: "true")
|
||||
.SetType("Boolean");
|
||||
properties[_("Slope max. angle")].SetValue(
|
||||
properties[_("Slope max. angle")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("slopeMaxAngle")));
|
||||
properties[_("Can grab platform ledges")]
|
||||
.SetGroup(_("Ledge"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canGrabPlatforms", false)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Grab offset on Y axis")].SetValue(
|
||||
properties[_("Automatically grab platform ledges without having to move horizontally")]
|
||||
.SetGroup(_("Ledge"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canGrabWithoutMoving", false)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
|
||||
properties[_("Grab tolerance on X axis")].SetValue(gd::String::From(
|
||||
properties[_("Grab tolerance on X axis")].SetGroup(_("Ledge")).SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
|
||||
properties[_("Use frame per second dependent trajectories (deprecated)")]
|
||||
.SetGroup(_("Jump"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Can go down from jumpthru platforms")]
|
||||
.SetGroup(_("Walk"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canGoDownFromJumpthru", false)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -90,6 +112,12 @@ bool PlatformerObjectBehavior::UpdateProperty(
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "0"));
|
||||
else if (name == _("Can grab platform ledges"))
|
||||
behaviorContent.SetAttribute("canGrabPlatforms", (value == "1"));
|
||||
else if (name == _("Automatically grab platform ledges without having to move horizontally"))
|
||||
behaviorContent.SetAttribute("canGrabWithoutMoving", (value == "1"));
|
||||
else if (name == _("Use frame per second dependent trajectories (deprecated)"))
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", (value == "1"));
|
||||
else if (name == _("Can go down from jumpthru platforms"))
|
||||
behaviorContent.SetAttribute("canGoDownFromJumpthru", (value == "1"));
|
||||
else if (name == _("Grab offset on Y axis"))
|
||||
behaviorContent.SetAttribute("yGrabOffset", value.To<double>());
|
||||
else {
|
||||
|
@@ -44,7 +44,7 @@ There are also more obvious obstacles that cover the character in the middle and
|
||||
|
||||
[](./diagrams/SlopeFollowingResult.svgz)
|
||||
|
||||
Obstacles can eventually encompass the character. So platforms edges don't have any collision with character.
|
||||
Obstacles can eventually encompass the character. So, platforms edges don't have any collision with character.
|
||||
To detect such cases, 2 flags are used:
|
||||
|
||||
- `foundOverHead` when an edge is over `headMaxY`
|
||||
@@ -55,3 +55,261 @@ To detect such cases, 2 flags are used:
|
||||
For further details on the implementation, please take a look at the comments in:
|
||||
- the function `gdjs.PlatformerObjectRuntimeBehavior._findHighestFloorAndMoveOnTop`
|
||||
- the class `gdjs.PlatformerObjectRuntimeBehavior.FollowConstraintContext`
|
||||
|
||||
## Jump trajectory
|
||||
|
||||
### Sequential calculus of the engine
|
||||
|
||||
The engine uses the previous speeds and positions to evaluate the new ones (with Verlet integrations). This is fast for sequential access and it's a good fit for what the engine does.
|
||||
|
||||
The model doesn't follow the laws of physics and this is fine because more realism doesn't mean more fun. Internally, it uses 2 independent speeds:
|
||||
- the current jumping speed `currentJumpSpeed`
|
||||
- the current falling speed `currentFallSpeed`
|
||||
|
||||
Note that in the engine `currentJumpSpeed` is actually an absolute speed but it's easier to visualize as a negative value as it is subtracted to `currentFallSpeed`.
|
||||
|
||||
The jump speed stays at its initial value while the jump is sustained. Then, it decreases (in absolute) according to the gravity until it reaches 0 and stays constant until the end.
|
||||
The falling speed starts at 0 and increase according to the gravity, as soon as the jump start. Until, it reaches the maximum falling speed and stays constant until the end too.
|
||||
|
||||
[](./diagrams/JumpSpeed.svgz) [](./diagrams/FallingSpeed.svgz)
|
||||
|
||||
### Requests solving and random access
|
||||
|
||||
#### Why a continuous model?
|
||||
|
||||
A function allows to get a position for any given time without calculating every previous positions.
|
||||
|
||||
#### Polynomial piece-wise function
|
||||
|
||||
The character trajectory during a jump looks like a quadratic function, but it's actually a piece-wise function.
|
||||
|
||||
On this first trajectory example, the curve almost follows the quadratic function in white, but it actually follows the grey ones at the start and the end of the jump. There is also an affine part at the end of the jump. It's not shown on this plot because it happens later on.
|
||||
|
||||

|
||||
|
||||
This example of trajectory shows the 4 functions. It has an affine part in the middle instead of a quadratic one like in the previous example. In practice, this jump trajectory will seem off, but a tiny affine part can happen in some configurations.
|
||||
|
||||

|
||||
|
||||
The piece-wise function has 3 borders:
|
||||
- at the end of the jump sustaining (jumpSustainTime)
|
||||
- when the maximum falling speed is reached (maxFallingTime)
|
||||
- when the jump speed reach 0 (jumpEndTime)
|
||||
|
||||
Depending on the when the maximum falling speed is reached, there can be 3 piece-wise functions:
|
||||
- after the jump end
|
||||
- sustain case
|
||||
- common case
|
||||
- free fall case
|
||||
- gliding case
|
||||
- before the jump end and after the sustaining
|
||||
- sustain case
|
||||
- common case
|
||||
- max falling case
|
||||
- gliding case
|
||||
- during the sustaining
|
||||
- sustain case
|
||||
- affine case
|
||||
- max falling case
|
||||
- gliding case
|
||||
|
||||
[](./diagrams/JumpCases.svgz)
|
||||
|
||||
#### SageMath model
|
||||
|
||||
The following model is written for [SageMath](https://www.sagemath.org/), an open-source mathematics software system.
|
||||
|
||||
```Python
|
||||
# First define the jump and falling functions #
|
||||
|
||||
currentFallingSpeed(t, gravity) = gravity * t
|
||||
fallingX(t, gravity) = integral(currentFallingSpeed(t, gravity), t)
|
||||
maxFallingTime(gravity, maxFallingSpeed) = solve([currentFallingSpeed(t, gravity)==maxFallingSpeed],t)[0].rhs()
|
||||
fallEndedX(t, gravity) = fallingX(maxFallingTime(gravity, maxFallingSpeed), gravity) + maxFallingSpeed * (t - maxFallingTime(gravity, maxFallingSpeed))
|
||||
|
||||
currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) = -jumpSpeed + gravity * (t - jumpSustainTime)
|
||||
sustainedX(t, jumpSpeed) = -jumpSpeed * t
|
||||
jumpingX(t, gravity, jumpSpeed, jumpSustainTime) = sustainedX(jumpSustainTime, jumpSpeed) + integral(currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime), t, jumpSustainTime, t)
|
||||
jumpEndTime(gravity, jumpSpeed, jumpSustainTime) = jumpSustainTime + jumpSpeed / gravity
|
||||
jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) = jumpingX(jumpEndTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
|
||||
|
||||
|
||||
# t < jumpSustainTime && t < maxFallingTime #
|
||||
|
||||
sustainCaseSpeed(t, gravity, jumpSpeed) = -jumpSpeed + currentFallingSpeed(t, gravity)
|
||||
sustainCaseHeight(t, gravity, jumpSpeed) = sustainedX(t, jumpSpeed) + fallingX(t, gravity)
|
||||
sustainCasePeakTime(gravity, jumpSpeed, jumpSustainTime) = solve([sustainCaseSpeed(t, gravity, jumpSpeed)==0], t)[0].rhs()
|
||||
sustainCasePeakHeight(gravity, jumpSpeed, jumpSustainTime) = sustainCaseHeight(sustainCasePeakTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
|
||||
|
||||
|
||||
# jumpSustainTime < t && t < maxFallingTime && t < jumpEndTime #
|
||||
|
||||
commonCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime) = currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) + currentFallingSpeed(t, gravity)
|
||||
commonCaseHeight(t, gravity, jumpSpeed, jumpSustainTime) = jumpingX(t, gravity, jumpSpeed, jumpSustainTime) + fallingX(t, gravity)
|
||||
commonCasePeakTime(gravity, jumpSpeed, jumpSustainTime) = solve([commonCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime)==0], t)[0].rhs()
|
||||
commonCasePeakHeight(gravity, jumpSpeed, jumpSustainTime) = commonCaseHeight(commonCasePeakTime(gravity, jumpSpeed, jumpSustainTime), gravity, jumpSpeed, jumpSustainTime)
|
||||
|
||||
|
||||
# jumpSustainTime < t && maxFallingTime < t && t < jumpEndTime #
|
||||
|
||||
maxFallingCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = currentJumpSpeed(t, gravity, jumpSpeed, jumpSustainTime) + maxFallingSpeed
|
||||
maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpingX(t, gravity, jumpSpeed, jumpSustainTime) + fallEndedX(t, gravity)
|
||||
maxFallingCasePeakTime(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseSpeed(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==0], t)[0].rhs()
|
||||
maxFallingCasePeakHeight(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = maxFallingCaseHeight(maxFallingCasePeakTime(gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed), gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)
|
||||
|
||||
# maxFallingTime < t && t < jumpSustainTime #
|
||||
# in this case the speed is constant (-jumpSpeed + maxFallingSpeed) there is no strict maximum. #
|
||||
|
||||
affineCaseSpeed(t, jumpSpeed, maxFallingSpeed) = -jumpSpeed + maxFallingSpeed
|
||||
affineCaseHeight(t, gravity, jumpSpeed, maxFallingSpeed) = sustainedX(t, jumpSpeed) + fallEndedX(t, gravity)
|
||||
|
||||
|
||||
# jumpEndTime < t && maxFallingTime < t #
|
||||
# This is a strictly descending phase, the peak can't happen here #
|
||||
|
||||
glidingCaseSpeed(t, maxFallingSpeed) = maxFallingSpeed
|
||||
glidingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) + fallEndedX(t, gravity)
|
||||
|
||||
|
||||
# jumpEndTime < t && t < maxFallingTime #
|
||||
# This is a strictly descending phase, the peak can't happen here #
|
||||
|
||||
freeFallCaseSpeed(t, gravity) = currentFallingSpeed(t, gravity)
|
||||
freeFallCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = jumpEndedX(t, gravity, jumpSpeed, jumpSustainTime) + fallingX(t, gravity)
|
||||
```
|
||||
|
||||
#### Find the initial jump speed for a given jump height
|
||||
|
||||
The great thing about quadratic functions is that it's easy to find solutions to a constraint. For instance, to find which initial jump speed to choose to jump a given height, the formula can be extracted like this:
|
||||
|
||||
```Python
|
||||
var('givenHeight')
|
||||
solve([sustainCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
|
||||
solve([commonCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
|
||||
solve([maxFallingCasePeakHeight(gravity, jumpSpeed, jumpSustainTime)==givenHeight], jumpSpeed)[1].rhs()
|
||||
```
|
||||
|
||||
The program will have to determine which formula to use. As the initial jump speed is unknown, it's not possible to know where the peak will happen, but jump speed solutions of each formula can be used to check if the peak actually happens during the right interval of time.
|
||||
|
||||
```JavaScript
|
||||
if (maxFallingSpeedReachedTime > jumpSustainTime) {
|
||||
// common case
|
||||
jumpSpeed = commonCaseJumpSpeed(jumpHeight);
|
||||
peakTime = commonCasePeakTime(jumpSpeed);
|
||||
|
||||
if (peakTime < jumpSustainTime) {
|
||||
// sustain case
|
||||
jumpSpeed = sustainCaseJumpSpeed(jumpHeight);
|
||||
}
|
||||
else if (peakTime > maxFallingSpeedReachedTime) {
|
||||
// max falling case
|
||||
jumpSpeed = maxFallingCaseJumpSpeed(jumpHeight);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// affine case can't have a maximum
|
||||
|
||||
// sustain case
|
||||
jumpSpeed = sustainCaseJumpSpeed(jumpHeight);
|
||||
peakTime = jumpSpeed / gravity;
|
||||
|
||||
if (peakTime > maxFallingSpeedReachedTime) {
|
||||
// max falling case
|
||||
jumpSpeed = maxFallingCaseJumpSpeed(jumpHeight);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Find the time for a given Y displacement
|
||||
|
||||
The same kind of logic from the previous section can be used to evaluate at which time a character will be at a given ordinate when going up or going down.
|
||||
|
||||
```Python
|
||||
sustainCaseUpTime(y, gravity, jumpSpeed) = solve([sustainCaseHeight(t, gravity, jumpSpeed)==y], t)[0].rhs()
|
||||
commonCaseUpTime(y, gravity, jumpSpeed, jumpSustainTime) = solve([height(t, gravity, jumpSpeed, jumpSustainTime)==y], t)[0].rhs()
|
||||
maxFallingCaseCaseUpTime(y, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[0].rhs()
|
||||
|
||||
sustainCaseDownTime(y, gravity, jumpSpeed) = solve([sustainCaseHeight(t, gravity, jumpSpeed)==y], t)[1].rhs()
|
||||
commonCaseDownTime(y, gravity, jumpSpeed, jumpSustainTime) = solve([height(t, gravity, jumpSpeed, jumpSustainTime)==y], t)[1].rhs()
|
||||
maxFallingCaseCaseDownTime(y, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([maxFallingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[1].rhs()
|
||||
glidingCaseDownTime(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([glidingCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[0].rhs() # only one solution (it's affine) #
|
||||
freeFallCaseDownTime(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed) = solve([freeFallCaseHeight(t, gravity, jumpSpeed, jumpSustainTime, maxFallingSpeed)==y], t)[1].rhs()
|
||||
|
||||
affineCaseTime(y, jumpSpeed, maxFallingSpeed) = solve([affineCaseHeight(t, jumpSpeed, maxFallingSpeed)==y], t)[0].rhs()
|
||||
```
|
||||
|
||||
The root conditions are the 3 piece-wise functions described at the end of the [Polynomial piece-wise function](#polynomial-piece-wise-function) section.
|
||||
Note that quadratic functions have a maximum or a minimum value which means there won't always be a solution. That's why there are "not a number" checks.
|
||||
|
||||
```JavaScript
|
||||
if (maxFallingSpeedReachedTime > jumpEndTime) {
|
||||
time = sustainCase(y);
|
||||
if (time > jumpSustainTime || Number.isNaN(time)) {
|
||||
time = commonCase(y);
|
||||
if (time > jumpEndTime || Number.isNaN(time)) {
|
||||
time = freeFallCase(y);
|
||||
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
|
||||
time = glidingCase(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (maxFallingSpeedReachedTime > jumpSustainTime) {
|
||||
time = sustainCase(y);
|
||||
if (time > jumpSustainTime || Number.isNaN(time)) {
|
||||
time = commonCase(y);
|
||||
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
|
||||
time = maxFallingCase(y);
|
||||
if (time > jumpEndTime || Number.isNaN(time)) {
|
||||
time = glidingCase(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
time = sustainCase(y);
|
||||
if (time > maxFallingSpeedReachedTime || Number.isNaN(time)) {
|
||||
time = maxFallingSpeed >= jumpSpeed ? affineCase(y) : time = Number.MAX_VALUE;
|
||||
if (time > jumpSustainTime || Number.isNaN(time)) {
|
||||
time = maxFallingCase(y);
|
||||
if (time > jumpEndTime || Number.isNaN(time)) {
|
||||
time = glidingCase(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Plotting trajectories
|
||||
|
||||
This is the code used to plot the first graphic of the [Polynomial piece-wise function](#polynomial-piece-wise-function) section.
|
||||
|
||||
```Python
|
||||
# The trajectory of the default jump settings
|
||||
#
|
||||
# gravity: 1000
|
||||
# maxFallingSpeed: 700
|
||||
# jumpSustainTime: 0.2
|
||||
# jumpSpeed: 600
|
||||
#
|
||||
# n(maxFallingTime(1000, 700))
|
||||
# 0.7
|
||||
|
||||
(
|
||||
plot(-sustainCaseHeight(t, 1000, 600), 0, 0.8, ymin=0, color='grey')
|
||||
+ plot(-commonCaseHeight(t, 1000, 600, 0.2), 0, 0.8, ymin=0, color='black')
|
||||
+ plot(-maxFallingCaseHeight(t, 1000, 600, 0.2, 700), 0, 0.8, ymin=0, color='grey')
|
||||
+ plot(-piecewise([
|
||||
((0, 0.2), sustainCaseHeight(t, 1000, 600)),
|
||||
((0.2, maxFallingTime(1000, 700)), commonCaseHeight(t, 1000, 600, 0.2)),
|
||||
((maxFallingTime(1000, 700), 2), maxFallingCaseHeight(t, 1000, 600, 0.2, 700))
|
||||
]), 0, 0.8, ymin=0, color='cyan')
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -5,7 +5,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior Benchmark', function () {
|
||||
const stepCount = 6000;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makeTestRuntimeScene();
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
objects = new Array(duplicateCount);
|
||||
for (let i = 0; i < duplicateCount; ++i) {
|
||||
|
BIN
Extensions/PlatformBehavior/diagrams/DefaultSettings.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/DefaultSettings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
Extensions/PlatformBehavior/diagrams/FallingSpeed.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/FallingSpeed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
Extensions/PlatformBehavior/diagrams/FallingSpeed.svgz
Normal file
BIN
Extensions/PlatformBehavior/diagrams/FallingSpeed.svgz
Normal file
Binary file not shown.
BIN
Extensions/PlatformBehavior/diagrams/JumpCases.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/JumpCases.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
Extensions/PlatformBehavior/diagrams/JumpCases.svgz
Normal file
BIN
Extensions/PlatformBehavior/diagrams/JumpCases.svgz
Normal file
Binary file not shown.
BIN
Extensions/PlatformBehavior/diagrams/JumpSpeed.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/JumpSpeed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
BIN
Extensions/PlatformBehavior/diagrams/JumpSpeed.svgz
Normal file
BIN
Extensions/PlatformBehavior/diagrams/JumpSpeed.svgz
Normal file
Binary file not shown.
BIN
Extensions/PlatformBehavior/diagrams/WithAffine.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/WithAffine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
@@ -24,29 +24,52 @@ namespace gdjs {
|
||||
isCollidingAnyPlatform: false,
|
||||
};
|
||||
|
||||
// To achieve pixel-perfect precision when positioning object on platform or
|
||||
// handling collision with "walls", edges of the hitboxes must be ignored during
|
||||
// collision checks, so that two overlapping edges are not considered as colliding.
|
||||
// For example, if a character is 10px width and is at position (0, 0), it must not be
|
||||
// considered as colliding with a platform which is at position (10, 0). Edges will
|
||||
// still be overlapping (because character hitbox right edge is at X position 10 and
|
||||
// platform hitbox left edge is also at X position 10).
|
||||
// This parameter "_ignoreTouchingEdges" will be passed to all collision handling functions.
|
||||
// Behavior configuration
|
||||
|
||||
/** To achieve pixel-perfect precision when positioning object on platform or
|
||||
* handling collision with "walls", edges of the hitboxes must be ignored during
|
||||
* collision checks, so that two overlapping edges are not considered as colliding.
|
||||
*
|
||||
* For example, if a character is 10px width and is at position (0, 0), it must not be
|
||||
* considered as colliding with a platform which is at position (10, 0). Edges will
|
||||
* still be overlapping (because character hitbox right edge is at X position 10 and
|
||||
* platform hitbox left edge is also at X position 10).
|
||||
*
|
||||
* This parameter "_ignoreTouchingEdges" will be passed to all collision handling functions.
|
||||
*/
|
||||
_ignoreTouchingEdges: boolean = true;
|
||||
_gravity: float;
|
||||
_maxFallingSpeed: float;
|
||||
_ladderClimbingSpeed: float;
|
||||
|
||||
private _acceleration: float;
|
||||
private _deceleration: float;
|
||||
private _maxSpeed: float;
|
||||
private _slopeMaxAngle: float;
|
||||
_slopeClimbingFactor: float = 1;
|
||||
|
||||
_gravity: float;
|
||||
_maxFallingSpeed: float;
|
||||
_jumpSpeed: float;
|
||||
_jumpSustainTime: float;
|
||||
|
||||
_ladderClimbingSpeed: float;
|
||||
|
||||
_canGrabPlatforms: boolean;
|
||||
_canGrabWithoutMoving: boolean;
|
||||
private _yGrabOffset: any;
|
||||
private _xGrabTolerance: any;
|
||||
_jumpSustainTime: float;
|
||||
_currentFallSpeed: float = 0;
|
||||
|
||||
_useLegacyTrajectory: boolean = true;
|
||||
|
||||
_canGoDownFromJumpthru: boolean = false;
|
||||
|
||||
// Behavior state
|
||||
|
||||
_currentSpeed: float = 0;
|
||||
_requestedDeltaX: float = 0;
|
||||
_requestedDeltaY: float = 0;
|
||||
_lastDeltaY: float = 0;
|
||||
_currentFallSpeed: float = 0;
|
||||
_canJump: boolean = false;
|
||||
_lastDirectionIsLeft: boolean = false;
|
||||
|
||||
private _ignoreDefaultControls: boolean;
|
||||
private _leftKey: boolean = false;
|
||||
@@ -58,6 +81,18 @@ namespace gdjs {
|
||||
_releasePlatformKey: boolean = false;
|
||||
_releaseLadderKey: boolean = false;
|
||||
|
||||
// This is useful for extensions that need to know
|
||||
// which keys were pressed and doesn't know the mapping
|
||||
// done by the scene events.
|
||||
private _wasLeftKeyPressed: boolean = false;
|
||||
private _wasRightKeyPressed: boolean = false;
|
||||
private _wasLadderKeyPressed: boolean = false;
|
||||
private _wasUpKeyPressed: boolean = false;
|
||||
private _wasDownKeyPressed: boolean = false;
|
||||
private _wasJumpKeyPressed: boolean = false;
|
||||
private _wasReleasePlatformKeyPressed: boolean = false;
|
||||
private _wasReleaseLadderKeyPressed: boolean = false;
|
||||
|
||||
private _state: State;
|
||||
_falling: Falling;
|
||||
_onFloor: OnFloor;
|
||||
@@ -69,18 +104,11 @@ namespace gdjs {
|
||||
_potentialCollidingObjects: Array<gdjs.PlatformRuntimeBehavior>;
|
||||
|
||||
/** Overlapped jump-thru platforms, updated with `_updateOverlappedJumpThru`. */
|
||||
private _overlappedJumpThru: Array<gdjs.PlatformRuntimeBehavior>;
|
||||
_overlappedJumpThru: Array<gdjs.PlatformRuntimeBehavior>;
|
||||
|
||||
private _hasReallyMoved: boolean = false;
|
||||
private _manager: gdjs.PlatformObjectsManager;
|
||||
|
||||
private _slopeMaxAngle: float;
|
||||
_slopeClimbingFactor: float = 1;
|
||||
|
||||
_requestedDeltaX: float = 0;
|
||||
_requestedDeltaY: float = 0;
|
||||
_lastDeltaY: float = 0;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
behaviorData,
|
||||
@@ -95,15 +123,19 @@ namespace gdjs {
|
||||
this._maxSpeed = behaviorData.maxSpeed;
|
||||
this._jumpSpeed = behaviorData.jumpSpeed;
|
||||
this._canGrabPlatforms = behaviorData.canGrabPlatforms || false;
|
||||
this._canGrabWithoutMoving = behaviorData.canGrabWithoutMoving;
|
||||
this._yGrabOffset = behaviorData.yGrabOffset || 0;
|
||||
this._xGrabTolerance = behaviorData.xGrabTolerance || 10;
|
||||
this._jumpSustainTime = behaviorData.jumpSustainTime || 0;
|
||||
this._ignoreDefaultControls = behaviorData.ignoreDefaultControls;
|
||||
this._potentialCollidingObjects = [];
|
||||
|
||||
this._overlappedJumpThru = [];
|
||||
this._useLegacyTrajectory = behaviorData.useLegacyTrajectory;
|
||||
this._canGoDownFromJumpthru = behaviorData.canGoDownFromJumpthru;
|
||||
this._slopeMaxAngle = 0;
|
||||
this.setSlopeMaxAngle(behaviorData.slopeMaxAngle);
|
||||
|
||||
this._potentialCollidingObjects = [];
|
||||
this._overlappedJumpThru = [];
|
||||
|
||||
this._manager = gdjs.PlatformObjectsManager.getManager(runtimeScene);
|
||||
|
||||
this._falling = new Falling(this);
|
||||
@@ -138,6 +170,12 @@ namespace gdjs {
|
||||
) {
|
||||
this.setCanGrabPlatforms(newBehaviorData.canGrabPlatforms);
|
||||
}
|
||||
if (
|
||||
oldBehaviorData.canGrabWithoutMoving !==
|
||||
newBehaviorData.canGrabWithoutMoving
|
||||
) {
|
||||
this._canGrabWithoutMoving = newBehaviorData.canGrabWithoutMoving;
|
||||
}
|
||||
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
|
||||
this._yGrabOffset = newBehaviorData.yGrabOffset;
|
||||
}
|
||||
@@ -147,6 +185,18 @@ namespace gdjs {
|
||||
if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) {
|
||||
this.setJumpSustainTime(newBehaviorData.jumpSustainTime);
|
||||
}
|
||||
if (
|
||||
oldBehaviorData.useLegacyTrajectory !==
|
||||
newBehaviorData.useLegacyTrajectory
|
||||
) {
|
||||
this._useLegacyTrajectory = newBehaviorData.useLegacyTrajectory;
|
||||
}
|
||||
if (
|
||||
oldBehaviorData.canGoDownFromJumpthru !==
|
||||
newBehaviorData.canGoDownFromJumpthru
|
||||
) {
|
||||
this._canGoDownFromJumpthru = newBehaviorData.canGoDownFromJumpthru;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -197,6 +247,10 @@ namespace gdjs {
|
||||
|
||||
this._requestedDeltaX += this._updateSpeed(timeDelta);
|
||||
|
||||
if (this._leftKey !== this._rightKey) {
|
||||
this._lastDirectionIsLeft = this._leftKey;
|
||||
}
|
||||
|
||||
//0.2) Track changes in object size
|
||||
this._state.beforeUpdatingObstacles(timeDelta);
|
||||
this._onFloor._oldHeight = object.getHeight();
|
||||
@@ -235,15 +289,23 @@ namespace gdjs {
|
||||
this._checkTransitionOnFloorOrFalling();
|
||||
}
|
||||
|
||||
this._wasLeftKeyPressed = this._leftKey;
|
||||
this._wasRightKeyPressed = this._rightKey;
|
||||
this._wasLadderKeyPressed = this._ladderKey;
|
||||
this._wasUpKeyPressed = this._upKey;
|
||||
this._wasDownKeyPressed = this._downKey;
|
||||
this._wasJumpKeyPressed = this._jumpKey;
|
||||
this._wasReleasePlatformKeyPressed = this._releasePlatformKey;
|
||||
this._wasReleaseLadderKeyPressed = this._releaseLadderKey;
|
||||
//4) Do not forget to reset pressed keys
|
||||
this._leftKey = false;
|
||||
this._rightKey = false;
|
||||
this._ladderKey = false;
|
||||
this._releaseLadderKey = false;
|
||||
this._upKey = false;
|
||||
this._downKey = false;
|
||||
this._releasePlatformKey = false;
|
||||
this._jumpKey = false;
|
||||
this._releasePlatformKey = false;
|
||||
this._releaseLadderKey = false;
|
||||
|
||||
//5) Track the movement
|
||||
this._hasReallyMoved =
|
||||
@@ -255,6 +317,7 @@ namespace gdjs {
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
|
||||
private _updateSpeed(timeDelta: float): float {
|
||||
const previousSpeed = this._currentSpeed;
|
||||
//Change the speed according to the player's input.
|
||||
// @ts-ignore
|
||||
if (this._leftKey) {
|
||||
@@ -284,7 +347,8 @@ namespace gdjs {
|
||||
if (this._currentSpeed < -this._maxSpeed) {
|
||||
this._currentSpeed = -this._maxSpeed;
|
||||
}
|
||||
return this._currentSpeed * timeDelta;
|
||||
// Use Verlet integration.
|
||||
return ((this._currentSpeed + previousSpeed) * timeDelta) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,11 +471,12 @@ namespace gdjs {
|
||||
|
||||
_setFalling() {
|
||||
this._state.leave();
|
||||
const from = this._state;
|
||||
this._state = this._falling;
|
||||
this._falling.enter();
|
||||
this._falling.enter(from);
|
||||
}
|
||||
|
||||
_setOnFloor(collidingPlatform: PlatformRuntimeBehavior) {
|
||||
_setOnFloor(collidingPlatform: gdjs.PlatformRuntimeBehavior) {
|
||||
this._state.leave();
|
||||
this._state = this._onFloor;
|
||||
this._onFloor.enter(collidingPlatform);
|
||||
@@ -424,7 +489,9 @@ namespace gdjs {
|
||||
this._jumping.enter(from);
|
||||
}
|
||||
|
||||
private _setGrabbingPlatform(grabbedPlatform: PlatformRuntimeBehavior) {
|
||||
private _setGrabbingPlatform(
|
||||
grabbedPlatform: gdjs.PlatformRuntimeBehavior
|
||||
) {
|
||||
this._state.leave();
|
||||
this._state = this._grabbingPlatform;
|
||||
this._grabbingPlatform.enter(grabbedPlatform);
|
||||
@@ -454,11 +521,12 @@ namespace gdjs {
|
||||
let oldX = object.getX();
|
||||
object.setX(
|
||||
object.getX() +
|
||||
(this._requestedDeltaX > 0
|
||||
? this._xGrabTolerance
|
||||
: -this._xGrabTolerance)
|
||||
(this._requestedDeltaX < 0 ||
|
||||
(this._requestedDeltaX === 0 && this._lastDirectionIsLeft)
|
||||
? -this._xGrabTolerance
|
||||
: this._xGrabTolerance)
|
||||
);
|
||||
const collidingPlatforms: PlatformRuntimeBehavior[] = gdjs.staticArray(
|
||||
const collidingPlatforms: gdjs.PlatformRuntimeBehavior[] = gdjs.staticArray(
|
||||
PlatformerObjectRuntimeBehavior.prototype._checkGrabPlatform
|
||||
);
|
||||
collidingPlatforms.length = 0;
|
||||
@@ -528,15 +596,18 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
_fall(timeDelta: float) {
|
||||
const previousFallSpeed = this._currentFallSpeed;
|
||||
this._currentFallSpeed += this._gravity * timeDelta;
|
||||
if (this._currentFallSpeed > this._maxFallingSpeed) {
|
||||
this._currentFallSpeed = this._maxFallingSpeed;
|
||||
}
|
||||
this._requestedDeltaY += this._currentFallSpeed * timeDelta;
|
||||
this._requestedDeltaY = Math.min(
|
||||
this._requestedDeltaY,
|
||||
this._maxFallingSpeed * timeDelta
|
||||
);
|
||||
if (this._useLegacyTrajectory) {
|
||||
this._requestedDeltaY += this._currentFallSpeed * timeDelta;
|
||||
} else {
|
||||
// Use Verlet integration.
|
||||
this._requestedDeltaY +=
|
||||
((this._currentFallSpeed + previousFallSpeed) / 2) * timeDelta;
|
||||
}
|
||||
}
|
||||
|
||||
//Scene change is not supported
|
||||
@@ -558,10 +629,13 @@ namespace gdjs {
|
||||
const y1 = this.owner.getY() + this._yGrabOffset - this._lastDeltaY;
|
||||
const y2 = this.owner.getY() + this._yGrabOffset;
|
||||
const platformY = platform.owner.getY() + platform.getYGrabOffset();
|
||||
// This must be inclusive for at least one position.
|
||||
// Otherwise, if the character is at the exact position,
|
||||
// it could not be able to grab the platform at any frame.
|
||||
return (
|
||||
platform.canBeGrabbed() &&
|
||||
((y1 < platformY && platformY < y2) ||
|
||||
(y2 < platformY && platformY < y1))
|
||||
((y1 < platformY && platformY <= y2) ||
|
||||
(y2 <= platformY && platformY < y1))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -770,7 +844,12 @@ namespace gdjs {
|
||||
return context;
|
||||
}
|
||||
|
||||
for (const hitbox of platformObject.getHitBoxes()) {
|
||||
for (const hitbox of platformObject.getHitBoxesAround(
|
||||
context.ownerMinX,
|
||||
context.headMinY,
|
||||
context.ownerMaxX,
|
||||
context.floorMaxY
|
||||
)) {
|
||||
if (hitbox.vertices.length < 3) {
|
||||
continue;
|
||||
}
|
||||
@@ -960,8 +1039,10 @@ namespace gdjs {
|
||||
* Update _potentialCollidingObjects member with platforms near the object.
|
||||
*/
|
||||
private _updatePotentialCollidingObjects(maxMovementLength: float) {
|
||||
const object = this.owner;
|
||||
|
||||
this._manager.getAllPlatformsAround(
|
||||
this.owner,
|
||||
object,
|
||||
maxMovementLength,
|
||||
this._potentialCollidingObjects
|
||||
);
|
||||
@@ -970,7 +1051,7 @@ namespace gdjs {
|
||||
// is not considered as colliding with itself, in the case that it also has the
|
||||
// platform behavior.
|
||||
for (let i = 0; i < this._potentialCollidingObjects.length; ) {
|
||||
if (this._potentialCollidingObjects[i].owner === this.owner) {
|
||||
if (this._potentialCollidingObjects[i].owner === object) {
|
||||
this._potentialCollidingObjects.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
@@ -1002,6 +1083,38 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**.
|
||||
* @param input The control to be tested [Left,Right,Up,Down,Ladder,Jump,Release,Release Ladder].
|
||||
* @returns true if the key was used since the last `doStepPreEvents` call.
|
||||
*/
|
||||
isUsingControl(input: string): boolean {
|
||||
if (input === 'Left') {
|
||||
return this._wasLeftKeyPressed;
|
||||
}
|
||||
if (input === 'Right') {
|
||||
return this._wasRightKeyPressed;
|
||||
}
|
||||
if (input === 'Up') {
|
||||
return this._wasUpKeyPressed;
|
||||
}
|
||||
if (input === 'Down') {
|
||||
return this._wasDownKeyPressed;
|
||||
}
|
||||
if (input === 'Ladder') {
|
||||
return this._wasLadderKeyPressed;
|
||||
}
|
||||
if (input === 'Jump') {
|
||||
return this._wasJumpKeyPressed;
|
||||
}
|
||||
if (input === 'Release') {
|
||||
return this._wasReleasePlatformKeyPressed;
|
||||
}
|
||||
if (input === 'Release Ladder') {
|
||||
return this._wasReleaseLadderKeyPressed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gravity of the Platformer Object.
|
||||
* @returns The current gravity.
|
||||
@@ -1082,6 +1195,18 @@ namespace gdjs {
|
||||
return this._currentSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current speed of the Platformer Object.
|
||||
* @param currentSpeed The current speed.
|
||||
*/
|
||||
setCurrentSpeed(currentSpeed: float): void {
|
||||
this._currentSpeed = gdjs.evtTools.common.clamp(
|
||||
currentSpeed,
|
||||
-this._maxSpeed,
|
||||
this._maxSpeed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current jump speed of the Platformer Object.
|
||||
* @returns The current jump speed.
|
||||
@@ -1117,8 +1242,26 @@ namespace gdjs {
|
||||
/**
|
||||
* Set the maximum falling speed of the Platformer Object.
|
||||
* @param maxFallingSpeed The maximum falling speed.
|
||||
* @param tryToPreserveAirSpeed If true and if jumping, tune the current jump speed to preserve the overall speed in the air.
|
||||
*/
|
||||
setMaxFallingSpeed(maxFallingSpeed: float): void {
|
||||
setMaxFallingSpeed(
|
||||
maxFallingSpeed: float,
|
||||
tryToPreserveAirSpeed: boolean = false
|
||||
): void {
|
||||
if (tryToPreserveAirSpeed && this._state === this._jumping) {
|
||||
// If the falling speed is too high compared to the new max falling speed,
|
||||
// reduce it and adapt the jump speed to preserve the overall vertical speed.
|
||||
const fallingSpeedOverflow = this._currentFallSpeed - maxFallingSpeed;
|
||||
if (fallingSpeedOverflow > 0) {
|
||||
this._currentFallSpeed -= fallingSpeedOverflow;
|
||||
this._jumping.setCurrentJumpSpeed(
|
||||
Math.max(
|
||||
0,
|
||||
this._jumping.getCurrentJumpSpeed() - fallingSpeedOverflow
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
this._maxFallingSpeed = maxFallingSpeed;
|
||||
}
|
||||
|
||||
@@ -1215,6 +1358,33 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the current jump.
|
||||
*
|
||||
* When the character is not in the jumping state this method has no effect.
|
||||
*/
|
||||
abortJump(): void {
|
||||
if (this._state === this._jumping) {
|
||||
this._currentFallSpeed = 0;
|
||||
this._setFalling();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current fall speed.
|
||||
*
|
||||
* When the character is not in the falling state this method has no effect.
|
||||
*/
|
||||
setCurrentFallSpeed(currentFallSpeed: float) {
|
||||
if (this._state === this._falling) {
|
||||
this._currentFallSpeed = gdjs.evtTools.common.clamp(
|
||||
currentFallSpeed,
|
||||
0,
|
||||
this._maxFallingSpeed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the Platformer Object can grab platforms.
|
||||
* @param enable Enable / Disable grabbing of platforms.
|
||||
@@ -1420,7 +1590,7 @@ namespace gdjs {
|
||||
*/
|
||||
class OnFloor implements State {
|
||||
private _behavior: PlatformerObjectRuntimeBehavior;
|
||||
private _floorPlatform: PlatformRuntimeBehavior | null = null;
|
||||
private _floorPlatform: gdjs.PlatformRuntimeBehavior | null = null;
|
||||
private _floorLastX: float = 0;
|
||||
private _floorLastY: float = 0;
|
||||
_oldHeight: float = 0;
|
||||
@@ -1433,7 +1603,7 @@ namespace gdjs {
|
||||
return this._floorPlatform;
|
||||
}
|
||||
|
||||
enter(floorPlatform: PlatformRuntimeBehavior) {
|
||||
enter(floorPlatform: gdjs.PlatformRuntimeBehavior) {
|
||||
this._floorPlatform = floorPlatform;
|
||||
this.updateFloorPosition();
|
||||
this._behavior._canJump = true;
|
||||
@@ -1490,7 +1660,7 @@ namespace gdjs {
|
||||
|
||||
checkTransitionBeforeX() {
|
||||
const behavior = this._behavior;
|
||||
//Check that the floor object still exists and is near the object.
|
||||
// Check that the floor object still exists and is near the object.
|
||||
if (
|
||||
!behavior._isIn(
|
||||
behavior._potentialCollidingObjects,
|
||||
@@ -1498,6 +1668,14 @@ namespace gdjs {
|
||||
)
|
||||
) {
|
||||
behavior._setFalling();
|
||||
} else if (
|
||||
this._behavior._downKey &&
|
||||
this._floorPlatform!._platformType ===
|
||||
gdjs.PlatformRuntimeBehavior.JUMPTHRU &&
|
||||
behavior._canGoDownFromJumpthru
|
||||
) {
|
||||
behavior._overlappedJumpThru.push(this._floorPlatform!);
|
||||
behavior._setFalling();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1687,8 +1865,17 @@ namespace gdjs {
|
||||
this._behavior = behavior;
|
||||
}
|
||||
|
||||
enter() {
|
||||
this._behavior._canJump = false;
|
||||
enter(from: State) {
|
||||
// Only forbid jumping when starting to fall from a platform,
|
||||
// not when falling during a jump. This is because the Jumping
|
||||
// state has already set `_canJump` to false and we don't want to reset
|
||||
// it again because it could have been set back to `true` to allow
|
||||
// for an "air jump".
|
||||
// Transition from Falling to Falling state should not happen,
|
||||
// but don't change anything if this ever happen.
|
||||
if (from !== this._behavior._jumping && from !== this) {
|
||||
this._behavior._canJump = false;
|
||||
}
|
||||
}
|
||||
|
||||
leave() {}
|
||||
@@ -1701,13 +1888,16 @@ namespace gdjs {
|
||||
|
||||
checkTransitionBeforeY(timeDelta: float) {
|
||||
const behavior = this._behavior;
|
||||
//Go on a ladder
|
||||
// Go on a ladder
|
||||
behavior._checkTransitionOnLadder();
|
||||
//Jumping
|
||||
// Jumping
|
||||
behavior._checkTransitionJumping();
|
||||
|
||||
//Grabbing a platform
|
||||
if (behavior._canGrabPlatforms && behavior._requestedDeltaX !== 0) {
|
||||
// Grabbing a platform
|
||||
if (
|
||||
behavior._canGrabPlatforms &&
|
||||
(behavior._requestedDeltaX !== 0 || behavior._canGrabWithoutMoving)
|
||||
) {
|
||||
behavior._checkGrabPlatform();
|
||||
}
|
||||
}
|
||||
@@ -1741,6 +1931,10 @@ namespace gdjs {
|
||||
return this._currentJumpSpeed;
|
||||
}
|
||||
|
||||
setCurrentJumpSpeed(currentJumpSpeed: number) {
|
||||
this._currentJumpSpeed = currentJumpSpeed;
|
||||
}
|
||||
|
||||
enter(from: State) {
|
||||
const behavior = this._behavior;
|
||||
this._timeSinceCurrentJumpStart = 0;
|
||||
@@ -1767,15 +1961,15 @@ namespace gdjs {
|
||||
|
||||
checkTransitionBeforeY(timeDelta: float) {
|
||||
const behavior = this._behavior;
|
||||
//Go on a ladder
|
||||
// Go on a ladder
|
||||
behavior._checkTransitionOnLadder();
|
||||
//Jumping
|
||||
// Jumping
|
||||
behavior._checkTransitionJumping();
|
||||
|
||||
//Grabbing a platform
|
||||
// Grabbing a platform
|
||||
if (
|
||||
behavior._canGrabPlatforms &&
|
||||
behavior._requestedDeltaX !== 0 &&
|
||||
(behavior._requestedDeltaX !== 0 || behavior._canGrabWithoutMoving) &&
|
||||
behavior._lastDeltaY >= 0
|
||||
) {
|
||||
behavior._checkGrabPlatform();
|
||||
@@ -1785,20 +1979,14 @@ namespace gdjs {
|
||||
beforeMovingY(timeDelta: float, oldX: float) {
|
||||
const behavior = this._behavior;
|
||||
|
||||
//Fall
|
||||
if (!this._jumpingFirstDelta) {
|
||||
behavior._fall(timeDelta);
|
||||
}
|
||||
this._jumpingFirstDelta = false;
|
||||
|
||||
// Check if the jump key is continuously held since
|
||||
// the beginning of the jump.
|
||||
if (!behavior._jumpKey) {
|
||||
this._jumpKeyHeldSinceJumpStart = false;
|
||||
}
|
||||
this._timeSinceCurrentJumpStart += timeDelta;
|
||||
behavior._requestedDeltaY -= this._currentJumpSpeed * timeDelta;
|
||||
|
||||
const previousJumpSpeed = this._currentJumpSpeed;
|
||||
// Decrease jump speed after the (optional) jump sustain time is over.
|
||||
const sustainJumpSpeed =
|
||||
this._jumpKeyHeldSinceJumpStart &&
|
||||
@@ -1806,6 +1994,27 @@ namespace gdjs {
|
||||
if (!sustainJumpSpeed) {
|
||||
this._currentJumpSpeed -= behavior._gravity * timeDelta;
|
||||
}
|
||||
|
||||
if (this._behavior._useLegacyTrajectory) {
|
||||
behavior._requestedDeltaY -= previousJumpSpeed * timeDelta;
|
||||
|
||||
// Fall
|
||||
// The condition is a legacy thing.
|
||||
// There is no actual reason not to fall at 1st frame.
|
||||
// Before a refactoring, it used to not be this obvious.
|
||||
if (!this._jumpingFirstDelta) {
|
||||
behavior._fall(timeDelta);
|
||||
}
|
||||
} else {
|
||||
// Use Verlet integration.
|
||||
behavior._requestedDeltaY +=
|
||||
((-previousJumpSpeed - this._currentJumpSpeed) / 2) * timeDelta;
|
||||
|
||||
// Fall
|
||||
behavior._fall(timeDelta);
|
||||
}
|
||||
this._jumpingFirstDelta = false;
|
||||
|
||||
if (this._currentJumpSpeed < 0) {
|
||||
behavior._setFalling();
|
||||
}
|
||||
@@ -1829,7 +2038,7 @@ namespace gdjs {
|
||||
this._behavior = behavior;
|
||||
}
|
||||
|
||||
enter(grabbedPlatform: PlatformRuntimeBehavior) {
|
||||
enter(grabbedPlatform: gdjs.PlatformRuntimeBehavior) {
|
||||
this._grabbedPlatform = grabbedPlatform;
|
||||
this._behavior._canJump = true;
|
||||
this._behavior._currentFallSpeed = 0;
|
||||
|
@@ -4,6 +4,7 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
declare var rbush: any;
|
||||
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
|
||||
|
||||
/**
|
||||
* Manages the common objects shared by objects having a
|
||||
@@ -18,12 +19,7 @@ namespace gdjs {
|
||||
* @param object The object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
this._platformRBush = new rbush(9, [
|
||||
'.owner.getAABB().min[0]',
|
||||
'.owner.getAABB().min[1]',
|
||||
'.owner.getAABB().max[0]',
|
||||
'.owner.getAABB().max[1]',
|
||||
]);
|
||||
this._platformRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +42,13 @@ namespace gdjs {
|
||||
* Add a platform to the list of existing platforms.
|
||||
*/
|
||||
addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this._platformRBush.insert(platformBehavior);
|
||||
if (platformBehavior.currentRBushAABB)
|
||||
platformBehavior.currentRBushAABB.updateAABBFromOwner();
|
||||
else
|
||||
platformBehavior.currentRBushAABB = new gdjs.BehaviorRBushAABB(
|
||||
platformBehavior
|
||||
);
|
||||
this._platformRBush.insert(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +56,7 @@ namespace gdjs {
|
||||
* added before.
|
||||
*/
|
||||
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this._platformRBush.remove(platformBehavior);
|
||||
this._platformRBush.remove(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +67,7 @@ namespace gdjs {
|
||||
getAllPlatformsAround(
|
||||
object: gdjs.RuntimeObject,
|
||||
maxMovementLength: number,
|
||||
result: gdjs.PlatformRuntimeBehavior[]
|
||||
result: PlatformRuntimeBehavior[]
|
||||
): any {
|
||||
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
|
||||
// is not necessarily in the middle of the object (for sprites for example).
|
||||
@@ -73,20 +75,37 @@ namespace gdjs {
|
||||
const oh = object.getHeight();
|
||||
const x = object.getDrawableX() + object.getCenterX();
|
||||
const y = object.getDrawableY() + object.getCenterY();
|
||||
const searchArea = gdjs.staticObject(
|
||||
const searchArea: SearchArea = gdjs.staticObject(
|
||||
PlatformObjectsManager.prototype.getAllPlatformsAround
|
||||
);
|
||||
// @ts-ignore
|
||||
) as SearchArea;
|
||||
searchArea.minX = x - ow / 2 - maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.minY = y - oh / 2 - maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.maxX = x + ow / 2 + maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.maxY = y + oh / 2 + maxMovementLength;
|
||||
const nearbyPlatforms = this._platformRBush.search(searchArea);
|
||||
const nearbyPlatforms: gdjs.BehaviorRBushAABB<
|
||||
PlatformRuntimeBehavior
|
||||
>[] = this._platformRBush.search(searchArea);
|
||||
|
||||
result.length = 0;
|
||||
result.push.apply(result, nearbyPlatforms);
|
||||
|
||||
// Extra check on the platform owner AABB
|
||||
// TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need
|
||||
// for this extra check once merged.
|
||||
for (let i = 0; i < nearbyPlatforms.length; i++) {
|
||||
const platform = nearbyPlatforms[i].behavior;
|
||||
const platformAABB = platform.owner.getAABB();
|
||||
const platformIsStillAround =
|
||||
platformAABB.min[0] <= searchArea.maxX &&
|
||||
platformAABB.min[1] <= searchArea.maxY &&
|
||||
platformAABB.max[0] >= searchArea.minX &&
|
||||
platformAABB.max[1] >= searchArea.minY;
|
||||
// Filter platforms that are not in the searched area anymore.
|
||||
// This can happen because platforms are not updated in the RBush before that
|
||||
// characters movement are being processed.
|
||||
if (platformIsStillAround) {
|
||||
result.push(platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +124,10 @@ namespace gdjs {
|
||||
_oldY: float = 0;
|
||||
_oldWidth: float = 0;
|
||||
_oldHeight: float = 0;
|
||||
_oldAngle: float = 0;
|
||||
currentRBushAABB: gdjs.BehaviorRBushAABB<
|
||||
PlatformRuntimeBehavior
|
||||
> | null = null;
|
||||
_manager: gdjs.PlatformObjectsManager;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
@@ -173,7 +196,8 @@ namespace gdjs {
|
||||
this._oldX !== this.owner.getX() ||
|
||||
this._oldY !== this.owner.getY() ||
|
||||
this._oldWidth !== this.owner.getWidth() ||
|
||||
this._oldHeight !== this.owner.getHeight()
|
||||
this._oldHeight !== this.owner.getHeight() ||
|
||||
this._oldAngle !== this.owner.getAngle()
|
||||
) {
|
||||
if (this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
@@ -183,6 +207,7 @@ namespace gdjs {
|
||||
this._oldY = this.owner.getY();
|
||||
this._oldWidth = this.owner.getWidth();
|
||||
this._oldHeight = this.owner.getHeight();
|
||||
this._oldAngle = this.owner.getAngle();
|
||||
}
|
||||
}
|
||||
|
||||
|
783
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
783
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
@@ -0,0 +1,783 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
[0, 60].forEach((slopeMaxAngle) => {
|
||||
describe(`(walk on flat floors, slopeMaxAngle: ${slopeMaxAngle}°)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: slopeMaxAngle,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
const slopesDimensions = {
|
||||
26: { width: 50, height: 25 },
|
||||
45: { width: 50, height: 50 },
|
||||
};
|
||||
|
||||
it('can walk from a platform to another one', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to a jump through', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
|
||||
it('can walk on a platform and go through a jump through', function () {
|
||||
// Jumpthru that are ignored had a side effects on the search context.
|
||||
// It made jumpthru appear solid when a platform was tested after them.
|
||||
|
||||
// Add the jumptru 1st to make RBrush gives it 1st.
|
||||
// There is no causality but it does in the current implementation.
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(30, -15);
|
||||
jumpThroughPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
object.setPosition(10, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(20);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to another one that not aligned', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// the 2nd platform is 1 pixel higher
|
||||
platform.getY() - 1
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to another one with a speed under 1 pixel/second', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The 2nd platform is 1 pixels higher.
|
||||
platform.getY() - 1
|
||||
);
|
||||
// Put the object just to the left of platform2 so that
|
||||
// it try climbing on it with a very small speed.
|
||||
object.setPosition(platform2.getX() - object.getWidth(), -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it("can't walk from a platform to another one that is a bit too high", function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The 2nd platform is 2 pixels higher.
|
||||
platform.getY() - 2
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the 2nd platform
|
||||
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk on a platform and be blocked by a wall', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
// the 2nd platform is 2 pixels higher
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The platform's top is over the object
|
||||
// and platform's bottom is under the object.
|
||||
platform.getY() - platform2.getHeight() + 5
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the 2nd platform
|
||||
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform and fell through a jump through that is at the right but 1 pixel higher', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// Even 1 pixel is too high to follow a jump through
|
||||
// because it's like it's gone through its right or left side.
|
||||
platform.getY() - 1
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// Fall under the jump through platform
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
}
|
||||
expect(object.getY()).to.be.above(platform.getY());
|
||||
});
|
||||
|
||||
it('can walk inside a tunnel platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addTunnelPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, 160);
|
||||
// The object falls on the bottom part of the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(200 - object.getHeight());
|
||||
|
||||
// The object walk on the bottom part of the platform.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(60);
|
||||
expect(object.getY()).to.be(200 - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
// less than 1 pixel per frame (50/60)
|
||||
50,
|
||||
// a commonly used value
|
||||
1500,
|
||||
].forEach((maxFallingSpeed) => {
|
||||
describe(`(on floor, maxFallingSpeed=${
|
||||
maxFallingSpeed / 60
|
||||
} pixels per frame)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object in the air.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -100);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
// TODO The character falls one frame then land instead of staying on the platform.
|
||||
it.skip('must not move when on the floor at startup', function () {
|
||||
object.setPosition(0, platform.getY() - object.getHeight());
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check the platformer object stays still.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('must not move when put on a platform while falling', function () {
|
||||
object.setPosition(0, platform.getY() - object.getHeight() - 300);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
object.setPosition(0, platform.getY() - object.getHeight());
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check the platformer object stays still.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('can track object height changes', function () {
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + 10,
|
||||
platform.getY() - object.getHeight() + 1
|
||||
);
|
||||
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getX()).to.be(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Make the platform under the character feet smaller.
|
||||
object.setCustomWidthAndHeight(object.getWidth(), 9);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
// The character follows it.
|
||||
expect(object.getY()).to.be(-19); // -19 = -10 (platform y) + -9 (object height)
|
||||
|
||||
// The character walks on the platform.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be(-19);
|
||||
expect(object.getX()).to.be.above(16);
|
||||
|
||||
// Make the platform under the character feet bigger.
|
||||
object.setCustomWidthAndHeight(object.getWidth(), 20);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// The character follows it.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
});
|
||||
|
||||
it('can track platform angle changes', function () {
|
||||
// The initial pltaforms AABB are put in RBush.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Now change the angle to check that the AABB is updated in RBush.
|
||||
platform.setAngle(90);
|
||||
|
||||
// Put the character above the rotated platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() / 2,
|
||||
platform.getY() +
|
||||
(platform.getHeight() - platform.getWidth()) / 2 -
|
||||
object.getHeight() -
|
||||
10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// The character should land on it.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getX()).to.be(30);
|
||||
expect(object.getY()).to.be(-44);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`(walk on flat floors with custom hitbox)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 25, y: 25 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 25, y: 25 },
|
||||
{ x: 75, y: 25 },
|
||||
{ x: 75, y: 75 },
|
||||
{ x: 25, y: 75 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
object.setUnscaledWidthAndHeight(100, 100);
|
||||
object.setCustomWidthAndHeight(20, 40);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
// The actual hitbox is 10x20.
|
||||
const objectWidth = 10;
|
||||
const objectHeight = 20;
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can walk on a platform and be blocked by a wall', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
// the 2nd platform is 2 pixels higher
|
||||
const wall = addPlatformObject(runtimeScene);
|
||||
wall.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The platform is top is over the object
|
||||
// and platform is bottom is under the object.
|
||||
platform.getY() - wall.getHeight() + 5
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 25; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the wall
|
||||
expect(object.getX()).to.be(wall.getX() - objectWidth);
|
||||
expect(object.getY()).to.be(platform.getY() - objectHeight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Floating-point error mitigations', function () {
|
||||
it('Specific coordinates with slopeMaxAngle=0 creating Y oscillations and drift on a moving floor', function () {
|
||||
const runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Create a Sprite object that has the origin at a specific position (see below)
|
||||
// and that has a slope max angle of 0 (so it can't climb on a floor even if it's a bit higher
|
||||
// than the bottom of the object).
|
||||
const object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1300,
|
||||
maxFallingSpeed: 1000,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 280,
|
||||
jumpSpeed: 750,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 0,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 5, y: 19 },
|
||||
centerPoint: { x: 5, y: 46 },
|
||||
points: [
|
||||
{ name: 'Center', x: 5, y: 46 },
|
||||
{ name: 'Origin', x: 5, y: 19 },
|
||||
],
|
||||
hasCustomCollisionMask: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Set the size of the object so that it results in a specific
|
||||
// Y position for the bottom of the object AABB:
|
||||
object.setUnscaledWidthAndHeight(10, 92);
|
||||
object.setCustomWidthAndHeight(10, 66.0008);
|
||||
// Origin Y is originally 19.
|
||||
// After the scaling, it is now 19*66.0008/92=13.6306.
|
||||
|
||||
// Set the Y position so that the object falls at a Y position on the floor
|
||||
// that would generate oscillations.
|
||||
object.setPosition(0, 139.3118);
|
||||
runtimeScene.addObject(object);
|
||||
|
||||
// Put a platform at a specific Y that can cause oscillations.
|
||||
const platform = addJumpThroughPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, 193.000000000001);
|
||||
// This means that the exact Y position the object should take is:
|
||||
// platform Y - height + origin Y = 193.000000000001-66.0008+13.6306 = 140.6298
|
||||
|
||||
// Wait for the object to fall on the floor
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
|
||||
// Ensure it is on the floor
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
// The Y position won't be exact because of floating point errors.
|
||||
// expect(object.getY()).to.be(140.6298)
|
||||
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
|
||||
|
||||
// Move the platform by 6 pixels to the right.
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Ensure the object followed the platform on the X axis.
|
||||
// If the floating point errors caused oscillations between two Y positions,
|
||||
// it won't work because the object will get repositioned back to its old X position
|
||||
// whenever the floor is considered "too high" for the object to reach.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
|
||||
expect(object.getX()).to.be(6);
|
||||
});
|
||||
});
|
||||
|
||||
[20, 30, 60, 120].forEach((framesPerSecond) => {
|
||||
describe(`(FPS independent trajectory: ${framesPerSecond} fps)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene(1000 / framesPerSecond);
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can walk', function () {
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
platform.setCustomWidthAndHeight(600, 32);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Accelerate
|
||||
for (let i = 0; i < framesPerSecond; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Reached the maximum speed
|
||||
expect(object.getX()).to.be.within(250 - epsilon, 250 + epsilon);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be.within(
|
||||
500 - epsilon,
|
||||
500 + epsilon
|
||||
);
|
||||
|
||||
// Decelerate
|
||||
for (let i = 0; i < framesPerSecond / 3; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Stopped
|
||||
expect(object.getX()).to.be.within(333, 334);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
1516
Extensions/PlatformBehavior/tests/JumpAndFallingPlatformer.spec.js
Normal file
1516
Extensions/PlatformBehavior/tests/JumpAndFallingPlatformer.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,761 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
[true, false].forEach((canGrabWithoutMoving) => {
|
||||
describe(`(grab platforms, canGrabWithoutMoving: ${canGrabWithoutMoving})`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object in the air.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
canGrabWithoutMoving: canGrabWithoutMoving,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -100);
|
||||
});
|
||||
|
||||
it('can grab and release the right ledge of a platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// The character grabs the platform.
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() + platform.getWidth() + 0,
|
||||
platform.getX() + platform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// The character releases the platform.
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
// The character falls.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be.above(0);
|
||||
});
|
||||
|
||||
it('can grab and release the left ledge of a platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() - object.getWidth() - 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// The character grabs the platform.
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() - object.getWidth() - 1,
|
||||
platform.getX() - object.getWidth() - 0
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// The character releases the platform.
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
// The character falls.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be.above(0);
|
||||
});
|
||||
|
||||
[true, false].forEach((addTopPlatformFirst) => {
|
||||
it('can grab every platform when colliding 2', function () {
|
||||
// The 2 platforms will be simultaneously in collision
|
||||
// with the object when it grabs one.
|
||||
let upperPlatform, lowerPlatform;
|
||||
if (addTopPlatformFirst) {
|
||||
upperPlatform = addPlatformObject(runtimeScene);
|
||||
upperPlatform.setPosition(0, -10);
|
||||
upperPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
lowerPlatform = addPlatformObject(runtimeScene);
|
||||
lowerPlatform.setPosition(0, 0);
|
||||
lowerPlatform.setCustomWidthAndHeight(60, 10);
|
||||
} else {
|
||||
lowerPlatform = addPlatformObject(runtimeScene);
|
||||
lowerPlatform.setPosition(0, 0);
|
||||
lowerPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
upperPlatform = addPlatformObject(runtimeScene);
|
||||
upperPlatform.setPosition(0, -10);
|
||||
upperPlatform.setCustomWidthAndHeight(60, 10);
|
||||
}
|
||||
|
||||
// Put the object near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 2,
|
||||
upperPlatform.getY() - 10
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that the object grabbed the upper platform
|
||||
expect(object.getX()).to.be.within(
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 0,
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(upperPlatform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// Release upper platform
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that the object grabbed the lower platform
|
||||
expect(object.getX()).to.be.within(
|
||||
lowerPlatform.getX() + lowerPlatform.getWidth() + 0,
|
||||
lowerPlatform.getX() + lowerPlatform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(lowerPlatform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('can grab a platform and jump', function () {
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
//Put the object near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check that the object grabbed the platform
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() + platform.getWidth() + 0,
|
||||
platform.getX() + platform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
//Check that the object is jumping
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
}
|
||||
expect(object.getY()).to.be.below(platform.getY());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('(grab platforms, canGrabWithoutMoving: true)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object in the air.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
canGrabWithoutMoving: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -100);
|
||||
});
|
||||
|
||||
it('can grab without moving and release the right ledge of a platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
// The character faces the platform
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// The character grabs the platform.
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() + platform.getWidth() + 0,
|
||||
platform.getX() + platform.getWidth() + 2
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// The character releases the platform.
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
// The character falls.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be.above(0);
|
||||
});
|
||||
|
||||
it('can grab without moving and release the left ledge of a platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the left ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() - object.getWidth() - 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
// The character faces the platform
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// The character grabs the platform.
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() - object.getWidth() - 2,
|
||||
platform.getX() - object.getWidth() - 0
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// The character releases the platform.
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
// The character falls.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be.above(0);
|
||||
});
|
||||
|
||||
it('must not grab automatically the right ledge of a platform when facing back', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
// The character faces the wrong way
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// The character couldn't grab the platform.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
expect(object.getY()).to.be.greaterThan(platform.getY() + 5);
|
||||
});
|
||||
|
||||
it('must not grab automatically the left ledge of a platform when facing back', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Put the character near the left ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() - object.getWidth() - 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
// The character faces the wrong way
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// The character couldn't grab the platform.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
expect(object.getY()).to.be.greaterThan(platform.getY() + 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(ladder)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
var scale;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
ladder = addLadderObject(runtimeScene);
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
});
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const climbLadder = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.below(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const releaseLadder = (frameCount) => {
|
||||
object.getBehavior('auto1').simulateReleaseLadderKey();
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const stayOnLadder = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
expect(object.getY()).to.be(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const jumpAndAscend = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.below(lastY);
|
||||
}
|
||||
};
|
||||
const jumpAndDescend = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can climb and release a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
const objectPositionAfterFirstClimb = object.getY();
|
||||
releaseLadder(10);
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
expect(object.getY()).to.be.within(
|
||||
// gravity is 1500, 10 frames falling ~ 21px
|
||||
objectPositionAfterFirstClimb + 20,
|
||||
objectPositionAfterFirstClimb + 21
|
||||
);
|
||||
climbLadder(23);
|
||||
// Check that we reached the maximum height
|
||||
const playerAtLadderTop = ladder.getY() - object.getHeight();
|
||||
expect(object.getY()).to.be.within(
|
||||
playerAtLadderTop - 3,
|
||||
playerAtLadderTop
|
||||
);
|
||||
|
||||
// The player goes a little over the ladder...
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
// ...and it falls even if up is pressed
|
||||
for (let i = 0; i < 13; ++i) {
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
fall(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('can jump and grab a ladder even on the ascending phase of a jump the 1st time', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.below(-30);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
|
||||
// Grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
stayOnLadder(10);
|
||||
climbLadder(2);
|
||||
});
|
||||
|
||||
it('can grab a ladder while on the descending phase of a jump', function () {
|
||||
// Need a bigger ladder
|
||||
ladder.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 18; ++i) {
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
|
||||
// starting to going down
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(1);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
|
||||
stayOnLadder(10);
|
||||
climbLadder(2);
|
||||
});
|
||||
|
||||
it('can jump from ladder to ladder', function () {
|
||||
// Need a bigger ladder
|
||||
ladder.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
|
||||
const ladder2 = addLadderObject(runtimeScene);
|
||||
ladder2.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder2.setPosition(ladder.getX() + ladder.getWidth(), ladder.getY());
|
||||
|
||||
object.setPosition(35, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
|
||||
// 1st time grabbing this ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(1);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
|
||||
// Jump right
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
// leave the 1st ladder
|
||||
expect(object.getX()).to.be.above(ladder2.getX());
|
||||
// and grab the 2nd one, even if still ascending
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
// still moves a little because of inertia
|
||||
for (let i = 0; i < 3; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
stayOnLadder(1);
|
||||
});
|
||||
|
||||
it('can fall from a ladder right side', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
|
||||
// Fall to the ladder right
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
// Here, if we had pressed Right the character would have been falling.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
// Now, it falls.
|
||||
fall(5);
|
||||
});
|
||||
|
||||
it('can walk from a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
|
||||
// Going from the ladder to the right
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
// and directly on the floor
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
});
|
||||
|
||||
it('can jump from a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
|
||||
// Jump from the ladder
|
||||
const stayY = object.getY();
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.below(stayY);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('can grab a ladder when falling', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(24);
|
||||
// Check that we reached the maximum height
|
||||
// The player goes a little over the ladder...
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
fall(10);
|
||||
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
climbLadder(5);
|
||||
});
|
||||
|
||||
it('should not grab a platform when grabbed to a ladder', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(ladder.getX() + ladder.getWidth(), -50);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY()
|
||||
);
|
||||
// Grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// The object is where it could grab the top platform if it where falling.
|
||||
expect(object.getX()).to.be.within(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getX() - object.getWidth() + 2
|
||||
);
|
||||
expect(object.getY()).to.be(topPlatform.getY());
|
||||
// Check that the object didn't grabbed the platform
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
|
||||
stayOnLadder(10);
|
||||
});
|
||||
|
||||
it('can grab a ladder when grabbed to a platform', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(ladder.getX() + ladder.getWidth(), -50);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Fall and Grab the platform
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY() - 10
|
||||
);
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
fall(1);
|
||||
}
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// try to grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,400 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
|
||||
describe('(moving platforms)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
const maxSpeed = 500;
|
||||
const maxFallingSpeed = 1500;
|
||||
const timeDelta = 1 / 60;
|
||||
const maxDeltaX = maxSpeed * timeDelta;
|
||||
const maxDeltaY = maxFallingSpeed * timeDelta;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: maxSpeed,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -40);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
it('follows a platform moving less than one pixel', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(0.36);
|
||||
});
|
||||
|
||||
it('falls from a platform moving down faster than the maximum falling speed', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object falls
|
||||
// +1 because it's the margin to check the floor
|
||||
platform.setY(platform.getY() + maxDeltaY + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(-30);
|
||||
});
|
||||
|
||||
// This test doesn't pass because the platform AABB are not always updated
|
||||
// before the platformer object moves.
|
||||
//
|
||||
// When the character is put on top of the platform to follow it up,
|
||||
// the platform AABB may not has updated in RBush
|
||||
// and the platform became out of the spacial search rectangle.
|
||||
it.skip('follows a platform that is slightly overlapping its top', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// the platform is slightly overlapping the top of the object
|
||||
platform.setY(object.getY() - platform.getHeight() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check that the object stays on the floor
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('must not follow a platform that is moved over its top', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// move the platform over the object
|
||||
platform.setY(object.getY() - platform.getHeight());
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// A second step to make sure that the AABB is updated in RBush.
|
||||
// TODO this is a bug
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check that the object falls
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getY()).to.be.above(-30);
|
||||
});
|
||||
|
||||
it('follows a moving platform when was grabbed to another', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(platform.getX() + 30, -50);
|
||||
|
||||
// Fall and Grab the platform
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// move the bottom platform to the object
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the platform reach the object
|
||||
expect(platform.getY()).to.be(object.getY() + object.getHeight());
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the object follows it and no longer grab the other platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
|
||||
// This may be a bug. Please, remove the skip if you fixed it.
|
||||
// It fails on the last 2 expect()
|
||||
it.skip('follows a moving platform when was grabbed to a ladder', function () {
|
||||
// object is 10 pixel higher than the platform and overlap the ladder
|
||||
object.setPosition(0, platform.getY() - object.getHeight() - 10);
|
||||
const ladder = addLadderObject(runtimeScene);
|
||||
ladder.setPosition(object.getX(), platform.getY() - ladder.getHeight());
|
||||
|
||||
// Fall and Grab the platform
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
|
||||
// move the bottom platform to the object
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
// the platform reach the object
|
||||
expect(platform.getY()).to.be(object.getY() + object.getHeight());
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the object follows it and no longer grab the other platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
});
|
||||
|
||||
[-10, -10.1, -9.9].forEach((platformY) => {
|
||||
[
|
||||
-maxDeltaY + epsilon,
|
||||
maxDeltaY - epsilon,
|
||||
-10,
|
||||
10,
|
||||
-10.1,
|
||||
10.1,
|
||||
0,
|
||||
].forEach((deltaY) => {
|
||||
[-maxDeltaX, maxDeltaX, 0].forEach((deltaX) => {
|
||||
it(`follows the platform moving (${deltaX}; ${deltaY}) with initial Y = ${platformY}`, function () {
|
||||
platform.setPosition(platform.getX(), platformY);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object landed right on the platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setPosition(
|
||||
platform.getX() + deltaX,
|
||||
platform.getY() + deltaY
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
// The object follow the platform
|
||||
// The rounding error is probably due to a separate call.
|
||||
// TODO Try to make it exact or find why
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
}
|
||||
expect(object.getX()).to.be(0 + 5 * deltaX);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[false, true].forEach((useJumpthru) => {
|
||||
describe(`(${
|
||||
useJumpthru ? 'useJumpthru' : 'regular'
|
||||
} moving platforms)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
const maxSpeed = 500;
|
||||
const maxFallingSpeed = 1500;
|
||||
const timeDelta = 1 / 60;
|
||||
const maxDeltaX = maxSpeed * timeDelta;
|
||||
const maxDeltaY = maxFallingSpeed * timeDelta;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: maxSpeed,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -40);
|
||||
|
||||
// Put a platform.
|
||||
if (useJumpthru) {
|
||||
platform = addJumpThroughPlatformObject(runtimeScene);
|
||||
} else {
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
}
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
// This test doesn't pass with jumpthru
|
||||
// because jumpthru that overlap the object are excluded from collision.
|
||||
// The probability it happens is: platform speed / falling speed.
|
||||
// We could use the Y speed to be more permissive about it:
|
||||
// If the previous position according to the speed is above the platform,
|
||||
// we could let it land.
|
||||
it.skip('can land to a platform that moved up and overlapped the object', function () {
|
||||
// Put the platform away so it won't collide with the falling object
|
||||
platform.setPosition(platform.getX(), 200);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const oldY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Put the platform under the falling object and overlap it a little
|
||||
// like a platform moving quickly can do
|
||||
platform.setPosition(
|
||||
platform.getX(),
|
||||
object.getY() + object.getHeight() - 2
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Check the object has landed on the platform.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
});
|
||||
|
||||
[-10, -10.1, -9.9].forEach((platformY) => {
|
||||
[
|
||||
-maxDeltaY + epsilon,
|
||||
maxDeltaY - epsilon,
|
||||
-10,
|
||||
10,
|
||||
-10.1,
|
||||
10.1,
|
||||
0,
|
||||
].forEach((deltaY) => {
|
||||
[-maxDeltaX, maxDeltaX, 0].forEach((deltaX) => {
|
||||
it(`follows the platform moving (${deltaX}; ${deltaY}) with initial Y = ${platformY}`, function () {
|
||||
platform.setPosition(platform.getX(), platformY);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setPosition(
|
||||
platform.getX() + deltaX,
|
||||
platform.getY() + deltaY
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
}
|
||||
expect(object.getX()).to.be(0 + 5 * deltaX);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
229
Extensions/PlatformBehavior/tests/PlatformerTestHelper.js
Normal file
229
Extensions/PlatformBehavior/tests/PlatformerTestHelper.js
Normal file
@@ -0,0 +1,229 @@
|
||||
|
||||
const makePlatformerTestRuntimeScene = (timeDelta = 1000 / 60) => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
resources: {
|
||||
resources: [],
|
||||
},
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return timeDelta;
|
||||
};
|
||||
return runtimeScene;
|
||||
};
|
||||
|
||||
const addPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
platform.setCustomWidthAndHeight(60, 32);
|
||||
runtimeScene.addObject(platform);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addUpSlopePlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 100);
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addDownSlopePlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 0, y: 0 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 100);
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addTunnelPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 0, y: 200 },
|
||||
{ x: 0, y: 300 },
|
||||
{ x: 100, y: 300 },
|
||||
{ x: 100, y: 200 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 300);
|
||||
platform.setCustomWidthAndHeight(100, 300);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addJumpThroughPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
platformType: 'Jumpthru',
|
||||
canBeGrabbed: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
platform.setCustomWidthAndHeight(60, 32);
|
||||
runtimeScene.addObject(platform);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addLadderObject = (runtimeScene) => {
|
||||
const ladder = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj3',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: false,
|
||||
platformType: 'Ladder',
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
ladder.setCustomWidthAndHeight(20, 60);
|
||||
runtimeScene.addObject(ladder);
|
||||
|
||||
return ladder;
|
||||
};
|
740
Extensions/PlatformBehavior/tests/SlopePlatformer.spec.js
Normal file
740
Extensions/PlatformBehavior/tests/SlopePlatformer.spec.js
Normal file
@@ -0,0 +1,740 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
|
||||
describe('(walk on slopes)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRightCanStop = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.not.be.below(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
const walkLeft = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.below(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.below(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
const slopesDimensions = {
|
||||
26: { width: 50, height: 25 },
|
||||
45: { width: 50, height: 50 },
|
||||
};
|
||||
|
||||
it('can walk from a platform to another one that is rotated', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
|
||||
const angle = (-30 * Math.PI) / 180;
|
||||
const centerDeltaX = platform2.getWidth() / 2;
|
||||
const centerDeltaY = platform2.getHeight() / 2;
|
||||
// to make the vertex of the 2 platform touch
|
||||
const vertexDeltaX =
|
||||
centerDeltaX * Math.cos(angle) +
|
||||
centerDeltaY * -Math.sin(angle) -
|
||||
centerDeltaX;
|
||||
const vertexDeltaY =
|
||||
centerDeltaX * Math.sin(angle) +
|
||||
centerDeltaY * Math.cos(angle) -
|
||||
centerDeltaY;
|
||||
|
||||
platform2.setAngle(-30);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth() + vertexDeltaX,
|
||||
platform.getY() + vertexDeltaY
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY());
|
||||
});
|
||||
|
||||
[26, 45].forEach((slopeAngle) => {
|
||||
it(`can go uphill from a 0° slope to a ${slopeAngle}° slope going right`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
// This is a mirror of the previous test.
|
||||
it(`can go uphill from a 0° slope to a ${slopeAngle}° slope going left`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(50, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() - slope.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(90, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkLeft(30);
|
||||
expect(object.getX()).to.be.below(platform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go uphill from a ${slopeAngle}° slope to a 0° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(slope.getX() + slope.getWidth(), slope.getY());
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go uphill from a ${slopeAngle}° slope to a 0° jump through platform`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setCustomWidthAndHeight(50, 50);
|
||||
jumpThroughPlatform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
|
||||
[
|
||||
[26, 45],
|
||||
[45, 26],
|
||||
[26, 26],
|
||||
[45, 45],
|
||||
].forEach((slopeAngles) => {
|
||||
it(`can go uphill from a ${slopeAngles[0]}° slope to a ${slopeAngles[1]}° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope1 = addUpSlopePlatformObject(runtimeScene);
|
||||
slope1.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[0]].width,
|
||||
slopesDimensions[slopeAngles[0]].height
|
||||
);
|
||||
slope1.setPosition(0, 0);
|
||||
|
||||
const slope2 = addUpSlopePlatformObject(runtimeScene);
|
||||
slope2.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[1]].width,
|
||||
slopesDimensions[slopeAngles[1]].height
|
||||
);
|
||||
slope2.setPosition(
|
||||
slope1.getX() + slope1.getWidth(),
|
||||
slope1.getY() - slope2.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope2.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(slope1.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
// TODO
|
||||
it.skip(`can go uphill from a 26° slope and be stopped by an obstacle on the head`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(100, 50);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const ceiling = addPlatformObject(runtimeScene);
|
||||
ceiling.setCustomWidthAndHeight(50, 50);
|
||||
ceiling.setPosition(
|
||||
50,
|
||||
slope.getY() - ceiling.getHeight() - object.getHeight() / 2
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk the slope and reach the ceiling.
|
||||
// It checks that the character never go left.
|
||||
walkRightCanStop(40);
|
||||
expect(object.getY()).to.be(ceiling.getY() + ceiling.getHeight());
|
||||
});
|
||||
|
||||
[26, 45].forEach((slopeAngle) => {
|
||||
it(`can go downhill from a 0° slope to a ${slopeAngle}° slope`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
expect(object.getY()).to.be.above(slope.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go downhill from a ${slopeAngle}° slope to a 0° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY() + slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
// The floor detection can't round it to 30
|
||||
// because the character bottom is 50 with rounding error
|
||||
// 29.999999999999996 + 20 = 50
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
[26, 45],
|
||||
[45, 26],
|
||||
[26, 26],
|
||||
[45, 45],
|
||||
].forEach((slopeAngles) => {
|
||||
it(`can go downhill from a ${slopeAngles[0]}° slope to a ${slopeAngles[1]}° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope1 = addDownSlopePlatformObject(runtimeScene);
|
||||
slope1.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[0]].width,
|
||||
slopesDimensions[slopeAngles[0]].height
|
||||
);
|
||||
slope1.setPosition(0, 0);
|
||||
|
||||
const slope2 = addDownSlopePlatformObject(runtimeScene);
|
||||
slope2.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[1]].width,
|
||||
slopesDimensions[slopeAngles[1]].height
|
||||
);
|
||||
slope2.setPosition(
|
||||
slope1.getX() + slope1.getWidth(),
|
||||
slope1.getY() + slope1.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(11);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope2.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
expect(object.getY()).to.be.above(slope2.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('(walk on slopes very fast)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 100000,
|
||||
deceleration: 1500,
|
||||
// It will move more than 1 width every frame
|
||||
maxSpeed: 1000, // fps * width = 60 * 10 = 600 plus a big margin
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.not.be.below(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
// TODO When the object is moving fast, sharp platform vertices can be missed.
|
||||
// Fixing this is would require to rethink how the floor is followed.
|
||||
// But, this might be an extreme enough case to don't care:
|
||||
// On a 800 width screen, a 32 width character would go through one screen in 400ms.
|
||||
// 800 / 32 / 60 = 0.416
|
||||
it.skip(`can go uphill from a 45° slope to a 0° jump through platform`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
jumpThroughPlatform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(6);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[0, 25].forEach((slopeMaxAngle) => {
|
||||
describe(`(walk on slopes, slopeMaxAngle: ${slopeMaxAngle}°)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: slopeMaxAngle,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
const walkRightCanStop = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.not.be.below(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
const walkLeftCanStop = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.not.be.above(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
(slopeMaxAngle === 0
|
||||
? [
|
||||
{ angle: 5.7, height: 5 },
|
||||
{ angle: 26, height: 25 },
|
||||
]
|
||||
: // slopeMaxAngle === 25
|
||||
[{ angle: 26, height: 25 }]
|
||||
).forEach((slopesDimension) => {
|
||||
it(`can't go uphill on a too steep slope (${slopesDimension.angle}°)`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, -10);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(20);
|
||||
const fallX = object.getX();
|
||||
const fallY = object.getY();
|
||||
|
||||
// Stay still when Right is pressed
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.within(
|
||||
fallX - epsilon,
|
||||
fallX + epsilon
|
||||
);
|
||||
expect(object.getY()).to.be.within(
|
||||
fallY - epsilon,
|
||||
fallY + epsilon
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it(`can go downhill on a too steep slope (${slopesDimension.angle}°)`, function () {
|
||||
// Put a platform.
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, -60);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(20);
|
||||
const fallX = object.getX();
|
||||
const fallY = object.getY();
|
||||
|
||||
// Fall and land on the platform in loop when Right is pressed
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastY = object.getY();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(
|
||||
behavior.isOnFloor() || behavior.isFallingWithoutJumping()
|
||||
).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
// The log of the character positions moving to the right
|
||||
// without any obstacle:
|
||||
// LOG: 'OnFloor 35.13888888888889 -20'
|
||||
// LOG: 'OnFloor 38.333333333333336 -20'
|
||||
// LOG: 'OnFloor 41.66666666666667 -20'
|
||||
// The character is 10 width, at 38.33 is left is 48.33
|
||||
[
|
||||
// remainingDeltaX === 1.333
|
||||
47,
|
||||
// remainingDeltaX === 0.833
|
||||
47.5,
|
||||
// remainingDeltaX === 0.333
|
||||
48,
|
||||
// remainingDeltaX is big
|
||||
49,
|
||||
// Platform tiles will result to pixel aligned junctions.
|
||||
// A rotated platform will probably result to not pixel aligned junctions.
|
||||
48.9,
|
||||
].forEach((slopeJunctionX) => {
|
||||
it(`(slopeJunctionX: ${slopeJunctionX}) can't go uphill from a 0° slope to a too steep slope (${slopesDimension.angle}°) going right`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(slopeJunctionX, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk toward the 2nd platform.
|
||||
walkRightCanStop(30);
|
||||
// Is stopped at the slope junction.
|
||||
expect(object.getX()).to.be.within(
|
||||
Math.floor(slope.getX()) - object.getWidth(),
|
||||
// When the junction is not pixel aligned, the character will be stopped
|
||||
// but is able to move forward until it reaches the obstacle.
|
||||
slope.getX() - object.getWidth()
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
// The log of the character positions moving to the left
|
||||
// without any obstacle:
|
||||
// LOG: 'OnFloor 54.861111111111114 -20'
|
||||
// LOG: 'OnFloor 51.66666666666667 -20'
|
||||
// LOG: 'OnFloor 48.333333333333336 -20'
|
||||
// This is a mirror of the previous case: x -> 100 - x
|
||||
[
|
||||
// remainingDeltaX === -1.333
|
||||
53,
|
||||
// remainingDeltaX === -0.833
|
||||
52.5,
|
||||
// remainingDeltaX === -0.333
|
||||
52,
|
||||
// remainingDeltaX is big
|
||||
51,
|
||||
// Platform tiles will result to pixel aligned junctions.
|
||||
// A rotated platform will probably result to not pixel aligned junctions.
|
||||
51.1,
|
||||
].forEach((slopeJunctionX) => {
|
||||
it(`(slopeJunctionX: ${slopeJunctionX}) can't go uphill from a 0° slope to a too steep slope (${slopesDimension.angle}°) going left`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(100 - slopeJunctionX, 50);
|
||||
platform.setPosition(slopeJunctionX, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(
|
||||
slopeJunctionX - slope.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(90, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk toward the 2nd platform.
|
||||
walkLeftCanStop(30);
|
||||
// Is stopped at the slope junction.
|
||||
expect(object.getX()).to.be.within(
|
||||
// When the junction is not pixel aligned, the character will be stopped
|
||||
// but is able to move forward until it reaches the obstacle.
|
||||
platform.getX(),
|
||||
Math.ceil(platform.getX())
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -136,9 +136,6 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
expect(characterBehavior.owner.getY()).to.be(oldY);
|
||||
};
|
||||
|
||||
const noCollision = gdjs.PlatformerObjectRuntimeBehavior._noCollision;
|
||||
const floorIsTooHigh = gdjs.PlatformerObjectRuntimeBehavior._floorIsTooHigh;
|
||||
|
||||
[false, true].forEach((swapVerticesOrder) => {
|
||||
describe(`(swapVertexOrder: ${swapVerticesOrder})`, function () {
|
||||
const collisionMasks = {
|
||||
@@ -304,6 +301,10 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
platform.setCustomWidthAndHeight(300, 300);
|
||||
platform.setPosition(position[0], position[1]);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
|
||||
runtimeScene
|
||||
);
|
||||
platformObstaclesManager.addPlatform(platformBehavior);
|
||||
|
||||
it('can detect a platform away downward', function () {
|
||||
character.setPosition(300, -210.1);
|
||||
@@ -372,6 +373,10 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
platform.setCustomWidthAndHeight(300, 300);
|
||||
platform.setPosition(position[0], position[1]);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
|
||||
runtimeScene
|
||||
);
|
||||
platformObstaclesManager.addPlatform(platformBehavior);
|
||||
|
||||
it('can detect an obstacle overlapping the top', function () {
|
||||
// -10 because the character can follow a platform downward.
|
||||
@@ -398,6 +403,10 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(250, -250);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
|
||||
runtimeScene
|
||||
);
|
||||
platformObstaclesManager.addPlatform(platformBehavior);
|
||||
|
||||
it('can detect a tunnel ceiling', function () {
|
||||
character.setPosition(300, -210.1);
|
||||
@@ -443,12 +452,15 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(
|
||||
runtimeScene,
|
||||
collisionMasks.verticalTunnel
|
||||
);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
|
||||
runtimeScene
|
||||
);
|
||||
platformObstaclesManager.addPlatform(platformBehavior);
|
||||
|
||||
it('can fell inside a vertical tunnel that fit the character', function () {
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
@@ -482,6 +494,10 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
|
||||
const platform = addPlatform(runtimeScene, collisionMasks.square);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
const platformObstaclesManager = gdjs.PlatformObjectsManager.getManager(
|
||||
runtimeScene
|
||||
);
|
||||
platformObstaclesManager.addPlatform(platformBehavior);
|
||||
|
||||
it('can detect a platform at its exact position', function () {
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ Copyright (c) 2008-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "ShapePainterObject.h"
|
||||
@@ -20,11 +21,14 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/shape_painter");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<ShapePainterObject>(
|
||||
"Drawer", //"Drawer" is kept for compatibility with GD<=3.6.76
|
||||
_("Shape painter"),
|
||||
_("Allows you to draw simple shapes on the screen"),
|
||||
"CppPlatform/Extensions/primitivedrawingicon.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<ShapePainterObject>(
|
||||
"Drawer", //"Drawer" is kept for compatibility with GD<=3.6.76
|
||||
_("Shape painter"),
|
||||
_("Allows you to draw simple shapes on the screen"),
|
||||
"CppPlatform/Extensions/primitivedrawingicon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
|
||||
@@ -40,10 +44,10 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
"res/actions/rectangle.png")
|
||||
|
||||
.AddParameter("object", _("Shape Painter object"), "Drawer")
|
||||
.AddParameter("expression", _("Top left side: X position"))
|
||||
.AddParameter("expression", _("Top left side: Y position"))
|
||||
.AddParameter("expression", _("Bottom right side: X position"))
|
||||
.AddParameter("expression", _("Bottom right side: Y position"))
|
||||
.AddParameter("expression", _("Left X position"))
|
||||
.AddParameter("expression", _("Top Y position"))
|
||||
.AddParameter("expression", _("Right X position"))
|
||||
.AddParameter("expression", _("Bottom Y position"))
|
||||
.SetFunctionName("DrawRectangle")
|
||||
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
|
||||
|
||||
@@ -131,10 +135,10 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
"res/actions/roundedRectangle.png")
|
||||
|
||||
.AddParameter("object", _("Shape Painter object"), "Drawer")
|
||||
.AddParameter("expression", _("Top left side: X position"))
|
||||
.AddParameter("expression", _("Top left side: Y position"))
|
||||
.AddParameter("expression", _("Bottom right side: X position"))
|
||||
.AddParameter("expression", _("Bottom right side: Y position"))
|
||||
.AddParameter("expression", _("Left X position"))
|
||||
.AddParameter("expression", _("Top Y position"))
|
||||
.AddParameter("expression", _("Right X position"))
|
||||
.AddParameter("expression", _("Bottom Y position"))
|
||||
.AddParameter("expression", _("Radius (in pixels)"))
|
||||
.SetFunctionName("DrawRoundedRectangle")
|
||||
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
|
||||
@@ -376,14 +380,14 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("closePath")
|
||||
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
|
||||
|
||||
obj.AddScopedAction(
|
||||
"ClearShapes",
|
||||
_("Clear shapes"),
|
||||
_("Clear the rendered shape(s). Useful if not set to be done automatically."),
|
||||
_("Clear the rendered image of _PARAM0_"),
|
||||
_("Advanced"),
|
||||
"res/actions/visibilite24.png",
|
||||
"res/actions/visibilite.png")
|
||||
obj.AddScopedAction("ClearShapes",
|
||||
_("Clear shapes"),
|
||||
_("Clear the rendered shape(s). Useful if not set to be "
|
||||
"done automatically."),
|
||||
_("Clear the rendered image of _PARAM0_"),
|
||||
_("Advanced"),
|
||||
"res/actions/visibilite24.png",
|
||||
"res/actions/visibilite.png")
|
||||
.AddParameter("object", _("Shape Painter object"), "Drawer");
|
||||
|
||||
obj.AddAction(
|
||||
@@ -632,5 +636,166 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("AreCoordinatesRelative")
|
||||
.SetIncludeFile("PrimitiveDrawing/ShapePainterObject.h");
|
||||
|
||||
obj.AddAction("Scale",
|
||||
_("Scale"),
|
||||
_("Modify the scale of the specified object."),
|
||||
_("the scale"),
|
||||
_("Size"),
|
||||
"res/actions/scale24.png",
|
||||
"res/actions/scale.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number",
|
||||
"ScaleX",
|
||||
_("Scale on X axis"),
|
||||
_("the width's scale of an object"),
|
||||
_("the width's scale"),
|
||||
_("Size"),
|
||||
"res/actions/scaleWidth24.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.UseStandardParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number",
|
||||
"ScaleY",
|
||||
_("Scale on Y axis"),
|
||||
_("the height's scale of an object"),
|
||||
_("the height's scale"),
|
||||
_("Size"),
|
||||
"res/actions/scaleHeight24.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.UseStandardParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("FlipX",
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip horizontally _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipX24.png",
|
||||
"res/actions/flipX.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("yesorno", _("Activate flipping"))
|
||||
.MarkAsSimple();
|
||||
|
||||
obj.AddAction("FlipY",
|
||||
_("Flip the object vertically"),
|
||||
_("Flip the object vertically"),
|
||||
_("Flip vertically _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipY24.png",
|
||||
"res/actions/flipY.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("yesorno", _("Activate flipping"))
|
||||
.MarkAsSimple();
|
||||
|
||||
obj.AddCondition("FlippedX",
|
||||
_("Horizontally flipped"),
|
||||
_("Check if the object is horizontally flipped"),
|
||||
_("_PARAM0_ is horizontally flipped"),
|
||||
_("Effects"),
|
||||
"res/actions/flipX24.png",
|
||||
"res/actions/flipX.png")
|
||||
.AddParameter("object", _("Object"), "Drawer");
|
||||
|
||||
obj.AddCondition("FlippedY",
|
||||
_("Vertically flipped"),
|
||||
_("Check if the object is vertically flipped"),
|
||||
_("_PARAM0_ is vertically flipped"),
|
||||
_("Effects"),
|
||||
"res/actions/flipY24.png",
|
||||
"res/actions/flipY.png")
|
||||
.AddParameter("object", _("Object"), "Drawer");
|
||||
|
||||
obj.AddAction("Width",
|
||||
_("Width"),
|
||||
_("Change the width of an object."),
|
||||
_("the width"),
|
||||
_("Size"),
|
||||
"res/actions/scaleWidth24.png",
|
||||
"res/actions/scale.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("Height",
|
||||
_("Height"),
|
||||
_("Change the height of an object."),
|
||||
_("the height"),
|
||||
_("Size"),
|
||||
"res/actions/scaleHeight24.png",
|
||||
"res/actions/scale.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction(
|
||||
"SetRotationCenter",
|
||||
_("Center of rotation"),
|
||||
_("Change the center of rotation of an object relatively to the "
|
||||
"object origin."),
|
||||
_("Change the center of rotation of _PARAM0_: _PARAM1_; _PARAM2_"),
|
||||
_("Angle"),
|
||||
"res/actions/position24.png",
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("X position"))
|
||||
.AddParameter("expression", _("Y position"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddAction("SetRectangularCollisionMask",
|
||||
_("Collision Mask"),
|
||||
_("Change the collision mask of an object to a rectangle "
|
||||
"relatively to the object origin."),
|
||||
_("Change the collision mask of _PARAM0_ to a rectangle from "
|
||||
"_PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_"),
|
||||
_("Position"),
|
||||
"res/actions/position24.png",
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("Left X position"))
|
||||
.AddParameter("expression", _("Top Y position"))
|
||||
.AddParameter("expression", _("Right X position"))
|
||||
.AddParameter("expression", _("Bottom Y position"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
obj.AddExpression("ToDrawingX",
|
||||
_("X drawing coordinate of a point from the scene"),
|
||||
_("X drawing coordinate of a point from the scene"),
|
||||
_("Position"),
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("X scene position"))
|
||||
.AddParameter("expression", _("Y scene position"));
|
||||
|
||||
obj.AddExpression("ToDrawingY",
|
||||
_("Y drawing coordinate of a point from the scene"),
|
||||
_("Y drawing coordinate of a point from the scene"),
|
||||
_("Position"),
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("X scene position"))
|
||||
.AddParameter("expression", _("Y scene position"));
|
||||
|
||||
obj.AddExpression("ToSceneX",
|
||||
_("X scene coordinate of a point from the drawing"),
|
||||
_("X scene coordinate of a point from the drawing"),
|
||||
_("Position"),
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("X drawing position"))
|
||||
.AddParameter("expression", _("Y drawing position"));
|
||||
|
||||
obj.AddExpression("ToSceneY",
|
||||
_("Y scene coordinate of a point from the drawing"),
|
||||
_("Y scene coordinate of a point from the drawing"),
|
||||
_("Position"),
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"), "Drawer")
|
||||
.AddParameter("expression", _("X drawing position"))
|
||||
.AddParameter("expression", _("Y drawing position"));
|
||||
|
||||
#endif
|
||||
}
|
||||
|
@@ -170,6 +170,73 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::AreCoordinatesRelative"]
|
||||
.SetFunctionName("areCoordinatesRelative");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Scale"]
|
||||
.SetFunctionName("setScale")
|
||||
.SetGetter("getScale");
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Drawer::SetScaleX"]
|
||||
.SetFunctionName("setScaleX")
|
||||
.SetGetter("getScaleX");
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Drawer::SetScaleY"]
|
||||
.SetFunctionName("setScaleY")
|
||||
.SetGetter("getScaleY");
|
||||
GetAllConditionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllConditionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::ScaleX"]
|
||||
.SetFunctionName("getScaleX");
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::ScaleY"]
|
||||
.SetFunctionName("getScaleY");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FlipX"]
|
||||
.SetFunctionName("flipX");
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FlipY"]
|
||||
.SetFunctionName("flipY");
|
||||
GetAllConditionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FlippedX"]
|
||||
.SetFunctionName("isFlippedX");
|
||||
GetAllConditionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FlippedY"]
|
||||
.SetFunctionName("isFlippedY");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Width"]
|
||||
.SetFunctionName("setWidth")
|
||||
.SetGetter("getWidth");
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Height"]
|
||||
.SetFunctionName("setHeight")
|
||||
.SetGetter("getHeight");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::SetRotationCenter"]
|
||||
.SetFunctionName("setRotationCenter");
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::SetRectangularCollisionMask"]
|
||||
.SetFunctionName("setRectangularCollisionMask");
|
||||
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["ToDrawingX"]
|
||||
.SetFunctionName("transformToDrawingX");
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["ToDrawingY"]
|
||||
.SetFunctionName("transformToDrawingY");
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["ToSceneX"]
|
||||
.SetFunctionName("transformToSceneX");
|
||||
GetAllExpressionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["ToSceneY"]
|
||||
.SetFunctionName("transformToSceneX");
|
||||
|
||||
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
|
||||
};
|
||||
};
|
||||
|
@@ -4,6 +4,26 @@ namespace gdjs {
|
||||
class ShapePainterRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.ShapePainterRuntimeObject;
|
||||
_graphics: PIXI.Graphics;
|
||||
/**
|
||||
* Graphics positions can need updates when shapes are added,
|
||||
* this avoids to do it each time.
|
||||
*/
|
||||
_positionXIsUpToDate = false;
|
||||
/**
|
||||
* Graphics positions can need updates when shapes are added,
|
||||
* this avoids to do it each time.
|
||||
*/
|
||||
_positionYIsUpToDate = false;
|
||||
/**
|
||||
* This allows to use the transformation of the renderer
|
||||
* and compute it only when necessary.
|
||||
*/
|
||||
_transformationIsUpToDate = false;
|
||||
|
||||
private static readonly _positionForTransformation: PIXI.IPointData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
|
||||
constructor(
|
||||
runtimeObject: gdjs.ShapePainterRuntimeObject,
|
||||
@@ -23,6 +43,7 @@ namespace gdjs {
|
||||
|
||||
clear() {
|
||||
this._graphics.clear();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawRectangle(x1: float, y1: float, x2: float, y2: float) {
|
||||
@@ -33,6 +54,7 @@ namespace gdjs {
|
||||
);
|
||||
this._graphics.drawRect(x1, y1, x2 - x1, y2 - y1);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawCircle(x: float, y: float, radius: float) {
|
||||
@@ -43,6 +65,7 @@ namespace gdjs {
|
||||
);
|
||||
this._graphics.drawCircle(x, y, radius);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawLine(x1: float, y1: float, x2: float, y2: float, thickness: float) {
|
||||
@@ -68,6 +91,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawLineV2(x1: float, y1: float, x2: float, y2: float, thickness: float) {
|
||||
@@ -79,6 +103,7 @@ namespace gdjs {
|
||||
this._graphics.moveTo(x1, y1);
|
||||
this._graphics.lineTo(x2, y2);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawEllipse(x1: float, y1: float, width: float, height: float) {
|
||||
@@ -89,6 +114,7 @@ namespace gdjs {
|
||||
);
|
||||
this._graphics.drawEllipse(x1, y1, width / 2, height / 2);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawRoundedRectangle(
|
||||
@@ -106,6 +132,7 @@ namespace gdjs {
|
||||
this._graphics.drawRoundedRect(x1, y1, x2 - x1, y2 - y1, radius);
|
||||
this._graphics.closePath();
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawStar(
|
||||
@@ -132,6 +159,7 @@ namespace gdjs {
|
||||
);
|
||||
this._graphics.closePath();
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawArc(
|
||||
@@ -164,6 +192,7 @@ namespace gdjs {
|
||||
this._graphics.closePath();
|
||||
}
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawBezierCurve(
|
||||
@@ -184,6 +213,7 @@ namespace gdjs {
|
||||
this._graphics.moveTo(x1, y1);
|
||||
this._graphics.bezierCurveTo(cpX, cpY, cpX2, cpY2, x2, y2);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawQuadraticCurve(
|
||||
@@ -202,6 +232,7 @@ namespace gdjs {
|
||||
this._graphics.moveTo(x1, y1);
|
||||
this._graphics.quadraticCurveTo(cpX, cpY, x2, y2);
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
beginFillPath() {
|
||||
@@ -213,6 +244,7 @@ namespace gdjs {
|
||||
|
||||
endFillPath() {
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawPathMoveTo(x1: float, y1: float) {
|
||||
@@ -221,6 +253,7 @@ namespace gdjs {
|
||||
|
||||
drawPathLineTo(x1: float, y1: float) {
|
||||
this._graphics.lineTo(x1, y1);
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawPathBezierCurveTo(
|
||||
@@ -232,6 +265,7 @@ namespace gdjs {
|
||||
toY: float
|
||||
) {
|
||||
this._graphics.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawPathArc(
|
||||
@@ -250,14 +284,17 @@ namespace gdjs {
|
||||
gdjs.toRad(endAngle),
|
||||
anticlockwise ? true : false
|
||||
);
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawPathQuadraticCurveTo(cpX: float, cpY: float, toX: float, toY: float) {
|
||||
this._graphics.quadraticCurveTo(cpX, cpY, toX, toY);
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
closePath() {
|
||||
this._graphics.closePath();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
updateOutline(): void {
|
||||
@@ -268,20 +305,185 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
updateXPosition(): void {
|
||||
if (!this._object._absoluteCoordinates) {
|
||||
this._graphics.position.x = this._object.x;
|
||||
} else {
|
||||
invalidateBounds() {
|
||||
this._object.invalidateBounds();
|
||||
this._positionXIsUpToDate = false;
|
||||
this._positionYIsUpToDate = false;
|
||||
}
|
||||
|
||||
updatePreRender(): void {
|
||||
this.updatePositionIfNeeded();
|
||||
}
|
||||
|
||||
updatePositionX(): void {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
this._graphics.pivot.x = 0;
|
||||
this._graphics.position.x = 0;
|
||||
} else {
|
||||
// Make the drawing rotate around the rotation center.
|
||||
this._graphics.pivot.x = this._object.getRotationCenterX();
|
||||
// Multiply by the scale to have the scale anchor
|
||||
// at the object position instead of the center.
|
||||
this._graphics.position.x =
|
||||
this._object.x +
|
||||
this._graphics.pivot.x * Math.abs(this._graphics.scale.x);
|
||||
}
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
updatePositionY(): void {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
this._graphics.pivot.y = 0;
|
||||
this._graphics.position.y = 0;
|
||||
} else {
|
||||
this._graphics.pivot.y = this._object.getRotationCenterY();
|
||||
this._graphics.position.y =
|
||||
this._object.y +
|
||||
this._graphics.pivot.y * Math.abs(this._graphics.scale.y);
|
||||
}
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
updatePositionIfNeeded() {
|
||||
if (!this._positionXIsUpToDate) {
|
||||
this.updatePositionX();
|
||||
this._positionXIsUpToDate = true;
|
||||
}
|
||||
if (!this._positionYIsUpToDate) {
|
||||
this.updatePositionY();
|
||||
this._positionYIsUpToDate = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateYPosition(): void {
|
||||
if (!this._object._absoluteCoordinates) {
|
||||
this._graphics.position.y = this._object.y;
|
||||
} else {
|
||||
this._graphics.position.y = 0;
|
||||
updateTransformationIfNeeded() {
|
||||
if (!this._transformationIsUpToDate) {
|
||||
this.updatePositionIfNeeded();
|
||||
this._graphics.updateTransform();
|
||||
}
|
||||
this._transformationIsUpToDate = true;
|
||||
}
|
||||
|
||||
updateRotationCenter(): void {
|
||||
// The pivot and position depends on the rotation center point.
|
||||
this._positionXIsUpToDate = false;
|
||||
this._positionYIsUpToDate = false;
|
||||
// The whole transformation changes based on the rotation center point.
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
this._graphics.angle = 0;
|
||||
} else {
|
||||
this._graphics.angle = this._object.angle;
|
||||
}
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
updateScaleX(): void {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
this._graphics.scale.x = 1;
|
||||
} else {
|
||||
this._graphics.scale.x = this._object._scaleX;
|
||||
}
|
||||
// updatePositionX() uses scale.x
|
||||
this._positionXIsUpToDate = false;
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
updateScaleY(): void {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
this._graphics.scale.y = 1;
|
||||
} else {
|
||||
this._graphics.scale.y = this._object._scaleY;
|
||||
}
|
||||
// updatePositionY() uses scale.y
|
||||
this._positionYIsUpToDate = false;
|
||||
this._transformationIsUpToDate = false;
|
||||
}
|
||||
|
||||
getDrawableX(): float {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
return this._graphics.getLocalBounds().left;
|
||||
}
|
||||
let localBound = this._graphics.getLocalBounds().left;
|
||||
if (this._object._flippedX) {
|
||||
const rotationCenterX = this._object.getRotationCenterX();
|
||||
localBound = 2 * rotationCenterX - localBound;
|
||||
}
|
||||
// When new shape are drawn, the bounds of the object can extend.
|
||||
// The object position stays the same but (drawableX; drawableY) can change.
|
||||
return (
|
||||
this._object.getX() + localBound * Math.abs(this._graphics.scale.x)
|
||||
);
|
||||
}
|
||||
|
||||
getDrawableY(): float {
|
||||
if (this._object._useAbsoluteCoordinates) {
|
||||
return this._graphics.getLocalBounds().top;
|
||||
}
|
||||
let localBound = this._graphics.getLocalBounds().top;
|
||||
if (this._object._flippedY) {
|
||||
const rotationCenterY = this._object.getRotationCenterY();
|
||||
localBound = 2 * rotationCenterY - localBound;
|
||||
}
|
||||
return (
|
||||
this._object.getY() + localBound * Math.abs(this._graphics.scale.y)
|
||||
);
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._graphics.width;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._graphics.height;
|
||||
}
|
||||
|
||||
getUnscaledWidth(): float {
|
||||
return this._graphics.getLocalBounds().width;
|
||||
}
|
||||
|
||||
getUnscaledHeight(): float {
|
||||
return this._graphics.getLocalBounds().height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The drawing origin relatively to the drawable top left corner.
|
||||
*/
|
||||
getFrameRelativeOriginX() {
|
||||
return -this._graphics.getLocalBounds().left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The drawing origin relatively to the drawable top left corner.
|
||||
*/
|
||||
getFrameRelativeOriginY() {
|
||||
return -this._graphics.getLocalBounds().top;
|
||||
}
|
||||
|
||||
transformToDrawing(point: FloatPoint): FloatPoint {
|
||||
this.updateTransformationIfNeeded();
|
||||
const position =
|
||||
ShapePainterRuntimeObjectPixiRenderer._positionForTransformation;
|
||||
position.x = point[0];
|
||||
position.y = point[1];
|
||||
this._graphics.localTransform.applyInverse(position, position);
|
||||
point[0] = position.x;
|
||||
point[1] = position.y;
|
||||
return point;
|
||||
}
|
||||
|
||||
transformToScene(point: FloatPoint): FloatPoint {
|
||||
this.updateTransformationIfNeeded();
|
||||
const position =
|
||||
ShapePainterRuntimeObjectPixiRenderer._positionForTransformation;
|
||||
position.x = point[0];
|
||||
position.y = point[1];
|
||||
this._graphics.localTransform.apply(position, position);
|
||||
point[0] = position.x;
|
||||
point[1] = position.y;
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -37,15 +37,25 @@ namespace gdjs {
|
||||
* The ShapePainterRuntimeObject allows to draw graphics shapes on screen.
|
||||
*/
|
||||
export class ShapePainterRuntimeObject extends gdjs.RuntimeObject {
|
||||
_scaleX: number = 1;
|
||||
_scaleY: number = 1;
|
||||
_blendMode: number = 0;
|
||||
_flippedX: boolean = false;
|
||||
_flippedY: boolean = false;
|
||||
_customCenter: FloatPoint | null = null;
|
||||
_customCollisionMask: Polygon[] | null = null;
|
||||
|
||||
_fillColor: integer;
|
||||
_outlineColor: integer;
|
||||
_fillOpacity: float;
|
||||
_outlineOpacity: float;
|
||||
_outlineSize: float;
|
||||
_absoluteCoordinates: boolean;
|
||||
_useAbsoluteCoordinates: boolean;
|
||||
_clearBetweenFrames: boolean;
|
||||
_renderer: gdjs.ShapePainterRuntimeObjectRenderer;
|
||||
|
||||
private static readonly _pointForTransformation: FloatPoint = [0, 0];
|
||||
|
||||
/**
|
||||
* @param runtimeScene The scene the object belongs to.
|
||||
* @param shapePainterObjectData The initial properties of the object
|
||||
@@ -74,7 +84,7 @@ namespace gdjs {
|
||||
this._fillOpacity = shapePainterObjectData.fillOpacity;
|
||||
this._outlineOpacity = shapePainterObjectData.outlineOpacity;
|
||||
this._outlineSize = shapePainterObjectData.outlineSize;
|
||||
this._absoluteCoordinates = shapePainterObjectData.absoluteCoordinates;
|
||||
this._useAbsoluteCoordinates = shapePainterObjectData.absoluteCoordinates;
|
||||
this._clearBetweenFrames = shapePainterObjectData.clearBetweenFrames;
|
||||
this._renderer = new gdjs.ShapePainterRuntimeObjectRenderer(
|
||||
this,
|
||||
@@ -133,9 +143,12 @@ namespace gdjs {
|
||||
if (
|
||||
oldObjectData.absoluteCoordinates !== newObjectData.absoluteCoordinates
|
||||
) {
|
||||
this._absoluteCoordinates = newObjectData.absoluteCoordinates;
|
||||
this._renderer.updateXPosition();
|
||||
this._renderer.updateYPosition();
|
||||
this._useAbsoluteCoordinates = newObjectData.absoluteCoordinates;
|
||||
this._renderer.updatePositionX();
|
||||
this._renderer.updatePositionY();
|
||||
this._renderer.updateAngle();
|
||||
this._renderer.updateScaleX();
|
||||
this._renderer.updateScaleY();
|
||||
}
|
||||
if (
|
||||
oldObjectData.clearBetweenFrames !== newObjectData.clearBetweenFrames
|
||||
@@ -161,7 +174,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getVisibilityAABB() {
|
||||
return this._absoluteCoordinates ? null : this.getAABB();
|
||||
return this._useAbsoluteCoordinates ? null : this.getAABB();
|
||||
}
|
||||
|
||||
drawRectangle(x1: float, y1: float, x2: float, y2: float) {
|
||||
@@ -325,11 +338,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setCoordinatesRelative(value: boolean): void {
|
||||
this._absoluteCoordinates = !value;
|
||||
this._useAbsoluteCoordinates = !value;
|
||||
}
|
||||
|
||||
areCoordinatesRelative(): boolean {
|
||||
return !this._absoluteCoordinates;
|
||||
return !this._useAbsoluteCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,7 +451,7 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
super.setX(x);
|
||||
this._renderer.updateXPosition();
|
||||
this._renderer.updatePositionX();
|
||||
}
|
||||
|
||||
setY(y: float): void {
|
||||
@@ -446,15 +459,337 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
super.setY(y);
|
||||
this._renderer.updateYPosition();
|
||||
this._renderer.updatePositionY();
|
||||
}
|
||||
|
||||
setAngle(angle: float): void {
|
||||
if (angle === this.angle) {
|
||||
return;
|
||||
}
|
||||
super.setAngle(angle);
|
||||
this._renderer.updateAngle();
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The center of rotation is defined relatively
|
||||
* to the drawing origin (the object position).
|
||||
* This avoid the center to move on the drawing
|
||||
* when new shapes push the bounds.
|
||||
*
|
||||
* When no custom center is defined, it will move
|
||||
* to stay at the center of the drawable bounds.
|
||||
*
|
||||
* @param x coordinate of the custom center
|
||||
* @param y coordinate of the custom center
|
||||
*/
|
||||
setRotationCenter(x: float, y: float): void {
|
||||
if (!this._customCenter) {
|
||||
this._customCenter = [0, 0];
|
||||
}
|
||||
this._customCenter[0] = x;
|
||||
this._customCenter[1] = y;
|
||||
this._renderer.updateRotationCenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The center X relatively to the drawing origin
|
||||
* (whereas `getCenterX()` is relative to the top left drawable bound and scaled).
|
||||
*/
|
||||
getRotationCenterX(): float {
|
||||
return this._customCenter
|
||||
? this._customCenter[0]
|
||||
: this._renderer.getUnscaledWidth() / 2 -
|
||||
this._renderer.getFrameRelativeOriginX();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The center Y relatively to the drawing origin
|
||||
* (whereas `getCenterY()` is relative to the top left drawable bound and scaled).
|
||||
*/
|
||||
getRotationCenterY(): float {
|
||||
return this._customCenter
|
||||
? this._customCenter[1]
|
||||
: this._renderer.getUnscaledHeight() / 2 -
|
||||
this._renderer.getFrameRelativeOriginY();
|
||||
}
|
||||
|
||||
getCenterX(): float {
|
||||
if (!this._customCenter) {
|
||||
return super.getCenterX();
|
||||
}
|
||||
return (
|
||||
this._customCenter[0] * Math.abs(this._scaleX) +
|
||||
this.getX() -
|
||||
this.getDrawableX()
|
||||
);
|
||||
}
|
||||
|
||||
getCenterY(): float {
|
||||
if (!this._customCenter) {
|
||||
return super.getCenterY();
|
||||
}
|
||||
return (
|
||||
this._customCenter[1] * Math.abs(this._scaleY) +
|
||||
this.getY() -
|
||||
this.getDrawableY()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the width of the object. This changes the scale on X axis of the object.
|
||||
*
|
||||
* @param newWidth The new width of the object, in pixels.
|
||||
*/
|
||||
setWidth(newWidth: float): void {
|
||||
const unscaledWidth = this._renderer.getUnscaledWidth();
|
||||
if (unscaledWidth !== 0) {
|
||||
this.setScaleX(newWidth / unscaledWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the height of the object. This changes the scale on Y axis of the object.
|
||||
*
|
||||
* @param newHeight The new height of the object, in pixels.
|
||||
*/
|
||||
setHeight(newHeight: float): void {
|
||||
const unscaledHeight = this._renderer.getUnscaledHeight();
|
||||
if (unscaledHeight !== 0) {
|
||||
this.setScaleY(newHeight / unscaledHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on X and Y axis of the object.
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScale(newScale: float): void {
|
||||
this.setScaleX(newScale);
|
||||
this.setScaleY(newScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on X axis of the object (changing its width).
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScaleX(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleX)) {
|
||||
return;
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._renderer.updateScaleX();
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on Y axis of the object (changing its width).
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScaleY(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleY)) {
|
||||
return;
|
||||
}
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScaleY();
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
|
||||
flipX(enable: boolean): void {
|
||||
if (enable !== this._flippedX) {
|
||||
this._scaleX *= -1;
|
||||
this._flippedX = enable;
|
||||
this._renderer.updateScaleX();
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
flipY(enable: boolean): void {
|
||||
if (enable !== this._flippedY) {
|
||||
this._scaleY *= -1;
|
||||
this._flippedY = enable;
|
||||
this._renderer.updateScaleY();
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
isFlippedX(): boolean {
|
||||
return this._flippedX;
|
||||
}
|
||||
|
||||
isFlippedY(): boolean {
|
||||
return this._flippedY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*
|
||||
* @return the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*/
|
||||
getScale(): number {
|
||||
const scaleX = Math.abs(this._scaleX);
|
||||
const scaleY = Math.abs(this._scaleY);
|
||||
return scaleX === scaleY ? scaleX : Math.sqrt(scaleX * scaleY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object on Y axis.
|
||||
*
|
||||
* @return the scale of the object on Y axis
|
||||
*/
|
||||
getScaleY(): float {
|
||||
return Math.abs(this._scaleY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object on X axis.
|
||||
*
|
||||
* @return the scale of the object on X axis
|
||||
*/
|
||||
getScaleX(): float {
|
||||
return Math.abs(this._scaleX);
|
||||
}
|
||||
|
||||
invalidateBounds() {
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
|
||||
getDrawableX(): float {
|
||||
return this._renderer.getDrawableX();
|
||||
}
|
||||
|
||||
getDrawableY(): float {
|
||||
return this._renderer.getDrawableY();
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return 32;
|
||||
return this._renderer.getWidth();
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return 32;
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
updatePreRender(runtimeScene: gdjs.RuntimeScene): void {
|
||||
this._renderer.updatePreRender();
|
||||
}
|
||||
|
||||
transformToDrawing(x: float, y: float) {
|
||||
const point = ShapePainterRuntimeObject._pointForTransformation;
|
||||
point[0] = x;
|
||||
point[1] = y;
|
||||
return this._renderer.transformToDrawing(point);
|
||||
}
|
||||
|
||||
transformToScene(x: float, y: float) {
|
||||
const point = ShapePainterRuntimeObject._pointForTransformation;
|
||||
point[0] = x;
|
||||
point[1] = y;
|
||||
return this._renderer.transformToScene(point);
|
||||
}
|
||||
|
||||
transformToDrawingX(x: float, y: float) {
|
||||
return this.transformToDrawing(x, y)[0];
|
||||
}
|
||||
|
||||
transformToDrawingY(x: float, y: float) {
|
||||
return this.transformToDrawing(x, y)[1];
|
||||
}
|
||||
|
||||
transformToSceneX(x: float, y: float) {
|
||||
return this.transformToScene(x, y)[0];
|
||||
}
|
||||
|
||||
transformToSceneY(x: float, y: float) {
|
||||
return this.transformToScene(x, y)[1];
|
||||
}
|
||||
|
||||
setRectangularCollisionMask(
|
||||
left: float,
|
||||
top: float,
|
||||
right: float,
|
||||
bottom: float
|
||||
) {
|
||||
if (!this._customCollisionMask) {
|
||||
const rectangle = new gdjs.Polygon();
|
||||
rectangle.vertices.push([0, 0]);
|
||||
rectangle.vertices.push([0, 0]);
|
||||
rectangle.vertices.push([0, 0]);
|
||||
rectangle.vertices.push([0, 0]);
|
||||
this._customCollisionMask = [rectangle];
|
||||
}
|
||||
const rectangle = this._customCollisionMask[0].vertices;
|
||||
|
||||
rectangle[0][0] = left;
|
||||
rectangle[0][1] = top;
|
||||
|
||||
rectangle[1][0] = right;
|
||||
rectangle[1][1] = top;
|
||||
|
||||
rectangle[2][0] = right;
|
||||
rectangle[2][1] = bottom;
|
||||
|
||||
rectangle[3][0] = left;
|
||||
rectangle[3][1] = bottom;
|
||||
|
||||
this.hitBoxesDirty = true;
|
||||
}
|
||||
|
||||
updateHitBoxes(): void {
|
||||
this.hitBoxes = this._defaultHitBoxes;
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const centerX = this.getCenterX();
|
||||
const centerY = this.getCenterY();
|
||||
const vertices = this.hitBoxes[0].vertices;
|
||||
if (this._customCollisionMask) {
|
||||
const customCollisionMaskVertices = this._customCollisionMask[0]
|
||||
.vertices;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const point = this.transformToScene(
|
||||
customCollisionMaskVertices[i][0],
|
||||
customCollisionMaskVertices[i][1]
|
||||
);
|
||||
vertices[i][0] = point[0];
|
||||
vertices[i][1] = point[1];
|
||||
}
|
||||
} else {
|
||||
if (centerX === width / 2 && centerY === height / 2) {
|
||||
vertices[0][0] = -centerX;
|
||||
vertices[0][1] = -centerY;
|
||||
vertices[1][0] = +centerX;
|
||||
vertices[1][1] = -centerY;
|
||||
vertices[2][0] = +centerX;
|
||||
vertices[2][1] = +centerY;
|
||||
vertices[3][0] = -centerX;
|
||||
vertices[3][1] = +centerY;
|
||||
} else {
|
||||
vertices[0][0] = 0 - centerX;
|
||||
vertices[0][1] = 0 - centerY;
|
||||
vertices[1][0] = width - centerX;
|
||||
vertices[1][1] = 0 - centerY;
|
||||
vertices[2][0] = width - centerX;
|
||||
vertices[2][1] = height - centerY;
|
||||
vertices[3][0] = 0 - centerX;
|
||||
vertices[3][1] = height - centerY;
|
||||
}
|
||||
if (!this._useAbsoluteCoordinates) {
|
||||
this.hitBoxes[0].rotate(gdjs.toRad(this.getAngle()));
|
||||
}
|
||||
this.hitBoxes[0].move(
|
||||
this.getDrawableX() + centerX,
|
||||
this.getDrawableY() + centerY
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
gdjs.registerObject(
|
||||
|
@@ -0,0 +1,205 @@
|
||||
// @ts-check
|
||||
|
||||
describe('gdjs.ShapePainterRuntimeObject (using a PIXI RuntimeGame with assets)', function () {
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
const makeSpriteRuntimeObjectWithCustomHitBox = (runtimeScene) =>
|
||||
new gdjs.ShapePainterRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: 'PrimitiveDrawing::Drawer',
|
||||
variables: [],
|
||||
behaviors: [],
|
||||
effects: [],
|
||||
fillColor: { r: 0, g: 0, b: 0 },
|
||||
outlineColor: { r: 0, g: 0, b: 0 },
|
||||
fillOpacity: 255,
|
||||
outlineOpacity: 255,
|
||||
outlineSize: 1,
|
||||
absoluteCoordinates: false,
|
||||
clearBetweenFrames: false,
|
||||
});
|
||||
|
||||
/** @param {gdjs.RuntimeScene} runtimeScene */
|
||||
const loadScene = (runtimeScene) => {
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [
|
||||
{
|
||||
name: '',
|
||||
visibility: true,
|
||||
effects: [],
|
||||
cameras: [],
|
||||
ambientLightColorR: 0,
|
||||
ambientLightColorG: 0,
|
||||
ambientLightColorB: 0,
|
||||
isLightingLayer: false,
|
||||
followBaseLayerCamera: true,
|
||||
},
|
||||
],
|
||||
r: 0,
|
||||
v: 0,
|
||||
b: 0,
|
||||
mangledName: 'Scene1',
|
||||
name: 'Scene1',
|
||||
stopSoundsOnStartup: false,
|
||||
title: '',
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
instances: [],
|
||||
variables: [],
|
||||
});
|
||||
};
|
||||
|
||||
it('properly computes bounds of the object (basics)', async () => {
|
||||
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
loadScene(runtimeScene);
|
||||
|
||||
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
|
||||
|
||||
expect(object.getWidth()).to.be(0);
|
||||
expect(object.getHeight()).to.be(0);
|
||||
|
||||
object.drawLineV2(10, 10, 20, 30, 3);
|
||||
|
||||
// Check the automatically computed bounds:
|
||||
expect(object.getDrawableX()).to.be(8.5);
|
||||
expect(object.getDrawableY()).to.be(8.5);
|
||||
expect(object.getWidth()).to.be(13);
|
||||
expect(object.getHeight()).to.be(23);
|
||||
|
||||
// Check the automatic center positioning:
|
||||
expect(object.getCenterXInScene()).to.be(15);
|
||||
expect(object.getCenterYInScene()).to.be(20);
|
||||
expect(object.getCenterX()).to.be(15 - 8.5);
|
||||
expect(object.getCenterY()).to.be(20 - 8.5);
|
||||
|
||||
// Check hit boxes:
|
||||
expect(object.getAABB()).to.eql({
|
||||
min: [8.5, 8.5],
|
||||
max: [8.5 + 13, 8.5 + 23],
|
||||
});
|
||||
|
||||
// Check after scaling (scaling is done from the origin):
|
||||
object.setScale(2);
|
||||
expect(object.getDrawableX()).to.be(17);
|
||||
expect(object.getDrawableY()).to.be(17);
|
||||
expect(object.getWidth()).to.be(13 * 2);
|
||||
expect(object.getHeight()).to.be(23 * 2);
|
||||
expect(object.getAABB()).to.eql({ min: [17, 17], max: [43, 63] });
|
||||
|
||||
// Check after rotating (rotating is done from the center):
|
||||
object.setAngle(45);
|
||||
expect(object.getDrawableX()).to.be(17); // Drawable X/Y is not impacted...
|
||||
expect(object.getDrawableY()).to.be(17);
|
||||
expect(object.getWidth()).to.be(13 * 2); // ...Neither is the size
|
||||
expect(object.getHeight()).to.be(23 * 2);
|
||||
expect(object.getAABB()).to.eql({
|
||||
// The hit boxes/AABB are rotated:
|
||||
min: [4.54415587728429, 14.54415587728429],
|
||||
max: [55.45584412271571, 65.45584412271572],
|
||||
});
|
||||
});
|
||||
|
||||
it('properly computes bounds of the object (custom center)', async () => {
|
||||
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
loadScene(runtimeScene);
|
||||
|
||||
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
|
||||
|
||||
expect(object.getWidth()).to.be(0);
|
||||
expect(object.getHeight()).to.be(0);
|
||||
|
||||
object.drawLineV2(10, 10, 20, 30, 3);
|
||||
object.setRotationCenter(10, 9);
|
||||
|
||||
// Check the automatically computed bounds (not impacted by the center):
|
||||
expect(object.getDrawableX()).to.be(8.5);
|
||||
expect(object.getDrawableY()).to.be(8.5);
|
||||
expect(object.getWidth()).to.be(13);
|
||||
expect(object.getHeight()).to.be(23);
|
||||
|
||||
// Check the center positioning:
|
||||
expect(object.getCenterXInScene()).to.be(10);
|
||||
expect(object.getCenterYInScene()).to.be(9);
|
||||
expect(object.getCenterX()).to.be(10 - 8.5);
|
||||
expect(object.getCenterY()).to.be(9 - 8.5);
|
||||
|
||||
// Check hit boxes (not impacted by the center, as no rotation is made):
|
||||
expect(object.getAABB()).to.eql({
|
||||
min: [8.5, 8.5],
|
||||
max: [8.5 + 13, 8.5 + 23],
|
||||
});
|
||||
|
||||
// Check after scaling (scaling is done from the origin):
|
||||
object.setScale(2);
|
||||
expect(object.getDrawableX()).to.be(17);
|
||||
expect(object.getDrawableY()).to.be(17);
|
||||
expect(object.getWidth()).to.be(13 * 2);
|
||||
expect(object.getHeight()).to.be(23 * 2);
|
||||
expect(object.getAABB()).to.eql({ min: [17, 17], max: [43, 63] });
|
||||
|
||||
// Check after rotating (rotating is done from the center):
|
||||
object.setAngle(45);
|
||||
expect(object.getAABB()).to.eql({
|
||||
// The hit boxes/AABB are rotated:
|
||||
min: [-13.941125496954278, 15.17157287525381],
|
||||
max: [36.970562748477136, 66.08326112068524],
|
||||
});
|
||||
|
||||
// Draw outside of the current bounds.
|
||||
const oldMinX = object.getAABB().min[0];
|
||||
const oldMinY = object.getAABB().min[1];
|
||||
const oldMaxX = object.getAABB().max[0];
|
||||
const oldMaxY = object.getAABB().max[1];
|
||||
const oldCenterX = object.getCenterXInScene();
|
||||
const oldCenterY = object.getCenterYInScene();
|
||||
object.drawLineV2(-10, -10, 21, 31, 3);
|
||||
|
||||
// Check that the center stays the same.
|
||||
expect(object.getCenterXInScene()).to.be(oldCenterX);
|
||||
expect(object.getCenterYInScene()).to.be(oldCenterY);
|
||||
|
||||
// Check that the AABB expands.
|
||||
const newAABB = object.getAABB();
|
||||
expect(newAABB.min[0]).below(oldMinX);
|
||||
expect(newAABB.min[1]).below(oldMinY);
|
||||
expect(newAABB.max[0]).above(oldMaxX);
|
||||
expect(newAABB.max[1]).above(oldMaxY);
|
||||
});
|
||||
|
||||
it('can transform points', async () => {
|
||||
const runtimeGame = await gdjs.getPixiRuntimeGameWithAssets();
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
loadScene(runtimeScene);
|
||||
|
||||
const object = makeSpriteRuntimeObjectWithCustomHitBox(runtimeScene);
|
||||
|
||||
object.drawLineV2(0, 0, 10, 10, 2);
|
||||
expect(object.getWidth()).to.be(12);
|
||||
expect(object.getHeight()).to.be(12);
|
||||
|
||||
// Check changes in position/scale are taken into account:
|
||||
object.setPosition(50, 100);
|
||||
expect(object.transformToScene(10, 20)).to.eql([60, 120]);
|
||||
|
||||
object.setScale(2);
|
||||
expect(object.transformToScene(10, 20)).to.eql([70, 140]);
|
||||
|
||||
// Check rotation with the default center:
|
||||
expect(object.getCenterXInScene()).to.be(60);
|
||||
expect(object.getCenterYInScene()).to.be(110);
|
||||
expect(object.transformToScene(5, 5)).to.eql([60, 110]);
|
||||
expect(object.transformToScene(10, 20)).to.eql([70, 140]);
|
||||
|
||||
object.setAngle(90);
|
||||
expect(object.transformToScene(5, 5)).to.eql([60, 110]);
|
||||
expect(object.transformToScene(10, 20)).to.eql([30, 120]);
|
||||
|
||||
// Check rotation with a custom center:
|
||||
object.setRotationCenter(20, 9);
|
||||
expect(object.transformToScene(10, 20)).to.eql([68, 98]);
|
||||
expect(object.transformToDrawing(68, 98)).to.eql([10, 20]);
|
||||
});
|
||||
});
|
@@ -1,17 +1,21 @@
|
||||
namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace spatialSound {
|
||||
const logger = new gdjs.Logger('Spatial Sound');
|
||||
export const setSoundPosition = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
channel: integer,
|
||||
x: float,
|
||||
y: float,
|
||||
z: float
|
||||
) =>
|
||||
runtimeScene
|
||||
.getSoundManager()
|
||||
.getSoundOnChannel(channel)
|
||||
.setSpatialPosition(x, y, z);
|
||||
) => {
|
||||
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
|
||||
if (sound) sound.setSpatialPosition(x, y, z);
|
||||
else
|
||||
logger.error(
|
||||
`Cannot set the spatial position of a non-existing sound on channel ${channel}.`
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,11 +20,14 @@ void DeclareTextEntryObjectExtension(gd::PlatformExtension& extension) {
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/text_entry");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<TextEntryObject>(
|
||||
"TextEntry",
|
||||
_("Text entry"),
|
||||
_("Invisible object used to get the text entered with the keyboard."),
|
||||
"CppPlatform/Extensions/textentry.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<TextEntryObject>("TextEntry",
|
||||
_("Text entry"),
|
||||
_("Invisible object used to get the text "
|
||||
"entered with the keyboard."),
|
||||
"CppPlatform/Extensions/textentry.png")
|
||||
.SetCategoryFullName(_("Advanced"));
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("TextEntryObject/TextEntryObject.h");
|
||||
|
@@ -26,10 +26,12 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.SetExtensionHelpPath("/objects/text");
|
||||
|
||||
gd::ObjectMetadata& obj =
|
||||
extension.AddObject<TextObject>("Text",
|
||||
_("Text"),
|
||||
_("Displays a text on the screen."),
|
||||
"CppPlatform/Extensions/texticon.png");
|
||||
extension
|
||||
.AddObject<TextObject>("Text",
|
||||
_("Text"),
|
||||
_("Displays a text on the screen."),
|
||||
"CppPlatform/Extensions/texticon.png")
|
||||
.SetCategoryFullName(_("Texts"));
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("TextObject/TextObject.h");
|
||||
@@ -513,14 +515,13 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetAngle")
|
||||
.SetIncludeFile("TextObject/TextObject.h");
|
||||
|
||||
obj.AddExpressionAndConditionAndAction(
|
||||
"number",
|
||||
"FontSize",
|
||||
_("Font size"),
|
||||
_("the font size of a text object"),
|
||||
_("the font size"),
|
||||
"",
|
||||
"res/conditions/characterSize24.png")
|
||||
obj.AddExpressionAndConditionAndAction("number",
|
||||
"FontSize",
|
||||
_("Font size"),
|
||||
_("the font size of a text object"),
|
||||
_("the font size"),
|
||||
"",
|
||||
"res/conditions/characterSize24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters("number");
|
||||
|
||||
|
@@ -86,6 +86,7 @@ module.exports = {
|
||||
.setDescription(
|
||||
_('This is the JSON file that was saved or exported from Tiled.')
|
||||
)
|
||||
.setGroup(_('Tilemap and tileset'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'tilesetJsonFile',
|
||||
@@ -98,6 +99,7 @@ module.exports = {
|
||||
"Optional, don't specify it if you've not saved the tileset in a different file."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Tilemap and tileset'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'tilemapAtlasImage',
|
||||
@@ -105,6 +107,7 @@ module.exports = {
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Atlas image'))
|
||||
.setGroup(_('Tilemap and tileset'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'displayMode',
|
||||
@@ -114,6 +117,7 @@ module.exports = {
|
||||
.addExtraInfo('all')
|
||||
.addExtraInfo('index')
|
||||
.setLabel(_('Display mode'))
|
||||
.setGroup(_('Appearance'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'layerIndex',
|
||||
@@ -125,18 +129,21 @@ module.exports = {
|
||||
'If "index" is selected as the display mode, this is the index of the layer to display.'
|
||||
)
|
||||
)
|
||||
.setGroup(_('Appearance'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'animationSpeedScale',
|
||||
new gd.PropertyDescriptor(objectContent.animationSpeedScale.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Animation speed scale'))
|
||||
.setGroup(_('Animation'))
|
||||
);
|
||||
objectProperties.set(
|
||||
'animationFps',
|
||||
new gd.PropertyDescriptor(objectContent.animationFps.toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Animation FPS'))
|
||||
.setGroup(_('Animation'))
|
||||
);
|
||||
|
||||
return objectProperties;
|
||||
@@ -193,7 +200,8 @@ module.exports = {
|
||||
'Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js'
|
||||
)
|
||||
.addIncludeFile('Extensions/TileMap/pako/dist/pako.min.js')
|
||||
.addIncludeFile('Extensions/TileMap/pixi-tilemap-helper.js');
|
||||
.addIncludeFile('Extensions/TileMap/pixi-tilemap-helper.js')
|
||||
.setCategoryFullName(_('Advanced'));
|
||||
|
||||
object
|
||||
.addCondition(
|
||||
@@ -620,10 +628,11 @@ module.exports = {
|
||||
.getValue();
|
||||
|
||||
try {
|
||||
const tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData(
|
||||
this._project,
|
||||
tilemapJsonFile
|
||||
);
|
||||
const tileMapJsonData =
|
||||
await this._pixiResourcesLoader.getResourceJsonData(
|
||||
this._project,
|
||||
tilemapJsonFile
|
||||
);
|
||||
|
||||
const tilesetJsonData = tilesetJsonFile
|
||||
? await this._pixiResourcesLoader.getResourceJsonData(
|
||||
|
@@ -25,11 +25,14 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects/tiled_sprite");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<TiledSpriteObject>(
|
||||
"TiledSprite",
|
||||
_("Tiled Sprite"),
|
||||
_("Displays an image repeated over an area."),
|
||||
"CppPlatform/Extensions/TiledSpriteIcon.png");
|
||||
gd::ObjectMetadata& obj =
|
||||
extension
|
||||
.AddObject<TiledSpriteObject>(
|
||||
"TiledSprite",
|
||||
_("Tiled Sprite"),
|
||||
_("Displays an image repeated over an area."),
|
||||
"CppPlatform/Extensions/TiledSpriteIcon.png")
|
||||
.SetCategoryFullName(_("General"));
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("TiledSpriteObject/TiledSpriteObject.h");
|
||||
@@ -82,7 +85,7 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Width"),
|
||||
_("Modify the width of a Tiled Sprite."),
|
||||
_("the width"),
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/actions/scaleWidth24.png",
|
||||
"res/actions/scaleWidth.png")
|
||||
|
||||
@@ -96,7 +99,7 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Width"),
|
||||
_("Test the width of a Tiled Sprite."),
|
||||
_("the width"),
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/conditions/scaleWidth24.png",
|
||||
"res/conditions/scaleWidth.png")
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
@@ -109,7 +112,7 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Height"),
|
||||
_("Modify the height of a Tiled Sprite."),
|
||||
_("the height"),
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/actions/scaleHeight24.png",
|
||||
"res/actions/scaleHeight.png")
|
||||
|
||||
@@ -123,7 +126,7 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Height"),
|
||||
_("Test the height of a Tiled Sprite."),
|
||||
_("the height"),
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/conditions/scaleHeight24.png",
|
||||
"res/conditions/scaleHeight.png")
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
@@ -132,34 +135,31 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetHeight")
|
||||
.SetIncludeFile("TiledSpriteObject/TiledSpriteObject.h");
|
||||
|
||||
// Deprecated: now available for all objects.
|
||||
obj.AddAction("Angle",
|
||||
_("Angle"),
|
||||
_("Modify the angle of a Tiled Sprite."),
|
||||
_("the angle"),
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/actions/rotate24.png",
|
||||
"res/actions/rotate.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SetAngle")
|
||||
.SetGetter("GetAngle")
|
||||
.SetIncludeFile("TiledSpriteObject/TiledSpriteObject.h");
|
||||
.SetHidden();
|
||||
|
||||
// Deprecated: now available for all objects.
|
||||
obj.AddCondition("Angle",
|
||||
"Angle",
|
||||
"Test the angle of a Tiled Sprite.",
|
||||
"the angle",
|
||||
_("Size and angle"),
|
||||
_("Size"),
|
||||
"res/conditions/rotate24.png",
|
||||
"res/conditions/rotate.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
.UseStandardRelationalOperatorParameters("number")
|
||||
.SetHidden() // Now available for all objects
|
||||
.SetFunctionName("GetAngle")
|
||||
.SetIncludeFile("TiledSpriteObject/TiledSpriteObject.h");
|
||||
.SetHidden();
|
||||
|
||||
obj.AddAction(
|
||||
"XOffset",
|
||||
@@ -196,8 +196,8 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Modify the offset used on the Y axis when displaying the image."),
|
||||
_("the Y offset"),
|
||||
_("Image offset"),
|
||||
"res/conditions/scaleWidth24.png",
|
||||
"res/conditions/scaleWidth.png")
|
||||
"res/conditions/scaleHeight24.png",
|
||||
"res/conditions/scaleHeight.png")
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced()
|
||||
@@ -211,8 +211,8 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Test the offset used on the Y axis when displaying the image."),
|
||||
_("the Y offset"),
|
||||
_("Image offset"),
|
||||
"res/conditions/scaleWidth24.png",
|
||||
"res/conditions/scaleWidth.png")
|
||||
"res/conditions/scaleHeight24.png",
|
||||
"res/conditions/scaleHeight.png")
|
||||
.AddParameter("object", _("Object"), "TiledSprite")
|
||||
.UseStandardRelationalOperatorParameters("number")
|
||||
.MarkAsAdvanced()
|
||||
|
@@ -77,17 +77,22 @@ class TiledSpriteObjectJsExtension : public gd::PlatformExtension {
|
||||
.SetFunctionName("getHeight")
|
||||
.SetIncludeFile(
|
||||
"Extensions/TiledSpriteObject/tiledspriteruntimeobject.js");
|
||||
|
||||
// Deprecated: now available for all objects.
|
||||
GetAllActionsForObject(
|
||||
"TiledSpriteObject::TiledSprite")["TiledSpriteObject::Angle"]
|
||||
.SetFunctionName("setAngle")
|
||||
.SetGetter("getAngle")
|
||||
.SetIncludeFile(
|
||||
"Extensions/TiledSpriteObject/tiledspriteruntimeobject.js");
|
||||
|
||||
// Deprecated: now available for all objects.
|
||||
GetAllConditionsForObject(
|
||||
"TiledSpriteObject::TiledSprite")["TiledSpriteObject::Angle"]
|
||||
.SetFunctionName("getAngle")
|
||||
.SetIncludeFile(
|
||||
"Extensions/TiledSpriteObject/tiledspriteruntimeobject.js");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"TiledSpriteObject::TiledSprite")["TiledSpriteObject::XOffset"]
|
||||
.SetFunctionName("setXOffset")
|
||||
|
@@ -76,11 +76,19 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateXOffset(): void {
|
||||
this._tiledSprite.tilePosition.x = -this._object._xOffset;
|
||||
// Known PIXI.js issue, the coordinates should not exceed the width/height of the texture,
|
||||
// otherwise the texture will be pixelated over time.
|
||||
// See https://github.com/pixijs/pixijs/issues/7891#issuecomment-947549553
|
||||
this._tiledSprite.tilePosition.x =
|
||||
-this._object._xOffset % this._tiledSprite.texture.width;
|
||||
}
|
||||
|
||||
updateYOffset(): void {
|
||||
this._tiledSprite.tilePosition.y = -this._object._yOffset;
|
||||
// Known PIXI.js issue, the coordinates should not exceed the width/height of the texture,
|
||||
// otherwise the texture will be pixelated over time.
|
||||
// See https://github.com/pixijs/pixijs/issues/7891#issuecomment-947549553
|
||||
this._tiledSprite.tilePosition.y =
|
||||
-this._object._yOffset % this._tiledSprite.texture.height;
|
||||
}
|
||||
|
||||
setColor(rgbColor): void {
|
||||
|
@@ -26,7 +26,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"TopDownMovementBehavior",
|
||||
_("Top-down movement (4 or 8 directions)"),
|
||||
"TopDownMovement",
|
||||
_("Objects with this behavior can be moved left, up, right, and "
|
||||
_("Move objects left, up, right, and "
|
||||
"down (and, optionally, diagonally)."),
|
||||
"",
|
||||
"CppPlatform/Extensions/topdownmovementicon.png",
|
||||
@@ -541,4 +541,4 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
|
||||
.UseStandardParameters("number");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -45,23 +45,24 @@ TopDownMovementBehavior::GetProperties(
|
||||
const gd::SerializerElement& behaviorContent) const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
|
||||
properties[_("Allows diagonals")]
|
||||
properties[_("Allows diagonals")].SetGroup(_("Movement"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("allowDiagonals") ? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Acceleration")].SetValue(
|
||||
properties[_("Acceleration")].SetGroup(_("Movement")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
|
||||
properties[_("Deceleration")].SetValue(
|
||||
properties[_("Deceleration")].SetGroup(_("Movement")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("deceleration")));
|
||||
properties[_("Max. speed")].SetValue(
|
||||
properties[_("Max. speed")].SetGroup(_("Movement")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties[_("Rotate speed")].SetValue(
|
||||
properties[_("Rotate speed")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angularMaxSpeed")));
|
||||
properties[_("Rotate object")]
|
||||
.SetGroup(_("Rotation"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("rotateObject") ? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Angle offset")].SetValue(
|
||||
properties[_("Angle offset")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
|
||||
properties[_("Default controls")]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
|
||||
@@ -80,6 +81,7 @@ TopDownMovementBehavior::GetProperties(
|
||||
else if (viewpoint == "CustomIsometry")
|
||||
viewpointStr = _("Custom Isometry");
|
||||
properties[_("Viewpoint")]
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetValue(viewpointStr)
|
||||
.SetType("Choice")
|
||||
.AddExtraInfo(_("Top-Down"))
|
||||
@@ -87,11 +89,13 @@ TopDownMovementBehavior::GetProperties(
|
||||
.AddExtraInfo(_("True Isometry (30°)"))
|
||||
.AddExtraInfo(_("Custom Isometry"));
|
||||
properties[_("Custom isometry angle")]
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("customIsometryAngle")))
|
||||
.SetDescription(_("If you choose \"Custom Isometry\", this allows to "
|
||||
"specify the angle of your isometry projection."));
|
||||
properties[_("Movement angle offset")]
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("movementAngleOffset")))
|
||||
.SetDescription(_(
|
||||
|
@@ -1,8 +1,9 @@
|
||||
// @ts-check
|
||||
describe('gdjs.TopDownMovementRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 8);
|
||||
const topDownName = 'auto1';
|
||||
|
||||
const createScene = () => {
|
||||
const createScene = (timeDelta = 1000 / 60) => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
// @ts-ignore - missing properties.
|
||||
@@ -38,7 +39,7 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return (1 / 60) * 1000;
|
||||
return timeDelta;
|
||||
};
|
||||
return runtimeScene;
|
||||
};
|
||||
@@ -427,4 +428,28 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[20, 30, 60, 120].forEach((framesPerSecond) => {
|
||||
describe(`(frames per second: ${framesPerSecond})`, function () {
|
||||
let runtimeScene;
|
||||
let player;
|
||||
beforeEach(function () {
|
||||
runtimeScene = createScene(1000 / framesPerSecond);
|
||||
player = addPlayer(runtimeScene, true);
|
||||
});
|
||||
|
||||
it('moves the same distance', function () {
|
||||
player.setPosition(200, 300);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// It takes 0,5 second to reach the maximum speed because
|
||||
// the acceleration is 400 and maxSpeed is 200.
|
||||
for (let i = 0; i < framesPerSecond / 2; i++) {
|
||||
player.getBehavior(topDownName).simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / framesPerSecond);
|
||||
}
|
||||
expect(player.getX()).to.be.within(250 - epsilon, 250 + epsilon);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -324,7 +324,10 @@ namespace gdjs {
|
||||
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
||||
let directionInRad = 0;
|
||||
let directionInDeg = 0;
|
||||
//Update the speed of the object
|
||||
const previousVelocityX = this._xVelocity;
|
||||
const previousVelocityY = this._yVelocity;
|
||||
|
||||
// Update the speed of the object:
|
||||
if (direction !== -1) {
|
||||
directionInRad =
|
||||
((direction + this._movementAngleOffset / 45) * Math.PI) / 4.0;
|
||||
@@ -344,52 +347,57 @@ namespace gdjs {
|
||||
this._yVelocity += norm * Math.sin(directionInRad);
|
||||
|
||||
this._stickForce = 0;
|
||||
} else {
|
||||
} else if (this._yVelocity !== 0 || this._xVelocity !== 0) {
|
||||
directionInRad = Math.atan2(this._yVelocity, this._xVelocity);
|
||||
directionInDeg =
|
||||
(Math.atan2(this._yVelocity, this._xVelocity) * 180.0) / Math.PI;
|
||||
directionInDeg = (directionInRad * 180.0) / Math.PI;
|
||||
const xVelocityWasPositive = this._xVelocity >= 0;
|
||||
const yVelocityWasPositive = this._yVelocity >= 0;
|
||||
this._xVelocity -=
|
||||
this._deceleration * timeDelta * Math.cos(directionInRad);
|
||||
this._yVelocity -=
|
||||
this._deceleration * timeDelta * Math.sin(directionInRad);
|
||||
// @ts-ignore
|
||||
if ((this._xVelocity > 0) ^ xVelocityWasPositive) {
|
||||
if (this._xVelocity > 0 !== xVelocityWasPositive) {
|
||||
this._xVelocity = 0;
|
||||
}
|
||||
// @ts-ignore
|
||||
if ((this._yVelocity > 0) ^ yVelocityWasPositive) {
|
||||
if (this._yVelocity > 0 !== yVelocityWasPositive) {
|
||||
this._yVelocity = 0;
|
||||
}
|
||||
}
|
||||
const speed = Math.sqrt(
|
||||
this._xVelocity * this._xVelocity + this._yVelocity * this._yVelocity
|
||||
);
|
||||
if (speed > this._maxSpeed) {
|
||||
const squaredSpeed =
|
||||
this._xVelocity * this._xVelocity + this._yVelocity * this._yVelocity;
|
||||
if (squaredSpeed > this._maxSpeed * this._maxSpeed) {
|
||||
this._xVelocity = this._maxSpeed * Math.cos(directionInRad);
|
||||
this._yVelocity = this._maxSpeed * Math.sin(directionInRad);
|
||||
}
|
||||
|
||||
// No acceleration for angular speed for now.
|
||||
this._angularSpeed = this._angularMaxSpeed;
|
||||
|
||||
//No acceleration for angular speed for now
|
||||
|
||||
//Position object
|
||||
// Position object.
|
||||
// This is a Verlet integration considering the acceleration as constant.
|
||||
// If you expand deltaX or deltaY, it gives, thanks to the usage of both
|
||||
// the old and the new velocity:
|
||||
// "velocity * timeDelta + acceleration * timeDelta^2 / 2".
|
||||
//
|
||||
// The acceleration is not actually always constant, particularly with a gamepad,
|
||||
// but the error is multiplied by timDelta^3. So, it shouldn't matter much.
|
||||
const deltaX = ((previousVelocityX + this._xVelocity) / 2) * timeDelta;
|
||||
const deltaY = ((previousVelocityY + this._yVelocity) / 2) * timeDelta;
|
||||
if (this._basisTransformation === null) {
|
||||
// Top-down viewpoint
|
||||
object.setX(object.getX() + this._xVelocity * timeDelta);
|
||||
object.setY(object.getY() + this._yVelocity * timeDelta);
|
||||
object.setX(object.getX() + deltaX);
|
||||
object.setY(object.getY() + deltaY);
|
||||
} else {
|
||||
// Isometry viewpoint
|
||||
const point = this._temporaryPointForTransformations;
|
||||
point[0] = this._xVelocity * timeDelta;
|
||||
point[1] = this._yVelocity * timeDelta;
|
||||
point[0] = deltaX;
|
||||
point[1] = deltaY;
|
||||
this._basisTransformation.toScreen(point, point);
|
||||
object.setX(object.getX() + point[0]);
|
||||
object.setY(object.getY() + point[1]);
|
||||
}
|
||||
|
||||
//Also update angle if needed
|
||||
// Also update angle if needed.
|
||||
if (this._xVelocity !== 0 || this._yVelocity !== 0) {
|
||||
this._angle = directionInDeg;
|
||||
if (this._rotateObject) {
|
||||
|
@@ -63,7 +63,7 @@ module.exports = {
|
||||
_('Tween'),
|
||||
'Tween',
|
||||
_(
|
||||
'Smoothly animate position, angle, scale and other properties of the object.'
|
||||
'Smoothly animate position, angle, scale and other properties of objects.'
|
||||
),
|
||||
'',
|
||||
'JsPlatform/Extensions/tween_behavior32.png',
|
||||
|
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2013 Jeremy Kahn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user