Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 184 additions & 73 deletions docs/developer/typescript/03_basics_tutorial/04_assigning_physics.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,176 @@
<!-- import { TechnicalNote } from '@site/src/components/TechnicalNote'; -->
<!-- import { UnstyledDetails } from '@site/src/components/UnstyledDetails'; -->
# Add physics to an entity

# Assigning Physics
This guide walks you through adding physics components to your entity, allowing it to behave like a real-world object.&#x20;

So far we have learned how to create an `Entity`, and how to tell the engine what we want our entity to be. In simple terms, we told the engine how to **create** our sphere.
:::hint{type="info"}
ℹ️ **Complete** [Hello World tutorial](./../02_hello_world/index.md) **first**

## Our problem
This guide uses the Hello.ts file from the [Hello World tutorial](./../02_hello_world/index.md) project to teach you how to add physics to an entity. If you haven't completed such tutorial, you use the code snippet from the introduction as a reference to follow along with this guide.
:::

We added some components to our sphere, so that the engine can draw the sphere into the screen and we can see it.
But right now it is only an "empty shell" that sits there doing nothing.
We cannot even move it or push it around! What a boring ball.
Lets fix that.
## Introduction

## Our solution
In iR Engine, entities do not have physics properties by default. If you want an entity to interact with forces like gravity, collisions, and movement, you need to explicitly define its physics behavior.&#x20;

We are going to add a Collider and a RigidBody to our sphere object.
For this excersice we'll work with the last implementation of the Hello.ts file from [Hello World tutorial](./../02_hello_world/index.md):

Physics properties are tricky to test, as they may not be readily visible.
Lets get a point of reference of how our project currently behaves, so we can be certain that the changes we make to our code are working as we expect them.
In order to do that, we are going to run our project from the studio and walk around the scene with an Avatar.
::::codedrawer{title="Hello.ts file"}
:::codeblocktabs-examples
```javascript
import { ECS } from '@ir-engine/packages/ecs'
import { NameComponent } from '@ir-engine/packages/spatial/src/common/NameComponent'
import { VisibleComponent } from '@ir-engine/packages/spatial/src/renderer/components/VisibleComponent'
import { TransformComponent } from '@ir-engine/packages/spatial/src/transform/components/TransformComponent'
import { PrimitiveGeometryComponent } from '@ir-engine/packages/engine/src/scene/components/PrimitiveGeometryComponent'
import { Vector3 } from 'three'
import { GeometryTypeEnum } from '@ir-engine/packages/engine/src/scene/constants/GeometryTypeEnum'
import { PhysicsSystem } from '@ir-engine/packages/spatial'

These are the steps needed to accomplish that:
// Define the custom component
export const HelloComponent = ECS.defineComponent({
name: 'ee.tutorial.HelloComponent',
jsonID: 'EE_tutorial_hello',
onInit: () => ({ initialized: false })
})

// Define the query to find entities with HelloComponent
const helloQuery = ECS.defineQuery([HelloComponent])

// Define the function to execute
const hello = () => {
for (const entity of helloQuery()) {
let { initialized } = ECS.getMutableComponent(entity, HelloComponent)
if (initialized.value) continue
initialized.set(true)

ECS.setComponent(entity, NameComponent, 'ee.tutorial.hello-entity')
ECS.setComponent(entity, VisibleComponent)
ECS.setComponent(entity, TransformComponent, { position: new Vector3(0, 1, 0) })
ECS.setComponent(entity, PrimitiveGeometryComponent, { geometryType: GeometryTypeEnum.SphereGeometry })
}
}

// Define the system
export const HelloSystem = ECS.defineSystem({
uuid: 'ee.tutorial.HelloSystem',
execute: hello,
insert: { after: PhysicsSystem }
})
```
:::

:::codeblocktabs-responses
```javascript
```
:::
::::

- Open the scene you created before, or click on `Create Scene` if you don't have it
- Press the `Play` button in the studio
- Move your Avatar around the scene by either:
- Pressing `WASD` in your keyboard
- Clicking anywhere on the ground with your mouse
***

You may notice that, if you try to hit the sphere with your avatar... you will instead walk right through it!
This happens because our Sphere doesn't have any Physics properties yet, so it can be "seen" but not "collided against".
## The problem: A static object

## Physics Properties
So far, we have **created an entity** and assigned it a **shape**. However, the entity does not interact with the world—it simply exists without movement or collision.

In order to correct our problem, we are now going to:
- **Issue**: The entity is **visible** but **does not respond to gravity or collisions**.
- **Expected behavior**: The entity should **fall, collide, and respond to forces** in the environment.

- Add a `RigidBodyComponent` of type `dynamic` to our entity
- Add a `ColliderComponent` with the shape of a `sphere`
You can confirm this by running the scene and **walking into the sphere with your avatar**. Right now, the avatar **walks through the object** because it has no physics properties.

Lets also change the position of ball so that it spawns some distance above the ground.
Here are your hints for this tutorial:
***

```ts
// Both the RigidBody and Collider components are part of the `Spatial/physics` engine module
'@ir-engine/packages/spatial/src/physics/components/.....'
// We can specify the dynamic type with:
{ type: 'dynamic' }
// We can specify the shape with:
{ shape: 'sphere' }
// Make the ball spawn 3units above the ground
Vector3(/* X */, /* Y */, /* Z */)
```
## Solution: Adding physics components

You will know that your code is correct if:
To fix this issue, you need to **assign physics properties** to the sphere by adding two key components:

- The ball has gravity and falls to the ground
- You try to go through the ball with the Avatar, but the engine stops you and you push the ball instead.
1. `RigidBodyComponent` – Enables **physics simulation** (gravity, forces, movement).
2. `ColliderComponent` – Defines a **collision shape**, allowing objects to interact physically.

<TechnicalNote title="Solution">
Additionally, you will adjust the **spawn height** of the sphere so it falls naturally due to gravity.

```ts
// Import both components from the Spatial/physics module
***

## Step 1: Import physics components

Physics components belong to the **spatial physics module** in iR Engine. Import them into your script:

```typescript
import { RigidBodyComponent } from '@ir-engine/packages/spatial/src/physics/components/RigidBodyComponent'
import { ColliderComponent } from '@ir-engine/packages/spatial/src/physics/components/ColliderComponent'
```
```ts
// Set both components to our entity

***

## Step 2: Assign physics properties

### 1. Enable rigid body physics

The **RigidBodyComponent** allows the sphere to move dynamically in response to forces and collisions. Set its **type** to `'dynamic'` so it responds to gravity and forces.

```typescript
ECS.setComponent(entity, RigidBodyComponent, { type: 'dynamic' })
```

**What this does:**

✅ The sphere **falls** to the ground.

✅ It can be **pushed by other objects**.

***

### 2. Define a collider shape

The **ColliderComponent** assigns a **collision shape** to the sphere, preventing other objects from passing through it. Use a **sphere collider** to match the shape of the entity.

```typescript
ECS.setComponent(entity, ColliderComponent, { shape: 'sphere' })
```
```ts
// Make the ball spawn 3 units along the Y axis (aka 3u above the ground)

**What this does:**

- The sphere **collides** with other objects.
- The avatar **can no longer walk through it**.

***

### 3. Adjust the spawn position

Currently, the sphere spawns **at ground level**, making it difficult to observe physics effects. Move it **3 units above the ground** so you can see it fall.

```tsx
ECS.setComponent(entity, TransformComponent, { position: new Vector3(0, 3, 0) })
```

<UnstyledDetails title="Full Solution">
**What this does:**

- The sphere starts **above the ground**.
- Gravity pulls it down **when the scene starts**.

***

## Step 3: Confirm physics interactions

Once you have added the physics components, **test the behavior** to ensure everything is working correctly.

### Expected behaviors:

✔ The sphere **falls** to the ground due to gravity.

✔ When the avatar walks into the sphere, **it moves instead of passing through**.

### If something is wrong:

⚠ If the sphere **doesn’t fall**, ensure the `RigidBodyComponent` is set to `type: 'dynamic'`.

⚠ If the avatar **still walks through the sphere**, double-check the `ColliderComponent`.

```ts title="ir-tutorial-basic/Step2.ts" showLineNumbers
***

## Final implementation

After making these updates, your system definition should look like this:

```typescript
import { ECS } from '@ir-engine/packages/ecs'
import { NameComponent } from '@ir-engine/packages/spatial/src/common/NameComponent'
import { VisibleComponent } from '@ir-engine/packages/spatial/src/renderer/components/VisibleComponent'
Expand All @@ -85,48 +179,65 @@ import { PrimitiveGeometryComponent } from '@ir-engine/packages/engine/src/scene
import { Vector3 } from 'three'
import { GeometryTypeEnum } from '@ir-engine/packages/engine/src/scene/constants/GeometryTypeEnum'
import { PhysicsSystem } from '@ir-engine/packages/spatial'
// Import both components from the Spatial/physics module
//highlight-start

// Import physics components
import { RigidBodyComponent } from '@ir-engine/packages/spatial/src/physics/components/RigidBodyComponent'
import { ColliderComponent } from '@ir-engine/packages/spatial/src/physics/components/ColliderComponent'
//highlight-end

export const BasicsComponent = ECS.defineComponent({
name: 'ee.tutorial.BasicsComponent',
jsonID: 'EE_tutorial_basics',
onInit: () => { return { initialized: false } }
// Define the custom component
export const PhysicsComponent = ECS.defineComponent({
name: 'ir-engine.tutorial.PhysicsComponent',
jsonID: 'IR_ENGINE_TUTORIAL_PHYSICS',
onInit: () => ({ initialized: false })
})

const basicsQuery = ECS.defineQuery([BasicsComponent])
// Define the query to find entities with PhysicsComponent
const physicsQuery = ECS.defineQuery([PhysicsComponent])

export const BasicsSystem = ECS.defineSystem({
uuid: 'ee.tutorial.BasicsSystem',
// Define the system to apply physics
export const PhysicsSystem = ECS.defineSystem({
uuid: 'ir-engine.tutorial.PhysicsSystem',
execute: () => {
for (const entity of basicsQuery()) {
let { initialized } = ECS.getMutableComponent(entity, BasicsComponent)
for (const entity of physicsQuery()) {
let { initialized } = ECS.getMutableComponent(entity, PhysicsComponent)
if (initialized.value) continue
initialized.set(true)

ECS.setComponent(entity, NameComponent, 'ee.tutorial.basics-entity')
ECS.setComponent(entity, NameComponent, 'ir-engine.physics-entity')
ECS.setComponent(entity, VisibleComponent)
// Make the ball spawn 3 units along the Y axis (aka 3u above the ground)
//highlight-start

// Set initial position (3 units above ground)
ECS.setComponent(entity, TransformComponent, { position: new Vector3(0, 3, 0) })
//highlight-end

// Assign primitive geometry
ECS.setComponent(entity, PrimitiveGeometryComponent, { geometryType: GeometryTypeEnum.SphereGeometry })
// Set both components to our entity
//highlight-start

// Enable physics
ECS.setComponent(entity, RigidBodyComponent, { type: 'dynamic' })
ECS.setComponent(entity, ColliderComponent, { shape: 'sphere' })
//highlight-end
}
},
insert: { after: PhysicsSystem }
})
```

</UnstyledDetails>
<!-- Full Solution End -->
</TechnicalNote>
<!-- Solution End -->
***

## Summary

✅ **Entities need physics components** to interact with forces like gravity.

✅ Use `RigidBodyComponent` to define movement and behavior.

✅ Use `ColliderComponent` to specify how an entity collides.

✅ Adjust the **spawn position** to test gravity and movement.

✅ The entity should now **fall** and **collide with the avatar** in the scene.

***

## ➡️ Next steps

[State Management](./05_state_management.md)&#x20;