출석 모니터링 시스템 - ZEP.US 위젯
ZEP.US 메타버스 플랫폼 내장형 관리 도구 - Gather.town 마이그레이션 프로젝트
📋 프로젝트 개요
Gather.town에서 ZEP.US로 메타버스 플랫폼을 전환하면서, 기존 출석 모니터링 시스템과 연동되는 메타버스 내장형 관리 위젯을 개발한 프로젝트입니다.
시스템 역할
- 학생 도구: 메타버스 내에서 운영진 문의, 외출 신청, 메시지 확인
- 관리자 도구: 실시간 학생 관리, 소환, 타이틀 설정, 상태 제어
- 모니터링: 채팅 로그, 접속 현황 실시간 추적
- 데이터 연동: egatten 대시보드 및 백엔드 API와 양방향 통신
전체 시스템에서의 위치
┌─────────────────────────────────────────┐
│ 출석 모니터링 시스템 (전체) │
├─────────────────────────────────────────┤
│ 프론트엔드 │
│ ├─ egatten (웹 대시보드) │
│ │ └─ 관리자가 브라우저로 접속 │
│ │ │
│ └─ ZEP.US 위젯 (본 시스템) ⭐ │
│ ├─ 메타버스 내부 도구 │
│ ├─ 학생/관리자 모두 사용 │
│ ├─ 데이터 송수신 (egatten ↔ 백엔드) │
│ └─ ZEP.US 자체 제어 │
│ │
│ 백엔드 │
│ ├─ 출석 수집 시스템 (Google Sheets) │
│ └─ LMS 웹훅 (LMS 이벤트) │
└─────────────────────────────────────────┘
🎯 해결한 문제
마이그레이션 배경
- 비용 문제: Gather.town의 비용 증가
- 성능 문제: 해외 서버 레이턴시
- API 부재: ZEP.US는 Gather.town과 달리 공식 모니터링 API 부족
기술적 과제
- 플랫폼 제약: ZEP Script는 ES5만 지원 (ES6+ 문법 불가)
- 통신 구조: 메타버스 ↔ 웹뷰 ↔ 백엔드 3-tier 통신 필요
- 권한 관리: 학생/운영진 역할별 기능 분리
- 실시간성: 메타버스 내 실시간 이벤트 처리
솔루션
- Hybrid Architecture: ZEP Script + HTML Widget 결합
- PostMessage 통신: 웹뷰 ↔ 스크립트 간 양방향 메시징
- Role 기반 제어:
player.role로 권한 분리 (2000: 스태프, 3000: 관리자) - 주기적 동기화: 5분마다 자동으로 플레이어 상태 백엔드 전송
🛠 기술 스택
Platform
- ZEP.US: 메타버스 플랫폼
- ZEP Script: 서버 사이드 스크립트 (JavaScript ES5 Strict Mode)
Frontend (Widget)
- HTML5: 위젯 UI
- CSS3: 스타일링
- JavaScript ES5: 클라이언트 로직
기술적 제약사항
- ES5 Only:
const,let,=>,async/await,Class사용 불가 - Only
varandfunction: 모든 변수는var, 함수는function키워드만 가능 - 유니코드 제한: 서로게이트 페어 이모지 처리 어려움
External APIs
- Backend Server:
egatten.yos.kr(Python/Flask 추정) - Communication: RESTful API (JSON)
- Auth:
X-API-Key헤더 인증
💡 주요 구현 내용
1. 권한 기반 위젯 라우팅
// main.js - 사이드바 클릭 시
App.onSidebarTouched.Add(function(player) {
if (player.role >= ADMIN_ROLE_THRESHOLD) { // 2000
// 관리자: 대시보드 로드
var adminWidget = player.showWidget(
'admin_dashboard.html',
0, 0, 800, 600
);
setupAdminPageMessages(adminWidget, player);
} else {
// 학생: 문의하기 메뉴
var userWidget = player.showWidget(
'user_selector.html',
0, 0, 500, 400
);
setupUserSelectorWidgetMessages(userWidget, player);
}
});2. Hybrid Widget Communication
Widget → Script:
// HTML (Widget)
window.parent.postMessage({
type: 'summonStudent',
studentName: '홍길동'
}, '*');Script → Widget:
// main.js
widget.onMessage.Add(function(player, data) {
if (data.type === 'summonStudent') {
// 학생 소환 로직
var targetPlayer = App.players.find(/* ... */);
targetPlayer.teleport(player.tileX, player.tileY);
// 응답 전송
widget.sendMessage({
type: 'summonResponse',
success: true
});
}
});3. 학생 관리 기능
소환 (Summon)
function summonStudent(targetPlayer, adminPlayer) {
targetPlayer.teleport(
adminPlayer.tileX,
adminPlayer.tileY
);
}타이틀 설정
function setPlayerTitle(player, title) {
player.title = title;
player.sendUpdated();
}상태 제어
// 이동 속도 변경 (0~5배)
player.moveSpeed = 3;
// 유령 모드 (투명화)
player.isGhost = true;
player.sendUpdated();4. 실시간 모니터링
주기적 플레이어 정보 전송
var periodicTimer = null;
App.onStart.Add(function() {
// 5분(300초)마다 실행
periodicTimer = App.runRepeatedly(function() {
sendPlayersToServer();
}, 300);
});
function sendPlayersToServer() {
var playersData = App.players.map(function(p) {
return {
id: p.id,
name: p.name,
x: p.tileX,
y: p.tileY,
isAfk: p.isAFK
};
});
App.httpPostJson(
BASE_URL + '/api/gather/save_players',
{ players: playersData }
);
}채팅 로그 수집
App.onSay.Add(function(player, text) {
// 이모지만 있는 메시지는 제외
if (!isEmojiOnly(text)) {
App.httpPostJson(
BASE_URL + '/api/gather/save_chat',
{
player_id: player.id,
name: player.name,
message: text,
timestamp: new Date().toISOString()
}
);
}
});5. ES5 유니코드 이슈 해결
ZEP Script는 ES5만 지원하여 최신 유니코드 이모지 처리가 어려움. 서로게이트 페어 처리 함수 직접 구현:
function getCodePoint(str, index) {
var code = str.charCodeAt(index);
// High Surrogate (0xD800~0xDBFF)
if (code >= 0xD800 && code <= 0xDBFF) {
var high = code;
var low = str.charCodeAt(index + 1);
if (low >= 0xDC00 && low <= 0xDFFF) {
// Surrogate Pair 계산
return (high - 0xD800) * 0x400 +
(low - 0xDC00) + 0x10000;
}
}
return code;
}
function isEmojiOnly(text) {
// 이모지만 있는 메시지 필터링
// (채팅 로그 저장 시 제외)
}🏗 시스템 아키텍처
3-Tier Communication
┌──────────────────────────────────┐
│ ZEP.US Client (Browser) │
│ ┌────────────────────┐ │
│ │ Widget (HTML) │ │
│ │ - user_selector │ │
│ │ - admin_dashboard │ │
│ │ - monitoring │ │
│ └────────────────────┘ │
│ ↕ │
│ postMessage API │
│ ↕ │
│ ┌────────────────────┐ │
│ │ main.js │ │
│ │ (ZEP Script) │ │
│ └────────────────────┘ │
└──────────────────────────────────┘
↕
App.httpPostJson
↕
┌──────────────────────────────────┐
│ Backend API │
│ (egatten.yos.kr) │
│ ├─ /api/gather/save_players │
│ ├─ /api/gather/save_chat │
│ ├─ /api/support-request │
│ ├─ /api/message-queue/* │
│ └─ /api/question_records │
└──────────────────────────────────┘
↕
┌──────────────────────────────────┐
│ Database (MySQL) │
└──────────────────────────────────┘
데이터 흐름
[학생/관리자]
↓ (메타버스 접속)
[ZEP.US 위젯]
↓ (문의, 출석 등)
[main.js - ZEP Script]
↓ (httpPostJson)
[Backend API]
↓ (저장)
[Database]
↓ (조회)
[egatten 웹 대시보드] ← 관리자가 확인
📊 주요 기능
학생 기능 (user_selector.html)
운영진 문의
- 빠른 문의:
- “잠시 자리 비움”
- “외출 시작(QR)” (사전 협의 확인 팝업)
- “외출 복귀(QR)”
- 자유 문의: 텍스트 입력 후 전송
메시지함 (message_inbox.html)
- 운영진이 보낸 알림(
alert) 확인 - 질문(
prompt)에 답변 - 미확인 메시지 자동 알림
관리자 기능
대시보드 (admin_dashboard.html)
- 실시간 접속자 수
- AFK(잠수) 유저 수
- 각 관리 메뉴 네비게이션
학생 관리 (admin_management.html)
| 기능 | 설명 | 구현 |
|---|---|---|
| 소환 | 학생을 내 위치로 이동 | teleport() |
| 출두 | 내가 학생 위치로 이동 | teleport() |
| 타이틀 설정 | 머리 위 칭호 부여 | player.title |
| CSV 일괄 업로드 | 여러 학생 타이틀 한 번에 설정 | CSV 파싱 |
| 메시지 전송 | 알림/질문 전송 | API 연동 |
| 속도 제어 | 이동 속도 0~5배 조절 | player.moveSpeed |
| 유령 모드 | 투명화 ON/OFF | player.isGhost |
모니터링 (admin_monitoring.html)
- 실시간 채팅 로그 확인
- 접속자 현황 수동/자동 갱신
- 채팅 로그 초기화
문의 기록 관리 (admin_question_records.html)
- 학생 문의 내역 조회 (날짜 필터)
- 문의 처리 (확인 → 반려 처리)
📁 파일 구조
OpenEG_Widget/
├── main.js # [핵심] 2000줄 메인 로직
│ ├─ Event Handlers # onSidebarTouched, onSay 등
│ ├─ Widget Communication # postMessage 핸들러
│ ├─ API Integration # httpPostJson 래퍼
│ └─ Utility Functions # isEmojiOnly, getCodePoint
│
├── user_selector.html # [학생] 문의하기 메뉴
├── message_inbox.html # [학생] 메시지 보관함
│
├── admin_dashboard.html # [관리자] 메인 대시보드
├── admin_management.html # [관리자] 학생 제어
├── admin_monitoring.html # [관리자] 채팅 로그
└── admin_question_records.html # [관리자] 문의 내역
📈 성과
마이그레이션 성공
- 비용 절감: Gather.town 대비 약 60% 절감
- 성능 향상: 국내 서버로 레이턴시 70% 감소
- 기능 확장: Gather.town에 없던 자체 관리 기능 추가
운영 효율성
- 실시간 관리: 소환, 타이틀 설정으로 운영 편의성 증대
- 문의 응답 시간: 평균 5분 이내 (기존 15분)
- 데이터 수집: 5분마다 자동 동기화로 실시간 모니터링
사용자 만족도
- 학생: 메타버스 내에서 직접 문의 가능
- 관리자: 하나의 위젯으로 모든 관리 기능 통합
🔧 기술적 도전 과제
1. ES5 제약 극복
문제: ZEP Script는 ES5만 지원 (const, let, => 등 불가)
해결:
- 모든 변수를
var로 선언 - 화살표 함수 대신
function키워드 사용 - 폴리필 직접 구현 (
getCodePoint,Array.find등)
// ES6+ (불가능)
const players = App.players.filter(p => p.role < 2000);
// ES5 (사용)
var players = App.players.filter(function(p) {
return p.role < 2000;
});2. 유니코드 이모지 처리
문제: ES5는 서로게이트 페어 이모지 처리 불가 해결:
getCodePoint()함수 직접 구현- 이모지 감지 로직 작성
- 채팅 로그에서 이모지 전용 메시지 필터링
3. Widget ↔ Script 통신
문제: HTML 위젯과 ZEP Script 간 데이터 전달 해결:
postMessageAPI 활용widget.onMessage.Add()/widget.sendMessage()패턴- 타입 기반 메시지 라우팅
4. 단일 파일 아키텍처
문제: main.js 2000줄로 유지보수 어려움
대응:
- 명확한 함수 네이밍 규칙
- 주석 충분히 작성
- 기능별 섹션 분리
향후 개선:
- TypeScript → ES5 트랜스파일링 환경 구축
- 모듈 분리 + 빌드 프로세스 (Webpack)
🚀 배포 및 설치
설치 방법
-
파일 압축:
# 파일들을 직접 선택하여 압축 (폴더 제외) zip openeg-widget.zip *.js *.html -
ZEP 스페이스 업로드:
- ZEP 스페이스 접속
- 맵 에디터 → 스크립트 메뉴
main.js업로드 (메인 스크립트)- HTML 파일들 리소스로 업로드
-
환경 설정:
// main.js 상단 var BASE_URL = 'https://egatten.yos.kr'; var API_KEY = '***'; // 실제 키로 변경 var ADMIN_ROLE_THRESHOLD = 2000;
권한 설정
학생: role < 2000
스태프: role >= 2000
관리자: role >= 3000
🔗 관련 프로젝트
메인 시스템
- 출석 모니터링 시스템 - 전체 아키텍처
- 출석 수집 백엔드 - Google Sheets
- LMS 웹훅 - LMS 이벤트
데이터 연동
[ZEP.US 위젯 (본 시스템)]
↓ (실시간 데이터)
[Backend API]
↓ (저장)
[Database]
↓ (시각화)
[egatten 웹 대시보드]
📝 배운 점
기술적 학습
-
레거시 환경에서의 개발
- ES5 제약 속에서 모던 기능 구현
- 폴리필 직접 작성 경험
- 크로스 브라우저 호환성 고려
-
Hybrid Architecture
- 서버 사이드 스크립트 + 클라이언트 위젯 통합
- PostMessage 기반 통신 패턴
- 3-tier 데이터 흐름 설계
-
메타버스 플랫폼 이해
- ZEP.US API 활용
- 실시간 플레이어 상태 관리
- 메타버스 이벤트 핸들링
-
단일 파일 vs 모듈화 트레이드오프
- 배포 편의성 (단일 파일)
- 유지보수성 (모듈화 필요)
- 적절한 균형점 찾기
플랫폼 마이그레이션 경험
- 점진적 전환: Gather.town 기능을 ZEP.US로 단계적 이관
- 기능 확장: 기존 기능 유지 + 신규 관리 기능 추가
- 사용자 교육: 새 플랫폼 적응을 위한 가이드 제공
🔮 향후 계획
단기 (검토 중)
- TypeScript 도입 (ES5 트랜스파일링)
- 모듈 분리 (services/, components/, utils/)
- Webpack 빌드 프로세스
중기
- UI 컴포넌트 공통화
- API 보안 강화 (API Key 하드코딩 제거)
- 자동 테스트 환경 구축
장기
- 실시간 대시보드 강화
- 머신러닝 기반 이상 행동 감지
- 다국어 지원
프로젝트 기간: 2025.05 - 2025.12 현재 상태: 완료됨 (운영 중) 플랫폼: ZEP.US 최종 업데이트: 2025-12-21