8pm STUDIOS ๐ฟ

ํ๋ช
: 8pm
ํ์: ๊นํ์ฐ, ๊น์ง์ค, ์ ์ฒ ํธ
๊ฐ๋ฐ ํ๊ฒฝ: Unity 2020.1 URP, Visual Studio, GitLab
์ ์ ๊ธฐ๊ฐ: 2020.12.28 ~ 2021.02.08
YouTube: ์ํ โ์์ ์จโ์ โ์ด๊ฑฐ ๋ฐฉํ ์ ๋ฆฌ์ผโ ์ดฌ์
Index
ํ๋ก์ ํธ ์๊ฐ
8pm STUDIOS๋ ๋ฐฐ์ฐ, ์๊ฐ, ์ฐ์ถ, ์ดฌ์๊ฐ๋ ์ ์ํ ๊ฐ์ ์ํ ์ดฌ์ ํ๋ซํผ์ด๋ค.
๋ชฉ์
๊ฒฝ์ ์ , ๋ฌผ๋ฆฌ์ ์ ์ฝ์ผ๋ก ์ธํด ์์ ์ ์ํ์ ๋ง๋ค๊ธฐ ์ด๋ ค์ด ์ํ์ธ๊ณผ ์ํ์ธ ์ง๋ง์๋ค์ด VR ์์์ ์์ ๋กญ๊ฒ ๋ฐฐ๊ฒฝ ๋ฐ ์ํ์ ์ฌ์ฉํ๊ณ ์๋ก ๋คํธ์ํนํ์ฌ ์ํ์ ์ดฌ์ํ ์ ์๋ค.
๋์ฆ
- ๋ค์ํ ์ํ์ ํ๊ฒฝ ์์์ ์ฐ๊ธฐํ๊ณ ์ถ์ ๋ฐฐ์ฐ
- ์ค๋ฆฌ์ง๋ ์ปจํ ์ธ ๋ฅผ ์ ๋ณด์ด๊ณ ์ถ์ ๊ฐ๋ ๋ฐ ์๊ฐ
- ํ๋ฆฌ ๋น์ฃผ์ผ(Pre-Visualization)์ ํตํ ๋์์ ์ฝํฐ ์ ์
- ์ปจํ ์ธ ๋ฅผ ๋ฐ๊ตดํด์ผ ํ๋ ์ ์์ ๋ฑ
๊ธฐ๋ ํจ๊ณผ
- ์ํ ์ ์ ์ง์ ์ฅ๋ฒฝ์ ๋ฎ์ถค
- ์ํ ๋ฐ ์ ๋๋ฉ์ด์ ์ ์์ ์ํ ๊ฐ์ํ์ค ์ฝํฐ ๋ฑ์ผ๋ก ํ์ฉ์ ๋๋ชจ
- 2์ฐจ ์ฐฝ์์ ํตํด ์ํํฌ๋ค์๊ฒ ์ํ ์ ์ฃผ์ธ๊ณต์ด ๋ ๋ฏํ ์ฒดํ์ ์ ๊ณต
๊ตฌ์กฐ
์ฌ์ฉ์๋ค์ ์ ์ ์ํ์ ์ฃผ์ ๋๋ ํค์๋๋ณ๋ก ๋ฐฉ์ ๋ง๋ค๊ณ ํ์ํ ์ญํ ์ ๊ตฌํ ์ ์๋ค. ํ์ด ๊ตฌ์ฑ๋๋ฉด ๋ฐฐ์ฐ๋ ๊ฐ์ ํ๊ฒฝ ์์์ ์ฐ๊ธฐ๋ฅผ ํ๊ณ ์ฐ์ถ๊ฐ๋ ์ ํ๊ฒฝ ์ธํ ์ ํ๋ฉฐ ์ดฌ์๊ฐ๋ ์ ์ดฌ์์ ์งํํ๋ค. ์ ์ฒด์ ์ธ ํ๋ก์ฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
์ญํ ๋ถ๋ด ๋ฐ ์ํ ์ ์ฐจ
: ํ ๊ตฌ์ฑ์
![]() | ![]() | |
---|---|---|
๊นํ์ฐ | ๊น์ง์ค | ์ ์ฒ ํธ |
์นด๋ฉ๋ผ ๊ธฐ๋ฅ, ์ปจํธ๋กค ๋ฐ์คํฌ | ์ฝํ ์ธ ๊ธฐํ, ์ธ๋ฒคํ ๋ฆฌ, ๋ก๋น | ๋คํธ์ํฌ, ํ๋ ์ด์ด ์ธํฐ๋ ์ |
: ๊ฐ๋ฐ ์ผ์
ํ๋ก์ ํธ ๊ธฐ๋ฅ
1. FilmCamera

๊ฐ์ ์ํ ์ดฌ์ ์ ์ดฌ์๊ฐ๋ ์ด ์ฌ์ฉํ ์นด๋ฉ๋ผ์ด๋ค. ๋ฌผ๋ฆฌ์ ์ธ ์ปจํธ๋กค๋ฌ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ VR ๋ฒํผ๊ณผ ์กฐ์ด์คํฑ ์ธํฐ๋ ์ ์ ํตํด ์นด๋ฉ๋ผ๋ฅผ ์ ์ดํ ์ ์๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์์ ๋ชฐ์ ๊ฐ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ํ๋ฆฌํฐ๋ ๋์ผ ์ ์๋ค. ๋ํ ์นด๋ฉ๋ผ์ ๊ธฐ๋ฅ์ ์ฌ์ฉ์์ ๋์ฆ์ ๋ง๊ฒ ์ค๊ณํ ์ ์๋ค. ๋๋ฌด ๋ง์ ๋ฒํผ์ ํ ๋ฒ์ ์ ๊ณต ํ๋ ๊ฒ ๋ณด๋ค ์ํ๋ ๊ธฐ๋ฅ์ ์ํ๋ ๋ฒํผ์ ์ถ๊ฐํ ์ ์๋๋ก ์ค๊ณํ์๋ค.
Camera Functions
- Primary Button : Record; Start, Stop
- Secondary Button : Record; Cancel
- Menu Button : Control Detail Functions
- Joystick : Zoom; In/Out, On/Off
- Focus : On/Off
- Stabilizer : Always On
- Clapper Board : Auto Update Info
- Preview Screen : Show Camera State
a. ๊ตฌ์กฐ
FilmCamera์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ๋ โDependency Cycleโ์ ํญ์ ์ผ๋์ ๋๊ณ ์ข ์์ฑ์ ๋์ด ์ฃผ๊ธฐ ์ํด ๋ ธ๋ ฅํ๋ค. ๋ฐ๋ผ์ FilmCameraController์์ ์นด๋ฉ๋ผ์ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฐธ์กฐํ๊ณ ์๋ ๊ฒ์ด ์๋๋ผ ์นด๋ฉ๋ผ์ ๊ธฐ๋ฅ๋ค์ด ์ปจํธ๋กค๋ฌ๋ฅผ ์ฐธ์กฐํ๊ณ ๊ฐ ๊ธฐ๋ฅ์ด ์๋ง์ ์ฝ๋ฐฑํจ์์ ๊ตฌ๋ ํ๋ ๋ฐฉ์์ผ๋ก ์ค๊ณํ์๋ค. ์ด๋ ๊ฒ ์ค๊ณํจ์ผ๋ก์จ FilmCameraController๊ฐ ์นด๋ฉ๋ผ ๊ธฐ๋ฅ๋ค๋ก๋ถํฐ ์์ ๋ก์์ง๊ณ ์ํ๋ ๊ธฐ๋ฅ์ ์ฝ๋์ ์์ ์์ด ์ถ๊ฐํ๊ฑฐ๋ ๋บ ์ ์๊ฒ ๋์๋ค.
: Delegate Functions
#region Delegates
public delegate void FilmCamButton();
public delegate void FilmCamJoystick(float val);
public FilmCamButton PrimaryButtonAction;
public FilmCamButton SecondaryButtonAction;
public FilmCamButton MenuButtonAction;
public FilmCamJoystick JoystickAction;
#endregion
: FilmCamera Sequence Diagram
b. CameraZoom

์นด๋ฉ๋ผ ์ค์ FixedLensZoom, SmoothZoom ๋๊ฐ์ง ๋ชจ๋๋ฅผ ์ ๊ณตํ๋ค.
์กฐ์ด์คํฑ์ Z์ถ ๋ฐฉํฅ์ผ๋ก๋ง ์์ง์ผ ์ ์๊ณ ๋น๊ธฐ๋ฉด ์ค์ธ ๋ฐ๋ฉด ์ค์์์ด ๋๋ค.
CameraZoom.cs
#region Subscribed Functions
/// <summary>
/// Perform smooth zoom.
/// </summary>
/// <param name="dir"></param>
private void Zoom(float dir)
{
var fov = _cam.fieldOfView + dir * zoomAmount * Time.deltaTime;
_cam.fieldOfView = Mathf.Clamp(fov,
lensPresets.Lens[lensPresets.Lens.Length - 1].FoV,
lensPresets.Lens[0].FoV);
}
/// <summary>
/// Perform fixed lens zoom.
/// </summary>
/// <param name="dir"></param>
private void FixedLens(float dir)
{
var fov = _cam.fieldOfView + dir * zoomAmount * Time.deltaTime;
var fovClamp = Mathf.Clamp(fov,
lensPresets.Lens[lensPresets.Lens.Length - 1].FoV,
lensPresets.Lens[0].FoV);
var index = lensPresets.GetLensIndex(fovClamp);
_cam.fieldOfView = lensPresets.Lens[index].FoV;
}
#endregion
๋ ํจ์ ๋ชจ๋ VR ์กฐ์ด์คํฑ์ผ๋ก๋ถํฐ ๋ฐ์ ๋ฐฉํฅ๊ฐ์ผ๋ก ์๋ก์ด fov ๊ฐ์ ๊ณ์ฐํ์ฌ ์นด๋ฉ๋ผ .fieldOfView
์ ๋ฃ์ด์ค๋ค.
FixedLensZoom๊ฐ์ ๊ฒฝ์ฐ โScriptable Objectโ์ธ lensPreset์ ๋ฏธ๋ฆฌ ์ ์๋ fov๊ฐ๊ณผ ๋น๊ตํ์ฌ ๊ฐ์ฅ ๊ฐ์ด ๋น์ทํ ๋ ์ฆ์ fov๋ฅผ ๋ฐํํ๋ ํํ์ด๋ค.
LensPresets.cs
#region Initialize
/// <summary>
/// This function should be called on Start.
/// </summary>
public void CreateDefaultLens()
{
if (!_isInitialized)
{
List<LensPreset> defaults = new List<LensPreset>();
defaults.Add(new LensPreset() { Name = "21mm", FoV = 60f });
defaults.Add(new LensPreset() { Name = "35mm", FoV = 38f });
defaults.Add(new LensPreset() { Name = "58mm", FoV = 23f });
defaults.Add(new LensPreset() { Name = "80mm", FoV = 17f });
defaults.Add(new LensPreset() { Name = "125mm", FoV = 10f });
_lens = defaults.ToArray();
_isInitialized = true;
}
}
#endregion
๊ณ์ฐ๋ ๊ฐ์ด _threshold
๋ณด๋ค ๋ฎ๋ค๋ฉด ๋ ์ฆ๋ฅผ ์ฐพ์๋ค๊ณ ํ๋จํ์ฌ ํด๋น ์ธ๋ฑ์ค๋ฅผ ๋ฐํํ๋ค.
LensPresets.cs
/// <summary>
/// Get the index of the lens preset that matches the fov value.
/// </summary>
/// <param name="fov"></param>
/// <returns></returns>
public int GetLensIndex(float fov)
{
for (int i = 0; i < _lens.Length; ++i)
{
if (Mathf.Abs(_lens[i].FoV) - Mathf.Abs(fov) <= _threshold)
return i;
}
return -1;
}
c. CameraFocus

ํฌ์ปค์ค ๊ธฐ๋ฅ์ ์ค์์ ์์ ๋ฐ๋ผ๋ณด๊ณ ์๋ ๋ฌผ์ฒด์ ์๋์ผ๋ก ์ด์ ์ ๋ง์ถ๋ค. ํฌ์ปค์ค ๋ชจ๋์๋ ๋งค๋ด์ผ ํฌ์ปค์ค ๋ชจ๋์ ์คํ ํฌ์ปค์ค ๋ชจ๋๊ฐ ์๋ค.
autoFocus ๊ธฐ๋ฅ์ ๋ฉ๋ด๋ฅผ ํตํด ์ผ๊ฑฐ๋ ๋ ์ ์๋ค.
CameraFocus.cs
#region Subscribed Functions
/// <summary>
/// ๋งค๋ด์ผ ํฌ์ปค์ค, ์คํ ํฌ์ปค์ค ๋ชจ๋ ๊ฐ์ ํจ์๋ก ์คํ๋๋ค.
/// </summary>
private void Focus()
{
StartCoroutine(Focusing());
}
private IEnumerator Focusing()
{
if (_isCoroutineRunning) yield break;
_isCoroutineRunning = true;
var ray = new Ray(_cam.transform.position, _cam.transform.forward);
if (Physics.SphereCast(ray, rayRadius, out var hit, rayLength))
{
var hitDst = Vector3.Distance(_cam.transform.position, hit.point);
var elapsedTime = 0f;
while (elapsedTime <= focusTime)
{
var lerpVal = Mathf.Lerp(_dof.focusDistance.value, hitDst,
focusAc.Evaluate(elapsedTime / focusTime));
_dof.focusDistance.value = lerpVal;
elapsedTime += Time.fixedDeltaTime;
yield return null;
}
_dof.focusDistance.value = hitDst;
}
_isCoroutineRunning = false;
}
ํฌ์ปค์ค ๊ธฐ๋ฅ์ .SphereCast
๋ฅผ ์ด์ ๋ง์ถ ๋์๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ _dof์ .focusDistance.value
์ ๋ฃ์ด์ค๋ค. ๋ถ๋๋ฝ๊ฒ ์ ํ์ ์ฃผ๊ธฐ ์ํด Mathf.Lerp
์ AnimationCurve๋ฅผ ์ฌ์ฉํ์๋ค.
d. CameraStabilizer

VR ์ปจํธ๋กค๋ฌ๋ ์ฌ์ฉ์์ ์์ง์์ ์ ํํ๊ฒ ๋ฐ๋ผ๊ฐ์ผ ํ๋ฏ๋ก ์์ ์์ง์๋ ๋ค ํ์ ํ๋ค. ํ์ง๋ง ์ฌ์ฉ์ ์ปจํธ๋กค๋ฌ์ ๋ฏผ๊ฐ๋๋ ์นด๋ฉ๋ผ๋ฅผ ๋ค๊ณ ์ดฌ์ ์ ์น๋ช ์ ์ผ ์ ์๋ค. CameraStabilizer๋ ์ด๋ฌํ ์ ๋จ๋ฆผ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค๋ค.
CameraStabilizer.cs
[Header("Camera"), SerializeField]
private Transform filmCam = null;
[SerializeField]
private Transform camAnchor = null;
[Header("Stabilizer"), SerializeField]
private float positionStabilizeValue = 0.6f;
[SerializeField]
private float rotationStabilizeValue = 0.6f;
void Update()
{
var positionOffset = Vector3.Distance(camAnchor.position, filmCam.position) + 1;
filmCam.position = Vector3.Lerp(filmCam.position, camAnchor.position,
positionOffset * Time.deltaTime * positionStabilizeValue * 10);
var rotationOffset = Quaternion.Angle(camAnchor.rotation, filmCam.rotation);
filmCam.rotation = Quaternion.Lerp(filmCam.rotation, camAnchor.rotation,
rotationOffset * Time.deltaTime * rotationStabilizeValue / 1.5f);
}
โUnity Cameraโ์ ํฌ์ง์
๊ฐ๊ณผ ๋กํ
์ด์
๊ฐ์ ์ค์ ํ camAnchor
๋ก ํฌ์ง์
์ ๊ฑฐ๋ฆฌ์ ๋ฐ๋ผ, ๋กํ
์ด์
์ ๊ฐ๋์ ๋ฐ๋ผ Lerpํ ๊ฐ์ด ์ ์ฉ๋๋ค.
e. ClapperBoard & Preview Screen

ClapperBoard๋ ์์ ํธ์ง์์๊ฒ ์นด๋ฉ๋ผ์ PreviewScreen์ ์ดฌ์ ๊ฐ๋ ์๊ฒ ์์ฃผ ์ค์ํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ค.
๋ นํ๊ฐ ์์๋๋ฉด ClapperBoard๊ฐ ์์ ์ ๋ณด์ ํจ๊ป ์์์ ํ๋ฉด์ ํ์๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. PreviewScreen์๋ ๋ ์ฆ ์ ๋ณด, ์ดฌ์ ๋ ์ง/์๊ฐ, ํ ์ดํฌ ์ ๋ณด, ๊ทธ๋ฆฌ๊ณ ๋ นํ ์ํ๋ฅผ ํ์ํ๋ค.
ClapperBoardController.cs
#region Subscribed Functions
/// <summary>
/// ๋
นํ ์์์ ํ๋ฒ ํธ์ถ๋๊ณ ๊ธฐ๋ก ๋ด์ฉ์ ์
๋ฐ์ดํธํ๋ค.
/// </summary>
private void Clap()
{
if (!_canClap) return;
gameObject.SetActive(true);
studioInfo.text = studioPreset.Studio;
productionInfo.text = studioPreset.Production;
directorInfo.text = studioPreset.Director;
dateInfo.text = DateTime.UtcNow.ToLocalTime().ToString("yyyy.MM.dd");
sceneInfo.text = studioPreset.Scene;
takeInfo.text = studioPreset.TakeCount.ToString();
Invoke("Action", delayTime);
}
private void Action() => _animator.SetTrigger("action");
ClapperBoard ์ ๋ณด ๋๋ถ๋ถ์ studioPreset
์ด๋ผ๋ Scriptable Object์ ๊ธฐ๋ก๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค. ๋
นํ๊ฐ ์์๋๋ฉด ์์ ๋๋ ์ด ์ดํ์ ์ฌ๋ ์ดํธ๋ฅผ ์น๊ฒ ๋๋ค.
2. ControlDeskSystem

ControlDeskSystem๋ ์ดฌ์๊ฐ๋ ์ด ํ๊ณณ์์ ์ฌ๋ฌ ๋ ์นด๋ฉ๋ผ๋ฅผ ํ ๋ฒ์ ์ ์ดํ๊ฑฐ๋ ์ฐ์ ์์์ ํ์ธํ๋ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉ๋๋ค. FilmCamera์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋๋ฌด ๋ง์ ๋ฒํผ์ ํ๊บผ๋ฒ์ ์ ๊ณตํ๋ฉด ์ฌ์ฉ๊ฐ ํ๊ฐ๋ ค ํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋ ๋จ์ด์ง ์ ์๊ธฐ ๋๋ฌธ์ ๊ฐ์ ๋ฒํผ์ ๋ชจ๋ ์ค์์น๋ฅผ ํตํด ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ํํ๊ฒ๋ ๊ตฌํํ์๋ค.
ControlDeskSystem๋ ๋๊ฐ์ง ๋ชจ๋๊ฐ ์๊ณ ๋ชจ๋์ ๋ฐ๋ผ ๊ฐ์ ๋ฒํผ์ด ๋ค๋ฅธ ๊ธฐ๋ฅ์ ํ๊ฒ ๋๋ค.
CameraControlMode
- Mode Switch Button : VideoControlMode๋ก ๋ณ๊ฒฝ
- Previous Button : ์ด์ ์นด๋ฉ๋ผ์ ํ๋ฆฌ๋ทฐ ์คํฌ๋ฆฐ์ ํ๋ฉด์ ๋์ด๋ค
- Next Button : ๋ค์ ์ธ๋ฑ์ค์ ์นด๋ฉ๋ผ ํ๋ฆฌ๋ทฐ ์คํฌ๋ฆฐ์ ํ๋ฉด์ ๋์ด๋ค
- Primary Button : ์ ํ๋ ์นด๋ฉ๋ผ ๋ นํ/์ค์ง
- Secondary Button : ํ์ฌ ์นด๋ฉ๋ผ ์ ํ/ํด์
VideoControlMode
- Mode Switch Button: CameraControlMode๋ก ๋ณ๊ฒฝ
- Previous Button: ์ ์ฅ๋์ด ์๋ ์ด์ ์์์ ์ฌ์
- Next Button: ์ ์ฅ๋์ด ์๋ ๋ค์ ์ธ๋ฑ์ค์ ์์์ ์ฌ์
- Primary Button: ์์ ์ฌ์/์ผ์์ ์ง, ์์ ์ญ์ ํ์ธ
- Secondary Button: ์์ ์ญ์ , ์์ ์ญ์ ์ทจ์
a. ๊ตฌ์กฐ
ControlDeskSystem์ ๊ตฌ์กฐ๋ FilmCamera์ ๊ตฌ์กฐ์ ๋น์ทํ๋ค. ControlDeskSystem์ ๊ธฐ๋ฅ์ ๋ชจ๋๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฝ๋ฐฑํจ์์ ๊ตฌ๋ ๋๋ ๊ธฐ๋ฅ์ด ๋ฐ๋๊ฒ ๋๋ค.
: Delegate Funcitons
#region Delegates
public delegate void ModeControlDeskButton(ControlMode mode);
public delegate void ControlDeskButton();
public ModeControlDeskButton ModeSwitchAction = null;
public ControlDeskButton PreviousAction = null;
public ControlDeskButton NextAction = null;
public ControlDeskButton PrimaryAction = null;
public ControlDeskButton SecondaryAction = null;
#endregion
: ControlDeskSystem Sequence Diagram
b. FilmCameraManager
ControlDeskSystem์์ ๋ชจ๋ ๋ณ๊ฒฝ ์ ํธ๊ฐ ๋ค์ด์ค๋ฉด ๊ตฌ๋
๋ OnModeChange()
ํจ์๊ฐ ํธ์ถ๋๊ณ ๋ชจ๋๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฝ๋ฐฑํจ์์ ๊ตฌ๋
ํ๊ฑฐ๋ ์ทจ์ํ๊ฒ ๋๋ค.
FilmCameraManager.cs
private void OnModeChange(ControlMode mode)
{
_currentMode = mode;
if (_currentMode.Equals(ControlMode.CameraViewer))
{
_deskController.PreviousAction += ShowPreviousCam;
_deskController.NextAction += ShowNextCam;
_deskController.PrimaryAction += RecordSelectedCam;
_deskController.SecondaryAction += SelectCam;
}
else
{
_deskController.PreviousAction -= ShowPreviousCam;
_deskController.NextAction -= ShowNextCam;
_deskController.PrimaryAction -= RecordSelectedCam;
_deskController.SecondaryAction -= SelectCam;
}
}
FilmCameraManager๋ ์ฌ๋ฌ ๋์ ์นด๋ฉ๋ผ๋ฅผ ๊ด๋ฆฌํ๋ ์ญํ ์ ๊ฐ์ง๊ณ ์๋ค.

FilmCameraManager.cs
private void ShowScreen(int index)
{
if (_filmCameras.Count > index)
{
var sc = _filmCameras[index].GetComponentInChildren<ScreenController>();
_viewScreen.GetComponent<Renderer>().material.mainTexture = sc.NotRecordingRt;
}
}
NextButton์ด๋ PreviousButton์ด ํธ์ถ๋๋ฉด ๋ค์ ์ธ๋ฑ์ค๋ฅผ ShowScreen()
์ ๋๊ฒจ์ค๋ค. ๋๊ฒจ๋ฐ์ ์ธ๋ฑ์ค์ ํด๋นํ๋ ์นด๋ฉ๋ผ์ RenderTexture๋ฅผ ScreenController๋ก ๋ถ ํฐ ๋ฐ์์จ๋ค. ๋ฐ์์จ RT๋ฅผ ํ๋ฉด ๋ฉํ
๋ฆฌ์ผ์ ๋ฃ์ด์ค๋ค.
c. VideoPlayerManager

VideoPlayerManager๋ ์ฐ์ ์์์ ๋ฐ๋ก ํ์ธํ ์ ์๊ฒ ํด์ค๋ค.
VideoPlayerManager.cs
/// <summary>
/// Update video name list.
/// ์์์ด ์ ์ฅ๋๋ฉด ํธ์ถ๋๋ค.
/// </summary>
public void UpdateVideoList()
{
var fileNames = Directory.GetFiles(CAPTURE_PATH, "*.mp4");
Array.Sort(fileNames);
if (_videoNames == null)
{
_videoNames = new List<string>();
}
else if (_videoNames.Count > 0)
{
_videoNames.Clear();
}
foreach (var name in fileNames)
{
_videoNames.Add(Path.GetFileNameWithoutExtension(name));
}
}
์์ ๋ชฉ๋ก์ ์์ ์ด๋ฆ์ผ๋ก ๊ด๋ฆฌ๋ฅผ ํ๋ค. ์๋ก์ด ์์์ด ์ฐํ๋ฉด UpdateVideoList()
๊ฐ ํธ์ถ๋๊ณ ํญ์ ์ต์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ง๊ฒ ๋๋ค.
VideoPlayerManager.cs
private void PlayPause()
{
if (_videoPlayer.isPlaying)
_videoPlayer.Pause();
else
_videoPlayer.Play();
}
private void PlayNew(int index)
{
if (_videoPlayer.isPlaying)
_videoPlayer.Stop();
if (_videoNames.Count > index)
StartCoroutine(LoadVideo(_videoNames[index]));
}
private IEnumerator LoadVideo(string name)
{
if (_isLoading) yield break;
_isLoading = true;
_videoPlayer.url = CAPTURE_PATH + name + ".mp4";
_isLoading = false;
}
ํ์ฌ ์ฌ์ ์์์ ์ธ๋ฑ์ค๋ก ๊ด๋ฆฌ๋๋ค. ์๋ก์ด ์์์ ๋ถ๋ฌ์ฌ ๋๋ ์ธ๋ฑ์ค์ ํด๋นํ๋ ์์ ์ด๋ฆ์ผ๋ก ์ฐพ๋๋ค.
3. CameraPathCreator

CameraPathCreator๋ก ์จ์ดํฌ์ธํธ๋ฅผ ๋ง๋ค์ด ์์ฝ๊ฒ ์ดฌ์ํ ์ ์๋ค. Anchor Point์ Control Point๋ฅผ VR ์์์ ์ํ๋ ๊ณณ์ ๋ฐฐ์นํ ์ ์๊ณ Cart์ ์นด๋ฉ๋ผ๋ฅผ ์ฅ์ฐฉํ๊ณ ์ถ๋ฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ค์ ํ ์๋๋ก Cart๊ฐ ์์ง์ด๊ฒ ๋๋ค.
a. ๊ตฌ์กฐ
CameraPathCreator๋ Object Pool์์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง Point๋ค์ ๋ฐฐ์นํ๊ณ PathViewer๊ฐ Point๋ค์ ์ด์ด์ Path๋ฅผ ๋ง๋ ๋ค.
: CameraPathCreator Sequence Diagram
b. BezierCurves

BezierCurves.cs
CameraPathCreator์ ํต์ฌ์ธ BezierCurve์ด๋ค. ์ํค์ ๊ณต์์ ์ฐธ๊ณ ํด์ static ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ์๋ค.
public static Vector3 LinearBezierCurves(Vector3 a, Vector3 b, float t)
{
return a + t * (b - a);
}
public static Vector3 QuadraticBezierCurves(Vector3 a, Vector3 b, Vector3 c, float t)
{
return Mathf.Pow(1 - t, 2) * a + 2 * (1 - t) * t * b + Mathf.Pow(t, 2) * c;
}
public static Vector3 CubicBezierCurves(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float t)
{
return Mathf.Pow(1 - t, 3) * a + 3 * Mathf.Pow(1 - t, 2) * t * b +
3 * (1 - t) * Mathf.Pow(t, 2) * c + Mathf.Pow(t, 3) * d;
}
c. PathCreator

PathCreator๋ CameraPathCreator์ ์ค์ฌ์ถ์ผ๋ก Object Pool์ PathViewer ์ฌ์ด์์ ์ปจํธ๋กค ํ๋ ์์ ์ ํ๋ค.
PathCreator.cs
/// <summary>
/// Gets called when an active Path Point is not detected
/// and when the VR button is pressed.
/// </summary>
public void AddPoint()
{
if (!_detector.IsTriggerStay)
{
var point = _pool.ActivatePoint(spawnPoint.position, spawnPoint.rotation);
if (point != null)
{
_points.Add(point);
}
if (_points.Count > 1)
{
_cart.transform.position = _points[0].position;
_cart.SetActive(true);
}
}
}
/// <summary>
/// Gets called when an active Path Point is detected
/// and when the VR button is pressed.
/// </summary>
/// <param name="point"></param>
public void RemovePoint(Transform point)
{
int index = _points.FindIndex(p => p == point);
if (index != -1)
{
_pool.TerminatePoint(_points[index]);
_points.RemoveAt(index);
}
if (_points.Count < 2)
{
_cart.SetActive(false);
}
}
Point์ ์์ฑ์ PointDetector์ Point๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋๊ฒ์ด ์์๋๋ง ๊ฐ๋ฅํ๋ค.
Point์ ์ญ์ ๋ PointDetector๋ก ๋ถํฐ ๊ฑด๋ค ๋ฐ์ Point์ Transform์ ์ธ๋ฑ์ค๋ฅผ ์ฐพ์ ObjectPool์์ .TerminatePoint()
๊ณผ์ ์ ๊ฑฐ์น๋ค.
d. PathPool
PathPool์ Stack์ ์ด์ฉํ๋ค. has-a ๋ฐฉ์์ผ๋ก Object Pool์ ์์ฑํ์ฌ MonoBehaviour์ life cycle์ ํ์ง ์๊ฒ ํ์๋ค.
PathPool.cs
public PathPool(MonoBehaviour mono, GameObject spawnObject, int pathCount)
{
InitVariables(mono, spawnObject, pathCount);
CreatePool();
}
private void CreatePool()
{
_poolRoot = GameObject.Find("PathPool");
if (_poolRoot == null)
{
_poolRoot = new GameObject("PathPool");
}
for (int i = 0; i < _pathCount; i++)
{
var point = MonoBehaviour.Instantiate(_spawnObject, _mono.transform.position,
Quaternion.identity, _poolRoot.transform);
point.SetActive(false);
_pathPool.Push(point);
}
}
e. PathViewer

PathViewer๋ ์ฒ์ ์์ฑ์์์ ๋๊ฒจ๋ฐ์ Point ๋ฆฌ์คํธ ๋ ํผ๋ฐ์ค๋ฅผ ๊ฐ์ง๊ณ LineRenderer๋ฅผ ํตํด ์ ์ ๊ทธ๋ฆฐ๋ค.
PathViewer.cs
/* Cubic Bezier Curve */
case var expression when points > 3:
t = 0f;
for (int i = 0; i < SegmentCount(); i++)
{
var cp1 = _points[i * 3 + 1].GetComponent<Renderer>();
var cp2 = _points[i * 3 + 2].GetComponent<Renderer>();
cp1.material = _controlPointMat;
cp2.material = _controlPointMat;
Vector3[] segment = GetPointsInSegment(i);
for (int j = 0; j < _lr.positionCount; j++)
{
_lr.SetPosition(j, BezierCurves.CubicBezierCurves(
segment[0], segment[1], segment[2], segment[3], t));
t += (1 / (float)_lr.positionCount);
}
}
break;
.GetPointsInSegment()
๋ Point๋ฅผ 3๊ฐ์ฉ ๊ฒน์น๋๋ก ๋ฐํ ํ๋ค.