Add 3D physics and 3D character behaviors (#7149)

* The 3D physics engine is powered by [Jolt Physics](https://github.com/jrouwe/JoltPhysics) a modern, performant, battle-tested, fully featured 3D physics engine used in AAA games, like Horizon: Forbidden West. The new 3D physics engine is perfect for making FPS, TPS, 3D platformer and in the future 3D racing games or any 3D game.
* Similar to the 2D physics engine, you can add the 3D Physics behavior to your object. For example, add the behavior to platforms with the type set to "Static".
* You can choose the collider shape: box, sphere, cylinder or capsule and modify physics properties of the objects having this behavior - much like the 2D physics engine.
* For characters, a dedicated "3D Physics Character" behavior is available. Pair it with one or more additional behaviors to get controls working out of the box: "3D Shooter keyboard mapper", "3D Platformer keyboard mapper", etc...
* Take a look at the new examples to give it a try! New examples and behaviors will be progressively added.
This commit is contained in:
D8H
2024-12-17 18:00:48 +01:00
committed by GitHub
parent 44daf709e4
commit e2281dfd82
19 changed files with 13673 additions and 32 deletions

1
.gitattributes vendored
View File

@@ -3,6 +3,7 @@ Extensions/ParticleSystem/SPARK/* linguist-vendored
Extensions/PhysicsBehavior/Box2D/* linguist-vendored
Extensions/PhysicsBehavior/box2djs/* linguist-vendored
Extensions/Physics2Behavior/box2d.js linguist-vendored
Extensions/Physics3DBehavior/*.wasm-compat.js linguist-vendored
Extensions/BBText/pixi-multistyle-text/* linguist-vendored
Extensions/P2P/A_peer.js linguist-vendored
Extensions/Multiplayer/peer.js linguist-vendored

View File

@@ -695,7 +695,7 @@ module.exports = {
.addCondition(
'IsDynamic',
_('Is dynamic'),
_('Test if an object is dynamic.'),
_('Check if an object is dynamic.'),
_('_PARAM0_ is dynamic'),
_('Dynamics'),
'res/physics32.png',
@@ -727,7 +727,7 @@ module.exports = {
.addCondition(
'IsStatic',
_('Is static'),
_('Test if an object is static.'),
_('Check if an object is static.'),
_('_PARAM0_ is static'),
_('Dynamics'),
'res/physics32.png',
@@ -759,7 +759,7 @@ module.exports = {
.addCondition(
'IsKinematic',
_('Is kinematic'),
_('Test if an object is kinematic.'),
_('Check if an object is kinematic.'),
_('_PARAM0_ is kinematic'),
_('Dynamics'),
'res/physics32.png',
@@ -790,9 +790,9 @@ module.exports = {
aut
.addCondition(
'IsBullet',
_('Is treat as bullet'),
_('Test if an object is being treat as a bullet.'),
_('_PARAM0_ is bullet'),
_('Is treated as a bullet'),
_('Check if the object is being treated as a bullet.'),
_('_PARAM0_ is treated as a bullet'),
_('Dynamics'),
'res/physics32.png',
'res/physics32.png'
@@ -825,7 +825,7 @@ module.exports = {
.addCondition(
'HasFixedRotation',
_('Has fixed rotation'),
_('Test if an object has fixed rotation.'),
_('Check if an object has fixed rotation.'),
_('_PARAM0_ has fixed rotation'),
_('Dynamics'),
'res/physics32.png',
@@ -859,7 +859,7 @@ module.exports = {
.addCondition(
'IsSleepingAllowed',
_('Is sleeping allowed'),
_('Test if an object can sleep.'),
_('Check if an object can sleep.'),
_('_PARAM0_ can sleep'),
_('Dynamics'),
'res/physics32.png',
@@ -898,7 +898,7 @@ module.exports = {
.addCondition(
'IsSleeping',
_('Is sleeping'),
_('Test if an object is sleeping.'),
_('Check if an object is sleeping.'),
_('_PARAM0_ is sleeping'),
_('Dynamics'),
'res/physics32.png',
@@ -1267,7 +1267,7 @@ module.exports = {
.addCondition(
'LayerEnabled',
_('Layer enabled'),
_('Test if an object has a specific layer enabled.'),
_('Check if an object has a specific layer enabled.'),
_('_PARAM0_ has layer _PARAM2_ enabled'),
_('Filtering'),
'res/physics32.png',
@@ -1303,7 +1303,7 @@ module.exports = {
.addCondition(
'MaskEnabled',
_('Mask enabled'),
_('Test if an object has a specific mask enabled.'),
_('Check if an object has a specific mask enabled.'),
_('_PARAM0_ has mask _PARAM2_ enabled'),
_('Filtering'),
'res/physics32.png',
@@ -1897,7 +1897,7 @@ module.exports = {
.addCondition(
'JointFirstObject',
_('Joint first object'),
_('Test if an object is the first object on a joint.'),
_('Check if an object is the first object on a joint.'),
_('_PARAM0_ is the first object for joint _PARAM2_'),
_('Joints'),
'res/physics32.png',
@@ -1913,7 +1913,7 @@ module.exports = {
.addCondition(
'JointSecondObject',
_('Joint second object'),
_('Test if an object is the second object on a joint.'),
_('Check if an object is the second object on a joint.'),
_('_PARAM0_ is the second object for joint _PARAM2_'),
_('Joints'),
'res/physics32.png',
@@ -2382,7 +2382,7 @@ module.exports = {
.addCondition(
'RevoluteJointLimitsEnabled',
_('Revolute joint limits enabled'),
_('Test if a revolute joint limits are enabled.'),
_('Check if a revolute joint limits are enabled.'),
_('Limits for revolute joint _PARAM2_ are enabled'),
_('Joints/Revolute'),
'JsPlatform/Extensions/revolute_joint24.png',
@@ -2461,7 +2461,7 @@ module.exports = {
.addCondition(
'RevoluteJointMotorEnabled',
_('Revolute joint motor enabled'),
_('Test if a revolute joint motor is enabled.'),
_('Check if a revolute joint motor is enabled.'),
_('Motor of revolute joint _PARAM2_ is enabled'),
_('Joints/Revolute'),
'JsPlatform/Extensions/revolute_joint24.png',
@@ -2700,7 +2700,7 @@ module.exports = {
.addCondition(
'PrismaticJointLimitsEnabled',
_('Prismatic joint limits enabled'),
_('Test if a prismatic joint limits are enabled.'),
_('Check if a prismatic joint limits are enabled.'),
_('Limits for prismatic joint _PARAM2_ are enabled'),
_('Joints/Prismatic'),
'JsPlatform/Extensions/prismatic_joint24.png',
@@ -2779,7 +2779,7 @@ module.exports = {
.addCondition(
'PrismaticJointMotorEnabled',
_('Prismatic joint motor enabled'),
_('Test if a prismatic joint motor is enabled.'),
_('Check if a prismatic joint motor is enabled.'),
_('Motor for prismatic joint _PARAM2_ is enabled'),
_('Joints/Prismatic'),
'JsPlatform/Extensions/prismatic_joint24.png',
@@ -3459,7 +3459,7 @@ module.exports = {
.addCondition(
'WheelJointMotorEnabled',
_('Wheel joint motor enabled'),
_('Test if a wheel joint motor is enabled.'),
_('Check if a wheel joint motor is enabled.'),
_('Motor for wheel joint _PARAM2_ is enabled'),
_('Joints/Wheel'),
'JsPlatform/Extensions/wheel_joint24.png',
@@ -4203,7 +4203,7 @@ module.exports = {
.getCodeExtraInformation()
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.setFunctionName('gdjs.physics2.objectsCollide');
.setFunctionName('gdjs.physics2.areObjectsColliding');
extension
.addCondition(

View File

@@ -960,10 +960,10 @@ namespace gdjs {
updateObjectFromBody() {
// Copy transform from body to the GD object.
// It's possible the behavior was either deactivated or the object deleted
// just before this doStepPreEvents (for example, another behavior deleted
// the object during its own doStepPreEvents). If the body is null, we just
// don't do anything (but still run the physics simulation - this is independent).
// The body is null when the behavior was either deactivated or the object deleted.
// It would be useless to try to recreate it as updateBodyFromObject already does it.
// If the body is null, we just don't do anything
// (but still run the physics simulation - this is independent).
if (this._body !== null) {
this.owner.setX(
this._body.GetPosition().get_x() * this._sharedData.worldScale -

View File

@@ -1,6 +1,6 @@
namespace gdjs {
export namespace physics2 {
export const objectsCollide = function (
export const areObjectsColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
namespace gdjs {
export namespace physics3d {
export const areObjectsColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.areObjectsColliding,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStartedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.hasCollisionStartedBetween,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStoppedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.hasCollisionStoppedBetween,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
type BehaviorNamePair = { character: string; physics: string };
const isOnPlatformAdapter = (
characterObject: gdjs.RuntimeObject,
physicsObject: gdjs.RuntimeObject,
behaviorNamePair: BehaviorNamePair
): boolean => {
const characterBehavior = characterObject.getBehavior(
behaviorNamePair.character
) as gdjs.PhysicsCharacter3DRuntimeBehavior;
const physicsBehavior = physicsObject.getBehavior(
behaviorNamePair.physics
) as gdjs.Physics3DRuntimeBehavior;
if (!characterBehavior || !physicsBehavior) {
return false;
}
return characterBehavior.isOnFloorObject(physicsBehavior);
};
const behaviorNamePair: BehaviorNamePair = { character: '', physics: '' };
export const isOnPlatform = (
characterObjectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
characterBehaviorName: string,
physicsObjectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
physicsBehaviorName: string,
inverted: boolean
) => {
behaviorNamePair.character = characterBehaviorName;
behaviorNamePair.physics = physicsBehaviorName;
return gdjs.evtTools.object.twoListsTest(
isOnPlatformAdapter,
characterObjectsLists,
physicsObjectsLists,
inverted,
behaviorNamePair
);
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
## Physics 3D Behaviors for GDevelop
This is the 3D physics engine for GDevelop, based on [Jolt Physics](https://github.com/jrouwe/JoltPhysics.js/) (WebAssembly, version 0.30.0).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1 @@
describe('Physics3DRuntimeBehavior', () => {});

View File

@@ -231,9 +231,13 @@ namespace gdjs {
...super.getNetworkSyncData(),
props: {
cs: this._currentSpeed,
// TODO Try to remove these 3 fields from the synch
// They are reset every frame and are not part of the state.
rdx: this._requestedDeltaX,
rdy: this._requestedDeltaY,
ldy: this._lastDeltaY,
cfs: this._currentFallSpeed,
cj: this._canJump,
ldl: this._lastDirectionIsLeft,

View File

@@ -30,17 +30,17 @@ GDevelop is a **full-featured, no-code, open-source** game development software.
GDevelop is composed of an **editor**, a **game engine**, an **ecosystem** of extensions as well as **online services** and commercial support.
| Directory | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Core` | Core classes, describing the structure of a game and tools to implement the IDE and work with GDevelop games. |
| `GDJS` | The game engine, written in TypeScript, using PixiJS and Three.js (WebGL), powering all GDevelop games. |
| `GDevelop.js` | Bindings of `Core`, `GDJS` and `Extensions` to JavaScript (with WebAssembly), used by the IDE. |
| `newIDE` | The game editor, written in JavaScript with React, Electron, PixiJS and Three.js. |
| `Extensions` | Built-in extensions for the game engine, providing objects, behaviors, events and new features. All the [community extensions are on this repository](https://github.com/GDevelopApp/GDevelop-extensions). |
| Directory | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Core` | Core classes, describing the structure of a game and tools to implement the IDE and work with GDevelop games. |
| `GDJS` | The game engine, written in TypeScript, using PixiJS and Three.js for 2D and 3D rendering (WebGL), powering all GDevelop games. |
| `GDevelop.js` | Bindings of `Core`, `GDJS` and `Extensions` to JavaScript (with WebAssembly), used by the IDE. |
| `newIDE` | The game editor, written in JavaScript with React, Electron, PixiJS and Three.js.js. |
| `Extensions` | Built-in extensions for the game engine, providing objects, behaviors and new features. For example, this includes the physics engines running in WebAssembly (Box2D or Jolt Physics for 3D). All the [community extensions are on this repository](https://github.com/GDevelopApp/GDevelop-extensions). |
To learn more about GDevelop Architecture, read the [architecture overview here](Core/GDevelop-Architecture-Overview.md).
Pre-generated documentation of the Core library, C++ and TypeScript game engines is [available here](https://docs.gdevelop.io).
Pre-generated documentation of the game engine is [available here](https://docs.gdevelop.io).
Status of the tests and builds: [![macOS and Linux build status](https://circleci.com/gh/4ian/GDevelop.svg?style=shield)](https://app.circleci.com/pipelines/github/4ian/GDevelop) [![Fast tests status](https://gdevelop.semaphoreci.com/badges/GDevelop/branches/master.svg?style=shields)](https://gdevelop.semaphoreci.com/projects/GDevelop) [![Windows Build status](https://ci.appveyor.com/api/projects/status/84uhtdox47xp422x/branch/master?svg=true)](https://ci.appveyor.com/project/4ian/gdevelop/branch/master) [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14" height="14" version="1.1" viewBox="0 0 14 14" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m7 0-6 3.5v7l6 3.5 6-3.5v-7l-6-3.5z" fill="#2e388a"/><g transform="translate(-.0405 .026)" fill="none" stroke="#53a6da" stroke-width=".7"><path d="m8.294 9.112c-1.291 0.7556-2.601 1.165-3.664 1.229-1.085 0.0656-1.803-0.2277-2.095-0.7272-0.2924-0.4996-0.1966-1.269 0.3921-2.183 0.5768-0.8956 1.575-1.837 2.866-2.593 1.291-0.7556 2.601-1.165 3.664-1.229 1.085-0.06552 1.803 0.2277 2.095 0.7272s0.1964 1.269-0.3862 2.174-1.581 1.846-2.872 2.602z"/><path d="m5.787 9.112c1.291 0.7556 2.601 1.165 3.664 1.229 1.085 0.0656 1.803-0.2277 2.095-0.7272 0.2924-0.4996 0.1966-1.269-0.3921-2.183-0.5768-0.8956-1.575-1.837-2.866-2.593-1.291-0.7556-2.601-1.165-3.664-1.229-1.085-0.06552-1.803 0.2277-2.095 0.7272s-0.1964 1.269 0.3862 2.174 1.581 1.846 2.872 2.602z"/><path d="m7.038 1.75c-0.5788 0-1.194 0.4711-1.686 1.441-0.4817 0.9502-0.7898 2.287-0.7898 3.783s0.3081 2.833 0.7898 3.783c0.4916 0.9697 1.107 1.441 1.686 1.441s1.194-0.4712 1.686-1.441c0.4817-0.9502 0.7898-2.287 0.7898-3.783s-0.3081-2.833-0.7898-3.783c-0.4916-0.9698-1.107-1.441-1.686-1.441z"/></g><path d="m7 8c0.5524 0 1-0.4478 1-1 0-0.5524-0.4478-1-1-1s-1 0.4478-1 1c0 0.5524 0.4478 1 1 1z" fill="#fff" stroke-width=".6439"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14" height="14" version="1.1" viewBox="0 0 14 14" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m7 0-6 3.5v7l6 3.5 6-3.5v-7l-6-3.5z" fill="#2e388a"/><g fill="#27aae1" stroke-width=".7"><path d="m9.1 3.801v1.398h-0.6992-2.801-2.1v1.4 1.4h1.4v-1.4h0.6992v2.801h-2.1v1.4h2.1 1.4v-1.4h1.4 0.6992v1.4h1.4v-1.4-1.4h-2.1v-1.4h2.1v-1.4-1.398h-1.4z"/><path d="m7 5.2c0.7732 0 1.4-0.6268 1.4-1.4s-0.6268-1.4-1.4-1.4-1.4 0.6268-1.4 1.4 0.6268 1.4 1.4 1.4z"/></g></svg>

After

Width:  |  Height:  |  Size: 528 B

View File

@@ -1,6 +1,7 @@
// @flow
import BehaviorPropertiesEditor from './Editors/BehaviorPropertiesEditor';
import Physics2Editor from './Editors/Physics2Editor';
import Physics3DEditor from './Editors/Physics3DEditor';
/**
* A service returning editor components for each behavior type.
@@ -16,6 +17,9 @@ const BehaviorsEditorService = {
'Physics2::Physics2Behavior': {
component: Physics2Editor,
},
'Physics3D::Physics3DBehavior': {
component: Physics3DEditor,
},
},
};

View File

@@ -0,0 +1,402 @@
// @flow
import { t } from '@lingui/macro';
import { Trans } from '@lingui/macro';
import * as React from 'react';
import { Line, Column, Spacer } from '../../../UI/Grid';
import Checkbox from '../../../UI/Checkbox';
import SelectField from '../../../UI/SelectField';
import SelectOption from '../../../UI/SelectOption';
import SemiControlledTextField from '../../../UI/SemiControlledTextField';
import { getMeasurementUnitShortLabel } from '../../../PropertiesEditor/PropertiesMapToSchema';
import MeasurementUnitDocumentation from '../../../PropertiesEditor/MeasurementUnitDocumentation';
import { type BehaviorEditorProps } from '../BehaviorEditorProps.flow';
import Text from '../../../UI/Text';
import DismissableAlertMessage from '../../../UI/DismissableAlertMessage';
import { ResponsiveLineStackLayout } from '../../../UI/Layout';
import useForceUpdate from '../../../Utils/UseForceUpdate';
import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
import InputAdornment from '@material-ui/core/InputAdornment';
import Tooltip from '@material-ui/core/Tooltip';
type Props = BehaviorEditorProps;
const NumericProperty = (props: {|
id?: string,
properties: gdMapStringPropertyDescriptor,
propertyName: string,
step: number,
onUpdate: (newValue: string) => void,
|}) => {
const { properties, propertyName, step, onUpdate, id } = props;
const property = properties.get(propertyName);
return (
<SemiControlledTextField
id={id}
fullWidth
value={property.getValue()}
key={propertyName}
floatingLabelText={property.getLabel()}
step={step}
onChange={onUpdate}
type="number"
endAdornment={<UnitAdornment property={property} />}
/>
);
};
const UnitAdornment = (props: {| property: gdPropertyDescriptor |}) => {
const { property } = props;
const measurementUnit = property.getMeasurementUnit();
return (
<Tooltip
title={
<MeasurementUnitDocumentation
label={measurementUnit.getLabel()}
description={measurementUnit.getDescription()}
elementsWithWords={measurementUnit.getElementsWithWords()}
/>
}
>
<InputAdornment position="end">
{getMeasurementUnitShortLabel(measurementUnit)}
</InputAdornment>
</Tooltip>
);
};
const BitGroupEditor = (props: {|
bits: Array<boolean>,
onChange: (index: number, value: boolean) => void,
firstIndex: number,
disabled: boolean,
|}) => {
return (
<div style={{ overflowX: 'auto', flex: 1 }}>
<ButtonGroup disableElevation fullWidth disabled={props.disabled}>
{props.bits.map((bit, index) => (
<Button
key={props.firstIndex + index}
variant={bit ? 'contained' : 'outlined'}
color={bit ? 'primary' : 'default'}
onClick={() => props.onChange(props.firstIndex + index, !bit)}
>
{props.firstIndex + index + 1}
</Button>
))}
</ButtonGroup>
</div>
);
};
const isBitEnabled = (bitsValue: number, pos: number) => {
return !!(bitsValue & (1 << pos));
};
const enableBit = (bitsValue: number, pos: number, enable: boolean) => {
if (enable) bitsValue |= 1 << pos;
else bitsValue &= ~(1 << pos);
return bitsValue;
};
const Physics3DEditor = (props: Props) => {
const { behavior, onBehaviorUpdated } = props;
const forceUpdate = useForceUpdate();
const updateBehaviorProperty = React.useCallback(
(property, value) => {
behavior.updateProperty(property, value);
forceUpdate();
onBehaviorUpdated();
},
[behavior, forceUpdate, onBehaviorUpdated]
);
const properties = behavior.getProperties();
const staticBits = Array(4).fill(null);
const dynamicBits = Array(4).fill(null);
const shape = properties.get('shape').getValue();
const layersValues = parseInt(properties.get('layers').getValue(), 10);
const masksValues = parseInt(properties.get('masks').getValue(), 10);
const isStatic = properties.get('bodyType').getValue() === 'Static';
return (
<Column
expand
// Avoid overflow on small screens
noOverflowParent
>
<Line>
<SelectField
id="physics3d-parameter-body-type"
key={'bodyType'}
fullWidth
floatingLabelText={properties.get('bodyType').getLabel()}
value={properties.get('bodyType').getValue()}
onChange={(e, i, newValue: string) =>
updateBehaviorProperty('bodyType', newValue)
}
>
{[
<SelectOption
key={'dynamic'}
value={'Dynamic'}
label={t`Dynamic`}
/>,
<SelectOption key={'static'} value={'Static'} label={t`Static`} />,
<SelectOption
key={'kinematic'}
value={'Kinematic'}
label={t`Kinematic`}
/>,
]}
</SelectField>
</Line>
<ResponsiveLineStackLayout>
<Checkbox
label={properties.get('bullet').getLabel()}
checked={properties.get('bullet').getValue() === 'true'}
onCheck={(e, checked) =>
updateBehaviorProperty('bullet', checked ? '1' : '0')
}
/>
<Checkbox
label={properties.get('fixedRotation').getLabel()}
checked={properties.get('fixedRotation').getValue() === 'true'}
onCheck={(e, checked) =>
updateBehaviorProperty('fixedRotation', checked ? '1' : '0')
}
/>
</ResponsiveLineStackLayout>
<Line>
<DismissableAlertMessage
identifier="physics2-shape-collisions"
kind="info"
>
<Trans>
The shape used in the Physics behavior is independent from the
collision mask of the object. Be sure to use the "Collision"
condition provided by the Physics behavior in the events. The usual
"Collision" condition won't take into account the shape that you've
set up here.
</Trans>
</DismissableAlertMessage>
</Line>
<ResponsiveLineStackLayout>
<SelectField
id="physics3d-parameter-shape"
fullWidth
floatingLabelText={properties.get('shape').getLabel()}
value={properties.get('shape').getValue()}
onChange={(e, i, newValue: string) =>
updateBehaviorProperty('shape', newValue)
}
>
<SelectOption key={'sphere'} value={'Sphere'} label={t`Sphere`} />
<SelectOption key={'box'} value={'Box'} label={t`Box`} />
<SelectOption key={'capsule'} value={'Capsule'} label={t`Capsule`} />
<SelectOption
key={'cylinder'}
value={'Cylinder'}
label={t`Cylinder`}
/>
</SelectField>
<SelectField
id="physics3d-parameter-shape-orientation"
fullWidth
floatingLabelText={properties.get('shapeOrientation').getLabel()}
value={properties.get('shapeOrientation').getValue()}
onChange={(e, i, newValue: string) =>
updateBehaviorProperty('shapeOrientation', newValue)
}
disabled={
properties.get('shape').getValue() === 'Sphere' ||
properties.get('shape').getValue() === 'Box'
}
>
<SelectOption key={'shape-orientation-z'} value={'Z'} label={t`Z`} />
<SelectOption key={'shape-orientation-y'} value={'Y'} label={t`Y`} />
<SelectOption key={'shape-orientation-x'} value={'X'} label={t`X`} />
</SelectField>
</ResponsiveLineStackLayout>
<ResponsiveLineStackLayout>
<SemiControlledTextField
fullWidth
value={properties.get('shapeDimensionA').getValue()}
key={'shapeDimensionA'}
floatingLabelText={shape === 'Box' ? 'Width' : 'Radius'}
min={0}
onChange={newValue =>
updateBehaviorProperty('shapeDimensionA', newValue)
}
type="number"
endAdornment={
<UnitAdornment property={properties.get('shapeDimensionA')} />
}
/>
{shape !== 'Sphere' && (
<SemiControlledTextField
fullWidth
value={properties.get('shapeDimensionB').getValue()}
key={'shapeDimensionB'}
floatingLabelText={shape === 'Box' ? 'Width' : 'Depth'}
min={0}
onChange={newValue =>
updateBehaviorProperty('shapeDimensionB', newValue)
}
type="number"
endAdornment={
<UnitAdornment property={properties.get('shapeDimensionB')} />
}
/>
)}
{shape === 'Box' && (
<SemiControlledTextField
fullWidth
value={properties.get('shapeDimensionC').getValue()}
key={'shapeDimensionC'}
floatingLabelText={'Depth'}
min={0}
onChange={newValue =>
updateBehaviorProperty('shapeDimensionC', newValue)
}
type="number"
endAdornment={
<UnitAdornment property={properties.get('shapeDimensionC')} />
}
/>
)}
</ResponsiveLineStackLayout>
<ResponsiveLineStackLayout>
<NumericProperty
id="physics3d-parameter-density"
properties={properties}
propertyName={'density'}
step={0.1}
onUpdate={newValue =>
updateBehaviorProperty(
'density',
parseFloat(newValue) > 0 ? newValue : '0'
)
}
/>
<NumericProperty
properties={properties}
propertyName={'gravityScale'}
step={0.1}
onUpdate={newValue =>
updateBehaviorProperty('gravityScale', newValue)
}
/>
</ResponsiveLineStackLayout>
<ResponsiveLineStackLayout>
<NumericProperty
properties={properties}
propertyName={'friction'}
step={0.1}
onUpdate={newValue =>
updateBehaviorProperty(
'friction',
parseFloat(newValue) > 0 ? newValue : '0'
)
}
/>
<NumericProperty
properties={properties}
propertyName={'restitution'}
step={0.1}
onUpdate={newValue =>
updateBehaviorProperty(
'restitution',
parseFloat(newValue) > 0 ? newValue : '0'
)
}
/>
</ResponsiveLineStackLayout>
<ResponsiveLineStackLayout>
<NumericProperty
properties={properties}
propertyName={'linearDamping'}
step={0.05}
onUpdate={newValue =>
updateBehaviorProperty('linearDamping', newValue)
}
/>
<NumericProperty
id="physics3d-parameter-angular-damping"
properties={properties}
propertyName={'angularDamping'}
step={0.05}
onUpdate={newValue =>
updateBehaviorProperty('angularDamping', newValue)
}
/>
</ResponsiveLineStackLayout>
<Line>
<Text style={{ marginRight: 10 }}>
{properties.get('layers').getLabel()}
</Text>
<BitGroupEditor
key={'static-layers'}
firstIndex={0}
bits={staticBits.map(
(_, index) => isBitEnabled(layersValues, index) && isStatic
)}
onChange={(index, value) => {
const newValue = enableBit(layersValues, index, value);
updateBehaviorProperty('layers', newValue.toString(10));
}}
disabled={!isStatic}
/>
<Spacer />
<BitGroupEditor
key={'dynamic-layers'}
firstIndex={4}
bits={dynamicBits.map(
(_, index) => isBitEnabled(layersValues, index + 4) && !isStatic
)}
onChange={(index, value) => {
const newValue = enableBit(layersValues, index, value);
updateBehaviorProperty('layers', newValue.toString(10));
}}
disabled={isStatic}
/>
</Line>
<Line>
<Text style={{ marginRight: 10 }}>
{properties.get('masks').getLabel()}
</Text>
<BitGroupEditor
key={'static-mask'}
firstIndex={0}
bits={staticBits.map(
(_, index) => isBitEnabled(masksValues, index) || isStatic
)}
onChange={(index, value) => {
const newValue = enableBit(masksValues, index, value);
updateBehaviorProperty('masks', newValue.toString(10));
}}
disabled={isStatic}
/>
<Spacer />
<BitGroupEditor
key={'dynamic-mask'}
firstIndex={4}
bits={dynamicBits.map(
(_, index) => isBitEnabled(masksValues, index + 4) || isStatic
)}
onChange={(index, value) => {
const newValue = enableBit(masksValues, index, value);
updateBehaviorProperty('masks', newValue.toString(10));
}}
disabled={isStatic}
/>
</Line>
</Column>
);
};
export default Physics3DEditor;