쓰리디아이 / 2017
낙하산 어트랙션
함께 달리는 경험을 만드는 NPC와 고스트

프로젝트 연도 / 상태2017
개발팀 · 리소스 인력 제외2인 · 팀원
사용 기술Unity3D · VR
OVERVIEW
프로젝트와 나의 역할
경주 NPC의 거리 기반 속도 조절과 CSV 기록 기반 고스트 재생, Kinect 자세 인식을 구현했습니다.
- 플레이어와의 경주용 NPC 구현
- 플레이어 데이터 저장을 통한 플레이어 고스트 NPC 구현
- 키넥트를 이용한 신체 트래킹 기능 구현
CHALLENGE
해결해야 했던 문제
초보 플레이어가 뒤처지면 NPC가 모두 시야 밖으로 사라짐
APPROACH
선택한 해결 방식
거리에 따라 NPC 속도를 조절하고, 기록된 궤적과 현재 플레이어 위치를 비교해 고스트를 주변에 재배치했습니다.
RECONSTRUCTED EXAMPLE
포트폴리오용 재구현 코드
CSV의 시간·위치·회전을 읽어 보간 재생하고, 플레이어와 멀어지면 가까운 기록 지점으로 이동하는 예제입니다.
당시 제품의 원본 소스가 아닌 새로 작성한 예제입니다. Unity 컴파일·실행 검증은 아직 하지 않았습니다.
코드 펼쳐보기
// Portfolio reconstruction, 2026. Not original production source.
// Based on the Notion parachute-attraction CSV ghost replay description.
using System;
using System.Globalization;
using System.Collections.Generic;
using UnityEngine;
public sealed class GhostReplay : MonoBehaviour
{
[Serializable] public struct Sample
{
public float time;
public Vector3 position;
public Quaternion rotation;
}
[SerializeField] TextAsset csv; // time,x,y,z,qx,qy,qz,qw; header required
[SerializeField] Transform player;
[SerializeField, Min(1)] float catchUpDistance = 40;
[SerializeField, Min(0)] float speed = 1;
readonly List<Sample> samples = new List<Sample>();
float clock;
int cursor;
void Start()
{
try { Load(csv == null ? null : csv.text); }
catch (Exception error) { Debug.LogError(error.Message, this); enabled = false; }
}
public void Load(string text)
{
if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("CSV is empty.");
var parsed = new List<Sample>();
var rows = text.Split('\n');
for (int i = 1; i < rows.Length; i++)
{
if (string.IsNullOrWhiteSpace(rows[i])) continue;
var cells = rows[i].Trim().Split(',');
if (cells.Length != 8) throw new FormatException("Expected 8 columns at row " + (i + 1));
float[] v = new float[8];
for (int j = 0; j < v.Length; j++)
if (!float.TryParse(cells[j], NumberStyles.Float, CultureInfo.InvariantCulture, out v[j])
|| float.IsNaN(v[j]) || float.IsInfinity(v[j]))
throw new FormatException("Invalid finite number at row " + (i + 1));
if (v[0] < 0 || (parsed.Count > 0 && v[0] <= parsed[parsed.Count - 1].time))
throw new FormatException("Timestamps must increase strictly.");
var q = new Quaternion(v[4], v[5], v[6], v[7]);
if (Quaternion.Dot(q, q) < 0.000001f) throw new FormatException("Zero quaternion.");
parsed.Add(new Sample { time = v[0], position = new Vector3(v[1], v[2], v[3]), rotation = q.normalized });
}
if (parsed.Count < 2) throw new FormatException("At least two samples are required.");
samples.Clear(); samples.AddRange(parsed); cursor = 0; clock = samples[0].time;
Apply();
}
void Update()
{
if (samples.Count < 2) return;
clock = Mathf.Min(clock + Time.deltaTime * Mathf.Max(0, speed), samples[samples.Count - 1].time);
Apply();
if (player != null && (transform.position - player.position).sqrMagnitude > catchUpDistance * catchUpDistance)
SeekNearest(player.position);
}
public void SeekNearest(Vector3 position)
{
if (samples.Count == 0) return;
int nearest = 0; float best = float.PositiveInfinity;
for (int i = 0; i < samples.Count; i++)
{
float distance = (samples[i].position - position).sqrMagnitude;
if (distance < best) { best = distance; nearest = i; }
}
clock = samples[nearest].time; cursor = Mathf.Min(nearest, samples.Count - 2); Apply();
}
void Apply()
{
while (cursor < samples.Count - 2 && samples[cursor + 1].time < clock) cursor++;
Sample a = samples[cursor], b = samples[cursor + 1];
float t = Mathf.InverseLerp(a.time, b.time, clock);
transform.SetPositionAndRotation(Vector3.Lerp(a.position, b.position, t), Quaternion.Slerp(a.rotation, b.rotation, t));
}
}
PROJECT MEDIA
실행 화면과 시연
기존 프로젝트 상세 설명 보기
낙하산 어트랙션 3D/VR 영상 개발
낙하산 시뮬레이터를 민간 체험용으로 응용한 3D/VR 어트랙션으로, 낙하산 동역학 기반 1인 체험과 NPC 경주, CSV 플레이 데이터 저장·재생을 통한 실측 기반 레이싱 기능을 포함합니다.
프로젝트 설명을 위한 이미지 및 영상 Youtube
### 01. 프로젝트 구성 (리소스 개발 인력 제외)
팀 구성 : 2인 (팀원으로 참여)
### 02. 프로젝트 내 본인 작업 내용
1. Unity 관련
1. 플레이어와의 경주용 NPC 구현
2. 플레이어 데이터 저장을 통한 플레이어 고스트 NPC 구현
3. 키넥트를 이용한 신체 트래킹 기능 구현
### 03. 담당 기능 개발
# Unity 관련
1. 플레이어와의 경주용 NPC 구현
1. 플레이어와의 상호 경쟁을 통한 스포츠 느낌이 느껴질 수 있도록 NPC 구현
- 개발 방안 : FSM NPC 구현
- 이슈 및 문제점 : 실제 플레이어가 조종 미숙으로 인해 제대로 조종을 못하게 될 시 NPC들이 전부 앞서 나가 플레이어가 홀로 남게 되는 문제가 발생 (민수용 콘텐츠에서 즐거움을 떨어트리는 요소)
- 해결방안 : NPC가 플레이어와 일정 거리이상 벗어나게 되면 속도 조정 및 너무 먼 NPC의 경우 재 생성
- 해결 가능 이유 : 해당 콘텐츠는 NPC와의 경주를 통해 1등이 되는 것이 목적이 아닌 다른 NPC와 함께 맵을 완주한다는 느낌의 콘텐츠 이므로 너무 멀리 떨어진 NPC을 재 생성 시켜 플레이어 주변에 보이게 하는 것이 효과적
2. 플레이어 데이터 저장을 통한 플레이어 고스트 NPC 구현
1. 딱딱한 움직임의 NPC이외의 고스트 플레이어를 통한 자연스러운 콘텐츠 개발
개발 방안 : 한번 플레이된 플레이어 정보를 CSV 파일로 저장하여 고스트 플레이어 NPC 구현
이슈 및 문제점 : FSM NPC와 마찬가지로 실제 플레이어의 조종 미숙으로 인해 제대로 조종이 안될 시 고스트 플레이어들도 전부 앞서 나가 플레이어가 여전히 홀로 남게 되는 문제가 발생
해결방안 : 현재 플레이어의 위치 기반 CSV 파일데이터를 통해 고스트 플레이어를 플레이어 주변 위치로 재생성
해결 가능 이유 : CSV 파일에는 고프트 플레이어의 플레이 궤적 정보가 남아 있으므로 현재 플레이어와의 위치 비교를 통해 위치 재생성 가능
3. 키넥트를 이용한 신체 트래킹 기능 구현
1. 사용자의 자세 인식을 통한 스카이 다이빙 체험을 위한 위치 인식 시스템 개발
- 개발 방안 : 키넥트를 이용한 외부 센서 없이 신체 트래킹
- 이슈 및 문제점 : 플레이어의 하체는 키넥트의 화각에 따라 트래킹이 되지 않는 문제가 발생
- 해결방안 : 플레이어의 하체는 트래킹을 포기하고, 동역학 정보에서도 하체 정보를 제외하여 동역학 진행
- 해결 가능 이유 : 민수용이고 스카이다이빙을 체험해 보지 않은 일반인을 대상으로 하는 콘텐츠 이므로 전문가 급의 정확한 자세는 필요치 않음

