> For the complete documentation index, see [llms.txt](https://docs.overdare.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.overdare.com/korean/manual/studio-manual/object/simulationball.md).

# SimulationBall

### 개요 <a href="#undefined" id="undefined"></a>

SimulationBall은 공의 움직임을 미리 계산한 뒤 그 결과를 재생하는 오브젝트입니다. 일반적인 물리 공보다 궤적을 예측하기 쉽고, 여러 플레이어가 함께 보는 환경에서도 보다 안정적으로 같은 결과를 표현할 수 있습니다.

특히 다음과 같은 상황에서 유용합니다.

* 축구공, 농구공처럼 공의 궤적이 중요한 게임
* 벽이나 바닥에 튀는 공의 움직임을 연출해야 하는 경우
* 공의 이동 경로를 미리 확인하면서 게임 플레이를 조정하고 싶은 경우

### 사용 방법 <a href="#undefined" id="undefined"></a>

#### SimulationBall 생성 및 충돌 대상 Trace Channel 설정 <a href="#simulationball-bound-trace-channel" id="simulationball-bound-trace-channel"></a>

<figure><img src="/files/yPxOlvJKba5G8x4rpYif" alt=""><figcaption></figcaption></figure>

레벨 브라우저에서 `SimulationBall`을 생성한 뒤, 사용할 위치에 배치합니다.

<figure><img src="/files/hOxNIZxc69Sc9xkcDus3" alt=""><figcaption></figcaption></figure>

SimulationBall이 어떤 오브젝트와 충돌할지는 **Collision Profile**과 **StaticObjectTypes**을 통해 정의합니다.

* `BallMeshCollisionProfile`: SimulationBall 자체가 가지는 Collision Profile입니다. 다른 오브젝트가 SimulationBall과 충돌할 때 참고합니다. 다만 해당 Profile은 실제 시뮬레이션에서 사용되지 않습니다.
* `StaticObjectTypes`: SimulationBall이 충돌 시뮬레이션을 수행할 때 사용할 Object Type 채널입니다. 실제로 Simulation 할 때, 해당 채널들과의 충돌을 바탕으로 궤적을 찾아냅니다.

따라서 벽이나 바닥처럼 공이 충돌해야 하는 오브젝트들은, `StaticObjectTypes` 에 해당 ObjectType을 추가하여야 합니다.

{% hint style="info" %}
`StaticObjectTypes` 배열이 비어 있으면 기본값으로 `WorldStatic` 채널만 충돌 대상이 됩니다. 다른 채널의 오브젝트와도 충돌해야 한다면 해당 채널을 반드시 추가하세요.
{% endhint %}

#### 에디터를 이용한 Simulation Preview <a href="#simulation-preview" id="simulation-preview"></a>

<figure><img src="/files/sPjbn06mPckfUUFUOJFB" alt=""><figcaption></figcaption></figure>

게임에서 시뮬레이션 볼에 필요한 적절한 물리 값을 찾으려면, 수차례 수치를 바꾸어 가며 테스트해야 합니다. 이를 돕기 위해 게임을 실제로 플레이하지 않고도 편집 단계에서 다양한 시뮬레이션을 실행해 볼 수 있는 프리뷰 기능을 제공합니다.

SimulationBall의 `EditorBallSimParams`에 여러 값을 넣어 보면서, 공이 어떤 궤적으로 날아가는지 에디터에서 바로 확인할 수 있습니다. 이때 `EnablePathMarker`를 켜 두면 계산된 궤적이 경로 마커로 표시되어 값을 비교하기 쉽습니다.

#### 스크립트를 이용한 Simulation 조작 <a href="#simulation-script" id="simulation-script"></a>

SimulationBall을 찾은 뒤, 시작 위치와 속도를 설정하고 시뮬레이션을 실행합니다.

`Simulate()`는 호출 직후 결과가 바로 완성되지 않습니다. 내부적으로 시뮬레이션을 비동기적으로 나누어 계산하기 때문에, `FindNextBallBounce()`나 `GetCFrameAtTime()`처럼 **시뮬레이션 결과를 조회하는 API**는 계산이 끝난 뒤에 호출해야 합니다. 반면 재생을 시작하는 `Play()`는 아래 예제처럼 `Simulate()` 직후에 이어서 호출해도 됩니다.

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

-- EnablePathMarker draws the simulated trajectory on screen for preview
Ball.EnablePathMarker = true


local Params = BallSimParams.new()
Params.Mass = 0.43
Params.InitialCFrame = CFrame.new(0, 100, -800)

-- InitialSpeed is in km/h and InitialDirection must be a unit vector
local InitialVelocity = Vector3.new(300, 900, 0) -- velocity vector in km/h scale
Params.InitialSpeed = InitialVelocity.Magnitude
Params.InitialDirection = InitialVelocity.Unit

Params.Simsteps = 120
Params.StepsPerSecond = 30

-- With spin values below, the ball can bounce along its spin direction on the ground
-- or curve in the air by the Magnus effect
--Params.InitialSpinAxis = Vector3.new(0, 1, 0)
--Params.InitialSpinSpeed = 0

Ball:Simulate(Params, false)
Ball:Play()
```

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhRPi87oM9ttlk5nyu7L7%2Fuploads%2FCL2hnpT5fooLEHZP4qo8%2Fsimball.mp4?alt=media&token=187f8f40-32aa-49fd-9d6f-0f6738d9fc1d>" %}

#### BallSimParams의 주요 속성 <a href="#ballsimparams" id="ballsimparams"></a>

공의 움직임은 `BallSimParams`의 주요 설정값을 바꾸면서 조정할 수 있습니다.

<table><thead><tr><th width="221.6666259765625">항목</th><th>설명</th></tr></thead><tbody><tr><td><strong>InitialCFrame</strong></td><td>공이 시작하는 위치와 초기 회전을 정합니다.</td></tr><tr><td><strong>InitialSpeed</strong></td><td>공이 발사되는 속력을 km/h 단위로 정합니다.</td></tr><tr><td><strong>InitialDirection</strong></td><td>공이 날아가는 방향을 단위 벡터로 정합니다.</td></tr><tr><td><strong>InitialSpinAxis</strong></td><td>공이 회전하는 축을 단위 벡터로 정의합니다.</td></tr><tr><td><strong>InitialSpinSpeed</strong></td><td>공의 회전 속도를 RPM(분당 회전수) 단위로 정의합니다(-12000~12000). 절댓값이 클수록 마그누스 힘이 커집니다.</td></tr><tr><td><strong>Mass</strong></td><td>공의 질량을 kg 단위로 정합니다. 충돌 임펄스와 회전 관성 계산에 사용됩니다.</td></tr><tr><td><strong>BaseGravity</strong></td><td>공이 아래로 떨어지는 정도(중력 가속도)를 cm/s² 단위로 조정합니다. 기본값 980이며, 0인 경우 무중력 상태이고, 음수가 되면 중력이 반대로 작동합니다.</td></tr><tr><td><strong>Restitution</strong></td><td>충돌 시 반발 계수를 정합니다(0~1). 1에 가까울수록 공이 잘 튀어 오릅니다.</td></tr><tr><td><strong>Friction</strong></td><td>지면과의 마찰 계수를 정합니다(0~1). 값이 클수록 지면 접촉 시 슬라이딩 속도가 빠르게 감소합니다.</td></tr><tr><td><strong>RollingFriction</strong></td><td>구름 저항 계수를 정합니다(0~1). 지면에서 구르는 동안 속도가 감소하는 정도를 결정합니다.</td></tr><tr><td><strong>SpinMagnusWeight</strong></td><td>공이 회전할 때 적용되는 마그누스 효과의 가중치를 정의합니다(0.0~0.1). 가중치가 클수록 궤적이 휘는 정도가 커지며, 축구공·골프공 수준의 자연스러운 효과는 약 0.01~0.015입니다.</td></tr><tr><td><strong>Simsteps</strong></td><td>시뮬레이션을 몇 단계로 나누어 계산할지 정합니다(1~14400). 값이 클수록 더 정밀해질 수 있지만 계산 비용도 늘어납니다.</td></tr><tr><td><strong>StepsPerSecond</strong></td><td>시뮬레이션 주파수를 Hz 단위(초당 스텝 수)로 정합니다(1~480). `Simsteps`와 함께 전체 시뮬레이션 길이(`Simsteps ÷ StepsPerSecond`)와 정밀도를 결정합니다.</td></tr></tbody></table>

예를 들어, 더 멀리 날리고 싶다면 `InitialSpeed` 값을 키우고, 더 높은 곳에서 시작하게 하려면 `InitialCFrame`의 높이 값을 올리면 됩니다.

### SimulationBall 추가 기능 사용 <a href="#simulationball-features" id="simulationball-features"></a>

SimulationBall은 일반적인 실시간 물리 시뮬레이션과 달리, 사전에 입력된 BallSimParams를 바탕으로 미리 공의 궤적과 충돌을 계산하고 그 결과를 Play하게 됩니다. 이를 통해 Play 전에 공의 이동 경로 또는 충돌 위치를 미리 알아낼 수 있습니다.

#### 공의 다음 Bound 위치 알아내기 <a href="#bound" id="bound"></a>

SimulationBall은 시뮬레이션이 끝난 뒤, 다음으로 튕길 지점의 정보를 미리 알아낼 수 있습니다.

이 기능은 다음과 같은 경우에 유용합니다.

* 공이 어느 벽에서 튈지 미리 확인하고 싶은 경우
* 다음 바운드 위치에 이펙트를 배치하고 싶은 경우
* AI 또는 게임 로직에서 다음 충돌 지점을 예측하고 싶은 경우

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

local Params = BallSimParams.new()

Params.Mass = 0.43
Params.InitialCFrame = CFrame.new(0, 100, -800)

-- InitialSpeed is in km/h and InitialDirection must be a unit vector
local InitialVelocity = Vector3.new(300, 900, 0) -- velocity vector in km/h scale
Params.InitialSpeed = InitialVelocity.Magnitude
Params.InitialDirection = InitialVelocity.Unit

Params.Simsteps = 120
Params.StepsPerSecond = 30


Ball:Simulate(Params, false)
-- Wait until the async simulation finishes before querying results
task.wait()
local NextBounce = Ball:FindNextBallBounce()

if NextBounce.BouncedTime > 0 then
    print("Next Bound Time:", NextBounce.BouncedTime)
    print("Next Bound Position:", NextBounce.BouncedPosition)
end

Ball:Play()
```

위 코드에서는 `FindNextBallBounce()`를 사용해 다음 바운드 정보를 가져옵니다.\
반환된 값 안에는 바운드가 일어나는 시간과 위치가 포함되어 있어, 공이 어디에서 튈지 미리 확인할 수 있습니다.

단, `Simulate()` 직후 바로 `FindNextBallBounce()`를 호출하면 아직 계산이 끝나지 않아 원하는 값을 얻지 못할 수 있습니다. 위 예제처럼 `task.wait()`로 잠시 대기한 뒤 호출해야 합니다. `Simsteps`가 커서 계산량이 많은 경우에는 한 프레임 대기로 부족할 수 있으므로, 유효한 값이 반환될 때까지 반복 확인하거나 충분한 시간을 두고 조회하세요.

#### N초 후 공의 물리 값 가져오기 <a href="#n" id="n"></a>

SimulationBall은 특정 시간이 지난 뒤 공의 상태도 미리 가져올 수 있습니다.

이 기능은 다음과 같은 경우에 유용합니다.

* N초 뒤 공의 위치를 알고 싶은 경우
* 공의 속도가 얼마나 되는지 확인하고 싶은 경우
* 회전 속도까지 포함해서 미래 상태를 예측하고 싶은 경우

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

local Params = BallSimParams.new()
Params.Mass = 0.43
Params.InitialCFrame = CFrame.new(0, 100, -800)

-- InitialSpeed is in km/h and InitialDirection must be a unit vector
local InitialVelocity = Vector3.new(300, 900, 0) -- velocity vector in km/h scale
Params.InitialSpeed = InitialVelocity.Magnitude
Params.InitialDirection = InitialVelocity.Unit

Params.Simsteps = 120
Params.StepsPerSecond = 30

Ball:Simulate(Params, false)
-- Wait until the async simulation finishes before querying results
task.wait()

local CheckTime = 2.0

local FutureCFrame = Ball:GetCFrameAtTime(CheckTime)
local FutureVelocity = Ball:GetLinearVelocityAtTime(CheckTime)
local FutureSpeed = Ball:GetSpeedAtTime(CheckTime)
local FutureAngularVelocity = Ball:GetAngularVelocityAtTime(CheckTime)

print("Position in 2s:", FutureCFrame.Position)
print("Velocity vector in 2s:", FutureVelocity)
print("Speed in 2s:", FutureSpeed)
print("Angular velocity in 2s:", FutureAngularVelocity)
```

위 예제에서는 `2초 후` 공의 물리 값을 미리 가져옵니다.

* `GetCFrameAtTime()` : 해당 시점의 위치와 회전
* `GetLinearVelocityAtTime()` : 해당 시점의 이동 속도 방향과 크기
* `GetSpeedAtTime()` : 해당 시점의 속력만 숫자로 확인
* `GetAngularVelocityAtTime()` : 해당 시점의 회전 속도 확인

즉, SimulationBall은 단순히 공을 재생하는 것뿐 아니라, **미래 시점의 위치와 속도, 회전까지 미리 조회**할 수 있습니다.

이때도 `Simulate()` 직후 바로 값을 가져오는 방식은 피하고, `task.wait()`로 잠시 대기한 뒤 조회하는 것이 좋습니다.

또한 `CheckTime`이 현재 시뮬레이션 범위를 넘어서면 기대한 값을 얻지 못할 수 있으므로, `Simsteps ÷ StepsPerSecond`가 조회하려는 시간보다 충분히 크도록 설정해야 합니다.

#### 목표 위치로 공을 자동 조준하기 <a href="#simulatetotarget" id="simulatetotarget"></a>

`SimulateToTarget()`을 사용하면 발사 속도와 방향을 직접 계산할 필요 없이, 목표 위치만 지정하면 해당 지점에 도달하는 속도와 방향을 자동으로 계산하여 시뮬레이션을 실행합니다.

이 기능은 다음과 같은 경우에 유용합니다.

* 특정 위치로 정확히 공을 던지는 트릭샷을 구현하고 싶은 경우
* NPC나 AI가 플레이어 위치를 향해 공을 던지게 하고 싶은 경우
* 발사 속도/방향을 수동으로 계산하는 대신 목표 지점만으로 궤적을 구성하고 싶은 경우

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

local Params = BallSimParams.new()
Params.Mass = 0.43
Params.InitialCFrame = CFrame.new(0, 100, -800)
Params.Simsteps = 120
Params.StepsPerSecond = 30

local TargetPosition = Vector3.new(0, 0, 800)

-- UseDesiredSpeed = false: search for both speed and direction to reach the target
-- AutoPlay = true: start playback immediately after the simulation completes
local Result = Ball:SimulateToTarget(Params, TargetPosition, false, true)

if Result.bHit then
    print("Hit time:", Result.HitTime)
    print("Actual launch speed (km/h):", Result.ActualSpeed)
else
    print("Could not find a trajectory that reaches the target")
end
```

`SimulateToTarget()`은 계산 결과를 `BallSimTargetResult`로 즉시 반환하므로, `Simulate()`와 달리 별도의 대기 없이 반환값을 바로 사용할 수 있습니다. `bHit`으로 목표에 도달하는 궤적을 찾았는지 확인하고, `HitTime`으로 도달 시각을, `ActualSpeed`/`Direction`으로 실제 사용된 속도와 방향을 확인할 수 있습니다.

#### 충돌 이벤트 활용하기 <a href="#collision-events" id="collision-events"></a>

SimulationBall은 재생 중 다른 오브젝트와 충돌하면 이벤트를 발생시킵니다. 골 판정, 사운드 재생, 이펙트 표시 같은 충돌 기반 로직을 구현할 때 사용합니다.

* `Touched`: 공이 다른 파트와 충돌했을 때 호출됩니다.
* `TouchEnded`: 공이 파트와의 접촉에서 떨어져 나갈 때 호출됩니다.
* `Bounded`: 공이 파트에 **바운스(충돌 반사)** 했을 때만 호출됩니다. 슬라이딩 충돌은 포함되지 않습니다.

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

-- Called on every collision
Ball.Touched:Connect(function(otherPart)
    if otherPart.Name == "Goal" then
        print("Goal!")
    end
end)

-- Called only when the ball actually bounces off a part
Ball.Bounded:Connect(function(otherPart, bounce)
    print("Ball bounced off:", otherPart.Name)
    print("Bounce position:", bounce.BouncedPosition)
end)
```

#### 재생 제어하기 <a href="#playback-control" id="playback-control"></a>

시뮬레이션 재생은 `Play()` 외에도 다양한 방법으로 제어할 수 있습니다.

* `Pause()`: 재생을 일시 정지합니다. `Play()`를 다시 호출하면 정지한 지점부터 이어서 재생됩니다.
* `Stop()`: 재생을 정지합니다. 이후 `Play()`를 호출하면 시뮬레이션 시작 시점부터 다시 재생됩니다.
* `Play(bReset)`: `bReset`에 `true`를 넘기면 재생 시간(`PlaybackTime`)을 초기화한 뒤 재생합니다. 생략하면 기본값 `false`로 동작합니다.
* `SetPlaybackTime(time)`: 재생 시간을 임의의 시점으로 이동합니다. 재생 중에도 호출할 수 있어 되감기나 특정 순간으로 건너뛰기에 사용할 수 있습니다.
* `SlomoFactor`: 재생 속도 배율입니다. 1.0이 정상 속도이며, 0.5면 절반 속도(슬로모션), 2.0이면 2배 속도로 재생됩니다.

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

Ball:Play()

-- Pause and resume from the same point
Ball:Pause()
Ball:Play()

-- Jump to the 2.5 second mark (also works while playing)
Ball:SetPlaybackTime(2.5)

-- Play at half speed for a slow-motion effect
Ball.SlomoFactor = 0.5

-- Stop, then replay from the beginning
Ball:Stop()
Ball:Play()
```

#### 비행 중 궤적 다시 계산하기 (ReSimulate) <a href="#resimulate" id="resimulate"></a>

재생 중인 공의 궤적을 특정 시점부터 새로 계산할 수도 있습니다. 패스를 가로채거나, 공중에서 방향을 바꾸는 연출 등에 활용할 수 있습니다.

* `ReSimulateWithDelay()`: 현재 재생 시간에서 지정한 지연 시간 후, 새로운 방향·속도·스핀으로 궤적을 재계산합니다.
* `ReSimulateToTargetWithDelay()`: 지연 시간 후 목표 위치를 향하도록 궤적을 재계산합니다. 스핀은 현재 각속도를 기반으로 자동 계산됩니다.
* `ReSimulateSpinToTargetWithDelay()`: 지연 시간 후 지정한 스핀 축·스핀 속도를 사용해 목표 위치를 향하도록 궤적을 재계산합니다.

```lua
local Workspace = game:GetService("Workspace")
local Ball = Workspace:WaitForChild("SimulationBall")

local TargetPosition = Vector3.new(100, 0, 50)

-- After 1 second, recalculate the trajectory toward the target
local Result = Ball:ReSimulateToTargetWithDelay(
    1.0,            -- delay from the current playback time (seconds)
    TargetPosition, -- target position
    100,            -- desired speed (km/h)
    120,            -- step count
    false           -- search for both speed and direction
)

if Result.bHit then
    print("Hit time:", Result.HitTime)
end
```

`ReSimulateToTargetWithDelay()`와 `ReSimulateSpinToTargetWithDelay()`는 `SimulateToTarget()`과 마찬가지로 `BallSimTargetResult`를 반환합니다.

### 주의사항 <a href="#undefined" id="undefined"></a>

* `Simulate()`는 즉시 완료되지 않으며 내부적으로 비동기적으로 처리됩니다. `FindNextBallBounce()`, `GetCFrameAtTime()` 등 시뮬레이션 결과를 조회하기 전에 `task.wait()`로 잠시 대기해야 하며, 계산량이 많은 경우 유효한 값이 반환될 때까지 반복 확인이 필요할 수 있습니다.
* 공을 재생하기 전에 먼저 시뮬레이션을 실행해야 합니다.
* `StaticObjectTypes`가 비어 있으면 `WorldStatic` 채널만 충돌 대상이 됩니다. 공이 튕겨야 할 오브젝트의 채널을 반드시 추가하세요.
* 속도 값이 너무 작으면 공이 거의 움직이지 않는 것처럼 보일 수 있습니다.
* 미래 시점 조회 시 `CheckTime`이 전체 시뮬레이션 길이(`Simsteps ÷ StepsPerSecond`)를 넘지 않도록 설정해야 합니다.
* 테스트 중에는 `EnablePathMarker`를 켜 두는 것이 확인에 도움이 됩니다.

### 참고 문서 <a href="#undefined" id="undefined"></a>

{% content-ref url="/pages/ca0qkgbvP2luHQy0HlFQ" %}
[SimulationBall](/korean/development/api-reference/classes/simulationball.md)
{% endcontent-ref %}

{% content-ref url="/pages/7wr61Ez7ceCHQBS3pGKH" %}
[BallSimParams](/korean/development/api-reference/datatype/ballsimparams.md)
{% endcontent-ref %}

{% content-ref url="/pages/CuxgqYP1nb0d2NnHP0uO" %}
[BallSimTargetResult](/korean/development/api-reference/datatype/ballsimtargetresult.md)
{% endcontent-ref %}

{% content-ref url="/pages/OesRZ7Wb7ZrqIsdB0uTW" %}
[BallBounce](/korean/development/api-reference/datatype/ballbounce.md)
{% endcontent-ref %}
