# Hello ZEP Script

> **ZEP Script 란?**
>
> ZEP Script는 ZEP에서 동작하는 스크립트 언어(Scripting Language)입니다.
>
> 아바타 및 오브젝트 조작, 결제, 채팅, 실시간 멀티플레이 등 다양한 기반 시스템을 제공하여,&#x20;
>
> 코딩이 처음이라도 쉽게 시작할 수 있습니다.
>
> 독창적인 게임부터, 캘린더와 방명록 등의 생산성 앱까지!
>
> 나만의 메타버스 앱을 만들어 메타버스를 운영하고 ZEP을 더 풍부하게 즐겨보세요!

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fy5XvcoJqBt7ohNI6e2Oe%2Fimage%20(10).png?alt=media&amp;token=2ea5fc8d-4a8a-4e9c-af4d-87a182a5a50d" alt=""><figcaption></figcaption></figure>

## ZEP Script 활용 사례

ZEP Script는 사용하기 편리합니다. 그리고 누구나 사용할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FLU5q38OJH5S3Zigk1uvj%2FGIFMaker_me%20(1).gif?alt=media&amp;token=45724f9a-769a-47f5-a3d8-30996401b376" alt=""><figcaption><p>ZEP Script를 활용하여 만든 맵</p></figcaption></figure>

### 🕹️게임

**ZEP Script**를 활용해 다양하고 재미있는 게임을 만들 수 있습니다.&#x20;

### 토끼산 오르기

🐰 [<mark style="color:purple;">토끼산 오르기 맵 구경하기</mark>](https://zep.us/play/8jqEO4)

떨어지는 장애물(눈덩이)을 피해 산 정상으로 올라가는 게임입니다. \
**미니게임 \[똥피하기]**&#xB97C; 활용하여 떨어지는 눈덩이를 구현할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F0ZLt9goeQzEqAbiyeXtD%2FGIFMaker_me%20(8).gif?alt=media&amp;token=23edc367-d718-4f3b-bfb5-6cc910cbd3b0" alt=""><figcaption></figcaption></figure>

### 봉봉학교 탐정단

🕵️ [<mark style="color:purple;">봉봉학교 탐정단 맵 구경가기</mark>](https://zep.us/play/2QzYAY)

제한 시간 내에 탐정이 되어 맵 곳곳을 탐험할 수 있는 방탈출 맵입니다.\
비네팅 효과로 인해 스릴 넘치는 공간을 체험헤보세요!

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fk6XNn0XF6w807040cpcj%2FGIFMaker_me%20(9).gif?alt=media&amp;token=771dfdbd-02cd-42cb-a818-dc7cfdd91aec" alt=""><figcaption></figcaption></figure>

### 결투 게임

🥊 [<mark style="color:purple;">결투장 맵 구경가기</mark>](https://zep.us/play/yOw9Av)

**미니게임 \[결투]**&#xB97C; 기반으로 만들어진 게임입니다. \
\[z]키를 눌러 상대를 공격하고 HP가 깎이지 않게 다른 사람들을 피해 끝까지 살아남으세요. \
가장 끝까지 살아남은 우승자는 왕으로 변할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FwDMm0S9iV6g2m8OvnUCT%2FGIFMaker_me%20(10)%20(1).gif?alt=media&amp;token=d9e50127-9233-4985-8fb4-a3dfc2428a6a" alt=""><figcaption></figcaption></figure>

###

### 🎉행사

**ZEP Script**를 활용하여 인터랙티브한 행사를 기획할 수 있습니다.&#x20;

### 클레이튼  뮤지엄

✈️ [<mark style="color:purple;">클레이튼 뮤지엄 구경가기</mark>](https://zep.us/play/DwPndd)

클레이튼의 기술적 강점 등을 **ZEP Script**를 활용해 효과적으로 보여준 사례입니다

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fji2WHvXySJH6S9SjEuiF%2F81EB889DEDBCAEEC8097%2584.gif?alt=media&amp;token=d62b11c6-4203-4d34-8ced-bdc8df3146c4" alt=""><figcaption></figcaption></figure>

### 유니세프 후원자 참여행사 메타버스

유니세프가 하는 일을 ZEP Script로 구현하여 ,\
이용자들이 맵을 체험하며 자연스럽게 학습을 할 수 있도록 제작했습니다

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FrJ4zOKWjNTnaqjhMVuKG%2FGIFMaker_me%20(3).gif?alt=media&amp;token=56f3f131-808c-4325-8e40-a97466852093" alt=""><figcaption></figcaption></figure>

### 더블제트  제트랜드

✈️ [<mark style="color:purple;">더블제트 제트랜드 구경가기</mark>](https://zep.us/play/D63k6z)

삼성 청소기 제트 브랜드를 체험할 수 있는 테마파크로 ZEP Script를 통해 게임 공간을 구현해두었습니다.\
행사 시에 이용자들에게 재미요소를 줄 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FQdBLyn1NWObj2Y7gnVOq%2FGIFMaker_me%20(6).gif?alt=media&amp;token=252ee683-352d-49a5-9727-35b115e2408b" alt=""><figcaption></figcaption></figure>


# ZEP Script 개발 가이드

## 시작하기

### 소개

> 메타버스 플랫폼 ZEP에서 동작하는 앱을 누구나 제작할 수 있습니다.
>
> 캐릭터 및 오브젝트 조작 기능, 커스텀 UI 기능 등 다양한 기능을 지원합니다.
>
> 독창적인 게임부터 캘린더, 방명록 등의 생산성 앱까지, 누구나 자신만의 메타버스 앱을 만들 수 있습니다.&#x20;

:fire: [<mark style="color:purple;">**ZEP 스크립트 활용 사례**</mark>](/creator/reference/zep)

### 개발 환경

* Javascript (ES6)
* Typescript
* Canvas
* Web (Desktop/Mobile)

####

### ZEP-Script SDK를 이용한 개발 및 배포

ZEP-Script SDK를 사용하면 APP 개발 및 배포를 쉽게 할 수 있습니다.

:bulb: [<mark style="color:purple;">**Javascript 개발 가이드**</mark>](/creator/dev-guide/javascript)

:computer: [<mark style="color:purple;">**Typescript 개발 가이드**</mark>](/creator/dev-guide/typescript)

### 유형

{% hint style="info" %}
ZEP Script를 통해 개발된 앱은 아래 세가지 형태 중 하나를 선택하여 맵에 적용할 수 있습니다. 앱과 맵을 연결하기 위해서, [<mark style="color:purple;">**배포 가이드**</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}

**1) 미니게임 ( Mini game )**&#x20;

미니게임은 어느 맵에서나 사용할 수 있는 설치형 앱입니다. 임베드 권한이 있는 스페이스에서 **\[사이드바] > \[미니게임 아이콘]**&#xC744; 클릭해 설치할 수 있습니다. **설치한 순간부터 작동**하며, 실행한 사용자가 [<mark style="color:purple;">점프(space bar)를 통해 박스를 밟을 경우 앱이 종료</mark>](/zep-script-api/zepscriptapi/scriptapp/lifecycle)됩니다.

<div><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FHducOsfp74YpNozhrpft%2FUntitled.png?alt=media&amp;token=3d1d5d97-5e7a-4dda-a590-c6d96de0de47" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FQT826IluUO7iMHlCodUW%2FUntitled%201.png?alt=media&amp;token=ed542d94-1f16-4bc0-a690-413a05c277d3" alt=""><figcaption></figcaption></figure></div>

#### **2) 노말 앱 ( Normal app )** <a href="#undefined" id="undefined"></a>

노말 앱은 특정 맵에서만 작동할 수 있는 앱입니다. 노말 앱을 개발하여 업로드하면, **관리자**이상의 권한을 가진 유저가 맵의 **\[설정] > \[맵 설정] > \[노말 앱 추가]**&#xC5D0;서 적용할 수 있습니다. 별도의 앱 설치 과정 없이 내가 제작한 맵에만 Script를 적용할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FvJoDTUuWoDCFSl3uGDGM%2FGroup%201304.png?alt=media&amp;token=c4682d44-6f1f-4700-a773-79d9d2092490" alt=""><figcaption></figcaption></figure>

#### **3) 사이드바 앱 ( Sidebar app )** <a href="#undefined" id="undefined"></a>

사이드바 앱은 PC 좌측 사이드바에 아이콘으로 표시되는 형태의 앱 입니다. 사이드바 앱을 개발하여 업로드하면, 관리자권한을 가진 맵의 플레이 화면에서 **\[사이드바] > \[앱 추가] > \[앱 관리]** 리스트에서 설치하여 추가할 수 있습니다. 사이드바 앱이 추가된 스페이스에서는 입장한 **모든 사람에게 사이드바 앱이 노출**됩니다.

{% hint style="info" %}
[<mark style="color:purple;">사이드바 앱 예제</mark>](/creator/tutor/sample/sidebarapp) 페이지를 참고해 사이드바 앱을 개발해보세요.
{% endhint %}

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FvHXxo5H3Md1so2Lpf0fa%2FGroup%201305.png?alt=media\&token=0fdf76b6-7754-41b6-a4c3-9d8fcb4a6f6b)

***


# 쉬운 개발 가이드

이 가이드에서는 ZEP Script 개발을 시도해 보고자 하는 분들을 위한 간단한 개발 환경 및 방법에 대해 설명합니다.

## **프로그램 설치**

ZEP Script를 이용해 앱을 개발하기 위해서는 코드를 작성할 수 있는 IDE(편집기)가 필요합니다. 이 가이드에서는 Visual studio code 소프트웨어를 설치해 보도록 하겠습니다.

1. Visual studio code 홈페이지([링크](https://code.visualstudio.com/))에 방문합니다.
2. PC에 설치된 OS에 맞는 소프트웨어를 다운로드하고 설치합니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FGczzAi7gIlG0Cl7CEHYa%2Fimage.png?alt=media&amp;token=813651e6-d918-4efc-987a-96eadd69d75b" alt=""><figcaption></figcaption></figure>

## **프로그램 실행 및 코드 작성 준비**

1. 설치한 소프트웨어를 실행합니다.
2. 좌측 상단에 \[파일] → \[새 파일]을 선택합니다.\
   ![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F06d8gGgMX1VPiJEMGzYF%2Fimage.png?alt=media\&token=334f19d2-30ab-4097-b92b-8287f8d18526)
3. 중앙 상단에 표시되는 파일 형식 입력에 main.js라고 입력합니다.

   (ZEP Script로 제작한 앱에는 반드시 main.js 파일이 있어야 합니다)

   <div align="left" data-full-width="false"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1aCs2GV2RioEp8D3h9kR%2Fimage.png?alt=media&amp;token=4fa66f65-3e4e-47f4-8ab7-2d79f5342e9d" alt="" width="563"><figcaption></figcaption></figure></div>
4. 파일을 저장할 폴더를 선택하신 후 파일 생성을 클릭합니다.

   <div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Foq68H7B6NWA5srbLs1A7%2Fimage.png?alt=media&amp;token=8cfcab2e-d5cf-4093-9c0e-31bb1e884b1a" alt="" width="563"><figcaption></figcaption></figure></div>

###

## 샘플 코드 작성하기

미니 게임을 실행하면 간단한 인사 메시지가 나오는 앱을 제작해 보도록 합니다.

사용할 API는 아래 3가지입니다.

* [App.onStart](https://docs-kr.zep.us/zep-script-api/zepscriptapi/scriptapp/lifecycle#onstart) - 앱이 실행될 때 이 API함수 내에 있는 명령들이 1회 실행됩니다.
* [App.sayToAll](https://docs-kr.zep.us/zep-script-api/zepscriptapi/scriptapp/methods#saytoall) - 채팅 창에 메시지를 표시하는 기능입니다.
* [App.showCenterLabel](https://docs-kr.zep.us/zep-script-api/zepscriptapi/scriptapp/methods#showcenterlabel) - 화면에 메시지를 표시하는 기능입니다.

더 많은 기능에 대해 알아보려면 ZEP-Script 가이드([링크](https://docs-kr.zep.us/zep-script-api/zepscriptapi))를 참고해 주세요.

아래와 같이 테스트 코드를 작성한 후 저장합니다.

```jsx
App.onStart.Add(function(){
    App.sayToAll("Hello, Zep!");
    App.centerLabel("Hello, Zep!")
})
```

저장이 완료되었으면, 앱을 업로드하기 위해 main.js파일이 있는 폴더로 이동해 해당 파일을 \*.zip파일로 압축합니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FTQ5cV9NyeJGJM3akUOvN%2Fimage.png?alt=media&amp;token=8d7db425-89d2-4fd9-9880-30895b7752ad" alt="" width="188"><figcaption></figcaption></figure></div>

##

## 앱 업로드 및 실행하기

### 앱 업로드 하기

1. 홈페이지 로그인한 후 우측 상단에 내 닉네임을 클릭해서 \[나의 앱]을 클릭합니다.![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FUEu0MikfviRFcbylMDKR%2Fimage.png?alt=media\&token=e0ba10ca-ad85-45eb-b4d3-2a492705b8ee)
2. \[나의 앱]에서 앱을 등록하거나 수정할 수 있습니다. 새로운 앱을 업로드하기 위해 우측 상단에 \[앱 업로드] 버튼을 클릭합니다.<br>

   <div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FgqHvS1bBY6bEJBHzhLgj%2Fimage.png?alt=media&amp;token=45fca23c-4ae1-4cfb-9343-5b9603941002" alt=""><figcaption></figcaption></figure></div>
3. 앱 업로드에 필요한 정보를 작성합니다.<br>

   <div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FtqPuXXPt0RsZrp89lZ3i%2Fimage.png?alt=media&amp;token=317436ba-bef9-44c7-96d8-537e4a33495c" alt=""><figcaption></figcaption></figure></div>

* 이름, 설명 등을 작성합니다.
* 앱 타입은 \[미니 게임]으로 합니다.
* \[ZIP 파일 업로드] 버튼을 눌러, 작성한 코드가 포함된 압축 파일을 등록합니다.
* 다 완료되었으면 \[업로드] 버튼을 눌러 완료합니다.

앱 종류 및 업로드에 대한 자세한 사항은 [\[배포 가이드\]](https://docs-kr.zep.us/creator/dev-guide/zep-script)를 참고해 주세요.

### 앱 테스트 하기

<mark style="background-color:yellow;">테스트를 위해 홈페이지에 로그인 후 스페이스를 만들어 주세요.</mark>

1. 좌측의 사이드바에서 \[미니 게임]을 클릭합니다.
2. 실행 가능한 미니 게임 목록 중에 내가 업로드한 앱을 클릭합니다. \[Hello, ZEP]

   <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F3bOyyNIK1ibpY5TauZue%2Fimage.png?alt=media&amp;token=62a8eb07-c6a3-4bb9-bb7c-74b562e28325" alt=""><figcaption></figcaption></figure>
3. 앱이 실행됨과 동시에 화면 상단과 채팅 창에 코드로 작성했던 Hello, ZEP! 이라는 메시지가 나타나는 것을 볼 수 있습니다.

   <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FAbD61K3FaT8WnNw5hCuh%2Fimage.png?alt=media&amp;token=cc499c95-3f27-4c0f-91c8-f06ebd8f44b5" alt=""><figcaption></figcaption></figure>

## ZEP Script 따라하기

기본적인 앱 제작, 업로드 방식에 대해 알아봤는데요,

ZEP Script에 대해 조금 더 학습하면, 자신이 원하는 앱을 만들 수 있습니다.

\[[기초 예제 코드](https://docs-kr.zep.us/creator/tutor)]를 따라서 만들어 보는 것부터 시작해 보세요!


# Javascript 개발 가이드

**zep-script-SDK** 라이브러리를 사용해 폴더 구조를 깔끔하게 유지하고, 프로젝트 압축 및 배포를 명령어만으로 간단하게 진행 할 수 있습니다. **zep-script-SDK** 사용 방법을 알아보겠습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FoXeriQAgRXPeKzGScr9f%2Fimage.png?alt=media&amp;token=7e20348e-32f2-4c11-9f09-61de08a730c4" alt=""><figcaption></figcaption></figure></div>

### 1. **node.js 설치**

{% hint style="info" %}
<https://nodejs.org/ko/> 링크로 이동해 node.js를 설치합니다.

안정적인 버전인 LTS 버전으로 설치하는 것을 추천 드립니다.
{% endhint %}

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FnMi8ny2QPlnoWIDUoEyy%2FUntitled%202.png?alt=media&amp;token=b8f8e96d-4fe7-412c-8efc-a0eb4ef0e709" alt=""><figcaption></figcaption></figure></div>

### 2. **프로젝트 폴더 구성하기**

라이브러리 사용을 위해 프로젝트 폴더는 다음과 같이 구성합니다.

res 폴더는 앱에서 쓰일 이미지, 사운드, html 파일 등을 넣는 폴더입니다.

→ 폴더 이름을 **res**로 지정해야합니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FiA6FKeQY1Liyx8KIIpos%2FUntitled%203.png?alt=media&amp;token=f599ac9b-f90c-47f8-924e-87a713ec446d" alt=""><figcaption></figcaption></figure></div>

### 3. CLI를 사용해 프로젝트를 .zip 파일로 만들기

CLI를 사용해 명령어로 프로젝트를 **.zip** 파일로 만들 수 있습니다.

CLI는 MacOS의 경우 터미널, Windows의 경우 Windows PowerShell 또는 Windows 11부터 제공되는 터미널 환경에서 실행하실 수 있습니다.

> **Windows에서 PowerShell 실행하기**
>
> main.js가 있는 폴더 빈 공간에 **Shift + 오른쪽클릭**을 해서
>
> **`여기에 PowerShell 창 열기`** 또는 **`여기서 명령창 열기`** 메뉴를 누르면
>
> ![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FhUNtzM9jyhMcSvPAk5r7%2FUntitled%204.png?alt=media\&token=b0291ba8-6092-4476-9157-0aaeba1aa741)
>
> 다음과 같이 **Windows PowerShell** 또는 **명령 프롬프트** 창이 실행됩니다.
>
> ![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FigNx7hFgThBPdPhrK03M%2FUntitled%205.png?alt=media\&token=e8211f46-bcf0-4d9a-b9fb-7da934018362)

**main.js** 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.

<pre class="language-powershell"><code class="lang-powershell"><strong>npx zep-script archive
</strong></code></pre>

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F7oPbBUbAWBtlqj2wRYfg%2FUntitled%206.png?alt=media&amp;token=126fa5b2-f4ad-49fb-a78b-a7253a08e7a8" alt=""><figcaption></figcaption></figure>

압축 과정이 성공했다면 다음과 같이 폴더에 압축 파일이 생긴 것을 확인 할 수 있습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F2ujCz1u9fktGZxKkg8QR%2FUntitled%207.png?alt=media&amp;token=2bc9ad7d-f88a-4b8b-a9cf-6538394080c4" alt=""><figcaption></figcaption></figure></div>

압축 파일을 만드는 것을 마지막으로, 앱을 배포할 준비가 끝났습니다.

### 4. 프로젝트 배포하기

**1.홈페이지에서 배포하기**

위에서 만든 압축(ZIP) 파일을 업로드 하는 방식으로 앱을 배포할 수 있습니다.

[<mark style="color:purple;">**ZEP Script 배포 가이드**</mark>](/creator/dev-guide/zep-script) 페이지를 참고해서 앱을 배포해보세요!

**2. 명령어(CLI)로 배포하기**

CLI를 사용해 위에서 만든 압축(ZIP) 파일을 배포할 수 있습니다.

다음과 같이 <mark style="color:purple;">`zep-script.json`</mark> 파일을 생성해 업로드 될 앱을 설정합니다. (이름이 동일해야 합니다.)

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FprZio3MdJjZ0EyXq3Foo%2FUntitled%208.png?alt=media&amp;token=60b673a4-b54f-4a32-90c5-29b453cbac94" alt=""><figcaption></figcaption></figure></div>

```json
{
    "appId": "Zjkgoj",  // app ID
    "name": "Template", // app 이름
    "description": "Template application" , // app 설명
    "type": "normal" // app 타입 ( "normal" or "minigame" or "sidebar" )
}
```

⭐ appID: 업로드할 앱의 ID를 입력합니다

* 기존 앱을 변경하시려면 <https://zep.us/me/apps/>에 접속해서 업로드할 앱을 선택 후 주소창에 apps/뒤에 붙는 값을 입력하시면 됩니다.(아래 예시 이미지에서 **Zjkoj입**니다)

<div align="center"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FehxORm4TL2gzyFypblXE%2FUntitled%209.png?alt=media&amp;token=9b330830-0a39-4ad4-ba6f-2f3a4b6a5c97" alt=""><figcaption></figcaption></figure></div>

**main.js** 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.

```powershell
npx zep-script publish
```

앱 업로드 과정에서 계정 인증이 필요합니다.

다음과 같이 email을 입력하는 부분이 나오면 배포 할 앱을 소유하고 있는 계정의 email을 입력합니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZj8fFeE5JiXVJeAwAZTM%2FUntitled%2010.png?alt=media&amp;token=f052cd94-27a9-4363-a023-5ed3a006ce5a" alt=""><figcaption></figcaption></figure></div>

**Sending login code to your email**이라는 메시지가 나오면 위에서 입력한 이메일의 메일 함에서 인증 코드를 확인한 뒤 입력해주면 배포가 시작됩니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FlJIg7SSQdXq3jmuDXxbk%2FUntitled%2011.png?alt=media&amp;token=07f5de5b-74e5-4f9e-937b-1ec3c4dcfff9" alt=""><figcaption><p>인증 메일 예시</p></figcaption></figure></div>

다음 그림과 같이 **Publishing…** 텍스트 좌측에 초록색 체크 표시가 생기면 배포가 완료된 것 입니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FtwrcvPunhGbTQQapEno0%2FUntitled%2012.png?alt=media&amp;token=44084a59-16a1-4261-8d3e-d3c5e4ed7b93" alt=""><figcaption></figcaption></figure></div>

위에서 **zep-script.json 파일**에 작성한 내용대로 [**나의 앱**](https://zep.us/me/apps) 페이지에 앱이 배포된 것을 확인 할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F4Dgb29gvPyEWg7cGGX1b%2FUntitled%2013.png?alt=media&amp;token=9b153256-5647-4095-b762-43150aad0f87" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**배포에 실패하는 경우 아래의 단계를 따라 재시도 해보세요.**

**npx 명령어를 사용하는 경우**

1. 터미널에서 "`npm i zep-script -g"` 명령 실행하여 SDK 패키지를 최신화
2. root 폴더에서 "`.zscsession"`  파일 삭제
3. "npx zep-script publish" 명령어로 배포 시도

**npm 명령어를 사용하는 경우**

1. package.json 파일의 "zep-script" 버전이 최신이 아니라면 최신 버전으로 업데이트
2. root 폴더에서 "`.zscsession"`  파일 삭제
3. "npm run deploy" 명령어로 배포 시도
   {% endhint %}

라이브러리 관련하여 보다 자세한 내용은 아래 github 레포지토리 내용을 참고해주세요.

:clap:[zep-script-sdk/packages/zep-script-cli at main · zep-us/zep-script-sdk](https://github.com/zep-us/zep-script-sdk/tree/main/packages/zep-script-cli)

***


# Typescript 개발 가이드

### ⭐ 타입스크립트 개발 시 주의할 점

타입스크립트로 개발 시 **App, Map, Player, Widget** 키워드 앞에 **Script** 키워드를 붙여야 합니다.

```jsx
// 자바스크립트 개발 시
App.sayToAll("Hello World!");

// 타입스크립트 개발 시
ScriptApp.sayToAll("Hello World!");
```

### 1. **node.js 설치**

{% hint style="info" %}
<https://nodejs.org/ko/> 링크로 이동해 node.js를 설치합니다.

안정적인 버전인 LTS 버전으로 설치하는 것을 추천드립니다.
{% endhint %}

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FVq3bghb3gRHMwRXjBKF6%2FUntitled.png?alt=media&amp;token=84d9be1b-04d9-4411-9b7a-cc580bf4f7f1" alt=""><figcaption></figcaption></figure>

### 2. **프로젝트 폴더 만들기**

CLI를 통해 ZEP Script 프로젝트를 개발하기 위한 샘플 프로젝트를 다운로드 받을 수 있습니다.

CLI는 MacOS의 경우 터미널, Windows의 경우 Windows PowerShell 또는 Windows 11부터 제공되는 터미널 환경에서 실행하실 수 있습니다.

> **Windows에서 PowerShell 실행하기**
>
> 폴더를 생성하고 싶은 곳 빈 공간에 **Shift + 오른쪽클릭**을 해서
>
> **`여기에 PowerShell 창 열기`** 또는 **`여기서 명령창 열기`** 메뉴를 누르면
>
> ![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1lORqNuyAVTfF89JN4Kp%2FUntitled%201.png?alt=media\&token=caab8a00-7e48-4457-afb4-e47a85f10c52)
>
> 다음과 같이 **Windows PowerShell** 또는 **명령 프롬프트** 창이 실행됩니다.
>
> <img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FpAzEf9tnTnQfMabj815j%2FUntitled%202.png?alt=media&amp;token=fbfa582b-5855-4295-b64d-57beb1a993f5" alt="" data-size="original">

다음과 같이 프로젝트 폴더를 생성하기 위한 명령어를 입력합니다.

<mark style="color:purple;">**`npx zep-script init {폴더 이름} --npm`**</mark>

<pre class="language-bash"><code class="lang-bash"><strong>npx zep-script@latest init MyZepApp --npm
</strong></code></pre>

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FOMef50mOyFRmCs1rVYss%2F%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202025-06-26%20094915.png?alt=media&amp;token=6cef794a-d64a-4d29-b958-6594ed5a16b1" alt=""><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMdw2H7BH44Va8KNHCnk7%2FUntitled%204.png?alt=media&amp;token=4a182a1d-6d76-4c02-a2f0-fad0bb9ec1ea" alt=""><figcaption></figcaption></figure></div>

위와 같이 <mark style="color:green;">`Project {폴더 이름} initialized successfully`</mark> 메시지가 출력 되었다면,

이제 타입스크립트로 **ZEP Script**를 개발할 수 있는 환경이 준비 된 것 입니다.

### 3. 폴더 구조 살펴보기

생성된 프로젝트 폴더로 이동하면 다음과 같이 파일들이 있는 것을 볼 수 있습니다.

여러 기능을 모듈로 나누고 싶다면, `src` 폴더내부에 파일을 추가해 확장할 수 있습니다.\
예: `playerUtils.ts`, `eventHandlers.ts` 등

**res** 폴더는 ‘**resources**’의 약자로, 앱에 필요한 이미지나 html 파일 등을 넣는 폴더입니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FqxiIqXdcDPTtKVAgq4YU%2Fimage.png?alt=media&amp;token=c5bda3ca-dcfe-4e2b-b19b-bff815f83488" alt=""><figcaption></figcaption></figure></div>

여기서 생성된 **main.ts** 파일은 앱 로직을 작성하는 메인 파일입니다.

**main.ts** 에는 다음과 같이 샘플 코드가 적혀있습니다. 해당 코드를 참고하여 개발을 하시면 됩니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F9gUSqMCLdfkSISveoQrc%2Fimage.png?alt=media&amp;token=b9b813fa-b24d-4475-8392-274ce67da69e" alt=""><figcaption></figcaption></figure></div>

### 4. **빌드하기**

**ZEP Script**는 자바스크립트가 아닌 언어로 실행 할 수 없기 때문에 타입스크립트를 자바스크립트로 변환하는 빌드 과정을 거쳐야 합니다.

**main.ts** 파일이 있는 폴더에서 명령 창을 열어 다음과 같이 빌드를 위한 명령어를 입력합니다.

```powershell
npm run build 또는 npx zep-script build
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FFKOuZKMuoNyTXtdngh0l%2FUntitled%207.png?alt=media&amp;token=5a14cd5f-a500-408b-afb9-3219d0cefa53" alt=""><figcaption></figcaption></figure></div>

빌드 완료 시 `res` 폴더에 `main.js` 파일이 생성됩니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F3yGl32WaADsBSZHg3npk%2Fimage.png?alt=media&amp;token=a1fca20d-2646-43db-869a-4d0916902eba" alt=""><figcaption></figcaption></figure></div>

**5. 압축 파일 만들기**

ZEP Script로 만든 앱을 배포하려면 main.js 파일과 앱에서 사용 할 이미지, html 파일 등을 함께 압축한 **.zip** 파일을 준비해야 합니다.

**main.ts** 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.

```powershell
npm run archive 또는 npx zep-script archive
```

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F2mgm14X8FwjKKuMZksQy%2FUntitled%2010.png?alt=media&amp;token=d58c5f00-9a4a-445c-b7b6-2adac5c656ec" alt=""><figcaption></figcaption></figure>

압축 과정이 성공했다면 다음과 같이 폴더에 압축 파일이 생긴 것을 확인 할 수 있습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FdwvFgfXSISRpvTjlJ57t%2FUntitled%2011.png?alt=media&amp;token=cbcd2cdc-404d-48bc-82ff-6d58feedd2a6" alt=""><figcaption></figcaption></figure></div>

압축 파일을 만드는 것을 마지막으로, 앱을 배포할 준비가 끝났습니다.

### 6. 프로젝트 배포하기

1. **홈페이지에서 배포하기**

위에서 만든 압축(ZIP) 파일을 홈페이지에서 업로드 하는 방식으로 앱을 배포할 수 있습니다.

[<mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script) 페이지를 참고해서 앱을 배포해보세요!

&#x20; &#x20;

&#x20; **2. 명령어(CLI)로 배포하기**

CLI를 사용해 위에서 만든 압축(ZIP) 파일을 배포할 수 있습니다.

프로젝트 폴더에서 <mark style="color:purple;">`zep-script.json`</mark> 파일을 열어 업로드 될 앱을 설정합니다.

( zep-script.json 파일이 없다면 생성해주세요 )

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FHEyK8aN8RJmauBAIDGiH%2Fimage.png?alt=media&amp;token=75bae856-d3f1-43aa-a32b-9d028d53150d" alt=""><figcaption></figcaption></figure></div>

```json
{
    "appId": "Zjkgoj",  // app ID
    "name": "Template", // app 이름
    "description": "Template application" , // app 설명
    "type": "normal" // app 타입 ( "normal" or "minigame" or "sidebar" )
}
```

⭐ appID: 업로드할 앱의 ID를 입력합니다

* 기존 앱을 변경하시려면 <https://zep.us/me/apps/>에 접속해서 업로드할 앱을 선택 후 주소창에 apps/뒤에 붙는 값을 입력하시면 됩니다.(아래 예시 이미지에서 **Zjkoj**입니다)

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FFg06s606QbzWv665Rzij%2FUntitled%2013.png?alt=media&amp;token=8ab78282-94b6-4d15-84e6-5d1999df393b" alt=""><figcaption></figcaption></figure>

설정이 끝났으면 프로젝트 폴더 경로에서 PowerShell 을 실행 한 뒤, 다음과 같이 배포를 하기 위한 명령어를 입력합니다.

```powershell
npm run deploy 또는 npx zep-script publish
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FBPLGCQgk58Tp8y1YNzov%2FUntitled%2014.png?alt=media&amp;token=f746f9dd-0440-4db2-a919-5424a94cd22b" alt=""><figcaption></figcaption></figure></div>

다음과 같이 email을 입력하는 부분이 나오면 배포 할 앱을 소유하고 있는 계정의 email을 입력합니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FooKvj7OMw7xTv3wJnXGr%2FUntitled%2015.png?alt=media&amp;token=b3b4155a-351b-4002-8ac3-bc35406269c5" alt=""><figcaption></figcaption></figure></div>

**Sending login code to your email..**&#xC774;라는 메시지가 나오면 위에서 입력한 이메일의 메일 함에서 인증 코드를 확인한 뒤 입력해주면 배포가 시작됩니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fg4UBKS1fklThtfTCgJ0E%2FUntitled%2016.png?alt=media&amp;token=912b1e58-d4a1-4366-870c-6278095fc8cd" alt=""><figcaption><p>메일 인증 예시</p></figcaption></figure>

다음 그림과 같이 **Publishing…** 텍스트 좌측에 초록색 체크 표시가 생기면 배포가 완료된 것 입니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FQ1s6Od2mabqcsmSdeVLe%2FUntitled%2017.png?alt=media&amp;token=cc44fd54-d2fa-4ce0-a02e-e6d9b71c24c5" alt=""><figcaption></figcaption></figure></div>

[**나의 앱**](https://zep.us/me/apps) 페이지에서 **zep-script.json** 에 작성한 내용대로 앱이 배포된 것을 확인 할 수 있습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FvaXsEeBeHNuJ8xY1HCwI%2Fimage.png?alt=media&amp;token=626d9c45-de0b-471e-8cfb-15da3fb02375" alt=""><figcaption></figcaption></figure></div>

{% hint style="warning" %}
**배포에 실패하는 경우 아래의 단계를 따라 재시도 해보세요.**

**npx 명령어를 사용하는 경우**

1. 터미널에서 "`npm i zep-script@latest -g` "명령을 실행하여 SDK 패키지를 최신화
2. root 폴더에서 "`.zscsession"`  파일 삭제
3. "npx zep-script publish" 명령어로 배포 시도

**npm 명령어를 사용하는 경우**

1. package.json 파일의 "zep-script" 버전이 최신이 아니라면 최신 버전으로 업데이트
   1. `npm i zep-script@latest` 명령어 실행
2. root 폴더에서 "`.zscsession"`  파일 삭제
3. "npm run deploy" 명령어로 배포 시도
   {% endhint %}

라이브러리 관련하여 보다 자세한 내용은 아래 github 레포지토리 내용을 참고해주세요.

[zep-script-sdk/packages/zep-script-cli at main · zep-us/zep-script-sdk](https://github.com/zep-us/zep-script-sdk/tree/main/packages/zep-script-cli)

***


# ZEP Script 배포 가이드

이 문서는 ZEP 스크립트로 만든 앱을 적용하는배포 과정에 대해 설명합니다.

## <mark style="background-color:purple;">STEP 1.</mark>

#### ZEP Script로 개발한 코드(**main.js)**, 사용할 이미지 파일, 위젯 파일들을 선택한 후 압축합니다.

{% hint style="danger" %}
주의:

\- 앱의 파일명과 형식은 반드시 “**main.js**”로 지정해주어야 합니다.&#x20;

\- 폴더를 압축하는 것이 아니라 **파일을 멀티 선택한 후 압축**해야 합니다.&#x20;

\- **\*.zip** 형식의 확장자를 지원합니다.
{% endhint %}

**예시 파일**

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F6wYF95Y7LVFdmWYIhrnv%2Fimage.png?alt=media\&token=09a791f4-b645-4160-ace5-c8f1278487c4)

{% file src="/files/flE4EOIaqhVKUAiy1SFU" %}

## <mark style="background-color:purple;">STEP 2.</mark>

#### ZEP에 회원가입 또는 로그인 후, [<mark style="color:purple;">나의 스페이스 목록</mark>](https://zep.us/spaces/me) 페이지에서 \[프로필] > \[나의 앱(Beta)]를 클릭합니다

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F9JaX5POjhzVt9kyQ6u39%2FUntitled.png?alt=media&amp;token=e9bbfc91-ed7d-4b51-9f40-8590714ed22b" alt=""><figcaption></figcaption></figure></div>

## <mark style="background-color:purple;">STEP 3.</mark>

#### [<mark style="color:purple;">나의 앱 페이지</mark>](https://zep.us/me/apps)에서 **\[앱 업로드]** 버튼을 클릭합니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FF6vA7d6SbXqnfLmyOPBL%2Fnoname.png?alt=media&amp;token=1a9ef425-db56-4a3b-b746-03ec7d5667b4" alt=""><figcaption></figcaption></figure>

## <mark style="background-color:purple;">STEP 4.</mark>

#### 앱 이름, 설명, 유형, 아이콘, 압축 파일 (스크립트, 위젯, 이미지 파일의 압축된 버전)을 설정 후 업로 **버튼**을 눌러줍니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FlPMlPXe2YV46zCMrnTvy%2Fupload.png?alt=media&amp;token=85c47d87-3d7a-40ef-bb69-9fa5e0c7d786" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FlBgBrwjv1gzdotkQFefW%2Fapp_icon.png?alt=media&amp;token=011723aa-23ff-4f24-9a53-40f7e31b326b" alt=""><figcaption><p>앱 이름과 아이콘이 적용된 모습 (미니게임)</p></figcaption></figure>

## <mark style="background-color:purple;">STEP 5.</mark>

#### 앱의 종류에 따라 원하는 맵에 설치하기.

* **노멀 앱**(**Normal app):**

  Owner 권한을 가진 맵의 사이드바에서 **\[설정] > \[맵 설정] > \[노말 앱 추가]**&#xC5D0;서

  적용할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FKEqTmUe0A9Our8PcWcoF%2Fimage.png?alt=media&amp;token=dbdec9ab-fedf-4f24-9a70-3432bb67eeba" alt=""><figcaption></figcaption></figure>

* **미니게임(Mini game):**

  플레이 화면의 **\[좌측 사이드바] > \[미니게임 버튼]**&#xC744; 클릭해 설치할 수 있습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F9cD4YK6p4zUJ391hhMyk%2FUntitled%201.png?alt=media&amp;token=07593c1a-3a16-4bc3-8cb2-3a595c4846fb" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FNFIj1mY1DX3AhpmVmpVR%2FUntitled%202.png?alt=media&amp;token=12dfa48f-0beb-4b85-a513-1204b88747fd" alt=""><figcaption></figcaption></figure></div>

## 🚧 **디버깅 및 에러 메시지**

앱 실행 시, 에러 메시지는 **스태프 권한 이상의 유저 채팅창에&#x20;**<mark style="color:red;">**빨간색 글씨**</mark>로 노출됩니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FLAdrcgPAu7JfvHYvu7BF%2Fimage.png?alt=media&amp;token=b92231e8-9f08-4e9e-ab94-b3f4fbb75aae" alt=""><figcaption></figcaption></figure></div>


# ZEP Script 따라하기

ZEP Script의 기본부터 고급 기능까지 실습할 수 있는 튜토리얼과 예제 코드를 따라하며 ZEP Script를 손쉽게 익혀보세요.

### 기초 예제 코드

기초 예제 코드를   따라 차근차근 ZEP Script를 익혀보세요!

{% content-ref url="/pages/G5HJLBG8sOWfWcz1J3q6" %}
[ZEP APP lifecycle 이해하기](/creator/tutor/tutorial/2)
{% endcontent-ref %}

{% content-ref url="/pages/PtNpMdpLPXpve1TAhQUl" %}
[메시지 출력해보기](/creator/tutor/tutorial/1)
{% endcontent-ref %}

{% content-ref url="/pages/IMUeuWBPqU5xLs3OSzcJ" %}
[캐릭터 이미지 바꾸기](/creator/tutor/tutorial/3)
{% endcontent-ref %}

{% content-ref url="/pages/HOjgujTdkQVZnYYkEngG" %}
[나만의 UI 만들기](/creator/tutor/tutorial/4)
{% endcontent-ref %}

{% content-ref url="/pages/cXwKwvHnqiUahcrzDtae" %}
[2초 스턴 효과 만들어보기](/creator/tutor/tutorial/5)
{% endcontent-ref %}

### ZEP Script 예제코드

ZEP Script 개발에 도움을 주는 다양한 예제 코드들을 확인해보세요.

{% content-ref url="/pages/OI6snaeVngZfpbWJYP93" %}
[타이머](/creator/tutor/sample/timer)
{% endcontent-ref %}

{% content-ref url="/pages/YGPvzykhZ4OrlEbbejCZ" %}
[사이드바 앱](/creator/tutor/sample/sidebarapp)
{% endcontent-ref %}

{% content-ref url="/pages/UeAMNvt45BRoTZ1n6OzV" %}
[좀비 게임](/creator/tutor/sample/zombie)
{% endcontent-ref %}

{% content-ref url="/pages/iBqdIgrUQ8HMN7vemuzR" %}
[페인트맨 게임](/creator/tutor/sample/paintman)
{% endcontent-ref %}

{% content-ref url="/pages/2aeHsYO8gb0U9Ne7qjeu" %}
[초성 퀴즈](/creator/tutor/sample/wordquiz)
{% endcontent-ref %}

{% content-ref url="/pages/lScaAm2qCsasGj6rnGsu" %}
[똥피하기 게임](/creator/tutor/sample/avoidpoop)
{% endcontent-ref %}

{% content-ref url="/pages/RwDKwax8VLl4d8121sgu" %}
[결투 게임](/creator/tutor/sample/boxing)
{% endcontent-ref %}

{% content-ref url="/pages/JxNeRB26NpMArX6v0FXr" %}
[달리기](/creator/tutor/sample/running)
{% endcontent-ref %}

## UI 예제 코드

### **기초 유형**

{% content-ref url="/pages/tHETfF3bSZLKCUsIz0Ck" %}
[Image List](/creator/tutor/undefined/image-list)
{% endcontent-ref %}

{% content-ref url="/pages/nzfTOPCLzj4tQ0CgE9a3" %}
[Text List](/creator/tutor/undefined/text-list)
{% endcontent-ref %}

{% content-ref url="/pages/QUKjF6PqVmnB7eDy7Wzp" %}
[Text Button List](/creator/tutor/undefined/text-button-list)
{% endcontent-ref %}

{% content-ref url="/pages/KJi60kxORVyDxxuX5s7a" %}
[Ranking List](/creator/tutor/undefined/ranking-list)
{% endcontent-ref %}

### **심화 유형**

{% content-ref url="/pages/EjzfLSPQwZoN7ajTvcmt" %}
[Select List](/creator/tutor/undefined/select-list)
{% endcontent-ref %}


# 기초 예제 코드

튜토리얼을 따라 차근차근 ZEP Script를 익혀보세요!

{% content-ref url="/pages/G5HJLBG8sOWfWcz1J3q6" %}
[ZEP APP lifecycle 이해하기](/creator/tutor/tutorial/2)
{% endcontent-ref %}

{% content-ref url="/pages/PtNpMdpLPXpve1TAhQUl" %}
[메시지 출력해보기](/creator/tutor/tutorial/1)
{% endcontent-ref %}

{% content-ref url="/pages/IMUeuWBPqU5xLs3OSzcJ" %}
[캐릭터 이미지 바꾸기](/creator/tutor/tutorial/3)
{% endcontent-ref %}

{% content-ref url="/pages/HOjgujTdkQVZnYYkEngG" %}
[나만의 UI 만들기](/creator/tutor/tutorial/4)
{% endcontent-ref %}

{% content-ref url="/pages/cXwKwvHnqiUahcrzDtae" %}
[2초 스턴 효과 만들어보기](/creator/tutor/tutorial/5)
{% endcontent-ref %}


# 메시지 출력해보기

1. App의 중앙 상단부에 메시지를 출력해봅니다.&#x20;

   기본 Center label 색상은 검은 배경에 흰색 텍스트로 노출됩니다.

```jsx
// main.js

App.showCenterLabel("Hello world");
```

2\. 실행 결과

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FBpcrNOLZU9Qhr8dnVyds%2FUntitled.png?alt=media&amp;token=83d33f80-7cba-4671-afff-e44366f742b0" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
참고

\- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.

\- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.&#x20;

\- 배포 방법을 아직 모르신다면, [<mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}


# ZEP APP lifecycle 이해하기

앱이 실행되어 종료될 때까지를 하나의 **생애 주기(Lifecycle)**&#xB77C;고 합니다.

Lifecycle 함수는 앱의 생애 주기를 관리해주는 함수입니다. 아래 그림과 같이 앱이 실행될 때는 **Enter 단계**의 함수들이 동작하고, 앱이 실행 중 일 때는 **Update 단계**의 함수들이 주기적으로 동작하며, 앱이 종료될 때는 **Exit 단계**의 함수들이 동작하게 됩니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMyaNE6tbqQvwyKBZDMuN%2F%EC%98%A4%ED%83%80%20%EC%88%98%EC%A0%95.png?alt=media&amp;token=8d5d68be-25a4-4da0-847f-5cb995175f96" alt=""><figcaption></figcaption></figure>

```jsx
// App이 최초로 시작될 때
App.onInit.Add(function(){
	// 이 시점에 App에는 플레이어들이 참가하지 않은 상태
  // App의 나머지 필요한 부분을 초기화시킨다.
});

// 플레이어가 들어올 때
App.onJoinPlayer.Add(function(player){
  // 해당하는 모든 플레이어가 이 이벤트를 통해 App에 입장
});

// 플레이어가 모두 입장한 뒤에 한번 호출
App.onStart.Add(function(){
  // App에서 원하는 플레이어 속성값을 부여할 수 있다.
});

// 플레이어가 떠날 때
App.onLeavePlayer.Add(function(player){
  // 플레이어가 단순히 중간에 나갔을 때
  // App이 종료될 때에서 이 이벤트를 통해 모두 App에서 퇴장합니다.
})

// 매 20ms(0.02초) 마다 실행
App.onUpdate.Add(function(){
})

// App이 종료될 때
App.onDestroy.Add(function(){
   // 이미 모든 플레이어가 App에서 나간 상태
   // App을 나머지를 정리한다.
})
```

***


# 캐릭터 이미지 바꾸기

1. **애니메이션이 들어간 SpriteSheet 샘플을 준비합니다. 없다면 아래 이미지를 다운 받아주세요.**

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FOXjJew5z7WwpWjhtbm5G%2Fblueman.png?alt=media&amp;token=50245444-f57c-4a96-b228-31458413a2a4" alt=""><figcaption></figcaption></figure></div>

<mark style="color:red;">\*주의: 직접 제작한 경우가 아닌 SpriteSheet는 저작권에 유의해서 사용해야 합니다.</mark>

**2. 위 이미지 같이 낱장의 이미지들이 동일한 규격으로 합쳐진 형태의 SpriteSheet 그림의 확장자는 png 파일이어야 합니다.**

* TIP: 캐릭터의 경우 한 동작의 그림 규격으로 48(px) x 64(px)를 권장합니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMuPyUMF4dc00PL5gGHJv%2Fsample1.png?alt=media&amp;token=7fd3b97d-c5ad-4e41-b85b-e2f334eb6ff6" alt=""><figcaption></figcaption></figure></div>

{% hint style="success" %}
스프라이트 시트(sprite sheet)는 여러 개의 작은 그래픽을 정렬하여 구성한 비트맵 이미지 파일입니다. 게임 개발에서 캐릭터의 연속적인 포즈를 한 장의 이미지에 구성하여 2D 애니메이션 제작에 사용됩니다.
{% endhint %}

**3. SpriteSheet를 읽는 API 로는** <mark style="color:purple;">`App.loadSpritesheet`</mark> **를 사용합니다.**

```jsx

App.loadSpritesheet(fileName: string, frameWidth: integer, frameHeight: integer, anims: array, frameRate: integer): ScriptDynamicResource

```

* 첫 번째 인자(fileName): 확장자가 포함된 SpriteSheet 시트의 파일 명을 넣습니다.
* 두 번째 인자(frameWidth): 하나의 동작이 그려져 있는 그림의 가로 크기(px)
* 세 번째 인자(frameHeight): 하나의 동작이 그려져 있는 그림의 높이(px)
* 네 번째 인자(anims): ZEP에서 정해져 있는 애니메이션 이름(left, right, up, down)과 각각에 사용될 그림 인덱스 넘버들

{% hint style="success" %}
그림 순서의 인덱스 넘버는 왼쪽 상단 가로 줄 부터 0으로 시작합니다.
{% endhint %}

* 다섯 번째 인자(frameRate): 1초에 몇 장을 돌아가게 할 것인지 정합니다.

**4. player의 속성을 변경하였으므로** <mark style="color:purple;">`player.sendUpdated()`</mark> **를 써주어야 실제 반영됩니다.**

**5. 예시 코드**

```jsx
// 블루맨이라는 변수를 자바스크립트 문법에 맞게 생성
let blueman = null;

// 변수에 SpriteSheet를 읽어 저장
blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // left 라는 이미 정해진 왼쪽 방향으로 걸을 때의 애니메이션 이름
    up: [15, 16, 17, 18, 19], // 그 이름에 쓰일 전체 파일에서의 인덱스 넘버들
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
}, 8); // 1초에 8장으로 한다.

// 플레이어가 입장할 때 바로 블루맨 그림으로 교체해준다.
App.onJoinPlayer.Add(function(player){
	player.sprite = blueman;
  
  // 플레이어 속성이 변경되었으므로 호출해서 실제 반영해준다.
	player.sendUpdated();
})
```

**6. 활용**

```jsx
// 변수 선언과 동시에 바로 저장이 가능하다.
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], 
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
}, 8);
```

{% hint style="success" %}
&#x20;플레이어가 입장할 때가 아닌, 다른 특수한 조건일 때에도 캐릭터가 변할 수 있겠죠? 생각해보세요. 그리고 만들어 보세요!
{% endhint %}

{% hint style="warning" %}
참고

\- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.&#x20;

\- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.

\- 배포 방법을 아직 모르신다면,[ <mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}


# 나만의 UI 만들기

1. ZEP 캔버스 위에 내가 원하는 형태의 UI를 그릴 수 있습니다. ZEP에서는 HTML 을 통해 UI를 표현할 수 있습니다.
2. <mark style="color:purple;">`App.showWidget()`</mark> 함수를 통해, HTML 파일을 특정 위치에 불러올 수 있습니다. 이 때 인자로 파일명, 정렬, 위젯의 가로와 세로 크기를 정해줍니다. 정렬에 들어가는 값은 [<mark style="color:purple;">ScriptApp</mark>](https://app.gitbook.com/o/-MkvEtFn2kFBYSN4_5rX/s/iW553XSGeKCAPpImxXi3/~/changes/bRylDKw8UxLaVWLvuhVV/creator/zep-script-v2_kr/api/scriptapp)에서 Methods를 참고해주세요.
3. 생성한 변수에 <mark style="color:purple;">`sendMessage()`</mark> 메소드를 통해, 원하는 값을 HTML 파일에 전달할 수 있습니다.
4. main.js에서 전달 받은 값을 사용하려면, HTML 문법에 맞게 작성한 다음 `addEventListener`를 통해 message 타입을 아래 예시와 같이 값을 받아올 수 있습니다.

* main.js

```jsx
//UI가 노출될 위치(정렬, 가로, 세로) 값을 변수로 사전에 제작
let postion = 'middle';
let width = 400;
let height = 400;

// my.html로 state라는 태그를 만들어 hello라는 값을 전달
let _widget = App.showWidget('my.html', position, width, height);
_widget.sendMessage({
	state: "hello",
}); 
```

* my.html

```html
<html>
<div id="test">-</div>
<script type="text/javascript">
		**window**.**addEventListener**('message', function(e) {
			// state라는 태그 밸류를 보냈으니 아래 형태로 받는다
			var state = e.data.state; 
			// 위의 id test 라는 곳의 텍스트로 적용한다
			document.getElementById("test").innerText = state;
		})
</script>
</html>
```

{% hint style="warning" %}
참고&#x20;

\- 튜토리얼의 App type은 installable App을 권장합니다.&#x20;

\- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.&#x20;

\- 배포 방법을 아직 모르신다면,[ <mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}

***

[외부 API 통신하기](/creator/reference/api)


# 2초 스턴 효과 만들어보기

### 2초 스턴 효과 만들어보기

```jsx
let _players = App.players;

// 플레이어가 스페이스에 입장 했을 때 이벤트
App.onJoinPlayer.Add(function(p) {
  p.tag = {
      sturn : false, // 스턴에 걸림 여부, false
      sTime : 2, //스턴 효과 2초로 설정
  };
	_players = App.players;
});

// 플레이어가 다른 플레이어를 공격혔을 때 (Z키)
App.onUnitAttacked.Add(function(sender, x, y, target) {
    if(!target.tag.sturn)
    {
        target.tag.sturn = true; // 스턴에 걸림 여부를 true로 변경
        target.moveSpeed = 0; // 이동속도를 0으로 변경
        target.sendUpdated();
    }
});

App.onUpdate.Add(function(dt){
	for(let i in _players) {
		let p = _players[i];
		// 스턴에 걸림 여부가 true 이면
		if(p.tag.sturn)
		{
		    p.tag.sTime -= dt; //스턴 효과 지속시간이 0이 될 때까지 update마다 dt만큼 빼줌
		    if(p.tag.sTime <= 0) // 스턴 효과 지속시간이 0 이하가 되면,
		    {
		        p.tag.sturn = false; // 스턴에 걸림 여부를 false로 변경
		        p.tag.sTime = 2; // 지속시간 2초로 초기화
		        p.moveSpeed = 80; // 이동속도 정상화
		        p.sendUpdated();
		    }
		}
	}
});

// 플레이어가 스페이스를 나갔을 때 이벤트
App.onLeavePlayer.Add(function(p) {
    p.moveSpeed = 80; // 이동속도 정상화
    p.sendUpdated();
    _players = App.players;
});
```

{% hint style="warning" %}
참고

\- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.&#x20;

\- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.

\- 배포 방법을 아직 모르신다면, [<mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}

***


# ZEP Script 예제 코드

ZEP Script 개발에 도움을 주는 다양한 예제 코드들을 확인해보세요

## 타이머

***

ZEP에서 동작하는 타이머를 만들 수 있습니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:alarm\_clock: [<mark style="color:purple;">**타이머 예제**</mark>](/creator/tutor/sample/timer)

## 방탈출 타이머

***

방탈출 스페이스에서 동작하는 타이머를 만들 수 있습니다.

필수 지정 영역을 적용해서 방탈출 타이머를 만들어보세요

:clock1: [<mark style="color:purple;">**방탈출 타이머 예제**</mark>](/creator/tutor/sample/escapetimer)

## 사이드바 앱

***

사이드바 앱은 PC 좌측 사이드바에 아이콘으로 표시되는 형태의 앱 입니다.

다음 예제를 개인의 개발 스타일에 맞게 수정해 사이드바 앱을 개발하실 수 있습니다..

:left\_facing\_fist: [<mark style="color:purple;">**사이드바 앱 예제**</mark>](/creator/tutor/sample/sidebarapp)

## 좀비 게임

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 좀비 게임 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:zombie: [<mark style="color:purple;">**좀비게임 예제**</mark>](/creator/tutor/sample/zombie)

## 페인트맨 게임

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 페인트맨 게임 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:art: [<mark style="color:purple;">**페인트맨 게임 예제**</mark>](/creator/tutor/sample/paintman)

## 초성 퀴즈

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 초성 퀴즈 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:woman\_gesturing\_ok: [<mark style="color:purple;">**초성 퀴즈 예제**</mark>](/creator/tutor/sample/wordquiz)

## 똥피하기 게임

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 똥피하기 게임 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:poop: [<mark style="color:purple;">**똥피하기 게임 예제**</mark>](/creator/tutor/sample/avoidpoop)

## 결투 게임

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 결투 게임 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:boxing\_glove: [<mark style="color:purple;">**결투 게임 예제**</mark>](/creator/tutor/sample/boxing)

## 달리기

***

\[하단바] > \[미디어 추가] > \[미니게임]에서 실제로 작동하는 달리기 게임 스크립트입니다.

개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.

:person\_running: [<mark style="color:purple;">**달리기 예제**</mark>](/creator/tutor/sample/running)


# 타이머

**Example (Timer)**

```jsx
let _timer = 90;
let _stateTimer = 0;

App.onUpdate.Add(function(dt){
	_stateTimer += dt;
	
	if(_stateTimer >= 1){
		_stateTimer = 0;
		_timer -= 1;
	}

	
	if(_timer <= 0){
			// time over then...
	}
})
```

***


# 방탈출 타이머

### Example (방탈출)타이머

**예제 코드**

{% file src="/files/8JTEnZm0RZRUyilIHPpY" %}

````
// Set game time
const GAME_TIME = 60 * 90;
let ONE_SEC = 1;

App.onJoinPlayer.Add(function (player) {
  player.tag = {};
});


App.onUpdate.Add(function (dt) {
  if (ONE_SEC > 0) {
    ONE_SEC -= dt;
  } else {
    ONE_SEC = 1;
    for (let player of App.players) {
      
      if (player.tag.playtime) {
        if(player.tag.startTimer){
          player.tag.playtime += ONE_SEC;
        }
      } else {
        player.tag.playtime = ONE_SEC;
      }
      // When GAME_TIME is exceeded
      if (player.tag.playtime >= GAME_TIME) {
        let minutes = Math.floor((GAME_TIME - player.tag.playtime) * -1 / 60);
        let seconds = Math.floor((GAME_TIME - player.tag.playtime) * -1 % 60);
        let minutes_string = minutes < 10 ? "0" + String(minutes) : String(minutes);
        let seconds_string = seconds < 10 ? "0" + String(seconds) : String(seconds);

        showTimerLabel(player, "main", "⏰ Overtime : ", `${minutes_string} : ${seconds_string}`);
      }
      else {
        let minutes = Math.floor((GAME_TIME - player.tag.playtime) / 60);
        let seconds = Math.floor((GAME_TIME - player.tag.playtime) % 60);
        let minutes_string = minutes < 10 ? "0" + String(minutes) : String(minutes);
        let seconds_string = seconds < 10 ? "0" + String(seconds) : String(seconds);

        showTimerLabel(player, "main", "⏰ Remaining time : ",`${minutes_string} : ${seconds_string}`);
      }
    }
  }
});



// Set timer start location name
App.addOnLocationTouched("start_timer", function (player) {
  player.tag.startTimer = true;
});

// Set timer end location name
App.addOnLocationTouched("end_timer", function (player) {
  player.tag.startTimer = false;
});

// Set timer widget view
function showTimerLabel(player, key, text1, text2) {
	const isMobile = player.isMobile;
	const topGap = isMobile ? 10 : -2; 

	const labelPercentWidth = isMobile ? 50 : 20;
	const labelDisplayTime = 300000;

	const parentStyle = `
    display: flex; 
    align-items: center;
    justify-content: center;
    text-align: center;
    `;

	const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

	const highlightSpanStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`;

    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px 24px',
    }

	let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${highlightSpanStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```
````

### 방탈출 타이머 지정  영역&#x20;

방탈출 타이머를 사용하려면 지정 영역을 꼭 설정해주세요&#x20;

* `start_timer` : 이 영역을 지나면 타이머가 시작됩니다.&#x20;
* `end_timer` : 이 영역을 지나면 타이머가 종료됩니다.


# 사이드바 앱

사이드바 앱 위젯을 표시하고, x 버튼을 눌러 닫는 간단한 예제입니다.

위젯에 이미지를 넣으려면 예제 html 코드처럼 **이미지를 base64 인코딩**해서 **img 태그로 감싸**주어야 합니다.

#### **1) 파일**

{% file src="/files/uP7hB8LCKs66XNmC1LXk" %}

#### **2) main.js**

```jsx
// 사이드바 앱이 터치(클릭)되었을 때 동작하는 함수
App.onSidebarTouched.Add(function (p) {
	p.tag.widget = p.showWidget("widget.html", "sidebar", 350, 350);
	p.tag.widget.onMessage.Add(function (player, data) {
		if (data.type == "close") {
			player.showCenterLabel("위젯이 닫혔습니다.");
			player.tag.widget.destroy();
			player.tag.widget = null;
		}
	});
});

// 플레이어가 입장 할 때 동작하는 함수
App.onJoinPlayer.Add(function (p) {
	p.tag = {
		widget: null,
	};
});

// 플레이어가 퇴장 할 때 동작하는 함수
App.onLeavePlayer.Add(function (p) {
	if (p.tag.widget) {
		p.tag.widget.destroy();
		p.tag.widget = null;
	}
});
```

#### **3) 사이드바 앱이 실행된 모습**

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FNN4a88w8nE9DnHhwEBjf%2FUntitled.png?alt=media&amp;token=f7cf0abb-7f16-4a8d-b4ce-95250d102a4a" alt=""><figcaption></figcaption></figure></div>


# 좀비 게임

#### **1) 파일**

{% file src="/files/Vlf83QQBffAId3PVeIKs" %}

#### **2) main.js**

```jsx
// load sprite
let monster = App.loadSpritesheet('monster.png', 96, 96, {
    // defined base anim
    left: [8, 9, 10, 11],
    up: [12, 13, 14, 15],
    down: [4, 5, 6, 7],
    right: [16, 17, 18, 19],
}, 8);

const STATE_INIT = 3000;
const STATE_READY = 3001;
const STATE_PLAYING = 3002;
const STATE_JUDGE = 3004;
const STATE_END = 3005;

let _start = false; // Whether the game starts
let _players = App.players; // App.players : get total players
let _lastSurvivor = null;
let _zombieKing = [];
let _state = STATE_INIT;
let _stateTimer = 0; // Timer to check status value
let _live = 0; // number of survivors
let _resultstr;

function startApp()
{
    if(_players.length > 2)
    {
        // Set the number of zombies
        let zombiCnt = Math.floor(_players.length * 0.1);
        if(zombiCnt < 1)
            zombiCnt = 1;

        let allPlayer = [];
        let zombieIdx = [];

        for(let i = 0; i < _players.length; ++i)
        {
            allPlayer.push(_players[i]);
        }

        for(let i = 0; i < zombiCnt; ++i)
        {
            let index = Math.floor(allPlayer.length * Math.random());
            if(!zombieIdx.includes(allPlayer[index].id))
            {
                zombieIdx.push(allPlayer[index].id);
                allPlayer.splice(index, 1);
            }
        }

        // give players zombie attribute
        for(let i in _players)
        {
            let p = _players[i];
            // create and utilize option data using tags.
            p.tag = {};
            if(zombieIdx.includes(p.id))
            {
                p.tag.zombie = true;
                p.tag.attack = 0;
            }
            else
            {
                p.tag.zombie = false;
                p.tag.attack = 0;
                _live++;
            }
            
            // when player property changed have to call this method
            // 플레이어 속성 변경 시 반드시 호출하여 업데이트 한다.
            p.sendUpdated();
        }                                                                                               
        
        _start = true;
    }
    else
    {
        App.showCenterLabel(`Playable number of people: 3 or more`);
        startState(STATE_END);
    }
}

function startState(state)
{
    _state = state;
    _stateTimer = 0;

    switch(_state)
    {
        case STATE_INIT:
            startApp();
            break;
        case STATE_READY:
            App.showCenterLabel("The game will start soon.");
            for(let i in _players)
            {
                let p = _players[i];
                p.moveSpeed = 0;
                // Change the attribute of zombies
                if(p.tag.zombie)
                {
                    p.title = '<P:ZERO>';
                    p.sprite = monster;
                }
                p.sendUpdated();
            }
            break;
        case STATE_PLAYING:
            for(let i in _players)
            {
                let p = _players[i];
                // Change speed and label text according to player status
                if(p.tag.zombie)
                {
                    p.moveSpeed = 85;
                    p.showCenterLabel('Infect people!');
                }
                else
                {
                    p.moveSpeed = 80;
                    p.showCenterLabel('Survive from zombies!');
                }
                p.sendUpdated();
            }
            break;
        case STATE_JUDGE:
            for(let i in _players) {
                let p = _players[i];
                p.moveSpeed = 0;
                p.sendUpdated();
            }

            judgement(_live);
            break;
        case STATE_END:
            _start = false;

            for(let i in _players)
            {
                let p = _players[i];
                p.moveSpeed = 80;
                p.title = null;
                p.sprite = null;
                p.sendUpdated();
            }

            // Clear all objects in the map
            Map.clearAllObjects();
            break;
    }
   
}

function checkSuvivors()
{
    let resultlive = 0;
    for(let i in _players)
    {
        let p = _players[i];
        if(!p.tag.zombie)
        {
            _lastSurvivor = p;
            ++resultlive;
        }
    }

    return resultlive;
}

function judgement(number)
{   
    // The highest number of attacks among all players
    let attack = 0;

    for(let i in _players)
    {
        let p = _players[i];

        if(p.tag.attack > attack)
            attack = p.tag.attack;            
    }
    
    zombieKing = [];
    for(let i in _players)
    {   
        let p = _players[i];
        
        if(p.tag.attack == attack)
            zombieKing.push(p);
    }

    let index = Math.floor(Math.random() * zombieKing.length);

    if(number == 1) // when there are survivors
        resultstr = `${_lastSurvivor.name} is the last survivor!\nThe Strongest Zombie
 [` + zombieKing[index].name + '] Number of infections : ' + attack;
    else if(number == 0) // when there are no survivors
        resultstr = `No survivors.\nThe Strongest Zombie [` + zombieKing[index].name +
 '] Number of infections : ' + attack;
}

App.onStart.Add(function(){
    startState(STATE_INIT);
});

// when player join the space event
// 플레이어가 스페이스에 입장 했을 때 이벤트
App.onJoinPlayer.Add(function(p) {
    p.tag = {
        zombie : false,
        attack : 0,
    };

    if(_start) {
        p.tag.zombie = true;
        p.sprite = monster;
        p.sendUpdated();
        
        judgement(checkSuvivors());
    }   
    _players = App.players;
});

// when player leave the space event
// 플레이어가 스페이스를 나갔을 때 이벤트
App.onLeavePlayer.Add(function(p) {
    if(_start) {
        judgement(checkSuvivors());
    }

    p.title = null;
    p.sprite = null;
    p.moveSpeed = 80;
    p.sendUpdated();

    _players = App.players;
});

// when the game block is pressed event
// 게임 블록을 밟았을 때 호출되는 이벤트
App.onDestroy.Add(function() {
    App.stopSound();
});

// when player touched other player event
// 플레이어가 다른 플레이어와 부딪혔을 때 
App.onPlayerTouched.Add(function(sender, target, x, y) {
    if(_state != STATE_PLAYING)
        return;

    if(!sender.tag.zombie)
        return;

    if(target.tag.zombie)
        return;

    target.tag.zombie = true;
    target.sprite = monster;
    sender.tag.attack += 1;
    target.sendUpdated();
    
    _live = checkSuvivors();
    if(_live >= 2)
    {
        App.showCenterLabel(`${target.name} is infected!\n(${_live} survivors!)`);
        return;
    }
    else
        startState(STATE_JUDGE);
});

// called every 20ms
// 20ms 마다 호출되는 업데이트
// param1 : deltatime ( elapsedTime )
App.onUpdate.Add(function(dt) {
    if(!_start)
        return;
    
    _stateTimer += dt;

    switch(_state)
    {
        case STATE_INIT:
            App.showCenterLabel("Become a zombie and infect people. The host is faster
    .\nPeople do their best to run away and be the last survivor.");
            
            if(_stateTimer >= 5)
                startState(STATE_READY);
            break;
        case STATE_READY:
            if(_stateTimer >= 3)
                startState(STATE_PLAYING);
            break;
        case STATE_PLAYING:
            break;
        case STATE_JUDGE:
            App.showCenterLabel(resultstr);

            if(_stateTimer >= 5)
                startState(STATE_END);
            break;
        case STATE_END:
            break;    
    }
});
```


# 페인트맨 게임

#### **1) 파일**

{% file src="/files/YhkJSdMw7A27eWDtY6NK" %}

**2) main.js**

```jsx
// load sprite
let redman = App.loadSpritesheet('redman.png', 48, 64, {
    left: [5, 6, 7, 8, 9],       // defined base anim 
    up: [15, 16, 17, 18, 19],    // defined base anim 
    down: [0, 1, 2, 3, 4],       // defined base anim 
    right: [10, 11, 12, 13, 14], // defined base anim 
}, 8);

// load sprite
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9],
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
}, 8);

const STATE_INIT = 3000;
const STATE_READY = 3001;
const STATE_PLAYING = 3002;
const STATE_JUDGE = 3004;
const STATE_END = 3005;

// load sprite
let red = App.loadSpritesheet('red.png');
let blue = App.loadSpritesheet('blue.png');
let tomb = App.loadSpritesheet('tomb.png');

let _start = false;
let _gameEnd = false;
let _state = STATE_INIT;
let _stateTimer = 0;
let _timer = 90;

let _objects = {};

let _redScore = 0;
let _blueScore = 0;

// for using hash key
let HEIGHT_KEY = 10000000;

let _blueTeam = [];
let _redTeam = [];

let _players = App.players; // App.players : get total players
let TEAM_COUNTER = 0;
let _resultStr = '';

function startState(state)
{
    _state = state;
    _stateTimer = 0;

    switch(_state)
    {
        case STATE_INIT:
            _start = true;
            _stateTimer = 0;
            _timer = 90;

            _redScore = 0;
            _blueScore = 0;
            _objects = {};

            for(let i in _players) {
                let p = _players[i];
                 // create and utilize option data using tags.
                p.tag = {
                    x: p.tileX,
                    y: p.tileY,
                    sturn : false,
                    sTime : 1,
                    super : false,
                    team: Math.floor(TEAM_COUNTER % 2),
                };

                TEAM_COUNTER++;

                if(p.tag.team == 0)
                    _redTeam.push(p);
                else if(p.tag.team == 1)
                    _blueTeam.push(p);

                p.sprite = p.tag.team == 0 ? redman : blueman;
                p.sendUpdated();
            }
            break;
        case STATE_READY:
            for(let i in _players) {
                let p = _players[i];
                p.moveSpeed = 0;
                p.sendUpdated();
            }
            break;
        case STATE_PLAYING:
            for(let i in _players) {
                let p = _players[i];
                p.moveSpeed = 80;
                p.sendUpdated();
            }
            break;
        case STATE_JUDGE:
            for(let i in _players) {
                let p = _players[i];
                p.moveSpeed = 0;
                p.sendUpdated();
            }
            break;
        case STATE_END:
            _start = false;

            for(let i in _players) {
                let p = _players[i];
                p.moveSpeed = 80;
                p.title = null;
                p.sprite = null;
                p.sendUpdated();
            }

            // remove all objects created by the zep-scripts
            // 젭스크립트로 생성된 모든 오브젝트를 제거
            Map.clearAllObjects();
            break;
    }
}

App.onStart.Add(function(){
    startState(STATE_INIT);
});

// when player join the space event
// 플레이어가 스페이스에 입장 했을 때 이벤트
App.onJoinPlayer.Add(function(p) {
    p.tag = {
        x: p.tileX,
        y: p.tileY,
        sturn : false,
        sTime : 1,
        super : false,
        team: Math.floor(TEAM_COUNTER % 2),
    };

    if(p.tag.team == 0)
        _redTeam.push(p);
    else if(p.tag.team == 1)
        _blueTeam.push(p);

    TEAM_COUNTER++;
    p.nameColor = p.tag.team == 0 ? 16711680 : 255;
    p.sprite = p.tag.team == 0 ? redman : blueman;
    p.sendUpdated();

    _players = App.players;
});

// when player leave the space event
// 플레이어가 스페이스를 나갔을 때 이벤트
App.onLeavePlayer.Add(function(p) {
    p.moveSpeed = 80;
    p.title = null;
    p.sprite = null;
    p.sendUpdated();

    _players = App.players; // update all plyers for update(dt)
});

// when player touched objects event
// 플레이어가 오브젝트와 부딪혔을 때 
App.onDestroy.Add(function() {
    Map.clearAllObjects();
})

// when player attacked other player event (z key)
// 플레이어가 다른 플레이어를 공격혔을 때 (Z키)
App.onUnitAttacked.Add(function(sender, x, y, target) {
    if(_state != STATE_PLAYING)
        return;

    // not stun, not invincible, not on the same team
    // 스턴상태가 아니고, 무적이 아니고, 같은 팀이 아니면 스턴을 건다.
    if(!target.tag.sturn && sender.tag.team != target.tag.team && !target.tag.super)
    {
        target.tag.sturn = true;
        target.moveSpeed = 0;
        target.sendUpdated();
    }
});

// called every 20ms
// 20ms 마다 호출되는 업데이트
// param1 : deltatime ( elapsedTime )
App.onUpdate.Add(function(dt) {
    if(!_start)
        return;

    _stateTimer += dt;

    switch(_state)
    {
        case STATE_INIT:
            App.showCenterLabel("The team that has painted the most land wins.\nHitting another teammate stuns them for 1 second.");
            
            if(_stateTimer >= 5)
            {
                startState(STATE_READY);
            }
            break;
        case STATE_READY:
            App.showCenterLabel("The game will start soon.");
            if(_stateTimer >= 3)
            {
                startState(STATE_PLAYING);
            }
            break;
        case STATE_PLAYING:
            App.showCenterLabel(_timer +  `\nRED TEAM  VS  BLUE TEAM\n` + _redScore + "  VS  " + _blueScore);
            if(_stateTimer >= 1) {
                _stateTimer = 0;
                _timer--;
            }

            // time over
            if(_timer <= 0)
            {
                if(_redScore > _blueScore)
                {
                    for(let i in _players) {
                        let p = _players[i];
                        p.title = null;
                        if(p.tag.team == 1)
                        {
                            p.sprite = tomb;
                            p.moveSpeed = 0;
                            p.sendUpdated();
                        }
                    }
                    _resultStr = 'RED TEAM  VS  BLUE TEAM\n' + _redScore + "  VS  " + _blueScore + '\nRED TEAM WIN';
                }
                else if(_redScore < _blueScore)
                {
                    for(let i in _players) {
                        let p = _players[i];
                        p.title = null;
                        if(p.tag.team == 0)
                        {
                            p.sprite = tomb;
                            p.moveSpeed = 0;
                            p.sendUpdated();
                        }
                        _resultStr = 'RED TEAM  VS  BLUE TEAM\n' + _redScore + "  VS  " + _blueScore + '\nBLUE TEAM WIN';
                    }
                }
                else
                {
                    for(let i in _players) {
                        let p = _players[i];
                        p.title = null;
                        p.sprite = null;
                        p.sendUpdated();
                    }  
                    _resultStr = 'RED TEAM  VS  BLUE TEAM\n' + _redScore + "  VS  " + _blueScore + '\nDRAW';
                }
                startState(STATE_JUDGE);
            }
            else
            {
                for(let i in _players) {
                    let p = _players[i];
                    
                    // for speed buff
                    if(_timer == 30 || _timer == 20 || _timer == 10)
                    {
                        if(_redScore > _blueScore)
                        {
                            if(p.tag.team == 1)
                            {
                                // set player title
                                p.title = '<SPEED UP>';
                                p.moveSpeed = 90;
                                p.sendUpdated();
                            }
                            else
                            {
                                p.title = null;
                                p.moveSpeed = 80;
                                p.sendUpdated();
                            }
                        }
                        else if(_redScore < _blueScore)
                        {
                            if(p.tag.team == 0)
                            {
                                p.title = '<SPEED UP>';
                                p.moveSpeed = 90;
                                p.sendUpdated();
                            }
                            else
                            {
                                p.title = null;
                                p.moveSpeed = 80;
                                p.sendUpdated();
                            }
                        }
                    }

                    // strun state check
                    if(p.tag.sturn)
                    {
                        p.tag.sTime -= dt;
                        if(p.tag.sTime <= 0)
                        {
                            p.tag.sturn = false;
                            p.tag.super = true;
                            p.tag.sTime = 1;
                            p.moveSpeed = 80;
                            p.sendUpdated();
                        }
                    }

                    // invincible state check
                    if(p.tag.super)
                    {
                        p.tag.sTime -= dt;
                        if(p.tag.sTime <= 0)
                        {
                            p.tag.super = false;
                            p.tag.sTime = 1;
                            p.sendUpdated();
                        }
                    }

                    // paint tile and update score
                    if(p.tag.x != p.tileX || p.tag.y != p.tileY) {
                        p.tag.x = p.tileX;
                        p.tag.y = p.tileY;
                        
                        let oldValue = _objects[p.tileY * HEIGHT_KEY + p.tileX];
                        if(oldValue == p.tag.team)
                            continue;
        
                        if(oldValue == 0) {
                            _redScore--;
                        } else if(oldValue == 1) {
                            _blueScore--;
                        }
        
                        if(p.tag.team == 0)
                            _redScore++;
                        else
                            _blueScore++;
                        
                        _objects[p.tileY * HEIGHT_KEY + p.tileX] = p.tag.team;
                        
                        Map.putObject(p.tileX, p.tileY, p.tag.team == 0 ? red : blue,
                        {
                            overlap: true,
                        });
                    }
                }
            }
            break;
        case STATE_JUDGE:
            App.showCenterLabel(_resultStr);

            if(_stateTimer >= 5)
            {
                startState(STATE_END);
            }
            break;
        case STATE_END:
            break;
    }
});
```


# 초성 퀴즈

**1) 파일**

{% file src="/files/y88GaUnCOGeWHrRG3qld" %}

**2) main.js**

```jsx
const STATE_INIT = 3000;
const STATE_READY = 3001;
const STATE_PLAYING = 3002;
const STATE_JUDGE = 3004;
const STATE_END = 3005;

const WORD_LINES = [
    '한글날 휴게소 현기증 형광펜 호날두 허니문 하노이 핫도그 홍두깨 헤드셋 해돋이 한라봉 한라산 호랑이 허리띠 현미경 흰머리 황무지 햄버거 학부모 휘발유 허벅지 하수구 호신술 홍수아 화승총 허스키 햄스터 해운대 활주로 휴지통 화장품 회초리 핫팬츠 하회탈',
    '피규어 팔꿈치 포도주 피라냐 피라미 피뢰침 프리킥 프라하 포미닛 팥빙수 표백제 판소리 피시방 팔씨름 피아노 편의점 파자마 포장지 표지판 파충류 파출부 파출소 포청천 팬클럽 포켓볼 피카소 피카츄 폭탄주 피터팬',
    '태극기 태권도 턱걸이 테니스 트렁크 터미널 퇴마사 토마토 탬버린 투석기 턱시도 탕수육 토스트 태양계 티아라 타이밍 탈의실 트위터 퇴직금 통조림 태진아 태평양 테헤란',
    '캥거루 콧구멍 코끼리 캐나다 콩나물 코너킥 컨디션 코러스 카메라 카메오 키보드 콤바인 코뿔소 케이크 코코넛 카타르 칵테일 커플링',
    '참기름 철가방 초능력 축농증 취두부 청계천 책갈피 책꽂이 초능력 축농증 창덕궁 차두리 최루탄 최면술 칠면조 청바지 청소년 출석부 찹쌀떡 청와대 첫인상 치와와 초인종 추어탕 초음파 침전물 청첩장 초콜릿 치트키 출판사 챔피언 침팬지 최홍만',
    '계기판 개나리 기내식 강낭콩 교도관 고드름 골동품 기러기 가로등 가래떡 글러브 그림자 기모노 금메달 거머리 교무실 공무원 건망증 구미호 김병만 국방부 거북선 광복절 곱빼기 가속도 각선미 기숙사 가오리 걸음마 강의실 거짓말 교차로 골키퍼 과태료 김태원 김태희 건포도 곰팡이 골판지 공포탄 김흥국 광화문 공휴일 고현정 꽹과리 까나리 깍두기 꽃다발 꽃등심 꼽등이 까마귀 까치발 깐풍기',
    '케첩 킹카 킹콩 컨닝 채찍 창문 참깨 천국 축구 출근 친구 치과 취권 채권 초과 족발 절벽 젖병 주부 전복 중복 정복 절반 족보 쟁반 주번 좀비 제비 사과 사기 시급 시계 손금 선거 수능 설날 스님',
    '모래 미래 미로 만루 떡밥 딸기 떡국 두유 득음 뉴욕 노을 낙엽 녹용 노인 눈물 냉면 나비 나방 가위 거울 근육 기타 깃털 구토 굴뚝 개념 그네 구름 기린 경마 경매 고막 가방 공부 고백 간병 갈비 김밥 거봉 군밤 기분 건빵 가시'
];

let WORDS = [];

let _state = STATE_INIT;
let _stateTimer = 0;
let _timer = 0;
let _choanswer = '';
let _answer = '';
let _start = false;
let _widget = null; // using for contents UI
let _players = App.player;
let _result = '';

for(let w in WORD_LINES)
    WORDS =  WORDS.concat(WORD_LINES[w].trim().split(' '));

function cho_hangul(str) {
    cho = ["ㄱ","ㄲ","ㄴ","ㄷ","ㄸ","ㄹ","ㅁ","ㅂ","ㅃ","ㅅ","ㅆ","ㅇ","ㅈ","ㅉ","ㅊ","ㅋ","ㅌ","ㅍ","ㅎ"];
    result = "";
    for (let i = 0; i < str.length; ++i ) {
      code = str.charCodeAt(i)-44032;
      if(code>-1 && code<11172) result += cho[Math.floor(code/588)];
      else result += str.charAt(i);
    }
    return result;
}

App.onStart.Add(function(){
    startState(STATE_INIT);
});

// when chatting event
// 채팅을 치면 호출되는 이벤트
// player : person who chatted
// text : chat text
// return : enter chatting box
// return false or true : not appear in chatting box
App.onSay.add(function(player, text) {
    if(_state == STATE_PLAYING)
    {
        if(_answer == text)
        {
            _result = player.name + '님 정답!\n정답은 ' + _answer;

            startState(STATE_JUDGE);
        }
    }
});

function startState(state) {
    _state = state;
    _stateTimer = 0;

    switch(_state)
    {
        case STATE_INIT:
            if(_widget)
            {
                _widget.destroy();
                _widget = null;
            }
            _answer = WORDS[Math.floor(Math.random() * WORDS.length)];
            _timer = 60;
    
            _choanswer = cho_hangul(_answer);
    
            // called html UI
            // param1 : file name
            // param2 : position 
            // [ top, topleft, topright, middle, middleleft, middleright, bottom, bottomleft, bottomright, popup ]
            // param3 : width size
            // param4 : height size
            _widget = App.showWidget('widget.html', 'top', 200, 300);
            
            _widget.sendMessage({
                state: _state,
                timer: _timer,
                answer: _choanswer,
            });

            startState(STATE_READY);
            break;
        case STATE_READY:
            _start = true;
            startState(STATE_PLAYING);
            break;
        case STATE_PLAYING:
            App.showCenterLabel('목표: 초성힌트로 단어를 찾아내세요.',0xFFFFFF, 0x000000, 115);
            _widget.sendMessage({
                state: _state,
                timer: _timer,
                answer: _choanswer,
            });
            break;
        case STATE_JUDGE:
            break;
        case STATE_END:
            if(_widget)
            {
                _widget.destroy();
                _widget = null; // must to do for using again
            }

            _start = false;
            break;
    }
}

App.onLeavePlayer.Add(function(p) {
    p.title = null;
    p.sprite = null;
    p.moveSpeed = 80;
    p.sendUpdated();
});

App.onDestroy.Add(function() {
    _start = false;
    
    if(_widget)
    {
        _widget.destroy();
        _widget = null;
    }
});

App.onUpdate.Add(function(dt) {
    if(!_start)
        return;

    _stateTimer += dt;

    switch(_state)
    {
        case STATE_INIT:
            break;
        case STATE_READY:
            _start = true;
            break;
        case STATE_PLAYING:
            if(_stateTimer >= 1)
            {
                _stateTimer = 0;
                _timer -= 1;
            }

            if(_timer == 0)
            {
                _result = '정답은 ' + _answer + ' 입니다.';
                startState(STATE_JUDGE);
            }
            break;
        case STATE_JUDGE:
            App.showCenterLabel(_result, 0xFFFFFF, 0x000000, 115);

            if(_stateTimer >= 3)
                startState(STATE_END);
            break;
        case STATE_END:
            break;
    }
});
```


# 똥피하기 게임

#### **1) 파일**

{% file src="/files/FQUzpDPseJ1u2HXG3xJl" %}

#### **2) main.js**

```jsx
const poop = App.loadSpritesheet("poop.png");
let tomb = App.loadSpritesheet("tomb.png", 32, 48, {
	left: [0],
	right: [0],
	up: [0],
	down: [0],
});

const STATE_INIT = 3000;
const STATE_READY = 3001;
const STATE_PLAYING = 3002;
const STATE_JUDGE = 3004;
const STATE_END = 3005;
const STATE_INTRO = 3006;

let _level = 1;
let _levelTimer = 15;
let _levelAddTimer = 0;

let _start = false;
let _ending = false;
let _timer = 5;

let _poops = [];
let _stateTimer = 0;

let _genTime = 0;
let _dropTime = 0;
let _flushTime = 0;

let _viewPlayer = false;
let _viewTimer = 1;
let _viewAddTimer = 0;

let _live = 0;
let _liveList = "";

let _widget = null;
let _widget2 = null;
let _players = App.players;

let HEIGHT_KEY = 10000000;
let _areaPoint = {};

let poopkeys = [];

// CustomLabel을 span 태그로 스타일을 지정해출력하는 함수
function customShowLabelWithRadius(player, str, width, radius) {
	if (player) {
		if (player.isMobile) {
			width = 100;
		}
	}
	// span 태그
	let spanStyle = `<span style="
			position: absolute;
			margin: auto;
			display: flex;
			align-items: center;
			justify-content: center;
			padding: 10px;
			width: ${width}%;
			top: 200px;
			left: ${(100 - width) / 2}%;
			background-color: rgba(0, 0, 0, 0.6);
			flex-direction: column;
			border-radius: ${radius}px;">`;

	let res = spanStyle + str + "</span>";
	if (player) {
		player.showCustomLabel(res, 0xffffff, 0x000000, -150, 100, 1);
	} else {
		App.showCustomLabel(res, 0xffffff, 0x000000, -150, 100, 1);
	}
}

// 모든 플레이어에게 라벨을 출력하는 함수
function showLabel(str) {
	for (let i in _players) {
		let p = _players[i];
		customShowLabelWithRadius(p, str, 40, 14);
	}
}

function startState(state) {
	_state = state;
	_stateTimer = 0;

	if (_widget) {
		_widget.destroy();
		_widget = null;
	}

	switch (_state) {
		case STATE_INTRO:
			// 게임시작 라벨 및 사운드 출력
			showLabel("\n 미니게임 - 똥피하기 \n\n");
			App.playSound("intro.ogg");

			// 게임시작 변수(_start)를 true로 변경
			_start = true;
			break;

		case STATE_INIT:
			// 게임 목표 라벨 출력 및 게임 변수 초기화
			showLabel("- 게임목표 - \n\n 위에서 떨어지는 똥을 피해 마지막까지 생존하세요 \n\n ( 잠시 후 게임이 시작됩니다 )");

			_stateTimer = 0;
			_genTime = 0;
			_dropTime = 0;
			_timer = 90;

			for (let i in _players) {
				let p = _players[i];
				p.tag = {
					alive: true,
				};
			}
			break;

		case STATE_PLAYING:
			break;

		case STATE_JUDGE:
			// 최종 생존자 라벨 출력
			Map.clearAllObjects();
			App.playSound("result.ogg", false);

			if (_live == 1) {
				showLabel(`- 최종 생존자는 - \n\n ${lastSurvivor.name}`);
			} else if (_live == 0) {
				showLabel(`생존자가 없습니다.`);
			} else {
				showLabel(`- 최종 생존자는 - \n\n ${_liveList}`);
			}

			break;

		case STATE_END:
			// 게임시작 변수(_start)를 false로 변경, 모든 플레이어의 속도 80, 스프라이트를 null(기본상태)로 변경
			_start = false;
			for (let i in _players) {
				let p = _players[i];
				p.sprite = null;
				p.moveSpeed = 80;
				p.sendUpdated();
			}
			App.stopSound(); // 사운드 Off
			break;
	}
}

App.onInit.Add(function () {
	if (_widget) {
		_widget.destroy();
		_widget = null;
	}
});

App.onStart.Add(function () {
	startState(STATE_INTRO);
});

App.onJoinPlayer.Add(function (p) {
	// 게임이 시작 된 후 새로운 플레이어가 입장하면 탈락으로 처리
	if (_start) {
		p.tag = {
			alive: false,
		};

		p.moveSpeed = 20;
		p.sprite = tomb;
		p.sendUpdated();
	} else {
		p.moveSpeed = 80;
		p.sprite = null;
		p.sendUpdated();
	}
	_players = App.players;
});

// 플레이어가 나갈 때 sprite title moveSpeed 초기화
App.onLeavePlayer.Add(function (p) {
	p.title = null;
	p.sprite = null;
	p.moveSpeed = 80;
	p.sendUpdated();

	_players = App.players;
});

// 똥 오브젝트에 닿았을 때
App.onAppObjectTouched.Add(function (player, key, x, y, tileID) {
	// 플레이어가 탈락하지 않은 상태일 경우 똥에 닿으면 탈락
	if (player.tag.alive) {
		_viewPlayer = true;
		_viewAddTimer = 0;
		player.tag.alive = false;
		player.sprite = tomb;
		player.moveSpeed = 20;
		player.sendUpdated();
		App.playSound("poopp.mp3");
		if (checkSuvivors() !== 1) {
			showLabel(`${player.name} 님 탈락!`);
		}
	}
});

// 생존자 수를 체크하는 자바스크립트 일반 함수
function checkSuvivors() {
	if (!_start) return;

	let alive = 0;
	for (let i in _players) {
		let p = _players[i];
		if (!p.sprite) {
			lastSurvivor = p;
			++alive;
		}
	}
	return alive;
}

// 최종 생존자의 이름을 _liveList에 추가하는 자바스크립트 일반 함수
function broardCastingSuvivors() {
	if (!_start) return;
	_liveList = "";
	for (let i in _players) {
		let p = _players[i];
		if (p.tag.alive) {
			_liveList += p.name + " ";
		}
	}
}

// 게임 종료 블록을 눌렀을 때
App.onDestroy.Add(function () {
	// 스크립트로 설치한 모든 오브젝트 제거
	Map.clearAllObjects();

	if (_widget) {
		_widget.destroy();
		_widget = null;
	}
	// 사운드 전체 Off
	App.stopSound();
});

App.onUpdate.Add(function (dt) {
	if (!_start) return;

	_stateTimer += dt;
	switch (_state) {
		case STATE_INTRO:
			// STATE_INTRO 상태에서 3초동안 똥피하기 인트로 라벨을 보여주고 STATE_INIT 로직 실행
			if (_stateTimer >= 3) {
				startState(STATE_INIT);
			}
			break;
		case STATE_INIT:
			// STATE_INIT 상태에서 3초동안 게임 목표 라벨을 보여주고 STATE_INIT 로직 실행
			if (_stateTimer >= 3) {
				startState(STATE_PLAYING);
			}
			break;

		case STATE_PLAYING:
			// 탈락 메시지를 표시중이지 않을 때 라벨을 표시하도록 처리
			if (!_viewPlayer) {
				if (_level < 6) {
					showLabel(`- 레벨 ${_level} - \n ${_timer} 초`);
				} else {
					showLabel(`- 최고레벨 - \n ${_timer} 초`);
				}
			}
			_genTime -= dt;
			let poopkey;
			if (_genTime <= 0) {
				// 오브젝트 생성주기(genTime)가 똥피하기 난이도 단계(_level)에 따라 짧아짐
				_genTime = Math.random() * (0.5 - _level * 0.05);
				let rand_X = Math.floor(Map.width * Math.random());

				// 똥 오브젝트의 key 값 생성
				poopkey = new Date().getTime() + Math.random();
				poopkeys.push(poopkey);

				// 똥피하기 난이도 단계(_level)에 따라 똥의 이동속도가 증가
				Map.putObjectWithKey(rand_X, 0, poop, {
					overlap: true,
					movespeed: 80 + _level * 10,
					key: poopkey,
				});

				// 똥 오브젝트를 맵 끝으로 이동
				Map.moveObjectWithKey(poopkey, rand_X, Map.height - 1, false);
			}

			//_flushTime(3초) 마다 맵 하단에 쌓인 똥 오브젝트를 삭제
			_flushTime += dt;
			if (_flushTime >= 3) {
				_flushTime = 0;
				for (let i = 0; i < poopkeys.length; i++) {
					let key = poopkeys[i];
					if (Map.getObjectWithKey(key).tileY == Map.height - 1) {
						Map.putObjectWithKey(Map.getObjectWithKey(key).tileX, Map.height - 1, null, {
							key: key,
						});
						poopkeys.splice(i--, 1);
					}
				}
			}

			// _levelTimer(15초) 마다 난이도 단계가 1씩 증가
			_levelAddTimer += dt;
			if (_levelAddTimer >= _levelTimer) {
				_level++;
				_levelAddTimer = 0;

				// 최고 난이도 단계를 6으로 제한
				if (_level > 6) {
					_level = 6;
				}
			}

			// 생존자 수 체크 함수 호출
			_live = checkSuvivors();

			// 생존자 수 가 1명 또는 0명일 경우 게임 종료 로직 실행
			if (_live == 1 || _live == 0) {
				startState(STATE_JUDGE);
			} else {
				if (_stateTimer >= 1) {
					_stateTimer = 0;
					_timer--;
					// 시간(_timer)변수가 0이 되면 최종 생존자 발표
					if (_timer <= 0) {
						broardCastingSuvivors();
						startState(STATE_JUDGE);
					}
				}
			}

			// 플레이어가 탈락 한 경우 1초 동안 탈락 메시지를 보여주고 _viewPlayer를 false로 변경
			if (!_viewPlayer) {
			} else {
				_viewAddTimer += dt;
				if (_viewAddTimer >= _viewTimer) {
					_viewAddTimer = 0;
					_viewPlayer = false;
				}
			}
			break;

		case STATE_JUDGE:
			// STATE_JUDGE 상태에서 5초 동안 최종 생존자 라벨을 보여준 뒤 게임 종료로직 실행
			if (_stateTimer >= 5) {
				startState(STATE_END);
			}
			break;
		case STATE_END:
			break;
	}
});

```

***


# 결투 게임

#### **1) 파일**

{% file src="/files/tzkMXs8BTpeDIgqDAVTJ" %}

#### **2) main.js**

```jsx
let ghost = App.loadSpritesheet("ghost.png", 32, 48, {
	left: [2],
	right: [1],
	up: [3],
	down: [0],
});

let redBoxing = App.loadSpritesheet("redBoxing.png");

const STATE_INTRO = 3001;
const STATE_INIT = 3002;
const STATE_RULE = 3003;
const STATE_PLAYING = 3004;
const STATE_JUDGE = 3005;
const STATE_END = 3006;

let lastSurvivor = null;
let _start = false;
let _players = App.players;

let _state = STATE_INIT;
let _stateTimer = 0;

let _alive = 0;

let _widget = null;

function init() {
	for (let i in _players) {
		let p = _players[i];
		setHPgage(p, p.tag.hp);
		p.sendUpdated();
	}
	_alive = checkSuvivors();
}

function startState(state) {
	_state = state;
	_stateTimer = 0;
	switch (_state) {
		case STATE_INTRO:
			for (let i in _players) {
				let p = _players[i];
				if (p.tag.widget) {
					p.tag.widget.destroy();
					p.tag.widget = null;
				}
			}
			// 게임시작
			_start = true;
			_widget = App.showWidget("intro.html", "middle", 350, 340);
			App.playSound("intro.wav");
			break;
		case STATE_INIT:
			init();
			break;
		case STATE_RULE:
			_widget = App.showWidget("rule.html", "middle", 400, 200);

			for (let i in _players) {
				let p = _players[i];
				p.moveSpeed = 0;
				p.sendUpdated();
			}
			break;
		case STATE_PLAYING:
			if (_widget) {
				_widget.destroy();
				_widget = null;
			}

			_widget = App.showWidget("status.html", "top", 700, 300);

			for (let i in _players) {
				let p = _players[i];
				p.moveSpeed = 80;
				p.sendUpdated();
			}

			break;
		case STATE_JUDGE:
			break;
		case STATE_END:
			if (_widget) {
				_widget.destroy();
				_widget = null;
			}

			_start = false;

			for (let i in _players) {
				let p = _players[i];
				p.sprite = null;
				p.attackSprite = null;
				p.title = null;
				p.sprite = null;
				p.moveSpeed = 80;
				p.sendUpdated();
			}

			Map.clearAllObjects();
			break;
	}
}

// 체력바 표시 함수
function setHPgage(p, hp) {
	switch (hp) {
		case 5:
			p.title = "▮▮▮▮▮";
			break;
		case 4:
			p.title = "▮▮▮▮";
			break;
		case 3:
			p.title = "▮▮▮";
			break;
		case 2:
			p.title = "▮▮";
			break;
		case 1:
			p.title = "▮";
			break;
	}
}

// 생존자 수 체크 함수
function checkSuvivors() {
	let alive = 0;
	for (let i in _players) {
		let p = _players[i];
		if (p.tag.alive) {
			lastSurvivor = p;
			++alive;
		}
	}
	return alive;
}

App.onStart.Add(function () {
	startState(STATE_INTRO);
});

App.onJoinPlayer.Add(function (p) {
	p.tag = {
		widget: null,
		alive: true,
		hp: 5,
		shield: false,
		time: 1, // 피격 후 1초간 무적 상태를 설정하기 위한 속성
	};

	p.attackSprite = redBoxing;

	// 게임 시작 후 새로운 플레이어 입장시 해당 플레이어를 죽음으로 처리
	if (_start) {
		p.moveSpeed = 5;
		p.sprite = ghost;
		p.tag.alive = false;
		p.sendUpdated();
	}

	_players = App.players;
});

App.onLeavePlayer.Add(function (p) {
	p.attackSprite = null;
	p.title = null;
	p.sprite = null;
	p.moveSpeed = 80;
	p.sendUpdated();

	_players = App.players;
});

// 다른 플레이어를 공격할 때 
App.onUnitAttacked.Add(function (sender, x, y, target) {
	if (_state != STATE_PLAYING) return;

	// 공격자가 죽은 상태이면 return
	if (!sender.tag.alive) return;

	// 타겟이 살아 있는 상태이면서 shield가 false인 경우
	if (target.tag.alive && !target.tag.shield) {
		target.tag.hp--; // 타겟의 체력이 1 만큼 감소

		// 타겟의 체력이 0이된 경우
		if (target.tag.hp == 0) {
			target.title = null; // 타이틀 삭제
			target.tag.alive = false; // alive 속성 false
			target.sprite = ghost; // 캐릭터 이미지를 ghost로 변경
			target.moveSpeed = 5; // moveSpeed 80 -> 5
			target.sendUpdated(); // 타겟 속성 업데이트

			_alive = checkSuvivors();
			// 생존자가 1명이면
			if (_alive == 1) {
				if (_widget) {
					_widget.destroy();
					_widget = null;
				}
				// 접속중인 모든 플레이어에게 result.html 위젯을 보여줌
				_widget = App.showWidget("result.html", "top", 1055, 500);
				// 위젯에 표시할 마지막 생존자의 닉네임을 전달
				_widget.sendMessage({
					alive: _alive,
					name: lastSurvivor.name,
				});

				App.playSound("result.wav");

				_stateTimer = 0;
				startState(STATE_JUDGE);
			}
		} else {
			// 타겟을 공격하면 타겟이 1초간 무적 상태가 됨. (shield = true)
			target.tag.shield = true;
			setHPgage(target, target.tag.hp);
			target.sendUpdated();
		}
	}
});

App.onUpdate.Add(function (dt) {
	if (!_start) return;

	_stateTimer += dt;
	switch (_state) {
		case STATE_INTRO:
			// intro.html 위젯을 5초 동안 표시하고 STATE_INIT 시작
			if (_stateTimer >= 5) {
				if (_widget) {
					_widget.destroy();
					_widget = null;
				}
				App.stopSound();

				startState(STATE_INIT);
			}
			break;
		case STATE_INIT:
			startState(STATE_RULE);
			break;

		// rule.html 위젯을 3초 동안 표시하고 STATE_PLAYING 시작
		case STATE_RULE:
			if (_stateTimer >= 3) {
				if (_widget) {
					_widget.destroy();
					_widget = null;
				}
				startState(STATE_PLAYING);
			}
			break;

		case STATE_PLAYING:
			// status.html 위젯의 생존자 수 업데이트
			if (_widget) {
				_widget.sendMessage({
					suvivors: _alive,
				});
			}

			for (let i in _players) {
				let p = _players[i];

				// 플레이어가 죽은 상태라면 건너뜀
				if (!p.tag.alive) continue;

				// 피격 후 1초가 지나면 shield 속성을 false로 변경
				if (p.tag.shield) {
					p.tag.time -= dt;
					if (p.tag.time <= 0) {
						p.tag.shield = false;
						p.tag.time = 1; // shield 지속시간 1초로 초기화
					}
				}
			}

			_alive = checkSuvivors();

			// 생존자가 1명 또는 0명일 경우 result.html 위젯 표시
			if (_alive == 1) {
				if (_widget) {
					_widget.destroy();
					_widget = null;
				}

				_widget = App.showWidget("result.html", "top", 1055, 500);
				_widget.sendMessage({
					alive: _alive,
					name: lastSurvivor.name,
				});

				App.playSound("result.wav");
				startState(STATE_JUDGE);
			} else if (_alive == 0) {
				if (_widget) {
					_widget.destroy();
					_widget = null;
				}

				_widget = App.showWidget("result.html", "top", 1055, 500);
				_widget.sendMessage({
					alive: _alive,
				});

				App.playSound("result.wav");
				startState(STATE_JUDGE);
			}

			break;
		//result.html 위젯을 5초 동안 표시하고 STATE_END 시작
		case STATE_JUDGE:
			if (_stateTimer >= 5) {
				startState(STATE_END);
			}
			break;
		case STATE_END:
			break;
	}
});

// App이 종료되거나 Game Block이 파괴 될 때 실행되는 함수
App.onDestroy.Add(function () {
	// 앱으로 설치된 모든 오브젝트 삭제
	Map.clearAllObjects();
});
```


# 달리기

#### **1) 파일**

{% file src="/files/cmgvOgOGGkHUkTRg1FSW" %}

#### **2) main.js**

```jsx
const STATE_INIT = 3000;
const STATE_READY = 3001;
const STATE_PLAYING = 3002;
const STATE_JUDGE = 3004;
const STATE_END = 3005;
const STATE_INTRO = 3006;

let _players = App.players; 
let _state = STATE_INIT; 
let _start = false; 
let _stateTimer = 0; 
let _countDown = 10; 
let _finishCountDown = false;
let _finishCount = 30;
let _finishTimer = 0; 
let _delayTimer = 0; 
let _playerBaseSpeed = 120; 
let _rank = 1; 
let _rankList = []; 

//모든 플레이어가 결승점을 통과한 경우 처리  
function finishCheck(){
	
	let allPlayer = 0; 
	let finishedPlayer = 0; 

	for(let p of _players){

		if(!p)
			continue; 

		if(p.tag.isNotPlayer)
			continue; 

		if(p.tag.isFinish)
			finishedPlayer++;
		
		allPlayer++; 
	}

	if(finishedPlayer == allPlayer)
		return true 
	
	return false; 
}

//달리기 맵 설정이 정상적으로 되어있는 지 확인하고, 아닌 경우 에러메세지를 리턴한다. 
function checkSetting(){

	let warningMsg = '';

	if (!Map.hasLocation("race_start_point")) {
		warningMsg = 'race_start_point';

	}
	if (!Map.hasLocation("race_end_point")) {
		warningMsg = 'race_end_point';

	}
	if (!Map.hasLocation("race_finish_point")) {
		warningMsg = 'race_finish_point';

	}
	
	return warningMsg; 
}

function startState(state){

	_state = state; 
  	_stateTimer = 0; 

  	switch (state) {
		
    	case STATE_INIT:
     		for(let p of _players){
				//달리기 앱을 실행한 유저를 확인한다.
				if(p.id == App.creatorID){
					if(p.isMobile)
					//유저의 환경이 모바일인 경우의 위젯 
						p.tag.widget = p.showWidget("setting.html", "bottom", 440, 340);
					else
					//유저의 환경이 pc인 경우의 위젯
						p.tag.widget = p.showWidget("setting.html", "middle", 440, 340);
					
					p.tag.widget.sendMessage({ 
						str_title         : 'Run',
						str_title_text1   : "A race to see who reaches the finish line the fastest",
						str_title_text2   : 'Click the Start Running button to start the game',
						str_title_start   : "Start",
						str_title_how     : "How to set up a map"
					});
					
					p.tag.widget.onMessage.Add(function (sender, msg) {
						//게임을 취소하는 경우, 나타난 위젯을 제거한다 
						if (msg.type == "cancle") {
							if(p.tag.widget_warning){
								p.tag.widget_warning.destroy();
								p.tag.widget_warning = null;
							}

							if(p.tag.widget){
								p.tag.widget.destroy();
								p.tag.widget = null;
							}
						
						} else {
							// 맵에 필요한 설정 체크							
							let warning = checkSetting();
		
							if(warning !== ''){
								if(p.tag.widget_warning){
									p.tag.widget_warning.destroy();
									p.tag.widget_warning = null;
								}
		
								if(p.isMobile)
									p.tag.widget_warning = App.showWidget("warning.html", "top", 440, 60);
								else 
									p.tag.widget_warning = App.showWidget("warning.html", "bottom", 440, 250);
								
								p.tag.widget_warning.sendMessage({
									str_warningText :  'Race track needs to be setup',
									str_warningText2 : ' needed)',
									str_warningText3 : 'Run',
									warningMsg: warning,
								})
							} else {
								if(p.tag.widget){
									p.tag.widget.destroy();
									p.tag.widget = null;
								}
								
								startState(STATE_INTRO);
								_start = true;
							}
						}
					})  
				}
			}
    		break;

		case STATE_INTRO: 
			App.showCenterLabel("\n minigame - RUN \n\n", 0xffffff, 0x000000, 120); ;
			break; 
		
		case STATE_READY:
			for(let p of _players){
				if(p.tag.start)
				   continue; 
				
				//모든 플레이어가 "race_start_point" 로케이션으로 이동한다 
				p.spawnAtLocation("race_start_point"); 

				p.moveSpeed = 0; 
			   	p.sendUpdated();
			}
			break;
		
		case STATE_PLAYING:	
			
			App.showCenterLabel("Start!!", 0xffffff, 0x000000, 120); ;
			
			for(let p of _players){
				if(p.tag.start)
				continue; 
				p.moveSpeed = _playerBaseSpeed; 
				p.sendUpdated();
			}
			break

		// 게임 결과 처리 
		case STATE_JUDGE:
			for(let i =0 ; i <_rankList.length; i++){
				let rank = ''; 
				if(i == 0)
					rank = '1st'; 
				else if(i == 1)
					rank = '2nd';
				else if(i == 2)
					rank = '3rd'; 
				else
					rank = `${i + 1}th`;
				//게임종료 후 등수 표시 
				App.sayToAll(`${rank} : ${_rankList[i].name} !`);
			}
			for(let p of _players){
				p.moveSpeed = 80; 
				p.sendUpdated();
			}
			_start = false;

			break

		// 게임 종료 처리 
		case STATE_END:
			for(let p of _players){
				//게임이 종료되면 모든 플레이어는 "race_end_point" 로케이션으로 이동한다 
				p.spawnAtLocation("race_end_point"); 
				
				if(p.tag.isNotPlayer)
					p.tag.isNotPlayer = false;
			}
			_start = false;
			
			break
  }
}

App.onLeavePlayer.add(function(p){
	_players = App.players;

	p.moveSpeed = 80; 
	p.sendUpdated();

})

// 플레이어가 해당 맵에 들어왔을 때 처리
App.onJoinPlayer.Add(function (p) {
	p.tag = {};
  
	//게임시작 후 들어온 플레이어 처리 
	if (_start) {
		p.tag.isNotPlayer = true;  

	} else {
		p.tag.isNotPlayer = false; 
		p.tag.speedTimer = 0;
		p.tag.isFinish = false;  
		p.tag.speedChnage = false; 
	  	p.sendUpdated();
	}
	//게임이 시작된 후 들어온 플레이어는 'race_end_point' 로케이션으로 이동시킨다 
	if(p.tag.isNotPlayer)
		p.spawnAtLocation("race_end_point"); 

	_players = App.players;
  });

App.onStart.Add(function(){
	startState(_state);

	//"speed_set_40", "speed_set_60", "speed_set_140", "speed_set_160" 으로 지정한 로케이션을 밟으면 
	//2초간 해당 로케이션 이름의 숫자와 같이 플레이어의 속도가 변한다. (2초 후 플레이어의 속도는 _playerBaseSpeed 가 된다 )
	//ex) "speed_set_40"를 밟으면 속도가 40으로 변함 
	let speed = [40,60,140,160]; 

	for(let i =0; i<speed.length; i++){
		App.addOnLocationTouched(`speed_set_${speed[i]}`, function(p){

			if(_state !== STATE_PLAYING)
				return; 

			p.moveSpeed = speed[i]; 
			p.tag.speedTimer = 0; 
			p.tag.speedChnage = true;

			//로케이션에 설정된 속도에 따라 "속도가 빨라졌습니다", "속도가 느려졌습니다" 등의 라벨이 해당 플레이어에게 띄워진다
			let str = '';
			if(i == 0)
				str = 'Speed greatly reduced';
			else if(i == 1)
				str = 'Speed reduced';
			else if(i == 2)
				str = 'Speed increased';
			else
				str = 'Speed greatly increased';

			p.showCenterLabel(`${str}`, 0xffffff, 0x000000, 120); 
			p.sendUpdated();	
		})
	}

	//"speed_set_random" 로케이션을 밟는 경우 플레이어의 속도가 40, 60, 140, 160 중 하나의 속도로 2초간 바뀐다 (2초 후 플레이어의 속도는 _playerBaseSpeed 가 된다 )
	App.addOnLocationTouched(`speed_set_random`, function(p){
		if(_state !== STATE_PLAYING)
			return; 

		p.moveSpeed = speed[Math.floor(Math.random() * 4)]; 
		//로케이션에 설정된 속도에 따라 "속도가 빨라졌습니다", "속도가 느려졌습니다" 등의 라벨이 해당 플레이어에게 띄워진다
		let str = '';
		if(p.moveSpeed == 40)
			str = 'Speed greatly reduced';
		else if(p.moveSpeed == 60)
			str = 'Speed reduced';
		else if(p.moveSpeed == 140)
			str = 'Speed increased';
		else
			str = 'Speed greatly increased';

		p.showCenterLabel(`${str}`, 0xffffff, 0x000000, 120); 
		p.tag.speedTimer = 0; 
		p.tag.speedChnage = true;
		p.sendUpdated();
	})

	//플레이어가 결승선을 통과하는 경우
	App.addOnLocationTouched("race_finish_point", function(p){
		if(p.tag.isFinish)
			return; 

		if(p.tag.isNotPlayer)
			return;

		_delayTimer = 0; 

		// 처음으로 플레이어가 결승선을 통과하는 경우 레이스 종료 카운트 다운을 시작한다(디폴트 30초)
		if(!_finishCountDown)
			_finishCountDown = true;

		// 플레이어가 결승선을 최초에 통과한 경우를 체크한다 
		p.tag.isFinish = true; 
		_rankList.push(p); 
		_rank++;
	})
});
 
App.onUpdate.Add(function(dt){
	_stateTimer += dt; 

	if(_start){
		for(let p of _players){
			if(p.tag.isNotPlayer)
				p.showCenterLabel("game is in progress, please wait..",0xffffff, 0x000000, 120);
		}
	}

	switch (_state) {
		case STATE_INIT:
		break;
		case STATE_INTRO: 
			if(_stateTimer >= 3){
				startState(STATE_READY);
			}
			break; 
		case STATE_READY:
			//출발선에서 _countDown 초(디폴트 10초) 간 레이스 카운트 다운을 시작한다 
			App.showCenterLabel(`${_countDown}  seconds later the race will start. `,0xffffff, 0x000000, 120); 

			if(_stateTimer >= 1){
				_stateTimer = 0; 
				_countDown --; 
			} 
			
			//카운트가 종료되면 (_countDown 가 0이 되면) 레이스가 시작된다  
			if(_countDown <= 0)
				startState(STATE_PLAYING); 
			break;
		case STATE_PLAYING:
			
			for(let p of _players){

				if(finishCheck() && _rankList.length == 0){
					p.showCenterLabel("There is no winner",0xffffff, 0x000000, 120);
					p.spawnAtLocation("race_end_point"); 
					startState(STATE_END);
				}
				// else if(finishCheck())
				// 	startState(STATE_JUDGE);
				
				if(p.tag.isNotPlayer)
					continue; 
				if(p.tag.speedChnage)
					p.tag.speedTimer += dt; 
				
				//어떤 속도 지정 타일을 밟아도 2초 후 기본 속도(_playerBaseSpeed)로 돌아온다 
				if(p.tag.speedTimer >= 2 && p.tag.speedChnage){
					p.moveSpeed = _playerBaseSpeed; 
					p.tag.speedChnage = false; 
					p.sendUpdated(); 
				}
			}
			
			if(_finishCountDown)
				{	
					_delayTimer += dt; 
					//플레이어가 결승선을 통과하면 1.5초간 플레이어의 이름과 레이스 카운트다운을 라벨로 나타낸다 
					if(_delayTimer <=1.5)
						App.showCenterLabel(`${_rankList[_rankList.length -1].name}  ${_rank - 1} place ! \n\n ${_finishCount} seconds later the race will end`,0xffffff, 0x000000, 120);
					else
						App.showCenterLabel(`${_finishCount} seconds later the race will end`,0xffffff, 0x000000, 120);

					_finishTimer += dt; 

					if(_finishTimer >= 1){
						_finishCount--; 
						_finishTimer = 0; 
					}
					// 레이스 카운트다운이 종료(0초)되었거나, 모든 플레이어가 결승선을 통과한 경우, 다음 단계로 넘어간다  
					if(_finishCount == 0 || finishCheck()){
						startState(STATE_JUDGE); 
					}
				}
			break; 
		case STATE_JUDGE:
			//5초간 우승자(1등)의 이름을 라벨로 나타낸다 
			if(_stateTimer <= 5){
				App.showCenterLabel(`- Winner - \n\n ${_rankList[0].name}`,0xffffff, 0x000000, 120);
			} 
			//5초간 "잠시 후 대기실로 이동합니다"라는 라벨을 띄웁다 
			else if(_stateTimer > 5 && _stateTimer <= 10 ){
				App.showCenterLabel("You will soon be transported to the waiting room",0xffffff, 0x000000, 120);
			}
			else {
				startState(STATE_END);
			}
			break;
		case STATE_END:
			
			break;
	}
});
```


# Custom Label 예제 코드

Cutom Label 예제 코드를 통해 미니게임에서 자주 쓰이는 Label을 구현할 수 있습니다.

### 예제코드

예제코드를 다운받아서 자신의 스페이스에 노말 앱으로 설치해보세요

0-9까지 숫자를 누르면 타입에 따른 Custom Label이 보여집니다.

{% file src="/files/A7z02qZALv5DrQhl85sJ" %}

### 타입 별 예제코드 보러가기

[**Type-A >** ](/creator/tutor/custom-label/type-a)

Type-A는 미니게임의 종류를 알려줄 때 인트로 Custom Label로 자주 사용됩니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FbeAO8wnNLsTfz5kPAbiQ%2FMask%20group-1.png?alt=media&amp;token=062da90b-a68a-49ea-83fc-de25aa72d3f1" alt=""><figcaption></figcaption></figure>

[**Type-B >** ](/creator/tutor/custom-label/type-b)

Type-B는 미니 게임에서 목표를 알려줄 때 자주 쓰이며, 두 줄로 된 긴문장을 쓸 때 주로 사용합니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FXSUkBqKFq7EuFbiD8HoA%2FMask%20group-2.png?alt=media&amp;token=31892b3b-fa86-4672-b059-b7baa22e5c10" alt=""><figcaption></figcaption></figure>

[**Type-C >** ](/creator/tutor/custom-label/type-c)

Type-C는 페인트 맨 팀 전 게임에서 사용된 Custom Label로, 대결 구도의 게임에서 자주 사용됩니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FPkZ0wwCvlFeuAOF4SSvw%2FMask%20group-3.png?alt=media&amp;token=133cdc37-c681-49ba-a246-0ca3fb3bffc5" alt=""><figcaption></figcaption></figure>

[**Type-D >**](/creator/tutor/custom-label/type-d)

Type-D는 Type-C와 마찬가지로 팀 전 점수를 기록할 때 자주 쓰이며, 3줄로 나누어 쓸 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FpFKSZT6OTQkB7vYOFr4e%2FMask%20group-4.png?alt=media&amp;token=8cdba13e-2348-441a-b4dd-864a2bdd197c" alt=""><figcaption></figcaption></figure>

[**Type-E >** ](/creator/tutor/custom-label/type-e)

Type-E는 가위바위보 게임에서 나온 Custom Label로, 이미지를 삽입할 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fv6puafmTYnrOl3nnuQ7x%2FMask%20group.png?alt=media&amp;token=8dbc1556-e543-42be-ab68-654b74fa5f63" alt=""><figcaption></figcaption></figure>

[**Type-F >** ](/creator/tutor/custom-label/type-f)

Type-F는 ox 퀴즈, 퀴즈 골든벨에서 자주 사용되는 Custom Label로 본문 text를 길게 넣을 수 있습니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FgysNpJvytJew76SIDGJh%2FMask%20group-5.png?alt=media&amp;token=31499e08-3400-4ae1-ab9f-6b1d998d9476" alt=""><figcaption></figcaption></figure>

[**Type-G >** ](/creator/tutor/custom-label/type-g)

Type-G는 한 줄 text를 사용할 수  있는 Custom Label입니다.&#x20;

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FRI4fdWa5j4RPZKgdgr0P%2FMask%20group-6.png?alt=media&amp;token=d900d934-448e-4c94-bb5e-e87deeb5a257" alt=""><figcaption></figcaption></figure>

[**Type-H >**](/creator/tutor/custom-label/type-h)

Type-H는 제한 시간이 있는 게임에서 남은 시간을 표시할 때 쓰는 Custom Label입니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FSOBZR9MXo8AJHjRwpWqp%2FMask%20group-7.png?alt=media&amp;token=838c0339-9268-4b7c-bfc6-cacb2b8d048d" alt=""><figcaption></figcaption></figure>

[**Type-I >** ](/creator/tutor/custom-label/type-i)

Type-I 는 미니 게임에서 우승자가 나오거나 플레이어가 특정될 때 나오는 Custom Label 입니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fj2AB9wqBFkmYuNuWxHOw%2FMask%20group-8.png?alt=media&amp;token=675fb06a-0930-4edd-b2f0-55112a579dc3" alt=""><figcaption></figcaption></figure>

[**Type-J >**](/creator/tutor/custom-label/type-j)

Type-J는 레벨이 있는 미니 게임에서 자주 쓰이는 Custom Label 입니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FFhUpDEWw05pqzE0YytZq%2FMask%20group-9.png?alt=media&amp;token=f8fc1903-2a66-4824-88be-921c9b9fd263" alt=""><figcaption></figcaption></figure>


# Type-A

Type-A는 미니게임의 종류를 알려줄 때 인트로 Custom Label로 자주 사용됩니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FxqWR6bWD4nJQsjbDOzQ0%2FMask%20group-1.png?alt=media&amp;token=cc9ca6c3-63b4-49fa-8653-e78306a6d7f9" alt=""><figcaption></figcaption></figure>

### 예제코드

```javascript
function showLabelTypeA(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // gap between top and label ( mobile 60px,  pc 48px ) 60px, pc는 48px이 되도록 설정한 값
    /**
     * Size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 70 : 28;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "16px" : "24px"};
    font-weight: 800;
    color: #FFEB3A;`;

    const customLabelOption = {
        key: key,
        borderRadius: "12px",
        fontOpacity: false,
        padding: "8px",
    };

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# Type-B

Type-B는 미니 게임에서 목표를 알려줄 때 자주 쓰이며, 두 줄로 된 긴문장을 쓸 때 주로 사용합니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FzsFAlyMjpID9KrmIiqgZ%2FMask%20group-2.png?alt=media&amp;token=a2054926-afd6-437c-aad3-6f1fdad65191" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeB(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
     * Size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 90 : 50;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "16px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700;
    color: white;`;

    const customLabelOption = {
        key: key,
        borderRadius: "12px",
        fontOpacity: false,
        padding: "8px",
    };

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# Type-C

Type-C는 페인트 맨 팀 전 게임에서 사용된 Custom Label로, 대결 구도의 게임에서 자주 사용됩니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1hFoT7yjRM4u6zwGSmft%2FMask%20group-3.png?alt=media&amp;token=91a2d8a9-4fef-4256-8d1c-5cd8e404833f" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeC(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "16px" : "24px"};
    font-weight: 800;
    color: white;`;

    const customLabelOption = {
        key: key,
        borderRadius: "12px",
        fontOpacity: false,
        padding: "8px",
    };

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# Type-D

Type-D는 Type-C와 마찬가지로 팀 전 점수를 기록할 때 자주 쓰이며, 3줄로 나누어 쓸 수 있습니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fo1IpE4vlrMcU7bfSx8fT%2FMask%20group-4.png?alt=media&amp;token=a9d4d12d-962b-46ae-b2fb-38a22cefbc36" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeD(player, key, text1, text2, text3) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "16px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "16px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const thirdRowStyle = `
    font-size: ${isMobile ? "16px" : "24px"};
    font-weight: 800;
    color: white;`;

    const customLabelOption = {
        key: key,
        borderRadius: "12px",
        fontOpacity: false,
        padding: "8px",
    };

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
        <span style="${thirdRowStyle}">${text3}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# Type-E

Type-E는 가위바위보 게임에서 나온 Custom Label로, 이미지를 삽입할 수 있습니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FFUfPY61topE0CEG7PmDL%2FMask%20group.png?alt=media&amp;token=04149d36-322e-48bd-bb2b-8a3a7d427498" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeE(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "48px" : "64px"};
    font-weight: 800;
    color: white;`;

    const customLabelOption = {
        key: key,
        borderRadius: "12px",
        fontOpacity: false,
        padding: "8px",
    };

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}

```


# Type-F

Type-F는 ox 퀴즈, 퀴즈 골든벨에서 자주 사용되는 Custom Label로 본문 text를 길게 넣을 수 있습니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fw3rsVFJ4LyCeAjMfo39H%2FMask%20group-5.png?alt=media&amp;token=26807934-ebb6-4a18-977f-6b3a54000c17" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeF(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 90 : 50;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "16px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`;

    const secondRowStyle = `
    font-size: ${isMobile ? "16px" : "18px"};
    font-weight: 700;
    color: white;`;

    const thirdRowStyle = `
    font-size: ${isMobile ? "11px" : "13px"};
    font-weight: 700;
    color: #D5D9E0;
    margin-top: 18px;
    `;

    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px',
    }

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${secondRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}

```


# Type-G

Type-G는 한 줄 text를 사용할 수  있는 Custom Label입니다.&#x20;

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FnhtpIr1nMbImyP0X8c19%2FMask%20group-6.png?alt=media&amp;token=3de3ccc2-6e74-4cfe-9872-20bc6cbbdb64" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeG(player, key, text) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px',
    }

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}

```


# Type-H

Type-H는 제한 시간이 있는 게임에서 남은 시간을 표시할 때 쓰는 Custom Label입니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZkkNEzingTO6XsJyzsAo%2FMask%20group-7.png?alt=media&amp;token=cd58c71b-9783-4ae3-8f7a-4fe700e0de1c" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeH(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    align-items: center;
    justify-content: center;
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const highlightSpanStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`; // Show red(#FF5353) when timeout is about to expire 


    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px',
    }

    let htmlStr = `<span style="${parentStyle}">
        <span style="${firstRowStyle}">${text1}</span>
        <span style="${highlightSpanStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# Type-I

Type-I 는 미니 게임에서 우승자가 나오거나 플레이어가 특정될 때 나오는 Custom Label 입니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZLs2HgFNVdLBm3cZP74u%2FMask%20group-8.png?alt=media&amp;token=e98b604f-2e61-42fb-8c7f-260cf1768e43" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeI(player, key, text1, text2) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; //60px from the top on mobile and 48px on PC.
    /**
     * size-based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const parentStyle = `
    display: flex; 
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    `;

    const firstRowStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`;

    const highlightSpanStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: #74FADA;
    `;

    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px',
    }

    let htmlStr = `<span style="${parentStyle}">
        <span style="${highlightSpanStyle}">${text1}</span>
        <span style="${firstRowStyle}">${text2}</span>
    </span>`;

    player.showCustomLabel(htmlStr, 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}

```


# Type-J

Type-J는 레벨이 있는 미니 게임에서 자주 쓰이는 Custom Label 입니다.

똥 피하기 게임에서 쓰인 것을 볼 수 있습니다.

### 이미지

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fj0EpVI8qt0oqf7PRRauP%2FMask%20group-9.png?alt=media&amp;token=625a0816-92e1-4e98-a9c4-f01cb2bebb28" alt=""><figcaption></figcaption></figure>

### 예제 코드

```javascript
function showLabelTypeJ(player, key, text1, text2, text3) {
    const isMobile = player.isMobile;
    const topGap = isMobile ? 10 : -2; // 60px from the top on mobile and 48px on PC.
    /**
     * size based @labelPercentWidth
     * XL: isMobile ? 90 : 50;
     * L: isMobile ? 80 : 40;
     * M: isMobile ? 70 : 28;
     * S: isMobile ? 60 : 20
     */
    const labelPercentWidth = isMobile ? 60 : 20;
    const labelDisplayTime = 300000;

    const directionColumnStyle = `
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    justify-content: center; 
    text-align: center;`.trim();

    const rowStyle = `
    display: flex; 
    align-items: center;
    justify-content: center;
    text-align: center;
    `.trim();

    const spanStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: white;`.trim();

    const highlightSpanStyle = `
    font-size: ${isMobile ? "14px" : "18px"};
    font-weight: 700; 
    color: #FFEB3A;`.trim(); // Show red(#FF5353) when timeout is about to expire 

    const customLabelOption = {
        key: key,
        borderRadius: '12px',
        fontOpacity: false,
        padding: '8px',
    }

    let htmlStr = `
<span style="${directionColumnStyle}">
    <span style="${spanStyle}">${text1}</span>
    <span style="${rowStyle}">
        <span style="${spanStyle}">${text2}</span>
        <span style="${highlightSpanStyle}">${text3}</span>
    </span>
</span>`;

    player.showCustomLabel(htmlStr.trim(), 0xffffff, 0x27262e, topGap, labelPercentWidth, 0.64, labelDisplayTime, customLabelOption);
}
```


# 사이드 바 앱 예제 코드

사이드 바 앱 예제 코드를 이용해 나만의 앱을 만들어 보세요!

각 항목에 포함된 zip파일을 받아서 사이드바 앱으로 업로드한 후 맵에 설치하시면 바로 실행해 보실 수 있습니다

### **기초 유형**

{% content-ref url="/pages/tHETfF3bSZLKCUsIz0Ck" %}
[Image List](/creator/tutor/undefined/image-list)
{% endcontent-ref %}

{% content-ref url="/pages/nzfTOPCLzj4tQ0CgE9a3" %}
[Text List](/creator/tutor/undefined/text-list)
{% endcontent-ref %}

{% content-ref url="/pages/QUKjF6PqVmnB7eDy7Wzp" %}
[Text Button List](/creator/tutor/undefined/text-button-list)
{% endcontent-ref %}

{% content-ref url="/pages/KJi60kxORVyDxxuX5s7a" %}
[Ranking List](/creator/tutor/undefined/ranking-list)
{% endcontent-ref %}

### **심화 유형**

{% content-ref url="/pages/EjzfLSPQwZoN7ajTvcmt" %}
[Select List](/creator/tutor/undefined/select-list)
{% endcontent-ref %}


# Image List

#### Image List 위젯은 이미지와 텍스트를 함께 표시하고 선택할 수 있는 UI입니다.

활용 예) 탑승 앱, 찌르기 앱, 아바타 특수 효과 등

**Image**

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F4CjFnONKpPwEvZPMEOk9%2FGroup%201364.png?alt=media\&token=3d3e5286-8023-4111-8f88-4368ecf1616a)

#### Example

{% file src="/files/xLhzZjKrV8LeMTYUsMXG" %}

```javascript
function openWidget(p)
{
    //Check if player is mobile or not
    if(p.isMobile)
        //Set the widget's name, anchor position, width, and height.
        p.tag.widget = p.showWidget('widget.html','sidebar',300,550);
    else
        p.tag.widget = p.showWidget('widget.html','sidebar',280,520);

    //Send data to widget
    p.tag.widget.sendMessage({
        isMobile : p.isMobile,
    })

    //Register events for messages sent from widgets
    p.tag.widget.onMessage.Add(function(sender, msg) {
        switch (msg.type)
        {
            case 'close':
                if(p.tag.widget)
                {
                    p.tag.widget.destroy();
                    p.tag.widget = null;
                }
                
                break;
            case 'submit':
                let choiceItem = msg.choice;
                p.showAlert('Item' + choiceItem);
                break;
        }
    })
}

//Event when a player joins the app
App.onJoinPlayer.Add(function(p) {
    //Reset the player's tag value
    p.tag = {};
});
// Action when the player clicks the sidebar app
App.onSidebarTouched.Add(function(p) {
    openWidget(p);
});


```


# Text List

#### Text List 형 위젯은 텍스트와 서브 텍스트를 같이 사용한 UI입니다.

활용 예 ) 내 포인트 앱 이용자 내역

**Image**

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZvQOtGIpGfNX1p1hXyHx%2Fimage.png?alt=media\&token=76dd4986-f677-4496-9733-be60f27dbc5b)

**Example**

{% file src="/files/NQHm3AfZKRkXNGtVlqmN" %}

```javascript
/**
 * Widget Handle Function
 * @param player Script Player Object
 * @returns 
 */
function openWidget(player)
{
    if (player.isMobile) {
        player.tag.widget = player.showWidget('widget.html', 'sidebar', 300, 500);
    } else {
        player.tag.widget = player.showWidget('widget.html', 'sidebar', 290, 460);
    }

    // send message to widget
    player.tag.widget.sendMessage({
        isMobile : player.isMobile,
    });

    // handle message from child frame(widget)
    player.tag.widget.onMessage.Add(function(sender, msg) {
        switch (msg.type)
        {
            case 'close':
                if(sender.tag.widget)
                {
                    sender.tag.widget.destroy();
                    sender.tag.widget = null;
                }
                break;
            case 'submit':
                sender.showAlert('OK');
                break;
        }
    })
}

App.onJoinPlayer.Add(function(player) {
    // Initialize tag properties of player
    player.tag = {};
});

App.onSidebarTouched.Add(function(player) {
    openWidget(player);
});


```


# Text Button List

#### Text Button List 형 위젯은 텍스트와 버튼을 함께 표시할 수 있는 UI입니다.

활용 앱 ) 카메라 관전 프리미엄 기능

#### Image

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FmNLotSQD7iT1g2xwEaqW%2Fimage.png?alt=media\&token=370a745c-cb5f-45b6-96f2-344ed19e6536)

**Example**

{% file src="/files/tAQ6urAqcEI8PQ47w53C" %}

```javascript
function openWidget(p)
{
    //Check if player is mobile or not
    if(p.isMobile)
        //Set the widget's name, anchor position, width, and height.
        p.tag.widget = p.showWidget('widget.html','sidebar',300,550);
    else
        p.tag.widget = p.showWidget('widget.html','sidebar',280,520);

    //Send data to widget
    p.tag.widget.sendMessage({
        isMobile : p.isMobile,
    })
    
    //Register events for messages sent from widgets
    p.tag.widget.onMessage.Add(function(sender, msg) {
        switch (msg.type)
        {
            case 'close':
                if(p.tag.widget)
                {
                    p.tag.widget.destroy();
                    p.tag.widget = null;
                }
                
                break;
        }
    })
}

//Event when a player joins the app
App.onJoinPlayer.Add(function(p) {
    // Initialize tag properties of player
    p.tag = {};
});

// Action when the player clicks the sidebar app
App.onSidebarTouched.Add(function(p) {
    openWidget(p);
});


```


# Ranking List

#### Ranking List 형 위젯은 플레이어의 랭킹을 정렬한 UI입니다.

활용 예 ) 달리기 공식 맵, 결투장 공식 맵, 승부 예측 앱, 포인트 앱

#### Image

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FHEDODin0VArauKQpEi1K%2Fimage.png?alt=media\&token=4b670d4e-0b03-45ad-9f10-1dbf8b5b682f)

#### Example

{% file src="/files/nmg5sF3qrdFJUW6LVVxY" %}

```javascript
const dummyData = {
    'a': {
        id: 'a',
        name: 'user1',
        point: 100,
    },
    'b': {
        id: 'b',
        name: 'user2',
        point: 200,
    },
    'c': {
        id: 'c',
        name: 'user3',
        point: 400,
    },
    'd': {
        id: 'd',
        name: 'user4',
        point: 1000,
    },
    'e': {
        id: 'e',
        name: 'user5',
        point: 2000,
    },
    'f': {
        id: 'f',
        name: 'user6',
        point: 4000,
    },
};
function openWidget(p)
{
    //Check if player is mobile or not
    if (p.isMobile)
        //Set the widget's name, anchor position, width, and height.
        p.tag.widget = p.showWidget('widget.html', 'sidebar', 350, 480);
    else
        p.tag.widget = p.showWidget('widget.html', 'middle', 400, 448);
    
    //Send data to widget
    p.tag.widget.sendMessage({
        data: dummyData,
        id: p.id,
        isMobile: p.isMobile,
    });
    
    //Register events for messages sent from widgets
    p.tag.widget.onMessage.Add(function (sender, msg) {
        if (msg.type == "close") {
            if (sender.tag.widget) {
                sender.tag.widget.destroy();
                sender.tag.widget = null;
            }
        }
    });
}

//Event when a player joins the app
App.onJoinPlayer.Add(function(p) {
    //Reset the player's tag value
    p.tag = {};
});

// Action when the player clicks the sidebar app
App.onSidebarTouched.Add(function(p) {
    openWidget(p);
});


```


# Select List

#### Select List 형 위젯은 텍스트와 텍스트 입력 창, 혹은 선택 버튼을 함께 사용할 수 있는 UI입니다.

활용 예 ) 탑승 앱, 찌르기 앱, 아바타 특수 효과 앱

#### Image

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F9tYFarZSoRL99qnbwOij%2Fimage.png?alt=media\&token=b958acf1-ac4f-481f-a241-18debe0da7d0)

####

#### Example

{% file src="/files/14laQzAmRS4hpdf6ksOM" %}

```javascript
// Editable fields. Used for validation.
const RequiredFields = {
    password: "string",
    lock: "boolean"
};

const SELECT_ITEM_DATA = {
    Select_1: {
        id: 1,
        order: 1, // Item sorting order
        displayName: "Item 1", // Item display name
        password: "password", // password
        lock: false, // lock(boolean)
    },
    Select_2: {
        id: 2,
        order: 2,
        displayName: "Item 2",
        password: "password",
        lock: false,
    },
    Select_3: {
        id: 3,
        order: 3,
        displayName: "Item 3",
        password: "password",
        lock: false,
    },
    Select_4: {
        id: 4,
        order: 4,
        displayName: "Item 4",
        password: "password",
        lock: false,
    },
    Select_5: {
        id: 5,
        order: 5,
        displayName: "Item 5",
        password: "password",
        lock: false,
    },
    Select_6: {
        id: 6,
        order: 6,
        displayName: "Item 6",
        password: "password",
        lock: false,
    },
    Select_7: {
        id: 7,
        order: 7,
        displayName: "Item 7",
        password: "password",
        lock: false,
    },
    Select_8: {
        id: 8,
        order: 8,
        displayName: "Item 8",
        password: "password",
        lock: false,
    },
};


App.onStart.Add(() => {
    init();
});

App.onJoinPlayer.Add(function (player) {
    player.tag = {};
});

App.onSidebarTouched.Add(function (player) {
    showMainWidget(player);
})

function init() {
    // ensureStorageInitialized
    if (!App.storage) {
        App.storage = "{}";
        App.save();
    }
    const appStorage = JSON.parse(App.storage);

    // updateItemDataFromStorage
    if (appStorage.setting) {
        for (let key in SELECT_ITEM_DATA) {
            if (appStorage.setting.hasOwnProperty(key)) {
                SELECT_ITEM_DATA[key] = Object.assign({}, SELECT_ITEM_DATA[key], appStorage.setting[key]);
            } else {
                SELECT_ITEM_DATA[key] = Object.assign({}, SELECT_ITEM_DATA[key]);
            }
        }
    }
}

function showMainWidget(player) {
    App.getStorage(() => {
        const appStorage = JSON.parse(App.storage);
        if (player.tag.widget) {
            player.tag.widget.destroy();
            player.tag.widget = null;
        }
        if (player.isMobile) {
            player.tag.widget = player.showWidget("SelectList.html", "sidebar", 0, 0);
        } else {
            player.tag.widget = player.showWidget("SelectList.html", "bottomleft", 0, 0);
        }

        let selectItemData = SELECT_ITEM_DATA;
        if (appStorage.setting) {
            let newList = {};
            for (let key in selectItemData) {
                if (appStorage.setting.hasOwnProperty(key)) {
                    newList[key] = Object.assign({}, selectItemData[key], appStorage.setting[key]);
                } else {
                    newList[key] = Object.assign({}, selectItemData[key]);
                }
            }
            selectItemData = newList;
        }

        player.tag.widget.sendMessage({
            type: "init",
            isMobile: player.isMobile,
            isAdmin: isAdmin(player),
            selectItemData: selectItemData,
        });

        player.tag.widget.onMessage.Add(function (sender, msg) {
            switch (msg.type) {
                case "close":
                    if (sender.tag.widget) {
                        sender.tag.widget.destroy();
                        sender.tag.widget = null;
                    }
                    break;
                case "requestSaveSetting":
                    const result = validateDatabase(msg.editData);
                    if (!result) {
                        sender.showAlert("Invalid Input");
                        return;
                    }
                    App.getStorage(() => {
                        const appStorage = JSON.parse(App.storage);
                        appStorage.setting = result;
                        App.setStorage(JSON.stringify(appStorage));

                        if (sender.tag.widget) {
                            sender.tag.widget.sendMessage({
                                type: "responseSaveSetting",
                            });
                        }

                        let selectItemData = {};
                        for (let key in SELECT_ITEM_DATA) {
                            if (appStorage.setting.hasOwnProperty(key)) {
                                selectItemData[key] = Object.assign({}, SELECT_ITEM_DATA[key], appStorage.setting[key]);
                                SELECT_ITEM_DATA[key] = selectItemData[key];
                            } else {
                                SELECT_ITEM_DATA[key] = Object.assign({}, SELECT_ITEM_DATA[key]);
                            }
                        }
                        if (selectItemData == {}) {
                            selectItemData = SELECT_ITEM_DATA;
                        }
                        handleUpdateSetting(selectItemData);
                    });

                    break;
            }
        });
    });

}

function handleUpdateSetting(selectItemData) {
    for (const player of App.players) {
        if (!player) continue;
        if (player.tag.widget) {
            player.tag.widget.sendMessage({
                type: "update",
                selectItemData: selectItemData,
            });
        }
    }
}

function validateDatabase(database) {
    let newDB = {};
    for (let key in database) {
        let item = database[key];
        newDB[key] = {};
        for (let field in RequiredFields) {
            // validateFields
            if (!item.hasOwnProperty(field) || typeof item[field] !== RequiredFields[field]) {
                return false;
            }
            if (field === "password" && (!item[field] && item["lock"])) {
                return false;
            }

            newDB[key][field] = item[field];
        }
    }
    return newDB;
}

function isAdmin(player) {
    return player.role >= 3000;
}

```


# ZEP Script FAQ

#### Q. ZEP Script로 캐릭터의 외양을 변경할 수 있나요?

{% hint style="success" %}
Spritesheet를 로드한 후, 특정 이벤트 발생 시 Player에 적용시키면 캐릭터의 외양을 변경할 수 있습니다. 아래 예제 문서를 참고해주세요.

:fairy: [<mark style="color:purple;">**캐릭터 이미지 바꾸기**</mark>](/creator/tutor/tutorial/3)
{% endhint %}

#### Q. ZEP Script 사용은 유료인가요?

{% hint style="success" %}
ZEP Script를 사용해 앱을 개발하고 배포하는 과정은 무료입니다.
{% endhint %}

#### Q. ZEP Script로 유저에게 과금을 하는 것이 가능한가요?

{% hint style="success" %}
ZEP Script에서 제공하는 결제 함수를 사용해 과금을 하는 것이 가능합니다.

자세한 내용은 [<mark style="color:purple;">**API 문서**</mark>](/zep-script-api/zepscriptapi/scriptplayer/methods#showbuyalert)를 참고해주세요,
{% endhint %}

#### Q. ZEP Script로 만든 앱을 유료로 판매할 수 있나요?

{% hint style="success" %}
[<mark style="color:purple;">**ZEP 에셋스토어**</mark>](https://zep.us/store/assets?type=app)에 앱을 업로드하고 판매할 수 있습니다.
{% endhint %}

#### Q. ZEP Script 로 만든 앱을 배포하지 않고 보다 쉽게 디버깅 할 수 있는 방법이 있나요?

{% hint style="success" %}
보다 나은 개발 환경을 현재 준비 중에 있습니다.
{% endhint %}

#### Q. 다른 언어로도 ZEP Script를 개발할 수 있나요?

{% hint style="success" %}
현재 ZEP Script 개발 환경은 **Javascript** 와  **Typescript** 언어로 제공하고 있습니다.

[<mark style="color:purple;">**Javascript 개발 가이드**</mark>](/creator/dev-guide/javascript) | [<mark style="color:purple;">**Typescript 개발 가이드**</mark>](/creator/dev-guide/typescript)
{% endhint %}

#### Q. 저희의 자체 서버 API와 연동을 하고 싶습니다. 어떻게 할 수 있나요?

{% hint style="success" %}
ZEP Script는 아래 방식을 통해서 Javascript 통신을 지원하고 있습니다. 자세한 내용은 아래 문서를 참고해주세요.&#x20;

:satellite\_orbital: [<mark style="color:purple;">**외부 API 통신하기**</mark>](/creator/reference/api)
{% endhint %}

***


# 부록

{% content-ref url="/pages/SJhAcKsANUnrFn4W8mCe" %}
[ZEP 스크립트 활용 사례](/creator/reference/zep)
{% endcontent-ref %}

{% content-ref url="/pages/6N3IG1fQABpHPpos7wc0" %}
[스페이스와 맵 이해하기](/creator/reference/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/NYBjr6SUu1kExPQAYncU" %}
[자바스크립트 키코드 표](/creator/reference/keycode)
{% endcontent-ref %}

{% content-ref url="/pages/gjoj7GGNyQQBYIEMNuKo" %}
[스프라이트시트 이해하기](/creator/reference/spritesheet)
{% endcontent-ref %}

{% content-ref url="/pages/PWVKbzRpvLZ3RSShT3sD" %}
[TileEffectType 상세 설명](/creator/reference/tileeffecttype)
{% endcontent-ref %}

{% content-ref url="/pages/pNhkgoVXVtKHafnqDP82" %}
[기준 좌표](/creator/reference/coordinate)
{% endcontent-ref %}

{% content-ref url="/pages/cPjXXbLOA0cNpfZ3gkNj" %}
[외부 API 통신하기](/creator/reference/api)
{% endcontent-ref %}

{% content-ref url="/pages/lqgnI4JUuFkdNoja9WQi" %}
[URL 쿼리스트링 활용하기](/creator/reference/url)
{% endcontent-ref %}

{% content-ref url="/pages/tL1zNIDDsxpITpe7WoaX" %}
[위젯에서 사용가능한 문법](/creator/reference/scriptaction)
{% endcontent-ref %}

{% content-ref url="/pages/C4WjMYSc8U9wYodYFNgY" %}
[모바일버튼 이미지 변경하기](/creator/reference/mobilebutton-image)
{% endcontent-ref %}

{% content-ref url="/pages/kbbYq1ORDhGncpBTbsN6" %}
[ZEP 스크립트 상호작용 오브젝트 가이드](/creator/reference/script-object-interaction)
{% endcontent-ref %}

{% content-ref url="/pages/jE090TJPep3C8JASB2tW" %}
[오브젝트 npcProperty](/creator/reference/npcproperty)
{% endcontent-ref %}


# ZEP 스크립트 활용 사례

## :joystick: 게임

ZEP 스크립트를 활용해 개발한 게임 사례를 확인해 보실 수 있습니다.

###

### 🗃 ZEP 스크립트 활용 사례

**ZEP Script**를 활용해 다양하고 재미있는 게임을 만들 수 있습니다.

**ZEP Script**를 활용한 게임 개발 사례들을 만나보세요.

####

### 강일병 구하기 게임 : 링티 X 바람의나라:연

바람:연 X ZEP X 링티가 콜라보하여 ‘**강일병 구하기**’ 라는 퀘스트 형태의 게임을 만들어 선보인 사례입니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FrO46AfjOOR1ju8APdsKK%2F%25EC%25A0%259C%25EB%25AA%25A9%25EC%259D%2584-%25EC%259E%2585%25EB%25A0%25A5%25ED%2595%25B4%25EC%25A3%25BC%25EC%2584%25B8%25EC%259A%2594_-001-cutout.png?alt=media&amp;token=af4807c3-3951-4eaf-b1a1-581e9cdca2f8" alt=""><figcaption></figcaption></figure></div>

### 팩맨 미니게임 : 링티 X 바람의나라:연

바람:연 X ZEP X 링티가 콜라보하여 \
‘팩맨’ 게임에 다람쥐, 도토리 등 바람의나라:연 이미지를 입혀 귀엽게 표현한 미니게임 입니다

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FTuHb3qLc4Cy4mTnphQbh%2FUntitled.png?alt=media&amp;token=d8a2b50b-d7f3-4e78-b64b-f9a32497e1eb" alt=""><figcaption></figcaption></figure></div>

### 슈퍼캣 채용박람회에서 진행된 페인트맨 게임

슈퍼캣 채용박람회에서 진행된 페인트맨 게임입니다.

ZEP Script를 활용하면 100명 이상이 참가하는 단체 게임 개발도 가능합니다!

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FctJplFBDrMDoH0LQGG9N%2FUntitled%201.png?alt=media&amp;token=b2f12b53-0239-4ed4-870b-aa6862ecb8e0" alt=""><figcaption></figcaption></figure></div>

### ZEP 유저가 개발한 오목 게임

ZEP 유저들이 ZEP Script를 활용해 개발한 게임 사례가 많아지고 있습니다.

ZEP 캐릭터를 움직여 두는 오목! 재미있지 않나요?

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F88M6igyY3JDgVG8ZpikC%2FUntitled%202.png?alt=media&amp;token=2abb8853-05f8-4fee-a18c-7e74de19d1f8" alt=""><figcaption></figcaption></figure></div>

## :tada: 행사

행사에서 ZEP 스크립트를 활용한 사례를 확인해 보실 수 있습니다.

### 🗃 ZEP Script 활용 사례

**ZEP Script**를 활용하면 좀 더 인터랙티브한 행사를 기획할 수 있습니다.

**ZEP Script**를 활용한 행사 사례들을 만나보세요.

### 클레이튼 뮤지엄 ( Klaytn Museum )

클레이튼의 기술적 강점 등을 **ZEP Script**를 활용해 효과적으로 보여준 사례입니다.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FuhZ13UrLi6ABgUmClL3P%2F%25ED%2581%25B4%25EB%25A0%2588%25EC%259D%25B4%25ED%258A%25BC%25EB%25AE%25A4%25EC%25A7%2580%25EC%2597%2584.gif?alt=media&amp;token=21256a58-3284-4b01-9921-9b78e452e51d" alt=""><figcaption><p>ZEP 스크립트로 구현한 컨베이어벨트</p></figcaption></figure>

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FrO7aK05Qmko8T2sXPbHw%2Fezgif.com-gif-maker%20(34).gif?alt=media&amp;token=fa721e69-fe56-4632-9b8f-1439f114c564" alt=""><figcaption><p>ZEP 스크립트로 구현한 보안 체험관</p></figcaption></figure>

***

***


# 스페이스와 맵 이해하기

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FjPweIoZYIE4CllOTd3GI%2F%EC%A0%9C%EB%AA%A9%EC%9D%84_%EC%9E%85%EB%A0%A5%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94_-001_(1).png?alt=media&amp;token=dd14b969-2483-4b51-aa2c-b92122390a6c" alt=""><figcaption><p>space와 map의 관</p></figcaption></figure>

space는 1개 이상의 map을 포함하고 있는 그룹의 단위 입니다.

space와 map은 다른 스페이스 또는 맵과 자신을 구분하기 위한 **HashID**를 가지고 있습니다.

space에 포함된 1개 이상의 map들 중 하나는 **‘첫 방문 맵’** 속성을 가지고 있으며, mapID 없이 spaceHashID만 가지고 스페이스에 접근 할 경우 ‘첫 방문 맵’ 속성을 가진 맵으로 이동하게 됩니다.

젭 맵 에디터에 들어가면 다음과 같은 URL 형식을 볼 수 있는데요,

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FK6c6qxAMN9Y1lWLOJvUq%2FUntitled.png?alt=media&amp;token=f86f135d-ca04-45b0-8782-9614a84dab7f" alt=""><figcaption></figcaption></figure></div>

여기서 **Ak42Xz가 SpaceHashID**, **25g3RQ가 MapHashID** 입니다.


# 자바스크립트 키코드 표

### ⌨️ 자바스크립트 키코드 표

<table data-header-hidden><thead><tr><th width="260">입력 키</th><th>키 코드</th></tr></thead><tbody><tr><td>backspace</td><td>8</td></tr><tr><td>tab</td><td>9</td></tr><tr><td>enter</td><td>13</td></tr><tr><td>shift(left)</td><td>16</td></tr><tr><td>shift(right)</td><td>16</td></tr><tr><td>ctrl(left)</td><td>17</td></tr><tr><td>ctrl(right)</td><td>17</td></tr><tr><td>alt(left)</td><td>18</td></tr><tr><td>alt(right)</td><td>18</td></tr><tr><td>pause/break</td><td>19</td></tr><tr><td>caps lock</td><td>20</td></tr><tr><td>escape</td><td>27</td></tr><tr><td>space</td><td>32</td></tr><tr><td>page up</td><td>33</td></tr><tr><td>page down</td><td>34</td></tr><tr><td>end</td><td>35</td></tr><tr><td>home</td><td>36</td></tr><tr><td>left arrow</td><td>37</td></tr><tr><td>up arrow</td><td>38</td></tr><tr><td>right arrow</td><td>39</td></tr><tr><td>down arrow</td><td>40</td></tr><tr><td>print screen</td><td>44</td></tr><tr><td>insert</td><td>45</td></tr><tr><td>delete</td><td>46</td></tr><tr><td>0</td><td>48</td></tr><tr><td>1</td><td>49</td></tr><tr><td>2</td><td>50</td></tr><tr><td>3</td><td>51</td></tr><tr><td>4</td><td>52</td></tr><tr><td>5</td><td>53</td></tr><tr><td>6</td><td>54</td></tr><tr><td>7</td><td>55</td></tr><tr><td>8</td><td>56</td></tr><tr><td>9</td><td>57</td></tr><tr><td>a</td><td>65</td></tr><tr><td>b</td><td>66</td></tr><tr><td>c</td><td>67</td></tr><tr><td>d</td><td>68</td></tr><tr><td>e</td><td>69</td></tr><tr><td>f</td><td>70</td></tr><tr><td>g</td><td>71</td></tr><tr><td>h</td><td>72</td></tr><tr><td>i</td><td>73</td></tr><tr><td>j</td><td>74</td></tr><tr><td>k</td><td>75</td></tr><tr><td>l</td><td>76</td></tr><tr><td>m</td><td>77</td></tr><tr><td>n</td><td>78</td></tr><tr><td>o</td><td>79</td></tr><tr><td>p</td><td>80</td></tr><tr><td>q</td><td>81</td></tr><tr><td>r</td><td>82</td></tr><tr><td>s</td><td>83</td></tr><tr><td>t</td><td>84</td></tr><tr><td>u</td><td>85</td></tr><tr><td>v</td><td>86</td></tr><tr><td>w</td><td>87</td></tr><tr><td>x</td><td>88</td></tr><tr><td>y</td><td>89</td></tr><tr><td>z</td><td>90</td></tr><tr><td>left window key</td><td>91</td></tr><tr><td>right window key</td><td>92</td></tr><tr><td>select key</td><td>93</td></tr><tr><td>numpad 0</td><td>96</td></tr><tr><td>numpad 1</td><td>97</td></tr><tr><td>numpad 2</td><td>98</td></tr><tr><td>numpad 3</td><td>99</td></tr><tr><td>numpad 4</td><td>100</td></tr><tr><td>numpad 5</td><td>101</td></tr><tr><td>numpad 6</td><td>102</td></tr><tr><td>numpad 7</td><td>103</td></tr><tr><td>numpad 8</td><td>104</td></tr><tr><td>numpad 9</td><td>105</td></tr><tr><td>multiply</td><td>106</td></tr><tr><td>add</td><td>107</td></tr><tr><td>substract</td><td>109</td></tr><tr><td>decimal point</td><td>110</td></tr><tr><td>divide</td><td>111</td></tr><tr><td>f1</td><td>112</td></tr><tr><td>f2</td><td>113</td></tr><tr><td>f3</td><td>114</td></tr><tr><td>f4</td><td>115</td></tr><tr><td>f5</td><td>116</td></tr><tr><td>f6</td><td>117</td></tr><tr><td>f7</td><td>118</td></tr><tr><td>f8</td><td>119</td></tr><tr><td>f9</td><td>120</td></tr><tr><td>f10</td><td>121</td></tr><tr><td>f11</td><td>122</td></tr><tr><td>f12</td><td>123</td></tr><tr><td>num lock</td><td>144</td></tr><tr><td>scroll lock</td><td>145</td></tr><tr><td>audio volume mute</td><td>173</td></tr><tr><td>audio volume down</td><td>174</td></tr><tr><td>media player</td><td>175</td></tr><tr><td>audio volume up</td><td>181</td></tr><tr><td>launch application 1</td><td>182</td></tr><tr><td>launch application 2</td><td>183</td></tr><tr><td>semi-colon</td><td>186</td></tr><tr><td>equal sign</td><td>187</td></tr><tr><td>comma</td><td>188</td></tr><tr><td>dash</td><td>189</td></tr><tr><td>period</td><td>190</td></tr><tr><td>forward slash</td><td>191</td></tr><tr><td>Backquote</td><td>192</td></tr><tr><td>open bracket</td><td>219</td></tr><tr><td>back slash</td><td>220</td></tr><tr><td>close bracket</td><td>221</td></tr><tr><td>single quote</td><td>222</td></tr></tbody></table>


# 스프라이트시트 이해하기

## App.loadSpritesheet

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fe4q2g39g43jsXY6AjOz9%2Fblueman.png?alt=media&amp;token=ebeca192-cb28-4539-8059-8e1777d58d90" alt=""><figcaption></figcaption></figure></div>

```jsx
// 이미지를 객체화 시키는 함수
App.loadSpritesheet(fileName: string, frameWidth: integer, frameHeight: integer, anims: array, frameRate: integer)
```

### frameWidth 와 frameHeight

블루맨 스프라이트 한 칸의 이미지 크기는 48픽셀 x 64픽셀 입니다.

frameWidth, frameHeight 파라미터에 48, 64를 지정하면 블루맨 스프라이트 각각의

이미지가 0번부터 41번까지 번호를 가지게 됩니다.

### anims

anims는 애니메이션을 지정할 배열을 의미합니다.

캐릭터 스프라이트로 사용 할 때와 오브젝트 스프라이트로 사용 할 때 양식이 다릅니다.

### 예시1) 단일 이미지인 경우

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F24tOYsXkiVFltJachN16%2Fblueman%201.png?alt=media&amp;token=003a4e36-63df-4efd-a993-a11c3b53ca9c" alt=""><figcaption></figcaption></figure></div>

스프라이트가 단일 이미지인 경우 파일 이름을 제외한 나머지 파라미터는 입력하지 않아도 됩니다.

```jsx
let blueman = App.loadSpritesheet('blueman.png')
```

단일 이미지도 캐릭터 이미지로 적용할 수 있습니다. 다만, 애니메이션을 지정하지 않았으니 어느 방향으로 움직여도 같은 이미지일 것 입니다.

### 예시2) 단일 애니메이션을 정의하는경우

단일 애니메이션을사용하는 경우에는 아래와 같이 하나의 배열로 애니메이션 프레임을 정의 할 수 있습니다.  춤추는 블루맨 오브젝트 애니메이션은 아래 코드와 같이 21번째 \~ 38번째 이미지에 해당하는 이미지 번호를 배열로 지정해 사용할 수 있습니다.

```jsx
let blueman_dance = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], // 21번째 ~ 38번째 이미지로 애니메이션을 구성
	8
);
```

### 예시3) 2개 이상의 애니메이션을 정의하는 경우

여러 애니메이션을 정의하는 경우에는 아래와 같이 anims 파라미터에 { } 브라켓을 입력 한뒤 다음과 같이 애니메이션 이름에 대한 이미지 배열을 지정해주면 됩니다. 아래 예시에서는 **`Map.playObjectAnimationWithKey`** 함수를 사용해 정의한 여러 애니메이션 중 "dance"에 대한 애니메이션을 실행합니다.

```
var blueman_sprite = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	{
		left: [5, 6, 7, 8, 9],
		up: [15, 16, 17, 18, 19],
		down: [0, 1, 2, 3, 4],
		right: [10, 11, 12, 13, 14],
		dance: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37],
		idle: [0],
	},
	8
);

// q 키를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
	Map.putObjectWithKey(10, 10, blueman_sprite, {
		overlap: true,
		movespeed: 80, // default: 80
		key: "blueman",
	});
	// "dance" 애니메이션 재생
	Map.playObjectAnimationWithKey("blueman", "dance", -1);
});
```

### 예시4) 캐릭터 애니메이션 정의

캐릭터는 오브젝트와 달리 플레이어가 조작하는 키에 따라 다양한 애니메이션 실행 할 수 있습니다.  캐릭터에 지정 가능한 애니메이션의 종류는 다음과 같이 총 9가지 이며, 특정 애니메이션에 대한 이미지가 부족한 경우 생략할 수 있습니다.

```jsx
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
    dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
    down_jump: [38],
    left_jump: [39],
    right_jump: [40],
    up_jump: [41],
}, 8);
// 플레이어가 입장하면 캐릭터 이미지가 바뀜
App.onJoinPlayer.Add(function(player){
	player.sprite = blueman;
	player.sendUpdated();
});
```

✅ 추가로 사용가능한 캐릭터 애니메이션 키

앉기 모션: `down_sit, left_sit, right_sit, up_sit`

서있는 모션: `down_idle, left_idle, right_idle, up_idle`

공격 모션: `down_attack, left_attack, right_attack, up_attack`

***

***


# TileEffectType 상세 설명

<mark style="color:purple;">`Map.putTileEffect`</mark> 함수에서 사용되는 **타일 효과(TileEffectType)**&#xC758; 사용 방법을 안내하는 페이지입니다. 타일 효과에 대한 자세한 설명은 아래 링크를 참고해주세요.

:fire: [<mark style="color:purple;">**타일 효과**</mark>](https://docs-kr.zep.us/creator/editor/undefined-1)

## 📗 기본 타일 효과

### TileEffectType.NONE

NONE 타입의 타일 효과 입니다.

**예시**

```jsx
//해당 좌표에 있는 타일 효과를 지웁니다.
Map.putTileEffect(x, y, TileEffectType.NONE);
```

###

### **TileEffectType.IMPASSABLE**

이동불가(IMPASSABLE) 타일 효과 입니다

**예시**

```jsx
//해당 좌표에 이동불가(IMPASSABLE) 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.IMPASSABLE);
```

###

### **TileEffectType.SPAWN**

플레이어가 맵에 진입 할 때 진입 지점을 설정하는 타일 효과 입니다

**예시**

```jsx
//해당 좌표에 스폰(SPAWN) 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.SPAWN);
```

## 🌀 포털 관련 타일 효과

### TileEffectType.PORTAL

**스페이스 내 다른 맵으로 이동** 또는 **맵 내 지정 영역**으로 이동 하는 타일 효과 입니다.

**파라미터**

<table><thead><tr><th width="167.33333333333331">이름</th><th width="118">타입</th><th>설명</th></tr></thead><tbody><tr><td>type</td><td>Number</td><td>type이 0 인 경우<br>스페이스 내 다른맵으로 이동하는 포털 타일을 설치합니다.<br>type이 1 인 경우<br>맵 내 지정 영역으로 이동하는 포털 타일을 설치합니다.</td></tr><tr><td>targetMapID</td><td>String</td><td>이동할 맵의 MapID 값</td></tr><tr><td>label</td><td>String</td><td>포털 위에 표시할 텍스트 값</td></tr><tr><td>triggerByTouch</td><td>Boolean</td><td>true인 경우: 닿았을 때 실행<br>false인 경우: F를 눌러 실행</td></tr><tr><td>invisible</td><td>Boolean</td><td>true인 경우: 기본 포털 이미지를 숨김<br>false인 경우: 기본 포털 이미지가 보임</td></tr><tr><td>locationName</td><td>String</td><td>이동할 로케이션의 이름 값 ( type이 1인 경우 필수 입력 )</td></tr></tbody></table>

**예시**

```jsx
// type: 0 인 경우
// 스페이스 내 다른맵으로 이동하는 포털 타일을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.PORTAL, {
	type: 0, // 필수
	locationName: "TEST",  // 선택
	targetMapID: "gyV1N2", // 필수
	label: "PORTAL-TYPE0", // 선택
  triggerByTouch: true // 선택, 기본값 false
});

// type: 1 인 경우
// 맵 내 지정 영역으로 이동하는 포털 타일을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.PORTAL, {
	type: 1, // 필수
	label: "PORTAL-TYPE1",  // 선택
	locationName: "TEST", // 필수
	invisible: true, // 선택, 기본 포털이미지 숨기기
	triggerByTouch: true  // 선택, 기본값 false
});
```

###

### TileEffectType.SPACE\_PORTAL

외부 스페이스로 이동하는 타일 효과 입니다.

**파라미터**

<table><thead><tr><th width="173.33333333333331">이름</th><th width="114">타입</th><th>설명</th></tr></thead><tbody><tr><td>label</td><td>String</td><td>포털 위에 표시할 텍스트 값</td></tr><tr><td>targetMapID</td><td>String</td><td>이동할 외부 스페이스 ID값<br>* https://zep.us/play/[스페이스 ID]</td></tr><tr><td> locationName</td><td>String</td><td>이동할 로케이션의 이름 값</td></tr><tr><td>triggerByTouch</td><td>Boolean</td><td>true인 경우: 닿았을 때 실행 <br>false인 경우: F를 눌러 실행</td></tr><tr><td>invisible</td><td>Boolean</td><td>true인 경우: 기본 포털 이미지를 숨김 <br>false인 경우: 기본 포털 이미지가 보임</td></tr></tbody></table>

**예시**

```jsx
// 외부 스페이스로 이동하는 포털 타일을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.SPACE_PORTAL, {
	label: "SPACE_PORTAL",  // 선택
	targetMapID: "zydmYD", //필수
	locationName: "SPACE1",  // 선택
	invisible: true,  // 선택, 기본값 false
	triggerByTouch: true,  // 선택, 기본값 false
});
```

## 🌐 임베드 관련 타일 효과

### TileEffectType.EMBED

팝업으로 웹 링크를 여는 타일 효과입니다.

**파라미터**

<table><thead><tr><th width="165.33333333333331">이름</th><th width="98">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>웹 URL 값</td></tr><tr><td>align2</td><td>String</td><td>팝업을 표시할 위치<br>’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’</td></tr><tr><td>label</td><td>String</td><td>포털 위에 표시할 텍스트 값</td></tr><tr><td>triggerByTouch</td><td>Boolean</td><td>true인 경우: 닿았을 때 실행<br>false인 경우: F를 눌러 실행</td></tr><tr><td>invisible</td><td>Boolean</td><td>true인 경우: 기본 포털 이미지를 숨김<br>false인 경우: 기본 포털 이미지가 보임</td></tr></tbody></table>

**예시**

```jsx
// 팝업으로 웹 링크를 여는 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.EMBED, {
	link: "https://zep.us/", // 필수
	align2: "top", // 필수
	label: "ZEP-SCRIPT-EMBED",  // 선택
});
```

###

### TileEffectType.WEB\_PORTAL

새 탭으로 웹 링크를 여는 타일 효과입니다.

**파라미터**

<table><thead><tr><th width="158.33333333333331">이름</th><th width="108">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>웹 URL 값</td></tr><tr><td>label</td><td>String</td><td>포털 위에 표시할 텍스트 값</td></tr><tr><td>invisible</td><td>Boolean</td><td>true인 경우: 기본 포털 이미지를 숨김<br>false인 경우: 기본 포털 이미지가 보임</td></tr></tbody></table>

**예시**

```jsx
// 새탭으로 웹 링크를 여는 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.WEB_PORTAL, {
	link: "https://zep.us/", // 필수
	label: "ZEP-SCRIPT-WEB-PORTAL", // 선택
	invisible: true, // 선택, 기본값 false
});
```

###

### TileEffectType.TILE\_EMBED

웹 화면을 고정 영역에 표시하는 타일 효과입니다.

**파라미터**

<table><thead><tr><th width="144.33333333333331">이름</th><th width="132">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>웹 URL 값</td></tr><tr><td>width</td><td>number</td><td>고정 영역의 너비 ( 타일 수 )</td></tr><tr><td>height</td><td>number</td><td>고정 영역의 높이 ( 타일 수 )</td></tr></tbody></table>

**예시**

```jsx
// 웹 화면을 고정 영역에 설치합니다.
Map.putTileEffect(x, y, TileEffectType.TILE_EMBED, {
	link: "https://zep.us/", // 필수
	width: 5, // 필수
	height: 5, // 필수
});
```

## 💠 유틸 타일 효과

### TileEffectType.PRIVATE\_AREA

프라이빗 영역 타일 효과 입니다.

**파라미터**

<table><thead><tr><th width="147.33333333333331">이름</th><th width="109">타입</th><th>설명</th></tr></thead><tbody><tr><td>id</td><td>Number</td><td>프라이빗 영역의 ID 값</td></tr><tr><td>impassable</td><td>Boolean</td><td>true일 경우 프라이빗 영역을 impassable(통과 불과)로 만듭니다</td></tr><tr><td>param1</td><td>String</td><td>param1 이 “true” 일 경우 인원제한 프라이빗 영역이 설정됩니다.</td></tr></tbody></table>

**예시**

```jsx
// 해당 좌표에 프라이빗 영역을 설치합니다.
Map.putTileEffect(18, 15, TileEffectType.PRIVATE_AREA, {
		id: 3, // 필수
		impassable: false,  // 선택, 기본 값 false
		param1: "true",   // 선택, 기본 값 "false"
	});
```

###

### TileEffectType.LOCATION

지정영역 타일 효과 입니다.

**파라미터**

<table><thead><tr><th width="138.33333333333331">이름</th><th width="126">타입</th><th>설명</th></tr></thead><tbody><tr><td>label</td><td>String</td><td>타일 위에 표시할 텍스트 값</td></tr><tr><td>name</td><td>String</td><td>지정 영역의 이름</td></tr><tr><td>width</td><td>number</td><td>고정 영역의 너비 ( 타일 수 )</td></tr><tr><td>height</td><td>number</td><td>고정 영역의 높이 ( 타일 수 )</td></tr></tbody></table>

**예시**

```jsx
// 해당 좌표에 지정 영역을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.LOCATION, {
	label: "LOCATION",  // 선택
	name: "zep-script-location", // 필수
	width: 3, // 필수
	height: 2, // 필수
});
```

###

### TileEffectType.AMBIENT\_SOUND

배경 음악 타일 효과 입니다.

**파라미터**

<table><thead><tr><th width="169.33333333333331">이름</th><th width="122">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>재생할 음악 파일의 이름 ( 압축 파일에 포함 )</td></tr><tr><td>activeDistance</td><td>Number</td><td>음악의 재생 범위 ( 타일 )</td></tr><tr><td>triggerByTouch</td><td>Boolean</td><td>true인 경우: 닿았을 때 실행<br>false인 경우: F를 눌러 실행</td></tr></tbody></table>

**예시**

```jsx
Map.putTileEffect(x, y, TileEffectType.AMBIENT_SOUND, {
	link: "ring.mp3", // 필수
	activeDistance: 1, // 필수
	triggerByTouch: false, // 선택
});
```


# 기준 좌표

기준 좌표란 오브젝트 배치 시 이미지가 그려지는 기준을 의미합니다.

다음과 같은 맵에 **Left-Top** 기준으로 오른쪽의 오브젝트 이미지를 3, 3에 삽입하면 어떻게 될까요?

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FOVhBBd1erlwm4O5abcNe%2FUntitled.png?alt=media&amp;token=021cc3c7-847a-41bd-9b5c-45646bec2cec" alt=""><figcaption></figcaption></figure>

기준 좌표가 **Left-Top**일 경우 다음 그림과 같이 3, 3 박스의 왼쪽 위 지점부터 이미지가 그려집니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F0XWvUAVqs5KLN0ZFU2yp%2FUntitled%201.png?alt=media&amp;token=5c4a0950-026b-4ab7-90ef-294c54d23df0" alt=""><figcaption></figcaption></figure></div>

***

***


# 외부 API 통신하기

외부 API에 GET, POST 등의 요청을 인자와 함께 보낼 수 있습니다.

### httpGet

[<mark style="color:purple;">한국어 별명 생성기</mark>](https://nickname.hwanmoo.kr/) API를 이용해 입장하는 플레이어의 닉네임을 바꿔보기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F4NxVaReOQTRYUh5Hba16%2FUntitled.png?alt=media\&token=7e3f7d3e-8df9-4d2b-b184-6443f4c9ba27)

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
	App.httpGet(
		"https://nickname.hwanmoo.kr/?format=json&count=1&max_length=6&whitespace=_",
		null,
		function (res) {
			// 응답 결과를 JSON 오브젝트로 변경
			let response = JSON.parse(res);
			player.name = response.words[0];
			player.sendUpdated();
		}
	);
});
```

### httpPost

앱에서 보낸 헤더와 데이터를 응답으로 받아 채팅창에 출력해보기.

```jsx
// q를 눌렀을 때 실행되는 함수
App.addOnKeyDown(81, function (player) {
	App.httpPost(
		"https://postman-echo.com/post",
		{
			"test-header": "zep",
		},
		{
			name: "zepscript",
		},
		(res) => {
			let response = JSON.parse(res);
			App.sayToAll(`보낸 헤더: ${response.headers["test-header"]}`, 0xffffff);
			App.sayToAll(`보낸 데이터: ${response.form.name}`, 0xffffff);
		}
	);
});
```

{% hint style="warning" %}
참고&#x20;

\- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.&#x20;

\- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.&#x20;

\- 배포 방법을 아직 모르신다면, [<mark style="color:purple;">ZEP Script 배포 가이드</mark>](/creator/dev-guide/zep-script)를 참고해주세요.
{% endhint %}


# URL 쿼리스트링 활용하기

URL 쿼리스트링이란 URL의 뒤에 입력 데이터를 함께 제공하는 데이터 전달 방법 입니다. \
**예시)** <mark style="color:purple;">`https://zep.us/play/{mapHashId}?{파라미터}={값}`</mark>

URL 쿼리스트링을 이용해 ZEP 스페이스 또는 ZEP Script로 데이터를 전달 할 수 있습니다.

## ZEP에서 사용 가능한 URL 쿼리스트링 파라미터

### 1. name

비로그인 유저 입장 시 닉네임을 name 파라미터로 전달한 값으로 설정할 수 있습니다.

* 예시: <mark style="color:purple;">`https://zep.us/play/{mapHashId}?name=학생A`</mark>

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FH2SBg07qF8X75XHSXHZw%2FUntitled.png?alt=media&amp;token=63e1c7c7-ca6c-47a8-a5aa-adcbe8c5a22c" alt=""><figcaption></figcaption></figure></div>

:warning: 게스트 유저 닉네임 설정 팝업 비활성화 설정을 해주세요!

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FkG1Jg4TdJHJjfptzGLHu%2FUntitled%201.png?alt=media&amp;token=47c96fb4-96ee-4799-9465-928d013b9fdf" alt=""><figcaption></figcaption></figure></div>

### 2. customData

ZEP Script player 객체에 데이터를 전달할 수 있습니다. \
SSO 토큰 정보 등의 유저 식별 정보를 ZEP Script로 전달하여 유저를 식별하는 등 화이트리스트 기능을 만드는 데 활용할 수 있습니다.

**예제 1** \
customData로 플레이어 정보를 받아 적용해보기 (노멀앱, 사이드바앱 권장)

⚡예제에서 사용한 URL

<mark style="color:purple;">`https://zep.us/play/{mapHashId}?customData={"name":"커스텀유저", "moveSpeed":150, "title":"커스텀타이틀"}`</mark>

```jsx
App.onJoinPlayer.Add(function (player) {
  // 전달받은 customData가 있는지 체크
	if (player.customData) {
		// customData를 오브젝트로 변환하여 사용
		let playerCustomData = JSON.parse(player.customData);
		if (playerCustomData.hasOwnProperty("name")) {
			player.name = playerCustomData["name"];
		}
		if (playerCustomData.hasOwnProperty("moveSpeed")) {
			player.moveSpeed = playerCustomData["moveSpeed"];
		}
		if (playerCustomData.hasOwnProperty("title")) {
			player.title = playerCustomData["title"];
		}
		App.sayToAll("커스텀 데이터를 적용했습니다");
		player.sendUpdated();
	}
  // 전달받은 customData가 없다면 메시지 출력 
  else {
		App.sayToAll("커스텀 데이터를 전달받지 못했습니다.");
	}
});
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FIxX4jY9rqd2M48cLoq2A%2FUntitled%202.png?alt=media&amp;token=7e0235a1-51cb-4617-bec9-cfee8a91f02c" alt=""><figcaption><p>customData를 전달받지 못한 경우</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FgL1sI8Qg3XcryCoEvXnW%2FUntitled%203.png?alt=media&amp;token=ff7ec3c1-a71f-4fd8-a339-b6e8b42ea769" alt=""><figcaption><p>customData를 전달받아 적용한 경우</p></figcaption></figure></div>

**예제 2**

간단한 토큰기반 화이트리스트 기능 만들어보기 (노멀앱, 사이드바앱 권장)

{% file src="/files/fgE7LgU5HV5dQtW2hCcG" %}

✅ 브라우저 개발자 도구(F12)에서 다음과 같이 base64 암호화를 하여 토큰을 만들었습니다.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FoOASWZo8UeUdNaIvOtio%2FUntitled%204.png?alt=media&amp;token=8b1fe85d-027f-4fa7-a301-942bf432a524" alt=""><figcaption></figcaption></figure></div>

⚡ 예제에서 사용한 URL

<mark style="color:purple;">`https://zep.us/play/{mapHashId}?customData=JTdCJTIydG9rZW4lMjIlM0ElMjIlRUMlOUMlQTAlRUMlQTAlODAxJTJGd2hpdGVMaXN0JTIyJTdE`</mark>

```jsx
App.onJoinPlayer.Add(function (player) {
	player.tag = {};
	if (player.customData) {
		let token = player.customData;
		// 위젯으로 토큰을 보내 복호화하기
		player.tag.widget = player.showWidget("main.html", "topleft", 1, 1);
		player.tag.widget.sendMessage({
			type: "decode",
			token: token,
		});
		player.tag.widget.onMessage.Add(function (player, data) {
			if (data.type == "decode") {
				let decodedToken = data.decodedToken;
				App.sayToAll(decodedToken);
				// 복호화 된 코드를 받은 후 오브젝트로 변환
				let playerData = JSON.parse(decodedToken);

				let playerName = playerData["token"].split("/")[0];
				let isTrusted = playerData["token"].split("/")[1];

				if (isTrusted == "whiteList") {
					player.name = playerName;
					player.title = "인증된유저";
					player.sendUpdated();
				}
				player.tag.widget.destroy();
				player.tag.widget = null;
			}
		});
	} else {
		App.sayToAll("커스텀 데이터를 전달받지 못했습니다.");
	}
});
```

```powershell
// 위젯에서 사용한 스크립트
window.addEventListener("message", function (e) {
  if (e.data.type == "decode") {
		// base64 토큰 복호화
    decodedToken = decodeURIComponent(atob(e.data.token));
    window.parent.postMessage({
      type: "decode",
      decodedToken: decodedToken
    }, "*")
  }
})
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Feuklzw9QHptFNIhd5buO%2FUntitled%205.png?alt=media&amp;token=5089fdea-1a2a-4e37-8537-d7757c756cc6" alt=""><figcaption><p>토큰으로 인증에 성공한 경우</p></figcaption></figure></div>


# 위젯에서 사용가능한 문법

## 새 창에서 웹페이지 열기 <a href="#windowopen" id="windowopen"></a>

위젯의 `<script>` 태그 안에 다음과같은 코드를 입력해 새 창에서 웹페이지를 열 수 있습니다.

```
let url = "https://zep.us/";
window.parent.postMessage({
	type: 'ScriptAction:OPEN_WINDOW',
	link: url,
	zepSystem: true
}, '*');
```


# 모바일버튼 이미지 변경하기

모바일 버튼의 이미지를 App.loadSpriteSheet로 불러온 이미지로 변경 할 수 있습니다.

> 참고: 모바일  점프버튼의 이미지 크기는 180 x 180px 입니다.

```

let button_image = App.loadSpritesheet("button_image.png");
let button;

App.onStart.Add(function () {
	// button 추가
	button = App.addMobileButton(8, 145, 75, function (player) {
		App.sayToAll(`${player.name}, 님이 버튼을 눌렀습니다.`)
	});

	button.image = button_image;
	button.sendUpdated();
});


```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FuJ7qpKmqTVVWoxD5filM%2Fimage.png?alt=media&amp;token=a4ec44ae-72c8-4eba-aa43-60c2ec948c73" alt=""><figcaption><p>모바일 버튼 이미지 변경 예시</p></figcaption></figure></div>


# ZEP 스크립트 상호작용 오브젝트 가이드

맵 에디터에서 오브젝트를 설치하여 상호작용시 ZEP 스크립트에서 감지해 상호 작용시 발생하는 이벤트를 스크립트로 작성할 수 있습니다.

\
맵 에디터에서 다음과 같이 ZEP Script 상호작용 오브젝트를 설치 할 수 있습니다.

&#x20;\[맵 에디터]  → \[오브젝트설치] → \[개발자 기능] → \[ZEP Script 상호작용 선택]

<div><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FdvREOS50NcblXZ1N1GKc%2Fimage.png?alt=media&amp;token=68311f09-4ade-499b-bec0-3b137af4a3ff" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMYR1wgzug7s0VoBR63WR%2Fimage.png?alt=media&amp;token=ed92ad4d-3efd-48b8-8466-405555b0890c" alt=""><figcaption></figcaption></figure></div>

오브젝트 설정에서 값을 입력한 뒤,&#x20;

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fd3xHn9I9W2hwnNBaZyrM%2Fimage.png?alt=media&amp;token=e498e873-e4eb-4269-b5d4-6f71d3200b59" alt=""><figcaption></figcaption></figure></div>

다음과 같이 스크립트를 작성하여 앱 실행 후, 오브젝트와 상호작용시 입력한 값이 출력되는 것을 확인할 수 있습니다.

```javascript
App.onObjectTouched.Add(function (sender, x, y, tileID, obj) {
    if (obj !== null) {
        if (obj.type == ObjectEffectType.INTERACTION_WITH_ZEPSCRIPTS) {
            App.sayToAll(`Number = ${obj.text}, Value = ${obj.param1}`, 0xFFFFFF);
        }
    } else {
        App.sayToAll(`obj is null`, 0xFFFFFF);
    }
});
```

Script에서 `[obj.type == ObjectEffectType.INTERACTION_WITH_ZEPSCRIPTS]`를 조건으로 활용해 주세요.


# 오브젝트 npcProperty

npcProperty는`Map.putObjectWithKey(x, y, dynamicResource, option)`함수의

option 파라미터에서 지정할 수 있으며, 다음 7가지 속성을 지정할 수 있습니다.

<table><thead><tr><th width="171.33333333333331">이름</th><th width="113">타입</th><th>설명</th></tr></thead><tbody><tr><td>name</td><td>string</td><td>오브젝트 위에 표시될 이름</td></tr><tr><td>hp</td><td>number</td><td>오브젝트의 현재 체력</td></tr><tr><td>hpMax</td><td>number</td><td>오브젝트의 최대 체력</td></tr><tr><td>gaugeWidth</td><td>number</td><td><p>체력 게이지바의 너비</p><p>생략시 이미지의 너비 크기로 설정됩니다.</p></td></tr><tr><td>hpColor</td><td>number</td><td>체력 게이지바의 색상<br> ex) 0x03ff03 (초록색)</td></tr></tbody></table>

**예제 1 - npcProperty를 사용해 오브젝트 생성하기**

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FxC35ScV5OcLBBuBW3xzj%2FGIF%202023-03-27%20%EC%98%A4%ED%9B%84%2012-35-27.gif?alt=media&amp;token=19ef6f61-5b1a-4a2b-abf5-e6203ad7d473" alt=""><figcaption></figcaption></figure></div>

{% file src="/files/8aMXbuLMGbiMhyMvSK6g" %}
예제 파일
{% endfile %}

```javascript
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9],
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
    dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
    down_jump: [38],
    left_jump: [39],
    right_jump: [40],
    up_jump: [41],
}, 8);
App.addOnKeyDown(81, function (player) {
    const objectKey = "TestBlueMan";
    const bluemanObject = Map.putObjectWithKey(18, 6, blueman, {
	npcProperty: { name: "BlueMan", hpColor: 0x03ff03, hp: 100, hpMax: 100 },
	overlap: true,
	movespeed: 100,
	key: objectKey, 
	useDirAnim: true,
        offsetX: -8,
        offsetY: -32,
    });

    Map.playObjectAnimationWithKey(objectKey, "down", -1);
});
```

**예제 2 - npcProperty로 체력이 깎이는 효과 구현하기**

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMEvdzCEr9tFURVDhF6qY%2FGIF%202023-03-30%20%EC%98%A4%ED%9B%84%203-30-36.gif?alt=media\&token=308c2dbd-32df-49c0-9b4d-5caa9a76031d)

{% file src="/files/YVP2oZHOPLgfiZnZBwC0" %}
예제 파일
{% endfile %}

```javascript
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
    dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
    down_jump: [38],
    left_jump: [39],
    right_jump: [40],
    up_jump: [41],
}, 8);

App.addOnKeyDown(81, function (player) {
    const objectKey = "TestBlueMan";
    const bluemanObject = Map.putObjectWithKey(18, 6, blueman, {
            npcProperty: { name: "BlueMan", hpColor: 0x03ff03, hp: 100, hpMax: 100 },
            overlap: true,
            collide: true, // ★ collid: true
            movespeed: 100, 
            key: objectKey, 
            useDirAnim: true,
            offsetX: -8,
            offsetY: -32,
    });

    Map.playObjectAnimationWithKey(objectKey, "down", -1);
});

App.onAppObjectAttacked.Add(function (p, x, y, layer, key) {
    const targetObject = Map.getObjectWithKey(key);
    targetObject.npcProperty.hp -= 10;
    if(targetObject.npcProperty.hp > 0) {
        const hpPercentage = targetObject.npcProperty.hp / targetObject.npcProperty.hpMax;
        if (hpPercentage < 0.3) {
            targetObject.npcProperty.hpColor = 0xff0000;
        } else if (hpPercentage < 0.7) {
            targetObject.npcProperty.hpColor = 0xffa500;
        }
        targetObject.sendUpdated();
    } else {
        Map.putObjectWithKey(targetObject.tileX, targetObject.tileY, null, { key: key })
    }
});

```


# ZEP Script API

이 문서는 ZEP Script 를 구성하는 4가지 클래스를 안내합니다.

### 소개

ZEP Script는 다음과 같은 4가지 클래스로 구성되어 있습니다.

### [<mark style="color:purple;">ScriptApp</mark>](/zep-script-api/zepscriptapi/scriptapp)

앱이 설치된 스페이스와 관련된 전반적인 기능을 담당하는 스크립트

* [<mark style="color:purple;">Lifecycle</mark>](/zep-script-api/zepscriptapi/scriptapp/lifecycle)
* [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptapp/field)
* [<mark style="color:purple;">Event Listeners</mark>](/zep-script-api/zepscriptapi/scriptapp/event-listeners)
* [<mark style="color:purple;">Callbacks</mark>](/zep-script-api/zepscriptapi/scriptapp/callbacks)
* [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptapp/methods)

### [<mark style="color:purple;">ScriptMap</mark>](/zep-script-api/zepscriptapi/scriptmap)

맵에 타일이나 오브젝트를 추가, 수정, 삭제하는 기능을 담당하는 스크립트

* [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptmap/field)
* [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptmap/methods)

### [<mark style="color:purple;">ScriptPlayer</mark>](/zep-script-api/zepscriptapi/scriptplayer)

플레이어의 설정이나 좌표 지정, 유저의 정보를 불러오는 기능을 담당하는 스크립트

* [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptplayer/field)
* [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptplayer/methods)

### [<mark style="color:purple;">ScriptWidget</mark>](/zep-script-api/zepscriptapi/scriptwidget)

미리 만들어놓은 HTML을 위젯 형태로 맵 내에서 사용할 수 있는 스크립트

* [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptwidget/field)
* [<mark style="color:purple;">Event Listeners</mark>](/zep-script-api/zepscriptapi/scriptwidget/event-listeners)
* [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptwidget/methods)


# API Summary

ZEP-Script API 한눈에 보기

## :mag:<mark style="color:blue;">Ctrl + F를 이용해 원하는 API를 찾아보세요!</mark>

## ═════════════════════

## 🕹️ ScriptApp&#x20;

## ═════════════════════

## ♻️ Lifecycle

### [onInit](/zep-script-api/zepscriptapi/scriptapp/lifecycle#oninit)

{% hint style="info" %}
&#x20;App.onInit.Add(function(){})
{% endhint %}

App이 최초로 시작될 때 한 번 호출됩니다.

### [onJoinPlayer](/zep-script-api/zepscriptapi/scriptapp/lifecycle#onjoinplayer)

{% hint style="info" %}
App.onJoinPlayer.Add(function(player){})
{% endhint %}

onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.

### [onStart](/zep-script-api/zepscriptapi/scriptapp/lifecycle#onstart)

{% hint style="info" %}
App.onStart.Add(function(){})
{% endhint %}

모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.

### [onUpdate](/zep-script-api/zepscriptapi/scriptapp/lifecycle#onupdate)

{% hint style="info" %}
App.onUpdate.Add(function(dt){})
{% endhint %}

약 20ms 마다 주기적으로 실행되는 함수입니다.

### [onLeavePlayer](/zep-script-api/zepscriptapi/scriptapp/lifecycle#onleaveplayer)

{% hint style="info" %}
&#x20;App.onLeavePlayer.Add(function(player){})
{% endhint %}

퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.

### [onDestroy](/zep-script-api/zepscriptapi/scriptapp/lifecycle#ondestroy)

{% hint style="info" %}
App.onDestroy.Add(function(){})
{% endhint %}

다른 App이 실행되거나, 설치한 Game Block이 파괴될 때 동작합니다.

## 🗃️ Field

### [spaceHashID & mapHashID](/zep-script-api/zepscriptapi/scriptapp/field#spacehashid-and-maphashid)

{% hint style="info" %}
App.spaceHashID: String \
App.mapHashID: String
{% endhint %}

앱이 설치된 스페이스의 spaceHashID와 mapHashID를 가져옵니다. [<mark style="color:purple;">(스페이스와 맵 이해하기)</mark>](/creator/reference/undefined)

### [creatorID](/zep-script-api/zepscriptapi/scriptapp/field#creatorid)

{% hint style="info" %}
App.creatorID
{% endhint %}

App을 실행한 플레이어의 ID 값을 가져옵니다.

### [players](/zep-script-api/zepscriptapi/scriptapp/field#players)

{% hint style="info" %}
App.players: ScriptPlayer\[]
{% endhint %}

맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.

### [playerCount](/zep-script-api/zepscriptapi/scriptapp/field#playercount)

{% hint style="info" %}
App.playerCount: Number
{% endhint %}

앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.

### [cameraEffect & cameraEffectParam1](/zep-script-api/zepscriptapi/scriptapp/field#cameraeffect-and-cameraeffectparam1)

{% hint style="info" %}
App.cameraEffect: NONE = 0, SPOTLIGHT = 1 \
App.cameraEffectParam1: Number
{% endhint %}

App.cameraEffect: 카메라 이펙트의 종류를 셋팅할 변수 값

App.cameraEffectParam1: 카메라 이펙트 효과의 범위 값

### [displayRatio](/zep-script-api/zepscriptapi/scriptapp/field#displayratio)

{% hint style="info" %}
App.displayRatio
{% endhint %}

화면의 줌을 컨트롤 하는 값 (기본 값: 1)

### [storage](/zep-script-api/zepscriptapi/scriptapp/field#storage)

{% hint style="info" %}
App.storage: String
{% endhint %}

스페이스 내의 App 데이터의 저장공간 입니다. (스페이스 한정) [**Storage 페이지**](/zep-script-api/zepscriptapi/scriptapp/storage)를 참고해주세요.

### [followPlayer](/zep-script-api/zepscriptapi/scriptapp/field#followplayer)

{% hint style="info" %}
&#x20;App.followPlayer: Boolean
{% endhint %}

App의 따라가기 기능 활성화 여부 값 입니다. (기본 값 : false)

### [showName](/zep-script-api/zepscriptapi/scriptapp/field#showname)

{% hint style="info" %}
&#x20;App.showName: Boolean
{% endhint %}

플레이어의닉네임 숨김여부 값 입니다. (기본 값: true)

### [appHashID](/zep-script-api/zepscriptapi/scriptapp/field#apphashid)

{% hint style="info" %}
App.appHashID: String&#x20;
{% endhint %}

앱의 HashID를 가져옵니다.

## ️💾 Storage

### [setStorage, getStorage](/zep-script-api/zepscriptapi/scriptapp/storage)

{% hint style="info" %}
App.setStorage(string) / App.getStorage(function())
{% endhint %}

App.setStorage 함수로 App storage를 저장하고, 앱을 사용중인 다른 맵의 App storage 변경사항이 있을 경우 App.getStorage 함수로 App storage를 동기화 할 수 있습니다.

{% hint style="warning" %}
App.getStorage 함수는 비동기 함수이기 때문에 App.getStorage 함수 다음 라인에 App.storage를 사용하는 코드를 작성할 경우 동기화를 보장할 수 없습니다.
{% endhint %}

## 🛰️ EventListeners

### [onSay](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onsay)

{% hint style="info" %}
App.onSay.Add(function(player, text){});
{% endhint %}

플레이어가 채팅을 입력할 때 동작합니다.

### [onPlayerTouched](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onplayertouched)

{% hint style="info" %}
App.onPlayerTouched.Add(function(sender, target, x, y){});
{% endhint %}

캐릭터들끼리 충돌할 때 동작합니다.

### [onObjectTouched](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onobjecttouched)

{% hint style="info" %}
App.onObjectTouched.Add(function(sender, x, y, tileID, obj){});
{% endhint %}

캐릭터가 오브젝트와 충돌할 때 동작합니다.

### [onAppObjectTouched](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onappobjecttouched)

{% hint style="info" %}
App.onAppObjectTouched.Add(function(sender, key, x, y ){});
{% endhint %}

️ 캐릭터가 키 값을 가진 오브젝트와 충돌할 때 동작합니다.

### [onUnitAttacked](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onunitattacked)

{% hint style="info" %}
App.onUnitAttacked.Add(function(sender, x, y, target){});
{% endhint %}

플레이어가 공격 키(Z)로 다른 캐릭터를 공격할 때 동작합니다.

### [onObjectAttacked](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onobjectattacked)

{% hint style="info" %}
App.onObjectAttacked.Add(function(sender, x, y){});
{% endhint %}

플레이어가 공격 키(Z)로 오브젝트를 공격할 때 동작합니다.

### [onSidebarTouched](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onsidebartouched)

{% hint style="info" %}
App.onSidebarTouched.Add(function(player){});
{% endhint %}

플레이어가 사이드바 앱을 클릭(터치) 할 때 동작합니다.

### [onTriggerObject](/zep-script-api/zepscriptapi/scriptapp/event-listeners#ontriggerobject)

{% hint style="info" %}
App.onTriggerObject.Add(function(player, layerID, x, y, key){});
{% endhint %}

오브젝트와 F 상호작용 시 동작하는 함수입니다.

### [onAppObjectAttacked](/zep-script-api/zepscriptapi/scriptapp/event-listeners#onappobjectattacked)

{% hint style="info" %}

```
App.onAppObjectAttacked.Add(function (sender, x, y, layer, key) {});
```

{% endhint %}

플레이어가 공격 키(Z)로 키 값을 가진 오브젝트를 공격할 때 동작합니다.

## ☎️ Callbacks

### [runLater](/zep-script-api/zepscriptapi/scriptapp/callbacks#runlater)

{% hint style="info" %}
App.runLater(function(){}, time: number);
{% endhint %}

time(초) 후에 callback 함수를 실행합니다.

### [addOnTileTouched](/zep-script-api/zepscriptapi/scriptapp/callbacks#addontiletouched)

{% hint style="info" %}
App.addOnTileTouched(x:number, y: number, function(player){})
{% endhint %}

지정한 x, y좌표에 플레이어가 도착할 경우 callback 함수를 실행합니다.

### [addOnLocationTouched](/zep-script-api/zepscriptapi/scriptapp/callbacks#addonlocationtouched)

{% hint style="info" %}
App.addOnLocationTouched(name: string, function(player){})
{% endhint %}

플레이어가 맵에디터에서 지정한 ‘지정영역’에 도착했을 때 callback 함수를 실행합니다.

### [addOnKeyDown](/zep-script-api/zepscriptapi/scriptapp/callbacks#addonkeydown)

{% hint style="info" %}
App.addOnKeyDown(keycode : number, function(player){});
{% endhint %}

플레이어가 지정된 키를 눌렀을 때 callback 함수를 실행합니다.

### [setTimeout](/zep-script-api/zepscriptapi/scriptapp/callbacks#settimeout)

{% hint style="info" %}
setTimeout(function(){}, time: number);
{% endhint %}

time(ms) 후에 callback 함수를 실행합니다.

### [setInterval](/zep-script-api/zepscriptapi/scriptapp/callbacks#setinterval)

{% hint style="info" %}
setInterval(function(){}, time: number);
{% endhint %}

time(ms) 간격으로 callback 함수를 실행합니다.

### [addMobileButton](/zep-script-api/zepscriptapi/scriptapp/callbacks#addmobilebutton)

{% hint style="info" %}
App.addMobileButton( anchor: number, posX: number, posY: number, function(player){} )
{% endhint %}

모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.

### [putMobilePunch](/zep-script-api/zepscriptapi/scriptapp/callbacks#putmobilepunch)

{% hint style="info" %}
App.putMobilePunch(enable: boolean = true)
{% endhint %}

enable이 true이면 모바일 환경에서 펀치 버튼이 추가됩니다.

### [putMobilePunchWithIcon](/zep-script-api/zepscriptapi/scriptapp/callbacks#putmobilepunchwithicon)

{% hint style="info" %}
App.putMobilePunchWithIcon(icon: ScriptDynamicResource)
{% endhint %}

로드한 이미지로 펀치 버튼을 만들어 추가합니다.

## 💠 Methods

### [loadSpritesheet](/zep-script-api/zepscriptapi/scriptapp/methods#loadspritesheet)

{% hint style="info" %}
App.loadSpritesheet(fileName: string, frameWidth: number, frameHeight: number, anims: array, frameRate: number): ScriptDynamicResource
{% endhint %}

스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.

### [showCenterLabel](/zep-script-api/zepscriptapi/scriptapp/methods#showcenterlabel)

{% hint style="info" %}
App.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: number = 0, time: number = 3000)
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

### [showCustomLabel](/zep-script-api/zepscriptapi/scriptapp/methods#showcustomlabel)

{% hint style="info" %}
App.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: number = 3000);
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

text 부분에 <mark style="color:purple;">`span`</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.

### [sayToAll](/zep-script-api/zepscriptapi/scriptapp/methods#saytoall)

{% hint style="info" %}
&#x20;App.sayToAll(text: string, color: uint = 0xFFFFFF)
{% endhint %}

채팅창에 text 내용을 출력합니다.

### [sayToStaffs](/zep-script-api/zepscriptapi/scriptapp/methods#saytostaffs)

{% hint style="info" %}
&#x20;App.sayToStaffs(text: string, color: uint = 0xFFFFFF)
{% endhint %}

Staff이상 권한의 유저 채팅창에 text 내용을 출력합니다.

### [showWidget](/zep-script-api/zepscriptapi/scriptapp/methods#showwidget)

{% hint style="info" %}
&#x20;App.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

모든 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.

### [showYoutubeWidget](/zep-script-api/zepscriptapi/scriptapp/methods#showyoutubewidget)

{% hint style="info" %}
App.showYoutubeWidget(link: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.

### [spawnPlayer](/zep-script-api/zepscriptapi/scriptapp/methods#spawnplayer)

{% hint style="info" %}
App.spawnPlayer(playeID: string, tileX: number, tileY: number)
{% endhint %}

playerID 에 해당하는 플레이어를 tileX, tileY 좌표로 이동시키는 함수입니다.

### [kickPlayer](/zep-script-api/zepscriptapi/scriptapp/methods#kickplayer)

{% hint style="info" %}
App.kickPlayer(playerID: string)
{% endhint %}

playerID 에 해당하는 플레이어를 추방하는 함수입니다.

### [forceDestroy](/zep-script-api/zepscriptapi/scriptapp/methods#forcedestroy)

{% hint style="info" %}
App.forceDestroy();
{% endhint %}

미니게임 앱을 강제 종료하는 함수입니다.

### [clearChat](/zep-script-api/zepscriptapi/scriptapp/methods#clearchat)

{% hint style="info" %}
App.clearChat();
{% endhint %}

모든 채팅 내용을 삭제하는 함수입니다.

### [getPlayerByID](/zep-script-api/zepscriptapi/scriptapp/methods#getplayerbyid)

{% hint style="info" %}
App.getPlayerByID(playerID: string);
{% endhint %}

id 에 해당하는 플레이어를 반환하는 함수입니다.

### [playSound](/zep-script-api/zepscriptapi/scriptapp/methods#playsound)

{% hint style="info" %}
App.playSound(fileName: string, loop: boolean = false, overlap: boolean = false)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

### [playSoundLink](/zep-script-api/zepscriptapi/scriptapp/methods#playsoundlink)

{% hint style="info" %}
App.playSoundLink(link: string, loop: boolean = false)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

### [stopSound](/zep-script-api/zepscriptapi/scriptapp/methods#stopsound)

{% hint style="info" %}
App.stopSound();
{% endhint %}

재생되고 있는 사운드를 멈추는 함수입니다.

### [httpGet](/zep-script-api/zepscriptapi/scriptapp/methods#httpget)

{% hint style="info" %}
App.httpGet(url: string, headers: object, function(res: string){})
{% endhint %}

http get 요청을 보내는 함수입니다.

### [httpPost](/zep-script-api/zepscriptapi/scriptapp/methods#httppost)

{% hint style="info" %}
App.httpPost(url: string, headers: object, body: object, function(res: string))
{% endhint %}

Form-Data 형태의 http post 요청을 보내는 함수 입니다.

### [httpPostJson](/zep-script-api/zepscriptapi/scriptapp/methods#httppostjson)

{% hint style="info" %}
App.httpPostJson(url: string, headers: object, body: object, function(res: string))
{% endhint %}

Json 형태의 http post 요청을 보내는 함수 입니다.

### [sendUpdated](/zep-script-api/zepscriptapi/scriptapp/methods#sendupdated)

{% hint style="info" %}
App.sendUpdated()
{% endhint %}

App 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.

### [changeAttackSound](/zep-script-api/zepscriptapi/scriptapp/methods#changeattacksound)

{% hint style="info" %}
App.changeAttackSound(fileName:string)
{% endhint %}

찌르기(Z키) 공격 효과음을 변경하는 함수입니다.

## ═════════════════════

## 🗺️ ScriptMap&#x20;

## ═════════════════════

## 🗃️ Field

### [width & height](/zep-script-api/zepscriptapi/scriptmap/field#width-and-height)

{% hint style="info" %}
Map.width : Number Map.height : Number
{% endhint %}

맵의 너비와 높이 값을 가져옵니다.

## 💠 Methods

### [putTileEffect](/zep-script-api/zepscriptapi/scriptmap/methods#puttileeffect)

{% hint style="info" %}
Map.putTileEffect(x: number, y: number, tileID: TileEffectType)
{% endhint %}

지정한 좌표에 타일 효과를 적용하는 함수입니다.

### [putObject](/zep-script-api/zepscriptapi/scriptmap/methods#putobject)

{% hint style="info" %}
Map.putObject(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)
{% endhint %}

지정한 좌표에 오브젝트를 놓는 함수입니다. (기준 좌표: Left Top) → [<mark style="color:purple;">**기준 좌표란?**</mark>](/creator/reference/coordinate)

### [putObjectMultiple](/zep-script-api/zepscriptapi/scriptmap/methods#putobjectmultiple)

{% hint style="info" %}
Map.putObjectMultiple(tileArray: array, type: PutObjectType, dynamicResource: ScriptDynamicResource);
{% endhint %}

오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다. 이 기능을 사용하면 한 번에 많은 오브젝트를 설치할 경우 부하를 줄이는 효과를 얻을 수 있습니다.

### [putObjectWithKey](/zep-script-api/zepscriptapi/scriptmap/methods#putobjectwithkey)

{% hint style="info" %}
Map.putObjectWithKey(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)
{% endhint %}

지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다. (기준 좌표: Left Top)

### [getObjectWithKey](/zep-script-api/zepscriptapi/scriptmap/methods#getobjectwithkey)

{% hint style="info" %}
Map.getObjectWithKey(key: String)
{% endhint %}

해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.

### [playObjectAnimation](/zep-script-api/zepscriptapi/scriptmap/methods#playobjectanimation)

{% hint style="info" %}
Map.playObjectAnimation(x: number, y: number, name: string)
{% endhint %}

해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.

해당 좌표 지점에[ <mark style="color:purple;">Map.putObject</mark>](#putobject) 함수가 선행되어야합니다.

### [playObjectAnimationWithKey](/zep-script-api/zepscriptapi/scriptmap/methods#playobjectanimationwithkey)

{% hint style="info" %}
Map.playObjectAnimation(key: string, animName: string, repeatCount: number)
{% endhint %}

key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.

###

### [moveObject](/zep-script-api/zepscriptapi/scriptmap/methods#moveobject)

{% hint style="info" %}
Map.moveObject(x: number, y: number, targetX: number, targetY: number, time: number)
{% endhint %}

x, y 좌표에 위치한 오브젝트를 targetX, targetY로 time 초 동안 움직이는 함수입니다.

해당 좌표 지점에 [<mark style="color:purple;">Map.putObject</mark>](https://app.gitbook.com/o/-MkvEtFn2kFBYSN4_5rX/s/iW553XSGeKCAPpImxXi3/~/changes/bRylDKw8UxLaVWLvuhVV/creator/zep-script-v2_kr/api/scriptmap/methods) 함수가 선행되어야합니다.

### [moveObjectWithKey](/zep-script-api/zepscriptapi/scriptmap/methods#moveobjectwithkey)

{% hint style="info" %}
Map.moveObjectWithKey(key: string, targetX: number, targetY: number, path:boolean = true)
{% endhint %}

key 값을 가진 오브젝트를 targetX, targetY로 움직이는 함수입니다.

### [clearAllObjects()](/zep-script-api/zepscriptapi/scriptmap/methods#clearallobjects)

{% hint style="info" %}
Map.clearAllObjects()
{% endhint %}

ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.

### [getTile](/zep-script-api/zepscriptapi/scriptmap/methods#gettile)

{% hint style="info" %}
Map.getTile(layer: number, x: number, y: number): number
{% endhint %}

해당하는 레이어의 x, y 좌표에 있는 타일의 타입 값을 리턴, 타일이 없으면 -1을 리턴합니다.

### [getLocation](/zep-script-api/zepscriptapi/scriptmap/methods#getlocation)

{% hint style="info" %}
Map.getLocation(locationName: string)
{% endhint %}

파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.

### [getLocationRandom](/zep-script-api/zepscriptapi/scriptmap/methods#getlocationrandom)

{% hint style="info" %}
Map.getLocationRandom(locationName: string)
{% endhint %}

파라미터로 전달한 로케이션이 2개이상  존재하는 경우, 무작위로 선택하여 로케이션 설치 좌표를 리턴합니다.

### [hasLocation](/zep-script-api/zepscriptapi/scriptmap/methods#haslocation)

{% hint style="info" %}
Map.hasLocation(locationName: String): Boolean
{% endhint %}

맵에 해당 로케이션이 있는지 체크하여 true/false 값을 리턴합니다.

### [getObjectsByType](/zep-script-api/zepscriptapi/scriptmap/methods#getobjectsbytype)

{% hint style="info" %}
Map.getObjectsByType(type: numer) : array
{% endhint %}

&#x20;Type에 해당하는 오브젝트들을 리턴하는 함수입니다.

### [getTopObjectsByType](/zep-script-api/zepscriptapi/scriptmap/methods#gettopobjectsbytype)

{% hint style="info" %}
Map.getTopObjectsByType(type: numer) : array
{% endhint %}

&#x20;Type에 해당하는 상단오브젝트들을 리턴하는 함수입니다.

### [sayObjectWithKey](/zep-script-api/zepscriptapi/scriptmap/methods#sayobjectwithkey)

{% hint style="info" %}
Map.sayObjectWithKey( key: string, message: string )
{% endhint %}

&#x20;key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.

## ═════════════════════

## 👥 ScriptPlayer&#x20;

## ═════════════════════

## 🗃️ Field

### [id , name](/zep-script-api/zepscriptapi/scriptplayer/field#id-name)

{% hint style="info" %}
player.id : Number player.name : String
{% endhint %}

플레이어의 id, 닉네임 값을 가져옵니다.

### [title](/zep-script-api/zepscriptapi/scriptplayer/field#title)

{% hint style="info" %}
player.title : String
{% endhint %}

title은 캐릭터 닉네임 위에 노란색으로 노출되는 텍스트입니다.

### [role](/zep-script-api/zepscriptapi/scriptplayer/field#role)

{% hint style="info" %}
player.role : Number
{% endhint %}

role은 플레이어의 권한을 나타내는 숫자 값 입니다. 플레이어의 role에 따라 다음과 같은 값을 출력합니다.

| 게스트 | -1   | 스태프  | 2000 |
| --- | ---- | ---- | ---- |
| 멤   | 0    | 관리자  | 3000 |
| 에디터 | 1000 | 맵소유자 | 3001 |

###

### [tileX, tileY](/zep-script-api/zepscriptapi/scriptplayer/field#tilex-tiley)

{% hint style="info" %}
&#x20;player.tileX: Number player.tileY: Number
{% endhint %}

플레이어의 캐릭터가 서있는 X 좌표 값과 Y 좌표 값입니다.

### [dir](/zep-script-api/zepscriptapi/scriptplayer/field#dir)

{% hint style="info" %}
player.dir : Number
{% endhint %}

플레이어의 캐릭터가 바라보고 있는 방향입니다.

캐릭터가 바라보고 있는 방향에 따라 다음과 같은 값을 출력합니다.

<div><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FhasohAG4tL42qlMxMJeO%2FUntitled-1_(1).png?alt=media&amp;token=8f57f3e5-5ae6-4e7f-9174-a052d15f56e6" alt=""><figcaption><p>캐릭터가 바라보는 방향에 따른 dir 값</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZjCNMuZ3t8q0YWJcngUz%2Fimage.png?alt=media&amp;token=59503601-1949-4726-94ae-843d764f0f5b" alt=""><figcaption></figcaption></figure></div>

### [moveSpeed](/zep-script-api/zepscriptapi/scriptplayer/field#movespeed)

{% hint style="info" %}
player.moveSpeed : Number
{% endhint %}

플레이어의 이동속도 값입니다. (기본 값: 80)

### [sprite](/zep-script-api/zepscriptapi/scriptplayer/field#sprite)

{% hint style="info" %}
player.sprite : ScriptDynamicResource
{% endhint %}

플레이어 캐릭터의 스프라이트 이미지입니다. (**null** 입력 시 기본 아바타 이미지로 초기화)

### [tag](/zep-script-api/zepscriptapi/scriptplayer/field#tag)

{% hint style="info" %}
&#x20;player.tag: Any
{% endhint %}

tag를 사용해 플레이어에게 필요한 속성 값을 부여 할 수 있습니다.

### [hidden](/zep-script-api/zepscriptapi/scriptplayer/field#hidden)

{% hint style="info" %}
player.hidden: Boolean
{% endhint %}

hidden 값이 true 이면, 해당 플레이어는 다른 플레이어에게 보이지 않습니다.

### [spotlight](/zep-script-api/zepscriptapi/scriptplayer/field#spotlight)

{% hint style="info" %}
player.spotlight: Boolean
{% endhint %}

플레이어의 스팟 라이트 기능 활성화 여부입니다.

### [attackType](/zep-script-api/zepscriptapi/scriptplayer/field#attacktype)

{% hint style="info" %}
player.attackType : Number
{% endhint %}

플레이어의 공격(Z키) 타입입니다. (기본: 0)

### [attackParam1](/zep-script-api/zepscriptapi/scriptplayer/field#attackparam1)

{% hint style="info" %}
player.attackParam1: Number
{% endhint %}

공격(Z키) 이미지가 날아가는 거리 속성입니다. 공격 가능 거리는 늘어나지 않습니다.

### [attackParam2](/zep-script-api/zepscriptapi/scriptplayer/field#attackparam2)

{% hint style="info" %}
player.attackParam2: Number
{% endhint %}

공격 가능 거리 속성입니다. attackType이 원거리 공격으로 설정 된 경우에만 유효합니다.

### [attackSprite](/zep-script-api/zepscriptapi/scriptplayer/field#attacksprite)

{% hint style="info" %}
player.attackSprite : ScriptDynamicResource
{% endhint %}

공격(Z키) 이미지 속성입니다.

### [walletAddress](/zep-script-api/zepscriptapi/scriptplayer/field#walletaddress)

{% hint style="info" %}
player.walletAddress : String
{% endhint %}

플레이어의 전자지갑 주소입니다.

### [storage](/zep-script-api/zepscriptapi/scriptplayer/field#storage)

{% hint style="info" %}
player.storage : String
{% endhint %}

스페이스 내의 Player 값 저장 공간 입니다 (스페이스 한정)

### [isMobile](/zep-script-api/zepscriptapi/scriptplayer/field#ismobile)

{% hint style="info" %}
&#x20;player.isMobile : Boolean
{% endhint %}

플레이어의 모바일 접속 여부를 true/false 로 출력합니다.

### [isMoving](/zep-script-api/zepscriptapi/scriptplayer/field#ismoving)

{% hint style="info" %}
player.isMoving : Boolean
{% endhint %}

플레이어가 움직이고 있으면 True, 아니면 False를 반환합니다.

### [isJumping](/zep-script-api/zepscriptapi/scriptplayer/field#isjumping)

{% hint style="info" %}
player.isJumping : Boolean
{% endhint %}

플레이어가 점프하고 있으면 True, 아니면 False를 반환합니다.

### [customData](/zep-script-api/zepscriptapi/scriptplayer/field#customdata)

{% hint style="info" %}
&#x20;player.customData : String
{% endhint %}

URL 쿼리스트링으로 전달 받은 값을 저장하는 필드입니다. :fire: [<mark style="color:purple;">URL 쿼리스트링 활용하기</mark>](/creator/reference/url)

### [displayRatio](/zep-script-api/zepscriptapi/scriptplayer/field#displayratio)

{% hint style="info" %}
player.displayRatio: number
{% endhint %}

플레이어화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )

### [titleColor](/zep-script-api/zepscriptapi/scriptplayer/field#titlecolor)

{% hint style="info" %}
player.titleColor: number
{% endhint %}

플레이어의 타이틀 색상을 읽거나 수정 할 수 있습니다.

### [emailHash](/zep-script-api/zepscriptapi/scriptplayer/field#emailhash)

{% hint style="info" %}
player.emailHash
{% endhint %}

플레이어의 이메일 Hash 값을 가져옵니다.

### [isGuest](/zep-script-api/zepscriptapi/scriptplayer/field#isguest)

{% hint style="info" %}
player.isGuest
{% endhint %}

비로그인 플레이어인 경우 true 값을 가집니다.

### [away](/zep-script-api/zepscriptapi/scriptplayer/field#away)

{% hint style="info" %}
player.away
{% endhint %}

5분 이상 비활성화된 유저인 경우 `true`값을 가집니다.

## 💠 Methods

### [showCenterLabel](/zep-script-api/zepscriptapi/scriptplayer/methods#showcenterlabel)

{% hint style="info" %}
&#x20;player.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: number = 0, time: number = 3000)
{% endhint %}

해당 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

### [showCustomLabel](/zep-script-api/zepscriptapi/scriptplayer/methods#showcustomlabel)

{% hint style="info" %}
player.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: number = 3000);
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

text 부분에 <mark style="color:purple;">`span`</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.

### [showWidget](/zep-script-api/zepscriptapi/scriptplayer/methods#showwidget)

{% hint style="info" %}
player.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

해당 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.

### [showBuyAlert](/zep-script-api/zepscriptapi/scriptplayer/methods#showbuyalert)

{% hint style="info" %}
player.showBuyAlert(itemName: string, price: number, callback: function, payToSpaceOwner: boolean = false)
{% endhint %}

플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다. **payToSpaceOwner** 옵션이 false인 경우 앱 소유자에게 수익이 전달되며, true인 경우에는 앱이 설치된 맵의 소유자에게 수익이 전달됩니다.

### [hideBuyAlert](/zep-script-api/zepscriptapi/scriptplayer/methods#hidebuyalert)

{% hint style="info" %}
player.hideBuyAlert()
{% endhint %}

플레이어의 구매 위젯을 닫습니다.

### [sendMessage](/zep-script-api/zepscriptapi/scriptplayer/methods#sendmessage)

{% hint style="info" %}
&#x20;player.sendMessage(text: string, color: uint = 0xFFFFFF)
{% endhint %}

유저 개인에게 채팅 메시지를 보내는 함수입니다.

### [showPrompt](/zep-script-api/zepscriptapi/scriptplayer/methods#showprompt)

{% hint style="info" %}
&#x20;player.showPrompt(text: string, function(inputText), option = {})
{% endhint %}

플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.

### [showConfirm](/zep-script-api/zepscriptapi/scriptplayer/methods#showconfirm)

{% hint style="info" %}
&#x20;player.showConfirm(text: string, function(result), option = {})
{% endhint %}

플레이어에게 확인창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다. cancel을 누를 경우에는 callback 함수가 동작하지 않습니다.

### [showAlert](/zep-script-api/zepscriptapi/scriptplayer/methods#showalert)

{% hint style="info" %}
&#x20;player.showAlert(text: string, function(), option = {})
{% endhint %}

플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.

### [showWidgetResponsive](/zep-script-api/zepscriptapi/scriptplayer/methods#showwidgetresponsive)

{% hint style="info" %}

```
player.showWidgetResponsive(fileName:string, marginTop:number, marginRight:number, marginBottom:number, marginLeft:number)
```

{% endhint %}

위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의 하여 위젯을 표시합니다.

화면의 크기가 여백을 포함한 위젯 영역보다 작아질 경우, 위젯의 크기가 비례하여 작아집니다

### [openWebLink](/zep-script-api/zepscriptapi/scriptplayer/methods#openweblink)

{% hint style="info" %}
player.openWebLink(url:string, popup:boolean=false)
{% endhint %}

플레이어에게 웹 URL을 새 창이나 팝업 창으로 표시합니다.

### [showEmbed](/zep-script-api/zepscriptapi/scriptplayer/methods#showembed)

{% hint style="info" %}
player.showWidget(url: string, align: string, width: number, height: number, hasBackdrop: boolean = true)
{% endhint %}

해당 플레이어에게 지정된 align의 위치에 url 임베드  화면을 표시하는 함수입니다.

### [showImageModal](/zep-script-api/zepscriptapi/scriptplayer/methods#showimagemodal)

{% hint style="info" %}
player.showImage(url: string)
{% endhint %}

플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.

### [showNoteModal](/zep-script-api/zepscriptapi/scriptplayer/methods#shownotemodal)

{% hint style="info" %}
player.showNoteModal(text: string)
{% endhint %}

플레이어에게 텍스트 창을 보여주는 함수입니다.

### [isEmail](/zep-script-api/zepscriptapi/scriptplayer/methods#isemail)

{% hint style="info" %}
player.isEmail(email: string): boolean
{% endhint %}

해당 플레이어의 이메일이 파라미터 값과 같다면 true, 아니면 false를 리턴합니다.

### [getLocationName](/zep-script-api/zepscriptapi/scriptplayer/methods#getlocationname)

{% hint style="info" %}
player.getLocationName : string
{% endhint %}

플레이어가 서있는 지정영역의 이름을 출력합니다.

### [spawnAt](/zep-script-api/zepscriptapi/scriptplayer/methods#spawnat)

{% hint style="info" %}
player.spawnAt(tileX: int ,tileY: int, dir: int = 0)
{% endhint %}

플레이어의 캐릭터를 tileX, tileY 좌표로 지정한 방향을 바라보게 이동시킵니다.

### [spawnAtLocation](/zep-script-api/zepscriptapi/scriptplayer/methods#spawnatlocation)

{% hint style="info" %}
player.spawnAtLocation(name: string, dir:int = 0)
{% endhint %}

플레이어의 캐릭터를 name에 해당하는 지정 영역으로 지정한 방향을 바라보게 이동시킵니다.

### [spawnAtMap](/zep-script-api/zepscriptapi/scriptplayer/methods#spawnatmap)

{% hint style="info" %}
player.spawnAtMap(spaceHashID string, mapHashID:string = null)
{% endhint %}

플레이어를 해당 스페이스 해당 맵으로 이동시킵니다.

### [setCameraTarget](/zep-script-api/zepscriptapi/scriptplayer/methods#setcameratarget)

{% hint style="info" %}
**\[1]** player.setCameraTarget( tileX: Number, tileY: Number, time: Number )

**\[2]** player.setCameraTarget( key: String, time: Number )
{% endhint %}

**\[1]** 플레이어의 시점을 지정된 좌표로 중심 이동시킵니다.

**\[2]** 플레이어의 시점을 특정 오브젝트로 중심 이동시킵니다.

### [setEffectSprite](/zep-script-api/zepscriptapi/scriptplayer/methods#seteffectsprite)

{% hint style="info" %}
player.setEffectSprite(resource: ScriptDynamicResource, offsetX: Number, offsetY: Number, type: Number)
{% endhint %}

플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.

### [playSound](/zep-script-api/zepscriptapi/scriptplayer/methods#playsound)

{% hint style="info" %}
Player.playSound(fileName: string, loop: boolean = false, overlap: boolean = false)
{% endhint %}

해당 플레이어에게 사운드를 재생하는 함수입니다.

### [playSoundLink](/zep-script-api/zepscriptapi/scriptplayer/methods#playsoundlink)

{% hint style="info" %}
player.playSoundLink(link: string, loop: boolean = false)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

### [sendUpdated](/zep-script-api/zepscriptapi/scriptplayer/methods#sendupdated)

{% hint style="info" %}
&#x20;player.sendUpdated()
{% endhint %}

App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.

### [save](/zep-script-api/zepscriptapi/scriptplayer/methods#save)

{% hint style="info" %}
player.save()
{% endhint %}

App, Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.

## ═════════════════════

## 🧙‍♂️ ScriptWidget&#x20;

## ═════════════════════

## 🗃️ Field

### [id](/zep-script-api/zepscriptapi/scriptwidget/field#id)

{% hint style="info" %}
widget.id
{% endhint %}

위젯의 아이디 값을 가져옵니다.

## 🛰️ EventListeners

### [onMessage](/zep-script-api/zepscriptapi/scriptwidget/event-listeners#onmessage)

{% hint style="info" %}
widget.onMessage.Add(function(player, data: any){});
{% endhint %}

위젯에서 App으로 메시지를 보내면 callback 함수가 동작합니다.

## 💠 Methods

### [sendMessage](/zep-script-api/zepscriptapi/scriptwidget/methods#sendmessage)

{% hint style="info" %}
widget.sendMessage(object: any)
{% endhint %}

App에서 위젯으로 데이터를 보냅니다.

### [destroy](/zep-script-api/zepscriptapi/scriptwidget/methods#destroy)

{% hint style="info" %}
widget.destroy()
{% endhint %}

위젯을 삭제하는 함수입니다.

## ═════════════════════

## 🔩 Utility Class&#x20;

## ═════════════════════

## ⏳ Time

### [getTime](/zep-script-api/zepscriptapi/utilityclass/time#gettime)

{% hint style="info" %}
Time.getTime()
{% endhint %}

ZEP 서버의 현재 시간을 milliseconds 단위의  값으로 리턴합니다.

### [getUtcTime](/zep-script-api/zepscriptapi/utilityclass/time#getutctime)

{% hint style="info" %}
Time.getUtcTime()
{% endhint %}

현재 UTC 시간을 milliseconds 단위의 값으로  리턴합니다.

### [getTimeInterval](/zep-script-api/zepscriptapi/utilityclass/time#gettimeinterval)

{% hint style="info" %}
Time.getTimeInterval(timeA: number, timeB: number, returnType: DateType)
{% endhint %}

`timeB - timeA`를 계산하고, 그 결과를 지정한 `returnType`으로 반환합니다.


# ScriptApp

ScriptApp 클래스는 아래와 같이 5개의 카테고리로 구성되어있습니다. 각 카테고리의 제목을 선택하시면, 자세한 내용을 확인할 수 있습니다.

### [<mark style="color:purple;">Lifecycle</mark>](/zep-script-api/zepscriptapi/scriptapp/lifecycle)

> 앱이 시작되어, 실행되고 종료될 때까지를 하나의 생애 주기(Lifecycle)라고 합니다. **앱이 시작될 때, 실행 중일 때 그리고 종료될 때** 등의 상황에서 필요한 동작들을 실행해 전체적인 앱의 생애주기를 만들어 나갈 수 있는 함수들이 모여있는 카테고리 입니다.

### [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptapp/field)&#x20;

> **스페이스나 맵, 플레이어 정보 등을 조회하거나, 저장공간을 활용**할 수 있는 App과 관련된 속성이 모여있는 카테고리입니다.

### [<mark style="color:purple;">Event Listeners</mark>](/zep-script-api/zepscriptapi/scriptapp/event-listeners)&#x20;

> 플레이어가 지정된 채팅을 입력하거나, 특정한 오브젝트를 공격할 와 같이 **맵 내에서 발생할 수 있는 다양한 상황을 감지하여 동작**하는 함수들이 모여있는 카테고리 입니다.

### [<mark style="color:purple;">Callbacks</mark> ](/zep-script-api/zepscriptapi/scriptapp/callbacks)

> 스크립트 개발자가 지정한 키를 플레이어가 누르거나 특정 지점에 도착했을 때와 같이 **조건을 설정하고 조건이 만족되었을 때 동작하는 함수**들이 모여있는 카테고리 입니다.

### [<mark style="color:purple;">Methods</mark> ](/zep-script-api/zepscriptapi/scriptapp/methods)

> 화면에 UI를 표시, 유저 이동 또는 강퇴, 사운드 재생 등 **편리한 기능을 제공하는 함수**들이 모여있는 카테고리 입니다.


# Lifecycle

### 소개

앱이 시작되어, 실행되고 종료될 때까지를 하나의 **생애 주기(Lifecycle)**&#xB77C;고 합니다. 관련 함수들을 이용하여 **앱이 시작될 때, 실행 중일 때 그리고 종료될 때** 등의 상황에서 필요한 동작들을 실행해 전체적인 앱의 생애주기를 만들 수 있습니다.

<table><thead><tr><th width="167">이름</th><th>설명</th></tr></thead><tbody><tr><td>onInit</td><td>App이 최초로 시작될 때 한 번 호출되는 함수입니다.</td></tr><tr><td>onJoinPlayer</td><td>onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장 시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.</td></tr><tr><td>onStart</td><td>모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.</td></tr><tr><td>onUpdate</td><td>약 20ms 마다 주기적으로 실행되는 함수입니다.</td></tr><tr><td>onLeavePlayer</td><td>퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.</td></tr><tr><td>onDestroy</td><td>다른 App이 실행되거나 설치한 Game Block이 파괴될 때 동작합니다.</td></tr></tbody></table>

### App 생애 주기(Lifecycle) 이해하기

Lifecycle 함수들은 생애 주기에 맞게 기능을 만들 수 있는 필수적인 함수입니다. \
아래 그림과 같이 앱이 실행될 때는 **Enter 단계**의 함수들이 동작하고, 앱이 실행 중 일때는 **Update 단계**의 함수들이 주기적으로 동작하며, 앱이 종료될 때는 **Exit 단계**의 함수들이 동작하게됩니다.

앱이 실행되어 종료될 때 까지 각 단계의 함수들을 잘 활용해 생애 주기에 맞게 App을 만들어보세요.

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fq9VayOPP4sz5UdkOxe5l%2FUntitled.png?alt=media&amp;token=658b4fb8-ce64-41c9-9cbe-eb02a60f7539" alt=""><figcaption></figcaption></figure>

<mark style="background-color:yellow;">**Lifecycle 함수 한 눈에 보기**</mark>

```jsx
// App 실행 시에 최초로 호출되는 이벤트 (유저 진입 전)
// Normal App과 Sidebar App은 Script 적용 후 맵이 실행될 때 호출 [ Enter ]
App.onInit.Add(function(){
});

// 모든 플레이어를 이 이벤트를 통해 App에 진입시킴 [ Enter ]
// 이후 플레이어가 입장 할 때마다 호출 [ Events ]
App.onJoinPlayer.Add(function(player) {
});

// 플레이어 모두 진입 시 최초로 시작되는 이벤트 [ Enter ]
App.onStart.Add(function(){
});

// 20ms 마다 호출되는 이벤트
// dt: deltatime(전 프레임이 완료되기까지 걸린 시간) [ Update ]
App.onUpdate.Add(function(dt){
});

// 이벤트 콜백 처리 후 다시 onUpdate

// App 종료 시 모든 플레이어를 App에서 나가게 함 [ Exit ]
App.onLeavePlayer.Add(function(player){
});

// App 종료 시 마지막으로 호출 [ Exit ]
// Normal App과 Sidebar App은 별도의 종료
App.onDestroy.Add(function(){
});
```

## 1️⃣ Enter 단계 함수

App의 실행과 함께 호출되는 **라이프사이클 Enter 단계**에서 호출되는 함수를 안내합니다.

### onInit

{% hint style="info" %}
&#x20;App.onInit.Add(function(){})
{% endhint %}

App이 최초로 시작될 때 한 번 호출됩니다.

**파라미터**

* 없음

**예제**

onInit 에서 채팅 출력해보기. ( 미니게임으로 만들어 확인해보세요. )

```jsx
App.onInit.Add(function(){
	App.sayToAll("-- onInit --")
	App.sayToAll("   ready..  ")
	App.sayToAll("------------")
});
```

###

### onJoinPlayer

{% hint style="info" %}
App.onJoinPlayer.Add(function(player){})
{% endhint %}

onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.

**파라미터**

<table><thead><tr><th width="118.33333333333331">이름</th><th width="112">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>player는 입장하는 플레이어를 가르킴<br>파라미터의 이름은 임의로 변경가능</td></tr></tbody></table>

**예시**

플레이어 입장시 메시지 출력해보기

```jsx
App.onJoinPlayer.Add(function(player){
	App.showCenterLabel(`${player.name}님이 입장하셨습니다.`)
});
```

###

### onStart

{% hint style="info" %}
App.onStart.Add(function(){})
{% endhint %}

모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.

**파라미터**

* 없음

**예제**

onStart에서 채팅 출력해보기 ( 미니게임으로 만들어 확인해보세요. )

```jsx
App.onStart.Add(function(){
	App.sayToAll("-- App Start --")
});
```

<mark style="background-color:yellow;">**Enter 단계 함수의 흐름 이해하기**</mark>

**Lifecycle Enter 단계**의 흐름을 코드로 확인해보세요.\
아래 코드를 미니게임으로 만들어 실행해보세요!

```jsx
// main.js
App.onInit.Add(function(){
	App.sayToAll("-- onInit --")
	App.sayToAll("   ready..  ")
	App.sayToAll("------------")
});

App.onJoinPlayer.Add(function(player){
	App.sayToAll(`${player.name}님이 입장하셨습니다.`)
});

App.onStart.Add(function(){
	App.sayToAll("-- App Start --")
});
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FqFPx56HP3nohhDEhDmQF%2FUntitled%201.png?alt=media&amp;token=1be56da9-9a49-42b0-a744-c8764ddf244f" alt=""><figcaption><p>Enter 단계 함수들이 순서대로 동작하는 모습.</p></figcaption></figure></div>

## 2️⃣ Update

Update에는 약 20ms 마다 주기적으로 실행되는 함수 onUpdate가 있습니다.

onJoinPlayer, onLeavePlayer 등의 이벤트가 발생하면 해당 이벤트 처리 후 다시 onUpdate가 주기적으로 실행됩니다.

### onUpdate

{% hint style="info" %}
App.onUpdate.Add(function(dt){})
{% endhint %}

약 20ms 마다 주기적으로 실행되는 함수입니다.

**파라미터**

<table><thead><tr><th width="93.33333333333331">이름</th><th width="110">타입</th><th>설명</th></tr></thead><tbody><tr><td>dt</td><td>Number</td><td>deltatime (전 프레임이 완료되기까지 걸린 시간, 약 20ms)<br>dt 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예시**

onUpdate 함수를 이용해 10초 카운트다운 만들어보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FoNnWOVOiqbxlamq6rf62%2Fcountdown.gif?alt=media&amp;token=50a456bd-e70c-40fd-a1fd-7b3569d4d263" alt=""><figcaption></figcaption></figure></div>

```jsx
let countdown = 10;
let timer = 0;
App.onUpdate.Add(function(dt){
	timer += dt;
	if(timer >=1){
		if(countdown >= 0)
		{
			App.showCenterLabel(countdown--);
		}
		else{
			App.showCenterLabel("Time Over");
		}
		timer = 0;
	}
})
```

## 3️⃣ Exit 단계 함수

앱이 종료될 때 실행되는 함수 입니다.

### onLeavePlayer

{% hint style="info" %}
&#x20;App.onLeavePlayer.Add(function(player){})
{% endhint %}

퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.

**파라미터**

<table><thead><tr><th width="99.33333333333331">이름</th><th width="97">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>player 파라미터는 퇴장하는 플레이어를 가르킴<br>player 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예시**

플레이어 퇴장 시 메시지 출력해보기

```jsx
// 플레이어가 퇴장할 때 실행
App.onLeavePlayer.Add(function(player){
	App.showCenterLabel(`${player.name}님이 퇴장하셨습니다.`)
});
```

###

### onDestroy

{% hint style="info" %}
App.onDestroy.Add(function(){})
{% endhint %}

다른 App이 실행되거나, 설치한 Game Block이 파괴될 때 동작합니다.

**파라미터**

* 없음

**예제**

Game Block 파괴 시 메시지 출력해보기 ( 미니게임 )

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F2ArQxB0WbpPSdtBS8Ryo%2Fdestroy.gif?alt=media&amp;token=a462ce40-46bd-41d1-b897-f20415aa0136" alt=""><figcaption></figcaption></figure></div>

```jsx
// Game Block 파괴 시 실행
App.onDestroy.Add(function(){
	App.showCenterLabel("게임 블록이 파괴되었습니다.")
});
```


# Field

### 소개

**Field**는 App과 관련된 속성 값들 입니다. 이 필드를 활용해 참가 중인 스페이스나 맵, 플레이어 정보 등을 조회하거나, 저장공간을 활용할 수 있습니다.

🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.

<table><thead><tr><th width="206">이름</th><th>설명</th></tr></thead><tbody><tr><td>🔒 spaceHashID</td><td>App이 설치된 스페이스의 해쉬값을 보여줍니다.</td></tr><tr><td>🔒 mapHashID</td><td>App이 설치된 맵의 해쉬값을 가져옵니다.</td></tr><tr><td>🔒 creatorID</td><td>App을 실행한 플레이어의 ID 값을 가져옵니다.</td></tr><tr><td>🔒 players</td><td>맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.</td></tr><tr><td>🔒 playerCount</td><td>앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.</td></tr><tr><td>cameraEffect</td><td>카메라 이펙트의 종류를 셋팅할 변수 값</td></tr><tr><td>cameraEffectParam</td><td>카메라 이펙트 효과의 범위 값</td></tr><tr><td>displayRatio</td><td>화면의 줌을 컨트롤 하는 값</td></tr><tr><td>storage</td><td>스페이스 내의 App 데이터의 저장공간(스페이스 한정)</td></tr><tr><td>followPlayer</td><td>App의 따라가기 기능 활성화 여부 값</td></tr><tr><td>showName</td><td>플레이어 닉네임 숨김 여부</td></tr><tr><td>🔒 appHashID</td><td>앱의 HashID 값을 가져옵니다.</td></tr><tr><td>enableFreeView</td><td>앱이 설치된 맵의 둘러보기 허용 여부를 설정할 수 있습니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### spaceHashID & mapHashID

{% hint style="info" %}
App.spaceHashID: String \
App.mapHashID: String
{% endhint %}

앱이 설치된 스페이스의 spaceHashID와 mapHashID를 가져옵니다. [<mark style="color:purple;">(스페이스와 맵 이해하기)</mark>](/creator/reference/undefined)

**예제**

앱이 설치된 맵의 spaceHashID와 mapHashID 출력해보기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
  // 채팅창에 spaceHashID와 mapHashID 출력해보기
	App.sayToAll(`spaceHashID: ${App.spaceHashID}`); // spaceHashID: Ak42Xz
	App.sayToAll(`mapHashID: ${App.mapHashID}`) // mapHashId: 25g3RQ
})
```

###

### creatorID

{% hint style="info" %}
App.creatorID
{% endhint %}

미니게임을 실행한 플레이어의 ID 값을 가져옵니다.\
⛔맵 내장형  앱인 노멀 앱과 사이드바 앱에서는 동작하지 않습니다.

**예제**

미니게임을 실행한 플레이어의 닉네임을 채팅창에 출력해보기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
	if(player.id == App.creatorID){
		App.sayToAll(`${player.name}님이 앱을 실행했습니다.`)
	}
})
```

####

### players

{% hint style="info" %}
App.players: ScriptPlayer\[]
{% endhint %}

맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.

**예제**

맵에 있는 모든 플레이어 닉네임을 출력해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
	//App.players를 이용해 채팅창에 접속해있는 모든 플레이어의 닉네임을 출력하기
	let players = App.players;
	for(let i in players){
		let player = players[i]
		App.sayToAll(player.name)
	}
})
```

####

### playerCount

{% hint style="info" %}
App.playerCount: Number
{% endhint %}

앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.

**예시**

맵에 있는 플레이어 수 출력해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
	// 현재 접속자 수를 채팅창에 출력
	App.sayToAll(`접속자 수: ${App.playerCount}`)
})
```

####

### cameraEffect & cameraEffectParam1

{% hint style="info" %}
App.cameraEffect: NONE = 0, SPOTLIGHT = 1 \
App.cameraEffectParam1: Number
{% endhint %}

App.cameraEffect: 카메라 이펙트의 종류를 셋팅할 변수 값

App.cameraEffectParam1: 카메라 이펙트 효과의 범위 값

**예제**

비네팅 효과를 On/Off 하는 키 함수 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// 한 번 누르면 비네팅 효과가 켜지고, 두 번 누르면 비네팅 효과가 꺼짐
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
	if(App.cameraEffect == 0){
		App.cameraEffect = 1; // 1 = 비네팅 효과
		App.cameraEffectParam1 = 500; // 비네팅 효과의 범위를 500으로 지정
	}
	else if(App.cameraEffect == 1){
		App.cameraEffect = 0; // 비네팅 효과 끄기
	}
	App.sendUpdated(); // 앱의 Field값이 변경되면 App.sendUpdated()로 변경값을 적용
})
```

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1LWO4mFWsSKP0Nj1gXwN%2FUntitled.png?alt=media&amp;token=7c8b2cb5-4b79-42fe-9610-808b19209acf" alt=""><figcaption><p>비네팅 효과 범위 500이 적용된 모습</p></figcaption></figure>

####

### displayRatio

{% hint style="info" %}
App.displayRatio
{% endhint %}

화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )

**예제**

화면의 줌을 컨트롤 하는 키 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// 한 번 누르면 화면의 줌 값이 커지고, 한 번 더 누르면 원래대로 돌아오는 키 함수 
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
	if(App.displayRatio == 1){
		App.displayRatio = 5;
	}else{
		App.displayRatio = 1;
	}
	App.sendUpdated(); //* 앱의 Field값이 변경되면 App.sendUpdated()로 변경값을 적용
})
```

<div><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fc1TWAG2I9fBrUhne4449%2FUntitled%201.png?alt=media&amp;token=3eadd85d-77b5-496b-ae53-3a5c463e9ecc" alt=""><figcaption><p>displayRatio 값이 5일 때</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FEW1iwHCQ9hpBt8hsW8xa%2FUntitled%202.png?alt=media&amp;token=181e3841-e430-411b-879f-bb248d416f44" alt=""><figcaption><p>displayRatio 값이 1일 때</p></figcaption></figure></div>

### storage

{% hint style="info" %}
App.storage: String
{% endhint %}

스페이스 내의 App 데이터의 저장공간 입니다 (스페이스 한정)

**예제**

[**Storage 페이지**](/zep-script-api/zepscriptapi/scriptapp/storage)를 참고해주세요

####

### followPlayer

{% hint style="info" %}
&#x20;App.followPlayer: Boolean
{% endhint %}

App의 따라가기 기능 활성화 여부 값 입니다. ( 기본 값 : false )

노멀 앱, 미니게임 앱이 실행 중인 경우 followPlayer 값이 false로 설정되어 ‘따라가기’ 기능이 비활성화됩니다.

**예제**

따라가기 기능을 끄고 키는 함수 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// followPlayer 값을 바꾸는 키 함수
App.addOnKeyDown(81,function(p){
	if(App.followPlayer){
		App.followPlayer = false;
	}else{
		App.followPlayer = true;
	}
	App.sayToAll(`App.followPlayer: ${App.followPlayer}`)
	App.sendUpdated(); //* 앱의 Field값이 변경되면 App.sendUpdated()로 변경값을 적용
})
```

###

### showName

{% hint style="info" %}
&#x20;App.showName: Boolean
{% endhint %}

플레이어 닉네임 숨김 여부

App.showName 을 false로 설정하면 모든 플레이어의 닉네임이 숨김 처리됩니다.

**예제**

```javascript
// q 키를 누르면 동작하는 함수
// showName 값을 바꾸는 키 함수
App.addOnKeyDown(81,function(p){
	if(App.showName){
		App.showName = false;
	}else{
		App.showName = true;
	}
	App.sayToAll(`App.showName: ${App.showName}`)
	App.sendUpdated(); //* 앱의 Field값이 변경되면 App.sendUpdated()로 변경값을 적용
})

```

###

### appHashID

{% hint style="info" %}
App.appHashID: String&#x20;
{% endhint %}

앱의 HashID를 가져옵니다.

**예제**

채팅창에앱의 HashID 출력하기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
	App.sayToAll(`appHashID: ${App.appHashID}`); 
})
```

###

### enableFreeView

{% hint style="info" %}
App.enableFreeView
{% endhint %}

앱이 설치된 맵의 둘러보기 허용 여부를 설정할 수 있습니다.

**예제**

단축키로 맵 둘러보기 허용 여부 설정하기

```jsx
// Q를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	if (App.enableFreeView) {
		App.enableFreeView = false;
	} else {
		App.enableFreeView = true;
	}
	App.sendUpdated();
	player.sendMessage(`App.enableFreeView = ${App.enableFreeView}`, 0x00ffff);
});
```

***

### 부록

[<mark style="color:purple;">스페이스와 맵 이해하기</mark>](https://app.gitbook.com/o/-MkvEtFn2kFBYSN4_5rX/s/iW553XSGeKCAPpImxXi3/~/changes/bRylDKw8UxLaVWLvuhVV/creator/zep-script-guide/reference/undefined)


# Storage

App storage는 스페이스 내의 App 데이터의 저장공간 입니다.

### App storage에 데이터 저장하기

> **권장하는 데이터 저장 방식**

{% hint style="info" %}
App.setStorage(string);
{% endhint %}

App.setStorage 함수는 기존 App storage 데이터 저장 방식을 보완한 데이터 저장 함수입니다.

> **권장하지 않는 데이터 저장 방식**

{% hint style="danger" %}
App.storage = string;\
App.save();
{% endhint %}

위  방식은 앱이 같은 스페이스 내 여러 맵에서 실행중인 경우, 데이터를 덮어쓰는 등의 문제가 발생할 수 있어 사용을 권장드리지 않습니다.

### App storage 값 읽어오기

{% hint style="info" %}
App.getStorage(function(){})
{% endhint %}

App.getStorage 함수는 앱이 실행중인 같은 스페이스 내 다른 맵의  App storage 데이터 변경 여부를 체크하여 같은 데이터를 가지도록 동기화 해주는 함수입니다.&#x20;

{% hint style="warning" %}
App.getStorage 함수는 비동기 함수이기 때문에 App.getStorage 함수 다음 라인에 App.storage를 사용하는 코드를 작성할 경우 동기화를 보장할 수 없습니다.
{% endhint %}

###

### App storage 사용 예제

아래 예제코드를 사이드바 앱으로 설치해 같은 스페이스 내 다른 맵에서 Q를 눌러 count 값이 동기화되는지 확인해보세요.

```javascript

App.onStart.Add(function(){
	if(App.storage == null){
		App.setStorage(JSON.stringify({count: 0}))
	}
})
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81,function(player){
	// App.storage를 최신화 한 후 callback 함수를 실행합니다.
	App.getStorage(function () {
		let appStorage = JSON.parse(App.storage);
		appStorage.count += 1;
		App.sayToAll(`count: ${appStorage.count}`)
		// App.setStorage를 사용해 변경내용을 저장합니다.
		App.setStorage(JSON.stringify(appStorage));
	});
	// App.getStorage 함수 다음 라인에 App.storage 코드를 작성할 경우 동기화를 보장할 수 없습니다.
	App.sayToAll(App.storage);
})
```


# Event Listeners

**소개**

플레이어가 지정된 채팅을 입력하거나, 특정한 오브젝트를 공격할 와 같이 ZEP 스페이스에서 발생하는 **특정 상황에 반응하여 동작하는 함수**들 입니다.

{% hint style="danger" %}
**EventListener**가 비정상적으로 많이 추가되는 경우 앱이 종료될 수 있습니다.

onUpdate문 또는 반복적으로 실행되는 문에서 EventListener를 등록하는 것을 지양해주세요.
{% endhint %}

<table><thead><tr><th width="218">이름</th><th>설명</th></tr></thead><tbody><tr><td>onSay</td><td>플레이어가 채팅을 입력할 때 동작하는 함수입니다.</td></tr><tr><td>onPlayerTouched</td><td>캐릭터들끼리 충돌할 때 동작하는 함수입니다.</td></tr><tr><td>onObjectTouched</td><td>캐릭터가 오브젝트와 충돌할 때 동작하는 함수입니다.</td></tr><tr><td>onAppObjectTouched</td><td>플레이어가 키 값을 가진 오브젝트와 충돌할 때 동작하는 함수입니다.</td></tr><tr><td>onUnitAttacked</td><td>공격 키(Z)로 다른 캐릭터를 공격할 때 동작하는 함수입니다.</td></tr><tr><td>onObjectAttacked</td><td>공격 키(Z)로 오브젝트를 공격할 때 동작하는 함수입니다.</td></tr><tr><td>onSidebarTouched</td><td>플레이어가 사이드바 앱을 클릭(터치)할 때 실행되는 함수입니다.</td></tr><tr><td>onTriggerObject</td><td>오브젝트와 F 상호작용 시 동작하는 함수입니다.</td></tr><tr><td>onAppObjectAttacked</td><td>플레이어가 키 값을 가진 오브젝트를 공격(Z키) 할 때 동작하는 함수입니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

<mark style="background-color:yellow;">**Event Listener 함수 한 눈에 보기**</mark>

<pre class="language-jsx"><code class="lang-jsx">// 플레이어들이 채팅창에 입력하는 모든 채팅에 대해 호출 되는 이벤트
// !로 시작하는 텍스트는 채팅창에 나오지 않으나, onSay 함수에는 사용 가능
App.onSay.Add(function(player, text) {
});

// 플레이어가 다른 플레이어와 부딪혔을 때 호출 되는 이벤트
App.onPlayerTouched.Add(function(sender, target, x, y){
});
<strong>
</strong>// 플레이어가 오브젝트와 부딪혔을 때 호출 되는 이벤트
App.onObjectTouched.Add(function(sender, x, y, tileID, obj) {  
});

// 플레이어가 키 값을 가진 오브젝트와 부딪혔을 때 호출 되는 이벤트
App.onAppObjectTouched.Add(function(key, sender, x, y){});

// 플레이어가 다른 플레이어를 공격할 때 (Z키) 호출 되는 이벤트
App.onUnitAttacked.Add(function(sender, x, y, target) {
});

// 플레이어가 오브젝트를 공격(Z키)했을 때 호출 되는 이벤트
App.onObjectAttacked.Add(function(sender, x, y){
});

// 플레이어가 사이드바 앱을 클릭(터치)할 때 호출 되는 이벤트
App.onSidebarTouched.Add(function(player){
}); 

// 오브젝트와 F 상호작용 시 동작하는 함수
App.onTriggerObject.Add(function(player, layer, x, y){
});

// 플레이어가 키 값을 가진 오브젝트를 공격할 때 (Z키) 호출 되는 이벤트
App.onAppObjectAttacked.Add(function (sender, x, y, layer, key) {
});
</code></pre>

####

### onSay

{% hint style="info" %}
App.onSay.Add(function(player, text){});
{% endhint %}

플레이어가 채팅을 입력할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="114.33333333333331">이름</th><th width="97">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>player 파라미터는 채팅을 입력한 플레이어를 가르킴<br>player 파라미터의 이름은 임의로 변경 가능</td></tr><tr><td>text</td><td>String</td><td>text는 입력한 채팅 내용을 가르킴<br>text 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

초성퀴즈 - 채팅으로 정답 맞추는 기능 만들어보기

```jsx
_answer = "ZEP" // 정답
// 플레이어가 채팅을 칠 때 실행
App.onSay.add(function(player, text) {
    if(_answer == text){
        App.showCenterLabel(player.name + '님 정답!\n정답은 ' + _answer);
    }
});
```

####

### onPlayerTouched

{% hint style="info" %}
App.onPlayerTouched.Add(function(sender, target, x, y){});
{% endhint %}

캐릭터들끼리 충돌할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="107.33333333333331">이름</th><th width="107">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 부딪힌 주체자를 가르킴</td></tr><tr><td>target</td><td>String</td><td>target은 부딪힘을 당한 플레이어를 가르킴</td></tr><tr><td>x, y</td><td>Number</td><td>x, y는 충돌한 x, y 좌표를 가르킴</td></tr><tr><td></td><td></td><td>sender, target, x, y 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

캐릭터끼리 부딪힐 때 메시지 출력해보기

```jsx
// 플레이어끼리 부딪힐 때 실행
App.onPlayerTouched.Add(function (sender, target, x, y) {
	App.showCenterLabel(
		`${sender.name}님과 ${target.name}님이 좌표: (${x}, ${y}) 에서 부딪혔습니다.`
	);
});
```

####

### onObjectTouched

{% hint style="info" %}
App.onObjectTouched.Add(function(sender, x, y,  tileID, obj){});
{% endhint %}

캐릭터가 오브젝트와 충돌 또는 상호작용 할 때 한 번 실행됩니다.

**파라미터**

<table><thead><tr><th width="229.33333333333331">이름</th><th width="128">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 오브젝트와 부딪힌 플레이어를 가르킴</td></tr><tr><td>x, y</td><td>Number</td><td>오브젝트와충돌한 x, y 좌표를 가르킴</td></tr><tr><td>tileID</td><td>Number</td><td>오브젝트의 타일 ID 입니다.</td></tr><tr><td>obj</td><td>Object</td><td>오브젝트 객체</td></tr></tbody></table>

**예제**

⭐ <mark style="color:purple;">`overlap: true`</mark> 속성이 없는 오브젝트는 충돌해도 함수가 실행되지 않습니다.

{% file src="/files/xkq8ljeofl3LUxbcaDaS" %}

<pre class="language-jsx"><code class="lang-jsx"><strong>let testObject = App.loadSpritesheet("object.png");
</strong>// 사용 가능한 ObjectEffectType
const ObjectEffectType = {
    NONE = 0,
    SHOW_NOTE = 1,
    SHOW_IMAGE = 2,
    PASSWORD_DOOR = 3,
    LINK_WEBSITE = 4,
    EMBED_WEBSITE = 5,
    API_CALL = 6,
    REPLACE_IMAGE = 7,
    NFT_GIVEAWAY = 8,
    NFT_DOOR = 9,
    POST_MESSAGE = 10,
    SHOW_CHAT_BALLOON = 11,
    FT_DOOR = 12,
    POST_MESSAGE_TO_APP = 13,
    DONATION_DOOR = 14,
    IMPASSABLE = 15,
    STAMP = 16,
    TOKEN_DONATION_DOOR = 17,
    CHANGE_OBJECT = 18,
    ANIMATION = 19,
    NFT_DOOR_MOVE = 20,
    INTERACTION_WITH_ZEPSCRIPTS = 21,
    MULTIPLE_CHOICE = 22,
}

App.onStart.Add(function () {
	Map.putObject(5, 5, testObject, { overlap: true });
});

// 플레이어와 오브젝트가 부딪힐 때 실행
App.onObjectTouched.Add(function (sender, x, y, tileID, obj) {
	Map.putObject(x, y, null);
	App.showCenterLabel(
		`${sender.name}님이 좌표: (${x}, ${y}) 에서 오브젝트와 부딪혔습니다.(타입: ${obj.type})`
	);
});
</code></pre>

####

### onAppObjectTouched

{% hint style="info" %}
App.onAppObjectTouched.Add(function(sender, key, x, y){});
{% endhint %}

️ 캐릭터가 키 값을 가진 오브젝트와 충돌할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="133.33333333333331">이름</th><th width="127">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 오브젝트와 부딪힌 플레이어를 가르킴</td></tr><tr><td>key</td><td>String</td><td>오브젝트의 Key 값</td></tr><tr><td>x, y</td><td>Number</td><td>x, y는 충돌한 x, y 좌표를 가르킴</td></tr></tbody></table>

**예제**

라벨 출력 예제

⭐ <mark style="color:purple;">`overlap: true`</mark> 속성이 없는 오브젝트는 충돌해도 함수가 실행되지 않습니다.

{% file src="/files/kFUS8EILULIHiWMSqnSt" %}

```jsx
let blueman_dance = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37],
	8
);

// Q를 누르면 동작 하는 함수
App.addOnKeyDown(81, function (player) {
	App.sayToAll("키 값을 가진 오브젝트 충돌 테스트");
	Map.putObjectWithKey(8, 5, blueman_dance, { overlap: true, key: "blueman" });
});

App.onAppObjectTouched.Add(function (player, key, x, y) {
	App.sayToAll(
		`${player.name}이 키 값이 ${key}인 오브젝트와 ${x},${y}에서 충돌!`
	);
});
```

####

### onUnitAttacked

{% hint style="info" %}
App.onUnitAttacked.Add(function(sender, x, y, target){});
{% endhint %}

플레이어가 공격 키(Z)로 다른 캐릭터를 공격할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="127.33333333333331">이름</th><th width="105">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 공격한 플레이어를 가르킴</td></tr><tr><td>x, y</td><td>Number</td><td>x, y는 공격한 플레이어의 x, y 좌표 값을 가르킴</td></tr><tr><td>target</td><td>Player</td><td>target은 공격 받은 플레이어를 가르킴</td></tr><tr><td></td><td></td><td>sender, x, y, target 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

플레이어를 공격 할 때 메시지 출력해보기

```jsx
// Z 키로 플레이어를 공격할 때 실행
App.onUnitAttacked.Add(function (sender, x, y, target) {
	App.showCenterLabel(`${sender.name}님이 ${target.name}님을 공격했습니다.`);
	App.sayToAll(`(${x}, ${y})`);
});
```

####

### onObjectAttacked

{% hint style="info" %}
App.onObjectAttacked.Add(function(sender, x, y){});
{% endhint %}

플레이어가 공격 키(Z)로 오브젝트를 공격할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="123.33333333333331">이름</th><th width="120">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 공격한 플레이어를 가르킴</td></tr><tr><td>x, y</td><td>Number</td><td>x, y는 오브젝트의 x, y 좌표 값을 가르킴</td></tr><tr><td></td><td></td><td>sender, x, y 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

오브젝트를 공격할 때 메시지 출력해보기

⭐ <mark style="color:purple;">`overlap: true`</mark> 속성이 없는 오브젝트는 공격해도 함수가 실행되지 않습니다.

{% file src="/files/uQ9d50cNL1KuyUbylNbS" %}

```jsx
let testObject = App.loadSpritesheet("object.png");

App.onStart.Add(function () {
	Map.putObject(5, 5, testObject, { overlap: true });
});
// Z 키로 오브젝트를 공격할 때 실행
App.onObjectAttacked.Add(function(sender, x, y){
	App.showCenterLabel(
		`${sender.name}님이 좌표: (${x}, ${y})에 위치 오브젝트를 공격했습니다.`
	);
})
```

####

### onSidebarTouched

{% hint style="info" %}
App.onSidebarTouched.Add(function(player){});
{% endhint %}

플레이어가 사이드바 앱을 클릭(터치) 할 때 동작합니다.

**파라미터**

<table><thead><tr><th width="139.33333333333331">이름</th><th width="125">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>사이드바 앱을 클릭한 player를 가르킴</td></tr></tbody></table>

**예제**

사이드바 앱 클릭 시 채팅창 메시지 출력하기.

```jsx
App.onSidebarTouched.Add(function (player) {
	App.sayToAll(`${player.name}님이 사이드바 앱을 클릭했습니다.`)
});
```

**관련 페이지**

[<mark style="color:purple;">사이드바 앱 예제</mark>](/creator/tutor/sample/sidebarapp)

### onTriggerObject

{% hint style="info" %}
App.onTriggerObject.Add(function(player, layerID, x, y, key){});
{% endhint %}

오브젝트와 F 상호작용 시 동작하는 함수를 작성할 수 있습니다.

맵에디터로 설치한 오브젝트 또는 스크립트에서  <mark style="color:purple;">**`type`**</mark>을  <mark style="color:purple;">**`INTERACTION_WITH_ZEPSCRIPTS`**</mark>**`(21)`**&#xB85C; 설치한 오브젝트와 상호작용 시 동작합니다.

**파라미터**

<table><thead><tr><th width="139.33333333333331">이름</th><th width="125">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>오브젝트와 상호작용한 player를 가르킴</td></tr><tr><td>layerID</td><td>Number</td><td>오브젝트가 설치된 레이어 ID<br>오브젝트인 경우 layerID = 3<br>상단 오브젝트인 경우 layerID = 5</td></tr><tr><td>x, y</td><td>Number</td><td>상호작용한 오브젝트의 x, y 좌표</td></tr><tr><td>key</td><td>String</td><td>상호작용한 오브젝트의 key 값</td></tr></tbody></table>

**예제**

오브젝트와 F 상호작용시 메시지 출력하기

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fpv0DLdffydgh0BFbIaun%2Fimage.png?alt=media&amp;token=7974cf04-6bc1-4eee-9d9c-6009d91d9abe" alt=""><figcaption></figcaption></figure>

{% file src="/files/WCZIkW2FIJOLiBBrJcPZ" %}

```javascript
let blueman = App.loadSpritesheet("blueman.png");

App.onJoinPlayer.Add(function (player) {
	Map.putObjectWithKey(player.tileX + 1, player.tileY, blueman, {
		type: ObjectEffectType.INTERACTION_WITH_ZEPSCRIPTS,
		impassable: true,
		key: "objectKey",
	});
});


App.onTriggerObject.Add(function (player, layerID, x, y, key) {
	App.sayToAll(`playerName: ${player.name} / layer: ${layerID} / coordinates:(${x}, ${y}) / key: ${key}`);
});
```

### onAppObjectAttacked

{% hint style="info" %}

```
App.onAppObjectAttacked.Add(function (sender, x, y, layer, key) {});
```

{% endhint %}

플레이어가 공격 키(Z)로 키 값을 가진 오브젝트를 공격할 때 동작합니다.

**관련 문서**: [오브젝트 npcProperty](/creator/reference/npcproperty)

**파라미터**

<table><thead><tr><th width="123.33333333333331">이름</th><th width="120">타입</th><th>설명</th></tr></thead><tbody><tr><td>sender</td><td>Player</td><td>sender는 공격한 플레이어를 가르킴</td></tr><tr><td>x, y</td><td>Number</td><td>x, y는 오브젝트의 x, y 좌표 값을 가르킴</td></tr><tr><td>layer</td><td>Number</td><td>오브젝트가 설치된 레이어</td></tr><tr><td>key</td><td>String</td><td>공격한 오브젝트의 키 값</td></tr></tbody></table>

**예제**

키 값을 가진 오브젝트를 공격할 때 메시지 출력해보기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FA40KztARou4paBiQuexH%2Fimage.png?alt=media\&token=f58abc2b-0da3-4979-bb48-89e551149880)

{% file src="/files/qqm7s6mhlMnHRXvHzt15" %}

⭐ <mark style="color:purple;">`collide: true`</mark> 속성이 없는 키 값 오브젝트는 공격해도 함수가 실행되지 않습니다.

```javascript
App.onAppObjectAttacked.Add(function (sender, x, y, layer, key) {
    App.showCenterLabel(
        `sender: ${sender.name} 
        coordinates: (${x}, ${y})
        layer: ${layer}
        key: ${key}`
    );
});
```

**관련 페이지**

[<mark style="color:purple;">사이드바 앱 예제</mark>](/creator/tutor/sample/sidebarapp)


# Callbacks

### 소개

스크립트 개발자가 지정한 키를 플레이어가 눌렀을 때 또는 스크립트 개발자가 지정한 지점에 도착했을 때 등, 조건을 설정하여 플레이어가 조건을 달성했을 경우 동작하는 함수들 입니다.

<table><thead><tr><th width="240">이름</th><th>설명</th></tr></thead><tbody><tr><td>runLater</td><td>지정한 시간(초) 후 동작하는 함수 입니다.</td></tr><tr><td>addOnTileTouched</td><td>지정한 x, y 좌표에 플레이어가 도착했을 때 실행되는 함수입니다.</td></tr><tr><td>addOnLocationTouched</td><td>지정한 ‘지정영역’에 플레이어가 도착했을 때 실행되는 함수입니다.</td></tr><tr><td>addOnKeyDown</td><td>플레이어가 지정된 키를 눌렀을 때 실행되는 함수 입니다.</td></tr><tr><td>setTimeout</td><td>지정한 시간(ms) 후 함수를 실행합니다.</td></tr><tr><td>setInterval</td><td>지정한 시간(ms) 간격으로 함수를 실행합니다.</td></tr><tr><td>addMobileButton</td><td>모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.</td></tr><tr><td>putMobilePunch</td><td>모바일 환경에서 펀치 버튼을 추가합니다.</td></tr><tr><td>putMobilePunchWithIcon</td><td>로드한 이미지로 펀치 버튼을 만들어 추가합니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

<mark style="background-color:yellow;">**Callbacks 함수 한 눈에 보기**</mark>

```javascript
// time(초) 후에 callback 함수를 실행
App.runLater(callback, time: number)

// 플레이어가 해당 위치의 타일과 부딪혔을 때 실행
App.addOnTileTouched(x: number, y: number, callback)

// 플레이어가 지정한 위치와 부딪혔을 때 실행
App.addOnLocationTouched(name: string, callback)

// 플레이어가 지정된 키를 눌렀을 때 실행
App.addOnKeyDown(keycode : number, callback);

// time(ms) 후에 callback 함수를 실행
setTimeout(callback, time: number)

// time(ms) 후 함수를 실행
setInterval(callback, time: number)

// 모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정
App.addMobileButton(anchor: number, posX: number, posY: number, function(player){} )

// 모바일 환경에서 펀치 버튼 추가/제거합니다.
App.putMobilePunch(enable: boolean = true)

// 로드한 이미지로 펀치 버튼을 만들어 추가합니다.
App.putMobilePunchWithIcon(icon: ScriptDynamicResource)

```

####

### runLater

{% hint style="info" %}
App.runLater(function(){}, time: number);
{% endhint %}

time(초) 후에 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="117.33333333333331">이름</th><th width="115">타입</th><th>설명</th></tr></thead><tbody><tr><td>time</td><td>Number</td><td>몇 초 후에 실행 될 지를 정하는 시간 (초)</td></tr></tbody></table>

**예제**

앱이 시작되고 5초 후 메시지 출력해보기

```jsx
App.onStart.Add(function () {
	App.runLater(function() {
		App.showCenterLabel("메시지");
	}, 5);
});
```

####

### addOnTileTouched

{% hint style="info" %}
App.addOnTileTouched(x: number, y: number, function(player){})
{% endhint %}

지정한 x, y좌표에 플레이어가 도착할 경우 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="112.33333333333331">이름</th><th width="131">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>number</td><td>지정 할 x, y 좌표</td></tr></tbody></table>

**예제**

플레이어가 지정 좌표에 도착 했을 때 메시지 출력해보기

```jsx
// 플레이어가 5, 5 좌표에 도착한 경우
App.addOnTileTouched(5, 5, function (player) {
	App.showCenterLabel(`${player.name}님이 (5, 5) 좌표에 도착!`);
});
```

####

### addOnLocationTouched

{% hint style="info" %}
addOnLocationTouched(name: string, function(player){})
{% endhint %}

플레이어가 맵에디터에서 지정한 ‘지정영역’에 도착했을 때 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="106.33333333333331">이름</th><th width="115">타입</th><th>설명</th></tr></thead><tbody><tr><td>name</td><td>String</td><td>맵 에디터에서 지정한 ‘지정 영역’의 이름</td></tr><tr><td>player</td><td>Player</td><td>지정 영역에 도착한 플레이어를 가르킴<br>파라미터의 이름은 임의로 지정 가능</td></tr></tbody></table>

**예제**

플레이어가 지정 영역에 도착했을 때 메시지 출력해보기

```jsx
// 플레이어가 이름이 "myLocation"인 영역에 도착했을 때 실행
App.addOnLocationTouched("myLocation", function(player){
	App.showCenterLabel(`${player.name}님이 myLocation에 도착했습니다.`)
});
```

####

### addOnKeyDown

{% hint style="info" %}
App.addOnKeyDown(keycode : number, function(player){});
{% endhint %}

플레이어가 지정된 키를 눌렀을 때 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="122.33333333333331">이름</th><th width="118">타입</th><th>설명</th></tr></thead><tbody><tr><td>keycode</td><td>Number</td><td>키에 해당하는 숫자<br><a href="/creator/reference/keycode"><mark style="color:purple;">자바스크립트 키코드 표</mark></a></td></tr><tr><td>player</td><td>Player</td><td>해당 키를 누른 플레이어를 가르킴<br>player 파라미터 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

a를 눌렀을 때 메시지 출력해보기 ( a의 키코드: 65 )

```jsx
// 플레이어가 a를 눌렀을 때 실행
App.addOnKeyDown(65, function(player){
	App.sayToAll(`${player.name}님이 a키를 눌렀습니다.`)
});
```

####

### setTimeout

{% hint style="info" %}
setTimeout(function(){}, time: number);
{% endhint %}

time(ms) 후에 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="123.33333333333331">이름</th><th width="140">타입</th><th>설명</th></tr></thead><tbody><tr><td>time</td><td>Number</td><td>callback 함수 실행 전 대기 시간 (ms)</td></tr></tbody></table>

**예제**

앱이 시작되고 5초 후 메시지 출력해보기

```jsx
App.onStart.Add(function () {
	setTimeout(function () {
		App.sayToAll("5초후 메시지 출력");
	}, 5000);
});
```

####

### setInterval

{% hint style="info" %}
setInterval(function(){}, time: number);
{% endhint %}

time(ms) 간격으로 callback 함수를 실행합니다.

**파라미터**

<table><thead><tr><th width="133.33333333333331">이름</th><th width="141">타입</th><th>설명</th></tr></thead><tbody><tr><td>time</td><td>Number</td><td>callback 함수 실행 주기 (ms)</td></tr></tbody></table>

**예제**

앱이 시작되고 1초 간격으로 메시지 출력해보기

```jsx
let time = 0;
App.onStart.Add(function () {
	setInterval(function () {
		App.sayToAll(`앱 실행 ${++time}초 경과`);
	}, 1000);
});
```

####

### addMobileButton

{% hint style="info" %}
App.addMobileButton( anchor: number, posX: number, posY: number, function(player){} )
{% endhint %}

모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.

> 모바일버튼의을이미지를 원하는 이미지로 변경 할 수 있습니다.
>
> [모바일버튼 이미지 변경하기](/creator/reference/mobilebutton-image)

**파라미터**

<table><thead><tr><th width="125.33333333333331">이름</th><th width="132">타입</th><th>설명</th></tr></thead><tbody><tr><td>anchor</td><td>Number</td><td>모바일 버튼의 위치를 숫자로 입력합니다.<br>TOP = 0,<br>TOP_LEFT = 1,<br>TOP_RIGHT = 2,<br>MIDDLE = 3,<br>MIDDLE_LEFT = 4,<br>MIDDLE_RIGHT = 5,<br>BOTTOM = 6,<br>BOTTOM_LEFT = 7,<br>BOTTOM_RIGHT = 8</td></tr><tr><td>posX</td><td>Number</td><td>모바일 버튼 x 방향 오프셋 수치</td></tr><tr><td>posY</td><td>Number</td><td>모바일 버튼 y 방향 오프셋 수치</td></tr><tr><td>player</td><td>Player</td><td>모바일 버튼을 누른 플레이어를 가르킴</td></tr></tbody></table>

**예제**

모바일 버튼 추가해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FwxWxtPixur0inT2ryhex%2Fbtnmobile.png?alt=media&amp;token=bd33647b-1b3b-4cab-8339-d66a56c4779f" alt=""><figcaption><p>예제를 참고해 버튼의 위치를 설정해보세요!</p></figcaption></figure></div>

<pre class="language-jsx"><code class="lang-jsx">App.onStart.Add(function () {
	// Bottom_Right
	App.addMobileButton(8, 145, 75, function (player) {
		App.sayToAll(`${player.name}, Bottom 버튼A`);
	});
	// Bottom_Right
	App.addMobileButton(8, 145, -20, function (player) {
		App.sayToAll(`${player.name}, Bottom 버튼B`);
	});
	// Top
	App.addMobileButton(0, 0, 400, function (player) {
		App.sayToAll(`${player.name}, TOP 버튼`);
	});
	// Top_Left
	App.addMobileButton(1, 50, 400, function (player) {
		App.sayToAll(`${player.name}, TOP_LEFT 버튼`);
	});
	// Top_right
	App.addMobileButton(2, 50, 400, function (player) {
		App.sayToAll(`${player.name}, TOP_RIGHT 버튼`);
	});
	// Middle
	App.addMobileButton(3, 0, 100, function (player) {
		App.sayToAll(`${player.name}, MIDDLE 버튼`);
	});
	// Middle_left
	App.addMobileButton(4, 50, 100, function (player) {
		App.sayToAll(`${player.name}, MIDDLE LEFT 버튼`);
	});
	// Middle_right
	App.addMobileButton(5, 50, 100, function (player) {
		App.sayToAll(`${player.name}, MIDDLE RIGHT 버튼`);
	});
<strong>});
</strong></code></pre>

###

### putMobilePunch

{% hint style="info" %}
App.putMobilePunch(enable: boolean = true)
{% endhint %}

enable이 true이면 모바일 환경에서 펀치 버튼이 추가됩니다.

**파라미터**

<table><thead><tr><th width="144.33333333333331">이름</th><th width="180">타입</th><th>설명</th></tr></thead><tbody><tr><td>enable</td><td>Boolean</td><td>모바일 펀치 버튼 활성화 여부 ( 기본 값 true )</td></tr></tbody></table>

**예제**

Q 버튼을 눌러 모바일 환경에 펀치 버튼을 추가/제거 해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FdebCpDgirD7kNBATEbKy%2FUntitled.png?alt=media&amp;token=5adacc6c-a989-4097-9355-29b37dbcf87b" alt=""><figcaption></figcaption></figure></div>

```jsx
let punchButton = false;
// Q 버튼을 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	if (!punchButton) {
		punchButton = true;
		App.putMobilePunch();
	} else {
		punchButton = false;
		App.putMobilePunch(false);
	}
});
```

***

### putMobilePunchWithIcon

{% hint style="info" %}
App.putMobilePunchWithIcon(icon: ScriptDynamicResource)
{% endhint %}

로드한 이미지로 펀치 버튼을 만들어 추가합니다.

**파라미터**

<table><thead><tr><th width="94.33333333333331">이름</th><th width="216">타입</th><th>설명</th></tr></thead><tbody><tr><td>icon</td><td>ScriptDynamicResource</td><td>App.loadSpriteSheet 함수로 로드한 이미지 리소스</td></tr></tbody></table>

**예제**

Q 버튼을 눌러 모바일 환경에 로드한 이미지로 펀치 버튼 추가하기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1ZUXon3BkbCfkgHmwBoz%2Fimage.png?alt=media&amp;token=6f7ec712-186d-4215-819a-2f9d3ad91324" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FWfvRfEmkhzTBX7OrBkhu%2FpunchIcon.png?alt=media&amp;token=5853a37c-56e7-45ad-b4ee-0ab7f035391b" alt=""><figcaption><p>펀치아이콘</p></figcaption></figure></div>

```jsx
const punchIcon = App.loadSpritesheet("punchIcon.png")
// Q 버튼을 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	App.putMobilePunchWithIcon(punchIcon);
});
```

***

### 부록

[<mark style="color:purple;">자바스크립트 키코드 표</mark>](/creator/reference/keycode)


# Methods

### 소개

화면에 UI를 표시, 유저 이동 또는 강퇴, 사운드 재생 등 편리한 기능을 제공하는 함수들 입니다.

Methods 함수는 용도에 따라 [<mark style="color:purple;">**UI**</mark>](#ui) <mark style="color:purple;">**,**</mark> [<mark style="color:purple;">**User Control**</mark>](#user-control)<mark style="color:purple;">**,**</mark> [<mark style="color:purple;">**Sound**</mark>](#sound)<mark style="color:purple;">**,**</mark> [<mark style="color:purple;">**통신**</mark>](#undefined-1)<mark style="color:purple;">**,**</mark> [<mark style="color:purple;">**공통**</mark>](#undefined-2) 메소드로 나눌 수 있습니다.

### UI

<table><thead><tr><th width="207">이름</th><th>설명</th></tr></thead><tbody><tr><td>loadSpritesheet</td><td>스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.</td></tr><tr><td>showCenterLabel</td><td>모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.</td></tr><tr><td>showCustomLabel</td><td>모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.<br>text 부분에 <mark style="color:purple;background-color:yellow;">span</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.</td></tr><tr><td>sayToAll</td><td>모든 플레이어의 채팅창에 text 를 출력하는 함수입니다.</td></tr><tr><td>sayToStaffs</td><td>Staff 권한 이상의 플레이어의 채팅창에 text 를 출력하는 함수입니다.</td></tr><tr><td>showWidget</td><td>모든플레이어에게 지정된 align의 위치에 html파일을 위젯으로 불러오는 함수입니다.</td></tr><tr><td>showYoutubeWidget</td><td>링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.</td></tr></tbody></table>

### **Control**

<table><thead><tr><th width="212">이름</th><th>설명</th></tr></thead><tbody><tr><td>spawnPlayer</td><td>플레이어를 지정한 x, y 좌표로 이동 시키는 함수입니다.</td></tr><tr><td>kickPlayer</td><td>플레이어를 추방하는 함수입니다.</td></tr><tr><td>forceDestroy</td><td>미니게임 앱을 강제 종료하는 함수입니다.</td></tr><tr><td>clearChat</td><td>모든 채팅 내용을 삭제하는 함수입니다.</td></tr><tr><td>getPlayerByID</td><td>id 에 해당하는 플레이어를 반환하는 함수입니다.</td></tr></tbody></table>

### Sound

<table><thead><tr><th width="212">이름</th><th>설명</th></tr></thead><tbody><tr><td>playSound</td><td>사운드 파일을 재생하는 함수입니다.</td></tr><tr><td>playSoundLink</td><td>사운드 URL을 재생하는 함수입니다.</td></tr><tr><td>stopSound</td><td>재생 중인 모든 사운드를 멈추는 함수입니다.</td></tr></tbody></table>

### 통신

<table><thead><tr><th width="216">이름</th><th>설명</th></tr></thead><tbody><tr><td>httpGet</td><td>http get 요청을 보내는 함수입니다.</td></tr><tr><td>httpPost</td><td>Form-Data 형태의 http post 요청을 보내는 함수 입니다.</td></tr><tr><td>httpPostJson</td><td>JSON 형태의 http post 요청을 보내는 함수입니다.</td></tr></tbody></table>

### 공통

<table><thead><tr><th width="217">이름</th><th>설명</th></tr></thead><tbody><tr><td>sendUpdated</td><td>App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### 🎨 **UI Methods**

<mark style="background-color:yellow;">**UI 함수 한 눈에 보기**</mark>

```jsx
// 스프라이트 시트 그림 파일을 읽어 객체화
App.loadSpritesheet(fileName: string, frameWidth: number, frameHeight: number, anims: array, frameRate: number): ScriptDynamicResource

// 모든 플레이어에게 지정된 위치에 해당 text를 1초간 표시
App.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: int = 0, time: int = 3000)

// 모든 플레이어에게 지정된 위치에 해당 text를 3초간 표시, 커스터마이징 가능
App.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: int = 3000);

// 채팅창에 해당 text내용을 출력
App.sayToAll(text: string, color: uint = 0xFFFFFF)

// 모든 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오기
App.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget

// 모든 플레이어에게 지정된 align의 위치에 해당 YouTube link의 동영상을 재생시킴
App.showYoutubeWidget(link: string, align: string, width: number, height: number): ScriptWidget
```

####

### loadSpritesheet

{% hint style="info" %}
App.loadSpritesheet(fileName: string, frameWidth: number, frameHeight: number, anims: array, frameRate: number): ScriptDynamicResource
{% endhint %}

스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.

ScriptDynamicResource에 대한 이해를 돕는 [<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!

**파라미터**

<table><thead><tr><th width="166.33333333333331">이름</th><th width="152">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>frameWidth frameHeight</td><td>number</td><td>한 프레임의 가로, 세로 픽셀 크기</td></tr><tr><td>anims</td><td>Array</td><td>애니메이션으로 지정할 frame 이미지 번호 배열</td></tr><tr><td>frameRate</td><td>number</td><td>프레임 하나 당 데이터를 표시하는 속도<br>frameRate: 8 → 1초에 8개의 이미지를 보여줌</td></tr></tbody></table>

**예제**

페인트맨 - 블루맨 스프라이트 이미지 적용해보기

{% file src="/files/BbXwzNer4wOQczIP3pn6" %}

```jsx
// 한 프레임의 사이즈 48x64
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
		dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
		down_jump: [38],
		left_jump: [39],
		right_jump: [40],
		up_jump: [41],
}, 8);
// 플레이어가 입장하면 캐릭터 이미지가 바뀜
App.onJoinPlayer.Add(function(player){
	player.sprite = blueman;
	player.sendUpdated();
});
```

####

### showCenterLabel

{% hint style="info" %}
App.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: number = 0, time: number = 3000)
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

**파라미터**

<table><thead><tr><th width="141.33333333333331">이름</th><th width="131">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>라벨에 출력할 텍스트</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색 (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr><tr><td>bgColor</td><td>Uint</td><td>메시지가 출력되는 라벨의 배경색<br>값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.</td></tr><tr><td>offset</td><td>number</td><td>offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다.<br>값을 입력하지 않을 경우, 0으로 지정됩니다.</td></tr><tr><td>time</td><td>number</td><td>라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )</td></tr></tbody></table>

**예제**

노란색 배경 입장 메시지 라벨을 2초간 출력해보기

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FymY4dl5cRejg7qmceQvE%2FUntitled.png?alt=media&amp;token=e697c0d0-dbf1-4ff1-9fe7-d95ca581e974" alt=""><figcaption></figcaption></figure>

```jsx
App.onJoinPlayer.Add(function(player){
	App.showCenterLabel(`${player.name}님이 입장하셨습니다.`, 0x000000, 0xFFFF00, 200, 2000); // 노란색 배경, 검정색 글씨로 표시하기
});
```

####

### showCustomLabel

{% hint style="info" %}
App.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: number = 3000, option: object = null);
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

text 부분에 <mark style="color:purple;">`span`</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.

**파라미터**

<table><thead><tr><th width="136.33333333333331">이름</th><th width="122">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>라벨에 출력할 텍스트 ( span 태그 허용 )</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색 (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.</td></tr><tr><td>bgColor</td><td>Uint</td><td>메시지가 출력되는 라벨의 배경색<br>값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr><tr><td>offset</td><td>number</td><td>offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다.<br>값을 입력하지 않을 경우, 0으로 지정됩니다.</td></tr><tr><td>width</td><td>number</td><td>라벨의 너비를 n%로 설정하는 값 입니다. (기본 값 100)</td></tr><tr><td>opacity</td><td>number</td><td>라벨의 배경 투명도를 설정하는 값 입니다. (기본 값 0.6, 범위 0 ~ 1)</td></tr><tr><td>time</td><td>number</td><td>라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )</td></tr></tbody></table>

**옵션**

<table><thead><tr><th width="159.33333333333331">이름</th><th width="112">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>라벨에 키 값을 할당하여, 서로 다른 키 값을 가진 라벨은 동시에 표시할 수 있습니다.</td></tr><tr><td>borderRadius</td><td>String</td><td>라벨의 모서리에 둥글기를 설정할 수 있습니다.<br>ex) borderRadius: "8px"</td></tr><tr><td>fontOpacity</td><td>boolean</td><td>false로 설정시 폰트에 투명도가 적용되지 않습니다.</td></tr><tr><td>padding</td><td>String</td><td>라벨 내부에 padding 값을 지정 할 수 있습니다.<br>ex) padding: "8px"</td></tr></tbody></table>

**예제**

[**커스텀 라벨 예제 코드 페이지**](/creator/tutor/custom-label)**를 참고해주세요**

####

### sayToAll

{% hint style="info" %}
&#x20;App.sayToAll(text: string, color: uint = 0xFFFFFF)
{% endhint %}

채팅창에 text 내용을 출력합니다.

**파라미터**

<table><thead><tr><th width="130.33333333333331">이름</th><th width="146">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>채팅창에 출력할 텍스트</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색을 지정합니다. (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr></tbody></table>

**예제**

입장메시지를 하늘색으로 출력해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FjrJoAf7qWyWxCIXn8ct1%2Fimage.png?alt=media&amp;token=36382963-56be-4e80-b7d8-7326fc19c57f" alt=""><figcaption></figcaption></figure></div>

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.sayToAll(`[info]${player.name} has entered.`, 0x00ffff); // 하늘색으로 표시하기
});
```

### sayToStaffs

{% hint style="info" %}
&#x20;App.sayToStaffs(text: string, color: uint = 0xFFFFFF)
{% endhint %}

Staff이상 권한의 유저 채팅창에 text 내용을 출력합니다.

**파라미터**

<table><thead><tr><th width="130.33333333333331">이름</th><th width="146">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>채팅창에 출력할 텍스트</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색을 지정합니다. (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr></tbody></table>

**예제**

입장메시지를 하늘색으로 출력해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F4vrjLuraNhXXibs0VX92%2Fimage.png?alt=media&amp;token=5010953a-0159-4a71-b133-af17edc675d2" alt=""><figcaption></figcaption></figure></div>

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.sayToStaffs(`[Staff] ${player.name} has entered.`, 0x00ffff); // 하늘색으로 표시하기
});
```

### showWidget

{% hint style="info" %}
&#x20;App.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

모든 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.

**파라미터**

<table><thead><tr><th width="155.33333333333331">이름</th><th width="126">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>align</td><td>String</td><td>위젯을 표시할 위치<br>’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’</td></tr><tr><td>width<br>height</td><td>number</td><td>위젯을 표시할 영역의 가로, 세로 크기(px)</td></tr></tbody></table>

**예제**

초성퀴즈 위젯 따라해보기

{% file src="/files/r3LiUM54pxLvLilyxNI7" %}

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FXaR7dhj0plZBszvrE5GV%2FUntitled%202.png?alt=media\&token=2775b92c-4aab-4f7e-a067-6026decfd9a0)

```jsx
let _widget = null;
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	_widget = App.showWidget("widget.html", "top", 200, 300); // 화면 상단, 200x300 영역에 위젯을 보여줌
	_widget.sendMessage({
		timer: 15,
		answer: "ㅅㅍㅋ",
	});
});
```

####

### showYoutubeWidget

{% hint style="info" %}
App.showYoutubeWidget(link: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.

**파라미터**

<table><thead><tr><th width="137.33333333333331">이름</th><th width="128">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>Youtube 컨텐츠 url</td></tr><tr><td>align</td><td>String</td><td>위젯을 표시할 위치<br>’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’</td></tr><tr><td>width<br>height</td><td>number</td><td>위젯을 표시할 영역의 가로, 세로 크기(px)</td></tr></tbody></table>

**예제**

Youtube 위젯 표시해보기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FSOlUtAMJkLk4mPIaRRFx%2FUntitled%203.png?alt=media\&token=030d7c34-d757-46f5-a77f-2db511f9d721)

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.showYoutubeWidget("https://www.youtube.com/watch?v=SXnMGIR8cjY","top",600,500);
});
```

###

## **🔗Control Methods**

<mark style="background-color:yellow;">**Control 함수 한 눈에 보기**</mark>

```jsx
// playerID 에 해당하는 플레이어를 tileX, tileY 좌표로 이동시킨다.
App.spawnPlayer(playeID: string, tileX: number, tileY: number)

// playerID 에 해당하는 플레이어를 추방한다.
// 추방당한 유저는 24시간 동안 해당 스페이스에 접근하지 못하게 된다.
App.kickPlayer(playeID: string)

// 미니게임 앱을 강제종료시킵니다.
App.forceDestroy();

// 모든 채팅내용을 삭제합니다.
App.clearChat();

//id 에 해당하는 플레이어를 반환하는 함수입니다.
App.getPlayerID(playerID:string);
```

####

### spawnPlayer

{% hint style="info" %}
App.spawnPlayer(playeID: string, tileX: number, tileY: number)
{% endhint %}

playerID 에 해당하는 플레이어를 tileX, tileY 좌표로 이동시키는 함수입니다.

**파라미터**

<table><thead><tr><th width="149.33333333333331">이름</th><th width="139">타입</th><th>설명</th></tr></thead><tbody><tr><td>playerID</td><td>String</td><td>플레이어의 ID 값</td></tr><tr><td>tileX<br>tileY</td><td>number</td><td>플레이어를 이동시킬 x, y 좌표 값</td></tr></tbody></table>

**예제**

입장하는 플레이어를 지정 좌표로 이동시키기

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.spawnPlayer(player.id, 5, 5); // 플레이어를 5,5 위치로 이동시키기
});
```

### kickPlayer

{% hint style="info" %}
App.kickPlayer(playerID: string)
{% endhint %}

playerID 에 해당하는 플레이어를 추방하는 함수입니다.

**파라미터**

<table><thead><tr><th width="150.33333333333331">이름</th><th width="144">타입</th><th>설명</th></tr></thead><tbody><tr><td>playerID</td><td>String</td><td>플레이어의 ID 값</td></tr></tbody></table>

**예제**

강퇴 명령어 만들어보기

⛔ 강퇴 당한 참가자는 24시간 동안 해당 스페이스에 접속하지 못합니다.

```jsx
// 플레이어가 채팅을 입력할 때 실행
// 명령어 형식 '!강퇴 닉네임'
App.onSay.Add(function (player, text) {
	let players = App.players;
	if (text.indexOf("!강퇴 ") == 0) {
		let nickname = text.slice(4);
		for (let i in players) {
			let p = players[i];
			if (p.name == nickname) {
				App.kickPlayer(p.id);
				break;
			}
		}
	}
});
```

### forceDestroy

{% hint style="info" %}
App.forceDestroy();
{% endhint %}

미니게임 앱을 강제 종료하는 함수입니다.

**예제**

미니게임 앱을 강제 종료시키기

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	App.forceDestroy();
});
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FPpFik4QtZxPRiI6Dk6ak%2FGIF%202022-11-25%20%EC%98%A4%EC%A0%84%2010-54-44.gif?alt=media&amp;token=09891261-c3b5-4527-b8b9-7d7d1c5c5a3a" alt=""><figcaption></figcaption></figure></div>

### clearChat

{% hint style="info" %}
App.clearChat();
{% endhint %}

모든 채팅 내용을 삭제하는 함수입니다.

**예제**

Q를 눌러 채팅 내용 지우기

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	App.clearChat();
});
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fe65VdheeKum05CX63rRe%2FGIF%202022-11-25%20%EC%98%A4%EC%A0%84%2011-04-14.gif?alt=media&amp;token=df79ab3c-8624-4a3f-8e2a-8660ab68866b" alt=""><figcaption><p>채팅이 삭제되는 모습</p></figcaption></figure></div>

### getPlayerByID

{% hint style="info" %}
App.getPlayerByID(playerID: string);
{% endhint %}

id 에 해당하는 플레이어를 반환하는 함수입니다.

**예제**

App.getPlayerByID 사용 방법

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	const myPlayer = App.getPlayerByID(player.id);
});
```

## 🔉 **Sound Methods**

<mark style="background-color:yellow;">**Sound 함수 한 눈에 보기**</mark>

```jsx
// 모든 플레이어에게 사운드를 재생
App.playSound(fileName: string, loop: boolean = false, overlap: boolean = false)

// 모든 플레이어에게 링크에 해당하는 사운드를 재생
App.playSoundLink(link: string, loop: boolean = false)

// 모든 재생되는 사운드를 멈춤
App.stopSound()

// 찌르기(Z키) 공격 효과음을 변경하는 함수입니다.
App.changeAttackSound(fileName:string)
```

### playSound

{% hint style="info" %}
App.playSound(fileName: string, loop: boolean = false, overlap: boolean = false)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

**파라미터**

<table><thead><tr><th width="172.33333333333331">이름</th><th width="160">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>loop</td><td>boolean</td><td>true: 반복 재생<br>false: 1회 재생</td></tr><tr><td>overlap</td><td>boolean</td><td>사운드 오버랩(겹침) 재생 가능 여부</td></tr></tbody></table>

**예제**

플레이어가 입장할 때 입장음 적용해보기 ( 파일 )

{% file src="/files/MFoMKAuui8WogohY8jA1" %}

```jsx
//플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.playSound("join.mp3", false, true);
});
```

####

### playSoundLink

{% hint style="info" %}
App.playSoundLink(link: string, loop: boolean = false)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

> 💡 **올바른 링크를 입력했는데 재생이 되지 않는 경우**
>
> CORS 정책을 위반한 경우일 가능성이 높습니다. CORS 정책을 맞출 수 없는 경우에는 playSoundLink 대신 음악 파일을 업로드 하여 playSound 함수를 사용하는 것을 권장드립니다.

**파라미터**

<table><thead><tr><th width="152.33333333333331">이름</th><th width="160">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>사운드 url</td></tr><tr><td>loop</td><td>boolean</td><td>true: 반복 재생<br>false: 1회 재생</td></tr></tbody></table>

**예제**

플레이어가 입장할 때 입장음 적용해보기 ( 사운드 url )

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	App.playSoundLink("https://zep.us/assets/sounds/ring.mp3",false);
});
```

####

### stopSound

{% hint style="info" %}
App.stopSound();
{% endhint %}

재생되고 있는 사운드를 멈추는 함수입니다.

**파라미터**

* 없음

**예제**

q를 누르면 사운드가 멈추는 기능 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81,function(p){
	App.stopSound();
})
```

### changeAttackSound

{% hint style="info" %}
App.changeAttackSound(fileName:string)
{% endhint %}

찌르기(Z키) 공격 효과음을 변경하는 함수입니다.

**파라미터**

<table><thead><tr><th width="152.33333333333331">이름</th><th width="160">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>적용할 사운드 파일명</td></tr></tbody></table>

**예제**

changeAttackSound 사용 방법

```jsx
App.onStart.Add(function(){
	App.changeAttackSound("attack.mp3");
})
```

## 📡 **통신 Methods**

<mark style="background-color:yellow;">**통신 함수 한 눈에 보기**</mark>

```jsx
// 해당 URL에 HTTP Get 요청을 실행
App.httpGet(url: string, headers: object, callback: ((string) => void))

// 해당 URL에 HTTP Post 포스팅을 실행
App.httpPost(url: string, headers: object, body: object, callback: ((string) => void))

// 해당 URL에 HTTP Post 포스팅을 실행
App.httpPostJson(url: string, headers: object, body: object, callback: ((string) => void))

```

####

### httpGet

{% hint style="info" %}
App.httpGet(url: string, headers: object, function(res: string){})
{% endhint %}

http get 요청을 보내는 함수입니다.

**파라미터**

<table><thead><tr><th width="168.33333333333331">이름</th><th width="164">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>요청을 보낼 주소</td></tr><tr><td>headers</td><td>Object</td><td>요청 헤더</td></tr><tr><td>res</td><td>String</td><td>요청에 대한 응답</td></tr></tbody></table>

**예제**

[한국어 별명 생성기](https://nickname.hwanmoo.kr/) API를 이용해 입장하는 플레이어의 닉네임을 바꿔보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FbcPvRi9HR7pL29PwZZCY%2FUntitled%204.png?alt=media&amp;token=5f2f7abb-fb23-4b68-b275-779cd8cc3bbb" alt=""><figcaption></figcaption></figure></div>

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
	App.httpGet(
		"https://nickname.hwanmoo.kr/?format=json&count=1&max_length=6&whitespace=_",
		null,
		function (res) {
			// 응답 결과를 JSON 오브젝트로 변경
			let response = JSON.parse(res);
			player.name = response.words[0];
			player.sendUpdated();
		}
	);
});
```

####

### httpPost

{% hint style="info" %}
App.httpPost(url: string, headers: object, body: object, function(res: string))
{% endhint %}

Form-Data 형태의 http post 요청을 보내는 함수 입니다.

**파라미터**

<table><thead><tr><th width="154.33333333333331">이름</th><th width="145">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>요청을 보낼 주소</td></tr><tr><td>headers</td><td>Object</td><td>요청 헤더, 요청 헤더가 없으면 <mark style="color:purple;background-color:yellow;"><strong>{ }</strong></mark> 를 입력.</td></tr><tr><td>body</td><td>Object</td><td>요청 바디 ( Form data )</td></tr><tr><td>res</td><td>String</td><td>요청에 대한 응답</td></tr></tbody></table>

**예제**

앱에서 보낸 헤더와 데이터를 응답으로 받아 채팅창에 출력해보기.

예제와 같이 key, value 값을 string 형태로 작성해야하며, 요청 서버에서 폼데이터를 받아 처리가 가능해야합니다.

```jsx
// q를 눌렀을 때 실행되는 함수
App.addOnKeyDown(81, function (player) {
	App.httpPost(
		"https://postman-echo.com/post",
		{
			"test-header": "zep",
		},
		{
			"name": "zepscript",
		},
		(res) => {
			// 요청 결과를 JSON 오브젝트로 변환
			let response = JSON.parse(res);
			App.sayToAll(`보낸 헤더: ${response.headers["test-header"]}`, 0xffffff);
			App.sayToAll(`보낸 데이터: ${response.form.name}`, 0xffffff);
		}
	);
});
```

####

### httpPostJson

{% hint style="info" %}
App.httpPostJson(url: string, headers: object, body: object, function(res: string))
{% endhint %}

Json 형태의 http post 요청을 보내는 함수 입니다.

**파라미터**

<table><thead><tr><th width="142.33333333333331">이름</th><th width="142">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>요청을 보낼 주소</td></tr><tr><td>headers</td><td>Object</td><td>요청 헤더, 요청 헤더가 없으면 <mark style="color:purple;background-color:yellow;"><strong>{ }</strong></mark> 를 입력.</td></tr><tr><td>body</td><td>Object</td><td>요청 바디 ( JSON data )</td></tr><tr><td>res</td><td>String</td><td>요청에 대한 응답</td></tr></tbody></table>

**예제**

앱에서 보낸 데이터를 응답으로 받아 채팅창에 출력해보기.

```jsx
// q를 눌렀을 때 실행되는 함수
App.addOnKeyDown(81, function (player) {
	App.httpPostJson(
		"https://postman-echo.com/post",
		{},
		{
			name: "zepscript",
		},
		(res) => {
			App.sayToAll(`${res}`, 0xffffff);
			// 요청 결과를 JSON 오브젝트로 변환
			let response = JSON.parse(res);
			App.sayToAll(`보낸 데이터: ${response.data.name}`, 0xffffff);
		}
	);
});
```

## 💠 **공통 Methods**

<mark style="background-color:yellow;">**공통 함수 한 눈에 보기**</mark>

```jsx
// App 관련 필드값이 변경되면 변경값을 적용함
App.sendUpdated()
```

***

### sendUpdated

{% hint style="info" %}
App.sendUpdated()
{% endhint %}

App 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.

**파라미터**

* 없음


# ScriptMap

ScriptApp 클래스는 아래와 같이 2개의 카테고리로 구성되어있습니다

### [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptmap/field)

> Map과 관련된 속성 값들이 모여있는 카테고리 입니다.

### [<mark style="color:purple;">Method</mark>](/zep-script-api/zepscriptapi/scriptmap/methods)

> Map에 타일 효과나, 오브젝트 설치 등 맵과 관련된 편리한 기능을 제공하는 함수들이 모여있는 카테고리 입니다.


# Field

### 소개

맵과 관련된 속성 값을 조회 할 수 있습니다. \
현재는 맵의 **이름(name)**, **너비(width), 높이(height)** 필드를 조회할 수 있습니다

🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.

| 이름        | 설명              |
| --------- | --------------- |
| 🔒 name   | 맵의 이름 값을 가져옵니다. |
| 🔒 width  | 맵의 너비 값을 가져옵니다. |
| 🔒 height | 맵의 높이 값을 가져옵니다  |

## 📚 API 설명 및 예제

### name

{% hint style="info" %}
Map.name: string
{% endhint %}

맵의 이름 값을 가져옵니다.

**예제**

맵의 이름 값을 채팅창에 출력해보기.

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	App.sayToAll(`맵 이름: ${Map.name}`);
});
```

### width & height

{% hint style="info" %}
Map.width : Number Map.height : Number
{% endhint %}

맵의 너비와 높이 값을 가져옵니다.

**예제**

맵의 너비, 높이 값을 채팅창에 출력해보기.

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	App.sayToAll(`맵 가로 크기: ${Map.width}`);
	App.sayToAll(`맵 세로 크기: ${Map.height}`);
});
```


# Methods

소개

Map에 타일 효과나 오브젝트 설치 등 맵과 관련된 편리한 기능을 제공하는 함수들 입니다.

<table><thead><tr><th width="270">이름</th><th>설명</th></tr></thead><tbody><tr><td>putTileEffect</td><td>지정한 좌표에 타일 효과를 적용하는 함수입니다</td></tr><tr><td>putObject</td><td>지정한 좌표에 오브젝트를 놓는 함수입니다.</td></tr><tr><td>putObjectMultiple</td><td>오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다.</td></tr><tr><td>putObjectWithKey</td><td>지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다.</td></tr><tr><td>getObjectWithKey</td><td>해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.</td></tr><tr><td>playObjectAnimation</td><td>해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.</td></tr><tr><td>playObjectAnimationWithKey</td><td>key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.</td></tr><tr><td>moveObject</td><td>x, y 좌표에 위치한 오브젝트를 지정한 x, y 좌표로 time 초 동안 움직이는 함수입니다.</td></tr><tr><td>moveObjectWithKey</td><td>키 값을 가진 오브젝트를 지정한 x,y 좌표로 움직이는 함수입니다.</td></tr><tr><td>clearAllObjects</td><td>ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.</td></tr><tr><td>getTile</td><td>해당하는 레이어의 x,y 좌표에 있는 타일의 enum 값을 반환하는 함수입니다.</td></tr><tr><td>hasLocation</td><td>맵에 해당 로케이션이 있는지 체크하여 true/false를 반환하는 함수입니다.</td></tr><tr><td>getObjectsByType</td><td>Type에 해당하는 오브젝트들을 반환하는 함수입니다.</td></tr><tr><td>getTopObjectsByType</td><td>Type에 해당하는 상단 오브젝트들을 반환하는 함수입니다.</td></tr><tr><td>sayObjectWithKey</td><td>key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.</td></tr><tr><td>getLocation</td><td>파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.</td></tr><tr><td>getLocationRandom</td><td>파라미터로 전달한 로케이션이 2개이상  존재하는 경우, 무작위로 선택하여 해당 로케이션의 설치 좌표를 리턴합니다.</td></tr><tr><td>getLocationList</td><td>파라미터로 전달된 로케이션 이름과 일치하는 모든 로케이션의 정보를 배열 형태로 반환합니다</td></tr></tbody></table>

## 📚 API 설명 및 예제

<mark style="background-color:yellow;">**Methods 함수 한 눈에 보기**</mark>

```javascript
// 지정된 좌표에 타일효과를 적용
Map.putTileEffect(x: number, y: number, tileID: TileEffectType)

// 지정된 좌표에 오브젝트를 놓음 (기준 좌표 : Left-Top)
Map.putObject(x: number, y: number, dynamicResource: ScriptDynamicResource)

// 오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다.
Map.putObjectMultiple(tileArray: array, type: PutObjectType, dynamicResource: ScriptDynamicResource, option: object);

// 지정된 좌표에 키 값을 가진 오브젝트를 놓음 (기준 좌표 : Left-Top)
Map.putObjectWithKey(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)

// 해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시킴 (putObject가 선행되어야 함)
Map.playObjectAnimation(x: number, y: number, name: string, loop: number)

// key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
Map.playObjectAnimationWithKey(key: string,) animName: string, repeatCount: number)

// ZEP 스크립트로 생성된 모든 오브젝트를 제거
Map.clearAllObjects()

// 해당 좌표의 오브젝트를 타겟 좌표로 time(초) 동안 이동
Map.moveObject(x: number, y: number, targetX: number, targetY: number, time: number)

// 해당 키 값을 가진 오브젝트를 타겟 좌표로 이동
Map.moveObjectWithKey(key: string, targetX: number, targetY: number, usePath:Boolean= true, time=0 )

// 해당하는 레이어의 x, y 좌표에 있는 타일의 타입 값을 리턴
Map.getTile(layer: number, x: number, y: number)

// 맵에 해당하는 로케이션이 있는지 true/false 값으로 리턴
Map.hasLocation(locationName: string)

// Type에 해당하는 오브젝트들을 반환하는 함수입니다.
Map.getObjectsByType(type: number)

// Type에 해당하는 상단 오브젝트들을 반환하는 함수입니다.
Map.getTopObjectsByType(type: number)

// key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.
Map.sayObjectWithKey( key: string, message: string )
```

####

### putTileEffect

{% hint style="info" %}
Map.putTileEffect(x: number, y: number, tileID: TileEffectType)
{% endhint %}

지정한 좌표에 타일 효과를 적용하는 함수입니다.

**파라미터**

TileEffectType에 대한 자세한 설명은 [<mark style="color:purple;">TileEffectType 상세 설명</mark>](/creator/reference/tileeffecttype) 페이지를 참고해주세요.

<table><thead><tr><th width="139.33333333333331">이름</th><th width="159">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>타일 효과를 설치할 x, y 좌표</td></tr><tr><td>tileID</td><td>TileEffectType</td><td><p>• TileEffectType.NONE : 없음<br>•TileEffectType.IMPASSABLE : 통과 불가<br>• TileEffectType.SPAWN : 스폰</p><p>• TileEffectType.PORTAL : 포털<br>• TileEffectType.PRIVATE_AREA : 프라이빗 공간<br>• TileEffectType.SPOTLIGHT : 스포트라이트<br>• TileEffectType.EMBED : 웹 링크<br>• TileEffectType.LOCATION : 지정 영역<br>• TileEffectType.AMBIENT_SOUND : 배경 음악<br>• TileEffectType.TILE_EMBED : 웹 임베드<br>• TileEffectType.WEB_PORTAL : 웹 포털<br>• TileEffectType.SPACE_PORTAL : 스페이스 포털</p></td></tr></tbody></table>

**예제**

**IMPASSABLE** 타일 효과 설치해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	// 5, 5 좌표에 IMPASSABLE 타일 효과 설치
	Map.putTileEffect(5, 5, TileEffectType.IMPASSABLE);
});
```

####

### putObject

{% hint style="info" %}
Map.putObject(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)
{% endhint %}

지정한 좌표에 오브젝트를 놓는 함수입니다. ( 기준 좌표: Left Top ) → [<mark style="color:purple;">**기준 좌표란?**</mark>](/creator/reference/coordinate)

ScriptDynamicResource에 대한 이해를 돕는 [<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!&#x20;

> null 값을 파라미터로 전달하여 스크립트로 설치한 오브젝트를 삭제 할 수 있습니다.
>
> ```
> Map.putObject(x, y, null);
> ```

**파라미터**

<table><thead><tr><th width="183.33333333333331">이름</th><th width="216">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>오브젝트를 놓을 x, y 좌표</td></tr><tr><td>dynamicResource</td><td>ScriptDynamicResource</td><td>App.loadSpritesheet 함수로 로드한 이미지</td></tr><tr><td>option</td><td>Object</td><td>onObjectTouched, onObjectAttacked 와 같은 App EventListener에서 오브젝트를 인식하려면 파라미터 란에 { overlap: true } 입력해야 합니다.</td></tr></tbody></table>

**예제**

블루맨 오브젝트 생성해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FEsF45f1QAbO4p550RiUc%2Fblueman.png?alt=media&amp;token=8744b3cb-57be-4865-b7b2-1e56dc19330d" alt=""><figcaption></figcaption></figure></div>

```jsx
// blueman.png를 객체화 하여 blueman 변수에 저장
let blueman = App.loadSpritesheet("blueman.png");

// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	// 5, 5 좌표에 blueman 오브젝트 생성
	Map.putObject(5, 5, blueman, {overlap: true});
});
// w 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	// 5, 5 좌표에 있는 오브젝트를 삭제
	Map.putObject(5, 5, null);
});
```

### putObjectMultiple

{% hint style="info" %}
Map.putObjectMultiple(tileArray: array, type: PutObjectType, dynamicResource: ScriptDynamicResource, option: object);
{% endhint %}

오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다. 이 기능을 사용하면 한 번에 많은 오브젝트를 설치할 경우 부하를 줄이는 효과를 얻을 수 있습니다.

**파라미터**

<table><thead><tr><th width="121.33333333333331">이름</th><th width="152">타입</th><th>설명</th></tr></thead><tbody><tr><td>tileArray</td><td>Array</td><td>오브젝트를 배치할 좌표들을 정의할 2차원 배열을 입력할 수 있습니다. (최대 길이 10 제한)</td></tr><tr><td>type</td><td>PutObjectType</td><td><p><code>PutObjectType.STROKE</code></p><ul><li>tileArray 배열에 정의된 좌표들을 순서대로 연결하여 경로를 생성한 다음, 생성된 경로 상의 모든 좌표들에 오브젝트를 배치합니다.</li></ul></td></tr><tr><td>dynamicResource</td><td>ScriptDynamicResource</td><td>App.loadSpritesheet 함수로 로드한 이미지</td></tr><tr><td>option</td><td>Object</td><td>onObjectTouched, onObjectAttacked 와 같은 App EventListener에서 오브젝트를 인식하려면 파라미터 란에 { overlap: true } 입력해야 합니다.</td></tr></tbody></table>

**예제**

사각형, 원 모양으로 오브젝트 배치하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F7DZTGj9cSPCP5XnhsOsg%2Fwarning.png?alt=media\&token=42ae9f1f-b731-4855-bf25-b755df3f4cce)

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FJQ58Tw2ohbYg1a6IXUFt%2Fimage.png?alt=media&amp;token=336482aa-1b51-48e9-90bd-5e30a1e2caf2" alt=""><figcaption><p><code>PutObjectType.STROKE</code> <br>사각형 모양으로 배치</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F1b22vZJONULREgAA56zM%2Fimage.png?alt=media&amp;token=de43a496-d160-4e79-8a3a-b5ce0d00cb38" alt=""><figcaption><p><code>PutObjectType.STROKE</code> <br>원 모양으로 배치</p></figcaption></figure></div>

```
const _mark = App.loadSpritesheet("mark.png");

// Q를 누르면 동작하는 함수 - 사각형 모양으로 배치
App.addOnKeyDown(81, function (player) {
	const tileArray = [
		[5, 5],
		[9, 5],
		[9, 9],
		[5, 9],
		[5, 5],
	];
	Map.putObjectMultiple(tileArray, PutObjectType.STROKE, _mark, { overlap: true });
});

// W를 누르면 동작하는 함수 - 원 모양으로 배치
App.addOnKeyDown(87, function (player) {
	const tileArray = [
		[10, 5],
		[8, 7],
		[8, 10],
		[10, 12],
		[13, 12],
		[15, 10],
		[15, 7],
		[13, 5],
		[10, 5],
	];
	Map.putObjectMultiple(tileArray, PutObjectType.STROKE, _mark, { overlap: true });
});
```

### putObjectWithKey

{% hint style="info" %}
Map.putObjectWithKey(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)
{% endhint %}

지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다. ( 기준 좌표: Left Top )

`dynamicResource` 파라미터에 `null` 입력시 해당 좌표의 오브젝트를 삭제할 수 있습니다.

**관련 문서:** [오브젝트 npcProperty](/creator/reference/npcproperty)

**파라미터**

<table><thead><tr><th width="183.33333333333331">이름</th><th width="214">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>오브젝트를 놓을 x, y 좌표</td></tr><tr><td>dynamicResource</td><td>ScriptDynamicResource</td><td>App.loadSpritesheet 함수로 로드한 이미지</td></tr></tbody></table>

**옵션 (option)**

값을 입력하지 않아도 Default 값이 적용됩니다

<table><thead><tr><th width="183.33333333333331">이름</th><th width="154">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>오브젝트의 키 값<br><strong>(Default: null)</strong></td></tr><tr><td>moveSpeed</td><td>Number</td><td><p>오브젝트의 이동속도</p><p><strong>(Default: 80)</strong></p></td></tr><tr><td>useDirAnim</td><td>Boolean</td><td>방향을 인지해서 애니메이션을 재생하는 옵션<br><strong>(Default: false)</strong></td></tr><tr><td>impassable</td><td>Boolean</td><td>오브젝트 통과불가 옵션<br><strong>(Default: false)</strong></td></tr><tr><td>offsetX</td><td>Number</td><td>오브젝트 이미지의 배치 기준점의 X좌표<br><strong>(Default: 0)</strong></td></tr><tr><td>offsetY</td><td>Number</td><td>오브젝트 이미지의 배치 기준점의 Y좌표<br><strong>(Default: 0)</strong></td></tr><tr><td>npcProperty</td><td>Object</td><td>npcProperty 관련 내용은 <a href="/creator/reference/npcproperty"><strong>문서</strong></a>를 참고해주세요.<br><strong>(Default: null)</strong></td></tr><tr><td>topObject</td><td>Boolean</td><td>true 로 설정하면 오브젝트가 상단오브젝트로  설치됩니다.<br><strong>(Default: false)</strong></td></tr></tbody></table>

**예제**

키 값을 가진 블루맨 오브젝트 생성해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FjWShei1tdfQfaQWGAZJF%2Fblueman.png?alt=media&amp;token=802a123f-07c0-4a2a-8291-1d997caaac9e" alt=""><figcaption></figcaption></figure></div>

```bash
let blueman = App.loadSpritesheet("blueman.png");
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	Map.putObjectWithKey(18, 6, blueman, {
		type:21, // zep-script 상호작용 오브젝트
		overlap: true, // 충돌 이벤트 감지
		movespeed: 100, // 이동속도, 기본값: 80
		key: "TestBlueMan", // 키 값
		impassalbe:true, // 통과 불가 오브젝트 설정
		useDirAnim: true, // 방향을 인지해서 애니메이션을 재생하는 옵션
        	offsetX: -8, // 오브젝트 이미지의 배치 기준점의 X좌표를 조정
              	offsetY: -32,// 오브젝트 이미지의 배치 기준점의 Y좌표를 조정
	});
});

// w 키를 누르면 동작하는 함수
App.addOnKeyDown(87, function (player) {
	// 오브젝트 삭제
	Map.putObjectWithKey(18, 6, null, {
		key: "TestBlueMan", // 키 값
	});
});
```

####

### getObjectWithKey

{% hint style="info" %}
Map.getObjectWithKey(key: String)
{% endhint %}

해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.

**파라미터**

<table><thead><tr><th width="108.33333333333331">이름</th><th width="160">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>정보를 가져올 오브젝트의 key 값</td></tr></tbody></table>

**예제**

키 값을 가진 오브젝트를 생성하고 관련 데이터를 채팅창에 출력해보기.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZzI4nWf0PeiPilsyPZuL%2FUntitled.png?alt=media&amp;token=655894f6-06a4-4e0c-9d28-f87b3fa07cba" alt=""><figcaption></figcaption></figure></div>

```bash
let blueman = App.loadSpritesheet("blueman.png");
App.onStart.Add(function() {
	Map.putObjectWithKey(18, 6, blueman, {
		overlap: true,
		movespeed: 80,
		key: "TestBlueMan", // 키 값
	});
});
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	let object_blueman = Map.getObjectWithKey("TestBlueMan");
	for(let data in object_blueman){
		App.sayToAll(`${data}: ${object_blueman[data]}`)
	}
})
```

### playObjectAnimation

{% hint style="info" %}
Map.playObjectAnimation(x: number, y: number, name: string, loop: number)
{% endhint %}

해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.

해당 좌표 지점에[ <mark style="color:purple;">Map.putObject</mark>](#putobject) 함수가 선행되어야합니다.

스프라이트 이미지를 처음 들어보신다면 [<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!

**파라미터**

<table><thead><tr><th width="117.33333333333326">이름</th><th width="137">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>타일 효과를 적용할 x, y 좌표</td></tr><tr><td>name</td><td>String</td><td><mark style="color:purple;background-color:yellow;">let 변수 = App.loadSpritesheet(...)</mark><br>스프라이트를 저장한 변수 이름을 다음과 같이 입력해야 합니다.<br>→ ‘#’ + 변수.id</td></tr><tr><td>loop</td><td>number</td><td>애니메이션 반복 횟수 (  -1: 계속반복)</td></tr></tbody></table>

**예제**

춤추는 블루맨 오브젝트 설치해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FVOxdlRD24vPOOFd95Zuc%2Fblueman%201.png?alt=media&amp;token=f84a5709-f61c-4924-92ef-70d4c98dd734" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fg5UZjUAiXu97SadtX3mK%2FGIF_2022-07-18_%EC%98%A4%ED%9B%84_2-38-22.gif?alt=media&amp;token=0b75a650-b4a8-4159-999c-32238b03d1ce" alt=""><figcaption></figcaption></figure></div>

```jsx
// 한 프레임의 사이즈 48x64
let blueman_dance = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], // 21번째 ~ 38번째 이미지로 애니메이션을 구성
	8
);
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	Map.putObject(5, 5, blueman_dance, { overlap: true });
	Map.playObjectAnimation(5, 5, "#" + blueman_dance.id, -1);
});
```

### playObjectAnimationWithKey

{% hint style="info" %}
Map.playObjectAnimation(key: string, animName: string, repeatCount: number)
{% endhint %}

key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.

<mark style="color:purple;">`Map.putObjectWithKey`</mark>함수가 선행되어야합니다.

스프라이트 이미지를 처음 들어보신다면 [<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!

**파라미터**

<table><thead><tr><th width="153.33333333333326">이름</th><th width="137">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>오브젝트의 키값</td></tr><tr><td>animName</td><td>String</td><td>플레이할 애니메이션의 이름</td></tr><tr><td>repeatCount</td><td>Number</td><td>애니메이션 반복 횟수 ( -1 입력시 무한반복  )</td></tr></tbody></table>

**예제**

춤추는 블루맨 오브젝트 설치해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FVOxdlRD24vPOOFd95Zuc%2Fblueman%201.png?alt=media&amp;token=f84a5709-f61c-4924-92ef-70d4c98dd734" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fg5UZjUAiXu97SadtX3mK%2FGIF_2022-07-18_%EC%98%A4%ED%9B%84_2-38-22.gif?alt=media&amp;token=0b75a650-b4a8-4159-999c-32238b03d1ce" alt=""><figcaption></figcaption></figure></div>

```jsx
var blueman_sprite = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	{
		left: [5, 6, 7, 8, 9],
		up: [15, 16, 17, 18, 19],
		down: [0, 1, 2, 3, 4],
		right: [10, 11, 12, 13, 14],
		dance: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37],
		idle: [0],
	},
	8
);

// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	Map.putObjectWithKey(10, 10, blueman_sprite, {
		overlap: true,
		movespeed: 80, // default: 80
		key: "blueman",
	});
	// "dance" 애니메이션 재생
	Map.playObjectAnimationWithKey("blueman", "dance", -1);
});
```

### moveObject

{% hint style="info" %}
Map.moveObject(x: number, y: number, targetX: number, targetY: number, time: number)
{% endhint %}

x, y 좌표에 위치한 오브젝트를 targetX, targetY로 time 초 동안 움직이는 함수입니다.

해당 좌표 지점에 [<mark style="color:purple;">Map.putObject</mark>](https://app.gitbook.com/o/-MkvEtFn2kFBYSN4_5rX/s/iW553XSGeKCAPpImxXi3/~/changes/bRylDKw8UxLaVWLvuhVV/creator/zep-script-v2_kr/api/scriptmap/methods) 함수가 선행되어야합니다.

스프라이트 이미지를 처음 들어보신다면 [<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!

**파라미터**

<table><thead><tr><th width="189.33333333333331">이름</th><th width="160">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>오브젝트가 위치한 x, y 좌표</td></tr><tr><td>targetX, targetY</td><td>Number</td><td>목적지 x, y 좌표</td></tr><tr><td>time</td><td>Number</td><td>목적지까지 걸리는 시간(초)</td></tr></tbody></table>

**예제**

블루맨 이동시켜보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FFBxHCHGSyMANujnqcCDe%2Fblueman%201.png?alt=media&amp;token=e1778c35-b1ab-4e55-b6f4-c5abafaa07e9" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FuDnqeoKnzGFU681VKL0d%2FmoveObject.gif?alt=media&amp;token=f75f250b-9da4-4b58-9dc3-141b33058c71" alt=""><figcaption></figcaption></figure></div>

```jsx
// 한 프레임의 사이즈 48x64
let blueman_right = App.loadSpritesheet(
	"blueman.png",
	48,
	64,
	[10, 11, 12, 13, 14], // 11 ~ 15번째 이미지로 애니메이션 구성
	8
);
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	Map.putObject(5, 5, blueman_right, { overlap: true });
	Map.playObjectAnimation(5, 5, "#" + blueman_right.id, -1);
	Map.moveObject(5, 5, 10, 5, 3) // (5,5)에서 (10,5)로 3초 동안 이동
});
```

####

### moveObjectWithKey

{% hint style="info" %}
Map.moveObjectWithKey(key: string, targetX: number, targetY: number, usePath:boolean = true)
{% endhint %}

key 값을 가진 오브젝트를 targetX, targetY로 움직이는 함수입니다.

💡 path 파라미터가 true인 경우, 목표 지점이 impassable 타일이거나 도달 할 수 없는 좌표이면 오브젝트가 움직이지 않습니다.

**파라미터**

<table><thead><tr><th width="171.33333333333331">이름</th><th width="134">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>오브젝트의 key 값</td></tr><tr><td>targetX, targetY</td><td>Number</td><td>목적지 x, y 좌표</td></tr><tr><td>usePath</td><td>Boolean</td><td>true일 경우 Impassable 타일을 통과하지 못합니다.<br>false일 경우 Impassable 타일을 무시하고 지나갑니다.</td></tr></tbody></table>

**예제**

moveObejctWithKey 동작 방식

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F9DH0Ku6RiK5ZOeeI8UzG%2FGIF%202022-11-25%20%EC%98%A4%EC%A0%84%2011-50-28.gif?alt=media&amp;token=e30b2cae-0e5b-476c-8408-3b9cbb572644" alt=""><figcaption></figcaption></figure></div>

```jsx
// 한 프레임의 사이즈 48x64
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
    dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
    down_jump: [38],
    left_jump: [39],
    right_jump: [40],
    up_jump: [41],
}, 10);
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown 설명(링크)
App.addOnKeyDown(81, function (player) {
	Map.putObjectWithKey(18, 6, blueman, {
		overlap: true,
		movespeed: 50, // default: 80
		key: "TestBlueMan",
                useDirAnim: true // 방향을 인지해서 애니메이션 재생하는 옵션
                offsetX: -8,
                offsetY: -32,
	});
	Map.moveObjectWithKey("TestBlueMan", 10, 10, true);
});
```

####

### clearAllObjects()

{% hint style="info" %}
Map.clearAllObjects()
{% endhint %}

ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.

**파라미터**

* 없음

**예제**

생성된 모든 오브젝트 삭제하기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FcqPIHzqT5kDiidMH3cba%2Fblueman.png?alt=media&amp;token=81c342e1-e175-4995-bc7c-8a1b0ebf340d" alt=""><figcaption></figcaption></figure></div>

```jsx
let blueman = App.loadSpritesheet("blueman.png");

// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	// 5, 5 좌표부터 blueman 오브젝트 5개 생성
	Map.putObject(5, 5, blueman, {overlap: true});
	Map.putObject(6, 5, blueman, {overlap: true});
	Map.putObject(7, 5, blueman, {overlap: true});
	Map.putObject(8, 5, blueman, {overlap: true});
	Map.putObject(9, 5, blueman, {overlap: true});
});

// w 키를 누르면 동작하는 함수
App.addOnKeyDown(87, function (player) {
	// 스크립트로 생성된 모든 오브젝트 제거
	Map.clearAllObjects();
});
```

####

### getTile

{% hint style="info" %}
Map.getTile(layer: number, x: number, y: number)
{% endhint %}

해당하는 레이어의 x, y 좌표에 있는 타일의 타입 값을 리턴, 타일이 없으면 -1을 리턴합니다.

**파라미터**

<table><thead><tr><th width="134">이름</th><th width="123.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>layer</td><td>Number</td><td>레이어에 해당하는 값<br>0 = Floor (바닥타일),<br>1 = WALL (벽타일),<br>2 = TileEffect (타일효과),<br>3 = Object (오브젝트),<br>5 = TopObject (상단오브젝트),</td></tr><tr><td>x, y</td><td>Number</td><td>x, y 좌표</td></tr></tbody></table>

**예제**

맵에 있는 모든 오브젝트의 타입을 조회하기

```jsx
const LayerType = {
	FLOOR: 0,
	WALL: 1,
	TILE_EFFECTS: 2,
	OBJECTS: 3,
	TOP_OBJECTS: 5,
};
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	let layer = LayerType.OBJECTS;
	for (let x = 0; x < Map.width; x++) {
		for (let y = 0; y < Map.height; y++) {
			let data = Map.getTile(layer, x, y);
			if (data >= 0) {
				App.sayToAll(`(${x},${y})  type: ${data}`);
			}
		}
	}
});
```

### getLocation

{% hint style="info" %}
Map.getLocation(locationName: string)
{% endhint %}

파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.

**파라미터**

<table><thead><tr><th width="166">이름</th><th width="123.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>locationName</td><td>String</td><td>로케이션 이름</td></tr></tbody></table>

**예제**

맵 에디터에서 **타일효과 > 지정 영역** 선택 후 영역 이름을 "**testLocation**" 으로 지정해 맵에 설치한 후 예제 코드를 실행해보세요.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FiNFdSzIwKDrlADluAxS5%2Fimage.png?alt=media&amp;token=e4531f0d-68f4-4ba4-8022-bf3df09aff1a" alt=""><figcaption></figcaption></figure></div>

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
    const targetLocationName = "testLocation";
    const targetLocation = Map.getLocation(targetLocationName);
    
    if(targetLocation) {
        player.sendMessage(`targetLocation placed at (${targetLocation.x}, ${targetLocation.y})`)
    } else {
        player.sendMessage(`${targetLocationName} is not exist!`)
    }
});
```

### getLocationRandom

{% hint style="info" %}
Map.getLocationRandom(locationName: string)
{% endhint %}

파라미터로 전달한 로케이션이 2개이상  존재하는 경우, 무작위로 선택하여 해당 로케이션의 설치 좌표를 리턴합니다.

**파라미터**

<table><thead><tr><th width="242">이름</th><th width="123.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>locationName</td><td>String</td><td>로케이션 이름</td></tr></tbody></table>

**예제**

맵 에디터에서 **타일효과 > 지정 영역** 선택 후 영역 이름을 "**testLocation**" 으로 지정해 맵에 여러개 설치한 후 예제 코드를 실행해보세요.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FiNFdSzIwKDrlADluAxS5%2Fimage.png?alt=media&amp;token=e4531f0d-68f4-4ba4-8022-bf3df09aff1a" alt=""><figcaption></figcaption></figure></div>

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
    const targetLocationName = "testLocation";
    const targetLocation = Map.getLocationRandom(targetLocationName);
    
    if(targetLocation) {
        player.sendMessage(`targetLocation placed at (${targetLocation.x}, ${targetLocation.y})`)
    } else {
        player.sendMessage(`${targetLocationName} is not exist!`)
    }
});
```

### getLocationList

{% hint style="info" %}
Map.getLocationList(locationName: string): Array
{% endhint %}

파라미터로 전달된 로케이션 이름과 일치하는 모든 로케이션의 정보를 배열 형태로 반환합니다.\
각각의 로케이션 정보는 **로케이션 설치 좌표(x, y), 크기(width, height),** 그리고 **라벨 값(label)**&#xC744; 포함하고 있습니다.

**파라미터**

<table><thead><tr><th width="242">이름</th><th width="123.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>locationName</td><td>String</td><td>로케이션 이름</td></tr></tbody></table>

**예제**

맵 에디터에서 **타일효과 > 지정 영역** 선택 후 영역 이름을 "**testLocation**" 으로 지정해 맵에 여러개 설치한 후 예제 코드를 실행해보세요.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FrA5vQIeT9rFA1w8wJCTQ%2Fimage.png?alt=media&amp;token=11413179-0471-47d5-b55f-906c7547c790" alt=""><figcaption></figcaption></figure></div>

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	const targetLocationName = "testLocation";
	const targetLocationList = Map.getLocationList(targetLocationName);

	if (targetLocationList) {
		for (const location of targetLocationList) {
			player.sendMessage(`${JSON.stringify(location)}`);
		}
	} else {
		player.sendMessage(`${targetLocationName} is not exist!`);
	}
});
```

### hasLocation

{% hint style="info" %}
Map.hasLocation(locationName: String): Boolean
{% endhint %}

맵에 해당 로케이션이 있는지 체크하여 true/false 값을 리턴합니다.

**파라미터**

| 이름           | 타입     | 설명      |
| ------------ | ------ | ------- |
| locationName | String | 로케이션 이름 |

**예제**

로케이션이 설치되어있는지 체크하는 키 함수 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	if(Map.hasLocation("test")){
		App.sayToAll("test 로케이션이 설치되어있습니다.")
	} else {
		App.sayToAll("test 로케이션이 설치되어있지 않습니다.")
	}
});
```

### getObjectsByType

{% hint style="info" %}
Map.getObjectsByType(type: numer) : Array
{% endhint %}

&#x20;Type에 해당하는 오브젝트들을 리턴하는 함수입니다.

**파라미터**

| 이름   | 타입     | 설명           |
| ---- | ------ | ------------ |
| type | Number | 오브젝트의 type 값 |

**예제**

모든 타입의 오브젝트 조회하기

```jsx
const ObjectEffectType = {
    NONE = 0,
    SHOW_NOTE = 1,
    SHOW_IMAGE = 2,
    PASSWORD_DOOR = 3,
    LINK_WEBSITE = 4,
    EMBED_WEBSITE = 5,
    API_CALL = 6,
    REPLACE_IMAGE = 7,
    NFT_GIVEAWAY = 8,
    NFT_DOOR = 9,
    POST_MESSAGE = 10,
    SHOW_CHAT_BALLOON = 11,
    FT_DOOR = 12,
    POST_MESSAGE_TO_APP = 13,
    DONATION_DOOR = 14,
    IMPASSABLE = 15,
    STAMP = 16,
    TOKEN_DONATION_DOOR = 17,
    CHANGE_OBJECT = 18,
    ANIMATION = 19,
    NFT_DOOR_MOVE = 20,
    INTERACTION_WITH_ZEPSCRIPTS = 21,
    MULTIPLE_CHOICE = 22,
}
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81,function(player){
    for(let key in ObjectEffectType){
        let type = ObjectEffectType[key];
        let arr = Map.getObjectsByType(type);
        let index = 0;
        for(let obj of arr){
            for(let key in obj){
                App.sayToAll(`${key}: ${obj[key]}`);        
            }
        }
    }
})
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FF2dFIiZPH4pUKucIe10P%2Fimage.png?alt=media&amp;token=970e0c60-51ac-41df-8261-b3e0a0f2856e" alt=""><figcaption><p>오브젝트 조회</p></figcaption></figure></div>

### getTopObjectsByType

{% hint style="info" %}
Map.getTopObjectsByType(type: numer) : array
{% endhint %}

&#x20;Type에 해당하는 상단오브젝트들을 리턴하는 함수입니다.

**파라미터**

| 이름   | 타입     | 설명           |
| ---- | ------ | ------------ |
| type | Number | 오브젝트의 type 값 |

**예제**

모든 타입의  상단오브젝트 조회하기

```jsx
const ObjectEffectType = {
    NONE : 0,
    SHOW_NOTE : 1,
    SHOW_IMAGE : 2,
    PASSWORD_DOOR : 3,
    LINK_WEBSITE : 4,
    EMBED_WEBSITE : 5,
    API_CALL : 6,
    REPLACE_IMAGE : 7,
    NFT_GIVEAWAY : 8,
    NFT_DOOR : 9,
    POST_MESSAGE : 10,
    SHOW_CHAT_BALLOON : 11,
    FT_DOOR : 12,
    POST_MESSAGE_TO_APP : 13,
    DONATION_DOOR : 14,
    IMPASSABLE : 15,
    STAMP : 16,
    TOKEN_DONATION_DOOR : 17,
    CHANGE_OBJECT : 18,
    ANIMATION : 19,
    NFT_DOOR_MOVE : 20,
    INTERACTION_WITH_ZEPSCRIPTS : 21,
    MULTIPLE_CHOICE : 22,
}
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81,function(player){
    for(let key in ObjectEffectType){
        let type = ObjectEffectType[key];
        let arr = Map.getTopObjectsByType(type);
        for(let obj of arr){
            for(let key in obj){
                App.sayToAll(`${key}: ${obj[key]}`);        
            }
        }
    }
})
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FF2dFIiZPH4pUKucIe10P%2Fimage.png?alt=media&amp;token=970e0c60-51ac-41df-8261-b3e0a0f2856e" alt=""><figcaption><p>상단 오브젝트 조회</p></figcaption></figure></div>

### sayObjectWithKey

{% hint style="info" %}
Map.sayObjectWithKey( key: string, message: string )
{% endhint %}

&#x20;key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.

**파라미터**

| 이름      | 타입     | 설명           |
| ------- | ------ | ------------ |
| key     | String | 대상오브젝트의 키 값  |
| message | String | 말풍선에 표시할 메시지 |

**예제**

&#x20;key 값을 가진 오브젝트 위에 말풍선 표시하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fb6S18wK09wyTuvGCAJT0%2Fimage.png?alt=media\&token=b61ef385-7458-41f7-b19b-9b123701bf90)

{% file src="/files/xDD3sIGsXtC9KXWgAltN" %}

<pre><code>const objectKey = "TestBlueMan";
<strong>// Q를 누르면 동작하는 함수
</strong>App.addOnKeyDown(81, function (player) {
	Map.putObjectWithKey(18, 6, blueman, {
		npcProperty: { name: "BlueMan", hpColor: 0x03ff03, hp: 100, hpMax: 100 },
		overlap: true,
		movespeed: 100, 
		key: objectKey, 
		useDirAnim: true
	});
});

// W를 누르면 동작하는 함수
App.addOnKeyDown(87, function(player){
    Map.sayObjectWithKey(objectKey, `I'm BlueMan!`)
})
</code></pre>

### 부록

[<mark style="color:purple;">스프라이트시트 이해하기</mark>](/creator/reference/spritesheet)

[<mark style="color:purple;">기준좌표란?</mark>](/creator/reference/coordinate)

***


# ScriptPlayer

**ScriptApp** 클래스는 아래와 같이 2개의 카테고리로 구성되어있습니다

### [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptplayer/field)

> 플레이어와 관련된 속성 값들 입니다.
>
> 맵 참가자의 이름이나 위치를 조회하거나, 캐릭터의 외형이나 이동 속도 변경 등 다양한 활용한 가능함 함수들이 모여있는 카테고리 입니다.

### [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptplayer/methods)

> 맵에 참가한 플레이어에게 UI나 사운드를 출력하거나, 특정 위치로 이동시키는 등 편리한 기능을 제공하는 함수들이 모여있는 카테고리입니다.


# Field

### 소개

플레이어와 관련된 속성 값들 입니다.

플레이어의 닉네임([<mark style="color:purple;">name</mark>](#id-name)), 위치([<mark style="color:purple;">tileX / tileY</mark>](#tilex-tiley)) 등을 조회하거나 플레이어에게 스팟라이트([<mark style="color:purple;">spotlight</mark>](#spotlight)), 안보임([<mark style="color:purple;">hidden</mark>](#hidden)) 기능을 활성화 할 수 있으며, 캐릭터의 이동속도([<mark style="color:purple;">moveSpeed</mark>](#movespeed)), 이미지([<mark style="color:purple;">sprite</mark>](#sprite))를 변경하거나 플레이어의 저장공간([<mark style="color:purple;">storage</mark>](#storage))을 활용할 수도 있습니다.

🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.

<table><thead><tr><th width="192">이름</th><th>설명</th></tr></thead><tbody><tr><td>🔒 id</td><td>플레이어의 id 값</td></tr><tr><td>name</td><td>플레이어의 닉네임 값</td></tr><tr><td>title</td><td>아바타 닉네임 위에 노란색으로 노출되는 텍스트</td></tr><tr><td>🔒 role</td><td>플레이어의 권한을 나타내는 숫자 값</td></tr><tr><td>🔒 tileX / tileY</td><td>아바타가 서있는 X 좌표 값과 Y 좌표 값</td></tr><tr><td>🔒 dir</td><td>아바타가 바라보고 있는 방향 값</td></tr><tr><td>moveSpeed</td><td>플레이어의 이동속도 값</td></tr><tr><td>sprite</td><td>아바타의 스프라이트 이미지 값</td></tr><tr><td>tag</td><td>필요한 속성 값을 부여 가능한 값 저장 공간</td></tr><tr><td>hidden</td><td>값이 true 이면, 다른 플레이어에게 보이지 않습니다.</td></tr><tr><td>spotlight</td><td>플레이어의 스팟라이트 기능 활성화 여부</td></tr><tr><td>attackType</td><td>플레이어의 공격(Z키) 타입</td></tr><tr><td>attackSprite</td><td>플레이어의 공격(Z키) 이미지 값</td></tr><tr><td>attackParam1</td><td>공격 이미지가 날아가는 거리 값</td></tr><tr><td>attackParam2</td><td>공격 가능 거리 값<br>attackType이 2(원거리 공격)으로 설정 된 경우에만 유효</td></tr><tr><td>🔒 walletAddress</td><td>플레이어의 전자지갑 주소 값</td></tr><tr><td>storage</td><td>스페이스 내의 Player 값 저장 공간(스페이스 한정)</td></tr><tr><td>🔒 isMobile</td><td>플레이어의 모바일 접속여부</td></tr><tr><td>🔒 isMoving</td><td>플레이어가 움직이고 있으면 True, 아니면 False를 반환</td></tr><tr><td>🔒 isJumping</td><td>플레이어가 점프하고 있으면 True, 아니면 False를 반환</td></tr><tr><td>🔒 customData</td><td>URL 쿼리 스트링을 읽어 값을 저장 할 수 있습니다.</td></tr><tr><td>displayRatio</td><td>플레이어의 화면 줌 비율을 조절 할 수 있습니다.</td></tr><tr><td>titleColor</td><td>플레이어의 타이틀 색상</td></tr><tr><td>🔒 emailHash</td><td>플레이어의 이메일 Hash 값</td></tr><tr><td>🔒 isGuest</td><td>비로그인 플레이어인 경우 true 값을 가집니다.</td></tr><tr><td>🔒 language</td><td>플레이어의 브라우저에서 사용하는 언어 설정 값</td></tr><tr><td>🔒 away</td><td>5분 이상 비활성화된 유저인 경우 <code>true</code>값을 가집니다.</td></tr><tr><td>enableFreeView</td><td>맵 둘러보기 허용 여부를 설정할 수 있습니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### id , name

{% hint style="info" %}
player.id : Number player.name : String
{% endhint %}

플레이어의 id, 닉네임 값을 가져옵니다.

**예제**

플레이어가 입장 할 때 플레이어의 id, name 값 출력해보기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
  App.sayToAll(`id: ${player.id} name: ${player.name}`);
})
```

####

### title

{% hint style="info" %}
player.title : String
{% endhint %}

title은 캐릭터 닉네임 위에 노란색으로 노출되는 텍스트입니다.

**예제**

플레이어가 입장 할 때 title 설정해보기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
	player.title = "타이틀";
	player.sendUpdated();
})
```

####

### role

{% hint style="info" %}
player.role : Number
{% endhint %}

role은 플레이어의 권한을 나타내는 숫자 값 입니다.

플레이어의 role에 따라 다음과 같은 값을 출력합니다.

| 일반/비로그인유저 | -1   | 스태프  | 2000 |
| --------- | ---- | ---- | ---- |
| 멤버        | 0    | 관리자  | 3000 |
| 에디터       | 1000 | 맵소유자 | 3001 |

**예제**

권한 값을 채팅 창에 표시해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	App.sayToAll(`${player.name}님의 권한: ${player.role}`)
})
```

####

### tileX, tileY

{% hint style="info" %}
&#x20;player.tileX: Number player.tileY: Number
{% endhint %}

플레이어의 캐릭터가 서있는 X 좌표 값과 Y 좌표 값입니다.

**예제**

내 캐릭터의 x, y 좌표 출력해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	App.sayToAll(`현재 좌표: (${player.tileX}, ${player.tileY})`)
})
```

####

### dir

{% hint style="info" %}
player.dir : Number
{% endhint %}

플레이어의 캐릭터가 바라보고 있는 방향입니다.

캐릭터가 바라보고 있는 방향에 따라 다음과 같은 값을 출력합니다.

<div><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FhasohAG4tL42qlMxMJeO%2FUntitled-1_(1).png?alt=media&amp;token=8f57f3e5-5ae6-4e7f-9174-a052d15f56e6" alt=""><figcaption><p>캐릭터가 바라보는 방향에 따른 dir 값</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZjCNMuZ3t8q0YWJcngUz%2Fimage.png?alt=media&amp;token=59503601-1949-4726-94ae-843d764f0f5b" alt=""><figcaption></figcaption></figure></div>

**예제**

캐릭터가 바라보고 있는 방향을 출력해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	App.sayToAll(`바라보고 있는 방향: ${player.dir}`)
})
```

####

### moveSpeed

{% hint style="info" %}
player.moveSpeed : Number
{% endhint %}

플레이어의 이동속도 값입니다.( 기본 값: 80 )

이동속도 값이 0이면 움직일 수 없습니다.

**예제**

q 키를 누르면 이동속도가 빨라지는 함수 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	player.moveSpeed = 150;
	player.sendUpdated();
})
```

####

### sprite

{% hint style="info" %}
player.sprite : ScriptDynamicResource
{% endhint %}

플레이어 캐릭터의 스프라이트 이미지입니다. ( **null** 입력 시 기본 아바타 이미지로 초기화 )

스프라이트 이미지를 처음 들어보신다면 [<mark style="color:purple;">**스프라이트시트 이해하기**</mark>](/creator/reference/spritesheet) 문서를 확인해보세요!

**예제**

페인트맨 - 블루맨 이미지를 캐릭터 이미지로 적용해보기

{% file src="/files/2JzKM0PwoR2p0RFZXeWm" %}

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F3jJiUrVMK9UZaOUlWGIK%2FUntitled.png?alt=media&amp;token=df8af7b3-cbc5-4a37-9d3b-ae4afa6b5fbd" alt=""><figcaption></figcaption></figure></div>

```jsx
// 한 프레임의 사이즈 48x64
let blueman = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
		dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
		down_jump: [38],
		left_jump: [39],
		right_jump: [40],
		up_jump: [41],
}, 8);
// 플레이어가 입장하면 캐릭터 이미지가 바뀜
App.onJoinPlayer.Add(function(player){
	player.sprite = blueman;
	player.sendUpdated();
});
```

####

### tag

{% hint style="info" %}
&#x20;player.tag: Any
{% endhint %}

tag를 사용해 플레이어에게 필요한 속성 값을 부여 할 수 있습니다.

**예제**

플레이어에게 ‘alive’ 속성 값 부여해보기. ‘alive’ 속성 값은 임의로 생성한 속성 입니다.

쓰이지 않는다면 아무 의미 없는 속성 값 이지만, 게임을 만들 때 플레이어의 생존 여부를 체크하는 중요한 속성으로 사용할 수 있습니다.

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
	player.tag = {
		alive: true,
	};
	player.sendUpdated();

	App.sayToAll(`alive: ${player.tag.alive}`);
});
```

####

### hidden

{% hint style="info" %}
player.hidden: Boolean
{% endhint %}

hidden 값이 true 이면, 해당 플레이어는 다른 플레이어에게 보이지 않습니다.

:warning: hidden인 상태에서 모습은 보이지 않지만, 오디오와 비디오 연결은 됩니다.

**예제**

캐릭터에게 hidden 속성을 부여해서 다른 플레이어에게 안 보이게 해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	player.hidden = true;
	player.sendUpdated();
});
```

####

### spotlight

{% hint style="info" %}
player.spotlight: Boolean
{% endhint %}

플레이어의 스팟 라이트 기능 활성화 여부입니다.

**예제**

q 키를 누르면 스팟 라이트 기능을 ON/OFF 하는 함수 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	if(player.spotlight){
		player.spotlight = false;
	}
	else{
		player.spotlight = true;
	}
	player.sendUpdated();
});
```

### attackType

{% hint style="info" %}
player.attackType : Number
{% endhint %}

플레이어의 공격(Z키) 타입입니다. ( 기본: 0 )

<table><thead><tr><th width="152">attackType</th><th>설명</th></tr></thead><tbody><tr><td>0</td><td>attackType을 설정 하지 않았을 때 기본 공격 타입을 의미합니다.</td></tr><tr><td>2</td><td>원거리 공격 타입입니다. attackParam2와 함께 설정할 때 유효합니다.</td></tr></tbody></table>

**예제**

캐릭터의 attackType 변경해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	player.attackType = 0;
	App.sayToAll(`attackType: ${player.attackType}`);
	player.sendUpdated();
});
```

####

### attackParam1

{% hint style="info" %}
player.attackParam1: Number
{% endhint %}

공격(Z키) 이미지가 날아가는 거리 속성입니다. 공격 가능 거리는 늘어나지 않습니다.

**예제**

attackParam1 변경해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fdede0S8IQAKCyVVQAwht%2FattackParam.gif?alt=media&amp;token=06c6fd15-cf22-4107-af98-81f67a7ff5a7" alt=""><figcaption></figcaption></figure></div>

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	player.attackType = 0;
	player.attackParam1 = 10;
	App.sayToAll(`attackType: ${player.attackType}`);
	App.sayToAll(`attackParam1: ${player.attackParam1}`);
	player.sendUpdated();
});
```

####

### attackParam2

{% hint style="info" %}
player.attackParam2: Number
{% endhint %}

공격 가능 거리 속성입니다. attackType이 원거리 공격으로 설정 된 경우에만 유효합니다.

**예제**

attackParam2 이용해 원거리 공격 설정 해보기.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FprTEIwn4TPHWqN3B6HCl%2FattackType2.gif?alt=media&amp;token=3880a248-13b5-4800-8d3d-94cc905c7ab9" alt=""><figcaption></figcaption></figure></div>

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	player.attackType = 2;
	player.attackParam2 = 5;
	App.sayToAll(`attackType: ${player.attackType}`);
	App.sayToAll(`attackParam2: ${player.attackParam2}`);
	player.sendUpdated();
});
```

####

### attackSprite

{% hint style="info" %}
player.attackSprite : ScriptDynamicResource
{% endhint %}

공격(Z키) 이미지를 지정할 수 있습니다.

**예제**

공격 이미지 적용해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FGQibpEEPhKyKEweYC70D%2FredBoxing.png?alt=media&amp;token=cd6d128d-292e-4b5a-853e-f85487c7c18b" alt=""><figcaption><p>예시 이미지 - 단일 이미지</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FTxBwdtd2r1zPEkmBNUdH%2Fattack_sprite.png?alt=media&amp;token=e0587b8c-2cc1-4cf4-9b6c-d88c2c905dc2" alt=""><figcaption><p>예시 이미지 - 연속된 이미지</p></figcaption></figure></div>

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FDTOtUjo6eQnOOXt36Pc0%2FGIF2.gif?alt=media&amp;token=149c1680-c03b-428e-9982-4f7d549b95dd" alt=""><figcaption><p>연속된 이미지의 경우</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FszYi23ifvm5OuN638Tqw%2FGIF%202023-08-03%20%EC%98%A4%EC%A0%84%2011-00-43.gif?alt=media&amp;token=3b5038e4-b89c-410d-8999-bc3457dda4ab" alt="" width="147"><figcaption><p>방향별 애니메이션 적용</p></figcaption></figure></div>

{% file src="/files/d1s15vxM9bJBN4MkEeMk" %}
방향별 애니메이션 예제 코드
{% endfile %}

```jsx
// 단일 이미지의 경우
let redBoxing = App.loadSpritesheet("redBoxing.png");
App.onJoinPlayer.Add(function (player) {
	player.attackSprite = redBoxing;
	player.sendUpdated();
});
```

```javascript
// 연속된 이미지의 경우
let attackSprite = App.loadSpritesheet("attack_sprite.png", 32, 32, [0, 1, 2], 3);
App.onJoinPlayer.Add(function (player) {
	player.attackSprite = attackSprite;
	player.sendUpdated();
});
```

```
// 방향별 애니메이션을 적용하는경우
let attackSprite = App.loadSpritesheet(
    "animation_sprite.png", // 이미지 파일명
    48, 48, // 이미지 프레임 사이즈
    {
        left: [0, 1, 2, 3, 4, 5], // 좌측을 보고 공격하는 경우
        right: [6, 7, 8, 9, 10, 11], // 우측을 보고 공격하는 경우
        down: [12, 13, 14, 15, 16, 17], // 위를 보고 공격하는 경우
        up: [18, 19, 20, 21, 22, 23], // 아래를 보고 공격하는 경우
    },
    6
);
App.onJoinPlayer.Add(function (player) {
	player.attackSprite = attackSprite;
	player.sendUpdated();
});
```

###

### walletAddress

{% hint style="info" %}
player.walletAddress : String
{% endhint %}

플레이어의 전자지갑 주소입니다.

**예제**

전자지갑 주소 출력해보기 ( 전자지갑 주소가 없을 경우 null이 출력 )

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	App.sayToAll(`${player.walletAddress}`)
});
```

####

### storage

{% hint style="info" %}
player.storage : String
{% endhint %}

스페이스 내의 Player 값 저장 공간 입니다 (스페이스 한정)

**예제**

플레이어 storage에 데이터를 저장하고, 확인해보기

💡 앱을 종료했다가 다시 켜도 저장된 값이 사라지지 않습니다.

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	player.storage = "data";
	player.save(); // storage의 값이 변경되면 player.save()로 변경값을 적용
})

// w 키를 누르면 동작하는 함수
App.addOnKeyDown(87,function(player){
	App.sayToAll(player.storage); // player storage에 저장된 값을 채팅창에 출력
})
```

####

### isMobile

{% hint style="info" %}
&#x20;player.isMobile : Boolean
{% endhint %}

플레이어의 모바일 접속 여부를 true/false 로 출력합니다.

**예제**

플레이어가 입장 할 때 입장메시지에 모바일/PC 표시해보기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){	
	if(player.isMobile){
		App.sayToAll(`${player.name}님이 모바일에서 접속했습니다.`)	
	} else{
		App.sayToAll(`${player.name}님이 PC에서 접속했습니다.`)
	}
});
```

####

### isMoving

{% hint style="info" %}
player.isMoving : Boolean
{% endhint %}

플레이어가 움직이고 있으면 True, 아니면 False를 반환합니다.

**예제**

플레이어의 움직임을 감지해서 메시지 출력해보기.

```jsx
App.onUpdate.Add(function (dt) {
	let _players = App.players;
	for (let i in _players) {
		let p = _players[i];
		if (p.isMoving) {
			App.sayToAll(`${p.name}님이 움직이는 중..`);
		}
	}
});
```

####

### isJumping

{% hint style="info" %}
player.isJumping : Boolean
{% endhint %}

플레이어가 점프하고 있으면 True, 아니면 False를 반환합니다.

**예제**

플레이어의 점프를 감지해서 메시지 출력해보기.

```jsx
App.onUpdate.Add(function (dt) {
	let _players = App.players;
	for (let i in _players) {
		let p = _players[i];
		if (p.isJumping) {
			App.sayToAll(`[시스템] ${p.name}님이 점프 중..`);
		}
	}
});
```

####

### customData

{% hint style="info" %}
&#x20;player.customData : String
{% endhint %}

URL 쿼리스트링으로 전달 받은 값을 저장하는 필드입니다.

**예제**

:fire: [<mark style="color:purple;">URL 쿼리스트링 활용하기</mark>](/creator/reference/url)

### displayRatio

{% hint style="info" %}
player.displayRatio
{% endhint %}

플레이어화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )

**예제**

화면의 줌을 컨트롤 하는 키 만들어보기

```jsx
// q 키를 누르면 동작하는 함수
// 한 번 누르면 화면의 줌 값이 커지고, 한 번 더 누르면 원래대로 돌아오는 키 함수 
App.addOnKeyDown(81,function(player){
	if(player.displayRatio == 1){
		player.displayRatio = 5;
	}else{
		player.displayRatio = 1;
	}
	player.sendUpdated(); //* player의 Field값이 변경되면 player.sendUpdated()로 변경값을 적용
})
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FoTwVGZeYmmk9FGq0ICGL%2Fimage.png?alt=media&amp;token=7e03b3ca-cb92-4e2e-bbc1-c821b45f0dab" alt=""><figcaption><p>displayRatio = 1</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FMhMEH4OyZlydQxqt0JVi%2Fimage.png?alt=media&amp;token=c653f7d4-725d-40d4-bd51-008f4a0d6b50" alt=""><figcaption><p>displayRatio = 5</p></figcaption></figure></div>

### titleColor

{% hint style="info" %}
player.titleColor
{% endhint %}

플레이어의 타이틀 색상을 읽거나 수정 할 수 있습니다.

Enum 값 또는 컬러  Hex Code 값을 입력할 수 있습니다.

<pre><code><strong>사용 가능한 Enum ColorType
</strong><strong>{ WHITE, BLACK, RED, GREEN, BLUE, ORANGE, PURPLE, GRAY, YELLOW, MAGENTA, CYAN }
</strong></code></pre>

**예제**

타이틀 색상 바꿔보기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FwtCUn9JyblxSiQXGHwRL%2Fimage.png?alt=media\&token=23cd40ed-23cd-4518-9251-e35ff6252b3c)

```jsx
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	player.title = "🔸Title🔸";
	// Enum 값으로 입력하는 경우
	player.titleColor = ColorType.CYAN;
	
	// Hex Code로 입력하는 경우 (주석을 해제해주세요)
	// player.titleColor = 0x00FFFF;
	
	player.sendUpdated(); //* player의 Field값이 변경되면 player.sendUpdated()로 변경값을 적용
});

```

### emailHash

{% hint style="info" %}
player.emailHash
{% endhint %}

플레이어의 이메일 Hash 값을 가져옵니다.

**예제**

플레이어의 이메일 Hash 값 출력하기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
  App.sayToAll(`name: ${player.name} emailHash: ${player.emailHash}`);
})
```

###

### isGuest

{% hint style="info" %}
player.isGuest
{% endhint %}

비로그인 플레이어인 경우 true 값을 가집니다.

**예제**

비로그인 유저 입장시 타이틀에 "GUEST" 표시하기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
  if(player.isGuest){
    player.title = "GUEST";
    player.sendUpdated();
  }
})
```

### language

{% hint style="info" %}
player.language
{% endhint %}

브라우저에서 사용하는 언어 설정값에 따라 다음과 같은 값을 가집니다.     &#x20;

> 한국어  "**ko**", 일본어: "**ja**", 영어: "**en**" &#x20;

**예제**

유저 입장시 브라우저에서 사용 하는 언어 설정 값 표시하기

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
  App.sayToAll(player.language);
})
```

### away

{% hint style="info" %}
player.away
{% endhint %}

장시간 비활성화된 유저인 경우 `true`값을 가집니다.

### enableFreeView

{% hint style="info" %}
player.enableFreeView
{% endhint %}

플레이어의 맵 둘러보기 허용 여부를 설정할 수 있습니다.

**예제**

단축키로 플레이어의 맵 둘러보기 허용 여부 설정하기

```jsx
// Q를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	if (player.enableFreeView) {
		player.enableFreeView = false;
	} else {
		player.enableFreeView = true;
	}
	player.sendUpdated();
	player.sendMessage(`player.enableFreeView = ${player.enableFreeView}`, 0x00ffff);
});
```


# Methods

### 소개

UI, 유저 컨트롤, 사운드 등 ZEP에서 일어날 수 있는 전반적인 기능을 제공하는 함수입니다.

플레이어 개인 화면에 UI를 표시, 플레이어를 이동, 플레이어 개인에게 사운드 재생 등 편리한 기능을 제공합니다.

### UI

<table><thead><tr><th width="230">이름</th><th>설명</th></tr></thead><tbody><tr><td>showCenterLabel</td><td>플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.</td></tr><tr><td>showCustomLabel</td><td>플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.<br>text 부분에 <mark style="color:purple;background-color:yellow;">span</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.</td></tr><tr><td>showWidget</td><td>플레이어에게 지정된 위치에 위젯을 불러오는 함수입니다.</td></tr><tr><td>showBuyAlert</td><td>플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.</td></tr><tr><td>hideBuyAlert</td><td>플레이어의 구매 위젯을 숨깁니다.</td></tr><tr><td>sendMessage</td><td>유저 개인에게 채팅 메시지를 보냅니다.</td></tr><tr><td>showPrompt</td><td>플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.</td></tr><tr><td>showConfirm</td><td>플레이어에게 확인창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 콜백함수를 작성할 수 있습니다.</td></tr><tr><td>showAlert</td><td>플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.</td></tr><tr><td>showImageModal</td><td>플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.</td></tr><tr><td>showNoteModal</td><td>플레이어에게 텍스트 창을 보여주는  함수입니다.</td></tr><tr><td>showWidgetResponsive</td><td>위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의하여 위젯을 표시합니다.</td></tr><tr><td>openWebLink</td><td>플레이어에게 웹 URL을 새 창이나 팝업 창으로 열어 보여줍니다.</td></tr><tr><td>showEmbed</td><td><p>URL을 임베드 형태로 표시합니다. </p><p>크기와  위치를 설정할  수 있습니다.</p></td></tr></tbody></table>

### Data Load

<table><thead><tr><th width="194">이름</th><th>설명</th></tr></thead><tbody><tr><td>isEmail</td><td>플레이어의 이메일을 비교하는 함수입니다.</td></tr><tr><td>getLocationName</td><td>플레이어가 서있는 지정 영역의 이름을 출력합니다.</td></tr></tbody></table>

### **User Control**

<table><thead><tr><th width="245">이름</th><th>설명</th></tr></thead><tbody><tr><td>spawnAt</td><td>플레이어의 캐릭터를 지정한 좌표로 이동시키는 함수입니다.</td></tr><tr><td>spawnAtLocation</td><td>플레이어의 캐릭터를 지정 영역으로 이동시키는 함수입니다.</td></tr><tr><td>spawnAtMap</td><td>플레이어를 다른 스페이스 또는 맵으로 이동시키는 함수입니다.</td></tr><tr><td>setCameraTarget</td><td>플레이어의 시점을 입한 좌표나  오브젝트로 이동시킵니다.</td></tr><tr><td>setEffectSprite</td><td>플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.</td></tr><tr><td>playEffectSprite</td><td>플레이어에게 애니메이션 효과를 입력 횟수만큼 반복 재생하고 사라지는 효과를 적용합니다.</td></tr><tr><td>disappearObject</td><td>플레이어 개인 화면 상에서 key 값을 가지는 오브젝트를 사라지게하는 함수입니다.</td></tr></tbody></table>

### Sound

<table><thead><tr><th width="191">이름</th><th>설명</th></tr></thead><tbody><tr><td>playSound</td><td>플레이어에게 사운드 파일을 재생하는 함수입니다.</td></tr><tr><td>playSoundLink</td><td>플레이어에게 사운드 URL을 재생하는 함수입니다.</td></tr><tr><td>stopSound</td><td>플레이어에게 재생중인 사운드를 중지시키는 함수입니다.</td></tr></tbody></table>

### **공통**

<table><thead><tr><th width="197">이름</th><th>설명</th></tr></thead><tbody><tr><td>sendUpdated</td><td>Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.</td></tr><tr><td>save</td><td>Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### 🎨 **UI Methods**

<mark style="background-color:yellow;">**UI 함수 한 눈에 보기**</mark>

<pre class="language-jsx"><code class="lang-jsx">// 플레이어에게 지정된 위치에 해당 text를 3초간 표시
player.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: int = 0, time: int = 3000)

// 모든 플레이어에게 지정된 위치에 해당 text를 3초간 표시, 커스터마이징 가능
player.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: int = 3000);

// 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러옴
player.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget

<strong>// 플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.
</strong>player.showBuyAlert(itemName: string, price: number, callback: function);

// 플레이어의 구매 위젯을 닫습니다.
player.hideBuyAlert();

// 플레이어 개인에게 채팅 메시지를 보냅니다.
player.sendMessage(message: string, color: number = 0xFFFFFF)

// 플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.
player.showPrompt(text: string, function(inputText))

// 플레이어에게 확인창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 콜백함수를 작성할 수 있습니다.
player.showConfirm(text: string, function(result))

// 플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.
player.showAlert(text: string, function())

// 위젯의 상/하/좌/우 여백을 화면 크기에 대한 % 비율로 정의하여 위젯을 표시합니다.
player.showWidgetResponsive(fileName:string, marginTop:number, marginRight:number, marginBottom:number, marginLeft:number)

//플레이어에게 웹 URL을 새 창이나 팝업 창으로 표시합니다.
player.openWebLink(url:string, popup:boolean);

// 지정된 align의 위치에 url 임베드창을 표시합니다.
player.showEmbed(url: string, align: string, width: number, height: number, hasBackdrop: boolean = true)
</code></pre>

### showCenterLabel

{% hint style="info" %}
&#x20;player.showCenterLabel(text: string, color: uint = 0xFFFFFF, bgColor: uint = 0x000000, offset: number = 0, time: number = 3000)
{% endhint %}

해당 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

**파라미터**

<table><thead><tr><th width="137.33333333333331">이름</th><th width="131">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>라벨에 출력할 텍스트</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색을 지정합니다. (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr><tr><td>bgColor</td><td>Uint</td><td>메시지가 출력되는 라벨의 배경색을 지정합니다.<br>값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.</td></tr><tr><td>offset</td><td>number</td><td>offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다.<br>값을 입력하지 않을 경우, 0으로 지정됩니다.</td></tr><tr><td>time</td><td>number</td><td>라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )</td></tr></tbody></table>

**예제**

노란색 라벨을 2초간 출력해보기

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F23JAWwCQKnHATxR0Ni6F%2FUntitled.png?alt=media&amp;token=568dd65a-59ca-42d0-899b-bde7d7720b6a" alt=""><figcaption></figcaption></figure>

```jsx
App.onJoinPlayer.Add(function(player){
	player.showCenterLabel(`${player.name}님 환영합니다.`, 0x000000, 0xFFFF00, 500, 2000); // 노란색 배경, 검정색 글씨로 표시하기
});
```

### showCustomLabel

{% hint style="info" %}
player.showCustomLabel(text: string, color: number = 0xFFFFFF, bgColor: number = 0x000000, offset: number = 0, width = 100, opacity = 0.6, time: number = 3000, option: object = null);
{% endhint %}

모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.

text 부분에 <mark style="color:purple;">`span`</mark> 태그를 넣어 텍스트를 꾸밀 수 있습니다.

**파라미터**

<table><thead><tr><th width="121.33333333333331">이름</th><th width="112">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>라벨에 출력할 텍스트 ( span 태그 허용 )</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색 (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr><tr><td>bgColor</td><td>Uint</td><td>메시지가 출력되는 라벨의 배경색<br>값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.</td></tr><tr><td>offset</td><td>number</td><td>offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다.<br>값을 입력하지 않을 경우, 0으로 지정됩니다.</td></tr><tr><td>width</td><td>number</td><td>라벨의 너비를 n%로 설정하는 값 입니다. (기본 값 100)</td></tr><tr><td>opacity</td><td>number</td><td>라벨의 배경 투명도를 설정하는 값 입니다. (기본 값 0.6, 범위 0 ~ 1)</td></tr><tr><td>time</td><td>number</td><td>라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )</td></tr></tbody></table>

**옵션**

<table><thead><tr><th width="159.33333333333331">이름</th><th width="112">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>라벨에 키 값을 할당하여, 서로 다른 키 값을 가진 라벨은 동시에 표시할 수 있습니다.</td></tr><tr><td>borderRadius</td><td>String</td><td>라벨의 모서리에 둥글기를 설정할 수 있습니다.<br>ex) borderRadius: "8px"</td></tr><tr><td>fontOpacity</td><td>boolean</td><td>false로 설정시 폰트에 투명도가 적용되지 않습니다.</td></tr><tr><td>padding</td><td>String</td><td>라벨 내부에 padding 값을 지정 할 수 있습니다.<br>ex) padding: "8px"</td></tr></tbody></table>

**예제**

[**커스텀 라벨 예제 코드 페이지**](/creator/tutor/custom-label)**를 참고해주세요**

####

### showWidget

{% hint style="info" %}
player.showWidget(fileName: string, align: string, width: number, height: number): ScriptWidget
{% endhint %}

해당 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.

**파라미터**

<table><thead><tr><th width="144.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>align</td><td>String</td><td>위젯을 표시할 위치<br>’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’</td></tr><tr><td>width<br>height</td><td>number</td><td>위젯을 표시할 영역의 가로, 세로 크기(px)</td></tr></tbody></table>

**예제**

초성퀴즈 위젯 따라해보기

{% file src="/files/QcwN5Adj0FO7vy8qXznY" %}

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fjl1Yqs9Kg5lSrc4Jfb4w%2FUntitled%201.png?alt=media&amp;token=7c3ea700-b802-4d3e-9dc1-75c72e156cf2" alt=""><figcaption></figcaption></figure></div>

```jsx
let _widget = null;
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	_widget = player.showWidget("widget.html", "top", 200, 300); // 화면 상단, 200x300 영역에 위젯을 보여줌
	_widget.sendMessage({
		timer: 15,
		answer: "ㅅㅍㅋ",
	});
});
```

### showBuyAlert

{% hint style="info" %}
player.showBuyAlert(itemName: string, price: number, callback: function, payToSpaceOwner: Boolean, option: object)
{% endhint %}

플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.

> 소모된 ZEM은 앱 제작자에게 돌아가며, [내 후원 내역 페이지](https://zep.us/manage/donations)에서 내역을 확인 할 수 있습니다.
>
> ZEM 정산 관련 내용은 [정산가이드 페이지](https://teamzep.notion.site/Settlement-Guide-29794e65dfaa42bc851a15ce2e4bad11)에서 확인하실 수 있습니다.

**파라미터**

<table><thead><tr><th width="194.33333333333331">이름</th><th width="115">타입</th><th>설명</th></tr></thead><tbody><tr><td>itemName</td><td>String</td><td>구매창에 표시할 아이템의 이름</td></tr><tr><td>price</td><td>number</td><td>아이템의 가격 (화폐단위: ZEM)</td></tr><tr><td>callback</td><td>function</td><td><p>구매 성공시 동작할 콜백함수</p><p>구매 성공 여부(<strong><code>success</code></strong>)와 구매정보(<strong><code>buyAlertResult</code></strong>) 데이터를 전달 받으며, buyAlertResult는 환불 기능에 사용됩니다.</p></td></tr><tr><td>payToSpaceOwner</td><td>Boolean</td><td><p>기본 값은 false로 설정되며<br>false인 경우 앱 소유자에게 수익이 전달되고,</p><p>true인 경우 맵 소유자에게 수익이 전달됩니다.</p></td></tr><tr><td>option</td><td>Object</td><td><p>다음 옵션들을 설정 할 수 있습니다.<br><strong><code>message</code></strong> : 구매창에 표시할 텍스트를 설정 할 수 있습니다.</p><p><strong><code>timer</code></strong> : 구매창을 표시할 시간(ms)을 설정할 수 있습니다.</p></td></tr></tbody></table>

**예제**&#x20;

구매정보 저장 및 환불 기능 예제

```jsx
const itemName = "ITEM";

// Q를 누르면 동작하는 함수 - 아이템을 구매하고 player.storage에 구매정보 저장하기
App.addOnKeyDown(81, function (player) {
	let pStorage = JSON.parse(player.storage);
	if (!player.tag) {
		player.tag = {};
	}
	if (pStorage == null) {
		pStorage = {};
	}
	//이미 아이템을 구매했다면 메시지 출력
	if (pStorage[itemName]) {
		player.showCenterLabel(`${itemName}을 이미 구매했습니다.`);
	} else {
		player.showBuyAlert(itemName, 0, function (success, buyAlertResult) {
			if (success) {
				App.sayToAll(`[정보] ${player.name}님이 ${itemName}을 구매했습니다!`);
				pStorage[itemName] = true;
				player.tag.buyAlertResult = buyAlertResult
				player.storage = JSON.stringify(pStorage);
				player.save();
			}
		}, 
		false,// false 인 경우 앱 소유자에게 수익전달, true이면 스페이스 소유자에게 수익 전달
		{
			message: `${itemName} custom message`,//message에 itemName에 해당하는 text가 있을 경우 강조되어 표시됨
			timer: 10000 // 10초 - 구매창 표시시간(ms)
		}
		);
	}
});

// W를 누르면 동작하는 함수 - 환불 기능
App.addOnKeyDown(87, function (player) {
	let pStorage = JSON.parse(player.storage);
	if( pStorage && player.tag.buyAlertResult )
	{	
		if( player.tag.buyAlertResult.Refund() )
		{
			App.sayToAll("===== refund success!");
			pStorage[itemName] = false;
		}
		else {
			App.sayToAll("===== refund failed");
		}
		player.tag.buyAlertResult = null;
		player.storage = JSON.stringify(pStorage);
		player.save();
	}
})
```

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2F5HKVfkpR2djLG2QeCRyB%2Fimage.png?alt=media&amp;token=dba4554f-0ef6-4b16-abcd-f1b2ab94e557" alt=""><figcaption><p>구매위젯 예시</p></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fw3iIh8aaz1xR0H0LhsSt%2FGIF%202023-03-30%20%EC%98%A4%ED%9B%84%205-42-34.gif?alt=media&amp;token=c42b5310-ffdf-4e90-a13a-a742b0830593" alt=""><figcaption><p>timer와 message 옵션을 설정한 경우</p></figcaption></figure></div>

### hideBuyAlert

{% hint style="info" %}
player.hideBuyAlert()
{% endhint %}

플레이어의 구매 위젯을 닫습니다.

**파라미터**&#x20;

없음

### sendMessage

{% hint style="info" %}
&#x20;player.sendMessage(text: string, color: uint = 0xFFFFFF)
{% endhint %}

유저 개인에게 채팅 메시지를 보내는 함수입니다.

**파라미터**

<table><thead><tr><th width="137.33333333333331">이름</th><th width="281">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>라벨에 출력할 텍스트</td></tr><tr><td>color</td><td>Uint</td><td>출력할 글씨의 색을 지정합니다. (HexCode)<br>값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.<br>➡️<a href="https://www.google.com/search?q=COLOR+PICKER&#x26;sxsrf=ALiCzsbc_6XvOn9SiJdEBkLmfLurJ4tvOA%3A1658153265956&#x26;ei=MWnVYrX3Ocv4wAOXk6-wBg&#x26;ved=0ahUKEwj105mjzoL5AhVLPHAKHZfJC2YQ4dUDCA4&#x26;uact=5&#x26;oq=COLOR+PICKER&#x26;gs_lcp=Cgdnd3Mtd2l6EAMyBAgjECcyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQ6CwgAEIAEELEDEIMBOhEILhCABBCxAxCDARDHARDRAzoRCC4QgAQQsQMQgwEQxwEQrwE6CAgAEIAEELEDOhAIABCABBCHAhCxAxCDARAUOgoIABCABBCHAhAUSgQIQRgASgQIRhgAUABYhRVgvxZoAHABeACAAYwBiAHmC5IBBDAuMTKYAQCgAQHAAQE&#x26;sclient=gws-wiz"><mark style="color:purple;">구글 색상 선택 도구</mark></a></td></tr></tbody></table>

**예제**

플레이어 개인에게만 보이는 환영메시지 출력하기.

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FVyjGG285QjncBQhVtU4r%2Fimage.png?alt=media&amp;token=4fb14e75-8736-4035-a32a-84ec05db5044" alt=""><figcaption></figcaption></figure></div>

```javascript

App.onJoinPlayer.Add(function(player){
    player.sendMessage(`${player.name}님 어서오세요!\nhttps://docs-kr.zep.us/ 링크 클릭시 가이드로 연결됩니다.`,0xffffff);
});

```

###

### showPrompt

{% hint style="info" %}
&#x20;player.showPrompt(title: string, function(inputText), option = {})
{% endhint %}

플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.

**파라미터**

<table><thead><tr><th width="174.33333333333331">이름</th><th width="281">타입</th><th>설명</th></tr></thead><tbody><tr><td>title</td><td>String</td><td>입력창의 타이틀</td></tr><tr><td>inputText</td><td>String</td><td>플레이어가 입력한 텍스트</td></tr></tbody></table>

**옵션**

값을 입력하지 않아도 default 값이 적용됩니다.

<table><thead><tr><th width="174.33333333333331">이름</th><th width="174">타입</th><th>설명</th></tr></thead><tbody><tr><td>content</td><td>String</td><td>입력창 위에 출력할 텍스트 <strong>(Default: null)</strong></td></tr><tr><td>confirmVariant</td><td>'primary' | 'alert'</td><td>confirm 버튼의 색상 <strong>(Default: "primary")</strong><br>- 'primary': 푸른색,<br>- 'alert': 붉은색 </td></tr><tr><td>cancelText</td><td>String</td><td>cancel 버튼의 텍스트 <strong>(Default: "취소")</strong></td></tr><tr><td>confirmText</td><td>String</td><td>confirm 버튼의 텍스트 <strong>(Default: "확인")</strong></td></tr><tr><td>placeholder</td><td>String</td><td>input placeholder 텍스트 <strong>(Default: null)</strong></td></tr><tr><td>textType</td><td>'text' | 'password'</td><td><p>입력 타입 <strong>(Default: "text")</strong></p><p>- 'text': 입력 값을 텍스트로 표시<br>- 'password': 입력 값을 *로 표시</p></td></tr></tbody></table>

**예제**

"1234"를 입력하는 경우 "Correct" 메시지 출력해보기

![붉은 버튼 적용](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FiHJfYYzNVibQKJEq4nYs%2Fimage.png?alt=media\&token=d3abf9ff-c270-4bfd-90f1-0627e6b51d6d)![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FNgZT6uEVXuQ0OmHcfNVx%2Fimage.png?alt=media\&token=7eeae8c8-ccc2-4493-abd3-66177eb26855)

```javascript
// Q키를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, function (player) {
    player.showPrompt("Password", function (inputText) {
            if (inputText == "1234") {
                player.showCenterLabel("Correct");
            } else {
                player.showCenterLabel("Incorrect");
            }
        },
        {
            content: 'Description', // 설명
            confirmVariant: 'primary', // 확인 버튼 색상 'primary' | 'alert'
            cancelText: 'custom cancel', // 취소 버튼 텍스트
            confirmText: 'custom confirm',// 확인 버튼 텍스트
            placeholder: 'Custom Placeholder',// 입력칸의 placeholder
            textType: 'password' // 입력 텍스트의 표시 형식 'text' | 'password'
        }
    );
});
```

### showConfirm

{% hint style="info" %}
&#x20;player.showConfirm(text: string, function(result), option = {})
{% endhint %}

플레이어에게 확인창을 보여주고, 플레이어가 확인 버튼을 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다. cancel을 누를 경우에는 callback 함수가 동작하지 않습니다.

**파라미터**

<table><thead><tr><th width="174.33333333333331">이름</th><th width="281">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>확인창에 출력할 텍스트</td></tr><tr><td>result</td><td>Boolean</td><td>플레이어가 OK를 누르는 경우 true</td></tr></tbody></table>

**옵션**

값을 입력하지 않아도 default 값이 적용됩니다.

<table><thead><tr><th width="174.33333333333331">이름</th><th width="174">타입</th><th>설명</th></tr></thead><tbody><tr><td>content</td><td>String</td><td>content 영역에 출력할 텍스트 <strong>(Default: null)</strong></td></tr><tr><td>confirmVariant</td><td>'primary' | 'alert'</td><td>confirm 버튼의 색상 <strong>(Default: 'primary')</strong><br>- primary: 푸른색,<br>- alert: 붉은색</td></tr><tr><td>cancelText</td><td>String</td><td>cancel 버튼의 텍스트 <strong>(Default: "취소")</strong></td></tr><tr><td>confirmText</td><td>String</td><td>confirm 버튼의 텍스트 <strong>(Default: "확인")</strong></td></tr></tbody></table>

**예제**

확인 버튼을 누른 경우 채팅창에 텍스트 출력하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FhUhtHAEKTgXIKOsuNB5d%2Fimage.png?alt=media\&token=a1bd0fb9-6a6a-4266-b1ec-26664c89b4be)![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FQX03JO5cXr1uRDFeF3DP%2Fimage.png?alt=media\&token=53ebb2e6-4505-4eb4-a2eb-bb90deca6239)

```javascript
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, function (player) {
    player.showConfirm("Confirm", function (result) {
            if (result) {
                App.sayToAll("ok");
            }
        },
        {
            content: 'Description', // 설명
            confirmVariant: 'alert', // 확인 버튼 색상 'primary' | 'alert';
            cancelText: 'custom cancel', // 취소 버튼 텍스트
            confirmText: 'custom confirm',// 확인 버튼 텍스트
        }
    );
});
```

### showAlert

{% hint style="info" %}
&#x20;player.showAlert(text: string, function(), option = {})
{% endhint %}

플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.

**파라미터**

<table><thead><tr><th width="174.33333333333331">이름</th><th width="281">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>경고창에 출력할 텍스트</td></tr></tbody></table>

**옵션**

값을 입력하지 않아도 default 값이 적용됩니다.

<table><thead><tr><th width="174.33333333333331">이름</th><th width="174">타입</th><th>설명</th></tr></thead><tbody><tr><td>content</td><td>String</td><td>content 영역에 출력할 텍스트 <strong>(Default: null)</strong></td></tr><tr><td>confirmText</td><td>String</td><td>confirm 버튼의 텍스트 <strong>(Default: "확인")</strong></td></tr></tbody></table>

**예제**

버튼을 누른 경우 채팅창에 텍스트 출력하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Fvd0fnXwZu3n4tHvEfrpP%2Fimage.png?alt=media\&token=0eefef61-2c43-4a25-8056-df92a62a0459)

```javascript
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
    player.showAlert("Alert", function () {
            App.sayToAll("ok");
        },
        {
            content: 'Description', // 설명
            confirmText: 'custom confirm',// 확인 버튼 텍스트
        }
    );
});
```

### showWidgetResponsive

{% hint style="info" %}

```
player.showWidgetResponsive(fileName:string, marginTop:number, marginRight:number, marginBottom:number, marginLeft:number)
```

{% endhint %}

위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의하여 위젯을 표시합니다.

화면의 크기가 여백을 포함한 위젯 영역보다 작아질 경우, 위젯의 크기가 비례하여 작아집니다.

**파라미터**

<table><thead><tr><th width="206.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>margin top/left/right/bottom</td><td>String</td><td>상/하/좌/우 여백의 % 값</td></tr></tbody></table>

**예제**

화면 크기를 줄이는 경우 위젯의 크기 변화

{% file src="/files/76cG0Pc8s9ezBXYSShEr" %}

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FZv7Z4CrBhEqVqQx26Zcc%2FwidgetResponsive.gif?alt=media&amp;token=aaba9d89-a2da-4bbf-a56f-ae55452762af" alt=""><figcaption></figcaption></figure>

```javascript
App.onJoinPlayer.Add(function (player) {
	player.tag = {};
});

// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	player.tag.widget = player.showWidgetResponsive("result.html", 15, 15, 15, 15);
	player.tag.widget.onMessage.Add(function (player, data) {
		if (data.type == "close") {
			player.tag.widget.destroy();
			player.tag.widget = null;
		}
	});
});
```

### openWebLink

{% hint style="info" %}

```
player.openWebLink(url:string, popup:boolean=false)
```

{% endhint %}

플레이어에게 웹 URL을 새 창이나 팝업 창으로 표시합니다.

**파라미터**

<table><thead><tr><th width="206.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>연결할 웹 url 주소</td></tr><tr><td>popup</td><td>boolean</td><td>true 인 경우, url 창을 팝업 형태로 표시합니다.</td></tr></tbody></table>

**예제**

openWebLink 팝업으로 여는 경우

```javascript
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	player.openWebLink("https://docs-kr.zep.us", true);
});
```

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FN6wofV6XdTki3pHiIZnf%2Fimage.png?alt=media&amp;token=9c595bf5-329f-43c1-afcc-fe4eb637ad73" alt=""><figcaption><p>팝업으로 url 창을 표시</p></figcaption></figure>

### showEmbed

{% hint style="info" %}
player.showEmbed(url: string, align: string, width: number, height: number, hasBackdrop: boolean = true)
{% endhint %}

해당 플레이어에게 지정된 align의 위치에 url 임베드  화면을 표시하는 함수입니다.

**파라미터**

<table><thead><tr><th width="153.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>웹 url 주소</td></tr><tr><td>align</td><td>String</td><td>임베드를 표시할 위치<br>‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’</td></tr><tr><td>width<br>height</td><td>number</td><td>임베드 영역의 가로, 세로 크기(px)</td></tr><tr><td>hasBackdrop</td><td>boolean</td><td>true일 경우 임베드의 바깥 배경에 그림자를 표시합니다.</td></tr></tbody></table>

**예제**

url 임베드창 표시하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FogjzYvkde1o6MKGwgbep%2Fimage.png?alt=media\&token=33563c5a-f0cf-4a09-b467-5d9c3ff59e88)![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FwarQ9VEmfiuxZ2ajc7Db%2Fimage.png?alt=media\&token=911451c7-3e22-4ba5-925d-fff8d09cc8f4)

```javascript
// Q를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	player.showEmbed("https://youtu.be/ztuTrpXJyks", "middle", 900, 600, true);
});
```

### showImageModal

{% hint style="info" %}
player.showImage(url: string)
{% endhint %}

플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.

**파라미터**

<table><thead><tr><th width="136.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>url</td><td>String</td><td>표시할이미지 url</td></tr></tbody></table>

**예제**

이미지 모달창 표시하기

<figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2Ff23h7qlPgo6KxGZomId3%2Fimage.png?alt=media&amp;token=0e9c7f0f-94a7-4c97-8c9c-309062306650" alt=""><figcaption></figcaption></figure>

```javascript
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
    player.showImageModal("https://cdn-static.zep.us/static/images/thumbnail.png");
});
```

### showNoteModal

{% hint style="info" %}
player.showNoteModal(text: string)
{% endhint %}

플레이어에게 텍스트 창을 보여주는  함수입니다.

**파라미터**

<table><thead><tr><th width="136.33333333333331">이름</th><th width="104">타입</th><th>설명</th></tr></thead><tbody><tr><td>text</td><td>String</td><td>표시할 텍스트</td></tr></tbody></table>

**예제**

텍스트 창 표시하기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FBMgfBHQSNYAC5QMe2eyg%2Fimage.png?alt=media\&token=247d944c-8f04-4f40-8f51-117189adedc0)

```javascript
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
    player.showNoteModal("Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatibus autem nisi soluta commodi a eius distinctio facilis est ea ullam. Dolorum a quis, impedit nisi voluptates magni architecto odit amet.");
});
```

## 💻 Data Load Methods

<mark style="background-color:yellow;">**Data Load Methods 함수 한 눈에 보기**</mark>

```jsx
// 지정한 이메일과 플레이어의 이메일을 비교
player.isEmail(email: string): boolean

// 플레이어가 서있는 구역이름을 호출
player.getLocationName(): string
```

### isEmail

{% hint style="info" %}
player.isEmail(email: string): boolean
{% endhint %}

해당 플레이어의 이메일이 파라미터 값과 같다면 true, 아니면 false를 리턴합니다.

**파라미터**

<table><thead><tr><th width="125.33333333333331">이름</th><th width="132">타입</th><th>설명</th></tr></thead><tbody><tr><td>email</td><td>String</td><td>비교할 이메일 텍스트</td></tr></tbody></table>

**예제**

플레이어의 이메일이 지정한 텍스트와 같은지 비교해보기

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
	let check = player.isEmail("supercat@supercat.co.kr");
	App.sayToAll(`이메일 일치 여부: ${check}`)
})
```

### getLocationName

{% hint style="info" %}
player.getLocationName : string
{% endhint %}

플레이어가 서있는 지정 영역의 이름을 출력합니다.

지정 영역은 **‘맵에디터 > 타일효과’** 에서 설정 할 수 있습니다.

**파라미터**

* 없음

**예제**

캐릭터가 서있는 타일의 영역이름 출력해보기

→ 지정 영역 설정이 안되있다면 공백으로 출력됩니다

```jsx
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
	App.sayToAll(`현재 서있는 구역: ${player.getLocationName()}`)
})
```

###

## 🙍‍♂️ **User Control**

<mark style="background-color:yellow;">**User Control 함수 한 눈에 보기**</mark>

```jsx
// 플레이어를 해당 좌표로 소환
player.spawnAt(tileX: int ,tileY: int, dir: int = 0)

// 플레이어를 해당 구역으로 소환
player.spawnAtLocation(name: string ,dir:int = 0)

// 플레이어를 해당 스페이스 해당 맵으로 이동시키기
player.spawnAtMap(spaceHashID string, mapHashID:string = null)
```

### spawnAt

{% hint style="info" %}
player.spawnAt(tileX: int ,tileY: int, dir: int = 0)
{% endhint %}

플레이어의 캐릭터를 tileX, tileY 좌표로 지정한 방향을 바라보게 이동시킵니다.

**파라미터**

<table><thead><tr><th width="134.33333333333331">이름</th><th width="152">타입</th><th>설명</th></tr></thead><tbody><tr><td>tileX<br>tileY</td><td>number</td><td>플레이어를 이동시킬 x, y 좌표 값</td></tr><tr><td>dir</td><td>number</td><td>- 캐릭터가 바라볼 방향<br>• 왼쪽 : 1      • 위쪽 : 2        • 오른쪽 : 3    • 아래쪽 : 4<br>• 왼쪽위 : 5  • 왼쪽아래 : 6 • 오른쪽위: 7  • 오른쪽아래: 8</td></tr></tbody></table>

**예제**

입장하는 플레이어를 지정한 좌표로 이동시키기

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	player.spawnAt(5, 5, 1); // 플레이어를 5,5 위치로 왼쪽 방향을 바라보게 이동시키기
});
```

### spawnAtLocation

{% hint style="info" %}
player.spawnAtLocation(name: string, dir:int = 0)
{% endhint %}

플레이어의 캐릭터를 name에 해당하는 지정 영역으로 지정한 방향을 바라보게 이동시킵니다.

**파라미터**

<table><thead><tr><th width="135.33333333333331">이름</th><th width="132">타입</th><th>설명</th></tr></thead><tbody><tr><td>name</td><td>String</td><td>플레이어를 이동시킬 지정 영역의 이름</td></tr><tr><td>dir</td><td>number</td><td>- 캐릭터가 바라볼 방향<br>• 왼쪽 : 1      • 위쪽 : 2         • 오른쪽 : 3    • 아래쪽 : 4<br>• 왼쪽위 : 5  • 왼쪽아래 : 6  • 오른쪽위: 7  • 오른쪽아래: 8</td></tr></tbody></table>

**예제**

입장하는 플레이어를 지정 영역으로 이동시키기

:warning: 같은 이름의 지정 영역이 여러 곳 있다면 해당 영역들 중 한 곳으로 랜덤 이동합니다.

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
// 플레이어를 "test"라는 이름의 지정영역으로 왼쪽 방향을 바라보게 소환하기
	player.spawnAtLocation("test", 1); 
});
```

### spawnAtMap

{% hint style="info" %}
player.spawnAtMap(spaceHashID string, mapHashID:string)
{% endhint %}

플레이어를 해당 스페이스 해당 맵으로 이동시킵니다.

**파라미터**

| 이름          | 타입     | 설명                         |
| ----------- | ------ | -------------------------- |
| spaceHashID | String | 이동할 스페이스의 spaceHashID      |
| mapHashID   | String | <p>이동할 맵의<br>mapHashID</p> |

**예제**

입장하는 플레이어를 ZEP 튜토리얼 맵으로 이동시키기 ([ <mark style="color:purple;">스페이스와 맵 이해하기</mark>](/creator/reference/undefined) )

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	// 플레이어를 ZEP 튜토리얼 맵으로 이동시키기
	player.spawnAtMap("65jeBA", "2YvXMJ");
});
```

### setCameraTarget

{% hint style="info" %}
**\[1]** player.setCameraTarget( tileX: Number, tileY: Number, time: Number )

**\[2]** player.setCameraTarget( key: String, time: Number )
{% endhint %}

**\[1]** 플레이어의 시점을 지정된 좌표로 중심 이동시킵니다.

**\[2]** 플레이어의 시점을 특정 오브젝트로 중심 이동시킵니다.

**파라미터**

| 이름    | 타입     | 설명                          |
| ----- | ------ | --------------------------- |
| tileX | Number | x좌표                         |
| tileY | Number | y좌표                         |
| key   | String | 오브젝트의 키 값                   |
| time  | Number | 시점이 목표 지점까지 이동하는데 걸리는 시간(초) |

**예제**

**\[1]** 플레이어가 보고 있는 화면의 중심을 입력한 좌표로 이동 및  초기화 시키기

```jsx
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
    player.setCameraTarget(0, 0, 2); // 플레이어의 시점을 [0,0] 좌표로 2초동안 이동시킵니다.
    player.sendUpdated(); 
});

// W를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.W, function (player) {
    player.setCameraTarget("");
    player.sendUpdated();
});
```

**\[2]** 플레이어의 시점을  오브젝트로 이동 및 초기화 시키기

{% file src="/files/uhsb0mVdJOXD9HmcUcYE" %}
예제 파일
{% endfile %}

```jsx

let sprite = App.loadSpritesheet('blueman.png', 48, 64, {
    left: [5, 6, 7, 8, 9], // 좌방향 이동 이미지
    up: [15, 16, 17, 18, 19],
    down: [0, 1, 2, 3, 4],
    right: [10, 11, 12, 13, 14],
    dance: [20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],
    down_jump: [38],
    left_jump: [39],
    right_jump: [40],
    up_jump: [41],
},5);

App.onJoinPlayer.Add(function (player) {
    App.runLater(function () {
        Map.putObjectWithKey(player.tileX, player.tileY, sprite, {
            overlap: true,
            movespeed: 50,
            key: "TestBlueMan",
            useDirAnim: true
        });
        Map.moveObjectWithKey("TestBlueMan", Map.width - 1, player.tileY, false);
    }, 2);
});

// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
    player.setCameraTarget("TestBlueMan",1);
    player.sendUpdated();
});

// W를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.W, function (player) {
    player.setCameraTarget("");
    player.sendUpdated();
});
```

### setEffectSprite

{% hint style="info" %}
player.setEffectSprite(resource: ScriptDynamicResource, offsetX: Number, offsetY: Number, type: Number)
{% endhint %}

플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.

**파라미터**

| 이름       | 타입                    | 설명                                                                 |
| -------- | --------------------- | ------------------------------------------------------------------ |
| resource | ScriptDynamicResource | 스크립트에 로드한 이미지 객체                                                   |
| offsetX  | Number                | px 단위로 x 축 방향의 오프셋을 설정할 수 있는 속성                                    |
| offsetY  | Number                | px 단위로 y 축 방향의 오프셋을 설정할 수 있는 속성                                    |
| type     | `0` \| `1`            | <p>설정타입<br>- <code>0</code>: 배경 설정<br>- <code>1</code> : 전경 설정</p> |

**사용 가능한 이펙트 애니메이션 키 값**

캐릭터의 움직임에 맞춰 애니메이션을 재생할 수 있도록 설정이 가능합니다. ([<mark style="color:purple;">**참고 문서**</mark>](/creator/reference/spritesheet))

{% hint style="warning" %}
캐릭터의 움직임에 대응하는 애니메이션이 정의되어 있지 않은 경우 애니메이션이 재생되지 않습니다.
{% endhint %}

```
down_idle // 정면에서 대기 상태
down // 정면에서 이동 중
left_idle // 왼쪽 방향에서 대기 상태
left // 왼쪽 방향으로 이동 중
right_idle // 오른쪽 방향에서 대기 상태
right // 오른쪽 방향으로 이동 중
up_idle // 위쪽 방향에서 대기 상태
up // 위쪽  방향으로 이동 중
dance // 춤추는 모션
down_jump, // 정면에서 점프하는 모션
left_jump, // 왼쪽 방향으로 점프하는 모션
right_jump, // 오른쪽 방향으로 점프하는 모션
up_jump, // 위쪽 방향으로 점프하는 모션
down_sit, // 정면 방향으로 앉아있는 모션
left_sit, // 왼쪽 방향에서 앉아있는 모션
right_sit, // 오른쪽 방향에서 앉아있는 모션
up_sit // 후면에서 앉아있는 모션
down_attack // 정면 방향 공격 모션
left_attack // 왼쪽 방향 공격 모션
right_attack // 오른쪽 방향 공격 모션
up_attack // 위쪽 방향 공격 모션
```

**예제**

플레이어 배경이미지 설정해보기

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FJvmqTlEGyIFbJJRfWl55%2FGIF%202023-08-03%20%EC%98%A4%EC%A0%84%2011-13-36.gif?alt=media\&token=290044da-26a1-47fc-b967-b7d8ba1ba7b4)

{% file src="/files/JE9Hlbj9xQ5FIaZIHCvy" %}
예제 코드
{% endfile %}

```jsx
let effect = App.loadSpritesheet(
	"effect_sprite.png",
	32,
	32,
	{
		down_idle: [0, 1, 2, 3],
		down_attack: [0, 1, 2, 3],
		down: [0, 1, 2, 3],

		left_idle: [4, 5, 6, 7],
		left_attack: [4, 5, 6, 7],
		left: [4, 5, 6, 7],

		right_idle: [8, 9, 10, 11],
		right_attack: [8, 9, 10, 11],
		right: [8, 9, 10, 11],

		up_idle: [0, 1, 2, 3],
		up_attack: [0, 1, 2, 3],
		up: [0, 1, 2, 3],

		dance: [0, 1, 2, 3],

		down_jump: [0],
		left_jump: [4],
		right_jump: [8],
		up_jump: [0],

		down_sit: [0, 1, 2, 3],
		left_sit: [4, 5, 6, 7],
		right_sit: [8, 9, 10, 11],
		up_sit: [0, 1, 2, 3],
	},
	5
);

// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
	player.setEffectSprite(effect, -32, -30, 0); // 배경 이미지 설정
	player.sendUpdated();
});

// W를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.W, function (player) {
	player.setEffectSprite(null); // 배경 이미지 삭제
	player.sendUpdated();
});
```

### playEffectSprite

{% hint style="info" %}
player.playEffectSprite(resource: ScriptDynamicResource, repeatNum, offsetX: Number, offsetY: Number)
{% endhint %}

플레이어에게 애니메이션 효과를 `repeatNum` 횟수만큼 반복 재생하고 사라지는 효과를 적용합니다.

**파라미터**

<table><thead><tr><th>이름</th><th width="219.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>resource</td><td>ScriptDynamicResource</td><td>스크립트에 로드한 이미지 객체</td></tr><tr><td>repeatNum</td><td>Number</td><td>애니메이션 재생을 반복할 횟수</td></tr><tr><td>offsetX</td><td>Number</td><td>px 단위로 x 축 방향의 오프셋을 설정할 수 있는 속성</td></tr><tr><td>offsetY</td><td>Number</td><td>px 단위로 y 축 방향의 오프셋을 설정할 수 있는 속성</td></tr></tbody></table>

#### 예제

1회 재생 후 사라지는 이펙트 효과

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FSW2fMz7SUHwmRP1IJVZ6%2FplayEffectSprite.gif?alt=media&amp;token=09fbce2b-de3a-4bd2-8342-7e0e7cadb79d" alt=""><figcaption></figcaption></figure></div>

{% file src="/files/o1UKgPCYQFuWqaB3s4ud" %}

```typescript
// 단일 애니메이션으로 정의해 사용합니다.
let effect = App.loadSpritesheet("effect_sprite.png", 32, 32, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 5);

// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
	player.playEffectSprite(effect, 1, -32, -30);
});
```

### disappearObject

{% hint style="info" %}
player.disappearObject(key: String)
{% endhint %}

플레이어 개인 화면 상에서 key 값을 가지는 오브젝트를 사라지게하는 함수입니다.

**파라미터**

<table><thead><tr><th>이름</th><th width="219.33333333333331">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>사라지게할 오브젝트의 key 값</td></tr></tbody></table>

#### 예제

오브젝트와 상호작용 시 개인에게 사라지게 하기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FtPeQ7yFWxucrv9bYi7XT%2FGIF%202024-03-05%20%EC%98%A4%EC%A0%84%2011-47-35.gif?alt=media&amp;token=08af108c-d468-4117-a5bd-f66afcd3ac3a" alt=""><figcaption></figcaption></figure></div>

{% file src="/files/lItW1NXJ0WbC9joooRh4" %}

```javascript
let blueman = App.loadSpritesheet("blueman.png");

App.onJoinPlayer.Add(function (player) {
	Map.putObjectWithKey(player.tileX + 1, player.tileY, blueman, {
		type: 21,
		impassable: true,
		key: "objectKey",
	});
});

App.onTriggerObject.Add(function (player, layerID, x, y, key) {
	if(key){
	    player.disappearObject(key);
	}
});

```

### putIndividualObject

{% hint style="info" %}
player.putObjectWithKey(x: number, y: number, dynamicResource: ScriptDynamicResource, option: JsValue)
{% endhint %}

지정한 좌표에 플레이어에게만  보이는 오브젝트를 설치하는 함수입니다. ( 기준 좌표: Left Top )

`dynamicResource` 파라미터에 `null` 입력시 해당 좌표의 오브젝트를 삭제할 수 있습니다.

**관련 문서:** [오브젝트 npcProperty](/creator/reference/npcproperty)

**파라미터**

<table><thead><tr><th width="183.33333333333331">이름</th><th width="214">타입</th><th>설명</th></tr></thead><tbody><tr><td>x, y</td><td>Number</td><td>오브젝트를 놓을 x, y 좌표</td></tr><tr><td>dynamicResource</td><td>ScriptDynamicResource</td><td>App.loadSpritesheet 함수로 로드한 이미지</td></tr></tbody></table>

**옵션 (option)**

값을 입력하지 않아도 Default 값이 적용됩니다

<table><thead><tr><th width="183.33333333333331">이름</th><th width="154">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>String</td><td>오브젝트의 키 값<br><strong>(Default: null)</strong></td></tr><tr><td>moveSpeed</td><td>Number</td><td><p>오브젝트의 이동속도</p><p><strong>(Default: 80)</strong></p></td></tr><tr><td>useDirAnim</td><td>Boolean</td><td>방향을 인지해서 애니메이션을 재생하는 옵션<br><strong>(Default: false)</strong></td></tr><tr><td>impassable</td><td>Boolean</td><td>오브젝트 통과불가 옵션<br><strong>(Default: false)</strong></td></tr><tr><td>offsetX</td><td>Number</td><td>오브젝트 이미지의 배치 기준점의 X좌표<br><strong>(Default: 0)</strong></td></tr><tr><td>offsetY</td><td>Number</td><td>오브젝트 이미지의 배치 기준점의 Y좌표<br><strong>(Default: 0)</strong></td></tr><tr><td>npcProperty</td><td>Object</td><td>npcProperty 관련 내용은 <a href="/creator/reference/npcproperty"><strong>문서</strong></a>를 참고해주세요.<br><strong>(Default: null)</strong></td></tr><tr><td>topObject</td><td>Boolean</td><td>true 로 설정하면 오브젝트가 상단오브젝트로  설치됩니다.<br><strong>(Default: false)</strong></td></tr></tbody></table>

**예제**

키 값을 가진 블루맨 오브젝트 생성해보기

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FjWShei1tdfQfaQWGAZJF%2Fblueman.png?alt=media&amp;token=802a123f-07c0-4a2a-8291-1d997caaac9e" alt=""><figcaption></figcaption></figure></div>

```bash
let blueman = App.loadSpritesheet("blueman.png");
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
	player.putIndividualObject(18, 6, blueman, {
		type:21, // zep-script 상호작용 오브젝트
		overlap: true, // 충돌 이벤트 감지
		movespeed: 100, // 이동속도, 기본값: 80
		key: "TestBlueMan", // 키 값
		impassalbe:true, // 통과 불가 오브젝트 설정
		useDirAnim: true, // 방향을 인지해서 애니메이션을 재생하는 옵션
        	offsetX: -8, // 오브젝트 이미지의 배치 기준점의 X좌표를 조정
              	offsetY: -32,// 오브젝트 이미지의 배치 기준점의 Y좌표를 조정
	});
});

// w 키를 누르면 동작하는 함수
App.addOnKeyDown(87, function (player) {
	// 오브젝트 삭제
	player.putIndividualObject(18, 6, null, {
		key: "TestBlueMan", // 키 값
	});
});
```

## 🔉 Sound Methods

<mark style="background-color:yellow;">**Sound Methods 함수 한 눈에 보기**</mark>

```jsx
// 플레이어에게 사운드를 재생
player.playSound(fileName: string, loop: boolean = false)

// 플레이어에게 링크에 해당하는 사운드를 재생
player.playSoundLink(link: string, loop: boolean = false)
```

### playSound

{% hint style="info" %}
player.playSound(fileName: string, loop: boolean = false, overlap: boolean = false, key: string = "ambient", volume: number = 1)
{% endhint %}

해당 플레이어에게 사운드를 재생하는 함수입니다.

**파라미터**

<table><thead><tr><th width="137.33333333333331">이름</th><th width="131">타입</th><th>설명</th></tr></thead><tbody><tr><td>fileName</td><td>String</td><td>불러올 파일의 이름</td></tr><tr><td>loop</td><td>boolean</td><td>true: 반복 재생<br>false: 1회 재생</td></tr><tr><td>overlap</td><td>boolean</td><td>사운드 오버랩(겹침) 재생 가능 여부</td></tr><tr><td>key</td><td>string</td><td>재생하는 사운드를 식별하는 데 사용되는 문자열입니다. <br>기본 값은 "ambient"로 설정되어 있습니다.</td></tr><tr><td>volume</td><td>number</td><td>사운드의 볼륨을 조절하는 데 사용되는 숫자입니다. <br>값의 범위는 0에서 1까지이며, 0은 소리가 없음을 나타내고, 1은 최대 볼륨을 나타냅니다.</td></tr></tbody></table>

**예제**

입장음 설정해보기(파일)

{% file src="/files/9aGFrafFuDp3Grwpqop1" %}

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	player.playSound("join.mp3",false, true, "join", 0.5);
});
```

### playSoundLink

{% hint style="info" %}
player.playSoundLink(link: string, loop: boolean = false, overlap: boolean = false, key: string = "ambient", volume: number = 1)
{% endhint %}

모든 플레이어에게 사운드를 재생하는 함수입니다.

> 💡 **올바른 링크를 입력했는데 재생이 되지 않는 경우**
>
> CORS 정책을 위반한 경우일 가능성이 높습니다. CORS 정책을 맞출 수 없는 경우에는 \
> playSoundLink 대신 음악 파일을 업로드 하여 playSound 함수를 사용하는 것을 권장 드립니다.

**파라미터**

<table><thead><tr><th width="147.33333333333331">이름</th><th width="133">타입</th><th>설명</th></tr></thead><tbody><tr><td>link</td><td>String</td><td>사운드 url</td></tr><tr><td>loop</td><td>boolean</td><td>true: 반복 재생<br>false: 1회 재생</td></tr><tr><td>overlap</td><td>boolean</td><td>사운드 오버랩(겹침) 재생 가능 여부</td></tr><tr><td>key</td><td>string</td><td>재생하는 사운드를 식별하는 데 사용되는 문자열입니다. <br>기본 값은 "ambient"로 설정되어 있습니다.</td></tr><tr><td>volume</td><td>number</td><td>사운드의 볼륨을 조절하는 데 사용되는 숫자입니다. <br>값의 범위는 0에서 1까지이며, 0은 소리가 없음을 나타내고, 1은 최대 볼륨을 나타냅니다.</td></tr></tbody></table>

**예제**

입장음 설정해보기(사운드 url)

```jsx
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
	player.playSoundLink("https://cdn-static-stage.zep.us/static/assets/sounds/new_donation_1.wav", false, true, "join", 0.5);
});
```

### stopSound

{% hint style="info" %}
player.stopSound(key: string)
{% endhint %}

key에 해당하는 사운드의 재생을 중지하는 함수입니다.

<table><thead><tr><th width="147.33333333333331">이름</th><th width="133">타입</th><th>설명</th></tr></thead><tbody><tr><td>key</td><td>string</td><td>중지하려는 사운드의 키 값</td></tr></tbody></table>

#### 예제

재생  중인 사운드 중지 시키기\
아래 예제 코드 실행 후 Q, W 키를 연속으로 입력해보세요.

```
//Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q,(player)=>{
	player.playSoundLink("https://cdn-static-stage.zep.us/static/assets/sounds/new_donation_1.wav", false, true, "donation", 0.5);
});

//W를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.w, (player)=>{
    player.stopSound("donation")
});
```

## 💠 **공통 Methods**

<mark style="background-color:yellow;">**공통 Methods 함수 한 눈에 보기**</mark>

```jsx
// 플레이어 필드값을 수정한 후 업데이트
player.sendUpdated()

// 플레이어 스토리지값을 저장
player.save()
```

### sendUpdated

{% hint style="info" %}
&#x20;player.sendUpdated()
{% endhint %}

App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.

**파라미터**

* 없음

### save

{% hint style="info" %}
player.save()
{% endhint %}

App, Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.

**파라미터**

* 없음

***


# ScriptWidget

ScriptWidget 클래스는 아래와 같이 3개의 카테고리로 구성되어있습니다.

### [<mark style="color:purple;">Field</mark>](/zep-script-api/zepscriptapi/scriptwidget/field)

> Widget 과 관련된 속성 값들이 모여있는 카테고리입니다.
>
> 현재는 위젯의 id 필드만 가지고 있으며, 업데이트 시 추가 될 수 있습니다.

### [<mark style="color:purple;">Event Listeners</mark>](/zep-script-api/zepscriptapi/scriptwidget/event-listeners)

> 위젯에서 App으로 데이터를 보낼 때 동작 하는 함수들이 모여있는 카테고리입니다. onMessage 함수가 있으며, 업데이트 시 추가 될 수 있습니다.

### [<mark style="color:purple;">Methods</mark>](/zep-script-api/zepscriptapi/scriptwidget/methods)

> 위젯으로 데이터를 보내는 함수, 위젯을 종료하는 함수 등이 모여있는 카테고리입니다.


# Field

### 소개

위젯과 관련된 속성 값을 조회 할 수 있습니다.

🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.

<table><thead><tr><th width="165">이름</th><th>설명</th></tr></thead><tbody><tr><td>🔒 id</td><td>위젯의 id 값을 가져옵니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### id

{% hint style="info" %}
widget.id
{% endhint %}

위젯의 아이디 값을 가져옵니다.

**예제**

위젯 아이디 값 출력해보기

{% file src="/files/9RU0t0vGgN7qNWkFaj1R" %}

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FWxAMdnm4FRMhUTwuyKTj%2FUntitled.png?alt=media\&token=ffbe8110-544c-4b8c-be8d-885158be6dcb)

```jsx
let _widget = null;
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
	_widget = player.showWidget("sample.html","top",300,300);
	App.sayToAll(`위젯 아이디: ${_widget.id}`)
});
```

***

### 부록

[위젯에서 사용가능한 문법](/creator/reference/scriptaction)


# Event Listeners

### 소개

위젯에서 App으로 데이터를 보낼때 동작 하는 함수들 입니다.

<table><thead><tr><th width="220">이름</th><th>설명</th></tr></thead><tbody><tr><td>onMessage</td><td>위젯에서 App으로 보낸 메시지를 수신 할 때 동작하는 함수입니다.</td></tr></tbody></table>

## 📚 API 설명 및 예제

### onMessage

{% hint style="info" %}
widget.onMessage.Add(function(player, data: any){});
{% endhint %}

위젯에서 App으로 메시지를 보내면 callback 함수가 동작합니다.

**파라미터**

<table><thead><tr><th width="133.33333333333331">이름</th><th width="138">타입</th><th>설명</th></tr></thead><tbody><tr><td>player</td><td>Player</td><td>player는 위젯을 소유한 플레이어를 가르킴<br>player 파라미터의 이름은 임의로 변경 가능</td></tr><tr><td>data</td><td>Object</td><td>data는 위젯에서 App으로 보낸 메시지를 가르킴<br>data 파라미터의 이름은 임의로 변경 가능</td></tr></tbody></table>

**예제**

x 버튼을 눌러서 위젯창 닫는 기능 만들어보기

{% file src="/files/PizN4kVrM1Ip04mOk3Pi" %}

![](https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FN1hpU3vCvDnzfHS9sjY0%2FUntitled.png?alt=media\&token=e9377306-0a60-45e5-884a-d352a936647e)

```jsx
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
	player.tag = {
		widget: null,
	};

	player.tag.widget = player.showWidget("sample.html.html", "top", 600, 500);
	player.tag.widget.onMessage.Add(function (player, msg) {
		// 위젯에서 App으로 'type: close'라는 메시지를 보내면 위젯을 파괴함
		if (msg.type == "close") {
			player.showCenterLabel("위젯이 닫혔습니다.");
			player.tag.widget.destroy();
			player.tag.widget = null;
		}
	});
});
```

**sample.html**: 버튼과 스크립트 부분

```jsx
<i onclick="closeWidget()" class="fa-solid fa-xmark"></i>
<script type="text/javascript">
	// x 버튼을 누르면 호출되는 함수
	function closeWidget() {
		// App으로 메시지를 보냄.
		window.parent.postMessage(
			{
				type: "close",
			},
			"*"
		);
	}
</script>
```

### 부록

[위젯에서 사용가능한 문법](/creator/reference/scriptaction)


# Methods

### 소개

App에서 위젯으로 데이터를 보내거나, 위젯을 파괴하는 함수입니다.

| 이름          | 설명                         |
| ----------- | -------------------------- |
| sendMessage | App에서 위젯으로 데이터를 보내는 함수입니다. |
| destroy     | 위젯을 삭제하는 함수입니다.            |

## 📚 API 설명 및 예제

#### <mark style="background-color:yellow;">**Methods 함수 한 눈에 보기**</mark>

```jsx
// 생성한 위젯에 메세지를 보냄
widget.sendMessage(object: any)

// 위젯을 제거
widget.destroy()
```

### sendMessage

{% hint style="info" %}
widget.sendMessage(object: any)
{% endhint %}

App에서 위젯으로 데이터를 보냅니다.

**파라미터**

<table><thead><tr><th width="146.33333333333331">이름</th><th width="103">타입</th><th>설명</th></tr></thead><tbody><tr><td>object</td><td>any</td><td>앱에서 위젯으로 보내는 데이터<br>Ex) { name : “test”, message : “message” }</td></tr></tbody></table>

**예제**

위젯에 텍스트와 이미지 데이터 보내기

{% file src="/files/PX572edWfqeWmPLWlIkB" %}

<div align="left"><figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FBDKaUXw8OekMSfDyzpVp%2FUntitled.png?alt=media&amp;token=2743154e-4d8e-4f03-8cd2-24bd745ccb0c" alt=""><figcaption></figcaption></figure> <figure><img src="https://2461137890-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiW553XSGeKCAPpImxXi3%2Fuploads%2FURUf3P52DYDMRaugqt6O%2FUntitled%201.png?alt=media&amp;token=65d8a7c5-6a52-420a-8d00-651cec3abdf3" alt=""><figcaption></figcaption></figure></div>

```jsx

// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
	player.tag = {
		widget: null,
	};
	
	player.tag.widget = player.showWidget("sample.html", "top", 300, 300);
	//위젯에서 앱으로 메시지를 보내면 동작하는 함수
	player.tag.widget.onMessage.Add(function (player, data) {
		if (data.type == "close") {
			player.showCenterLabel("위젯이 닫혔습니다.");
			player.tag.widget.destroy();
			player.tag.widget = null;
		}
	});
	player.sendUpdated();
});

// q를 누르면 동작하는 함수
// 위젯에 텍스트와 blueman 이미지를 보냄
// App.addOnKeyDown 설명(링크)
App.addOnKeyDown(81, function (player) {
	if (player.tag.widget) {
		player.tag.widget.sendMessage({
			text: "블루맨",
		});
	}
});
```

**sample.html**: 텍스트, 이미지 데이터를 받아 표시하는 부분

```jsx
//앱에서 위젯으로 메시지를 보내면 동작하는 함수
window.addEventListener("message", function (e) {
	let text = e.data.text;
	let content = document.getElementById("content");
	if (content) {
		while (content.hasChildNodes()) {
			content.removeChild(content.firstChild);
		}
		let content_image = document.createElement("img");
		content_image.setAttribute("src", BLUEMAN);
		content_image.setAttribute("class", "cover");
		content.append(content_image);
	}
	if (text) {
		document.getElementById("text").innerText = text;
	}
});
// 이미지로 사용할 블루맨 이미지 (base64 인코딩)
const BLUEMAN = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAABACAYAAABcIPRGAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAF+UlEQVRoge2ZX2hTVxzHPxk+lOFDbJCtHYVYmpCO7qHEsDWtyFrppkLtxDD3v8hgm7TLgw42Xeek6wSnD7XgJhvi0G1CpDphlHWdIiN3jrb40CDlRmyhKA7XGvaw9WFw9nBzbm9u7k1zb9L5sHzhkntPfuec7/fc3/md8zsXKqigggoqqKCCCiqooIL/Kzyr3L5Y7b5Wo1GddF1wUC+cVw+udr+uIYxXXXBQv4/0Tgvz/zbXQ4VoVoRoVoSo2fORkM8YCMpn46+8KFHAIyXTz+JG1MPjbx7OKYv0The0vxEt3ZNKFaCPXrMiuPfVoaIrNiuCZkWv/lDcSPh83bYuwgouhGHOyHbckCjpDTzqi+j3htHUn41ldv/Pqwdz2vmvIHy+7pyIY3cVa+P2LbidRULG+Cp/EID0WAzQYr8ss8LSnKqvCYHOhF4G+lrhiNMaR7RtkB6LrUhcosofJOBPsDSnkh6L6SLcomQBVuS9wZCtfUadATQhdQzq9d2iJAHSFSR5SVyStILRpsofBFVrpxQRbiAA0REKC0AEOhMi0jtd1IQlO2kjvdMi0JnIaQcXk9j1G+gIhfX7pTmVibEYi+p1oKOo+tXBp3JGvSMU5ueZKcc8XAuo9Xq4mxFc7uujKbwWb2AU7j/Q/ly/zr5i1mYxOUomrZKa6mPoJ4Var7uAWNJCVuv10BRuwBswRR8pxAxTuTcQpCnc4Jo8lCmMAkRfeifnWfnuc1c2TuFGurjc10di4lcAho4PlEwivq8fgFikha7hYUe8Cr0Bc0TQG20KN9AUbuDQF2dzDIwjbDe6djaH336tCLr5sBMgIr3TejxfnPqGhYVLAtPIxCItlpWLcQ2jjV07xcBqEotAZ4J/Xm4CtJW2OvwKPl83aG/FU98TJzV1a7nG+nW5kUc+W102dVJTt6jviYNDtzYLEIHORN5WQIowwJP11Rwo4+dRxs8X7NDOxqnvSxgFCJ+vWye/5tsUaz/eVWiz5ekaHiaTVu3D5kq4/4BMWnVNHpYFCJ+vO2eUM+oMf/R8Cmjb3ux22dyJp74nbikiumV3wWdJ3o3b5BAwk/cGQ2TUGX2PDkXt08XtM0PagmbwayPpHLcpE3lLAbCcYPy1MMHCwqViOxEAuhAbGIgX225B5IVRt+TlXKnv0TIzbY/UoBucnIdj/RpxaZsei+WFZqfwACLyzCbuLT5vJu6IvJz8H+xcQkkq7K3LNzw5D9HWKEdGqgBtntnMLUcCkCImrv/ipA6AGLkyAaCT+nqfD4DT5xJEW6O6oZJU2POq9nbeOL4AaGIBdrZHzO06FgBZEQCbt+/KM7z2wwVC/M3Z65Oynhi5MkFowxMA7HgriTcYYvNjV/jsw3dtO3zvkxNc+72djDrD96daAZiZveNahLlCwYxIJjHGxOPm7F1dgIT6Y/4ASASfu6DfSwHgXoR5JfYUuiTxjlCY22eGWEyO8uSGWk6fS7BjkyZkaU7l4tVJy84uXp3Ug4S0b/TX0Oiv4YVnN5J1R0dppZN8QFzu66NreJiOUBhv9GlAizZd/XH2Dwzp5I6MVHFkJGXRhDZPluZU2LSWmdk7NPpr9H+liJ3tkaKjk6OMrG33NhaTo3z5/utawf0HtO3elmOTOnqK8S3byagzedf4lu2kjp7SbZWkkteHVVkhOPE3YVykMmlVv69u3cr+gSF9cq50LiQn+7H+ODdn79Lor+Hi1UmUpMKB9qCjVdrprBdygUpN3cK4IzWuBTKPMAoxl8k1YP/AEAAHXtRcUu6pqlu3FsXPcVIvk3iz65hHvZjnQGeCvXUG8i7gNKn31PfE86JEXXDQctTtYDy5q++JZ8+TllHs6IO7U4m8tcN4Lip3svfm1JzzUhmhMhYNZpTf9HunO9RyfO4UVkmP8cjcfBRvhDyWd8upbB/5rCDzCNM3YisYF0xHKIsAY/IjYUqCLEVY1XOKcggoZoQt4eaLjBllO1r0/nlCv89uy/Py5/RYTN/xlgvleAOuSWXrPfwv9W0tG2lr2QjYjr6ERyZNxjr/a/wLheTsVoCMSQ0AAAAASUVORK5CYII="
```

####

### destroy

{% hint style="info" %}
widget.destroy()
{% endhint %}

위젯을 삭제하는 함수입니다.

**파라미터**

* 없음

**예제**

:fire: [<mark style="color:purple;">Event Listeners</mark>](/zep-script-api/zepscriptapi/scriptwidget/event-listeners)

### 부록

[위젯에서 사용가능한 문법](/creator/reference/scriptaction)


# UtilityClass

유용하게 사용할 수 있는 작은 단위의 클래스들을 소개합니다.

### [Time](/zep-script-api/zepscriptapi/utilityclass#time)

> **ZEP** 서버의 시간을 조회하는 등 시간 데이터를 가져오는 함수를 모아 놓은 클래스입니다.


# Time

### **소개**

ZEP 서버의 시간을 조회하는 등 시간 데이터를 가져오는 함수를 모아놓은 클래스입니다.

### Methods

<table><thead><tr><th width="174">이름</th><th>설명</th></tr></thead><tbody><tr><td>getTime</td><td>ZEP 서버의 현재 시간을 milliseconds 단위의  값으로 리턴합니다.</td></tr><tr><td>getUtcTime</td><td>현재 UTC 시간을 milliseconds 단위의 값으로  리턴합니다.</td></tr><tr><td>getTimeInterval</td><td>파라미터로 입력한 두 시간의 차이를 계산해 값을 리턴합니다.</td></tr></tbody></table>

### getTime

{% hint style="info" %}
Time.getTime()
{% endhint %}

ZEP 서버의 현재 시간을 milliseconds 단위의  값으로 리턴합니다.

#### 예제

```
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
    player.sendMessage(`Server Time: ${Time.getTime()}`);
});
```

###

### getUtcTime

{% hint style="info" %}
Time.getUtcTime()
{% endhint %}

현재 UTC 시간을 milliseconds 단위의 값으로  리턴합니다.

#### 예제

```
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
    player.sendMessage(`UTC Time: ${Time.getUtcTime()}`);
});
```

### getTimeInterval

{% hint style="info" %}
Time.getTimeInterval(timeA: number, timeB: number, returnType: DateType)
{% endhint %}

`timeB - timeA`를 계산하고, 그 결과를 지정한 `returnType`으로 반환합니다.

**파라미터**

<table><thead><tr><th width="156.33333333333331">이름</th><th width="159">타입</th><th>설명</th></tr></thead><tbody><tr><td>timeA, timeB</td><td>Number</td><td>차이를 계산할 시간 (milliseconds)</td></tr><tr><td>returnType</td><td>DateType</td><td><p>DateType.MILLISECONDS ( ms 단위 )</p><p>DateType.SECONDS (초 단위 )</p><p>DateType.MINUTES ( 분 단위 )</p><p>DateType.HOURS ( 시간 단위 )</p><p>DateType.DAYS ( 일 단위 )</p></td></tr></tbody></table>

#### 예제

```
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
    const currentTime = Time.getTime();
    const utcTime = Time.getUtcTime();
    player.sendMessage(`timeInterval: ${Time.getTimeInterval(currentTime, utcTime, DateType.MILLISECONDS)}`);
})
```


