mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
134 Commits
experiment
...
v5.5.238
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c481ecd6b5 | ||
![]() |
e0898dd9b0 | ||
![]() |
5561334efa | ||
![]() |
6c4bb4f79e | ||
![]() |
8b2d2e2fe7 | ||
![]() |
49d128c964 | ||
![]() |
f24d1e0916 | ||
![]() |
9faa4c0c69 | ||
![]() |
a04b8f65db | ||
![]() |
e1cf7d23cd | ||
![]() |
b74b221844 | ||
![]() |
38affc15b4 | ||
![]() |
948488d92b | ||
![]() |
f5902d0346 | ||
![]() |
f28dc8e88a | ||
![]() |
1f41749fa3 | ||
![]() |
a4908a4d42 | ||
![]() |
aa7754e658 | ||
![]() |
58ea9387aa | ||
![]() |
775266c974 | ||
![]() |
eb9794cd1f | ||
![]() |
130732adde | ||
![]() |
7a98e73d61 | ||
![]() |
1f26b72b4b | ||
![]() |
a15ffb5b47 | ||
![]() |
1a5f72283a | ||
![]() |
0460b283ba | ||
![]() |
e212e7c780 | ||
![]() |
84100fc7cf | ||
![]() |
11a8682b07 | ||
![]() |
d3a0bbdfb1 | ||
![]() |
15f3a45d6a | ||
![]() |
f0a4f352cc | ||
![]() |
d16b3e8154 | ||
![]() |
614fb97288 | ||
![]() |
8a40d3645a | ||
![]() |
2b7dadf2a8 | ||
![]() |
c338e16e4f | ||
![]() |
aded08471d | ||
![]() |
cccb59b1c5 | ||
![]() |
3592fb7e62 | ||
![]() |
307c92991c | ||
![]() |
4b3f077669 | ||
![]() |
352bae518e | ||
![]() |
c958f4d522 | ||
![]() |
35bbb37ad2 | ||
![]() |
1d48acc841 | ||
![]() |
87702edccc | ||
![]() |
1f0ba7c19a | ||
![]() |
b4d08a99ad | ||
![]() |
8acaa06e42 | ||
![]() |
27ee85b5d4 | ||
![]() |
bbe2d1854e | ||
![]() |
d338690ff5 | ||
![]() |
571a6d8c1a | ||
![]() |
ddb5157c0a | ||
![]() |
64f01354bc | ||
![]() |
37fd99e542 | ||
![]() |
23be4a5849 | ||
![]() |
64c0ee8f98 | ||
![]() |
e5ecce3abf | ||
![]() |
5c71a4da56 | ||
![]() |
dff99b79cb | ||
![]() |
5fe46ea8ea | ||
![]() |
4a590adac4 | ||
![]() |
279d41cdb7 | ||
![]() |
5cf65a9f62 | ||
![]() |
08b05c13b6 | ||
![]() |
eb55c85f4e | ||
![]() |
8a243440db | ||
![]() |
b3e4e6b89c | ||
![]() |
a1a25f6df4 | ||
![]() |
6114a6cec1 | ||
![]() |
5058964937 | ||
![]() |
4488675540 | ||
![]() |
6a2d2c9e67 | ||
![]() |
b43c42d763 | ||
![]() |
69112183d4 | ||
![]() |
a4c2778b8d | ||
![]() |
f26e56c3bf | ||
![]() |
f5f9944fc4 | ||
![]() |
9467caf1e9 | ||
![]() |
00376f39d5 | ||
![]() |
40b6a34dc5 | ||
![]() |
17d2b8c2c2 | ||
![]() |
935af42d23 | ||
![]() |
d4a8d468cb | ||
![]() |
b16099aee0 | ||
![]() |
c17b918a43 | ||
![]() |
d58e8c7ef9 | ||
![]() |
ddd6b6e3a8 | ||
![]() |
e629c132ea | ||
![]() |
b80e03f153 | ||
![]() |
11e36ff3f1 | ||
![]() |
22de356413 | ||
![]() |
caefa04fbe | ||
![]() |
cf2e7d67d7 | ||
![]() |
685e444b2d | ||
![]() |
a9c1045afd | ||
![]() |
24e0d37583 | ||
![]() |
d44997d372 | ||
![]() |
062aa888f8 | ||
![]() |
de4c2ae4ad | ||
![]() |
29ad7308c3 | ||
![]() |
19b21c280e | ||
![]() |
fbfe8b246a | ||
![]() |
73f66eb51f | ||
![]() |
d62ba2b9a0 | ||
![]() |
323a2b6c2f | ||
![]() |
8e4cccd562 | ||
![]() |
795795ba40 | ||
![]() |
4af86b36e5 | ||
![]() |
b00632a625 | ||
![]() |
6f23f76441 | ||
![]() |
a6cd4b3c5d | ||
![]() |
81d63c41b6 | ||
![]() |
a924840228 | ||
![]() |
b013297c8e | ||
![]() |
ca77a31037 | ||
![]() |
5adb2240d5 | ||
![]() |
9d42be3362 | ||
![]() |
21201dec29 | ||
![]() |
08229cbe1d | ||
![]() |
96e9dd7c4b | ||
![]() |
7dbc687200 | ||
![]() |
7e1f2c6c97 | ||
![]() |
37cba12e4a | ||
![]() |
cdd80bca9e | ||
![]() |
3293d24c36 | ||
![]() |
bf31781d7a | ||
![]() |
f6c43b2db3 | ||
![]() |
f00156a654 | ||
![]() |
41fd1cbcee | ||
![]() |
52c807d74a |
@@ -13,17 +13,18 @@ orbs:
|
||||
aws-cli: circleci/aws-cli@2.0.6
|
||||
macos: circleci/macos@2.5.1 # For Rosetta (see below)
|
||||
node: circleci/node@5.2.0 # For a recent npm version (see below)
|
||||
win: circleci/windows@5.1.0
|
||||
jobs:
|
||||
# Build the **entire** app for macOS.
|
||||
# Build the **entire** app for macOS (including the GDevelop.js library).
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 14.2.0
|
||||
resource_class: macos.m1.large.gen1
|
||||
xcode: 16.4.0
|
||||
resource_class: m4pro.medium
|
||||
steps:
|
||||
- checkout
|
||||
# Install Rosetta for AWS CLI and disable TSO to speed up S3 uploads (https://support.circleci.com/hc/en-us/articles/19334402064027-Troubleshooting-slow-uploads-to-S3-for-jobs-using-an-m1-macOS-resource-class)
|
||||
- macos/install-rosetta
|
||||
- run: sudo sysctl net.inet.tcp.tso=0
|
||||
# - run: sudo sysctl net.inet.tcp.tso=0
|
||||
|
||||
# Install a recent version of npm to workaround a notarization issue because of a symlink made by npm: https://github.com/electron-userland/electron-builder/issues/7755
|
||||
# Node.js v20.14.0 comes with npm v10.7.0.
|
||||
@@ -46,9 +47,9 @@ jobs:
|
||||
# GDevelop.js dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- gd-macos-nodejs-dependencies---
|
||||
- gd-macos-nodejs-dependencies-
|
||||
|
||||
- run:
|
||||
name: Install GDevelop.js dependencies
|
||||
@@ -69,7 +70,8 @@ jobs:
|
||||
- newIDE/electron-app/node_modules
|
||||
- newIDE/app/node_modules
|
||||
- GDevelop.js/node_modules
|
||||
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- GDJS/node_modules
|
||||
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
|
||||
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
|
||||
# Note: Code signing is done using CSC_LINK (see https://www.electron.build/code-signing).
|
||||
@@ -86,15 +88,37 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: newIDE/electron-app/dist
|
||||
|
||||
|
||||
# Upload artifacts (AWS)
|
||||
- run:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/
|
||||
command: |
|
||||
export PATH=~/.local/bin:$PATH
|
||||
for i in 1 2 3 4 5 6 7; do
|
||||
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/ && break
|
||||
echo "Retry $i failed... retrying in 10 seconds"
|
||||
sleep 10
|
||||
done
|
||||
if [ $i -eq 7 ]; then
|
||||
echo "All retries for deployment failed!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- run:
|
||||
name: Deploy to S3 (latest)
|
||||
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
|
||||
command: |
|
||||
export PATH=~/.local/bin:$PATH
|
||||
for i in 1 2 3 4 5 6 7; do
|
||||
aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/ && break
|
||||
echo "Retry $i failed... retrying in 10 seconds"
|
||||
sleep 10
|
||||
done
|
||||
if [ $i -eq 7 ]; then
|
||||
echo "All retries for deployment failed!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the **entire** app for Linux.
|
||||
# Build the app for Linux (using a pre-built GDevelop.js library).
|
||||
build-linux:
|
||||
# CircleCI docker workers are failing if they don't have enough memory (no swap)
|
||||
resource_class: xlarge
|
||||
@@ -107,51 +131,33 @@ jobs:
|
||||
- checkout
|
||||
- aws-cli/setup
|
||||
|
||||
# System dependencies (for Electron Builder and Emscripten)
|
||||
# System dependencies (for Electron Builder)
|
||||
- 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 3.1.21 && ./emsdk activate 3.1.21 && cd ..
|
||||
name: Update system dependencies
|
||||
command: sudo apt-get update
|
||||
|
||||
- run:
|
||||
name: Install system dependencies for Electron builder
|
||||
command: sudo apt install icnsutils && sudo apt install graphicsmagick && sudo apt install rsync
|
||||
|
||||
# GDevelop.js dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- gd-linux-nodejs-dependencies---
|
||||
- gd-linux-nodejs-dependencies-
|
||||
|
||||
- run:
|
||||
name: Install GDevelop.js dependencies and build it
|
||||
command: cd GDevelop.js && npm install && cd ..
|
||||
|
||||
# Build GDevelop.js (and run tests to ensure it works)
|
||||
- run:
|
||||
name: Build GDevelop.js
|
||||
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
|
||||
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
|
||||
|
||||
# GDevelop IDE dependencies (after building GDevelop.js to avoid downloading a pre-built version)
|
||||
# GDevelop IDE dependencies (using an exact version of GDevelop.js, built previously)
|
||||
- run:
|
||||
name: Install GDevelop IDE dependencies
|
||||
command: cd newIDE/app && npm install && cd ../electron-app && npm install
|
||||
command: export REQUIRES_EXACT_LIBGD_JS_VERSION=true && cd newIDE/app && npm install && cd ../electron-app && npm install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- newIDE/electron-app/node_modules
|
||||
- newIDE/app/node_modules
|
||||
- GDevelop.js/node_modules
|
||||
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- GDJS/node_modules
|
||||
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
|
||||
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
|
||||
- run:
|
||||
@@ -295,14 +301,203 @@ jobs:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: aws s3 sync Binaries/embuild/GDevelop.js s3://gdevelop-gdevelop.js/$(git rev-parse --abbrev-ref HEAD)/variant/debug-sanitizers/commit/$(git rev-parse HEAD)/
|
||||
|
||||
# Trigger AppVeyor build, which also does a Windows build (keep it for redundancy).
|
||||
trigger-appveyor-windows-build:
|
||||
docker:
|
||||
- image: cimg/node:16.13
|
||||
steps:
|
||||
- run:
|
||||
name: Trigger AppVeyor Windows build
|
||||
command: |
|
||||
curl -H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${APPVEYOR_API_KEY}" \
|
||||
--data "{
|
||||
\"accountName\": \"4ian\",
|
||||
\"projectSlug\": \"gdevelop\",
|
||||
\"branch\": \"${CIRCLE_BRANCH}\"
|
||||
}" \
|
||||
-X POST https://ci.appveyor.com/api/builds
|
||||
|
||||
build-windows:
|
||||
executor:
|
||||
name: win/default
|
||||
size: medium
|
||||
working_directory: /home/circleci/project
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
# See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/
|
||||
#
|
||||
# This is necessary because of "signing to be FIPS-140 compliant". See
|
||||
# https://github.com/electron-userland/electron-builder/issues/6158
|
||||
#
|
||||
# Make sure to DISABLE "malware blocker" in SSL.com to avoid errors like:
|
||||
# Error information: "Error: SignerSign() failed." (-2146893821/0x80090003)
|
||||
name: Download and Unzip eSignerCKA Setup
|
||||
command: |
|
||||
|
||||
Invoke-WebRequest -OutFile eSigner_CKA_1.0.3.zip "https://www.ssl.com/download/ssl-com-esigner-cka-1-0-3"
|
||||
|
||||
Expand-Archive -Force eSigner_CKA_1.0.3.zip
|
||||
|
||||
Remove-Item eSigner_CKA_1.0.3.zip
|
||||
|
||||
Move-Item -Destination "eSigner_CKA_1.0.3.exe" -Path "eSigner_CKA_*\*.exe"
|
||||
- run:
|
||||
name: Setup eSignerCKA in Silent Mode
|
||||
command: |
|
||||
|
||||
mkdir -p "/home/circleci/project/eSignerCKA"
|
||||
|
||||
./eSigner_CKA_1.0.3.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="/home/circleci/project/eSignerCKA" | Out-Null
|
||||
- run:
|
||||
name: Config Account Information on eSignerCKA
|
||||
command: |
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe config -mode product -user "$env:ESIGNER_USER_NAME" -pass "$env:ESIGNER_USER_PASSWORD" -totp "$env:ESIGNER_USER_TOTP" -key "/home/circleci/project/eSignerCKA/master.key" -r
|
||||
- run:
|
||||
name: Load Certificate into Windows Store
|
||||
command: |
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe unload
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe load
|
||||
- run:
|
||||
name: Select Certificate From Windows Store and Sign Sample File with SignTool
|
||||
command: |
|
||||
|
||||
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
|
||||
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
- restore_cache:
|
||||
name: Restore node_modules cache
|
||||
keys:
|
||||
- v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
- v1-win-node-
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
no_output_timeout: 25m
|
||||
# Remove package-lock.json because they seems to cause the npm install to be stuck. We should try again after re-generating them.
|
||||
# Also install setuptools as something requires distutils in electron-app, and it was removed in Python 3.12.
|
||||
# setuptools will make distutils available again (but we should migrate our packages probably).
|
||||
command: |
|
||||
pip install setuptools
|
||||
|
||||
cd newIDE\app
|
||||
|
||||
npm -v
|
||||
|
||||
Remove-Item package-lock.json
|
||||
|
||||
$Env:REQUIRES_EXACT_LIBGD_JS_VERSION = "true"
|
||||
|
||||
npm install
|
||||
|
||||
cd ..\electron-app
|
||||
|
||||
Remove-Item package-lock.json
|
||||
|
||||
npm install
|
||||
|
||||
cd ..\..
|
||||
|
||||
- save_cache:
|
||||
name: Save node_modules cache
|
||||
key: v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
paths:
|
||||
- newIDE/app/node_modules
|
||||
- newIDE/electron-app/node_modules
|
||||
- GDJS/node_modules
|
||||
|
||||
- run:
|
||||
name: Build NSIS executable (with code signing)
|
||||
command: |
|
||||
cd newIDE\electron-app
|
||||
|
||||
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
|
||||
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
# Use a custom signtool path because of the signtool.exe bundled withy electron-builder not working for some reason.
|
||||
# Can also be found in versioned folders like "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe".
|
||||
# or "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\signtool.exe".
|
||||
|
||||
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"
|
||||
|
||||
# Extract thumbprint and subject name of the certificate (will be passed to electron-builder).
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = $CodeSigningCert.Thumbprint
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
|
||||
|
||||
# Build the nsis installer (signed: electron-builder will use SignTool.exe with the certificate)
|
||||
|
||||
node scripts/build.js --win nsis --publish=never
|
||||
|
||||
cd ..\..
|
||||
- run:
|
||||
name: Build AppX (without code signing)
|
||||
# Don't sign the appx (it will be signed by the Microsoft Store).
|
||||
command: |
|
||||
cd newIDE\electron-app
|
||||
|
||||
# Build the appx (not signed). Ensure all variables used for code signing are empty.
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = ''
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
|
||||
|
||||
$Env:CSC_LINK = ''
|
||||
|
||||
$Env:CSC_KEY_PASSWORD = ''
|
||||
|
||||
node scripts/build.js --skip-app-build --win appx --publish=never
|
||||
|
||||
cd ..\..
|
||||
|
||||
- run:
|
||||
name: Clean binaries
|
||||
shell: cmd.exe
|
||||
command: |
|
||||
rmdir /s /q newIDE\electron-app\dist\win-unpacked
|
||||
|
||||
- run:
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
# Install the CLI for the current user
|
||||
|
||||
pip install --quiet --upgrade --user awscli
|
||||
|
||||
# Add the user-Scripts dir to PATH for this step and the next.
|
||||
|
||||
$binDir = (python -m site --user-base) + "\Scripts"
|
||||
$Env:Path += ";$binDir"
|
||||
|
||||
# Sanity check:
|
||||
aws --version
|
||||
|
||||
# Upload artifacts (S3)
|
||||
- run:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: |
|
||||
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/commit/$Env:CIRCLE_SHA1/"
|
||||
|
||||
- run:
|
||||
name: Deploy to S3 (latest)
|
||||
command: |
|
||||
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/latest/"
|
||||
|
||||
# Upload artifacts (CircleCI)
|
||||
- store_artifacts:
|
||||
path: newIDE/electron-app/dist
|
||||
|
||||
workflows:
|
||||
gdevelop_js-wasm:
|
||||
jobs:
|
||||
- build-gdevelop_js-wasm-only
|
||||
gdevelop_js-wasm-extra-checks:
|
||||
jobs:
|
||||
- build-gdevelop_js-debug-sanitizers-and-extra-checks:
|
||||
# Extra checks are resource intensive so don't all run them.
|
||||
# Extra checks are resource intensive so don't always run them.
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
@@ -310,13 +505,36 @@ workflows:
|
||||
- /experimental-build.*/
|
||||
builds:
|
||||
jobs:
|
||||
- build-gdevelop_js-wasm-only
|
||||
- build-macos:
|
||||
# The macOS version builds by itself GDevelop.js
|
||||
# (so we verify we can build it on macOS).
|
||||
# requires:
|
||||
# - build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- build-linux:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- build-windows:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- trigger-appveyor-windows-build:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Thumbs.db
|
||||
.claude
|
||||
|
3
.vscode/tasks.json
vendored
3
.vscode/tasks.json
vendored
@@ -38,8 +38,7 @@
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"isBackground": true,
|
||||
"runOptions": { "instanceLimit": 1, "runOn": "folderOpen" }
|
||||
"isBackground": true
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
|
@@ -61,10 +61,12 @@ void GroupEvent::UnserializeFrom(gd::Project& project,
|
||||
project, events, element.GetChild("events"));
|
||||
|
||||
parameters.clear();
|
||||
gd::SerializerElement& parametersElement = element.GetChild("parameters");
|
||||
parametersElement.ConsiderAsArrayOf("parameters");
|
||||
for (std::size_t i = 0; i < parametersElement.GetChildrenCount(); ++i)
|
||||
parameters.push_back(parametersElement.GetChild(i).GetValue().GetString());
|
||||
if (element.HasChild("parameters")) {
|
||||
gd::SerializerElement& parametersElement = element.GetChild("parameters");
|
||||
parametersElement.ConsiderAsArrayOf("parameters");
|
||||
for (std::size_t i = 0; i < parametersElement.GetChildrenCount(); ++i)
|
||||
parameters.push_back(parametersElement.GetChild(i).GetValue().GetString());
|
||||
}
|
||||
}
|
||||
|
||||
void GroupEvent::SetBackgroundColor(unsigned int colorR_,
|
||||
|
@@ -163,6 +163,21 @@ void LinkEvent::UnserializeFrom(gd::Project& project,
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
vector<gd::String> LinkEvent::GetAllSearchableStrings() const {
|
||||
vector<gd::String> allSearchableStrings;
|
||||
|
||||
allSearchableStrings.push_back(target);
|
||||
|
||||
return allSearchableStrings;
|
||||
}
|
||||
|
||||
bool LinkEvent::ReplaceAllSearchableStrings(
|
||||
std::vector<gd::String> newSearchableString) {
|
||||
if (newSearchableString[0] == target) return false;
|
||||
SetTarget(newSearchableString[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {
|
||||
return BaseEvent::AcceptVisitor(eventVisitor) ||
|
||||
eventVisitor.VisitLinkEvent(*this);
|
||||
|
@@ -109,6 +109,10 @@ class GD_CORE_API LinkEvent : public gd::BaseEvent {
|
||||
|
||||
virtual bool IsExecutable() const override { return true; };
|
||||
|
||||
virtual std::vector<gd::String> GetAllSearchableStrings() const override;
|
||||
virtual bool ReplaceAllSearchableStrings(
|
||||
std::vector<gd::String> newSearchableString) override;
|
||||
|
||||
virtual void SerializeTo(SerializerElement& element) const override;
|
||||
virtual void UnserializeFrom(gd::Project& project,
|
||||
const SerializerElement& element) override;
|
||||
|
@@ -286,6 +286,20 @@ class GD_CORE_API BaseEvent {
|
||||
* \brief True if the event should be folded in the events editor.
|
||||
*/
|
||||
bool IsFolded() const { return folded; }
|
||||
|
||||
/**
|
||||
* \brief Set the AI generated event ID.
|
||||
*/
|
||||
void SetAiGeneratedEventId(const gd::String& aiGeneratedEventId_) {
|
||||
aiGeneratedEventId = aiGeneratedEventId_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the AI generated event ID.
|
||||
*/
|
||||
const gd::String& GetAiGeneratedEventId() const {
|
||||
return aiGeneratedEventId;
|
||||
}
|
||||
///@}
|
||||
|
||||
std::weak_ptr<gd::BaseEvent>
|
||||
@@ -304,6 +318,7 @@ class GD_CORE_API BaseEvent {
|
||||
bool disabled; ///< True if the event is disabled and must not be executed
|
||||
gd::String type; ///< Type of the event. Must be assigned at the creation.
|
||||
///< Used for saving the event for instance.
|
||||
gd::String aiGeneratedEventId; ///< When generated by an AI/external tool.
|
||||
|
||||
static gd::EventsList badSubEvents;
|
||||
static gd::VariablesContainer badLocalVariables;
|
||||
|
@@ -221,6 +221,8 @@ void EventsListSerialization::UnserializeEventsFrom(
|
||||
|
||||
event->SetDisabled(eventElem.GetBoolAttribute("disabled", false));
|
||||
event->SetFolded(eventElem.GetBoolAttribute("folded", false));
|
||||
event->SetAiGeneratedEventId(
|
||||
eventElem.GetStringAttribute("aiGeneratedEventId", ""));
|
||||
|
||||
list.InsertEvent(event, list.GetEventsCount());
|
||||
}
|
||||
@@ -236,6 +238,8 @@ void EventsListSerialization::SerializeEventsTo(const EventsList& list,
|
||||
if (event.IsDisabled())
|
||||
eventElem.SetAttribute("disabled", event.IsDisabled());
|
||||
if (event.IsFolded()) eventElem.SetAttribute("folded", event.IsFolded());
|
||||
if (!event.GetAiGeneratedEventId().empty())
|
||||
eventElem.SetAttribute("aiGeneratedEventId", event.GetAiGeneratedEventId());
|
||||
eventElem.AddChild("type").SetValue(event.GetType());
|
||||
|
||||
event.SerializeTo(eventElem);
|
||||
|
@@ -37,8 +37,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.SetIcon("res/actions/position24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Angle"))
|
||||
.SetIcon("res/actions/direction24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
|
||||
.SetIcon("res/actions/scale24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
|
||||
"res/actions/scale24_black.png");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
|
||||
"", _("Base object"), _("Base object"), "res/objeticon24.png");
|
||||
@@ -235,7 +235,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddAction("SetAngle",
|
||||
_("Angle"),
|
||||
_("Change the angle of rotation of an object (in degrees)."),
|
||||
_("Change the angle of rotation of an object (in degrees). For "
|
||||
"3D objects, this is the rotation around the Z axis."),
|
||||
_("the angle"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24_black.png",
|
||||
@@ -250,7 +251,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
obj.AddAction("Rotate",
|
||||
_("Rotate"),
|
||||
_("Rotate an object, clockwise if the speed is positive, "
|
||||
"counterclockwise otherwise."),
|
||||
"counterclockwise otherwise. For 3D objects, this is the "
|
||||
"rotation around the Z axis."),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/rotate24_black.png",
|
||||
@@ -634,7 +636,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddCondition("Angle",
|
||||
_("Angle"),
|
||||
_("Compare the angle of the specified object."),
|
||||
_("Compare the angle, in degrees, of the specified object. "
|
||||
"For 3D objects, this is the angle around the Z axis."),
|
||||
_("the angle (in degrees)"),
|
||||
_("Angle"),
|
||||
"res/conditions/direction24_black.png",
|
||||
@@ -835,14 +838,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.MarkAsAdvanced()
|
||||
.SetRelevantForLayoutEventsOnly();
|
||||
|
||||
obj.AddAction(
|
||||
"PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
obj.AddAction("PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectvar", _("Array variable"))
|
||||
.AddParameter("trueorfalse", _("Boolean to add"))
|
||||
@@ -1268,7 +1270,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddExpression("Angle",
|
||||
_("Angle"),
|
||||
_("Current angle, in degrees, of the object"),
|
||||
_("Current angle, in degrees, of the object. For 3D "
|
||||
"objects, this is the angle around the Z axis."),
|
||||
_("Angle"),
|
||||
"res/actions/direction_black.png")
|
||||
.AddParameter("object", _("Object"));
|
||||
@@ -1571,7 +1574,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
extension
|
||||
.AddAction("Create",
|
||||
_("Create an object"),
|
||||
_("Create an object at specified position"),
|
||||
_("Create an instance of the object at the specified position."
|
||||
"The created object instance will be available for the next "
|
||||
"actions and sub-events."),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ "
|
||||
"(layer: _PARAM4_)"),
|
||||
"",
|
||||
|
@@ -72,7 +72,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("normalize",
|
||||
_("Normalize a value between `min` and `max` to a value between 0 and 1."),
|
||||
_("Normalize a value between `min` and `max` to a value "
|
||||
"between 0 and 1."),
|
||||
_("Remap a value between 0 and 1."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
@@ -124,7 +125,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("mod",
|
||||
_("Modulo"),
|
||||
_("x mod y"),
|
||||
_("Compute \"x mod y\". GDevelop does NOT support the \% "
|
||||
"operator. Use this mod(x, y) function instead."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("x (as in x mod y)"))
|
||||
@@ -184,11 +186,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("asinh",
|
||||
_("Arcsine"),
|
||||
_("Arcsine"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"asinh", _("Arcsine"), _("Arcsine"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -218,11 +217,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("cbrt",
|
||||
_("Cube root"),
|
||||
_("Cube root"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cbrt", _("Cube root"), _("Cube root"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -260,12 +256,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -293,29 +290,20 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("int",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"int", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("rint",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"rint", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("round",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"round", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -324,8 +312,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
_("Round a number to the Nth decimal place"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"))
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
.AddParameter("expression", _("Number to Round"))
|
||||
.AddParameter("expression", _("Decimal Places"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("exp",
|
||||
@@ -336,19 +324,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("log",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"log", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("ln",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"ln", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
@@ -387,11 +369,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("The exponent (n in x^n)"));
|
||||
|
||||
extension
|
||||
.AddExpression("sec",
|
||||
_("Secant"),
|
||||
_("Secant"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sec", _("Secant"), _("Secant"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -403,12 +382,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -428,12 +408,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -463,26 +444,28 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("x (in a+(b-a)*x)"));
|
||||
|
||||
extension
|
||||
.AddExpression("XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
extension
|
||||
.AddExpression("YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
@@ -497,7 +480,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("lerpAngle",
|
||||
_("Lerp (Linear interpolation) between two angles"),
|
||||
_("Linearly interpolates between two angles (in degrees) by taking the shortest direction around the circle."),
|
||||
_("Linearly interpolates between two angles (in degrees) "
|
||||
"by taking the shortest direction around the circle."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Starting angle, in degrees"))
|
||||
|
@@ -779,6 +779,26 @@ gd::String PlatformExtension::GetBehaviorFullType(
|
||||
return extensionName + separator + behaviorName;
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetExtensionFromFullBehaviorType(
|
||||
const gd::String& type) {
|
||||
const auto separatorIndex =
|
||||
type.find(PlatformExtension::GetNamespaceSeparator());
|
||||
if (separatorIndex == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return type.substr(0, separatorIndex);
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetBehaviorNameFromFullBehaviorType(
|
||||
const gd::String& type) {
|
||||
const auto separatorIndex =
|
||||
type.find(PlatformExtension::GetNamespaceSeparator());
|
||||
if (separatorIndex == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return type.substr(separatorIndex + 2);
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
|
@@ -651,6 +651,10 @@ class GD_CORE_API PlatformExtension {
|
||||
static gd::String GetBehaviorFullType(const gd::String& extensionName,
|
||||
const gd::String& behaviorName);
|
||||
|
||||
static gd::String GetExtensionFromFullBehaviorType(const gd::String& type);
|
||||
|
||||
static gd::String GetBehaviorNameFromFullBehaviorType(const gd::String& type);
|
||||
|
||||
static gd::String GetObjectEventsFunctionFullType(
|
||||
const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
|
@@ -63,7 +63,6 @@ void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
|
||||
}
|
||||
|
||||
// Copy missing behaviors
|
||||
auto &behaviors = object.GetAllBehaviorContents();
|
||||
for (const auto &pair : defaultBehaviors) {
|
||||
const auto &behaviorName = pair.first;
|
||||
const auto &defaultBehavior = pair.second;
|
||||
@@ -82,11 +81,9 @@ void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
|
||||
}
|
||||
}
|
||||
// Delete extra behaviors
|
||||
for (auto it = behaviors.begin(); it != behaviors.end(); ++it) {
|
||||
const auto &behaviorName = it->first;
|
||||
for (auto &behaviorName : object.GetAllBehaviorNames()) {
|
||||
if (!defaultObject->HasBehaviorNamed(behaviorName)) {
|
||||
object.RemoveBehavior(behaviorName);
|
||||
--it;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1781,6 +1781,14 @@ void WholeProjectRefactorer::DoRenameBehavior(
|
||||
projectBrowser.ExposeFunctions(project, behaviorParameterRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::UpdateBehaviorsSharedData(gd::Project &project) {
|
||||
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
|
||||
gd::Layout &layout = project.GetLayout(i);
|
||||
|
||||
layout.UpdateBehaviorsSharedData(project);
|
||||
}
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::DoRenameObject(
|
||||
gd::Project &project, const gd::String &oldObjectType,
|
||||
const gd::String &newObjectType, const gd::ProjectBrowser &projectBrowser) {
|
||||
|
@@ -704,6 +704,16 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
static size_t GetLayoutAndExternalLayoutLayerInstancesCount(
|
||||
gd::Project &project, gd::Layout &layout, const gd::String &layerName);
|
||||
|
||||
/**
|
||||
* This ensures that the scenes had an instance of shared data for
|
||||
* every behavior of every object that can be used on the scene
|
||||
* (i.e. the objects of the scene and the global objects)
|
||||
*
|
||||
* Must be called when a behavior have been added/deleted
|
||||
* from a global object or an object has been made global.
|
||||
*/
|
||||
static void UpdateBehaviorsSharedData(gd::Project &project);
|
||||
|
||||
virtual ~WholeProjectRefactorer(){};
|
||||
|
||||
private:
|
||||
|
@@ -250,25 +250,28 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
}
|
||||
const auto &eventsBasedObject = project->GetEventsBasedObject(GetType());
|
||||
|
||||
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
if (IsForcedToOverrideEventsBasedObjectChildrenConfiguration()) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
else if (eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
if (variantName.empty() ||
|
||||
!eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,15 @@ public:
|
||||
variantName = variantName_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy events-based objects don't have any instance in their default
|
||||
* variant since there wasn't a graphical editor at the time. In this case,
|
||||
* the editor doesn't allow to choose a variant, but a variant may have stayed
|
||||
* after a user rolled back the extension. This variant must be ignored.
|
||||
*
|
||||
* @return true when its events-based object doesn't have any initial
|
||||
* instance.
|
||||
*/
|
||||
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
|
||||
|
||||
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
|
||||
|
@@ -8,6 +8,8 @@
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String Effect::badStringParameterValue;
|
||||
|
||||
void Effect::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", GetName());
|
||||
|
@@ -3,8 +3,7 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef GDCORE_EFFECT_H
|
||||
#define GDCORE_EFFECT_H
|
||||
#pragma once
|
||||
#include <map>
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
@@ -35,28 +34,43 @@ class GD_CORE_API Effect {
|
||||
void SetFolded(bool fold = true) { folded = fold; }
|
||||
bool IsFolded() const { return folded; }
|
||||
|
||||
void SetDoubleParameter(const gd::String& name, double value) {
|
||||
void SetDoubleParameter(const gd::String &name, double value) {
|
||||
doubleParameters[name] = value;
|
||||
}
|
||||
|
||||
double GetDoubleParameter(const gd::String& name) {
|
||||
return doubleParameters[name];
|
||||
double GetDoubleParameter(const gd::String &name) const {
|
||||
auto itr = doubleParameters.find(name);
|
||||
return itr == doubleParameters.end() ? 0 : itr->second;
|
||||
}
|
||||
|
||||
void SetStringParameter(const gd::String& name, const gd::String& value) {
|
||||
bool HasDoubleParameter(const gd::String &name) const {
|
||||
return doubleParameters.find(name) != doubleParameters.end();
|
||||
}
|
||||
|
||||
void SetStringParameter(const gd::String &name, const gd::String &value) {
|
||||
stringParameters[name] = value;
|
||||
}
|
||||
|
||||
const gd::String& GetStringParameter(const gd::String& name) {
|
||||
return stringParameters[name];
|
||||
const gd::String &GetStringParameter(const gd::String &name) const {
|
||||
auto itr = stringParameters.find(name);
|
||||
return itr == stringParameters.end() ? badStringParameterValue : itr->second;
|
||||
}
|
||||
|
||||
void SetBooleanParameter(const gd::String& name, bool value) {
|
||||
bool HasStringParameter(const gd::String &name) const {
|
||||
return stringParameters.find(name) != stringParameters.end();
|
||||
}
|
||||
|
||||
void SetBooleanParameter(const gd::String &name, bool value) {
|
||||
booleanParameters[name] = value;
|
||||
}
|
||||
|
||||
bool GetBooleanParameter(const gd::String& name) {
|
||||
return booleanParameters[name];
|
||||
bool GetBooleanParameter(const gd::String &name) const {
|
||||
auto itr = booleanParameters.find(name);
|
||||
return itr == booleanParameters.end() ? false : itr->second;
|
||||
}
|
||||
|
||||
bool HasBooleanParameter(const gd::String &name) const {
|
||||
return booleanParameters.find(name) != booleanParameters.end();
|
||||
}
|
||||
|
||||
const std::map<gd::String, double>& GetAllDoubleParameters() const {
|
||||
@@ -94,7 +108,9 @@ class GD_CORE_API Effect {
|
||||
std::map<gd::String, double> doubleParameters; ///< Values of parameters being doubles, keyed by names.
|
||||
std::map<gd::String, gd::String> stringParameters; ///< Values of parameters being strings, keyed by names.
|
||||
std::map<gd::String, bool> booleanParameters; ///< Values of parameters being booleans, keyed by names.
|
||||
|
||||
static gd::String badStringParameterValue; ///< Empty string returned by
|
||||
///< GeStringParameter
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
#endif
|
||||
|
@@ -83,6 +83,9 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element, bool isEx
|
||||
element.SetAttribute("iconUrl", iconUrl);
|
||||
element.SetAttribute("helpPath", helpPath);
|
||||
element.SetAttribute("gdevelopVersion", gdevelopVersion);
|
||||
if (changelog.GetChangesCount() > 0) {
|
||||
changelog.SerializeTo(element.AddChild("changelog"));
|
||||
}
|
||||
auto& dependenciesElement = element.AddChild("dependencies");
|
||||
dependenciesElement.ConsiderAsArray();
|
||||
for (auto& dependency : dependencies)
|
||||
@@ -139,6 +142,9 @@ void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom(
|
||||
iconUrl = element.GetStringAttribute("iconUrl");
|
||||
helpPath = element.GetStringAttribute("helpPath");
|
||||
gdevelopVersion = element.GetStringAttribute("gdevelopVersion");
|
||||
if (element.HasChild("changelog")) {
|
||||
changelog.UnserializeFrom(element.GetChild("changelog"));
|
||||
}
|
||||
|
||||
if (element.HasChild("origin")) {
|
||||
gd::String originName =
|
||||
|
@@ -12,9 +12,11 @@
|
||||
#include "GDCore/Project/EventsBasedBehavior.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsFunctionsContainer.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtensionChangelog.h"
|
||||
#include "GDCore/Project/VariablesContainer.h"
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Tools/SerializableWithNameList.h"
|
||||
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
class Project;
|
||||
@@ -406,6 +408,7 @@ class GD_CORE_API EventsFunctionsExtension {
|
||||
gd::String helpPath; ///< The relative path to the help for this extension in
|
||||
///< the documentation (or an absolute URL).
|
||||
gd::String gdevelopVersion;
|
||||
gd::EventsFunctionsExtensionChangelog changelog;
|
||||
gd::SerializableWithNameList<EventsBasedBehavior> eventsBasedBehaviors;
|
||||
gd::SerializableWithNameList<EventsBasedObject> eventsBasedObjects;
|
||||
std::vector<gd::DependencyMetadata> dependencies;
|
||||
|
105
Core/GDCore/Project/EventsFunctionsExtensionChangelog.h
Normal file
105
Core/GDCore/Project/EventsFunctionsExtensionChangelog.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* @brief The change of a specific extension version (only the breaking
|
||||
* changes).
|
||||
*/
|
||||
class GD_CORE_API EventsFunctionsExtensionVersionChange {
|
||||
public:
|
||||
EventsFunctionsExtensionVersionChange(){};
|
||||
virtual ~EventsFunctionsExtensionVersionChange(){};
|
||||
|
||||
const gd::String &GetVersion() const { return version; };
|
||||
gd::EventsFunctionsExtensionVersionChange &
|
||||
SetVersion(const gd::String &version_) {
|
||||
version = version_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const gd::String &GetBreakingChangesDescription() const { return version; };
|
||||
gd::EventsFunctionsExtensionVersionChange &
|
||||
GetBreakingChangesDescription(const gd::String &breakingChangesDescription_) {
|
||||
breakingChangesDescription = breakingChangesDescription_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Serialize the EventsFunctionsExtensionVersionChange to the specified
|
||||
* element
|
||||
*/
|
||||
void SerializeTo(gd::SerializerElement &element) const {
|
||||
element.SetAttribute("version", version);
|
||||
element.AddChild("breaking")
|
||||
.SetMultilineStringValue(breakingChangesDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Load the EventsFunctionsExtensionVersionChange from the specified
|
||||
* element.
|
||||
*/
|
||||
void UnserializeFrom(const gd::SerializerElement &element) {
|
||||
version = element.GetStringAttribute("version");
|
||||
breakingChangesDescription =
|
||||
element.GetChild("breaking").GetMultilineStringValue();
|
||||
}
|
||||
|
||||
private:
|
||||
gd::String version;
|
||||
gd::String breakingChangesDescription;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The changelog of an extension (only the breaking changes).
|
||||
*/
|
||||
class GD_CORE_API EventsFunctionsExtensionChangelog {
|
||||
public:
|
||||
EventsFunctionsExtensionChangelog(){};
|
||||
virtual ~EventsFunctionsExtensionChangelog(){};
|
||||
|
||||
/**
|
||||
* \brief Return the number of variants.
|
||||
*/
|
||||
std::size_t GetChangesCount() const { return versionChanges.size(); }
|
||||
|
||||
/**
|
||||
* \brief Serialize the EventsFunctionsExtensionChangelog to the specified
|
||||
* element
|
||||
*/
|
||||
void SerializeTo(gd::SerializerElement &element) const {
|
||||
element.ConsiderAsArray();
|
||||
for (const auto &versionChange : versionChanges) {
|
||||
versionChange.SerializeTo(element.AddChild(""));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Load the EventsFunctionsExtensionChangelog from the specified
|
||||
* element.
|
||||
*/
|
||||
void UnserializeFrom(const gd::SerializerElement &element) {
|
||||
versionChanges.clear();
|
||||
element.ConsiderAsArray();
|
||||
for (std::size_t i = 0; i < element.GetChildrenCount(); ++i) {
|
||||
gd::EventsFunctionsExtensionVersionChange versionChange;
|
||||
versionChange.UnserializeFrom(element.GetChild(i));
|
||||
versionChanges.push_back(versionChange);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<gd::EventsFunctionsExtensionVersionChange> versionChanges;
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -365,6 +365,8 @@ class GD_CORE_API InitialInstance {
|
||||
* the same initial instance between serialization.
|
||||
*/
|
||||
InitialInstance& ResetPersistentUuid();
|
||||
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
|
@@ -36,7 +36,7 @@ namespace gd {
|
||||
|
||||
gd::BehaviorsSharedData Layout::badBehaviorSharedData("", "");
|
||||
|
||||
Layout::Layout(const Layout &other)
|
||||
Layout::Layout(const Layout& other)
|
||||
: objectsContainer(gd::ObjectsContainer::SourceType::Scene) {
|
||||
Init(other);
|
||||
}
|
||||
@@ -54,6 +54,8 @@ Layout::Layout()
|
||||
backgroundColorG(209),
|
||||
backgroundColorB(209),
|
||||
stopSoundsOnStartup(true),
|
||||
resourcesPreloading("inherit"),
|
||||
resourcesUnloading("inherit"),
|
||||
standardSortMethod(true),
|
||||
disableInputWhenNotFocused(true),
|
||||
variables(gd::VariablesContainer::SourceType::Scene),
|
||||
@@ -244,6 +246,10 @@ void Layout::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("title", GetWindowDefaultTitle());
|
||||
element.SetAttribute("standardSortMethod", standardSortMethod);
|
||||
element.SetAttribute("stopSoundsOnStartup", stopSoundsOnStartup);
|
||||
if (resourcesPreloading != "inherit")
|
||||
element.SetAttribute("resourcesPreloading", resourcesPreloading);
|
||||
if (resourcesUnloading != "inherit")
|
||||
element.SetAttribute("resourcesUnloading", resourcesUnloading);
|
||||
element.SetAttribute("disableInputWhenNotFocused",
|
||||
disableInputWhenNotFocused);
|
||||
|
||||
@@ -304,6 +310,10 @@ void Layout::UnserializeFrom(gd::Project& project,
|
||||
element.GetStringAttribute("title", "(No title)", "titre"));
|
||||
standardSortMethod = element.GetBoolAttribute("standardSortMethod");
|
||||
stopSoundsOnStartup = element.GetBoolAttribute("stopSoundsOnStartup");
|
||||
resourcesPreloading =
|
||||
element.GetStringAttribute("resourcesPreloading", "inherit");
|
||||
resourcesUnloading =
|
||||
element.GetStringAttribute("resourcesUnloading", "inherit");
|
||||
disableInputWhenNotFocused =
|
||||
element.GetBoolAttribute("disableInputWhenNotFocused");
|
||||
|
||||
@@ -391,6 +401,8 @@ void Layout::Init(const Layout& other) {
|
||||
standardSortMethod = other.standardSortMethod;
|
||||
title = other.title;
|
||||
stopSoundsOnStartup = other.stopSoundsOnStartup;
|
||||
resourcesPreloading = other.resourcesPreloading;
|
||||
resourcesUnloading = other.resourcesUnloading;
|
||||
disableInputWhenNotFocused = other.disableInputWhenNotFocused;
|
||||
initialInstances = other.initialInstances;
|
||||
layers = other.layers;
|
||||
|
@@ -349,6 +349,36 @@ class GD_CORE_API Layout {
|
||||
* launched
|
||||
*/
|
||||
bool StopSoundsOnStartup() const { return stopSoundsOnStartup; }
|
||||
|
||||
/**
|
||||
* Set when the scene must preload its resources: `at-startup`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
void SetResourcesPreloading(gd::String resourcesPreloading_) {
|
||||
resourcesPreloading = resourcesPreloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scene must preload its resources: `at-startup`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
const gd::String& GetResourcesPreloading() const {
|
||||
return resourcesPreloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the scene must unload its resources: `at-scene-exit`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
void SetResourcesUnloading(gd::String resourcesUnloading_) {
|
||||
resourcesUnloading = resourcesUnloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scene must unload its resources: `at-scene-exit`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
const gd::String& GetResourcesUnloading() const { return resourcesUnloading; }
|
||||
///@}
|
||||
|
||||
/** \name Saving and loading
|
||||
@@ -381,6 +411,10 @@ class GD_CORE_API Layout {
|
||||
behaviorsSharedData; ///< Initial shared datas of behaviors
|
||||
bool stopSoundsOnStartup = true; ///< True to make the scene stop all sounds at
|
||||
///< startup.
|
||||
gd::String
|
||||
resourcesPreloading; ///< `at-startup`, `never` or `inherit` (default).
|
||||
gd::String
|
||||
resourcesUnloading; ///< `at-scene-exit`, `never` or `inherit` (default).
|
||||
bool standardSortMethod = true; ///< True to sort objects using standard sort.
|
||||
bool disableInputWhenNotFocused = true; /// If set to true, the input must be
|
||||
/// disabled when the window do not have the
|
||||
|
@@ -81,6 +81,11 @@ class GD_CORE_API ObjectsContainersList {
|
||||
/**
|
||||
* \brief Return the container of the variables for the specified object or
|
||||
* group of objects.
|
||||
*
|
||||
* \warning In most cases, prefer to use other methods to access variables or use
|
||||
* ObjectVariableHelper::MergeVariableContainers if you know you're dealing with a group.
|
||||
* This is because the variables container of an object group does not exist and the one from
|
||||
* first object of the group will be returned.
|
||||
*/
|
||||
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
|
||||
const gd::String& objectOrGroupName) const;
|
||||
|
@@ -74,7 +74,9 @@ Project::Project()
|
||||
gdMinorVersion(gd::VersionWrapper::Minor()),
|
||||
gdBuildVersion(gd::VersionWrapper::Build()),
|
||||
variables(gd::VariablesContainer::SourceType::Global),
|
||||
objectsContainer(gd::ObjectsContainer::SourceType::Global) {}
|
||||
objectsContainer(gd::ObjectsContainer::SourceType::Global),
|
||||
sceneResourcesPreloading("at-startup"),
|
||||
sceneResourcesUnloading("never") {}
|
||||
|
||||
Project::~Project() {}
|
||||
|
||||
@@ -1166,6 +1168,13 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
else
|
||||
std::cout << "ERROR: The project current platform is NULL.";
|
||||
|
||||
if (sceneResourcesPreloading != "at-startup") {
|
||||
propElement.SetAttribute("sceneResourcesPreloading", sceneResourcesPreloading);
|
||||
}
|
||||
if (sceneResourcesUnloading != "never") {
|
||||
propElement.SetAttribute("sceneResourcesUnloading", sceneResourcesUnloading);
|
||||
}
|
||||
|
||||
resourcesManager.SerializeTo(element.AddChild("resources"));
|
||||
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
|
||||
objectsContainer.SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
|
||||
@@ -1307,6 +1316,9 @@ void Project::Init(const gd::Project& game) {
|
||||
variables = game.GetVariables();
|
||||
|
||||
projectFile = game.GetProjectFile();
|
||||
|
||||
sceneResourcesPreloading = game.sceneResourcesPreloading;
|
||||
sceneResourcesUnloading = game.sceneResourcesUnloading;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -964,6 +964,37 @@ class GD_CORE_API Project {
|
||||
*/
|
||||
ResourcesManager& GetResourcesManager() { return resourcesManager; }
|
||||
|
||||
/**
|
||||
* Set when the scenes must preload their resources: `at-startup`, `never`
|
||||
* (default).
|
||||
*/
|
||||
void SetSceneResourcesPreloading(gd::String sceneResourcesPreloading_) {
|
||||
sceneResourcesPreloading = sceneResourcesPreloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scenes must preload their resources: `at-startup`, `never`
|
||||
* (default).
|
||||
*/
|
||||
const gd::String& GetSceneResourcesPreloading() const {
|
||||
return sceneResourcesPreloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the scenes must unload their resources: `at-scene-exit`, `never`
|
||||
* (default).
|
||||
*/
|
||||
void SetSceneResourcesUnloading(gd::String sceneResourcesUnloading_) {
|
||||
sceneResourcesUnloading = sceneResourcesUnloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scenes must unload their resources: `at-scene-exit`, `never`
|
||||
* (default).
|
||||
*/
|
||||
const gd::String& GetSceneResourcesUnloading() const {
|
||||
return sceneResourcesUnloading;
|
||||
}
|
||||
///@}
|
||||
|
||||
/** \name Variable management
|
||||
@@ -1121,6 +1152,10 @@ class GD_CORE_API Project {
|
||||
ExtensionProperties
|
||||
extensionProperties; ///< The properties of the extensions.
|
||||
gd::WholeProjectDiagnosticReport wholeProjectDiagnosticReport;
|
||||
gd::String sceneResourcesPreloading; ///< `at-startup` or `never`
|
||||
///< (default: `at-startup`).
|
||||
gd::String sceneResourcesUnloading; ///< `at-scene-exit` or `never`
|
||||
///< (default: `never`).
|
||||
mutable unsigned int gdMajorVersion =
|
||||
0; ///< The GD major version used the last
|
||||
///< time the project was saved.
|
||||
|
@@ -21,14 +21,19 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
element.AddChild("unit").SetStringValue(measurementUnit.GetName());
|
||||
}
|
||||
element.AddChild("label").SetStringValue(label);
|
||||
element.AddChild("description").SetStringValue(description);
|
||||
element.AddChild("group").SetStringValue(group);
|
||||
SerializerElement& extraInformationElement =
|
||||
element.AddChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
for (const gd::String& information : extraInformation) {
|
||||
extraInformationElement.AddChild("").SetStringValue(information);
|
||||
if (!description.empty())
|
||||
element.AddChild("description").SetStringValue(description);
|
||||
if (!group.empty()) element.AddChild("group").SetStringValue(group);
|
||||
|
||||
if (!extraInformation.empty()) {
|
||||
SerializerElement& extraInformationElement =
|
||||
element.AddChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
for (const gd::String& information : extraInformation) {
|
||||
extraInformationElement.AddChild("").SetStringValue(information);
|
||||
}
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
}
|
||||
@@ -59,16 +64,21 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
: gd::MeasurementUnit::GetUndefined();
|
||||
}
|
||||
label = element.GetChild("label").GetStringValue();
|
||||
description = element.GetChild("description").GetStringValue();
|
||||
group = element.GetChild("group").GetStringValue();
|
||||
description = element.HasChild("description")
|
||||
? element.GetChild("description").GetStringValue()
|
||||
: "";
|
||||
group = element.HasChild("group") ? element.GetChild("group").GetStringValue()
|
||||
: "";
|
||||
|
||||
extraInformation.clear();
|
||||
const SerializerElement& extraInformationElement =
|
||||
element.GetChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
for (std::size_t i = 0; i < extraInformationElement.GetChildrenCount(); ++i)
|
||||
extraInformation.push_back(
|
||||
extraInformationElement.GetChild(i).GetStringValue());
|
||||
if (element.HasChild("extraInformation")) {
|
||||
const SerializerElement& extraInformationElement =
|
||||
element.GetChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
for (std::size_t i = 0; i < extraInformationElement.GetChildrenCount(); ++i)
|
||||
extraInformation.push_back(
|
||||
extraInformationElement.GetChild(i).GetStringValue());
|
||||
}
|
||||
|
||||
hidden = element.HasChild("hidden")
|
||||
? element.GetChild("hidden").GetBoolValue()
|
||||
|
@@ -7,9 +7,9 @@
|
||||
#define GDCORE_PROPERTYDESCRIPTOR
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Project/MeasurementUnit.h"
|
||||
#include "GDCore/Project/QuickCustomization.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
@@ -17,6 +17,19 @@ class SerializerElement;
|
||||
|
||||
namespace gd {
|
||||
|
||||
class GD_CORE_API PropertyDescriptorChoice {
|
||||
public:
|
||||
PropertyDescriptorChoice(const gd::String& value, const gd::String& label)
|
||||
: value(value), label(label) {}
|
||||
|
||||
const gd::String& GetValue() const { return value; }
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
|
||||
private:
|
||||
gd::String value;
|
||||
gd::String label;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Used to describe a property shown in a property grid.
|
||||
* \see gd::Object
|
||||
@@ -31,8 +44,12 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \param propertyValue The value of the property.
|
||||
*/
|
||||
PropertyDescriptor(gd::String propertyValue)
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false),
|
||||
deprecated(false), advanced(false),
|
||||
: currentValue(propertyValue),
|
||||
type("string"),
|
||||
label(""),
|
||||
hidden(false),
|
||||
deprecated(false),
|
||||
advanced(false),
|
||||
hasImpactOnOtherProperties(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()),
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
|
||||
@@ -41,10 +58,13 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \brief Empty constructor creating an empty property to be displayed.
|
||||
*/
|
||||
PropertyDescriptor()
|
||||
: hidden(false), deprecated(false), advanced(false),
|
||||
: hidden(false),
|
||||
deprecated(false),
|
||||
advanced(false),
|
||||
hasImpactOnOtherProperties(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()),
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
@@ -88,13 +108,20 @@ class GD_CORE_API PropertyDescriptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change the group where this property is displayed to the user, if any.
|
||||
* \brief Change the group where this property is displayed to the user, if
|
||||
* any.
|
||||
*/
|
||||
PropertyDescriptor& SetGroup(gd::String group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PropertyDescriptor& AddChoice(const gd::String& value,
|
||||
const gd::String& label) {
|
||||
choices.push_back(PropertyDescriptorChoice(value, label));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set and replace the additional information for the property.
|
||||
*/
|
||||
@@ -118,7 +145,8 @@ class GD_CORE_API PropertyDescriptor {
|
||||
/**
|
||||
* \brief Change the unit of measurement of the property value.
|
||||
*/
|
||||
PropertyDescriptor& SetMeasurementUnit(const gd::MeasurementUnit &measurementUnit_) {
|
||||
PropertyDescriptor& SetMeasurementUnit(
|
||||
const gd::MeasurementUnit& measurementUnit_) {
|
||||
measurementUnit = measurementUnit_;
|
||||
return *this;
|
||||
}
|
||||
@@ -128,14 +156,18 @@ class GD_CORE_API PropertyDescriptor {
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
const gd::String& GetDescription() const { return description; }
|
||||
const gd::String& GetGroup() const { return group; }
|
||||
const gd::MeasurementUnit& GetMeasurementUnit() const { return measurementUnit; }
|
||||
const gd::MeasurementUnit& GetMeasurementUnit() const {
|
||||
return measurementUnit;
|
||||
}
|
||||
|
||||
const std::vector<gd::String>& GetExtraInfo() const {
|
||||
return extraInformation;
|
||||
}
|
||||
|
||||
std::vector<gd::String>& GetExtraInfo() {
|
||||
return extraInformation;
|
||||
std::vector<gd::String>& GetExtraInfo() { return extraInformation; }
|
||||
|
||||
const std::vector<PropertyDescriptorChoice>& GetChoices() const {
|
||||
return choices;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,23 +210,26 @@ class GD_CORE_API PropertyDescriptor {
|
||||
bool IsAdvanced() const { return advanced; }
|
||||
|
||||
/**
|
||||
* \brief Check if the property has impact on other properties - which means a change
|
||||
* must re-render other properties.
|
||||
* \brief Check if the property has impact on other properties - which means a
|
||||
* change must re-render other properties.
|
||||
*/
|
||||
bool HasImpactOnOtherProperties() const { return hasImpactOnOtherProperties; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property has impact on other properties - which means a change
|
||||
* must re-render other properties.
|
||||
* \brief Set if the property has impact on other properties - which means a
|
||||
* change must re-render other properties.
|
||||
*/
|
||||
PropertyDescriptor& SetHasImpactOnOtherProperties(bool enable) {
|
||||
hasImpactOnOtherProperties = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
|
||||
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
|
||||
return quickCustomizationVisibility;
|
||||
}
|
||||
|
||||
PropertyDescriptor& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
|
||||
PropertyDescriptor& SetQuickCustomizationVisibility(
|
||||
QuickCustomization::Visibility visibility) {
|
||||
quickCustomizationVisibility = visibility;
|
||||
return *this;
|
||||
}
|
||||
@@ -231,15 +266,17 @@ class GD_CORE_API PropertyDescriptor {
|
||||
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<PropertyDescriptorChoice>
|
||||
choices; //< The optional choices for the property.
|
||||
std::vector<gd::String>
|
||||
extraInformation; ///< Can be used to store for example the available
|
||||
///< choices, if a property is a displayed as a combo
|
||||
///< box.
|
||||
extraInformation; ///< Can be used to store an additional information
|
||||
///< like an object type.
|
||||
bool hidden;
|
||||
bool deprecated;
|
||||
bool advanced;
|
||||
bool hasImpactOnOtherProperties;
|
||||
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
|
||||
gd::MeasurementUnit
|
||||
measurementUnit; //< The unit of measurement of the property vale.
|
||||
QuickCustomization::Visibility quickCustomizationVisibility;
|
||||
};
|
||||
|
||||
|
@@ -5,8 +5,6 @@ namespace gdjs {
|
||||
type Object3DNetworkSyncDataType = {
|
||||
// z is position on the Z axis, different from zo, which is Z order
|
||||
z: number;
|
||||
w: number;
|
||||
h: number;
|
||||
d: number;
|
||||
rx: number;
|
||||
ry: number;
|
||||
@@ -116,8 +114,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
z: this.getZ(),
|
||||
w: this.getWidth(),
|
||||
h: this.getHeight(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
ry: this.getRotationY(),
|
||||
@@ -130,8 +126,6 @@ namespace gdjs {
|
||||
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.w !== undefined) this.setWidth(networkSyncData.w);
|
||||
if (networkSyncData.h !== undefined) this.setHeight(networkSyncData.h);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
this.setRotationX(networkSyncData.rx);
|
||||
|
@@ -25,6 +25,8 @@ namespace gdjs {
|
||||
topFaceVisible: boolean;
|
||||
bottomFaceVisible: boolean;
|
||||
tint: string | undefined;
|
||||
isCastingShadow: boolean;
|
||||
isReceivingShadow: boolean;
|
||||
materialType: 'Basic' | 'StandardWithoutMetalness';
|
||||
};
|
||||
}
|
||||
@@ -71,8 +73,10 @@ namespace gdjs {
|
||||
string,
|
||||
];
|
||||
_materialType: gdjs.Cube3DRuntimeObject.MaterialType =
|
||||
gdjs.Cube3DRuntimeObject.MaterialType.Basic;
|
||||
gdjs.Cube3DRuntimeObject.MaterialType.StandardWithoutMetalness;
|
||||
_tint: string;
|
||||
_isCastingShadow: boolean = true;
|
||||
_isReceivingShadow: boolean = true;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -121,6 +125,8 @@ namespace gdjs {
|
||||
];
|
||||
|
||||
this._tint = objectData.content.tint || '255;255;255';
|
||||
this._isCastingShadow = objectData.content.isCastingShadow || false;
|
||||
this._isReceivingShadow = objectData.content.isReceivingShadow || false;
|
||||
|
||||
this._materialType = this._convertMaterialType(
|
||||
objectData.content.materialType
|
||||
@@ -430,6 +436,18 @@ namespace gdjs {
|
||||
) {
|
||||
this.setMaterialType(newObjectData.content.materialType);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isCastingShadow !==
|
||||
newObjectData.content.isCastingShadow
|
||||
) {
|
||||
this.updateShadowCasting(newObjectData.content.isCastingShadow);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isReceivingShadow !==
|
||||
newObjectData.content.isReceivingShadow
|
||||
) {
|
||||
this.updateShadowReceiving(newObjectData.content.isReceivingShadow);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -531,6 +549,14 @@ namespace gdjs {
|
||||
this._materialType = newMaterialType;
|
||||
this._renderer._updateMaterials();
|
||||
}
|
||||
updateShadowCasting(value: boolean) {
|
||||
this._isCastingShadow = value;
|
||||
this._renderer.updateShadowCasting();
|
||||
}
|
||||
updateShadowReceiving(value: boolean) {
|
||||
this._isReceivingShadow = value;
|
||||
this._renderer.updateShadowReceiving();
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Cube3DRuntimeObject {
|
||||
|
@@ -81,13 +81,14 @@ namespace gdjs {
|
||||
.map((_, index) =>
|
||||
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[index])
|
||||
);
|
||||
|
||||
const boxMesh = new THREE.Mesh(geometry, materials);
|
||||
|
||||
super(runtimeObject, instanceContainer, boxMesh);
|
||||
this._boxMesh = boxMesh;
|
||||
this._cube3DRuntimeObject = runtimeObject;
|
||||
|
||||
boxMesh.receiveShadow = this._cube3DRuntimeObject._isReceivingShadow;
|
||||
boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
||||
this.updateSize();
|
||||
this.updatePosition();
|
||||
this.updateRotation();
|
||||
@@ -114,6 +115,13 @@ namespace gdjs {
|
||||
new THREE.BufferAttribute(new Float32Array(tints), 3)
|
||||
);
|
||||
}
|
||||
updateShadowCasting() {
|
||||
this._boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
||||
}
|
||||
updateShadowReceiving() {
|
||||
this._boxMesh.receiveShadow =
|
||||
this._cube3DRuntimeObject._isReceivingShadow;
|
||||
}
|
||||
|
||||
updateFace(faceIndex: integer) {
|
||||
const materialIndex = faceIndexToMaterialIndex[faceIndex];
|
||||
|
@@ -1,4 +1,12 @@
|
||||
namespace gdjs {
|
||||
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
|
||||
z: float;
|
||||
d: float;
|
||||
rx: float;
|
||||
ry: float;
|
||||
ifz: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for 3D custom objects.
|
||||
*/
|
||||
@@ -34,7 +42,6 @@ namespace gdjs {
|
||||
objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this._renderer.reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender() {
|
||||
@@ -78,6 +85,30 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
z: this.getZ(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
ry: this.getRotationY(),
|
||||
ifz: this.isFlippedZ(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObject3DNetworkSyncDataType
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
this.setRotationX(networkSyncData.rx);
|
||||
if (networkSyncData.ry !== undefined)
|
||||
this.setRotationY(networkSyncData.ry);
|
||||
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object position on the Z axis.
|
||||
*/
|
||||
|
@@ -44,10 +44,7 @@ namespace gdjs {
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
this._threeGroup.clear();
|
||||
}
|
||||
|
||||
_updateThreeGroup() {
|
||||
|
@@ -6,6 +6,7 @@ namespace gdjs {
|
||||
r: number;
|
||||
t: string;
|
||||
}
|
||||
const shadowHelper = false;
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::DirectionalLight',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
@@ -17,19 +18,63 @@ namespace gdjs {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
light: THREE.DirectionalLight;
|
||||
rotationObject: THREE.Group;
|
||||
_isEnabled: boolean = false;
|
||||
top: string = 'Y-';
|
||||
elevation: float = 45;
|
||||
rotation: float = 0;
|
||||
private _top: string = 'Z+';
|
||||
private _elevation: float = 45;
|
||||
private _rotation: float = 0;
|
||||
private _shadowMapSize: float = 1024;
|
||||
private _minimumShadowBias: float = 0;
|
||||
private _distanceFromCamera: float = 1500;
|
||||
private _frustumSize: float = 4000;
|
||||
|
||||
private _isEnabled: boolean = false;
|
||||
private _light: THREE.DirectionalLight;
|
||||
private _shadowMapDirty = true;
|
||||
private _shadowCameraDirty = true;
|
||||
private _shadowCameraHelper: THREE.CameraHelper | null;
|
||||
|
||||
constructor() {
|
||||
this.light = new THREE.DirectionalLight();
|
||||
this.light.position.set(1, 0, 0);
|
||||
this.rotationObject = new THREE.Group();
|
||||
this.rotationObject.add(this.light);
|
||||
this.updateRotation();
|
||||
this._light = new THREE.DirectionalLight();
|
||||
|
||||
if (shadowHelper) {
|
||||
this._shadowCameraHelper = new THREE.CameraHelper(
|
||||
this._light.shadow.camera
|
||||
);
|
||||
} else {
|
||||
this._shadowCameraHelper = null;
|
||||
}
|
||||
|
||||
this._light.shadow.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
private _updateShadowCamera(): void {
|
||||
if (!this._shadowCameraDirty) {
|
||||
return;
|
||||
}
|
||||
this._shadowCameraDirty = false;
|
||||
|
||||
this._light.shadow.camera.near = 1;
|
||||
this._light.shadow.camera.far = this._distanceFromCamera + 10000;
|
||||
this._light.shadow.camera.right = this._frustumSize / 2;
|
||||
this._light.shadow.camera.left = -this._frustumSize / 2;
|
||||
this._light.shadow.camera.top = this._frustumSize / 2;
|
||||
this._light.shadow.camera.bottom = -this._frustumSize / 2;
|
||||
}
|
||||
|
||||
private _updateShadowMapSize(): void {
|
||||
if (!this._shadowMapDirty) {
|
||||
return;
|
||||
}
|
||||
this._shadowMapDirty = false;
|
||||
|
||||
this._light.shadow.mapSize.set(
|
||||
this._shadowMapSize,
|
||||
this._shadowMapSize
|
||||
);
|
||||
|
||||
// Force the recreation of the shadow map texture:
|
||||
this._light.shadow.map?.dispose();
|
||||
this._light.shadow.map = null;
|
||||
this._light.shadow.needsUpdate = true;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
@@ -53,7 +98,12 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.add(this.rotationObject);
|
||||
scene.add(this._light);
|
||||
scene.add(this._light.target);
|
||||
if (this._shadowCameraHelper) {
|
||||
scene.add(this._shadowCameraHelper);
|
||||
}
|
||||
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
@@ -65,82 +115,164 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.remove(this.rotationObject);
|
||||
scene.remove(this._light);
|
||||
scene.remove(this._light.target);
|
||||
if (this._shadowCameraHelper) {
|
||||
scene.remove(this._shadowCameraHelper);
|
||||
}
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {
|
||||
// Apply any update to the camera or shadow map size.
|
||||
this._updateShadowCamera();
|
||||
this._updateShadowMapSize();
|
||||
|
||||
// Avoid shadow acne due to depth buffer precision.
|
||||
const biasMultiplier =
|
||||
this._shadowMapSize < 1024
|
||||
? 2
|
||||
: this._shadowMapSize < 2048
|
||||
? 1.25
|
||||
: 1;
|
||||
this._light.shadow.bias = -this._minimumShadowBias * biasMultiplier;
|
||||
|
||||
// Apply update to the light position and its target.
|
||||
// By doing this, the shadows are "following" the GDevelop camera.
|
||||
if (!target.getRuntimeLayer) {
|
||||
return;
|
||||
}
|
||||
const layer = target.getRuntimeLayer();
|
||||
const x = layer.getCameraX();
|
||||
const y = layer.getCameraY();
|
||||
const z = layer.getCameraZ(layer.getInitialCamera3DFieldOfView());
|
||||
|
||||
const roundedX = Math.floor(x / 100) * 100;
|
||||
const roundedY = Math.floor(y / 100) * 100;
|
||||
const roundedZ = Math.floor(z / 100) * 100;
|
||||
if (this._top === 'Y-') {
|
||||
const posLightX =
|
||||
roundedX +
|
||||
this._distanceFromCamera *
|
||||
Math.cos(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightY =
|
||||
roundedY -
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._elevation));
|
||||
const posLightZ =
|
||||
roundedZ +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
this._light.position.set(posLightX, posLightY, posLightZ);
|
||||
this._light.target.position.set(roundedX, roundedY, roundedZ);
|
||||
} else {
|
||||
const posLightX =
|
||||
roundedX +
|
||||
this._distanceFromCamera *
|
||||
Math.cos(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightY =
|
||||
roundedY +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightZ =
|
||||
roundedZ +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._elevation));
|
||||
|
||||
this._light.position.set(posLightX, posLightY, posLightZ);
|
||||
this._light.target.position.set(roundedX, roundedY, roundedZ);
|
||||
}
|
||||
}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'intensity') {
|
||||
this.light.intensity = value;
|
||||
this._light.intensity = value;
|
||||
} else if (parameterName === 'elevation') {
|
||||
this.elevation = value;
|
||||
this.updateRotation();
|
||||
this._elevation = value;
|
||||
} else if (parameterName === 'rotation') {
|
||||
this.rotation = value;
|
||||
this.updateRotation();
|
||||
this._rotation = value;
|
||||
} else if (parameterName === 'distanceFromCamera') {
|
||||
this._distanceFromCamera = value;
|
||||
} else if (parameterName === 'frustumSize') {
|
||||
this._frustumSize = value;
|
||||
} else if (parameterName === 'minimumShadowBias') {
|
||||
this._minimumShadowBias = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'intensity') {
|
||||
return this.light.intensity;
|
||||
return this._light.intensity;
|
||||
} else if (parameterName === 'elevation') {
|
||||
return this.elevation;
|
||||
return this._elevation;
|
||||
} else if (parameterName === 'rotation') {
|
||||
return this.rotation;
|
||||
return this._rotation;
|
||||
} else if (parameterName === 'distanceFromCamera') {
|
||||
return this._distanceFromCamera;
|
||||
} else if (parameterName === 'frustumSize') {
|
||||
return this._frustumSize;
|
||||
} else if (parameterName === 'minimumShadowBias') {
|
||||
return this._minimumShadowBias;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {
|
||||
if (parameterName === 'color') {
|
||||
this.light.color = new THREE.Color(
|
||||
this._light.color = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'top') {
|
||||
this.top = value;
|
||||
this.updateRotation();
|
||||
this._top = value;
|
||||
}
|
||||
if (parameterName === 'shadowQuality') {
|
||||
if (value === 'low' && this._shadowMapSize !== 512) {
|
||||
this._shadowMapSize = 512;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
if (value === 'medium' && this._shadowMapSize !== 1024) {
|
||||
this._shadowMapSize = 1024;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
if (value === 'high' && this._shadowMapSize !== 2048) {
|
||||
this._shadowMapSize = 2048;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateColorParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'color') {
|
||||
this.light.color.setHex(value);
|
||||
this._light.color.setHex(value);
|
||||
}
|
||||
}
|
||||
getColorParameter(parameterName: string): number {
|
||||
if (parameterName === 'color') {
|
||||
return this.light.color.getHex();
|
||||
return this._light.color.getHex();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
updateRotation() {
|
||||
if (this.top === 'Z+') {
|
||||
// 0° is a light from the right of the screen.
|
||||
this.rotationObject.rotation.z = gdjs.toRad(this.rotation);
|
||||
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
|
||||
} else {
|
||||
// 0° becomes a light from Z+.
|
||||
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
|
||||
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {
|
||||
if (parameterName === 'isCastingShadow') {
|
||||
this._light.castShadow = value;
|
||||
}
|
||||
}
|
||||
getNetworkSyncData(): DirectionalLightFilterNetworkSyncData {
|
||||
return {
|
||||
i: this.light.intensity,
|
||||
c: this.light.color.getHex(),
|
||||
e: this.elevation,
|
||||
r: this.rotation,
|
||||
t: this.top,
|
||||
i: this._light.intensity,
|
||||
c: this._light.color.getHex(),
|
||||
e: this._elevation,
|
||||
r: this._rotation,
|
||||
t: this._top,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(syncData: any): void {
|
||||
this.light.intensity = syncData.i;
|
||||
this.light.color.setHex(syncData.c);
|
||||
this.elevation = syncData.e;
|
||||
this.rotation = syncData.r;
|
||||
this.top = syncData.t;
|
||||
this.updateRotation();
|
||||
this._light.intensity = syncData.i;
|
||||
this._light.color.setHex(syncData.c);
|
||||
this._elevation = syncData.e;
|
||||
this._rotation = syncData.r;
|
||||
this._top = syncData.t;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
@@ -18,18 +18,15 @@ namespace gdjs {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
light: THREE.HemisphereLight;
|
||||
rotationObject: THREE.Group;
|
||||
_top: string = 'Z+';
|
||||
_elevation: float = 90;
|
||||
_rotation: float = 0;
|
||||
|
||||
_isEnabled: boolean = false;
|
||||
top: string = 'Y-';
|
||||
elevation: float = 45;
|
||||
rotation: float = 0;
|
||||
_light: THREE.HemisphereLight;
|
||||
|
||||
constructor() {
|
||||
this.light = new THREE.HemisphereLight();
|
||||
this.light.position.set(1, 0, 0);
|
||||
this.rotationObject = new THREE.Group();
|
||||
this.rotationObject.add(this.light);
|
||||
this._light = new THREE.HemisphereLight();
|
||||
this.updateRotation();
|
||||
}
|
||||
|
||||
@@ -54,7 +51,7 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.add(this.rotationObject);
|
||||
scene.add(this._light);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
@@ -66,96 +63,106 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.remove(this.rotationObject);
|
||||
scene.remove(this._light);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'intensity') {
|
||||
this.light.intensity = value;
|
||||
this._light.intensity = value;
|
||||
} else if (parameterName === 'elevation') {
|
||||
this.elevation = value;
|
||||
this._elevation = value;
|
||||
this.updateRotation();
|
||||
} else if (parameterName === 'rotation') {
|
||||
this.rotation = value;
|
||||
this._rotation = value;
|
||||
this.updateRotation();
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'intensity') {
|
||||
return this.light.intensity;
|
||||
return this._light.intensity;
|
||||
} else if (parameterName === 'elevation') {
|
||||
return this.elevation;
|
||||
return this._elevation;
|
||||
} else if (parameterName === 'rotation') {
|
||||
return this.rotation;
|
||||
return this._rotation;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {
|
||||
if (parameterName === 'skyColor') {
|
||||
this.light.color = new THREE.Color(
|
||||
this._light.color = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
this.light.groundColor = new THREE.Color(
|
||||
this._light.groundColor = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'top') {
|
||||
this.top = value;
|
||||
this._top = value;
|
||||
this.updateRotation();
|
||||
}
|
||||
}
|
||||
updateColorParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'skyColor') {
|
||||
this.light.color.setHex(value);
|
||||
this._light.color.setHex(value);
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
this.light.groundColor.setHex(value);
|
||||
this._light.groundColor.setHex(value);
|
||||
}
|
||||
}
|
||||
getColorParameter(parameterName: string): number {
|
||||
if (parameterName === 'skyColor') {
|
||||
return this.light.color.getHex();
|
||||
return this._light.color.getHex();
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
return this.light.groundColor.getHex();
|
||||
return this._light.groundColor.getHex();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
updateRotation() {
|
||||
if (this.top === 'Z+') {
|
||||
// 0° is a light from the right of the screen.
|
||||
this.rotationObject.rotation.z = gdjs.toRad(this.rotation);
|
||||
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
|
||||
if (this._top === 'Y-') {
|
||||
// `rotation` at 0° becomes a light from Z+.
|
||||
this._light.position.set(
|
||||
Math.cos(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
-Math.sin(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation))
|
||||
);
|
||||
} else {
|
||||
// 0° becomes a light from Z+.
|
||||
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
|
||||
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
|
||||
// `rotation` at 0° is a light from the right of the screen.
|
||||
this._light.position.set(
|
||||
Math.cos(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(this._elevation))
|
||||
);
|
||||
}
|
||||
}
|
||||
getNetworkSyncData(): HemisphereLightFilterNetworkSyncData {
|
||||
return {
|
||||
i: this.light.intensity,
|
||||
sc: this.light.color.getHex(),
|
||||
gc: this.light.groundColor.getHex(),
|
||||
e: this.elevation,
|
||||
r: this.rotation,
|
||||
t: this.top,
|
||||
i: this._light.intensity,
|
||||
sc: this._light.color.getHex(),
|
||||
gc: this._light.groundColor.getHex(),
|
||||
e: this._elevation,
|
||||
r: this._rotation,
|
||||
t: this._top,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(
|
||||
syncData: HemisphereLightFilterNetworkSyncData
|
||||
): void {
|
||||
this.light.intensity = syncData.i;
|
||||
this.light.color.setHex(syncData.sc);
|
||||
this.light.groundColor.setHex(syncData.gc);
|
||||
this.elevation = syncData.e;
|
||||
this.rotation = syncData.r;
|
||||
this.top = syncData.t;
|
||||
this._light.intensity = syncData.i;
|
||||
this._light.color.setHex(syncData.sc);
|
||||
this._light.groundColor.setHex(syncData.gc);
|
||||
this._elevation = syncData.e;
|
||||
this._rotation = syncData.r;
|
||||
this._top = syncData.t;
|
||||
this.updateRotation();
|
||||
}
|
||||
})();
|
||||
|
@@ -859,7 +859,9 @@ module.exports = {
|
||||
propertyName === 'rightFaceResourceRepeat' ||
|
||||
propertyName === 'topFaceResourceRepeat' ||
|
||||
propertyName === 'bottomFaceResourceRepeat' ||
|
||||
propertyName === 'enableTextureTransparency'
|
||||
propertyName === 'enableTextureTransparency' ||
|
||||
propertyName === 'isCastingShadow' ||
|
||||
propertyName === 'isReceivingShadow'
|
||||
) {
|
||||
objectContent[propertyName] = newValue === '1';
|
||||
return true;
|
||||
@@ -887,8 +889,8 @@ module.exports = {
|
||||
.getOrCreate('facesOrientation')
|
||||
.setValue(objectContent.facesOrientation || 'Y')
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y')
|
||||
.addExtraInfo('Z')
|
||||
.addChoice('Y', 'Y')
|
||||
.addChoice('Z', 'Z')
|
||||
.setLabel(_('Faces orientation'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -948,8 +950,8 @@ module.exports = {
|
||||
.getOrCreate('backFaceUpThroughWhichAxisRotation')
|
||||
.setValue(objectContent.backFaceUpThroughWhichAxisRotation || 'X')
|
||||
.setType('choice')
|
||||
.addExtraInfo('X')
|
||||
.addExtraInfo('Y')
|
||||
.addChoice('X', 'X')
|
||||
.addChoice('Y', 'Y')
|
||||
.setLabel(_('Back face orientation'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -1083,11 +1085,29 @@ module.exports = {
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('materialType')
|
||||
.setValue(objectContent.materialType || 'Basic')
|
||||
.setValue(objectContent.materialType || 'StandardWithoutMetalness')
|
||||
.setType('choice')
|
||||
.addExtraInfo('Basic')
|
||||
.addExtraInfo('StandardWithoutMetalness')
|
||||
.setLabel(_('Material type'));
|
||||
.addChoice('Basic', _('Basic (no lighting, no shadows)'))
|
||||
.addChoice(
|
||||
'StandardWithoutMetalness',
|
||||
_('Standard (without metalness)')
|
||||
)
|
||||
.setLabel(_('Material type'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue(objectContent.isCastingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow casting'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isReceivingShadow')
|
||||
.setValue(objectContent.isReceivingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow receiving'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
@@ -1105,7 +1125,7 @@ module.exports = {
|
||||
topFaceResourceName: '',
|
||||
bottomFaceResourceName: '',
|
||||
frontFaceVisible: true,
|
||||
backFaceVisible: false,
|
||||
backFaceVisible: true,
|
||||
leftFaceVisible: true,
|
||||
rightFaceVisible: true,
|
||||
topFaceVisible: true,
|
||||
@@ -1116,8 +1136,10 @@ module.exports = {
|
||||
rightFaceResourceRepeat: false,
|
||||
topFaceResourceRepeat: false,
|
||||
bottomFaceResourceRepeat: false,
|
||||
materialType: 'Basic',
|
||||
materialType: 'StandardWithoutMetalness',
|
||||
tint: '255;255;255',
|
||||
isCastingShadow: true,
|
||||
isReceivingShadow: true,
|
||||
};
|
||||
|
||||
Cube3DObject.updateInitialInstanceProperty = function (
|
||||
@@ -1894,11 +1916,11 @@ module.exports = {
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('top')
|
||||
.setValue('Y-')
|
||||
.setValue('Z+')
|
||||
.setLabel(_('3D world top'))
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y-')
|
||||
.addExtraInfo('Z+')
|
||||
.addExtraInfo('Y-')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('elevation')
|
||||
@@ -1913,6 +1935,47 @@ module.exports = {
|
||||
.setLabel(_('Rotation (in degrees)'))
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue('false')
|
||||
.setLabel(_('Shadow casting'))
|
||||
.setType('boolean')
|
||||
.setGroup(_('Shadows'));
|
||||
properties
|
||||
.getOrCreate('shadowQuality')
|
||||
.setValue('medium')
|
||||
.addChoice('low', _('Low quality'))
|
||||
.addChoice('medium', _('Medium quality'))
|
||||
.addChoice('high', _('High quality'))
|
||||
.setLabel(_('Shadow quality'))
|
||||
.setType('choice')
|
||||
.setGroup(_('Shadows'));
|
||||
properties
|
||||
.getOrCreate('minimumShadowBias')
|
||||
.setValue('0')
|
||||
.setLabel(_('Shadow bias'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Use this to avoid "shadow acne" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 1.25 for medium quality and 2 for low quality.'
|
||||
)
|
||||
)
|
||||
.setType('number')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
properties
|
||||
.getOrCreate('frustumSize')
|
||||
.setValue('4000')
|
||||
.setLabel(_('Shadow frustum size'))
|
||||
.setType('number')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
properties
|
||||
.getOrCreate('distanceFromCamera')
|
||||
.setValue('1500')
|
||||
.setLabel(_("Distance from layer's camera"))
|
||||
.setType('number')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
@@ -1944,11 +2007,11 @@ module.exports = {
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('top')
|
||||
.setValue('Y-')
|
||||
.setValue('Z+')
|
||||
.setLabel(_('3D world top'))
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y-')
|
||||
.addExtraInfo('Z+')
|
||||
.addExtraInfo('Y-')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('elevation')
|
||||
@@ -3210,6 +3273,8 @@ module.exports = {
|
||||
|
||||
this._threeObject = new THREE.Group();
|
||||
this._threeObject.rotation.order = 'ZYX';
|
||||
this._threeObject.castShadow = true;
|
||||
this._threeObject.receiveShadow = true;
|
||||
this._threeGroup.add(this._threeObject);
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,7 @@ Model3DObjectConfiguration::Model3DObjectConfiguration()
|
||||
: width(100), height(100), depth(100), rotationX(0), rotationY(0),
|
||||
rotationZ(0), modelResourceName(""), materialType("StandardWithoutMetalness"),
|
||||
originLocation("ModelOrigin"), centerLocation("ModelOrigin"),
|
||||
keepAspectRatio(true), crossfadeDuration(0.1f) {}
|
||||
keepAspectRatio(true), crossfadeDuration(0.1f), isCastingShadow(true), isReceivingShadow(true) {}
|
||||
|
||||
bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
|
||||
const gd::String &newValue) {
|
||||
@@ -75,6 +75,16 @@ bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
|
||||
crossfadeDuration = newValue.To<double>();
|
||||
return true;
|
||||
}
|
||||
if(propertyName == "isCastingShadow")
|
||||
{
|
||||
isCastingShadow = newValue == "1";
|
||||
return true;
|
||||
}
|
||||
if(propertyName == "isReceivingShadow")
|
||||
{
|
||||
isReceivingShadow = newValue == "1";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -143,19 +153,20 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["materialType"]
|
||||
.SetValue(materialType.empty() ? "Basic" : materialType)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Basic")
|
||||
.AddExtraInfo("StandardWithoutMetalness")
|
||||
.AddExtraInfo("KeepOriginal")
|
||||
.SetLabel(_("Material"));
|
||||
.AddChoice("Basic", _("Basic (no lighting, no shadows)"))
|
||||
.AddChoice("StandardWithoutMetalness", _("Standard (without metalness)"))
|
||||
.AddChoice("KeepOriginal", _("Keep original"))
|
||||
.SetLabel(_("Material"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
objectProperties["originLocation"]
|
||||
.SetValue(originLocation.empty() ? "TopLeft" : originLocation)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("ModelOrigin")
|
||||
.AddExtraInfo("TopLeft")
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.AddChoice("ModelOrigin", _("Model origin"))
|
||||
.AddChoice("TopLeft", _("Top left"))
|
||||
.AddChoice("ObjectCenter", _("Object center"))
|
||||
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
|
||||
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
|
||||
.SetLabel(_("Origin point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
@@ -163,10 +174,10 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["centerLocation"]
|
||||
.SetValue(centerLocation.empty() ? "ObjectCenter" : centerLocation)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("ModelOrigin")
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.AddChoice("ModelOrigin", _("Model origin"))
|
||||
.AddChoice("ObjectCenter", _("Object center"))
|
||||
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
|
||||
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
|
||||
.SetLabel(_("Center point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
@@ -178,6 +189,20 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.SetGroup(_("Animations"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond());
|
||||
|
||||
objectProperties["isCastingShadow"]
|
||||
.SetValue(isCastingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow casting"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
objectProperties["isReceivingShadow"]
|
||||
.SetValue(isReceivingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow receiving"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
@@ -210,6 +235,8 @@ void Model3DObjectConfiguration::DoUnserializeFrom(
|
||||
centerLocation = content.GetStringAttribute("centerLocation");
|
||||
keepAspectRatio = content.GetBoolAttribute("keepAspectRatio");
|
||||
crossfadeDuration = content.GetDoubleAttribute("crossfadeDuration");
|
||||
isCastingShadow = content.GetBoolAttribute("isCastingShadow");
|
||||
isReceivingShadow = content.GetBoolAttribute("isReceivingShadow");
|
||||
|
||||
RemoveAllAnimations();
|
||||
auto &animationsElement = content.GetChild("animations");
|
||||
@@ -239,6 +266,8 @@ void Model3DObjectConfiguration::DoSerializeTo(
|
||||
content.SetAttribute("centerLocation", centerLocation);
|
||||
content.SetAttribute("keepAspectRatio", keepAspectRatio);
|
||||
content.SetAttribute("crossfadeDuration", crossfadeDuration);
|
||||
content.SetAttribute("isCastingShadow", isCastingShadow);
|
||||
content.SetAttribute("isReceivingShadow", isReceivingShadow);
|
||||
|
||||
auto &animationsElement = content.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
|
@@ -160,6 +160,8 @@ public:
|
||||
const gd::String& GetCenterLocation() const { return centerLocation; };
|
||||
|
||||
bool shouldKeepAspectRatio() const { return keepAspectRatio; };
|
||||
bool shouldCastShadow() const { return isCastingShadow; };
|
||||
bool shouldReceiveShadow() const { return isReceivingShadow; };
|
||||
///@}
|
||||
|
||||
protected:
|
||||
@@ -182,6 +184,8 @@ private:
|
||||
gd::String centerLocation;
|
||||
|
||||
bool keepAspectRatio;
|
||||
bool isCastingShadow;
|
||||
bool isReceivingShadow;
|
||||
|
||||
std::vector<Model3DAnimation> animations;
|
||||
static Model3DAnimation badAnimation; //< Bad animation when an out of bound
|
||||
|
@@ -38,6 +38,8 @@ namespace gdjs {
|
||||
| 'BottomCenterY';
|
||||
animations: Model3DAnimation[];
|
||||
crossfadeDuration: float;
|
||||
isCastingShadow: boolean;
|
||||
isReceivingShadow: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,6 +103,8 @@ namespace gdjs {
|
||||
_animationSpeedScale: float = 1;
|
||||
_animationPaused: boolean = false;
|
||||
_crossfadeDuration: float = 0;
|
||||
_isCastingShadow: boolean = true;
|
||||
_isReceivingShadow: boolean = true;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -123,6 +127,8 @@ namespace gdjs {
|
||||
objectData.content.materialType
|
||||
);
|
||||
|
||||
this.setIsCastingShadow(objectData.content.isCastingShadow);
|
||||
this.setIsReceivingShadow(objectData.content.isReceivingShadow);
|
||||
this.onModelChanged(objectData);
|
||||
|
||||
this._crossfadeDuration = objectData.content.crossfadeDuration || 0;
|
||||
@@ -195,6 +201,18 @@ namespace gdjs {
|
||||
newObjectData.content.centerLocation
|
||||
);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isCastingShadow !==
|
||||
newObjectData.content.isCastingShadow
|
||||
) {
|
||||
this.setIsCastingShadow(newObjectData.content.isCastingShadow);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isReceivingShadow !==
|
||||
newObjectData.content.isReceivingShadow
|
||||
) {
|
||||
this.setIsReceivingShadow(newObjectData.content.isReceivingShadow);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -358,6 +376,16 @@ namespace gdjs {
|
||||
return this._renderer.hasAnimationEnded();
|
||||
}
|
||||
|
||||
setIsCastingShadow(value: boolean): void {
|
||||
this._isCastingShadow = value;
|
||||
this._renderer._updateShadow();
|
||||
}
|
||||
|
||||
setIsReceivingShadow(value: boolean): void {
|
||||
this._isReceivingShadow = value;
|
||||
this._renderer._updateShadow();
|
||||
}
|
||||
|
||||
setCrossfadeDuration(duration: number): void {
|
||||
if (this._crossfadeDuration === duration) return;
|
||||
this._crossfadeDuration = duration;
|
||||
|
@@ -286,6 +286,7 @@ namespace gdjs {
|
||||
this.get3DRendererObject().remove(this._threeObject);
|
||||
this.get3DRendererObject().add(threeObject);
|
||||
this._threeObject = threeObject;
|
||||
this._updateShadow();
|
||||
|
||||
// Start the current animation on the new 3D object.
|
||||
this._animationMixer = new THREE.AnimationMixer(root);
|
||||
@@ -323,6 +324,13 @@ namespace gdjs {
|
||||
return this._originalModel.animations[animationIndex].name;
|
||||
}
|
||||
|
||||
_updateShadow() {
|
||||
this._threeObject.traverse((child) => {
|
||||
child.castShadow = this._model3DRuntimeObject._isCastingShadow;
|
||||
child.receiveShadow = this._model3DRuntimeObject._isReceivingShadow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if animation has ended.
|
||||
* The animation had ended if:
|
||||
|
@@ -14,6 +14,7 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
|
||||
effects: [],
|
||||
content: {},
|
||||
childrenContent: {},
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
});
|
||||
runtimeScene.addObject(customObject);
|
||||
customObject.setPosition(500, 250);
|
||||
|
@@ -75,9 +75,9 @@ module.exports = {
|
||||
.getOrCreate('align')
|
||||
.setValue(objectContent.align)
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Base alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -88,9 +88,9 @@ module.exports = {
|
||||
.getOrCreate('verticalTextAlignment')
|
||||
.setValue(objectContent.verticalTextAlignment)
|
||||
.setType('choice')
|
||||
.addExtraInfo('top')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('bottom')
|
||||
.addChoice('top', _('Top'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('bottom', _('Bottom'))
|
||||
.setLabel(_('Vertical alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -508,7 +508,7 @@ module.exports = {
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader,
|
||||
propertyOverridings
|
||||
getPropertyOverridings
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
@@ -516,7 +516,7 @@ module.exports = {
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader,
|
||||
propertyOverridings
|
||||
getPropertyOverridings
|
||||
);
|
||||
|
||||
const bbTextStyles = {
|
||||
@@ -555,9 +555,11 @@ module.exports = {
|
||||
gd.ObjectJsImplementation
|
||||
);
|
||||
|
||||
const rawText = this._propertyOverridings.has('Text')
|
||||
? this._propertyOverridings.get('Text')
|
||||
: object.content.text;
|
||||
const propertyOverridings = this.getPropertyOverridings();
|
||||
const rawText =
|
||||
propertyOverridings && propertyOverridings.has('Text')
|
||||
? propertyOverridings.get('Text')
|
||||
: object.content.text;
|
||||
if (rawText !== this._pixiObject.text) {
|
||||
this._pixiObject.text = rawText;
|
||||
}
|
||||
|
@@ -61,9 +61,9 @@ module.exports = {
|
||||
.getOrCreate('align')
|
||||
.setValue(objectContent.align)
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -74,9 +74,9 @@ module.exports = {
|
||||
.getOrCreate('verticalTextAlignment')
|
||||
.setValue(objectContent.verticalTextAlignment)
|
||||
.setType('choice')
|
||||
.addExtraInfo('top')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('bottom')
|
||||
.addChoice('top', _('Top'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('bottom', _('Bottom'))
|
||||
.setLabel(_('Vertical alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -631,7 +631,7 @@ module.exports = {
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader,
|
||||
propertyOverridings
|
||||
getPropertyOverridings
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
@@ -639,7 +639,7 @@ module.exports = {
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader,
|
||||
propertyOverridings
|
||||
getPropertyOverridings
|
||||
);
|
||||
|
||||
// We'll track changes of the font to trigger the loading of the new font.
|
||||
@@ -665,9 +665,11 @@ module.exports = {
|
||||
|
||||
// Update the rendered text properties (note: Pixi is only
|
||||
// applying changes if there were changed).
|
||||
this._pixiObject.text = this._propertyOverridings.has('Text')
|
||||
? this._propertyOverridings.get('Text')
|
||||
: object.content.text;
|
||||
const propertyOverridings = this.getPropertyOverridings();
|
||||
this._pixiObject.text =
|
||||
propertyOverridings && propertyOverridings.has('Text')
|
||||
? propertyOverridings.get('Text')
|
||||
: object.content.text;
|
||||
|
||||
const align = object.content.align;
|
||||
this._pixiObject.align = align;
|
||||
|
@@ -12,7 +12,7 @@ This project is released under the MIT License.
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
void DestroyOutsideBehavior::InitializeContent(gd::SerializerElement& content) {
|
||||
content.SetAttribute("extraBorder", 0);
|
||||
content.SetAttribute("extraBorder", 300);
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
|
@@ -12,35 +12,42 @@ This project is released under the MIT License.
|
||||
|
||||
void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
extension
|
||||
.SetExtensionInformation("DestroyOutsideBehavior",
|
||||
_("Destroy Outside Screen Behavior"),
|
||||
_("This behavior can be used to destroy "
|
||||
"objects when they go outside of "
|
||||
"the bounds of the camera. Useful for bullets "
|
||||
"or other short-lived objects."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionInformation(
|
||||
"DestroyOutsideBehavior",
|
||||
_("Destroy Outside Screen Behavior"),
|
||||
_("This behavior can be used to destroy objects when they go "
|
||||
"outside of the bounds of the 2D camera. Useful for 2D bullets or "
|
||||
"other short-lived objects. Don't use it for 3D objects in a "
|
||||
"FPS/TPS game or any game with a camera not being a top view "
|
||||
"(for 3D objects, prefer comparing "
|
||||
"the position, for example Z position to see if an object goes "
|
||||
"outside of the bound of the map). Be careful when using this "
|
||||
"behavior because if the object appears outside of the screen, it "
|
||||
"will be immediately removed."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Game mechanic")
|
||||
.SetTags("screen")
|
||||
.SetExtensionHelpPath("/behaviors/destroyoutside");
|
||||
|
||||
gd::BehaviorMetadata& aut =
|
||||
extension.AddBehavior("DestroyOutside",
|
||||
_("Destroy when outside of the screen"),
|
||||
_("DestroyOutside"),
|
||||
_("Destroy objects automatically when they go "
|
||||
"outside of the screen's borders."),
|
||||
"",
|
||||
"CppPlatform/Extensions/destroyoutsideicon.png",
|
||||
"DestroyOutsideBehavior",
|
||||
std::make_shared<DestroyOutsideBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
extension
|
||||
.AddBehavior("DestroyOutside",
|
||||
_("Destroy when outside of the screen"),
|
||||
_("DestroyOutside"),
|
||||
_("Destroy objects automatically when they go "
|
||||
"outside of the 2D camera borders."),
|
||||
"",
|
||||
"CppPlatform/Extensions/destroyoutsideicon.png",
|
||||
"DestroyOutsideBehavior",
|
||||
std::make_shared<DestroyOutsideBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
aut.AddCondition("ExtraBorder",
|
||||
_("Additional border"),
|
||||
_("Compare the additional border that the object must cross "
|
||||
"before being deleted."),
|
||||
_("Additional border (extra distance before deletion)"),
|
||||
_("Compare the extra distance (in pixels) the object must "
|
||||
"travel beyond the screen before it gets deleted."),
|
||||
_("the additional border"),
|
||||
_("Destroy outside configuration"),
|
||||
"CppPlatform/Extensions/destroyoutsideicon24.png",
|
||||
@@ -53,9 +60,9 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetExtraBorder");
|
||||
|
||||
aut.AddAction("ExtraBorder",
|
||||
_("Additional border"),
|
||||
_("Change the additional border that the object must cross "
|
||||
"before being deleted."),
|
||||
_("Additional border (extra distance before deletion)"),
|
||||
_("Change the extra distance (in pixels) the object must "
|
||||
"travel beyond the screen before it gets deleted."),
|
||||
_("the additional border"),
|
||||
_("Destroy outside configuration"),
|
||||
"CppPlatform/Extensions/destroyoutsideicon24.png",
|
||||
|
@@ -35,25 +35,32 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
std::make_shared<DraggableBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>());
|
||||
|
||||
aut.AddCondition("Dragged",
|
||||
_("Being dragged"),
|
||||
_("Check if the object is being dragged."),
|
||||
_("_PARAM0_ is being dragged"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
aut.AddCondition(
|
||||
"Dragged",
|
||||
_("Being dragged"),
|
||||
_("Check if the object is being dragged. This means the mouse button "
|
||||
"or touch is pressed on it. When the mouse button or touch is "
|
||||
"released, the object is no longer being considered dragged (use "
|
||||
"the condition \"Was just dropped\" to check when the dragging is "
|
||||
"ending)."),
|
||||
_("_PARAM0_ is being dragged"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "Draggable")
|
||||
.SetFunctionName("IsDragged");
|
||||
|
||||
aut.AddCondition("Dropped",
|
||||
_("Was just dropped"),
|
||||
_("Check if the object was just dropped after being dragged."),
|
||||
_("_PARAM0_ was just dropped"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
aut.AddCondition(
|
||||
"Dropped",
|
||||
_("Was just dropped"),
|
||||
_("Check if the object was just dropped after being dragged (the "
|
||||
"mouse button or touch was just released this frame)."),
|
||||
_("_PARAM0_ was just dropped"),
|
||||
_("Draggable"),
|
||||
"CppPlatform/Extensions/draggableicon24.png",
|
||||
"CppPlatform/Extensions/draggableicon16.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "Draggable")
|
||||
|
9
Extensions/JsExtensionTypes.d.ts
vendored
9
Extensions/JsExtensionTypes.d.ts
vendored
@@ -27,7 +27,7 @@ class RenderedInstance {
|
||||
associatedObjectConfiguration: gdObjectConfiguration,
|
||||
pixiContainer: PIXI.Container,
|
||||
pixiResourcesLoader: Class<PixiResourcesLoader>,
|
||||
propertyOverridings: Map<string, string> = new Map<string, string>()
|
||||
getPropertyOverridings: (() => Map<string, string>) | null = null
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -80,6 +80,8 @@ class RenderedInstance {
|
||||
getDefaultHeight(): number;
|
||||
|
||||
getDefaultDepth(): number;
|
||||
|
||||
getPropertyOverridings(): Map<string, string> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +109,8 @@ class Rendered3DInstance {
|
||||
associatedObjectConfiguration: gdObjectConfiguration,
|
||||
pixiContainer: PIXI.Container,
|
||||
threeGroup: THREE.Group,
|
||||
pixiResourcesLoader: Class<PixiResourcesLoader>
|
||||
pixiResourcesLoader: Class<PixiResourcesLoader>,
|
||||
getPropertyOverridings: (() => Map<string, string>) | null = null
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -174,6 +177,8 @@ class Rendered3DInstance {
|
||||
* Return the depth of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultDepth(): number;
|
||||
|
||||
getPropertyOverridings(): Map<string, string> | null;
|
||||
}
|
||||
|
||||
declare type ObjectsRenderingService = {
|
||||
|
@@ -31,6 +31,73 @@ module.exports = {
|
||||
.addInstructionOrExpressionGroupMetadata(_('Multiplayer'))
|
||||
.setIcon('JsPlatform/Extensions/multiplayer.svg');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'CurrentLobbyID',
|
||||
_('Current lobby ID'),
|
||||
_('Returns current lobby ID.'),
|
||||
_('Lobbies'),
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
|
||||
.setFunctionName('gdjs.multiplayer.getLobbyID');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'QuickJoinWithLobbyID',
|
||||
_('Join a specific lobby by its ID'),
|
||||
_(
|
||||
'Join a specific lobby. The player will join the game instantly if this is possible.'
|
||||
),
|
||||
_('Join a specific lobby by its ID _PARAM1_'),
|
||||
_('Lobbies'),
|
||||
'JsPlatform/Extensions/multiplayer.svg',
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter('string', _('Lobby ID'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Display loader while joining a lobby.'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Display game lobbies if unable to join a specific one.'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.setDefaultValue('yes')
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
|
||||
.setFunctionName('gdjs.multiplayer.authenticateAndQuickJoinWithLobbyID');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'QuickJoinLobby',
|
||||
|
@@ -293,6 +293,8 @@ namespace gdjs {
|
||||
x: objectNetworkSyncData.x,
|
||||
y: objectNetworkSyncData.y,
|
||||
z: objectNetworkSyncData.z,
|
||||
w: objectNetworkSyncData.w,
|
||||
h: objectNetworkSyncData.h,
|
||||
zo: objectNetworkSyncData.zo,
|
||||
a: objectNetworkSyncData.a,
|
||||
hid: objectNetworkSyncData.hid,
|
||||
@@ -369,6 +371,9 @@ namespace gdjs {
|
||||
this._lastSentBasicObjectSyncData = {
|
||||
x: objectNetworkSyncData.x,
|
||||
y: objectNetworkSyncData.y,
|
||||
z: objectNetworkSyncData.z,
|
||||
w: objectNetworkSyncData.w,
|
||||
h: objectNetworkSyncData.h,
|
||||
zo: objectNetworkSyncData.zo,
|
||||
a: objectNetworkSyncData.a,
|
||||
hid: objectNetworkSyncData.hid,
|
||||
|
@@ -17,9 +17,29 @@ namespace gdjs {
|
||||
}[];
|
||||
};
|
||||
|
||||
type LobbyStatus =
|
||||
| 'waiting'
|
||||
| 'starting'
|
||||
| 'playing'
|
||||
| 'migrating'
|
||||
| 'migrated';
|
||||
type LobbyConnectionStatus = 'waiting' | 'ready' | 'connected';
|
||||
type InGamePlayerStatus = 'playing' | 'left';
|
||||
type PlayerStatus = LobbyConnectionStatus | InGamePlayerStatus;
|
||||
|
||||
type LobbyPlayer = {
|
||||
playerId: string;
|
||||
status: PlayerStatus;
|
||||
playerNumber: number;
|
||||
};
|
||||
|
||||
type Lobby = {
|
||||
id: string;
|
||||
status: 'waiting' | 'starting' | 'playing' | 'migrating' | 'migrated';
|
||||
minPlayers: number;
|
||||
maxPlayers: number;
|
||||
canJoinAfterStart: boolean;
|
||||
players: LobbyPlayer[];
|
||||
status: LobbyStatus;
|
||||
};
|
||||
|
||||
type QuickJoinLobbyResponse =
|
||||
@@ -105,6 +125,7 @@ namespace gdjs {
|
||||
let _quickJoinLobbyFailureReason:
|
||||
| 'FULL'
|
||||
| 'NOT_ENOUGH_PLAYERS'
|
||||
| 'DOES_NOT_EXIST'
|
||||
| 'UNKNOWN'
|
||||
| null = null;
|
||||
let _lobbyId: string | null = null;
|
||||
@@ -1697,11 +1718,87 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
export const authenticateAndQuickJoinLobby = async (
|
||||
export const getLobbyID = (): string => {
|
||||
return _lobbyId || '';
|
||||
};
|
||||
|
||||
const quickJoinWithLobbyID = async (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
lobbyID: string,
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (_isQuickJoiningOrStartingAGame) return;
|
||||
const _gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!_gameId) {
|
||||
logger.error(
|
||||
'The game ID is missing, the quick join lobby action cannot continue.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_quickJoinLobbyFailureReason = null;
|
||||
_isQuickJoiningOrStartingAGame = true;
|
||||
if (displayLoader) {
|
||||
gdjs.multiplayerComponents.displayLoader(runtimeScene, true);
|
||||
}
|
||||
|
||||
const quickJoinWithLobbyIDRelativeUrl = `/play/game/${_gameId}/public-lobby/${lobbyID}`;
|
||||
|
||||
try {
|
||||
const lobby: Lobby = await gdjs.evtTools.network.retryIfFailed(
|
||||
{ times: 2 },
|
||||
() =>
|
||||
fetchAsPlayer({
|
||||
relativeUrl: quickJoinWithLobbyIDRelativeUrl,
|
||||
method: 'GET',
|
||||
dev: isUsingGDevelopDevelopmentEnvironment,
|
||||
})
|
||||
);
|
||||
|
||||
const isFull = lobby.players.length === lobby.maxPlayers;
|
||||
if (isFull) {
|
||||
logger.error('Lobby is full - cannot quick join it.');
|
||||
_quickJoinLobbyJustFailed = true;
|
||||
_quickJoinLobbyFailureReason = 'FULL';
|
||||
onLobbyQuickJoinFinished(runtimeScene);
|
||||
if (openLobbiesPageIfFailure) {
|
||||
openLobbiesWindow(runtimeScene);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lobby.status === 'playing') {
|
||||
_actionAfterJoiningLobby = 'JOIN_GAME';
|
||||
} else if (lobby.status === 'waiting') {
|
||||
if (lobby.players.length === 0) {
|
||||
_actionAfterJoiningLobby = 'START_GAME';
|
||||
} else {
|
||||
_actionAfterJoiningLobby = 'OPEN_LOBBY_PAGE';
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Lobby in wrong status: ${lobby.status}`);
|
||||
}
|
||||
|
||||
handleJoinLobbyEvent(runtimeScene, lobbyID);
|
||||
} catch (error) {
|
||||
const errorCode = parseInt(error.message.match(/\d{3}/)?.[0]);
|
||||
if (errorCode === 404) {
|
||||
logger.error('Lobby does not exist.');
|
||||
_quickJoinLobbyFailureReason = 'DOES_NOT_EXIST';
|
||||
} else {
|
||||
logger.error('An error occurred while joining a lobby:', error);
|
||||
_quickJoinLobbyFailureReason = 'UNKNOWN';
|
||||
}
|
||||
_quickJoinLobbyJustFailed = true;
|
||||
onLobbyQuickJoinFinished(runtimeScene);
|
||||
if (openLobbiesPageIfFailure) {
|
||||
openLobbiesWindow(runtimeScene);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isQuickJoiningTooFast = () => {
|
||||
const requestDoneAt = Date.now();
|
||||
if (_lastQuickJoinRequestDoneAt) {
|
||||
if (requestDoneAt - _lastQuickJoinRequestDoneAt < 500) {
|
||||
@@ -1709,12 +1806,18 @@ namespace gdjs {
|
||||
logger.warn(
|
||||
'Last request to quick join a lobby was sent too little time ago. Ignoring this one.'
|
||||
);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
_lastQuickJoinRequestDoneAt = requestDoneAt;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isNotCorrectlyAuthenticatedForQuickJoin = async (
|
||||
runtimeScene: RuntimeScene
|
||||
) => {
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (!playerId || !playerToken) {
|
||||
@@ -1724,14 +1827,43 @@ namespace gdjs {
|
||||
.promise;
|
||||
_isWaitingForLogin = false;
|
||||
|
||||
if (status === 'logged') {
|
||||
await quickJoinLobby(
|
||||
runtimeScene,
|
||||
displayLoader,
|
||||
openLobbiesPageIfFailure
|
||||
);
|
||||
if (status !== 'logged') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const authenticateAndQuickJoinWithLobbyID = async (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
lobbyID: string,
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
if (await isNotCorrectlyAuthenticatedForQuickJoin(runtimeScene)) {
|
||||
return;
|
||||
}
|
||||
await quickJoinWithLobbyID(
|
||||
runtimeScene,
|
||||
lobbyID,
|
||||
displayLoader,
|
||||
openLobbiesPageIfFailure
|
||||
);
|
||||
};
|
||||
|
||||
export const authenticateAndQuickJoinLobby = async (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
if (await isNotCorrectlyAuthenticatedForQuickJoin(runtimeScene)) {
|
||||
return;
|
||||
}
|
||||
await quickJoinLobby(
|
||||
|
@@ -116,48 +116,7 @@ namespace gdjs {
|
||||
|
||||
_updateLocalPositions() {
|
||||
const obj = this._object;
|
||||
this._centerSprite.position.x = obj._lBorder;
|
||||
this._centerSprite.position.y = obj._tBorder;
|
||||
|
||||
//Right
|
||||
this._borderSprites[0].position.x = obj._width - obj._rBorder;
|
||||
this._borderSprites[0].position.y = obj._tBorder;
|
||||
|
||||
//Top-right
|
||||
this._borderSprites[1].position.x =
|
||||
obj._width - this._borderSprites[1].width;
|
||||
this._borderSprites[1].position.y = 0;
|
||||
|
||||
//Top
|
||||
this._borderSprites[2].position.x = obj._lBorder;
|
||||
this._borderSprites[2].position.y = 0;
|
||||
|
||||
//Top-Left
|
||||
this._borderSprites[3].position.x = 0;
|
||||
this._borderSprites[3].position.y = 0;
|
||||
|
||||
//Left
|
||||
this._borderSprites[4].position.x = 0;
|
||||
this._borderSprites[4].position.y = obj._tBorder;
|
||||
|
||||
//Bottom-Left
|
||||
this._borderSprites[5].position.x = 0;
|
||||
this._borderSprites[5].position.y =
|
||||
obj._height - this._borderSprites[5].height;
|
||||
|
||||
//Bottom
|
||||
this._borderSprites[6].position.x = obj._lBorder;
|
||||
this._borderSprites[6].position.y = obj._height - obj._bBorder;
|
||||
|
||||
//Bottom-Right
|
||||
this._borderSprites[7].position.x =
|
||||
obj._width - this._borderSprites[7].width;
|
||||
this._borderSprites[7].position.y =
|
||||
obj._height - this._borderSprites[7].height;
|
||||
}
|
||||
|
||||
_updateSpritesAndTexturesSize() {
|
||||
const obj = this._object;
|
||||
this._centerSprite.width = Math.max(
|
||||
obj._width - obj._rBorder - obj._lBorder,
|
||||
0
|
||||
@@ -167,35 +126,107 @@ namespace gdjs {
|
||||
0
|
||||
);
|
||||
|
||||
let leftMargin = obj._lBorder;
|
||||
let rightMargin = obj._rBorder;
|
||||
if (this._centerSprite.width === 0 && obj._lBorder + obj._rBorder > 0) {
|
||||
leftMargin =
|
||||
(obj._width * obj._lBorder) / (obj._lBorder + obj._rBorder);
|
||||
rightMargin = obj._width - leftMargin;
|
||||
}
|
||||
let topMargin = obj._tBorder;
|
||||
let bottomMargin = obj._bBorder;
|
||||
if (this._centerSprite.height === 0 && obj._tBorder + obj._bBorder > 0) {
|
||||
topMargin =
|
||||
(obj._height * obj._tBorder) / (obj._tBorder + obj._bBorder);
|
||||
bottomMargin = obj._height - topMargin;
|
||||
}
|
||||
|
||||
//Right
|
||||
this._borderSprites[0].width = obj._rBorder;
|
||||
this._borderSprites[0].width = rightMargin;
|
||||
this._borderSprites[0].height = Math.max(
|
||||
obj._height - obj._tBorder - obj._bBorder,
|
||||
obj._height - topMargin - bottomMargin,
|
||||
0
|
||||
);
|
||||
|
||||
//Top
|
||||
this._borderSprites[2].height = obj._tBorder;
|
||||
this._borderSprites[2].height = topMargin;
|
||||
this._borderSprites[2].width = Math.max(
|
||||
obj._width - obj._rBorder - obj._lBorder,
|
||||
obj._width - rightMargin - leftMargin,
|
||||
0
|
||||
);
|
||||
|
||||
//Left
|
||||
this._borderSprites[4].width = obj._lBorder;
|
||||
this._borderSprites[4].width = leftMargin;
|
||||
this._borderSprites[4].height = Math.max(
|
||||
obj._height - obj._tBorder - obj._bBorder,
|
||||
obj._height - topMargin - bottomMargin,
|
||||
0
|
||||
);
|
||||
|
||||
//Bottom
|
||||
this._borderSprites[6].height = obj._bBorder;
|
||||
this._borderSprites[6].height = bottomMargin;
|
||||
this._borderSprites[6].width = Math.max(
|
||||
obj._width - obj._rBorder - obj._lBorder,
|
||||
obj._width - rightMargin - leftMargin,
|
||||
0
|
||||
);
|
||||
|
||||
//Top-right
|
||||
this._borderSprites[1].width = rightMargin;
|
||||
this._borderSprites[1].height = topMargin;
|
||||
|
||||
//Top-Left
|
||||
this._borderSprites[3].width = leftMargin;
|
||||
this._borderSprites[3].height = topMargin;
|
||||
|
||||
//Bottom-Left
|
||||
this._borderSprites[5].width = leftMargin;
|
||||
this._borderSprites[5].height = bottomMargin;
|
||||
|
||||
//Bottom-Right
|
||||
this._borderSprites[7].width = rightMargin;
|
||||
this._borderSprites[7].height = bottomMargin;
|
||||
|
||||
this._wasRendered = true;
|
||||
this._spritesContainer.cacheAsBitmap = false;
|
||||
|
||||
const leftBorder = leftMargin;
|
||||
const topBorder = topMargin;
|
||||
const rightBorder = obj._width - rightMargin;
|
||||
const bottomBorder = obj._height - bottomMargin;
|
||||
|
||||
this._centerSprite.position.x = leftBorder;
|
||||
this._centerSprite.position.y = topBorder;
|
||||
|
||||
//Right
|
||||
this._borderSprites[0].position.x = rightBorder;
|
||||
this._borderSprites[0].position.y = topBorder;
|
||||
|
||||
//Top-right
|
||||
this._borderSprites[1].position.x = rightBorder;
|
||||
this._borderSprites[1].position.y = 0;
|
||||
|
||||
//Top
|
||||
this._borderSprites[2].position.x = leftBorder;
|
||||
this._borderSprites[2].position.y = 0;
|
||||
|
||||
//Top-Left
|
||||
this._borderSprites[3].position.x = 0;
|
||||
this._borderSprites[3].position.y = 0;
|
||||
|
||||
//Left
|
||||
this._borderSprites[4].position.x = 0;
|
||||
this._borderSprites[4].position.y = topBorder;
|
||||
|
||||
//Bottom-Left
|
||||
this._borderSprites[5].position.x = 0;
|
||||
this._borderSprites[5].position.y = bottomBorder;
|
||||
|
||||
//Bottom
|
||||
this._borderSprites[6].position.x = leftBorder;
|
||||
this._borderSprites[6].position.y = bottomBorder;
|
||||
|
||||
//Bottom-Right
|
||||
this._borderSprites[7].position.x = rightBorder;
|
||||
this._borderSprites[7].position.y = bottomBorder;
|
||||
}
|
||||
|
||||
setTexture(
|
||||
@@ -340,7 +371,6 @@ namespace gdjs {
|
||||
)
|
||||
)
|
||||
);
|
||||
this._updateSpritesAndTexturesSize();
|
||||
this._updateLocalPositions();
|
||||
this.updatePosition();
|
||||
this._wrapperContainer.pivot.x = this._object._width / 2;
|
||||
@@ -349,14 +379,12 @@ namespace gdjs {
|
||||
|
||||
updateWidth(): void {
|
||||
this._wrapperContainer.pivot.x = this._object._width / 2;
|
||||
this._updateSpritesAndTexturesSize();
|
||||
this._updateLocalPositions();
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
updateHeight(): void {
|
||||
this._wrapperContainer.pivot.y = this._object._height / 2;
|
||||
this._updateSpritesAndTexturesSize();
|
||||
this._updateLocalPositions();
|
||||
this.updatePosition();
|
||||
}
|
||||
|
@@ -25,8 +25,6 @@ namespace gdjs {
|
||||
export type PanelSpriteObjectData = ObjectData & PanelSpriteObjectDataType;
|
||||
|
||||
export type PanelSpriteNetworkSyncDataType = {
|
||||
wid: number;
|
||||
hei: number;
|
||||
op: number;
|
||||
color: string;
|
||||
};
|
||||
@@ -124,8 +122,6 @@ namespace gdjs {
|
||||
getNetworkSyncData(): PanelSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
op: this.getOpacity(),
|
||||
color: this.getColor(),
|
||||
};
|
||||
@@ -138,12 +134,6 @@ namespace gdjs {
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.op !== undefined) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
|
@@ -37,9 +37,9 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
extension
|
||||
.AddObject<ParticleEmitterObject>(
|
||||
"ParticleEmitter",
|
||||
_("Particles emitter"),
|
||||
_("2D particles emitter"),
|
||||
_("Displays a large number of small 2D particles to create "
|
||||
"visual effects."),
|
||||
"visual effects in a 2D game or user interface."),
|
||||
"CppPlatform/Extensions/particleSystemicon.png")
|
||||
.SetCategoryFullName(_("Visual effect"))
|
||||
.AddDefaultBehavior("EffectCapability::EffectBehavior");
|
||||
|
@@ -194,9 +194,9 @@ ParticleEmitterObject::GetProperties() const {
|
||||
: GetRendererType() == Line ? "Line"
|
||||
: "Image")
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Circle")
|
||||
.AddExtraInfo("Line")
|
||||
.AddExtraInfo("Image")
|
||||
.AddChoice("Circle", _("Circle"))
|
||||
.AddChoice("Line", _("Line"))
|
||||
.AddChoice("Image", _("Image"))
|
||||
.SetLabel(_("Particle type"))
|
||||
.SetHasImpactOnOtherProperties(true);
|
||||
|
||||
|
@@ -818,7 +818,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -852,7 +852,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -886,7 +886,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Can sleep?'), '', false)
|
||||
.addParameter('yesorno', _('Can sleep'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setSleepingAllowed');
|
||||
@@ -1296,7 +1296,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Layer (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1332,7 +1332,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Mask (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -2409,7 +2409,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointLimits');
|
||||
|
||||
@@ -2488,7 +2488,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointMotor');
|
||||
|
||||
@@ -2727,7 +2727,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointLimits');
|
||||
|
||||
@@ -2806,7 +2806,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointMotor');
|
||||
|
||||
@@ -3486,7 +3486,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableWheelJointMotor');
|
||||
|
||||
|
@@ -274,7 +274,7 @@ module.exports = {
|
||||
.setLabel('Fixed Rotation')
|
||||
.setDescription(
|
||||
_(
|
||||
"If enabled, the object won't rotate and will stay at the same angle. Useful for characters for example."
|
||||
"If enabled, the object won't rotate and will stay at the same angle."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Movement'));
|
||||
@@ -845,7 +845,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -870,7 +870,7 @@ module.exports = {
|
||||
'SetFixedRotation',
|
||||
_('Fixed rotation'),
|
||||
_(
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate."
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate. This action has no effect on characters."
|
||||
),
|
||||
_('Set _PARAM0_ fixed rotation: _PARAM2_'),
|
||||
_('Dynamics'),
|
||||
@@ -879,7 +879,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -927,6 +927,54 @@ module.exports = {
|
||||
.setFunctionName('setDensity')
|
||||
.setGetter('getDensity');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetX',
|
||||
_('Shape offset X'),
|
||||
_('the object shape offset on X.'),
|
||||
_('the shape offset on X'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetX')
|
||||
.setGetter('getShapeOffsetX');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetY',
|
||||
_('Shape offset Y'),
|
||||
_('the object shape offset on Y.'),
|
||||
_('the shape offset on Y'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetY')
|
||||
.setGetter('getShapeOffsetY');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetZ',
|
||||
_('Shape offset Z'),
|
||||
_('the object shape offset on Z.'),
|
||||
_('the shape offset on Z'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetZ')
|
||||
.setGetter('getShapeOffsetZ');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
@@ -1054,7 +1102,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Layer (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1090,7 +1138,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Mask (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -1270,7 +1318,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Application point on Z axis'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
'Use `MassCenterX`, `MassCenterY` and `MassCenterZ` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
@@ -1544,6 +1592,19 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterY');
|
||||
|
||||
aut
|
||||
.addExpression(
|
||||
'MassCenterZ',
|
||||
_('Mass center Z'),
|
||||
_('Mass center Z'),
|
||||
'',
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterZ');
|
||||
}
|
||||
// Collision
|
||||
extension
|
||||
|
@@ -630,24 +630,34 @@ namespace gdjs {
|
||||
|
||||
override onDeActivate() {
|
||||
this._sharedData.removeFromBehaviorsList(this);
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {
|
||||
this._sharedData.addToBehaviorsList(this);
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
}
|
||||
|
||||
_destroyBody() {
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
}
|
||||
|
||||
override onActivate() {
|
||||
this._sharedData.addToBehaviorsList(this);
|
||||
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
this.updateBodyFromObject();
|
||||
resetToDefaultBodyUpdater() {
|
||||
this.bodyUpdater = new gdjs.Physics3DRuntimeBehavior.DefaultBodyUpdater(
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
resetToDefaultCollisionChecker() {
|
||||
this.collisionChecker =
|
||||
new gdjs.Physics3DRuntimeBehavior.DefaultCollisionChecker(this);
|
||||
}
|
||||
|
||||
createShape(): Jolt.Shape {
|
||||
@@ -927,9 +937,7 @@ namespace gdjs {
|
||||
const angularVelocityY = angularVelocity.GetY();
|
||||
const angularVelocityZ = angularVelocity.GetZ();
|
||||
|
||||
let bodyID = this._body.GetID();
|
||||
bodyInterface.RemoveBody(bodyID);
|
||||
bodyInterface.DestroyBody(bodyID);
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
@@ -938,7 +946,7 @@ namespace gdjs {
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
bodyID = this._body.GetID();
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
||||
@@ -1178,6 +1186,33 @@ namespace gdjs {
|
||||
this._needToRecreateBody = true;
|
||||
}
|
||||
|
||||
getShapeOffsetX(): float {
|
||||
return this.shapeOffsetX;
|
||||
}
|
||||
|
||||
setShapeOffsetX(shapeOffsetX: float): void {
|
||||
this.shapeOffsetX = shapeOffsetX;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getShapeOffsetY(): float {
|
||||
return this.shapeOffsetY;
|
||||
}
|
||||
|
||||
setShapeOffsetY(shapeOffsetY: float): void {
|
||||
this.shapeOffsetY = shapeOffsetY;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getShapeOffsetZ(): float {
|
||||
return this.shapeOffsetZ;
|
||||
}
|
||||
|
||||
setShapeOffsetZ(shapeOffsetZ: float): void {
|
||||
this.shapeOffsetZ = shapeOffsetZ;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getFriction(): float {
|
||||
return this.friction;
|
||||
}
|
||||
@@ -1542,9 +1577,9 @@ namespace gdjs {
|
||||
}
|
||||
const body = this._body!;
|
||||
|
||||
const deltaX = towardX - body.GetPosition().GetX();
|
||||
const deltaY = towardY - body.GetPosition().GetY();
|
||||
const deltaZ = towardZ - body.GetPosition().GetZ();
|
||||
const deltaX = towardX - this.owner3D.getX();
|
||||
const deltaY = towardY - this.owner3D.getY();
|
||||
const deltaZ = towardZ - this.owner3D.getZ();
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
@@ -1602,19 +1637,16 @@ namespace gdjs {
|
||||
length: float,
|
||||
towardX: float,
|
||||
towardY: float,
|
||||
towardZ: float,
|
||||
originX: float,
|
||||
originY: float,
|
||||
originZ: float
|
||||
towardZ: float
|
||||
): void {
|
||||
if (this._body === null) {
|
||||
if (!this._createBody()) return;
|
||||
}
|
||||
const body = this._body!;
|
||||
|
||||
const deltaX = towardX - originX;
|
||||
const deltaY = towardY - originY;
|
||||
const deltaZ = towardZ - originZ;
|
||||
const deltaX = towardX - this.owner3D.getX();
|
||||
const deltaY = towardY - this.owner3D.getY();
|
||||
const deltaZ = towardZ - this.owner3D.getZ();
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
@@ -1623,12 +1655,7 @@ namespace gdjs {
|
||||
|
||||
this._sharedData.bodyInterface.AddImpulse(
|
||||
body.GetID(),
|
||||
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio),
|
||||
this.getRVec3(
|
||||
originX * this._sharedData.worldInvScale,
|
||||
originY * this._sharedData.worldInvScale,
|
||||
originZ * this._sharedData.worldInvScale
|
||||
)
|
||||
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -29,6 +29,7 @@ namespace gdjs {
|
||||
owner3D: gdjs.RuntimeObject3D;
|
||||
private _physics3DBehaviorName: string;
|
||||
private _physics3D: Physics3D | null = null;
|
||||
private _isHookedToPhysicsStep = false;
|
||||
_vehicleController: Jolt.WheeledVehicleController | null = null;
|
||||
_stepListener: Jolt.VehicleConstraintStepListener | null = null;
|
||||
_vehicleCollisionTester: Jolt.VehicleCollisionTesterCastCylinder | null =
|
||||
@@ -153,13 +154,19 @@ namespace gdjs {
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior.activated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sharedData = behavior._sharedData;
|
||||
|
||||
this._physics3D = {
|
||||
behavior,
|
||||
};
|
||||
sharedData.registerHook(this);
|
||||
if (!this._isHookedToPhysicsStep) {
|
||||
sharedData.registerHook(this);
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
|
||||
@@ -330,25 +337,33 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override onDeActivate() {
|
||||
if (this._stepListener) {
|
||||
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
|
||||
if (!this._physics3D) {
|
||||
return;
|
||||
}
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {
|
||||
if (this._stepListener) {
|
||||
this._sharedData.physicsSystem.AddStepListener(this._stepListener);
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior) {
|
||||
return;
|
||||
}
|
||||
behavior._destroyBody();
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
_destroyBody() {
|
||||
if (!this._vehicleController) {
|
||||
return;
|
||||
}
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
if (this._stepListener) {
|
||||
// stepListener is removed by onDeActivate
|
||||
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
|
||||
Jolt.destroy(this._stepListener);
|
||||
this._stepListener = null;
|
||||
}
|
||||
@@ -360,6 +375,8 @@ namespace gdjs {
|
||||
// It is destroyed with the constraint.
|
||||
this._vehicleCollisionTester = null;
|
||||
if (this._physics3D) {
|
||||
const { behavior } = this._physics3D;
|
||||
behavior.resetToDefaultBodyUpdater();
|
||||
this._physics3D = null;
|
||||
}
|
||||
}
|
||||
@@ -733,7 +750,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setWheelOffsetZ(wheelOffsetZ: float): void {
|
||||
this._wheelOffsetY = wheelOffsetZ;
|
||||
this._wheelOffsetZ = wheelOffsetZ;
|
||||
this._updateWheels();
|
||||
}
|
||||
|
||||
@@ -783,11 +800,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFrontWheelDrive(): boolean {
|
||||
return this._hasBackWheelDrive;
|
||||
return this._hasFrontWheelDrive;
|
||||
}
|
||||
|
||||
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
||||
this._hasBackWheelDrive = hasFrontWheelDrive;
|
||||
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
||||
this.invalidateShape();
|
||||
}
|
||||
|
||||
@@ -1110,7 +1127,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
destroyBody() {
|
||||
this.carBehavior.onDestroy();
|
||||
this.carBehavior._destroyBody();
|
||||
this.physicsBodyUpdater.destroyBody();
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@ namespace gdjs {
|
||||
owner3D: gdjs.RuntimeObject3D;
|
||||
private _physics3DBehaviorName: string;
|
||||
private _physics3D: Physics3D | null = null;
|
||||
private _isHookedToPhysicsStep = false;
|
||||
character: Jolt.CharacterVirtual | null = null;
|
||||
/**
|
||||
* sharedData is a reference to the shared data of the scene, that registers
|
||||
@@ -169,10 +170,15 @@ namespace gdjs {
|
||||
if (this._physics3D) {
|
||||
return this._physics3D;
|
||||
}
|
||||
if (!this.activated()) {
|
||||
return null;
|
||||
}
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
|
||||
if (!behavior.activated()) {
|
||||
return null;
|
||||
}
|
||||
const sharedData = behavior._sharedData;
|
||||
const jolt = sharedData.jolt;
|
||||
const extendedUpdateSettings = new Jolt.ExtendedUpdateSettings();
|
||||
@@ -196,7 +202,10 @@ namespace gdjs {
|
||||
shapeFilter,
|
||||
};
|
||||
this.setStairHeightMax(this._stairHeightMax);
|
||||
sharedData.registerHook(this);
|
||||
if (!this._isHookedToPhysicsStep) {
|
||||
sharedData.registerHook(this);
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
|
||||
@@ -390,36 +399,48 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override onDeActivate() {
|
||||
this.collisionChecker.clearContacts();
|
||||
if (!this._physics3D) {
|
||||
return;
|
||||
}
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {}
|
||||
override onActivate() {
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior) {
|
||||
return;
|
||||
}
|
||||
behavior._destroyBody();
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
this._destroyCharacter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the character and its body from the physics engine.
|
||||
* This method is called when:
|
||||
* - The Physics3D behavior is deactivated
|
||||
* - This behavior is deactivated
|
||||
* - The object is destroyed
|
||||
*
|
||||
* Only deactivating the character behavior won't destroy the character.
|
||||
* Indeed, deactivated characters don't move as characters but still have collisions.
|
||||
*/
|
||||
_destroyCharacter() {
|
||||
_destroyBody() {
|
||||
if (this.character) {
|
||||
if (this._canBePushed) {
|
||||
this.charactersManager.removeCharacter(this.character);
|
||||
Jolt.destroy(this.character.GetListener());
|
||||
}
|
||||
this.collisionChecker.clearContacts();
|
||||
// The body is destroyed with the character.
|
||||
Jolt.destroy(this.character);
|
||||
this.character = null;
|
||||
if (this._physics3D) {
|
||||
const { behavior } = this._physics3D;
|
||||
behavior.resetToDefaultBodyUpdater();
|
||||
behavior.resetToDefaultCollisionChecker();
|
||||
this._physics3D.behavior._body = null;
|
||||
const {
|
||||
extendedUpdateSettings,
|
||||
@@ -1780,7 +1801,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
destroyBody() {
|
||||
this.characterBehavior._destroyCharacter();
|
||||
this.characterBehavior._destroyBody();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -36,6 +36,7 @@ void PlatformerObjectBehavior::InitializeContent(
|
||||
behaviorContent.SetAttribute("yGrabOffset", 0);
|
||||
behaviorContent.SetAttribute("xGrabTolerance", 10);
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", false);
|
||||
behaviorContent.SetAttribute("useRepeatedJump", false);
|
||||
behaviorContent.SetAttribute("canGoDownFromJumpthru", true);
|
||||
}
|
||||
|
||||
@@ -108,11 +109,11 @@ PlatformerObjectBehavior::GetProperties(
|
||||
.SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties["IgnoreDefaultControls"]
|
||||
.SetLabel(_("Default controls"))
|
||||
.SetLabel(_("Disable default keyboard controls"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
|
||||
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
|
||||
? "false"
|
||||
: "true")
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties["SlopeMaxAngle"]
|
||||
.SetLabel(_("Slope max. angle"))
|
||||
@@ -156,14 +157,23 @@ PlatformerObjectBehavior::GetProperties(
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
|
||||
properties["UseLegacyTrajectory"]
|
||||
.SetLabel(_("Use frame rate dependent trajectories (deprecated, it's "
|
||||
"recommended to leave this unchecked)"))
|
||||
.SetLabel(_("Use frame rate dependent trajectories "
|
||||
"(deprecated — best left unchecked)"))
|
||||
.SetGroup(_("Deprecated options"))
|
||||
.SetDeprecated()
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties["UseRepeatedJump"]
|
||||
.SetLabel(_("Allows repeated jumps while holding the jump key "
|
||||
"(deprecated — best left unchecked)"))
|
||||
.SetGroup(_("Deprecated options"))
|
||||
.SetDeprecated()
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useRepeatedJump", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties["CanGoDownFromJumpthru"]
|
||||
.SetLabel(_("Can go down from jumpthru platforms"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
|
||||
@@ -180,13 +190,15 @@ bool PlatformerObjectBehavior::UpdateProperty(
|
||||
const gd::String& name,
|
||||
const gd::String& value) {
|
||||
if (name == "IgnoreDefaultControls")
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "0"));
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "1"));
|
||||
else if (name == "CanGrabPlatforms")
|
||||
behaviorContent.SetAttribute("canGrabPlatforms", (value == "1"));
|
||||
else if (name == "CanGrabWithoutMoving")
|
||||
behaviorContent.SetAttribute("canGrabWithoutMoving", (value == "1"));
|
||||
else if (name == "UseLegacyTrajectory")
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", (value == "1"));
|
||||
else if (name == "UseRepeatedJump")
|
||||
behaviorContent.SetAttribute("useRepeatedJump", (value == "1"));
|
||||
else if (name == "CanGoDownFromJumpthru")
|
||||
behaviorContent.SetAttribute("canGoDownFromJumpthru", (value == "1"));
|
||||
else if (name == "YGrabOffset")
|
||||
|
@@ -23,7 +23,6 @@ namespace gdjs {
|
||||
interface JumpingStateNetworkSyncData {
|
||||
cjs: number;
|
||||
tscjs: number;
|
||||
jkhsjs: boolean;
|
||||
jfd: boolean;
|
||||
}
|
||||
|
||||
@@ -57,6 +56,7 @@ namespace gdjs {
|
||||
juk: boolean;
|
||||
rpk: boolean;
|
||||
rlk: boolean;
|
||||
jkhsjs: boolean;
|
||||
sn: string;
|
||||
ssd: StateNetworkSyncData;
|
||||
}
|
||||
@@ -119,6 +119,7 @@ namespace gdjs {
|
||||
private _xGrabTolerance: any;
|
||||
|
||||
_useLegacyTrajectory: boolean;
|
||||
_useRepeatedJump: boolean;
|
||||
|
||||
_canGoDownFromJumpthru: boolean = false;
|
||||
|
||||
@@ -139,6 +140,7 @@ namespace gdjs {
|
||||
_upKey: boolean = false;
|
||||
_downKey: boolean = false;
|
||||
_jumpKey: boolean = false;
|
||||
_jumpKeyHeldSinceJumpStart: boolean = false;
|
||||
_releasePlatformKey: boolean = false;
|
||||
_releaseLadderKey: boolean = false;
|
||||
|
||||
@@ -204,6 +206,10 @@ namespace gdjs {
|
||||
behaviorData.useLegacyTrajectory === undefined
|
||||
? true
|
||||
: behaviorData.useLegacyTrajectory;
|
||||
this._useRepeatedJump =
|
||||
behaviorData.useRepeatedJump === undefined
|
||||
? true
|
||||
: behaviorData.useRepeatedJump;
|
||||
this._canGoDownFromJumpthru = behaviorData.canGoDownFromJumpthru;
|
||||
this._slopeMaxAngle = 0;
|
||||
this.setSlopeMaxAngle(behaviorData.slopeMaxAngle);
|
||||
@@ -249,6 +255,7 @@ namespace gdjs {
|
||||
juk: this._wasJumpKeyPressed,
|
||||
rpk: this._wasReleasePlatformKeyPressed,
|
||||
rlk: this._wasReleaseLadderKeyPressed,
|
||||
jkhsjs: this._jumpKeyHeldSinceJumpStart,
|
||||
sn: this._state.toString(),
|
||||
ssd: this._state.getNetworkSyncData(),
|
||||
},
|
||||
@@ -306,6 +313,9 @@ namespace gdjs {
|
||||
if (behaviorSpecificProps.rlk !== this._releaseLadderKey) {
|
||||
this._releaseLadderKey = behaviorSpecificProps.rlk;
|
||||
}
|
||||
if (behaviorSpecificProps.jkhsjs !== this._jumpKeyHeldSinceJumpStart) {
|
||||
this._jumpKeyHeldSinceJumpStart = behaviorSpecificProps.jkhsjs;
|
||||
}
|
||||
|
||||
if (behaviorSpecificProps.sn !== this._state.toString()) {
|
||||
switch (behaviorSpecificProps.sn) {
|
||||
@@ -427,6 +437,11 @@ namespace gdjs {
|
||||
(inputManager.isKeyPressed(LSHIFTKEY) ||
|
||||
inputManager.isKeyPressed(RSHIFTKEY) ||
|
||||
inputManager.isKeyPressed(SPACEKEY)));
|
||||
// Check if the jump key is continuously held since
|
||||
// the beginning of the jump.
|
||||
if (!this._jumpKey) {
|
||||
this._jumpKeyHeldSinceJumpStart = false;
|
||||
}
|
||||
|
||||
this._ladderKey ||
|
||||
(this._ladderKey =
|
||||
@@ -471,7 +486,16 @@ namespace gdjs {
|
||||
this._state.beforeMovingX();
|
||||
|
||||
//Ensure the object is not stuck
|
||||
if (this._separateFromPlatforms(this._potentialCollidingObjects, true)) {
|
||||
const hasPopOutOfPlatform = this._separateFromPlatforms(
|
||||
this._potentialCollidingObjects,
|
||||
true
|
||||
);
|
||||
if (hasPopOutOfPlatform && !this._jumpKey) {
|
||||
// TODO This is probably unnecessary because `_canJump` is already set
|
||||
// to true when entering the `OnFloor` state.
|
||||
// This is wrongly allowing double jumps when characters are flipped
|
||||
// with an offset center.
|
||||
|
||||
//After being unstuck, the object must be able to jump again.
|
||||
this._canJump = true;
|
||||
}
|
||||
@@ -750,7 +774,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
_checkTransitionJumping() {
|
||||
if (this._canJump && this._jumpKey) {
|
||||
if (
|
||||
this._canJump &&
|
||||
this._jumpKey &&
|
||||
(!this._jumpKeyHeldSinceJumpStart || this._useRepeatedJump)
|
||||
) {
|
||||
this._setJumping();
|
||||
}
|
||||
}
|
||||
@@ -2270,7 +2298,6 @@ namespace gdjs {
|
||||
private _behavior: PlatformerObjectRuntimeBehavior;
|
||||
private _currentJumpSpeed: number = 0;
|
||||
private _timeSinceCurrentJumpStart: number = 0;
|
||||
private _jumpKeyHeldSinceJumpStart: boolean = false;
|
||||
private _jumpingFirstDelta: boolean = false;
|
||||
|
||||
constructor(behavior: PlatformerObjectRuntimeBehavior) {
|
||||
@@ -2288,7 +2315,7 @@ namespace gdjs {
|
||||
enter(from: State) {
|
||||
const behavior = this._behavior;
|
||||
this._timeSinceCurrentJumpStart = 0;
|
||||
this._jumpKeyHeldSinceJumpStart = true;
|
||||
behavior._jumpKeyHeldSinceJumpStart = true;
|
||||
|
||||
if (from !== behavior._jumping && from !== behavior._falling) {
|
||||
this._jumpingFirstDelta = true;
|
||||
@@ -2329,17 +2356,12 @@ namespace gdjs {
|
||||
beforeMovingY(timeDelta: float, oldX: float) {
|
||||
const behavior = this._behavior;
|
||||
|
||||
// Check if the jump key is continuously held since
|
||||
// the beginning of the jump.
|
||||
if (!behavior._jumpKey) {
|
||||
this._jumpKeyHeldSinceJumpStart = false;
|
||||
}
|
||||
this._timeSinceCurrentJumpStart += timeDelta;
|
||||
|
||||
const previousJumpSpeed = this._currentJumpSpeed;
|
||||
// Decrease jump speed after the (optional) jump sustain time is over.
|
||||
const sustainJumpSpeed =
|
||||
this._jumpKeyHeldSinceJumpStart &&
|
||||
behavior._jumpKeyHeldSinceJumpStart &&
|
||||
this._timeSinceCurrentJumpStart < behavior._jumpSustainTime;
|
||||
if (!sustainJumpSpeed) {
|
||||
this._currentJumpSpeed -= behavior._gravity * timeDelta;
|
||||
@@ -2374,7 +2396,6 @@ namespace gdjs {
|
||||
return {
|
||||
cjs: this._currentJumpSpeed,
|
||||
tscjs: this._timeSinceCurrentJumpStart,
|
||||
jkhsjs: this._jumpKeyHeldSinceJumpStart,
|
||||
jfd: this._jumpingFirstDelta,
|
||||
};
|
||||
}
|
||||
@@ -2382,7 +2403,6 @@ namespace gdjs {
|
||||
updateFromNetworkSyncData(data: JumpingStateNetworkSyncData) {
|
||||
this._currentJumpSpeed = data.cjs;
|
||||
this._timeSinceCurrentJumpStart = data.tscjs;
|
||||
this._jumpKeyHeldSinceJumpStart = data.jkhsjs;
|
||||
this._jumpingFirstDelta = data.jfd;
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: slopeMaxAngle,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -331,6 +332,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -491,6 +493,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -624,6 +627,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 0,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -728,6 +732,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -26,6 +26,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -172,6 +173,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -287,6 +289,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: true,
|
||||
useRepeatedJump: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -383,6 +386,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: true,
|
||||
useRepeatedJump: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -481,6 +485,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: true,
|
||||
useRepeatedJump: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -555,6 +560,8 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
/** @type {gdjs.PlatformerObjectRuntimeBehavior} */
|
||||
let characterBehavior;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
@@ -578,6 +585,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -585,6 +593,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -32);
|
||||
characterBehavior = object.getBehavior('auto1');
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
@@ -644,6 +653,48 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
expect(object.getY()).to.be(-30);
|
||||
});
|
||||
|
||||
it('can only jump once while the jump key is held', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++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(characterBehavior.isFalling()).to.be(false);
|
||||
expect(characterBehavior.isFallingWithoutJumping()).to.be(false);
|
||||
expect(characterBehavior.isMoving()).to.be(false);
|
||||
|
||||
// The character jumps a first time.
|
||||
for (let i = 0; i < 80; ++i) {
|
||||
characterBehavior.simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
characterBehavior.isJumping(true);
|
||||
}
|
||||
// The character lands back on the floor
|
||||
// while the player holds the jump key.
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
characterBehavior.simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
characterBehavior.isOnFloor(true);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// The character doesn't jump a 2nd time.
|
||||
characterBehavior.simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
characterBehavior.isOnFloor(true);
|
||||
|
||||
// The player release the jump key.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
characterBehavior.isOnFloor(true);
|
||||
|
||||
// The character can now jump again.
|
||||
characterBehavior.simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
characterBehavior.isJumping(true);
|
||||
});
|
||||
|
||||
it('can jump, and only sustain the jump while key held', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
@@ -1126,6 +1177,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
canGoDownFromJumpthru: true,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -1467,6 +1519,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -1497,6 +1550,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
@@ -1637,6 +1691,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -1758,6 +1813,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -1843,6 +1899,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -1921,6 +1978,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -26,6 +26,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
canGrabWithoutMoving: canGrabWithoutMoving,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -233,6 +234,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
canGrabWithoutMoving: true,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -398,6 +400,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -32,6 +32,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -308,6 +309,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -27,6 +27,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -438,6 +439,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
@@ -546,6 +548,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
slopeMaxAngle: slopeMaxAngle,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -36,6 +36,7 @@ describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, fu
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
useLegacyTrajectory: false,
|
||||
useRepeatedJump: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
|
@@ -311,7 +311,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
"such as \"Path line\" (in the Advanced category) can be "
|
||||
"used to draw. Be sure to use \"End fill path\" action when "
|
||||
"you're done drawing the shape."),
|
||||
_("Begins drawing filling of an advanced path "
|
||||
_("Begin drawing filling of an advanced path "
|
||||
"with _PARAM0_ (start: _PARAM1_;_PARAM2_)"),
|
||||
_("Advanced"),
|
||||
"res/actions/beginFillPath24.png",
|
||||
|
@@ -246,10 +246,10 @@ std::map<gd::String, gd::PropertyDescriptor> ShapePainterObject::GetProperties()
|
||||
objectProperties["antialiasing"]
|
||||
.SetValue(GetAntialiasing())
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("none")
|
||||
.AddExtraInfo("low")
|
||||
.AddExtraInfo("medium")
|
||||
.AddExtraInfo("high")
|
||||
.AddChoice("none", _("None"))
|
||||
.AddChoice("low", _("Low quality"))
|
||||
.AddChoice("medium", _("Medium quality"))
|
||||
.AddChoice("high", _("High quality"))
|
||||
.SetGroup(_("Drawing"))
|
||||
.SetLabel(_("Antialiasing"))
|
||||
.SetDescription(_("Antialiasing mode"));
|
||||
|
@@ -195,11 +195,35 @@ namespace gdjs {
|
||||
}
|
||||
/**
|
||||
* To be called when the game is disposed.
|
||||
* Clear the Spine Atlases loaded in this manager.
|
||||
* Clear the Spine atlases loaded in this manager.
|
||||
*/
|
||||
dispose(): void {
|
||||
this._loadedSpineAtlases.clear();
|
||||
this._loadingSpineAtlases.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the Spine atlases loaded in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
|
||||
if (loadedSpineAtlas) {
|
||||
loadedSpineAtlas.dispose();
|
||||
this._loadedSpineAtlases.delete(resourceData);
|
||||
}
|
||||
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
|
||||
if (loadingSpineAtlas) {
|
||||
loadingSpineAtlas.then((atl) => atl.dispose());
|
||||
this._loadingSpineAtlases.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -126,5 +126,22 @@ namespace gdjs {
|
||||
dispose(): void {
|
||||
this._loadedSpines.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the Spine skeleton data loaded in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedSpine = this._loadedSpines.get(resourceData);
|
||||
if (loadedSpine) {
|
||||
this._loadedSpines.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,6 @@ namespace gdjs {
|
||||
|
||||
export type SpineNetworkSyncDataType = {
|
||||
opa: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
scaX: float;
|
||||
scaY: float;
|
||||
flipX: boolean;
|
||||
@@ -117,8 +115,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
opa: this._opacity,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
scaX: this.getScaleX(),
|
||||
scaY: this.getScaleY(),
|
||||
flipX: this.isFlippedX(),
|
||||
@@ -137,12 +133,6 @@ namespace gdjs {
|
||||
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
|
||||
this.setOpacity(syncData.opa);
|
||||
}
|
||||
if (syncData.wid !== undefined && syncData.wid !== this.getWidth()) {
|
||||
this.setWidth(syncData.wid);
|
||||
}
|
||||
if (syncData.hei !== undefined && syncData.hei !== this.getHeight()) {
|
||||
this.setHeight(syncData.hei);
|
||||
}
|
||||
if (syncData.scaX !== undefined && syncData.scaX !== this.getScaleX()) {
|
||||
this.setScaleX(syncData.scaX);
|
||||
}
|
||||
|
@@ -78,6 +78,9 @@ module.exports = {
|
||||
} else if (propertyName === 'disabled') {
|
||||
objectContent.disabled = newValue === '1';
|
||||
return true;
|
||||
} else if (propertyName === 'spellCheck') {
|
||||
objectContent.spellCheck = newValue === '1';
|
||||
return true;
|
||||
} else if (propertyName === 'maxLength') {
|
||||
objectContent.maxLength = newValue;
|
||||
return true;
|
||||
@@ -131,14 +134,14 @@ module.exports = {
|
||||
.getOrCreate('inputType')
|
||||
.setValue(objectContent.inputType || '')
|
||||
.setType('choice')
|
||||
.addExtraInfo('text')
|
||||
.addExtraInfo('text area')
|
||||
.addExtraInfo('email')
|
||||
.addExtraInfo('password')
|
||||
.addExtraInfo('number')
|
||||
.addExtraInfo('telephone number')
|
||||
.addExtraInfo('url')
|
||||
.addExtraInfo('search')
|
||||
.addChoice('text', _('Text'))
|
||||
.addChoice('text area', _('Text area'))
|
||||
.addChoice('email', _('Email'))
|
||||
.addChoice('password', _('Password'))
|
||||
.addChoice('number', _('Number'))
|
||||
.addChoice('telephone number', _('Telephone number'))
|
||||
.addChoice('url', _('URL'))
|
||||
.addChoice('search', _('Search'))
|
||||
.setLabel(_('Input type'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -160,6 +163,13 @@ module.exports = {
|
||||
.setLabel(_('Disabled'))
|
||||
.setGroup(_('Field'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('spellCheck')
|
||||
.setValue(objectContent.spellCheck ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Enable spell check'))
|
||||
.setGroup(_('Field'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('textColor')
|
||||
.setValue(objectContent.textColor || '0;0;0')
|
||||
@@ -250,9 +260,9 @@ module.exports = {
|
||||
.getOrCreate('textAlign')
|
||||
.setValue(objectContent.textAlign || 'left')
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Text alignment'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
@@ -272,6 +282,7 @@ module.exports = {
|
||||
borderWidth: 1,
|
||||
readOnly: false,
|
||||
disabled: false,
|
||||
spellCheck: false,
|
||||
paddingX: 2,
|
||||
paddingY: 1,
|
||||
textAlign: 'left',
|
||||
@@ -592,6 +603,21 @@ module.exports = {
|
||||
.setFunctionName('setDisabled')
|
||||
.setGetter('isDisabled');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'boolean',
|
||||
'SpellCheck',
|
||||
_('Spell check enabled'),
|
||||
_('spell check is enabled'),
|
||||
_('spell check enabled'),
|
||||
'',
|
||||
'res/conditions/text24_black.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('boolean', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setSpellCheck')
|
||||
.setGetter('isSpellCheckEnabled');
|
||||
|
||||
// Other expressions/conditions/actions:
|
||||
|
||||
// Deprecated
|
||||
|
@@ -106,6 +106,7 @@ namespace gdjs {
|
||||
this.updateBorderWidth();
|
||||
this.updateDisabled();
|
||||
this.updateReadOnly();
|
||||
this.updateSpellCheck();
|
||||
this.updateTextAlign();
|
||||
this.updateMaxLength();
|
||||
this.updatePadding();
|
||||
@@ -342,6 +343,12 @@ namespace gdjs {
|
||||
this._input.readOnly = this._object.isReadOnly();
|
||||
}
|
||||
|
||||
updateSpellCheck() {
|
||||
if (!this._input) return;
|
||||
|
||||
this._input.spellcheck = this._object.isSpellCheckEnabled();
|
||||
}
|
||||
|
||||
updateMaxLength() {
|
||||
const input = this._input;
|
||||
if (!input) return;
|
||||
|
@@ -54,6 +54,7 @@ namespace gdjs {
|
||||
disabled: boolean;
|
||||
readOnly: boolean;
|
||||
// ---- Values can be undefined because of support for these feature was added in v5.5.222.
|
||||
spellCheck?: boolean;
|
||||
paddingX?: float;
|
||||
paddingY?: float;
|
||||
textAlign?: SupportedTextAlign;
|
||||
@@ -64,8 +65,6 @@ namespace gdjs {
|
||||
|
||||
export type TextInputNetworkSyncDataType = {
|
||||
opa: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
txt: string;
|
||||
frn: string;
|
||||
fs: number;
|
||||
@@ -79,6 +78,7 @@ namespace gdjs {
|
||||
bw: float;
|
||||
dis: boolean;
|
||||
ro: boolean;
|
||||
sc: boolean;
|
||||
};
|
||||
|
||||
export type TextInputNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -118,6 +118,7 @@ namespace gdjs {
|
||||
private _borderWidth: float;
|
||||
private _disabled: boolean;
|
||||
private _readOnly: boolean;
|
||||
private _spellCheck: boolean;
|
||||
private _isSubmitted: boolean;
|
||||
_renderer: TextInputRuntimeObjectRenderer;
|
||||
|
||||
@@ -142,6 +143,10 @@ namespace gdjs {
|
||||
this._borderWidth = objectData.content.borderWidth;
|
||||
this._disabled = objectData.content.disabled;
|
||||
this._readOnly = objectData.content.readOnly;
|
||||
this._spellCheck =
|
||||
objectData.content.spellCheck !== undefined
|
||||
? objectData.content.spellCheck
|
||||
: false;
|
||||
this._textAlign = parseTextAlign(objectData.content.textAlign);
|
||||
this._maxLength = objectData.content.maxLength || 0;
|
||||
this._paddingX =
|
||||
@@ -228,6 +233,12 @@ namespace gdjs {
|
||||
if (oldObjectData.content.readOnly !== newObjectData.content.readOnly) {
|
||||
this.setReadOnly(newObjectData.content.readOnly);
|
||||
}
|
||||
if (
|
||||
newObjectData.content.spellCheck !== undefined &&
|
||||
oldObjectData.content.spellCheck !== newObjectData.content.spellCheck
|
||||
) {
|
||||
this.setSpellCheck(newObjectData.content.spellCheck);
|
||||
}
|
||||
if (
|
||||
newObjectData.content.maxLength !== undefined &&
|
||||
oldObjectData.content.maxLength !== newObjectData.content.maxLength
|
||||
@@ -260,8 +271,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
opa: this.getOpacity(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
txt: this.getText(),
|
||||
frn: this.getFontResourceName(),
|
||||
fs: this.getFontSize(),
|
||||
@@ -275,6 +284,7 @@ namespace gdjs {
|
||||
bw: this.getBorderWidth(),
|
||||
dis: this.isDisabled(),
|
||||
ro: this.isReadOnly(),
|
||||
sc: this.isSpellCheckEnabled(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -282,8 +292,6 @@ namespace gdjs {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
|
||||
if (syncData.opa !== undefined) this.setOpacity(syncData.opa);
|
||||
if (syncData.wid !== undefined) this.setWidth(syncData.wid);
|
||||
if (syncData.hei !== undefined) this.setHeight(syncData.hei);
|
||||
if (syncData.txt !== undefined) this.setText(syncData.txt);
|
||||
if (syncData.frn !== undefined) this.setFontResourceName(syncData.frn);
|
||||
if (syncData.fs !== undefined) this.setFontSize(syncData.fs);
|
||||
@@ -297,6 +305,7 @@ namespace gdjs {
|
||||
if (syncData.bw !== undefined) this.setBorderWidth(syncData.bw);
|
||||
if (syncData.dis !== undefined) this.setDisabled(syncData.dis);
|
||||
if (syncData.ro !== undefined) this.setReadOnly(syncData.ro);
|
||||
if (syncData.sc !== undefined) this.setSpellCheck(syncData.sc);
|
||||
}
|
||||
|
||||
updatePreRender(instanceContainer: RuntimeInstanceContainer): void {
|
||||
@@ -569,6 +578,15 @@ namespace gdjs {
|
||||
return this._readOnly;
|
||||
}
|
||||
|
||||
setSpellCheck(value: boolean) {
|
||||
this._spellCheck = value;
|
||||
this._renderer.updateSpellCheck();
|
||||
}
|
||||
|
||||
isSpellCheckEnabled(): boolean {
|
||||
return this._spellCheck;
|
||||
}
|
||||
|
||||
isFocused(): boolean {
|
||||
return this._renderer.isFocused();
|
||||
}
|
||||
|
@@ -160,9 +160,9 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
|
||||
objectProperties["textAlignment"]
|
||||
.SetValue(textAlignment)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("left")
|
||||
.AddExtraInfo("center")
|
||||
.AddExtraInfo("right")
|
||||
.AddChoice("left", _("Left"))
|
||||
.AddChoice("center", _("Center"))
|
||||
.AddChoice("right", _("Right"))
|
||||
.SetLabel(_("Alignment"))
|
||||
.SetDescription(_("Alignment of the text when multiple lines are displayed"))
|
||||
.SetGroup(_("Font"))
|
||||
@@ -171,9 +171,9 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
|
||||
objectProperties["verticalTextAlignment"]
|
||||
.SetValue(verticalTextAlignment)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("top")
|
||||
.AddExtraInfo("center")
|
||||
.AddExtraInfo("bottom")
|
||||
.AddChoice("top", _("Top"))
|
||||
.AddChoice("center", _("Center"))
|
||||
.AddChoice("bottom", _("Bottom"))
|
||||
.SetLabel(_("Vertical alignment"))
|
||||
.SetGroup(_("Font"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
@@ -102,9 +102,9 @@ const defineTileMap = function (extension, _, gd) {
|
||||
'displayMode',
|
||||
new gd.PropertyDescriptor(objectContent.displayMode)
|
||||
.setType('choice')
|
||||
.addExtraInfo('visible')
|
||||
.addExtraInfo('all')
|
||||
.addExtraInfo('index')
|
||||
.addChoice('visible', _('Visible layers'))
|
||||
.addChoice('all', _('All layers'))
|
||||
.addChoice('index', _('Only the layer with the specified index'))
|
||||
.setLabel(_('Display mode'))
|
||||
.setGroup(_('Appearance'))
|
||||
);
|
||||
|
@@ -17,8 +17,6 @@ namespace gdjs {
|
||||
export type SimpleTileMapNetworkSyncDataType = {
|
||||
op: number;
|
||||
ai: string;
|
||||
wid: number;
|
||||
hei: number;
|
||||
// TODO: Support tilemap synchronization. Find an efficient way to send tiles changes.
|
||||
};
|
||||
|
||||
@@ -170,8 +168,6 @@ namespace gdjs {
|
||||
...super.getNetworkSyncData(),
|
||||
op: this._opacity,
|
||||
ai: this._atlasImage,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -186,18 +182,6 @@ namespace gdjs {
|
||||
) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
if (
|
||||
networkSyncData.wid !== undefined &&
|
||||
networkSyncData.wid !== this.getWidth()
|
||||
) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (
|
||||
networkSyncData.hei !== undefined &&
|
||||
networkSyncData.hei !== this.getHeight()
|
||||
) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.ai !== undefined) {
|
||||
// TODO: support changing the atlas texture
|
||||
}
|
||||
|
@@ -26,8 +26,6 @@ namespace gdjs {
|
||||
os: float;
|
||||
fo: float;
|
||||
oo: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
};
|
||||
|
||||
export type TilemapCollisionMaskNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -202,8 +200,6 @@ namespace gdjs {
|
||||
os: this.getOutlineSize(),
|
||||
fo: this.getFillOpacity(),
|
||||
oo: this.getOutlineOpacity(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -236,12 +232,6 @@ namespace gdjs {
|
||||
if (networkSyncData.oo !== undefined) {
|
||||
this.setOutlineOpacity(networkSyncData.oo);
|
||||
}
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData): void {
|
||||
|
@@ -25,8 +25,6 @@ namespace gdjs {
|
||||
lai: number;
|
||||
lei: number;
|
||||
asps: number;
|
||||
wid: number;
|
||||
hei: number;
|
||||
};
|
||||
|
||||
export type TilemapNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -158,8 +156,6 @@ namespace gdjs {
|
||||
lai: this._layerIndex,
|
||||
lei: this._levelIndex,
|
||||
asps: this._animationSpeedScale,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,12 +186,6 @@ namespace gdjs {
|
||||
if (networkSyncData.asps !== undefined) {
|
||||
this.setAnimationSpeedScale(networkSyncData.asps);
|
||||
}
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData): void {
|
||||
|
@@ -15,8 +15,6 @@ namespace gdjs {
|
||||
export type TiledSpriteObjectData = ObjectData & TiledSpriteObjectDataType;
|
||||
|
||||
export type TiledSpriteNetworkSyncDataType = {
|
||||
wid: number;
|
||||
hei: number;
|
||||
xo: number;
|
||||
yo: number;
|
||||
op: number;
|
||||
@@ -83,8 +81,6 @@ namespace gdjs {
|
||||
getNetworkSyncData(): TiledSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
xo: this.getXOffset(),
|
||||
yo: this.getYOffset(),
|
||||
op: this.getOpacity(),
|
||||
@@ -99,12 +95,6 @@ namespace gdjs {
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.xo !== undefined) {
|
||||
this.setXOffset(networkSyncData.xo);
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ void TopDownMovementBehavior::InitializeContent(
|
||||
behaviorContent.SetAttribute("viewpoint", "TopDown");
|
||||
behaviorContent.SetAttribute("customIsometryAngle", 30);
|
||||
behaviorContent.SetAttribute("movementAngleOffset", 0);
|
||||
behaviorContent.SetAttribute("useLegacyTurnBack", false);
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
@@ -89,10 +90,20 @@ TopDownMovementBehavior::GetProperties(
|
||||
.SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
|
||||
properties["IgnoreDefaultControls"]
|
||||
.SetLabel(_("Default controls"))
|
||||
.SetLabel(_("Disable default keyboard controls"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden)
|
||||
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
|
||||
? "false"
|
||||
: "true")
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties["UseLegacyTurnBack"]
|
||||
.SetLabel(_("Only use acceleration to turn back "
|
||||
"(deprecated — best left unchecked)"))
|
||||
.SetGroup(_("Deprecated options"))
|
||||
.SetDeprecated()
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTurnBack", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
|
||||
gd::String viewpoint = behaviorContent.GetStringAttribute("viewpoint");
|
||||
@@ -145,7 +156,7 @@ bool TopDownMovementBehavior::UpdateProperty(
|
||||
const gd::String& name,
|
||||
const gd::String& value) {
|
||||
if (name == "IgnoreDefaultControls") {
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "0"));
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "1"));
|
||||
return true;
|
||||
}
|
||||
if (name == "AllowDiagonals") {
|
||||
@@ -156,6 +167,9 @@ bool TopDownMovementBehavior::UpdateProperty(
|
||||
behaviorContent.SetAttribute("rotateObject", (value != "0"));
|
||||
return true;
|
||||
}
|
||||
if (name == "UseLegacyTurnBack") {
|
||||
behaviorContent.SetAttribute("useLegacyTurnBack", (value == "1"));
|
||||
}
|
||||
if (name == "Viewpoint") {
|
||||
// Fix the offset angle when switching between top-down and isometry
|
||||
const gd::String& oldValue =
|
||||
|
@@ -37,6 +37,7 @@ namespace gdjs {
|
||||
private _angleOffset: float;
|
||||
private _ignoreDefaultControls: boolean;
|
||||
private _movementAngleOffset: float;
|
||||
private _useLegacyTurnBack: boolean;
|
||||
|
||||
/** The latest angle of movement, in degrees. */
|
||||
private _angle: float = 0;
|
||||
@@ -102,6 +103,10 @@ namespace gdjs {
|
||||
behaviorData.customIsometryAngle
|
||||
);
|
||||
this._movementAngleOffset = behaviorData.movementAngleOffset || 0;
|
||||
this._useLegacyTurnBack =
|
||||
behaviorData.useLegacyTurnBack === undefined
|
||||
? true
|
||||
: behaviorData.useLegacyTurnBack;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): TopDownMovementNetworkSyncData {
|
||||
@@ -303,9 +308,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getSpeed(): float {
|
||||
return Math.sqrt(
|
||||
this._xVelocity * this._xVelocity + this._yVelocity * this._yVelocity
|
||||
);
|
||||
return Math.hypot(this._xVelocity, this._yVelocity);
|
||||
}
|
||||
|
||||
getXVelocity(): float {
|
||||
@@ -468,74 +471,68 @@ namespace gdjs {
|
||||
// variables without assigning them a value.
|
||||
let directionInRad = 0;
|
||||
let directionInDeg = 0;
|
||||
let cos = 1;
|
||||
let sin = 0;
|
||||
|
||||
let isMoving = false;
|
||||
let targetedSpeed = 0;
|
||||
// Update the speed of the object:
|
||||
if (direction !== -1) {
|
||||
isMoving = true;
|
||||
directionInRad =
|
||||
((direction + this._movementAngleOffset / 45) * Math.PI) / 4.0;
|
||||
directionInDeg = direction * 45 + this._movementAngleOffset;
|
||||
// This makes the trigo resilient to rounding errors on directionInRad.
|
||||
cos = Math.cos(directionInRad);
|
||||
sin = Math.sin(directionInRad);
|
||||
if (cos === -1 || cos === 1) {
|
||||
sin = 0;
|
||||
}
|
||||
if (sin === -1 || sin === 1) {
|
||||
cos = 0;
|
||||
}
|
||||
this._xVelocity += this._acceleration * timeDelta * cos;
|
||||
this._yVelocity += this._acceleration * timeDelta * sin;
|
||||
targetedSpeed = this._maxSpeed;
|
||||
} else if (this._stickForce !== 0) {
|
||||
isMoving = true;
|
||||
if (!this._allowDiagonals) {
|
||||
this._stickAngle = 90 * Math.floor((this._stickAngle + 45) / 90);
|
||||
}
|
||||
directionInDeg = this._stickAngle + this._movementAngleOffset;
|
||||
directionInRad = (directionInDeg * Math.PI) / 180;
|
||||
const norm = this._acceleration * timeDelta * this._stickForce;
|
||||
// This makes the trigo resilient to rounding errors on directionInRad.
|
||||
cos = Math.cos(directionInRad);
|
||||
sin = Math.sin(directionInRad);
|
||||
if (cos === -1 || cos === 1) {
|
||||
sin = 0;
|
||||
}
|
||||
if (sin === -1 || sin === 1) {
|
||||
cos = 0;
|
||||
}
|
||||
this._xVelocity += norm * cos;
|
||||
this._yVelocity += norm * sin;
|
||||
targetedSpeed = this._maxSpeed * this._stickForce;
|
||||
|
||||
this._wasStickUsed = true;
|
||||
this._stickForce = 0;
|
||||
} else if (this._yVelocity !== 0 || this._xVelocity !== 0) {
|
||||
isMoving = true;
|
||||
directionInRad = Math.atan2(this._yVelocity, this._xVelocity);
|
||||
directionInDeg = (directionInRad * 180.0) / Math.PI;
|
||||
const xVelocityWasPositive = this._xVelocity >= 0;
|
||||
const yVelocityWasPositive = this._yVelocity >= 0;
|
||||
}
|
||||
if (isMoving) {
|
||||
// This makes the trigo resilient to rounding errors on directionInRad.
|
||||
cos = Math.cos(directionInRad);
|
||||
sin = Math.sin(directionInRad);
|
||||
let cos = Math.cos(directionInRad);
|
||||
let sin = Math.sin(directionInRad);
|
||||
if (cos === -1 || cos === 1) {
|
||||
sin = 0;
|
||||
}
|
||||
if (sin === -1 || sin === 1) {
|
||||
cos = 0;
|
||||
}
|
||||
this._xVelocity -= this._deceleration * timeDelta * cos;
|
||||
this._yVelocity -= this._deceleration * timeDelta * sin;
|
||||
if (this._xVelocity > 0 !== xVelocityWasPositive) {
|
||||
this._xVelocity = 0;
|
||||
}
|
||||
if (this._yVelocity > 0 !== yVelocityWasPositive) {
|
||||
this._yVelocity = 0;
|
||||
|
||||
let currentSpeed = Math.hypot(this._xVelocity, this._yVelocity);
|
||||
const dotProduct = this._xVelocity * cos + this._yVelocity * sin;
|
||||
if (dotProduct < 0) {
|
||||
// The object is turning back.
|
||||
// Keep the negative velocity projected on the new direction.
|
||||
currentSpeed = dotProduct;
|
||||
}
|
||||
const speed = TopDownMovementRuntimeBehavior.getAcceleratedSpeed(
|
||||
currentSpeed,
|
||||
targetedSpeed,
|
||||
this._maxSpeed,
|
||||
this._acceleration,
|
||||
this._deceleration,
|
||||
timeDelta,
|
||||
this._useLegacyTurnBack
|
||||
);
|
||||
this._xVelocity = speed * cos;
|
||||
this._yVelocity = speed * sin;
|
||||
}
|
||||
|
||||
const squaredSpeed =
|
||||
this._xVelocity * this._xVelocity + this._yVelocity * this._yVelocity;
|
||||
if (squaredSpeed > this._maxSpeed * this._maxSpeed) {
|
||||
this._xVelocity = this._maxSpeed * cos;
|
||||
this._yVelocity = this._maxSpeed * sin;
|
||||
const ratio = this._maxSpeed / Math.sqrt(squaredSpeed);
|
||||
this._xVelocity *= ratio;
|
||||
this._yVelocity *= ratio;
|
||||
}
|
||||
|
||||
// No acceleration for angular speed for now.
|
||||
@@ -589,9 +586,72 @@ namespace gdjs {
|
||||
this._rightKey = false;
|
||||
this._upKey = false;
|
||||
this._downKey = false;
|
||||
this._stickForce = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static getAcceleratedSpeed(
|
||||
currentSpeed: float,
|
||||
targetedSpeed: float,
|
||||
speedMax: float,
|
||||
acceleration: float,
|
||||
deceleration: float,
|
||||
timeDelta: float,
|
||||
useLegacyTurnBack: boolean = false
|
||||
): float {
|
||||
let newSpeed = currentSpeed;
|
||||
const turningBackAcceleration = useLegacyTurnBack
|
||||
? acceleration
|
||||
: Math.max(acceleration, deceleration);
|
||||
if (targetedSpeed < 0) {
|
||||
if (currentSpeed <= targetedSpeed) {
|
||||
// Reduce the speed to match the stick force.
|
||||
newSpeed = Math.min(
|
||||
targetedSpeed,
|
||||
currentSpeed + turningBackAcceleration * timeDelta
|
||||
);
|
||||
} else if (currentSpeed <= 0) {
|
||||
// Accelerate
|
||||
newSpeed -= Math.max(-speedMax, acceleration * timeDelta);
|
||||
} else {
|
||||
// Turn back at least as fast as it would stop.
|
||||
newSpeed = Math.max(
|
||||
targetedSpeed,
|
||||
currentSpeed - turningBackAcceleration * timeDelta
|
||||
);
|
||||
}
|
||||
} else if (targetedSpeed > 0) {
|
||||
if (currentSpeed >= targetedSpeed) {
|
||||
// Reduce the speed to match the stick force.
|
||||
newSpeed = Math.max(
|
||||
targetedSpeed,
|
||||
currentSpeed - turningBackAcceleration * timeDelta
|
||||
);
|
||||
} else if (currentSpeed >= 0) {
|
||||
// Accelerate
|
||||
newSpeed = Math.min(
|
||||
speedMax,
|
||||
currentSpeed + acceleration * timeDelta
|
||||
);
|
||||
} else {
|
||||
// Turn back at least as fast as it would stop.
|
||||
newSpeed = Math.min(
|
||||
targetedSpeed,
|
||||
currentSpeed + turningBackAcceleration * timeDelta
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Decelerate and stop.
|
||||
if (currentSpeed < 0) {
|
||||
newSpeed = Math.min(currentSpeed + deceleration * timeDelta, 0);
|
||||
}
|
||||
if (currentSpeed > 0) {
|
||||
newSpeed = Math.max(currentSpeed - deceleration * timeDelta, 0);
|
||||
}
|
||||
}
|
||||
return newSpeed;
|
||||
}
|
||||
|
||||
simulateControl(input: string) {
|
||||
if (input === 'Left') {
|
||||
this._leftKey = true;
|
||||
|
@@ -60,7 +60,7 @@ module.exports = {
|
||||
'Tween',
|
||||
_('Tweening'),
|
||||
_(
|
||||
'Animate object properties over time. This allows smooth transitions, animations or movement of objects to specified positions.'
|
||||
'Smoothly animate object properties over time — such as position, rotation scale, opacity, and more — as well as variables. Ideal for creating fluid transitions and UI animations. While you can use tweens to move objects, other behaviors (like platform, physics, ellipse movement...) or forces are often better suited for dynamic movement. Tween is best used for animating UI elements, static objects that need to move from one point to another, or other values like variables.'
|
||||
),
|
||||
'Matthias Meike, Florian Rival',
|
||||
'Open source (MIT License)'
|
||||
|
@@ -18,8 +18,6 @@ namespace gdjs {
|
||||
|
||||
export type VideoNetworkSyncDataType = {
|
||||
op: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
// We don't sync volume, as it's probably a user setting?
|
||||
pla: boolean;
|
||||
loop: boolean;
|
||||
@@ -105,8 +103,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
op: this._opacity,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
pla: this.isPlayed(),
|
||||
loop: this.isLooped(),
|
||||
ct: this.getCurrentTime(),
|
||||
@@ -120,12 +116,6 @@ namespace gdjs {
|
||||
if (this._opacity !== undefined && this._opacity && syncData.op) {
|
||||
this.setOpacity(syncData.op);
|
||||
}
|
||||
if (this.getWidth() !== undefined && this.getWidth() !== syncData.wid) {
|
||||
this.setWidth(syncData.wid);
|
||||
}
|
||||
if (this.getHeight() !== undefined && this.getHeight() !== syncData.hei) {
|
||||
this.setHeight(syncData.hei);
|
||||
}
|
||||
if (syncData.pla !== undefined && this.isPlayed() !== syncData.pla) {
|
||||
syncData.pla ? this.play() : this.pause();
|
||||
}
|
||||
|
@@ -14,6 +14,12 @@ namespace gdjs {
|
||||
animatable?: SpriteAnimationData[];
|
||||
variant: string;
|
||||
childrenContent: { [objectName: string]: ObjectConfiguration & any };
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
export type CustomObjectNetworkSyncDataType = ObjectNetworkSyncData & {
|
||||
ifx: boolean;
|
||||
ify: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -111,9 +117,19 @@ namespace gdjs {
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (customObjectData.variant) {
|
||||
if (
|
||||
customObjectData.variant &&
|
||||
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
@@ -154,10 +170,12 @@ namespace gdjs {
|
||||
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
|
||||
super.reinitialize(objectData);
|
||||
|
||||
this._initializeFromObjectData(objectData);
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(objectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
// When changing the variant, the instance is like a new instance.
|
||||
// We call again `onCreated` at the end, like done by the constructor
|
||||
// the first time it's created.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
@@ -172,9 +190,57 @@ namespace gdjs {
|
||||
newObjectData.animatable || []
|
||||
);
|
||||
}
|
||||
if (oldObjectData.variant !== newObjectData.variant) {
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const hasInnerAreaChanged =
|
||||
oldObjectData.isInnerAreaFollowingParentSize &&
|
||||
this._instanceContainer._initialInnerArea &&
|
||||
this._innerArea &&
|
||||
(this._instanceContainer._initialInnerArea.min[0] !==
|
||||
this._innerArea.min[0] ||
|
||||
this._instanceContainer._initialInnerArea.min[1] !==
|
||||
this._innerArea.min[1] ||
|
||||
this._instanceContainer._initialInnerArea.max[0] !==
|
||||
this._innerArea.max[0] ||
|
||||
this._instanceContainer._initialInnerArea.max[1] !==
|
||||
this._innerArea.max[1]);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(newObjectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
this.onCreated();
|
||||
|
||||
// Keep the custom size
|
||||
if (hasInnerAreaChanged) {
|
||||
this.setWidth(width);
|
||||
this.setHeight(height);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObjectNetworkSyncDataType {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
ifx: this.isFlippedX(),
|
||||
ify: this.isFlippedY(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObjectNetworkSyncDataType
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.ifx !== undefined) {
|
||||
this.flipX(networkSyncData.ifx);
|
||||
}
|
||||
if (networkSyncData.ify !== undefined) {
|
||||
this.flipY(networkSyncData.ify);
|
||||
}
|
||||
}
|
||||
|
||||
override extraInitializationFromInitialInstance(
|
||||
initialInstanceData: InstanceData
|
||||
) {
|
||||
@@ -212,7 +278,12 @@ namespace gdjs {
|
||||
// Let behaviors do something before the object is destroyed.
|
||||
super.onDeletedFromScene();
|
||||
// Destroy the children.
|
||||
this._instanceContainer.onDestroyFromScene(this._runtimeScene);
|
||||
this._instanceContainer.onDeletedFromScene(this._runtimeScene);
|
||||
}
|
||||
|
||||
override onDestroyed(): void {
|
||||
this._instanceContainer._destroy();
|
||||
super.onDestroyed();
|
||||
}
|
||||
|
||||
override update(parent: gdjs.RuntimeInstanceContainer): void {
|
||||
@@ -235,6 +306,8 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* This method is called when the preview is being hot-reloaded.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
*/
|
||||
onHotReloading(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
@@ -243,6 +316,8 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* This method is called each tick after events are done.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
* @param parent The instanceContainer owning the object
|
||||
*/
|
||||
doStepPostEvents(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
@@ -250,6 +325,8 @@ namespace gdjs {
|
||||
/**
|
||||
* This method is called when the object is being removed from its parent
|
||||
* container and is about to be destroyed/reused later.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
*/
|
||||
onDestroy(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
|
@@ -8,7 +8,6 @@ namespace gdjs {
|
||||
objectData: ObjectData & CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this.getRenderer().reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender(): gdjs.CustomRuntimeObject2DRenderer {
|
||||
|
@@ -24,7 +24,7 @@ namespace gdjs {
|
||||
*
|
||||
* @see gdjs.CustomRuntimeObject._innerArea
|
||||
**/
|
||||
private _initialInnerArea: {
|
||||
_initialInnerArea: {
|
||||
min: [float, float, float];
|
||||
max: [float, float, float];
|
||||
} | null = null;
|
||||
@@ -47,6 +47,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
addLayer(layerData: LayerData) {
|
||||
if (this._layers.containsKey(layerData.name)) {
|
||||
return;
|
||||
}
|
||||
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
|
||||
this._layers.put(layerData.name, layer);
|
||||
this._orderedLayers.push(layer);
|
||||
@@ -68,9 +71,13 @@ namespace gdjs {
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (this._isLoaded) {
|
||||
this.onDestroyFromScene(this._parent);
|
||||
this.onDeletedFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
this._setOriginalInnerArea(eventsBasedObjectVariantData);
|
||||
|
||||
// Registering objects
|
||||
@@ -83,7 +90,8 @@ namespace gdjs {
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
!eventsBasedObjectVariantData.name
|
||||
(!eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
@@ -178,26 +186,25 @@ namespace gdjs {
|
||||
*
|
||||
* @param instanceContainer The container owning the object.
|
||||
*/
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
onDeletedFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (!this._isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the objects they are being destroyed
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDeletedFromScene();
|
||||
// The object can free all its resource directly...
|
||||
object.onDestroyed();
|
||||
}
|
||||
// ...as its container cache `_instancesRemoved` is also destroy.
|
||||
this._destroy();
|
||||
|
||||
this._isLoaded = false;
|
||||
}
|
||||
|
||||
_destroy() {
|
||||
override _destroy() {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDestroyed();
|
||||
}
|
||||
// It should not be necessary to reset these variables, but this help
|
||||
// ensuring that all memory related to the container is released immediately.
|
||||
super._destroy();
|
||||
@@ -375,11 +382,9 @@ namespace gdjs {
|
||||
): FloatPoint {
|
||||
const position = result || [0, 0];
|
||||
this._customObject.applyObjectTransformation(sceneX, sceneY, position);
|
||||
return this._parent.convertInverseCoords(
|
||||
position[0],
|
||||
position[1],
|
||||
position
|
||||
);
|
||||
return this._parent
|
||||
.getLayer(this._customObject.getLayer())
|
||||
.convertInverseCoords(position[0], position[1], 0, position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -162,5 +162,29 @@ namespace gdjs {
|
||||
this._invalidModel.scene.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the models, resources loaded and destroy 3D models loaders in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
|
||||
if (loadedThreeModel) {
|
||||
loadedThreeModel.scene.clear();
|
||||
this._loadedThreeModels.delete(resourceData);
|
||||
}
|
||||
|
||||
const downloadedArrayBuffer =
|
||||
this._downloadedArrayBuffers.get(resourceData);
|
||||
if (downloadedArrayBuffer) {
|
||||
this._downloadedArrayBuffers.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('ResourceLoader');
|
||||
const debugLogger = new gdjs.Logger('ResourceLoader - debug').enable(false);
|
||||
|
||||
const addSearchParameterToUrl = (
|
||||
url: string,
|
||||
@@ -96,17 +97,15 @@ namespace gdjs {
|
||||
*/
|
||||
private _globalResources: Array<string>;
|
||||
/**
|
||||
* Resources by scene names.
|
||||
* Resources and the loading state of each scene, indexed by scene name.
|
||||
*/
|
||||
private _sceneResources: Map<string, Array<string>>;
|
||||
/**
|
||||
* Keep track of which scene whose resources has already be pre-loaded.
|
||||
*/
|
||||
private _sceneNamesToLoad: Set<string>;
|
||||
/**
|
||||
* Keep track of which scene whose resources has already be loaded.
|
||||
*/
|
||||
private _sceneNamesToMakeReady: Set<string>;
|
||||
private _sceneLoadingStates: Map<
|
||||
string,
|
||||
{
|
||||
resourceNames: Array<string>;
|
||||
status: 'not-loaded' | 'loaded' | 'ready';
|
||||
}
|
||||
> = new Map();
|
||||
/**
|
||||
* A queue of scenes whose resources are still to be pre-loaded.
|
||||
*/
|
||||
@@ -127,11 +126,12 @@ namespace gdjs {
|
||||
private _spineManager: SpineManager | null = null;
|
||||
|
||||
/**
|
||||
* Only used by events.
|
||||
* The name of the scene for which resources are currently being loaded.
|
||||
*/
|
||||
private currentLoadingSceneName: string = '';
|
||||
/**
|
||||
* Only used by events.
|
||||
* The progress, between 0 and 1, of the loading of the resource, for the
|
||||
* scene that is being loaded (see `currentLoadingSceneName`).
|
||||
*/
|
||||
private currentSceneLoadingProgress: float = 0;
|
||||
/**
|
||||
@@ -144,8 +144,8 @@ namespace gdjs {
|
||||
/**
|
||||
* @param runtimeGame The game.
|
||||
* @param resourceDataArray The resources data of the game.
|
||||
* @param globalResources The resources needed for any layer.
|
||||
* @param layoutDataArray The resources used by each layer.
|
||||
* @param globalResources The resources needed for any scene.
|
||||
* @param layoutDataArray The resources used by each scene.
|
||||
*/
|
||||
constructor(
|
||||
runtimeGame: RuntimeGame,
|
||||
@@ -158,9 +158,6 @@ namespace gdjs {
|
||||
this._globalResources = globalResources;
|
||||
|
||||
// These 3 attributes are filled by `setResources`.
|
||||
this._sceneResources = new Map<string, Array<string>>();
|
||||
this._sceneNamesToLoad = new Set<string>();
|
||||
this._sceneNamesToMakeReady = new Set<string>();
|
||||
this.setResources(resourceDataArray, globalResources, layoutDataArray);
|
||||
|
||||
this._imageManager = new gdjs.ImageManager(this);
|
||||
@@ -224,23 +221,31 @@ namespace gdjs {
|
||||
): void {
|
||||
this._globalResources = globalResources;
|
||||
|
||||
this._sceneResources.clear();
|
||||
this._sceneNamesToLoad.clear();
|
||||
this._sceneNamesToMakeReady.clear();
|
||||
this._sceneLoadingStates.clear();
|
||||
|
||||
for (const layoutData of layoutDataArray) {
|
||||
this._sceneResources.set(
|
||||
layoutData.name,
|
||||
layoutData.usedResources.map((resource) => resource.name)
|
||||
);
|
||||
this._sceneNamesToLoad.add(layoutData.name);
|
||||
this._sceneNamesToMakeReady.add(layoutData.name);
|
||||
this._sceneLoadingStates.set(layoutData.name, {
|
||||
resourceNames: layoutData.usedResources.map(
|
||||
(resource) => resource.name
|
||||
),
|
||||
status: 'not-loaded',
|
||||
});
|
||||
}
|
||||
// TODO Clearing the queue doesn't abort the running task, but it should
|
||||
// not matter as resource loading is really fast in preview mode.
|
||||
this._sceneToLoadQueue.length = 0;
|
||||
for (let index = layoutDataArray.length - 1; index >= 0; index--) {
|
||||
const layoutData = layoutDataArray[index];
|
||||
this._sceneToLoadQueue.push(new SceneLoadingTask(layoutData.name));
|
||||
|
||||
const resourcesPreloading = layoutData.resourcesPreloading || 'inherit';
|
||||
const resolvedResourcesPreloading =
|
||||
resourcesPreloading === 'inherit'
|
||||
? this._runtimeGame.getSceneResourcesPreloading()
|
||||
: resourcesPreloading;
|
||||
|
||||
if (resolvedResourcesPreloading === 'at-startup') {
|
||||
this._sceneToLoadQueue.push(new SceneLoadingTask(layoutData.name));
|
||||
}
|
||||
}
|
||||
|
||||
this._resources.clear();
|
||||
@@ -271,8 +276,10 @@ namespace gdjs {
|
||||
onProgress(loadedCount, this._resources.size);
|
||||
}
|
||||
);
|
||||
this._sceneNamesToLoad.clear();
|
||||
this._sceneNamesToMakeReady.clear();
|
||||
|
||||
for (const sceneLoadingState of this._sceneLoadingStates.values()) {
|
||||
sceneLoadingState.status = 'ready';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,17 +289,21 @@ namespace gdjs {
|
||||
firstSceneName: string,
|
||||
onProgress: (count: number, total: number) => void
|
||||
): Promise<void> {
|
||||
const sceneResources = this._sceneResources.get(firstSceneName);
|
||||
if (!sceneResources) {
|
||||
const firstSceneState = this._sceneLoadingStates.get(firstSceneName);
|
||||
if (!firstSceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + firstSceneName + '".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadedCount = 0;
|
||||
const resources = [...this._globalResources, ...sceneResources.values()];
|
||||
const resourceNames = [
|
||||
...this._globalResources,
|
||||
...firstSceneState.resourceNames,
|
||||
];
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
resources,
|
||||
resourceNames,
|
||||
maxForegroundConcurrency,
|
||||
maxAttempt,
|
||||
async (resourceName) => {
|
||||
@@ -304,11 +315,11 @@ namespace gdjs {
|
||||
await this._loadResource(resource);
|
||||
await this._processResource(resource);
|
||||
loadedCount++;
|
||||
onProgress(loadedCount, resources.length);
|
||||
onProgress(loadedCount, resourceNames.length);
|
||||
}
|
||||
);
|
||||
this._setSceneAssetsLoaded(firstSceneName);
|
||||
this._setSceneAssetsReady(firstSceneName);
|
||||
|
||||
firstSceneState.status = 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,17 +329,32 @@ namespace gdjs {
|
||||
* scenes.
|
||||
*/
|
||||
async loadAllSceneInBackground(): Promise<void> {
|
||||
if (this.currentLoadingSceneName) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogger.log('Loading all scene resources, in background.');
|
||||
while (this._sceneToLoadQueue.length > 0) {
|
||||
debugLogger.log(
|
||||
`Still resources of ${this._sceneToLoadQueue.length} scene(s) to load: ${this._sceneToLoadQueue.map((task) => task.sceneName).join(', ')}`
|
||||
);
|
||||
const task = this._sceneToLoadQueue[this._sceneToLoadQueue.length - 1];
|
||||
if (task === undefined) {
|
||||
continue;
|
||||
}
|
||||
this.currentLoadingSceneName = task.sceneName;
|
||||
if (!this.areSceneAssetsLoaded(task.sceneName)) {
|
||||
debugLogger.log(
|
||||
`Loading (but not processing) resources for scene ${task.sceneName}.`
|
||||
);
|
||||
await this._doLoadSceneResources(
|
||||
task.sceneName,
|
||||
async (count, total) => task.onProgress(count, total)
|
||||
);
|
||||
debugLogger.log(
|
||||
`Done loading (but not processing) resources for scene ${task.sceneName}.`
|
||||
);
|
||||
|
||||
// A scene may have been moved last while awaiting resources to be
|
||||
// downloaded (see _prioritizeScene).
|
||||
this._sceneToLoadQueue.splice(
|
||||
@@ -340,6 +366,7 @@ namespace gdjs {
|
||||
this._sceneToLoadQueue.pop();
|
||||
}
|
||||
}
|
||||
debugLogger.log(`Scene resources loading finished.`);
|
||||
this.currentLoadingSceneName = '';
|
||||
}
|
||||
|
||||
@@ -347,16 +374,17 @@ namespace gdjs {
|
||||
sceneName: string,
|
||||
onProgress?: (count: number, total: number) => Promise<void>
|
||||
): Promise<void> {
|
||||
const sceneResources = this._sceneResources.get(sceneName);
|
||||
if (!sceneResources) {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + sceneName + '".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadedCount = 0;
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
[...sceneResources.values()],
|
||||
sceneState.resourceNames,
|
||||
this._isLoadingInForeground
|
||||
? maxForegroundConcurrency
|
||||
: maxBackgroundConcurrency,
|
||||
@@ -369,11 +397,13 @@ namespace gdjs {
|
||||
}
|
||||
await this._loadResource(resource);
|
||||
loadedCount++;
|
||||
this.currentSceneLoadingProgress = loadedCount / this._resources.size;
|
||||
onProgress && (await onProgress(loadedCount, this._resources.size));
|
||||
this.currentSceneLoadingProgress =
|
||||
loadedCount / sceneState.resourceNames.length;
|
||||
onProgress &&
|
||||
(await onProgress(loadedCount, sceneState.resourceNames.length));
|
||||
}
|
||||
);
|
||||
this._setSceneAssetsLoaded(sceneName);
|
||||
sceneState.status = 'loaded';
|
||||
}
|
||||
|
||||
private async _loadResource(resource: ResourceData): Promise<void> {
|
||||
@@ -405,8 +435,8 @@ namespace gdjs {
|
||||
}
|
||||
await this.loadSceneResources(sceneName, onProgress);
|
||||
|
||||
const sceneResources = this._sceneResources.get(sceneName);
|
||||
if (!sceneResources) {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + sceneName + '".'
|
||||
);
|
||||
@@ -414,7 +444,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
let parsedCount = 0;
|
||||
for (const resourceName of sceneResources) {
|
||||
for (const resourceName of sceneState.resourceNames) {
|
||||
const resource = this._resources.get(resourceName);
|
||||
if (!resource) {
|
||||
logger.warn('Unable to find resource "' + resourceName + '".');
|
||||
@@ -422,9 +452,10 @@ namespace gdjs {
|
||||
}
|
||||
await this._processResource(resource);
|
||||
parsedCount++;
|
||||
onProgress && (await onProgress(parsedCount, sceneResources.length));
|
||||
onProgress &&
|
||||
(await onProgress(parsedCount, sceneState.resourceNames.length));
|
||||
}
|
||||
this._setSceneAssetsReady(sceneName);
|
||||
sceneState.status = 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,15 +469,25 @@ namespace gdjs {
|
||||
sceneName: string,
|
||||
onProgress?: (count: number, total: number) => void
|
||||
): Promise<void> {
|
||||
debugLogger.log(
|
||||
`Prioritization of loading of resources for scene ${sceneName} was requested.`
|
||||
);
|
||||
|
||||
this._isLoadingInForeground = true;
|
||||
const task = this._prioritizeScene(sceneName);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!task) {
|
||||
this._isLoadingInForeground = false;
|
||||
debugLogger.log(
|
||||
`Loading of resources for scene ${sceneName} was immediately resolved.`
|
||||
);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
task.registerCallback(() => {
|
||||
debugLogger.log(
|
||||
`Loading of resources for scene ${sceneName} just finished.`
|
||||
);
|
||||
this._isLoadingInForeground = false;
|
||||
resolve();
|
||||
}, onProgress);
|
||||
@@ -463,6 +504,51 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when a scene is unloaded.
|
||||
*/
|
||||
unloadSceneResources({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
}: {
|
||||
unloadedSceneName: string;
|
||||
newSceneName: string | null;
|
||||
}): void {
|
||||
if (!unloadedSceneName) return;
|
||||
debugLogger.log(
|
||||
`Unloading of resources for scene ${unloadedSceneName} was requested.`
|
||||
);
|
||||
|
||||
const sceneUniqueResourcesByKindMap =
|
||||
this._getResourcesByKindOnlyUsedInUnloadedScene({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
});
|
||||
|
||||
for (const [kindResourceManager, resourceManager] of this
|
||||
._resourceManagersMap) {
|
||||
const resources =
|
||||
sceneUniqueResourcesByKindMap.get(kindResourceManager);
|
||||
if (resources) {
|
||||
debugLogger.log(
|
||||
`Unloading of resources of kind ${kindResourceManager} for scene ${unloadedSceneName}: `,
|
||||
resources.map((resource) => resource.name).join(', ')
|
||||
);
|
||||
resourceManager.unloadResourcesList(resources);
|
||||
}
|
||||
}
|
||||
|
||||
debugLogger.log(
|
||||
`Unloading of resources for scene ${unloadedSceneName} finished.`
|
||||
);
|
||||
|
||||
const sceneState = this._sceneLoadingStates.get(unloadedSceneName);
|
||||
if (sceneState) {
|
||||
sceneState.status = 'not-loaded';
|
||||
}
|
||||
// TODO: mark the scene as unloaded so it's not automatically loaded again eagerly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a given scene at the end of the queue.
|
||||
*
|
||||
@@ -470,16 +556,41 @@ namespace gdjs {
|
||||
* this scene will be the next to be loaded.
|
||||
*/
|
||||
private _prioritizeScene(sceneName: string): SceneLoadingTask | null {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return null;
|
||||
if (sceneState.status === 'loaded' || sceneState.status === 'ready') {
|
||||
debugLogger.log(
|
||||
`Scene ${sceneName} is already loaded. Skipping prioritization.`
|
||||
);
|
||||
|
||||
// The scene is already loaded, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The scene is not loaded: either prioritize it or add it to the loading queue.
|
||||
const taskIndex = this._sceneToLoadQueue.findIndex(
|
||||
(task) => task.sceneName === sceneName
|
||||
);
|
||||
if (taskIndex < 0) {
|
||||
// The scene is already loaded.
|
||||
return null;
|
||||
let task: SceneLoadingTask;
|
||||
if (taskIndex !== -1) {
|
||||
// There is already a task for this scene in the queue.
|
||||
// Move it so that it's loaded first.
|
||||
task = this._sceneToLoadQueue[taskIndex];
|
||||
this._sceneToLoadQueue.splice(taskIndex, 1);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
} else {
|
||||
// There is no task for this scene in the queue.
|
||||
// It might be because the scene was unloaded or never loaded.
|
||||
// In this case, we need to add a new task to the queue.
|
||||
task = new SceneLoadingTask(sceneName);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
}
|
||||
const task = this._sceneToLoadQueue[taskIndex];
|
||||
this._sceneToLoadQueue.splice(taskIndex, 1);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
|
||||
// Re-start the loading process in the background. While at the beginning of the game
|
||||
// it's not needed because already launched, a scene might be unloaded. This means
|
||||
// that we then need to relaunch the loading process.
|
||||
this.loadAllSceneInBackground();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -511,7 +622,10 @@ namespace gdjs {
|
||||
* (but maybe not parsed).
|
||||
*/
|
||||
areSceneAssetsLoaded(sceneName: string): boolean {
|
||||
return !this._sceneNamesToLoad.has(sceneName);
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return false;
|
||||
|
||||
return sceneState.status === 'loaded' || sceneState.status === 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,15 +633,10 @@ namespace gdjs {
|
||||
* parsed.
|
||||
*/
|
||||
areSceneAssetsReady(sceneName: string): boolean {
|
||||
return !this._sceneNamesToMakeReady.has(sceneName);
|
||||
}
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return false;
|
||||
|
||||
private _setSceneAssetsLoaded(sceneName: string): void {
|
||||
this._sceneNamesToLoad.delete(sceneName);
|
||||
}
|
||||
|
||||
private _setSceneAssetsReady(sceneName: string): void {
|
||||
this._sceneNamesToMakeReady.delete(sceneName);
|
||||
return sceneState.status === 'ready';
|
||||
}
|
||||
|
||||
getResource(resourceName: string): ResourceData | null {
|
||||
@@ -636,6 +745,70 @@ namespace gdjs {
|
||||
getSpineAtlasManager(): gdjs.SpineAtlasManager | null {
|
||||
return this._spineAtlasManager;
|
||||
}
|
||||
|
||||
injectMockResourceManagerForTesting(
|
||||
resourceKind: ResourceKind,
|
||||
resourceManager: ResourceManager
|
||||
) {
|
||||
this._resourceManagersMap.set(resourceKind, resourceManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of resources that are only used in the scene that is being unloaded,
|
||||
* and that are not used in any other loaded scene (or the scene that is coming next).
|
||||
*/
|
||||
private _getResourcesByKindOnlyUsedInUnloadedScene({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
}: {
|
||||
unloadedSceneName: string;
|
||||
newSceneName: string | null;
|
||||
}): Map<ResourceKind, ResourceData[]> {
|
||||
const unloadedSceneState =
|
||||
this._sceneLoadingStates.get(unloadedSceneName);
|
||||
if (!unloadedSceneState) {
|
||||
return new Map<ResourceKind, ResourceData[]>();
|
||||
}
|
||||
|
||||
// Construct the set of all resources to unload. These are the resources
|
||||
// used in the scene that is being unloaded minus all the resources used
|
||||
// by the other scenes that are loaded (and the possible scene that is coming next).
|
||||
const resourceNamesToUnload = new Set<string>(
|
||||
unloadedSceneState.resourceNames
|
||||
);
|
||||
for (const [
|
||||
sceneName,
|
||||
sceneState,
|
||||
] of this._sceneLoadingStates.entries()) {
|
||||
if (sceneName === unloadedSceneName) continue;
|
||||
|
||||
if (
|
||||
sceneName === newSceneName ||
|
||||
sceneState.status === 'loaded' ||
|
||||
sceneState.status === 'ready'
|
||||
) {
|
||||
sceneState.resourceNames.forEach((resourceName) => {
|
||||
resourceNamesToUnload.delete(resourceName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result = new Map<ResourceKind, ResourceData[]>();
|
||||
resourceNamesToUnload.forEach((resourceName) => {
|
||||
const resourceData = this._resources.get(resourceName);
|
||||
if (!resourceData) return;
|
||||
|
||||
const kind = resourceData.kind;
|
||||
const resources = result.get(kind);
|
||||
if (resources) {
|
||||
resources.push(resourceData);
|
||||
} else {
|
||||
result.set(kind, [resourceData]);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
type PromiseError<T> = { item: T; error: Error };
|
||||
|
@@ -35,5 +35,15 @@ namespace gdjs {
|
||||
* Using the manager after calling this method is undefined behavior.
|
||||
*/
|
||||
dispose(): void;
|
||||
|
||||
/**
|
||||
* Should clear all specified resources data and anything stored by this manager
|
||||
* for these resources.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources that need to be clear
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void;
|
||||
}
|
||||
}
|
||||
|
@@ -811,6 +811,8 @@ namespace gdjs {
|
||||
this._objectsCtor = new Hashtable();
|
||||
this._allInstancesList = [];
|
||||
this._instancesRemoved = [];
|
||||
this._layersCameraCoordinates = {};
|
||||
this._initialBehaviorSharedData = new Hashtable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -116,6 +116,9 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getRuntimeLayer(): gdjs.RuntimeLayer {
|
||||
return this;
|
||||
}
|
||||
getRenderer(): gdjs.LayerRenderer {
|
||||
return this._renderer;
|
||||
}
|
||||
|
@@ -222,7 +222,9 @@ namespace gdjs {
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message,
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -472,13 +474,24 @@ namespace gdjs {
|
||||
newExternalLayoutData.associatedLayout
|
||||
);
|
||||
|
||||
const oldObjectDataList =
|
||||
HotReloader.resolveCustomObjectConfigurations(
|
||||
oldProjectData,
|
||||
oldLayoutData ? oldLayoutData.objects : []
|
||||
);
|
||||
const newObjectDataList =
|
||||
HotReloader.resolveCustomObjectConfigurations(
|
||||
newProjectData,
|
||||
newLayoutData ? newLayoutData.objects : []
|
||||
);
|
||||
|
||||
sceneStack._stack.forEach((runtimeScene) => {
|
||||
this._hotReloadRuntimeSceneInstances(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
oldLayoutData ? oldLayoutData.objects : [],
|
||||
newLayoutData ? newLayoutData.objects : [],
|
||||
oldObjectDataList,
|
||||
newObjectDataList,
|
||||
oldExternalLayoutData.instances,
|
||||
newExternalLayoutData.instances,
|
||||
runtimeScene
|
||||
@@ -725,7 +738,6 @@ namespace gdjs {
|
||||
// scene (see `_hotReloadRuntimeInstanceContainer` call from
|
||||
// `_hotReloadRuntimeSceneInstances`).
|
||||
objects: mergedChildObjectDataList,
|
||||
childrenContent: mergedChildObjectDataList,
|
||||
};
|
||||
return mergedObjectConfiguration;
|
||||
});
|
||||
|
@@ -380,11 +380,8 @@ namespace gdjs {
|
||||
.isMouseInsideCanvas();
|
||||
};
|
||||
|
||||
const _cursorIsOnObject = function (
|
||||
obj: gdjs.RuntimeObject,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
return obj.cursorOnObject(instanceContainer);
|
||||
const _cursorIsOnObject = function (obj: gdjs.RuntimeObject) {
|
||||
return obj.cursorOnObject();
|
||||
};
|
||||
|
||||
export const cursorOnObject = function (
|
||||
@@ -397,7 +394,7 @@ namespace gdjs {
|
||||
_cursorIsOnObject,
|
||||
objectsLists,
|
||||
inverted,
|
||||
instanceContainer
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -362,8 +362,7 @@ namespace gdjs {
|
||||
if (objectsLists.items.hasOwnProperty(name)) {
|
||||
const allObjects = objectsContext.getObjects(name);
|
||||
const objectsList = objectsLists.items[name];
|
||||
objectsList.length = 0;
|
||||
objectsList.push.apply(objectsList, allObjects);
|
||||
gdjs.copyArray(allObjects, objectsList);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@@ -205,6 +205,28 @@ namespace gdjs {
|
||||
this._loadedFontFamily.clear();
|
||||
this._loadedFontFamilySet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the caches of loaded font families.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const resource = this._loadedFontFamily.get(resourceData);
|
||||
if (resource) {
|
||||
this._loadedFontFamily.delete(resourceData);
|
||||
}
|
||||
|
||||
const fontName = this._getFontFamilyFromFilename(resourceData);
|
||||
if (fontName) {
|
||||
this._loadedFontFamilySet.delete(fontName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Register the class to let the engine use it.
|
||||
|
@@ -397,60 +397,68 @@ namespace gdjs {
|
||||
document.addEventListener(
|
||||
'pause',
|
||||
function () {
|
||||
const soundList = that._freeSounds.concat(that._freeMusics);
|
||||
for (let key in that._sounds) {
|
||||
if (that._sounds.hasOwnProperty(key)) {
|
||||
soundList.push(that._sounds[key]);
|
||||
}
|
||||
}
|
||||
for (let key in that._musics) {
|
||||
if (that._musics.hasOwnProperty(key)) {
|
||||
soundList.push(that._musics[key]);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < soundList.length; i++) {
|
||||
const sound = soundList[i];
|
||||
if (!sound.paused() && !sound.stopped()) {
|
||||
sound.pause();
|
||||
that._pausedSounds.push(sound);
|
||||
}
|
||||
}
|
||||
that._paused = true;
|
||||
that.pauseAllActiveSounds();
|
||||
},
|
||||
false
|
||||
);
|
||||
document.addEventListener(
|
||||
'resume',
|
||||
function () {
|
||||
try {
|
||||
for (let i = 0; i < that._pausedSounds.length; i++) {
|
||||
const sound = that._pausedSounds[i];
|
||||
if (!sound.stopped()) {
|
||||
sound.play();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message &&
|
||||
typeof error.message === 'string' &&
|
||||
error.message.startsWith('Maximum call stack size exceeded')
|
||||
) {
|
||||
console.warn(
|
||||
'An error occurred when resuming paused sounds while the game was in background:',
|
||||
error
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
that._pausedSounds.length = 0;
|
||||
that._paused = false;
|
||||
that.resumeAllActiveSounds();
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pauseAllActiveSounds(): void {
|
||||
const soundList = this._freeSounds.concat(this._freeMusics);
|
||||
for (let key in this._sounds) {
|
||||
if (this._sounds.hasOwnProperty(key)) {
|
||||
soundList.push(this._sounds[key]);
|
||||
}
|
||||
}
|
||||
for (let key in this._musics) {
|
||||
if (this._musics.hasOwnProperty(key)) {
|
||||
soundList.push(this._musics[key]);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < soundList.length; i++) {
|
||||
const sound = soundList[i];
|
||||
if (!sound.paused() && !sound.stopped()) {
|
||||
sound.pause();
|
||||
this._pausedSounds.push(sound);
|
||||
}
|
||||
}
|
||||
this._paused = true;
|
||||
}
|
||||
|
||||
resumeAllActiveSounds(): void {
|
||||
try {
|
||||
for (let i = 0; i < this._pausedSounds.length; i++) {
|
||||
const sound = this._pausedSounds[i];
|
||||
if (!sound.stopped()) {
|
||||
sound.play();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message &&
|
||||
typeof error.message === 'string' &&
|
||||
error.message.startsWith('Maximum call stack size exceeded')
|
||||
) {
|
||||
console.warn(
|
||||
'An error occurred when resuming paused sounds while the game was in background:',
|
||||
error
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this._pausedSounds.length = 0;
|
||||
this._paused = false;
|
||||
}
|
||||
|
||||
getResourceKinds(): ResourceKind[] {
|
||||
return resourceKinds;
|
||||
}
|
||||
@@ -931,6 +939,28 @@ namespace gdjs {
|
||||
dispose(): void {
|
||||
this.unloadAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this unloads all audio from the specified resources from memory.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const musicRes = this._loadedMusics.get(resourceData);
|
||||
if (musicRes) {
|
||||
this.unloadAudio(resourceData.name, true);
|
||||
}
|
||||
|
||||
const soundRes = this._loadedSounds.get(resourceData);
|
||||
if (soundRes) {
|
||||
this.unloadAudio(resourceData.name, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the class to let the engine use it.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user