# 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>

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

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

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

* `BallTraceChannel`: SimulationBall이 충돌 시뮬레이션을 수행할 때 사용할 **Trace Channel**입니다. 이 채널을 기준으로 Trace를 수행하여, 해당 채널에 대해 Block으로 응답하는 Collision Profile을 가진 오브젝트와만 충돌합니다.
* `BallMeshCollisionProfile`: SimulationBall 자체가 가지는 Collision Profile입니다.

따라서 벽이나 바닥처럼 공이 충돌해야 하는 오브젝트에는, `BallTraceChannel`로 지정된 Trace Channel에 대해 Block으로 응답하는 Collision Profile을 적용해야 합니다.

#### 스크립트를 이용한 Simaulation 조작 <a href="#simaulation" id="simaulation"></a>

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

`Simulate()`는 호출 직후 결과가 바로 완성되지 않습니다. 내부적으로 시뮬레이션을 비동기적으로 나누어 계산하기 때문에, `Simulate()` 직후에는 아직 결과가 준비되지 않았을 수 있습니다.

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

-- EnablePathMarker는 SimulationBall의 궤적을 미리 볼 수 있도록 화면에 표시해 줍니다.
Ball.EnablePathMarker = true


local Params = BallSimParams.new()
Params.Mass = 0.43
Params.InitialCFrame = CFrame.new(0, 100, -800)
Params.InitialVelocity = Vector3.new(300, 900, 0)
Params.Simsteps = 120
Params.DeltaTime = 1 / 30

-- 아래 Spin 정보를 넣으면, 공이 지면에 닿았을 때, 회전방향으로 튕기거나 공중에서 마그누스 효과(커브볼)를 표현할 수 있습니다.
--Params.InitialSpinAxis = Vector3.new(0, 1, 0)
--Params.InitialSpinSpeed = 0

Ball:Simulate(Params)
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>" %}

#### BallSimParam의 주요 속성 <a href="#ballsimparam" id="ballsimparam"></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>InitialVelocity</strong></td><td>공이 날아가는 방향과 힘을 정합니다.</td></tr><tr><td><strong>InitialSpinAxis</strong></td><td>공이 회전하는 축을 정의합니다.</td></tr><tr><td><strong>InitialSpinSpeed</strong></td><td>공의 회전 속도를 정의합니다.</td></tr><tr><td><strong>Mass</strong></td><td>공의 무게감을 조정할 때 사용합니다.</td></tr><tr><td><strong>BaseGravity</strong></td><td>공이 아래로 떨어지는 정도(중력 값)를 조정합니다. 기본 980 으로 0인 경우 무중력 상태이고, 음수가 되면 중력이 반대로 작동합니다.</td></tr><tr><td><strong>EnablePathMarker</strong></td><td>공의 이동 경로를 화면에서 확인할 수 있도록 합니다.</td></tr><tr><td><strong>SpinMagnusWeight</strong></td><td>공이 회전할 때 적용되는 마그누스 효과의 가중치를 정의합니다. 가중치가 클 수록, 궤적이 휘는 정도가 커집니다.</td></tr><tr><td><strong>Simsteps</strong></td><td>시뮬레이션을 몇 단계로 나누어 계산할지 정합니다. 값이 클수록 더 정밀해질 수 있지만 계산 비용도 늘어납니다.</td></tr><tr><td><strong>DeltaTime</strong></td><td>각 시뮬레이션 단계의 시간 간격입니다. `Simsteps`와 함께 전체 시뮬레이션 길이와 정밀도를 결정합니다.</td></tr></tbody></table>

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

### SimaulationBall 추가 기능 사용 <a href="#simaulationball" id="simaulationball"></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)
Params.InitialVelocity = Vector3.new(300, 900, 0)
Params.Simsteps = 120
Params.DeltaTime = 1 / 30


Ball:Simulate(Params)
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()`로 잠시 대기한 뒤 호출해야 합니다.

#### 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)
Params.InitialVelocity = Vector3.new(300, 900, 0)
Params.Simsteps = 120
Params.DeltaTime = 1 / 30

Ball:Simulate(Params)
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 × DeltaTime`가 조회하려는 시간보다 충분히 크도록 설정해야 합니다.

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

{% hint style="info" %}
SimulationBall이 벽 또는 바닥과 충돌하려면, 해당 벽/바닥에 적용된 **Collision Profile**이 SimulationBall의 `BallTraceChannel`에 대해 **Block**으로 응답하도록 설정되어 있어야 합니다.
{% endhint %}

* `Simulate()`는 즉시 완료되지 않으며 내부적으로 비동기적으로 처리됩니다. 시뮬레이션결과 조회 전에 `task.wait()`로 잠시 대기해야 합니다.
* 공을 재생하기 전에 먼저 시뮬레이션을 실행해야 합니다.
* 속도 값이 너무 작으면 공이 거의 움직이지 않는 것처럼 보일 수 있습니다.
* 테스트 중에는 `EnablePathMarker`를 켜 두는 것이 확인에 도움이 됩니다.

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

{% content-ref url="/pages/iYYwRA7LVMPQ75P1DqUW" %}
[SimulationBall](/korean/manual/studio-manual/object/simulationball.md)
{% endcontent-ref %}

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.overdare.com/korean/manual/studio-manual/object/simulationball.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
