0%

Unity

관람객이 시작 버튼을 누르면 로봇팔이 준비를 마치고, 2분 동안 사람과 로봇이 **진짜 에어하키 경기**를 벌인다면 어떨까요?

전*원··수정됨 2026.07.21

관람객이 시작 버튼을 누르면  로봇팔이 준비를 마치고, 2분 동안 사람과 로봇이 **진짜 에어하키 경기**를 벌인다면 어떨까요?

점수가 날 때마다 사용자 쪽은 30초, 로봇 쪽은 10초의 쿨다운이 걸려 센서 노이즈가 중복 득점을 만들지 않고, 마지막 골이 언제 터졌는지에 따라 종료 후 정리 시간까지 **동적으로 계산**된다면요. 이번 포스팅에서는 Unity를 UI 허브로, 별도 PC의 로봇 제어 프로그램을 UDP로, 시리얼 보드를 통한 센서·에어 제어를 단일 상태 머신에 통합한 인천 학생과학관 '에어하키' 콘텐츠의 기술적인 구현 과정을 공유합니다.

아키텍처_01_전체구조.png

1. 시스템 아키텍처 개요

본 프로젝트는 크게 세 가지 단계로 구성됩니다.

Stopwatch 기반 상태 머신: Idle → Waiting(25초) → Playing(120초) → Ended(동적 대기) 네 단계를 System.Diagnostics.Stopwatch로 관리하여 Time.deltaTime 누적 오차를 제거

이중 출력 제어: 로봇팔 제어는 UDP(127.0.0.1:5555)로 별도 프로그램에 ASCII 명령('1'/'2'/'3')을 전달, 에어 테이블 제어는 시리얼[0xFA, 0x31/0x30])로 동일 보드에 송신

비트 다중화 수신: 시리얼 보드로부터 한 바이트 안에 **시작 버튼·유저 골·로봇 골 3개 신호를 비트 단위로 다중화**한 패킷을 받아 쿨다운 기반으로 처리

이 모든 과정은 Unity 2022 LTS의 단일 씬에서 GameManagerRobotManagerSerialManager 세 싱글톤이 이벤트와 코루틴으로 협업하며 동작합니다.

2. 주요 기술적 구현

2.1 Stopwatch 기반 정밀 상태 머신 (GameManager_AirHockey.cs)

게임 타이머는 전통적으로 Time.deltaTime을 누적하지만, 프레임 드롭이 누적되면 2분 게임이 2분 1초 또는 1분 59초로 어긋날 수 있습니다. GameManager는 대기·게임·유저 쿨다운·로봇 쿨다운·시작 쿨다운까지 총 다섯 개의 Stopwatch를 독립적으로 운영하여 운영체제 기준 실제 경과 시간을 측정하고, 매 프레임 UpdateWaitTimer() / UpdateGameTimer()가 해당 Stopwatch의 Elapsed.TotalSeconds를 읽어 UI를 갱신합니다. 특히 종료 후 복귀 시간은 waitTime = max(0, ROBOT_PUCK_RETRIEVAL_TIME - lastGoalRemaining) + ROBOT_SHUTDOWN_TIME 공식으로 **마지막 골 시점에 따라 동적 계산**되어, 경기 종료 직전 골이 터져도 로봇팔이 퍽을 회수하고 정리할 시간을 확보합니다.

2.2 한 바이트 3비트 다중화 센서 프로토콜 (SerialManager_AirHockey.cs)

시리얼 대역폭과 레이턴시를 최소화하기 위해 *[0xFA, 상태바이트] 단 2바이트**로 세 개의 독립 센서 신호를 전달하도록 프로토콜을 설계했습니다. 상태바이트는 bit0 = 시작 버튼, bit1 = 유저 골, bit2 = 로봇 골로 할당되어, ProcessReceivedBuffer()가 비트 마스크로 한 번에 분해하여 각각의 userScore / robotScore / startButton 디버그 필드에 반영합니다. 이를 GameManager.ProcessSerialInput()이 매 프레임 폴링하여 prevBit 와 비교한 **0→1 에지 감지**로 골 이벤트를 잡아내므로, 센서 비트가 1로 고정되어도 중복 발화 없이 단 한 번만 처리됩니다.

2.3 자동 재연결 이벤트 + 쿨다운 센서 리셋 (SerialManager + RobotManager 협업)

시리얼 USB가 순간적으로 빠졌다가 다시 꽂히는 상황에 대비해 SerialManager.SerialConnectionTask가 2초 간격 재시도 루프로 포트를 재확보하고, 성공 시 OnReconnected 이벤트를 발화합니다. RobotManager는 이 이벤트를 구독하여 **게임 진행 중Playing 상태)이면 즉시 [0xFA, 0x31] 에어 ON 명령을 재전송**해 게임이 끊김 없이 복구되도록 처리했습니다. 추가로 GameManager가 골 처리 직후 SerialManager.ResetButtonState()를 호출해 해당 비트를 0으로 강제 리셋하는 안전장치를 두어, 보드 펌웨어가 비트를 계속 1로 유지하는 경우에도 다음 골을 놓치지 않도록 보완했습니다.

3. 사용자 경험(UX) 최적화

비대칭 쿨다운 설계 : 사용자 골 30초 / 로봇 골 10초의 **비대칭 쿨다운**은 사용자 골 후 로봇팔이 퍽을 회수하는 긴 준비 시간을 자연스럽게 게임 플로우에 녹여낸 선택으로, 게임이 끊기는 느낌 없이 매끄럽게 이어지도록 했습니다.

외부 파일 기반 현장 튜닝 : 에어 OFF 유지 시간stopTime)을 **운영 PC 바탕화면의 Info.txt** 에서 StopTime=24 형식으로 읽어 와, 사이트별 로봇팔 속도 차이에 맞춰 재빌드 없이 텍스트 파일만 수정하면 되도록 설계했습니다.

110초 시점 에어 재송신 : 보드 측 타이머 만료로 경기 막판에 에어가 꺼지는 사고를 방지하기 위해 GameProcessCoroutine이 110초 시점에 [0xFA, 0x31]을 한 번 더 보내 마지막 10초 구간의 퍽 흐름을 보장했습니다.

상태별 차별 팝업 : EPOPUPTYPE enum(Wait / Win / Lose / None)으로 경기 단계마다 시각적 피드백을 명확히 분리해, 관람객이 대기 시간·승패 결과·로봇 정리 대기를 헷갈리지 않고 인지할 수 있도록 했습니다.

4. 마치며

이 프로젝트는 Unity 앱·로봇 제어 프로그램·시리얼 보드라는 **세 개의 비동기 시스템**을 단일한 경기 흐름 아래 맞물리게 하는 작업이었습니다. Stopwatch 기반 정밀 타이머, 비트 다중화 프로토콜, 재연결 이벤트 체인 같은 요소는 기술적으로도 흥미로웠지만, 관람객에게는 "버튼 한 번만 누르면 25초 뒤 로봇이 살아나 나와 진짜 경기를 한다"는 몰입감을 흔들림 없이 제공했다는 점에서 큰 의미가 있었습니다.

향후에는 경기 중 **골 리플레이 영상 저장·재생** 기능을 추가하고, 승률·최다 골·최단 경기 시간 같은 **세션 통계 대시보드**를 보조 모니터에 띄워 체험 이후의 여운까지 디자인하며, 현재 외부 파일로 운영되는 타이밍 상수들을 **인게임 운영자 UI**로 승격시켜 관리 편의성을 한층 끌어올릴 예정입니다.

읽기 도구

7분 읽기

이 글이 도움이 되었나요?

다음으로 읽기