Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
ZEP Script 란?
ZEP Script는 ZEP에서 동작하는 스크립트 언어(Scripting Language)입니다.
아바타 및 오브젝트 조작, 결제, 채팅, 실시간 멀티플레이 등 다양한 기반 시스템을 제공하여,
코딩이 처음이라도 쉽게 시작할 수 있습니다.
독창적인 게임부터, 캘린더와 방명록 등의 생산성 앱까지!
나만의 메타버스 앱을 만들어 메타버스를 운영하고 ZEP을 더 풍부하게 즐겨보세요!
ZEP Script는 사용하기 편리합니다. 그리고 누구나 사용할 수 있습니다.
ZEP Script를 활용해 다양하고 재미있는 게임을 만들 수 있습니다.
떨어지는 장애물(눈덩이)을 피해 산 정상으로 올라가는 게임입니다. 미니게임 [똥피하기]를 활용하여 떨어지는 눈덩이를 구현할 수 있습니다.
제한 시간 내에 탐정이 되어 맵 곳곳을 탐험할 수 있는 방탈출 맵입니다. 비네팅 효과로 인해 스릴 넘치는 공간을 체험헤보세요!
미니게임 [결투]를 기반으로 만들어진 게임입니다. [z]키를 눌러 상대를 공격하고 HP가 깎이지 않게 다른 사람들을 피해 끝까지 살아남으세요. 가장 끝까지 살아남은 우승자는 왕으로 변할 수 있습니다.
ZEP Script를 활용하여 인터랙티브한 행사를 기획할 수 있습니다.
클레이튼의 기술적 강점 등을 ZEP Script를 활용해 효과적으로 보여준 사례입니다
유니세프가 하는 일을 ZEP Script로 구현하여 , 이용자들이 맵을 체험하며 자연스럽게 학습을 할 수 있도록 제작했습니다
삼성 청소기 제트 브랜드를 체험할 수 있는 테마파크로 ZEP Script를 통해 게임 공간을 구현해두었습니다. 행사 시에 이용자들에게 재미요소를 줄 수 있습니다.
메타버스 플랫폼 ZEP에서 동작하는 앱을 누구나 제작할 수 있습니다.
캐릭터 및 오브젝트 조작 기능, 커스텀 UI 기능 등 다양한 기능을 지원합니다.
독창적인 게임부터 캘린더, 방명록 등의 생산성 앱까지, 누구나 자신만의 메타버스 앱을 만들 수 있습니다.
🔥
Javascript (ES6)
Typescript
Canvas
Web (Desktop/Mobile)
ZEP-Script SDK를 사용하면 APP 개발 및 배포를 쉽게 할 수 있습니다.
💡
💻
1) 미니게임 ( Mini game )
미니게임은 어느 맵에서나 사용할 수 있는 설치형 앱입니다. 임베드 권한이 있는 스페이스에서 [사이드바] > [미니게임 아이콘]을 클릭해 설치할 수 있습니다. 설치한 순간부터 작동하며, 실행한 사용자가 됩니다.
노말 앱은 특정 맵에서만 작동할 수 있는 앱입니다. 노말 앱을 개발하여 업로드하면, 관리자이상의 권한을 가진 유저가 맵의 [설정] > [맵 설정] > [노말 앱 추가]에서 적용할 수 있습니다. 별도의 앱 설치 과정 없이 내가 제작한 맵에만 Script를 적용할 수 있습니다.
사이드바 앱은 PC 좌측 사이드바에 아이콘으로 표시되는 형태의 앱 입니다. 사이드바 앱을 개발하여 업로드하면, 관리자권한을 가진 맵의 플레이 화면에서 [사이드바] > [앱 추가] > [앱 관리] 리스트에서 설치하여 추가할 수 있습니다. 사이드바 앱이 추가된 스페이스에서는 입장한 모든 사람에게 사이드바 앱이 노출됩니다.
이 가이드에서는 ZEP Script 개발을 시도해 보고자 하는 분들을 위한 간단한 개발 환경 및 방법에 대해 설명합니다.
ZEP Script를 이용해 앱을 개발하기 위해서는 코드를 작성할 수 있는 IDE(편집기)가 필요합니다. 이 가이드에서는 Visual studio code 소프트웨어를 설치해 보도록 하겠습니다.
Visual studio code 홈페이지()에 방문합니다.
PC에 설치된 OS에 맞는 소프트웨어를 다운로드하고 설치합니다.
설치한 소프트웨어를 실행합니다.
좌측 상단에 [파일] → [새 파일]을 선택합니다.
중앙 상단에 표시되는 파일 형식 입력에 main.js라고 입력합니다.
(ZEP Script로 제작한 앱에는 반드시 main.js 파일이 있어야 합니다)
파일을 저장할 폴더를 선택하신 후 파일 생성을 클릭합니다.
미니 게임을 실행하면 간단한 인사 메시지가 나오는 앱을 제작해 보도록 합니다.
사용할 API는 아래 3가지입니다.
- 앱이 실행될 때 이 API함수 내에 있는 명령들이 1회 실행됩니다.
- 채팅 창에 메시지를 표시하는 기능입니다.
- 화면에 메시지를 표시하는 기능입니다.
더 많은 기능에 대해 알아보려면 ZEP-Script 가이드()를 참고해 주세요.
아래와 같이 테스트 코드를 작성한 후 저장합니다.
저장이 완료되었으면, 앱을 업로드하기 위해 main.js파일이 있는 폴더로 이동해 해당 파일을 *.zip파일로 압축합니다.
홈페이지 로그인한 후 우측 상단에 내 닉네임을 클릭해서 [나의 앱]을 클릭합니다.
[나의 앱]에서 앱을 등록하거나 수정할 수 있습니다. 새로운 앱을 업로드하기 위해 우측 상단에 [앱 업로드] 버튼을 클릭합니다.
앱 업로드에 필요한 정보를 작성합니다.
이름, 설명 등을 작성합니다.
앱 타입은 [미니 게임]으로 합니다.
[ZIP 파일 업로드] 버튼을 눌러, 작성한 코드가 포함된 압축 파일을 등록합니다.
다 완료되었으면 [업로드] 버튼을 눌러 완료합니다.
앱 종류 및 업로드에 대한 자세한 사항은 를 참고해 주세요.
테스트를 위해 홈페이지에 로그인 후 스페이스를 만들어 주세요.
좌측의 사이드바에서 [미니 게임]을 클릭합니다.
실행 가능한 미니 게임 목록 중에 내가 업로드한 앱을 클릭합니다. [Hello, ZEP]
앱이 실행됨과 동시에 화면 상단과 채팅 창에 코드로 작성했던 Hello, ZEP! 이라는 메시지가 나타나는 것을 볼 수 있습니다.
기본적인 앱 제작, 업로드 방식에 대해 알아봤는데요,
ZEP Script에 대해 조금 더 학습하면, 자신이 원하는 앱을 만들 수 있습니다.
[]를 따라서 만들어 보는 것부터 시작해 보세요!
튜토리얼을 따라 차근차근 ZEP Script를 익혀보세요!
App.onStart.Add(function(){
App.sayToAll("Hello, Zep!");
App.centerLabel("Hello, Zep!")
})
App의 중앙 상단부에 메시지를 출력해봅니다.
기본 Center label 색상은 검은 배경에 흰색 텍스트로 노출됩니다.
// main.js
App.showCenterLabel("Hello world");
2. 실행 결과
참고
- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.
- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.
- 배포 방법을 아직 모르신다면, ZEP Script 배포 가이드를 참고해주세요.
space는 1개 이상의 map을 포함하고 있는 그룹의 단위 입니다.
space와 map은 다른 스페이스 또는 맵과 자신을 구분하기 위한 HashID를 가지고 있습니다.
space에 포함된 1개 이상의 map들 중 하나는 ‘첫 방문 맵’ 속성을 가지고 있으며, mapID 없이 spaceHashID만 가지고 스페이스에 접근 할 경우 ‘첫 방문 맵’ 속성을 가진 맵으로 이동하게 됩니다.
젭 맵 에디터에 들어가면 다음과 같은 URL 형식을 볼 수 있는데요,
여기서 Ak42Xz가 SpaceHashID, 25g3RQ가 MapHashID 입니다.
맵 에디터에서 오브젝트를 설치하여 상호작용시 ZEP 스크립트에서 감지해 상호 작용시 발생하는 이벤트를 스크립트로 작성할 수 있습니다.
맵 에디터에서 다음과 같이 ZEP Script 상호작용 오브젝트를 설치 할 수 있습니다.
[맵 에디터] → [오브젝트설치] → [개발자 기능] → [ZEP Script 상호작용 선택]
오브젝트 설정에서 값을 입력한 뒤,
다음과 같이 스크립트를 작성하여 앱 실행 후, 오브젝트와 상호작용시 입력한 값이 출력되는 것을 확인할 수 있습니다.
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]
를 조건으로 활용해 주세요.
기준 좌표란 오브젝트 배치 시 이미지가 그려지는 기준을 의미합니다.
다음과 같은 맵에 Left-Top 기준으로 오른쪽의 오브젝트 이미지를 3, 3에 삽입하면 어떻게 될까요?
기준 좌표가 Left-Top일 경우 다음 그림과 같이 3, 3 박스의 왼쪽 위 지점부터 이미지가 그려집니다.
앱이 실행되어 종료될 때까지를 하나의 생애 주기(Lifecycle)라고 합니다.
Lifecycle 함수는 앱의 생애 주기를 관리해주는 함수입니다. 아래 그림과 같이 앱이 실행될 때는 Enter 단계의 함수들이 동작하고, 앱이 실행 중 일 때는 Update 단계의 함수들이 주기적으로 동작하며, 앱이 종료될 때는 Exit 단계의 함수들이 동작하게 됩니다.
Example (Timer)
모바일 버튼의 이미지를 App.loadSpriteSheet로 불러온 이미지로 변경 할 수 있습니다.
참고: 모바일 점프버튼의 이미지 크기는 180 x 180px 입니다.
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...
}
})
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;
});
// 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을 나머지를 정리한다.
})
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);
}
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);
}
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();
});
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);
});
App storage는 스페이스 내의 App 데이터의 저장공간 입니다.
권장하는 데이터 저장 방식
App.setStorage 함수는 기존 App storage 데이터 저장 방식을 보완한 데이터 저장 함수입니다.
권장하지 않는 데이터 저장 방식
App.storage = string; App.save();
위 방식은 앱이 같은 스페이스 내 여러 맵에서 실행중인 경우, 데이터를 덮어쓰는 등의 문제가 발생할 수 있어 사용을 권장드리지 않습니다.
App.getStorage 함수는 앱이 실행중인 같은 스페이스 내 다른 맵의 App storage 데이터 변경 여부를 체크하여 같은 데이터를 가지도록 동기화 해주는 함수입니다.
App.getStorage 함수는 비동기 함수이기 때문에 App.getStorage 함수 다음 라인에 App.storage를 사용하는 코드를 작성할 경우 동기화를 보장할 수 없습니다.
아래 예제코드를 사이드바 앱으로 설치해 같은 스페이스 내 다른 맵에서 Q를 눌러 count 값이 동기화되는지 확인해보세요.
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);
})
ScriptApp 클래스는 아래와 같이 5개의 카테고리로 구성되어있습니다. 각 카테고리의 제목을 선택하시면, 자세한 내용을 확인할 수 있습니다.
앱이 시작되어, 실행되고 종료될 때까지를 하나의 생애 주기(Lifecycle)라고 합니다. 앱이 시작될 때, 실행 중일 때 그리고 종료될 때 등의 상황에서 필요한 동작들을 실행해 전체적인 앱의 생애주기를 만들어 나갈 수 있는 함수들이 모여있는 카테고리 입니다.
스페이스나 맵, 플레이어 정보 등을 조회하거나, 저장공간을 활용할 수 있는 App과 관련된 속성이 모여있는 카테고리입니다.
플레이어가 지정된 채팅을 입력하거나, 특정한 오브젝트를 공격할 와 같이 맵 내에서 발생할 수 있는 다양한 상황을 감지하여 동작하는 함수들이 모여있는 카테고리 입니다.
스크립트 개발자가 지정한 키를 플레이어가 누르거나 특정 지점에 도착했을 때와 같이 조건을 설정하고 조건이 만족되었을 때 동작하는 함수들이 모여있는 카테고리 입니다.
화면에 UI를 표시, 유저 이동 또는 강퇴, 사운드 재생 등 편리한 기능을 제공하는 함수들이 모여있는 카테고리 입니다.
맵과 관련된 속성 값을 조회 할 수 있습니다. 현재는 맵의 너비(width), 높이(height) 필드를 조회할 수 있습니다
🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.
🔒 width
맵의 너비 값을 가져옵니다.
🔒 height
맵의 높이 값을 가져옵니다
맵의 너비와 높이 값을 가져옵니다.
예제
맵의 너비, 높이 값을 채팅창에 출력해보기.
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
App.sayToAll(`맵 가로 크기: ${Map.width}`);
App.sayToAll(`맵 세로 크기: ${Map.height}`);
});
Type-C는 페인트 맨 팀 전 게임에서 사용된 Custom Label로, 대결 구도의 게임에서 자주 사용됩니다.
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-H는 제한 시간이 있는 게임에서 남은 시간을 표시할 때 쓰는 Custom Label입니다.
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-E는 가위바위보 게임에서 나온 Custom Label로, 이미지를 삽입할 수 있습니다.
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-D는 Type-C와 마찬가지로 팀 전 점수를 기록할 때 자주 쓰이며, 3줄로 나누어 쓸 수 있습니다.
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-J는 레벨이 있는 미니 게임에서 자주 쓰이는 Custom Label 입니다.
똥 피하기 게임에서 쓰인 것을 볼 수 있습니다.
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);
}
Type-G는 한 줄 text를 사용할 수 있는 Custom Label입니다.
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);
}
외부 API에 GET, POST 등의 요청을 인자와 함께 보낼 수 있습니다.
한국어 별명 생성기 API를 이용해 입장하는 플레이어의 닉네임을 바꿔보기
// 플레이어가 입장할 때 동작하는 함수
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();
}
);
});
앱에서 보낸 헤더와 데이터를 응답으로 받아 채팅창에 출력해보기.
// 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);
}
);
});
참고
- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.
- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.
- 배포 방법을 아직 모르신다면, ZEP Script 배포 가이드를 참고해주세요.
Type-F는 ox 퀴즈, 퀴즈 골든벨에서 자주 사용되는 Custom Label로 본문 text를 길게 넣을 수 있습니다.
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);
}
ZEP 스크립트를 활용해 개발한 게임 사례를 확인해 보실 수 있습니다.
ZEP Script를 활용해 다양하고 재미있는 게임을 만들 수 있습니다.
ZEP Script를 활용한 게임 개발 사례들을 만나보세요.
바람:연 X ZEP X 링티가 콜라보하여 ‘강일병 구하기’ 라는 퀘스트 형태의 게임을 만들어 선보인 사례입니다.
바람:연 X ZEP X 링티가 콜라보하여 ‘팩맨’ 게임에 다람쥐, 도토리 등 바람의나라:연 이미지를 입혀 귀엽게 표현한 미니게임 입니다
슈퍼캣 채용박람회에서 진행된 페인트맨 게임입니다.
ZEP Script를 활용하면 100명 이상이 참가하는 단체 게임 개발도 가능합니다!
ZEP 유저들이 ZEP Script를 활용해 개발한 게임 사례가 많아지고 있습니다.
ZEP 캐릭터를 움직여 두는 오목! 재미있지 않나요?
행사에서 ZEP 스크립트를 활용한 사례를 확인해 보실 수 있습니다.
ZEP Script를 활용하면 좀 더 인터랙티브한 행사를 기획할 수 있습니다.
ZEP Script를 활용한 행사 사례들을 만나보세요.
클레이튼의 기술적 강점 등을 ZEP Script를 활용해 효과적으로 보여준 사례입니다.
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);
}
// 사이드바 앱이 터치(클릭)되었을 때 동작하는 함수
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;
}
});
/**
* 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);
});
🔒 id
위젯의 id 값을 가져옵니다.
let _widget = null;
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
_widget = player.showWidget("sample.html","top",300,300);
App.sayToAll(`위젯 아이디: ${_widget.id}`)
});
예제 코드
// 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);
}
```
방탈출 타이머를 사용하려면 지정 영역을 꼭 설정해주세요
start_timer
: 이 영역을 지나면 타이머가 시작됩니다.
end_time
: 이 영역을 지나면 타이머가 종료됩니다.
ZEP Script 개발에 도움을 주는 다양한 예제 코드들을 확인해보세요
ZEP에서 동작하는 타이머를 만들 수 있습니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
⏰ 타이머 예제
방탈출 스페이스에서 동작하는 타이머를 만들 수 있습니다.
필수 지정 영역을 적용해서 방탈출 타이머를 만들어보세요
사이드바 앱은 PC 좌측 사이드바에 아이콘으로 표시되는 형태의 앱 입니다.
다음 예제를 개인의 개발 스타일에 맞게 수정해 사이드바 앱을 개발하실 수 있습니다..
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 좀비 게임 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
🧟 좀비게임 예제
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 페인트맨 게임 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 초성 퀴즈 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
🙆♀️ 초성 퀴즈 예제
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 똥피하기 게임 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 결투 게임 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
🥊 결투 게임 예제
[하단바] > [미디어 추가] > [미니게임]에서 실제로 작동하는 달리기 게임 스크립트입니다.
개인의 개발 스타일에 맞게 수정해서 사용할 수 있습니다.
🏃 달리기 예제
ZEP 서버의 시간을 조회하는 등 시간 데이터를 가져오는 함수를 모아놓은 클래스입니다.
getTime
ZEP 서버의 현재 시간을 milliseconds 단위의 값으로 리턴합니다.
getUtcTime
현재 UTC 시간을 milliseconds 단위의 값으로 리턴합니다.
getTimeInterval
파라미터로 입력한 두 시간의 차이를 계산해 값을 리턴합니다.
ZEP 서버의 현재 시간을 milliseconds 단위의 값으로 리턴합니다.
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
player.sendMessage(`Server Time: ${Time.getTime()}`);
});
현재 UTC 시간을 milliseconds 단위의 값으로 리턴합니다.
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
player.sendMessage(`UTC Time: ${Time.getUtcTime()}`);
});
timeB - timeA
를 계산하고, 그 결과를 지정한 returnType
으로 반환합니다.
파라미터
timeA, timeB
Number
차이를 계산할 시간 (milliseconds)
returnType
DateType
DateType.MILLISECONDS ( ms 단위 )
DateType.SECONDS (초 단위 )
DateType.MINUTES ( 분 단위 )
DateType.HOURS ( 시간 단위 )
DateType.DAYS ( 일 단위 )
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.q, (player)=>{
const currentTime = Time.getTime();
const utcTime = Time.getUtcTime();
player.sendMessage(`timeInterval: ${Time.getTimeInterval(currentTime, utcTime, DateType.MILLISECONDS)}`);
})
Cutom Label 예제 코드를 통해 미니게임에서 자주 쓰이는 Label을 구현할 수 있습니다.
예제코드를 다운받아서 자신의 스페이스에 노말 앱으로 설치해보세요
0-9까지 숫자를 누르면 타입에 따른 Custom Label이 보여집니다.
Type-A는 미니게임의 종류를 알려줄 때 인트로 Custom Label로 자주 사용됩니다.
Type-B는 미니 게임에서 목표를 알려줄 때 자주 쓰이며, 두 줄로 된 긴문장을 쓸 때 주로 사용합니다.
Type-C는 페인트 맨 팀 전 게임에서 사용된 Custom Label로, 대결 구도의 게임에서 자주 사용됩니다.
Type-D는 Type-C와 마찬가지로 팀 전 점수를 기록할 때 자주 쓰이며, 3줄로 나누어 쓸 수 있습니다.
Type-E는 가위바위보 게임에서 나온 Custom Label로, 이미지를 삽입할 수 있습니다.
Type-F는 ox 퀴즈, 퀴즈 골든벨에서 자주 사용되는 Custom Label로 본문 text를 길게 넣을 수 있습니다.
Type-G는 한 줄 text를 사용할 수 있는 Custom Label입니다.
Type-H는 제한 시간이 있는 게임에서 남은 시간을 표시할 때 쓰는 Custom Label입니다.
Type-I 는 미니 게임에서 우승자가 나오거나 플레이어가 특정될 때 나오는 Custom Label 입니다.
Type-J는 레벨이 있는 미니 게임에서 자주 쓰이는 Custom Label 입니다.
활용 예 ) 달리기 공식 맵, 결투장 공식 맵, 승부 예측 앱, 포인트 앱
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);
});
이 문서는 ZEP 스크립트로 만든 앱을 적용하는배포 과정에 대해 설명합니다.
주의:
- 앱의 파일명과 형식은 반드시 “main.js”로 지정해주어야 합니다.
- 폴더를 압축하는 것이 아니라 파일을 멀티 선택한 후 압축해야 합니다.
- *.zip 형식의 확장자를 지원합니다.
예시 파일
노멀 앱(Normal app):
Owner 권한을 가진 맵의 사이드바에서 [설정] > [맵 설정] > [노말 앱 추가]에서
적용할 수 있습니다.
미니게임(Mini game):
플레이 화면의 [좌측 사이드바] > [미니게임 버튼]을 클릭해 설치할 수 있습니다.
앱 실행 시, 에러 메시지는 스태프 권한 이상의 유저 채팅창에 빨간색 글씨로 노출됩니다.
블루맨 스프라이트 한 칸의 이미지 크기는 48픽셀 x 64픽셀 입니다.
frameWidth, frameHeight 파라미터에 48, 64를 지정하면 블루맨 스프라이트 각각의
이미지가 0번부터 41번까지 번호를 가지게 됩니다.
anims는 애니메이션을 지정할 배열을 의미합니다.
캐릭터 스프라이트로 사용 할 때와 오브젝트 스프라이트로 사용 할 때 양식이 다릅니다.
스프라이트가 단일 이미지인 경우 파일 이름을 제외한 나머지 파라미터는 입력하지 않아도 됩니다.
단일 이미지도 캐릭터 이미지로 적용할 수 있습니다. 다만, 애니메이션을 지정하지 않았으니 어느 방향으로 움직여도 같은 이미지일 것 입니다.
단일 애니메이션을사용하는 경우에는 아래와 같이 하나의 배열로 애니메이션 프레임을 정의 할 수 있습니다. 춤추는 블루맨 오브젝트 애니메이션은 아래 코드와 같이 21번째 ~ 38번째 이미지에 해당하는 이미지 번호를 배열로 지정해 사용할 수 있습니다.
여러 애니메이션을 정의하는 경우에는 아래와 같이 anims 파라미터에 { } 브라켓을 입력 한뒤 다음과 같이 애니메이션 이름에 대한 이미지 배열을 지정해주면 됩니다. 아래 예시에서는 Map.playObjectAnimationWithKey
함수를 사용해 정의한 여러 애니메이션 중 "dance"에 대한 애니메이션을 실행합니다.
캐릭터는 오브젝트와 달리 플레이어가 조작하는 키에 따라 다양한 애니메이션 실행 할 수 있습니다. 캐릭터에 지정 가능한 애니메이션의 종류는 다음과 같이 총 9가지 이며, 특정 애니메이션에 대한 이미지가 부족한 경우 생략할 수 있습니다.
✅ 추가로 사용가능한 캐릭터 애니메이션 키
앉기 모션: down_sit, left_sit, right_sit, up_sit
서있는 모션: down_idle, left_idle, right_idle, up_idle
공격 모션: down_attack, left_attack, right_attack, up_attack
npcProperty는Map.putObjectWithKey(x, y, dynamicResource, option)
함수의
option 파라미터에서 지정할 수 있으며, 다음 7가지 속성을 지정할 수 있습니다.
예제 1 - npcProperty를 사용해 오브젝트 생성하기
예제 2 - npcProperty로 체력이 깎이는 효과 구현하기
// 이미지를 객체화 시키는 함수
App.loadSpritesheet(fileName: string, frameWidth: integer, frameHeight: integer, anims: array, frameRate: integer)
let blueman = App.loadSpritesheet('blueman.png')
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
);
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);
});
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();
});
name
string
오브젝트 위에 표시될 이름
hp
number
오브젝트의 현재 체력
hpMax
number
오브젝트의 최대 체력
gaugeWidth
number
체력 게이지바의 너비
생략시 이미지의 너비 크기로 설정됩니다.
hpColor
number
체력 게이지바의 색상 ex) 0x03ff03 (초록색)
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);
});
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 })
}
});
onMessage
위젯에서 App으로 보낸 메시지를 수신 할 때 동작하는 함수입니다.
player
Player
player는 위젯을 소유한 플레이어를 가르킴 player 파라미터의 이름은 임의로 변경 가능
data
Object
data는 위젯에서 App으로 보낸 메시지를 가르킴 data 파라미터의 이름은 임의로 변경 가능
// 플레이어가 입장할 때 동작하는 함수
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;
}
});
});
<i onclick="closeWidget()" class="fa-solid fa-xmark"></i>
<script type="text/javascript">
// x 버튼을 누르면 호출되는 함수
function closeWidget() {
// App으로 메시지를 보냄.
window.parent.postMessage(
{
type: "close",
},
"*"
);
}
</script>
이 문서는 ZEP Script 를 구성하는 4가지 클래스를 안내합니다.
ZEP Script는 다음과 같은 4가지 클래스로 구성되어 있습니다.
앱이 설치된 스페이스와 관련된 전반적인 기능을 담당하는 스크립트
맵에 타일이나 오브젝트를 추가, 수정, 삭제하는 기능을 담당하는 스크립트
플레이어의 설정이나 좌표 지정, 유저의 정보를 불러오는 기능을 담당하는 스크립트
미리 만들어놓은 HTML을 위젯 형태로 맵 내에서 사용할 수 있는 스크립트
애니메이션이 들어간 SpriteSheet 샘플을 준비합니다. 없다면 아래 이미지를 다운 받아주세요.
*주의: 직접 제작한 경우가 아닌 SpriteSheet는 저작권에 유의해서 사용해야 합니다.
2. 위 이미지 같이 낱장의 이미지들이 동일한 규격으로 합쳐진 형태의 SpriteSheet 그림의 확장자는 png 파일이어야 합니다.
TIP: 캐릭터의 경우 한 동작의 그림 규격으로 48(px) x 64(px)를 권장합니다.
스프라이트 시트(sprite sheet)는 여러 개의 작은 그래픽을 정렬하여 구성한 비트맵 이미지 파일입니다. 게임 개발에서 캐릭터의 연속적인 포즈를 한 장의 이미지에 구성하여 2D 애니메이션 제작에 사용됩니다.
3. SpriteSheet를 읽는 API 로는 App.loadSpritesheet
를 사용합니다.
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)과 각각에 사용될 그림 인덱스 넘버들
그림 순서의 인덱스 넘버는 왼쪽 상단 가로 줄 부터 0으로 시작합니다.
다섯 번째 인자(frameRate): 1초에 몇 장을 돌아가게 할 것인지 정합니다.
4. player의 속성을 변경하였으므로 player.sendUpdated()
를 써주어야 실제 반영됩니다.
5. 예시 코드
// 블루맨이라는 변수를 자바스크립트 문법에 맞게 생성
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. 활용
// 변수 선언과 동시에 바로 저장이 가능하다.
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);
플레이어가 입장할 때가 아닌, 다른 특수한 조건일 때에도 캐릭터가 변할 수 있겠죠? 생각해보세요. 그리고 만들어 보세요!
참고
- 튜토리얼의 App type은 미니게임(Mini game)을 권장합니다.
- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.
- 배포 방법을 아직 모르신다면, ZEP Script 배포 가이드를 참고해주세요.
URL 쿼리스트링이란 URL의 뒤에 입력 데이터를 함께 제공하는 데이터 전달 방법 입니다.
예시) https://zep.us/play/{mapHashId}?{파라미터}={값}
URL 쿼리스트링을 이용해 ZEP 스페이스 또는 ZEP Script로 데이터를 전달 할 수 있습니다.
비로그인 유저 입장 시 닉네임을 name 파라미터로 전달한 값으로 설정할 수 있습니다.
예시: https://zep.us/play/{mapHashId}?name=학생A
⚠️ 게스트 유저 닉네임 설정 팝업 비활성화 설정을 해주세요!
ZEP Script player 객체에 데이터를 전달할 수 있습니다. SSO 토큰 정보 등의 유저 식별 정보를 ZEP Script로 전달하여 유저를 식별하는 등 화이트리스트 기능을 만드는 데 활용할 수 있습니다.
예제 1 customData로 플레이어 정보를 받아 적용해보기 (노멀앱, 사이드바앱 권장)
⚡예제에서 사용한 URL
https://zep.us/play/{mapHashId}?customData={"name":"커스텀유저", "moveSpeed":150, "title":"커스텀타이틀"}
예제 2
간단한 토큰기반 화이트리스트 기능 만들어보기 (노멀앱, 사이드바앱 권장)
✅ 브라우저 개발자 도구(F12)에서 다음과 같이 base64 암호화를 하여 토큰을 만들었습니다.
⚡ 예제에서 사용한 URL
https://zep.us/play/{mapHashId}?customData=JTdCJTIydG9rZW4lMjIlM0ElMjIlRUMlOUMlQTAlRUMlQTAlODAxJTJGd2hpdGVMaXN0JTIyJTdE
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("커스텀 데이터를 전달받지 못했습니다.");
}
});
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("커스텀 데이터를 전달받지 못했습니다.");
}
});
// 위젯에서 사용한 스크립트
window.addEventListener("message", function (e) {
if (e.data.type == "decode") {
// base64 토큰 복호화
decodedToken = decodeURIComponent(atob(e.data.token));
window.parent.postMessage({
type: "decode",
decodedToken: decodedToken
}, "*")
}
})
sendMessage
App에서 위젯으로 데이터를 보내는 함수입니다.
destroy
위젯을 삭제하는 함수입니다.
// 생성한 위젯에 메세지를 보냄
widget.sendMessage(object: any)
// 위젯을 제거
widget.destroy()
object
any
앱에서 위젯으로 보내는 데이터 Ex) { name : “test”, message : “message” }
// 플레이어가 입장할 때 동작하는 함수
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: "블루맨",
});
}
});
//앱에서 위젯으로 메시지를 보내면 동작하는 함수
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="
zep-script-SDK 라이브러리를 사용해 폴더 구조를 깔끔하게 유지하고, 프로젝트 압축 및 배포를 명령어만으로 간단하게 진행 할 수 있습니다. zep-script-SDK 사용 방법을 알아보겠습니다.
라이브러리 사용을 위해 프로젝트 폴더는 다음과 같이 구성합니다.
res 폴더는 앱에서 쓰일 이미지, 사운드, html 파일 등을 넣는 폴더입니다.
→ 폴더 이름을 res로 지정해야합니다.
CLI를 사용해 명령어로 프로젝트를 .zip 파일로 만들 수 있습니다.
CLI는 MacOS의 경우 터미널, Windows의 경우 Windows PowerShell 또는 Windows 11부터 제공되는 터미널 환경에서 실행하실 수 있습니다.
Windows에서 PowerShell 실행하기
main.js가 있는 폴더 빈 공간에 Shift + 오른쪽클릭을 해서
여기에 PowerShell 창 열기
또는여기서 명령창 열기
메뉴를 누르면다음과 같이 Windows PowerShell 또는 명령 프롬프트 창이 실행됩니다.
main.js 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.
npx zep-script archive
압축 과정이 성공했다면 다음과 같이 폴더에 압축 파일이 생긴 것을 확인 할 수 있습니다.
압축 파일을 만드는 것을 마지막으로, 앱을 배포할 준비가 끝났습니다.
1.홈페이지에서 배포하기
위에서 만든 압축(ZIP) 파일을 업로드 하는 방식으로 앱을 배포할 수 있습니다.
ZEP Script 배포 가이드 페이지를 참고해서 앱을 배포해보세요!
2. 명령어(CLI)로 배포하기
CLI를 사용해 위에서 만든 압축(ZIP) 파일을 배포할 수 있습니다.
다음과 같이 zep-script.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입니다)
main.js 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.
npx zep-script publish
앱 업로드 과정에서 계정 인증이 필요합니다.
다음과 같이 email을 입력하는 부분이 나오면 배포 할 앱을 소유하고 있는 계정의 email을 입력합니다.
Sending login code to your email이라는 메시지가 나오면 위에서 입력한 이메일의 메일 함에서 인증 코드를 확인한 뒤 입력해주면 배포가 시작됩니다.
다음 그림과 같이 Publishing… 텍스트 좌측에 초록색 체크 표시가 생기면 배포가 완료된 것 입니다.
위에서 zep-script.json 파일에 작성한 내용대로 나의 앱 페이지에 앱이 배포된 것을 확인 할 수 있습니다.
배포에 실패하는 경우 아래의 단계를 따라 재시도 해보세요.
npx 명령어를 사용하는 경우
터미널에서 "npm i zep-script -g"
명령 실행하여 SDK 패키지를 최신화
root 폴더에서 ".zscsession"
파일 삭제
"npx zep-script publish" 명령어로 배포 시도
npm 명령어를 사용하는 경우
package.json 파일의 "zep-script" 버전이 최신이 아니라면 최신 버전으로 업데이트
root 폴더에서 ".zscsession"
파일 삭제
"npm run deploy" 명령어로 배포 시도
라이브러리 관련하여 보다 자세한 내용은 아래 github 레포지토리 내용을 참고해주세요.
👏zep-script-sdk/packages/zep-script-cli at main · zep-us/zep-script-sdk
1) 파일
2) main.js
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;
}
});
타입스크립트로 개발 시 App, Map, Player, Widget 키워드 앞에 Script 키워드를 붙여야 합니다.
// 자바스크립트 개발 시
App.sayToAll("Hello World!");
// 타입스크립트 개발 시
ScriptApp.sayToAll("Hello World!");
CLI를 통해 ZEP Script 프로젝트를 개발하기 위한 샘플 프로젝트를 다운로드 받을 수 있습니다.
CLI는 MacOS의 경우 터미널, Windows의 경우 Windows PowerShell 또는 Windows 11부터 제공되는 터미널 환경에서 실행하실 수 있습니다.
Windows에서 PowerShell 실행하기
폴더를 생성하고 싶은 곳 빈 공간에 Shift + 오른쪽클릭을 해서
여기에 PowerShell 창 열기
또는여기서 명령창 열기
메뉴를 누르면다음과 같이 Windows PowerShell 또는 명령 프롬프트 창이 실행됩니다.
다음과 같이 프로젝트 폴더를 생성하기 위한 명령어를 입력합니다.
npx zep-script init {폴더 이름} --npm
npx zep-script@latest init MyZepApp --npm
위와 같이 Project {폴더 이름} initialized successfully
메시지가 출력 되었다면,
이제 타입스크립트로 ZEP Script를 개발할 수 있는 환경이 준비 된 것 입니다.
생성된 프로젝트 폴더로 이동하면 다음과 같이 파일들이 있는 것을 볼 수 있습니다.
여러 기능을 모듈로 나누고 싶다면, src
폴더내부에 파일을 추가해 확장할 수 있습니다.
예: playerUtils.ts
, eventHandlers.ts
등
res 폴더는 ‘resources’의 약자로, 앱에 필요한 이미지나 html 파일 등을 넣는 폴더입니다.
여기서 생성된 main.ts 파일은 앱 로직을 작성하는 메인 파일입니다.
main.ts 에는 다음과 같이 샘플 코드가 적혀있습니다. 해당 코드를 참고하여 개발을 하시면 됩니다.
ZEP Script는 자바스크립트가 아닌 언어로 실행 할 수 없기 때문에 타입스크립트를 자바스크립트로 변환하는 빌드 과정을 거쳐야 합니다.
main.ts 파일이 있는 폴더에서 명령 창을 열어 다음과 같이 빌드를 위한 명령어를 입력합니다.
npm run build 또는 npx zep-script build
빌드 완료 시 res
폴더에 main.js
파일이 생성됩니다.
5. 압축 파일 만들기
ZEP Script로 만든 앱을 배포하려면 main.js 파일과 앱에서 사용 할 이미지, html 파일 등을 함께 압축한 .zip 파일을 준비해야 합니다.
main.ts 파일이 있는 폴더에서 명령 창을 열어서 다음과 같이 압축 파일을 만들기 위한 명령어를 입력합니다.
npm run archive 또는 npx zep-script archive
압축 과정이 성공했다면 다음과 같이 폴더에 압축 파일이 생긴 것을 확인 할 수 있습니다.
압축 파일을 만드는 것을 마지막으로, 앱을 배포할 준비가 끝났습니다.
홈페이지에서 배포하기
위에서 만든 압축(ZIP) 파일을 홈페이지에서 업로드 하는 방식으로 앱을 배포할 수 있습니다.
ZEP Script 배포 가이드 페이지를 참고해서 앱을 배포해보세요!
2. 명령어(CLI)로 배포하기
CLI를 사용해 위에서 만든 압축(ZIP) 파일을 배포할 수 있습니다.
프로젝트 폴더에서 zep-script.json
파일을 열어 업로드 될 앱을 설정합니다.
( zep-script.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입니다)
설정이 끝났으면 프로젝트 폴더 경로에서 PowerShell 을 실행 한 뒤, 다음과 같이 배포를 하기 위한 명령어를 입력합니다.
npm run deploy 또는 npx zep-script publish
다음과 같이 email을 입력하는 부분이 나오면 배포 할 앱을 소유하고 있는 계정의 email을 입력합니다.
Sending login code to your email..이라는 메시지가 나오면 위에서 입력한 이메일의 메일 함에서 인증 코드를 확인한 뒤 입력해주면 배포가 시작됩니다.
다음 그림과 같이 Publishing… 텍스트 좌측에 초록색 체크 표시가 생기면 배포가 완료된 것 입니다.
나의 앱 페이지에서 zep-script.json 에 작성한 내용대로 앱이 배포된 것을 확인 할 수 있습니다.
배포에 실패하는 경우 아래의 단계를 따라 재시도 해보세요.
npx 명령어를 사용하는 경우
터미널에서 "npm i zep-script@latest -g
"명령을 실행하여 SDK 패키지를 최신화
root 폴더에서 ".zscsession"
파일 삭제
"npx zep-script publish" 명령어로 배포 시도
npm 명령어를 사용하는 경우
package.json 파일의 "zep-script" 버전이 최신이 아니라면 최신 버전으로 업데이트
npm i zep-script@latest
명령어 실행
root 폴더에서 ".zscsession"
파일 삭제
"npm run deploy" 명령어로 배포 시도
라이브러리 관련하여 보다 자세한 내용은 아래 github 레포지토리 내용을 참고해주세요.
zep-script-sdk/packages/zep-script-cli at main · zep-us/zep-script-sdk
활용 예 ) 탑승 앱, 찌르기 앱, 아바타 특수 효과 앱
// 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;
}
앱이 시작되어, 실행되고 종료될 때까지를 하나의 생애 주기(Lifecycle)라고 합니다. 관련 함수들을 이용하여 앱이 시작될 때, 실행 중일 때 그리고 종료될 때 등의 상황에서 필요한 동작들을 실행해 전체적인 앱의 생애주기를 만들 수 있습니다.
Lifecycle 함수들은 생애 주기에 맞게 기능을 만들 수 있는 필수적인 함수입니다. 아래 그림과 같이 앱이 실행될 때는 Enter 단계의 함수들이 동작하고, 앱이 실행 중 일때는 Update 단계의 함수들이 주기적으로 동작하며, 앱이 종료될 때는 Exit 단계의 함수들이 동작하게됩니다.
앱이 실행되어 종료될 때 까지 각 단계의 함수들을 잘 활용해 생애 주기에 맞게 App을 만들어보세요.
Lifecycle 함수 한 눈에 보기
App의 실행과 함께 호출되는 라이프사이클 Enter 단계에서 호출되는 함수를 안내합니다.
App이 최초로 시작될 때 한 번 호출됩니다.
파라미터
없음
예제
onInit 에서 채팅 출력해보기. ( 미니게임으로 만들어 확인해보세요. )
onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.
파라미터
예시
플레이어 입장시 메시지 출력해보기
모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.
파라미터
없음
예제
onStart에서 채팅 출력해보기 ( 미니게임으로 만들어 확인해보세요. )
Enter 단계 함수의 흐름 이해하기
Lifecycle Enter 단계의 흐름을 코드로 확인해보세요. 아래 코드를 미니게임으로 만들어 실행해보세요!
Update에는 약 20ms 마다 주기적으로 실행되는 함수 onUpdate가 있습니다.
onJoinPlayer, onLeavePlayer 등의 이벤트가 발생하면 해당 이벤트 처리 후 다시 onUpdate가 주기적으로 실행됩니다.
약 20ms 마다 주기적으로 실행되는 함수입니다.
파라미터
예시
onUpdate 함수를 이용해 10초 카운트다운 만들어보기
앱이 종료될 때 실행되는 함수 입니다.
퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.
파라미터
예시
플레이어 퇴장 시 메시지 출력해보기
다른 App이 실행되거나, 설치한 Game Block이 파괴될 때 동작합니다.
파라미터
없음
예제
Game Block 파괴 시 메시지 출력해보기 ( 미니게임 )
onInit
App이 최초로 시작될 때 한 번 호출되는 함수입니다.
onJoinPlayer
onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장 시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.
onStart
모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.
onUpdate
약 20ms 마다 주기적으로 실행되는 함수입니다.
onLeavePlayer
퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.
onDestroy
다른 App이 실행되거나 설치한 Game Block이 파괴될 때 동작합니다.
// 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(){
});
App.onInit.Add(function(){
App.sayToAll("-- onInit --")
App.sayToAll(" ready.. ")
App.sayToAll("------------")
});
player
Player
player는 입장하는 플레이어를 가르킴 파라미터의 이름은 임의로 변경가능
App.onJoinPlayer.Add(function(player){
App.showCenterLabel(`${player.name}님이 입장하셨습니다.`)
});
App.onStart.Add(function(){
App.sayToAll("-- App Start --")
});
// 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 --")
});
dt
Number
deltatime (전 프레임이 완료되기까지 걸린 시간, 약 20ms) dt 파라미터의 이름은 임의로 변경 가능
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;
}
})
player
Player
player 파라미터는 퇴장하는 플레이어를 가르킴 player 파라미터의 이름은 임의로 변경 가능
// 플레이어가 퇴장할 때 실행
App.onLeavePlayer.Add(function(player){
App.showCenterLabel(`${player.name}님이 퇴장하셨습니다.`)
});
// Game Block 파괴 시 실행
App.onDestroy.Add(function(){
App.showCenterLabel("게임 블록이 파괴되었습니다.")
});
// 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;
}
});
Field는 App과 관련된 속성 값들 입니다. 이 필드를 활용해 참가 중인 스페이스나 맵, 플레이어 정보 등을 조회하거나, 저장공간을 활용할 수 있습니다.
🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.
🔒 spaceHashID
App이 설치된 스페이스의 해쉬값을 보여줍니다.
🔒 mapHashID
App이 설치된 맵의 해쉬값을 가져옵니다.
🔒 creatorID
App을 실행한 플레이어의 ID 값을 가져옵니다.
🔒 players
맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.
🔒 playerCount
앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.
cameraEffect
카메라 이펙트의 종류를 셋팅할 변수 값
cameraEffectParam
카메라 이펙트 효과의 범위 값
displayRatio
화면의 줌을 컨트롤 하는 값
storage
스페이스 내의 App 데이터의 저장공간(스페이스 한정)
followPlayer
App의 따라가기 기능 활성화 여부 값
showName
플레이어 닉네임 숨김 여부
🔒 appHashID
앱의 HashID 값을 가져옵니다.
enableFreeView
앱이 설치된 맵의 둘러보기 허용 여부를 설정할 수 있습니다.
앱이 설치된 스페이스의 spaceHashID와 mapHashID를 가져옵니다. (스페이스와 맵 이해하기)
예제
앱이 설치된 맵의 spaceHashID와 mapHashID 출력해보기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
// 채팅창에 spaceHashID와 mapHashID 출력해보기
App.sayToAll(`spaceHashID: ${App.spaceHashID}`); // spaceHashID: Ak42Xz
App.sayToAll(`mapHashID: ${App.mapHashID}`) // mapHashId: 25g3RQ
})
미니게임을 실행한 플레이어의 ID 값을 가져옵니다. ⛔맵 내장형 앱인 노멀 앱과 사이드바 앱에서는 동작하지 않습니다.
예제
미니게임을 실행한 플레이어의 닉네임을 채팅창에 출력해보기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
if(player.id == App.creatorID){
App.sayToAll(`${player.name}님이 앱을 실행했습니다.`)
}
})
맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.
예제
맵에 있는 모든 플레이어 닉네임을 출력해보기
// 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)
}
})
앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.
예시
맵에 있는 플레이어 수 출력해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
// 현재 접속자 수를 채팅창에 출력
App.sayToAll(`접속자 수: ${App.playerCount}`)
})
App.cameraEffect: 카메라 이펙트의 종류를 셋팅할 변수 값
App.cameraEffectParam1: 카메라 이펙트 효과의 범위 값
예제
비네팅 효과를 On/Off 하는 키 함수 만들어보기
// 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()로 변경값을 적용
})
화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )
예제
화면의 줌을 컨트롤 하는 키 만들어보기
// q 키를 누르면 동작하는 함수
// 한 번 누르면 화면의 줌 값이 커지고, 한 번 더 누르면 원래대로 돌아오는 키 함수
// App.addOnKeyDown 설명
App.addOnKeyDown(81,function(p){
if(App.displayRatio == 1){
App.displayRatio = 5;
}else{
App.displayRatio = 1;
}
App.sendUpdated(); //* 앱의 Field값이 변경되면 App.sendUpdated()로 변경값을 적용
})
스페이스 내의 App 데이터의 저장공간 입니다 (스페이스 한정)
예제
Storage 페이지를 참고해주세요
App의 따라가기 기능 활성화 여부 값 입니다. ( 기본 값 : false )
노멀 앱, 미니게임 앱이 실행 중인 경우 followPlayer 값이 false로 설정되어 ‘따라가기’ 기능이 비활성화됩니다.
예제
따라가기 기능을 끄고 키는 함수 만들어보기
// 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()로 변경값을 적용
})
플레이어 닉네임 숨김 여부
App.showName 을 false로 설정하면 모든 플레이어의 닉네임이 숨김 처리됩니다.
예제
// 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()로 변경값을 적용
})
앱의 HashID를 가져옵니다.
예제
채팅창에앱의 HashID 출력하기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
App.sayToAll(`appHashID: ${App.appHashID}`);
})
앱이 설치된 맵의 둘러보기 허용 여부를 설정할 수 있습니다.
예제
단축키로 맵 둘러보기 허용 여부 설정하기
// 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);
});
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();
});
2) main.js
// 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;
}
});
Map.putTileEffect
함수에서 사용되는 타일 효과(TileEffectType)의 사용 방법을 안내하는 페이지입니다. 타일 효과에 대한 자세한 설명은 아래 링크를 참고해주세요.
🔥 타일 효과
NONE 타입의 타일 효과 입니다.
예시
//해당 좌표에 있는 타일 효과를 지웁니다.
Map.putTileEffect(x, y, TileEffectType.NONE);
이동불가(IMPASSABLE) 타일 효과 입니다
예시
//해당 좌표에 이동불가(IMPASSABLE) 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.IMPASSABLE);
플레이어가 맵에 진입 할 때 진입 지점을 설정하는 타일 효과 입니다
예시
//해당 좌표에 스폰(SPAWN) 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.SPAWN);
스페이스 내 다른 맵으로 이동 또는 맵 내 지정 영역으로 이동 하는 타일 효과 입니다.
파라미터
type
Number
type이 0 인 경우 스페이스 내 다른맵으로 이동하는 포털 타일을 설치합니다. type이 1 인 경우 맵 내 지정 영역으로 이동하는 포털 타일을 설치합니다.
targetMapID
String
이동할 맵의 MapID 값
label
String
포털 위에 표시할 텍스트 값
triggerByTouch
Boolean
true인 경우: 닿았을 때 실행 false인 경우: F를 눌러 실행
invisible
Boolean
true인 경우: 기본 포털 이미지를 숨김 false인 경우: 기본 포털 이미지가 보임
locationName
String
이동할 로케이션의 이름 값 ( type이 1인 경우 필수 입력 )
예시
// 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
});
외부 스페이스로 이동하는 타일 효과 입니다.
파라미터
label
String
포털 위에 표시할 텍스트 값
targetMapID
String
이동할 외부 스페이스 ID값 * https://zep.us/play/[스페이스 ID]
locationName
String
이동할 로케이션의 이름 값
triggerByTouch
Boolean
true인 경우: 닿았을 때 실행 false인 경우: F를 눌러 실행
invisible
Boolean
true인 경우: 기본 포털 이미지를 숨김 false인 경우: 기본 포털 이미지가 보임
예시
// 외부 스페이스로 이동하는 포털 타일을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.SPACE_PORTAL, {
label: "SPACE_PORTAL", // 선택
targetMapID: "zydmYD", //필수
locationName: "SPACE1", // 선택
invisible: true, // 선택, 기본값 false
triggerByTouch: true, // 선택, 기본값 false
});
팝업으로 웹 링크를 여는 타일 효과입니다.
파라미터
link
String
웹 URL 값
align2
String
팝업을 표시할 위치 ’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’
label
String
포털 위에 표시할 텍스트 값
triggerByTouch
Boolean
true인 경우: 닿았을 때 실행 false인 경우: F를 눌러 실행
invisible
Boolean
true인 경우: 기본 포털 이미지를 숨김 false인 경우: 기본 포털 이미지가 보임
예시
// 팝업으로 웹 링크를 여는 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.EMBED, {
link: "https://zep.us/", // 필수
align2: "top", // 필수
label: "ZEP-SCRIPT-EMBED", // 선택
});
새 탭으로 웹 링크를 여는 타일 효과입니다.
파라미터
link
String
웹 URL 값
label
String
포털 위에 표시할 텍스트 값
invisible
Boolean
true인 경우: 기본 포털 이미지를 숨김 false인 경우: 기본 포털 이미지가 보임
예시
// 새탭으로 웹 링크를 여는 타일 효과를 설치합니다.
Map.putTileEffect(x, y, TileEffectType.WEB_PORTAL, {
link: "https://zep.us/", // 필수
label: "ZEP-SCRIPT-WEB-PORTAL", // 선택
invisible: true, // 선택, 기본값 false
});
웹 화면을 고정 영역에 표시하는 타일 효과입니다.
파라미터
link
String
웹 URL 값
width
number
고정 영역의 너비 ( 타일 수 )
height
number
고정 영역의 높이 ( 타일 수 )
예시
// 웹 화면을 고정 영역에 설치합니다.
Map.putTileEffect(x, y, TileEffectType.TILE_EMBED, {
link: "https://zep.us/", // 필수
width: 5, // 필수
height: 5, // 필수
});
프라이빗 영역 타일 효과 입니다.
파라미터
id
Number
프라이빗 영역의 ID 값
impassable
Boolean
true일 경우 프라이빗 영역을 impassable(통과 불과)로 만듭니다
param1
String
param1 이 “true” 일 경우 인원제한 프라이빗 영역이 설정됩니다.
예시
// 해당 좌표에 프라이빗 영역을 설치합니다.
Map.putTileEffect(18, 15, TileEffectType.PRIVATE_AREA, {
id: 3, // 필수
impassable: false, // 선택, 기본 값 false
param1: "true", // 선택, 기본 값 "false"
});
지정영역 타일 효과 입니다.
파라미터
label
String
타일 위에 표시할 텍스트 값
name
String
지정 영역의 이름
width
number
고정 영역의 너비 ( 타일 수 )
height
number
고정 영역의 높이 ( 타일 수 )
예시
// 해당 좌표에 지정 영역을 설치합니다.
Map.putTileEffect(x, y, TileEffectType.LOCATION, {
label: "LOCATION", // 선택
name: "zep-script-location", // 필수
width: 3, // 필수
height: 2, // 필수
});
배경 음악 타일 효과 입니다.
파라미터
link
String
재생할 음악 파일의 이름 ( 압축 파일에 포함 )
activeDistance
Number
음악의 재생 범위 ( 타일 )
triggerByTouch
Boolean
true인 경우: 닿았을 때 실행 false인 경우: F를 눌러 실행
예시
Map.putTileEffect(x, y, TileEffectType.AMBIENT_SOUND, {
link: "ring.mp3", // 필수
activeDistance: 1, // 필수
triggerByTouch: false, // 선택
});
스크립트 개발자가 지정한 키를 플레이어가 눌렀을 때 또는 스크립트 개발자가 지정한 지점에 도착했을 때 등, 조건을 설정하여 플레이어가 조건을 달성했을 경우 동작하는 함수들 입니다.
runLater
지정한 시간(초) 후 동작하는 함수 입니다.
addOnTileTouched
지정한 x, y 좌표에 플레이어가 도착했을 때 실행되는 함수입니다.
addOnLocationTouched
지정한 ‘지정영역’에 플레이어가 도착했을 때 실행되는 함수입니다.
addOnKeyDown
플레이어가 지정된 키를 눌렀을 때 실행되는 함수 입니다.
setTimeout
지정한 시간(ms) 후 함수를 실행합니다.
setInterval
지정한 시간(ms) 간격으로 함수를 실행합니다.
addMobileButton
모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.
putMobilePunch
모바일 환경에서 펀치 버튼을 추가합니다.
putMobilePunchWithIcon
로드한 이미지로 펀치 버튼을 만들어 추가합니다.
Callbacks 함수 한 눈에 보기
// 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)
time(초) 후에 callback 함수를 실행합니다.
파라미터
time
Number
몇 초 후에 실행 될 지를 정하는 시간 (초)
예제
앱이 시작되고 5초 후 메시지 출력해보기
App.onStart.Add(function () {
App.runLater(function() {
App.showCenterLabel("메시지");
}, 5);
});
지정한 x, y좌표에 플레이어가 도착할 경우 callback 함수를 실행합니다.
파라미터
x, y
number
지정 할 x, y 좌표
예제
플레이어가 지정 좌표에 도착 했을 때 메시지 출력해보기
// 플레이어가 5, 5 좌표에 도착한 경우
App.addOnTileTouched(5, 5, function (player) {
App.showCenterLabel(`${player.name}님이 (5, 5) 좌표에 도착!`);
});
플레이어가 맵에디터에서 지정한 ‘지정영역’에 도착했을 때 callback 함수를 실행합니다.
파라미터
name
String
맵 에디터에서 지정한 ‘지정 영역’의 이름
player
Player
지정 영역에 도착한 플레이어를 가르킴 파라미터의 이름은 임의로 지정 가능
예제
플레이어가 지정 영역에 도착했을 때 메시지 출력해보기
// 플레이어가 이름이 "myLocation"인 영역에 도착했을 때 실행
App.addOnLocationTouched("myLocation", function(player){
App.showCenterLabel(`${player.name}님이 myLocation에 도착했습니다.`)
});
플레이어가 지정된 키를 눌렀을 때 callback 함수를 실행합니다.
파라미터
keycode
Number
키에 해당하는 숫자
player
Player
해당 키를 누른 플레이어를 가르킴 player 파라미터 이름은 임의로 변경 가능
예제
a를 눌렀을 때 메시지 출력해보기 ( a의 키코드: 65 )
// 플레이어가 a를 눌렀을 때 실행
App.addOnKeyDown(65, function(player){
App.sayToAll(`${player.name}님이 a키를 눌렀습니다.`)
});
time(ms) 후에 callback 함수를 실행합니다.
파라미터
time
Number
callback 함수 실행 전 대기 시간 (ms)
예제
앱이 시작되고 5초 후 메시지 출력해보기
App.onStart.Add(function () {
setTimeout(function () {
App.sayToAll("5초후 메시지 출력");
}, 5000);
});
time(ms) 간격으로 callback 함수를 실행합니다.
파라미터
time
Number
callback 함수 실행 주기 (ms)
예제
앱이 시작되고 1초 간격으로 메시지 출력해보기
let time = 0;
App.onStart.Add(function () {
setInterval(function () {
App.sayToAll(`앱 실행 ${++time}초 경과`);
}, 1000);
});
모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.
모바일버튼의을이미지를 원하는 이미지로 변경 할 수 있습니다.
파라미터
anchor
Number
모바일 버튼의 위치를 숫자로 입력합니다. TOP = 0, TOP_LEFT = 1, TOP_RIGHT = 2, MIDDLE = 3, MIDDLE_LEFT = 4, MIDDLE_RIGHT = 5, BOTTOM = 6, BOTTOM_LEFT = 7, BOTTOM_RIGHT = 8
posX
Number
모바일 버튼 x 방향 오프셋 수치
posY
Number
모바일 버튼 y 방향 오프셋 수치
player
Player
모바일 버튼을 누른 플레이어를 가르킴
예제
모바일 버튼 추가해보기
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 버튼`);
});
});
enable이 true이면 모바일 환경에서 펀치 버튼이 추가됩니다.
파라미터
enable
Boolean
모바일 펀치 버튼 활성화 여부 ( 기본 값 true )
예제
Q 버튼을 눌러 모바일 환경에 펀치 버튼을 추가/제거 해보기
let punchButton = false;
// Q 버튼을 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
if (!punchButton) {
punchButton = true;
App.putMobilePunch();
} else {
punchButton = false;
App.putMobilePunch(false);
}
});
로드한 이미지로 펀치 버튼을 만들어 추가합니다.
파라미터
icon
ScriptDynamicResource
App.loadSpriteSheet 함수로 로드한 이미지 리소스
예제
Q 버튼을 눌러 모바일 환경에 로드한 이미지로 펀치 버튼 추가하기
const punchIcon = App.loadSpritesheet("punchIcon.png")
// Q 버튼을 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
App.putMobilePunchWithIcon(punchIcon);
});
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;
}
});
ZEP 캔버스 위에 내가 원하는 형태의 UI를 그릴 수 있습니다. ZEP에서는 HTML 을 통해 UI를 표현할 수 있습니다.
App.showWidget()
함수를 통해, HTML 파일을 특정 위치에 불러올 수 있습니다. 이 때 인자로 파일명, 정렬, 위젯의 가로와 세로 크기를 정해줍니다. 정렬에 들어가는 값은 ScriptApp에서 Methods를 참고해주세요.
생성한 변수에 sendMessage()
메소드를 통해, 원하는 값을 HTML 파일에 전달할 수 있습니다.
main.js에서 전달 받은 값을 사용하려면, HTML 문법에 맞게 작성한 다음 addEventListener
를 통해 message 타입을 아래 예시와 같이 값을 받아올 수 있습니다.
main.js
//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>
<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>
참고
- 튜토리얼의 App type은 installable App을 권장합니다.
- .js파일의 파일이름은 반드시 main 이어야 합니다. main.js 파일을 준비합니다.
- 배포 방법을 아직 모르신다면, ZEP Script 배포 가이드를 참고해주세요.
backspace
8
tab
9
enter
13
shift(left)
16
shift(right)
16
ctrl(left)
17
ctrl(right)
17
alt(left)
18
alt(right)
18
pause/break
19
caps lock
20
escape
27
space
32
page up
33
page down
34
end
35
home
36
left arrow
37
up arrow
38
right arrow
39
down arrow
40
print screen
44
insert
45
delete
46
0
48
1
49
2
50
3
51
4
52
5
53
6
54
7
55
8
56
9
57
a
65
b
66
c
67
d
68
e
69
f
70
g
71
h
72
i
73
j
74
k
75
l
76
m
77
n
78
o
79
p
80
q
81
r
82
s
83
t
84
u
85
v
86
w
87
x
88
y
89
z
90
left window key
91
right window key
92
select key
93
numpad 0
96
numpad 1
97
numpad 2
98
numpad 3
99
numpad 4
100
numpad 5
101
numpad 6
102
numpad 7
103
numpad 8
104
numpad 9
105
multiply
106
add
107
substract
109
decimal point
110
divide
111
f1
112
f2
113
f3
114
f4
115
f5
116
f6
117
f7
118
f8
119
f9
120
f10
121
f11
122
f12
123
num lock
144
scroll lock
145
audio volume mute
173
audio volume down
174
media player
175
audio volume up
181
launch application 1
182
launch application 2
183
semi-colon
186
equal sign
187
comma
188
dash
189
period
190
forward slash
191
Backquote
192
open bracket
219
back slash
220
close bracket
221
single quote
222
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;
}
});
Spritesheet를 로드한 후, 특정 이벤트 발생 시 Player에 적용시키면 캐릭터의 외양을 변경할 수 있습니다. 아래 예제 문서를 참고해주세요.
ZEP Script를 사용해 앱을 개발하고 배포하는 과정은 무료입니다.
ZEP Script에서 제공하는 결제 함수를 사용해 과금을 하는 것이 가능합니다.
자세한 내용은 API 문서를 참고해주세요,
ZEP 에셋스토어에 앱을 업로드하고 판매할 수 있습니다.
보다 나은 개발 환경을 현재 준비 중에 있습니다.
현재 ZEP Script 개발 환경은 Javascript 와 Typescript 언어로 제공하고 있습니다.
ZEP Script는 아래 방식을 통해서 Javascript 통신을 지원하고 있습니다. 자세한 내용은 아래 문서를 참고해주세요.
🛰️ 외부 API 통신하기
소개
플레이어가 지정된 채팅을 입력하거나, 특정한 오브젝트를 공격할 와 같이 ZEP 스페이스에서 발생하는 특정 상황에 반응하여 동작하는 함수들 입니다.
EventListener가 비정상적으로 많이 추가되는 경우 앱이 종료될 수 있습니다.
onUpdate문 또는 반복적으로 실행되는 문에서 EventListener를 등록하는 것을 지양해주세요.
onSay
플레이어가 채팅을 입력할 때 동작하는 함수입니다.
onPlayerTouched
캐릭터들끼리 충돌할 때 동작하는 함수입니다.
onObjectTouched
캐릭터가 오브젝트와 충돌할 때 동작하는 함수입니다.
onAppObjectTouched
플레이어가 키 값을 가진 오브젝트와 충돌할 때 동작하는 함수입니다.
onUnitAttacked
공격 키(Z)로 다른 캐릭터를 공격할 때 동작하는 함수입니다.
onObjectAttacked
공격 키(Z)로 오브젝트를 공격할 때 동작하는 함수입니다.
onSidebarTouched
플레이어가 사이드바 앱을 클릭(터치)할 때 실행되는 함수입니다.
onTriggerObject
오브젝트와 F 상호작용 시 동작하는 함수입니다.
onAppObjectAttacked
플레이어가 키 값을 가진 오브젝트를 공격(Z키) 할 때 동작하는 함수입니다.
Event Listener 함수 한 눈에 보기
// 플레이어들이 채팅창에 입력하는 모든 채팅에 대해 호출 되는 이벤트
// !로 시작하는 텍스트는 채팅창에 나오지 않으나, onSay 함수에는 사용 가능
App.onSay.Add(function(player, text) {
});
// 플레이어가 다른 플레이어와 부딪혔을 때 호출 되는 이벤트
App.onPlayerTouched.Add(function(sender, target, x, y){
});
// 플레이어가 오브젝트와 부딪혔을 때 호출 되는 이벤트
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) {
});
플레이어가 채팅을 입력할 때 동작합니다.
파라미터
player
Player
player 파라미터는 채팅을 입력한 플레이어를 가르킴 player 파라미터의 이름은 임의로 변경 가능
text
String
text는 입력한 채팅 내용을 가르킴 text 파라미터의 이름은 임의로 변경 가능
예제
초성퀴즈 - 채팅으로 정답 맞추는 기능 만들어보기
_answer = "ZEP" // 정답
// 플레이어가 채팅을 칠 때 실행
App.onSay.add(function(player, text) {
if(_answer == text){
App.showCenterLabel(player.name + '님 정답!\n정답은 ' + _answer);
}
});
캐릭터들끼리 충돌할 때 동작합니다.
파라미터
sender
Player
sender는 부딪힌 주체자를 가르킴
target
String
target은 부딪힘을 당한 플레이어를 가르킴
x, y
Number
x, y는 충돌한 x, y 좌표를 가르킴
sender, target, x, y 파라미터의 이름은 임의로 변경 가능
예제
캐릭터끼리 부딪힐 때 메시지 출력해보기
// 플레이어끼리 부딪힐 때 실행
App.onPlayerTouched.Add(function (sender, target, x, y) {
App.showCenterLabel(
`${sender.name}님과 ${target.name}님이 좌표: (${x}, ${y}) 에서 부딪혔습니다.`
);
});
캐릭터가 오브젝트와 충돌 또는 상호작용 할 때 한 번 실행됩니다.
파라미터
sender
Player
sender는 오브젝트와 부딪힌 플레이어를 가르킴
x, y
Number
오브젝트와충돌한 x, y 좌표를 가르킴
tileID
Number
오브젝트의 타일 ID 입니다.
obj
Object
오브젝트 객체
예제
⭐ overlap: true
속성이 없는 오브젝트는 충돌해도 함수가 실행되지 않습니다.
let testObject = App.loadSpritesheet("object.png");
// 사용 가능한 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})`
);
});
️ 캐릭터가 키 값을 가진 오브젝트와 충돌할 때 동작합니다.
파라미터
sender
Player
sender는 오브젝트와 부딪힌 플레이어를 가르킴
key
String
오브젝트의 Key 값
x, y
Number
x, y는 충돌한 x, y 좌표를 가르킴
예제
라벨 출력 예제
⭐ overlap: true
속성이 없는 오브젝트는 충돌해도 함수가 실행되지 않습니다.
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}에서 충돌!`
);
});
플레이어가 공격 키(Z)로 다른 캐릭터를 공격할 때 동작합니다.
파라미터
sender
Player
sender는 공격한 플레이어를 가르킴
x, y
Number
x, y는 공격한 플레이어의 x, y 좌표 값을 가르킴
target
Player
target은 공격 받은 플레이어를 가르킴
sender, x, y, target 파라미터의 이름은 임의로 변경 가능
예제
플레이어를 공격 할 때 메시지 출력해보기
// Z 키로 플레이어를 공격할 때 실행
App.onUnitAttacked.Add(function (sender, x, y, target) {
App.showCenterLabel(`${sender.name}님이 ${target.name}님을 공격했습니다.`);
App.sayToAll(`(${x}, ${y})`);
});
플레이어가 공격 키(Z)로 오브젝트를 공격할 때 동작합니다.
파라미터
sender
Player
sender는 공격한 플레이어를 가르킴
x, y
Number
x, y는 오브젝트의 x, y 좌표 값을 가르킴
sender, x, y 파라미터의 이름은 임의로 변경 가능
예제
오브젝트를 공격할 때 메시지 출력해보기
⭐ overlap: true
속성이 없는 오브젝트는 공격해도 함수가 실행되지 않습니다.
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})에 위치 오브젝트를 공격했습니다.`
);
})
플레이어가 사이드바 앱을 클릭(터치) 할 때 동작합니다.
파라미터
player
Player
사이드바 앱을 클릭한 player를 가르킴
예제
사이드바 앱 클릭 시 채팅창 메시지 출력하기.
App.onSidebarTouched.Add(function (player) {
App.sayToAll(`${player.name}님이 사이드바 앱을 클릭했습니다.`)
});
관련 페이지
오브젝트와 F 상호작용 시 동작하는 함수를 작성할 수 있습니다.
맵에디터로 설치한 오브젝트 또는 스크립트에서 type
을 INTERACTION_WITH_ZEPSCRIPTS
(21)
로 설치한 오브젝트와 상호작용 시 동작합니다.
파라미터
player
Player
오브젝트와 상호작용한 player를 가르킴
layerID
Number
오브젝트가 설치된 레이어 ID 오브젝트인 경우 layerID = 3 상단 오브젝트인 경우 layerID = 5
x, y
Number
상호작용한 오브젝트의 x, y 좌표
key
String
상호작용한 오브젝트의 key 값
예제
오브젝트와 F 상호작용시 메시지 출력하기
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}`);
});
플레이어가 공격 키(Z)로 키 값을 가진 오브젝트를 공격할 때 동작합니다.
관련 문서: 오브젝트 npcProperty
파라미터
sender
Player
sender는 공격한 플레이어를 가르킴
x, y
Number
x, y는 오브젝트의 x, y 좌표 값을 가르킴
layer
Number
오브젝트가 설치된 레이어
key
String
공격한 오브젝트의 키 값
예제
키 값을 가진 오브젝트를 공격할 때 메시지 출력해보기
⭐ collide: true
속성이 없는 키 값 오브젝트는 공격해도 함수가 실행되지 않습니다.
App.onAppObjectAttacked.Add(function (sender, x, y, layer, key) {
App.showCenterLabel(
`sender: ${sender.name}
coordinates: (${x}, ${y})
layer: ${layer}
key: ${key}`
);
});
관련 페이지
활용 앱 ) 카메라 관전 프리미엄 기능
Example
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);
});
ZEP-Script API 한눈에 보기
App이 최초로 시작될 때 한 번 호출됩니다.
onInit이 호출된 후, 접속해 있는 모든 플레이어를 해당 이벤트를 통해 입장시키고, 이후 입장하는 플레이어가 있을 때 마다 동작합니다.
모든 플레이어가 onJoinPlayer를 통해 입장한 후 한 번 호출 됩니다.
약 20ms 마다 주기적으로 실행되는 함수입니다.
퇴장하는 플레이어가 있을 때 마다 동작합니다. 이후, 다른 App이 실행되거나 설치한 Game Block이 파괴될 때 모든 플레이어를 이 함수를 통해 퇴장시킵니다.
다른 App이 실행되거나, 설치한 Game Block이 파괴될 때 동작합니다.
앱이 설치된 스페이스의 spaceHashID와 mapHashID를 가져옵니다. (스페이스와 맵 이해하기)
App을 실행한 플레이어의 ID 값을 가져옵니다.
맵에 있는 모든 플레이어 리스트를 배열로 가져옵니다.
앱이 설치된 맵에 있는 플레이어의 수를 가져옵니다.
App.cameraEffect: 카메라 이펙트의 종류를 셋팅할 변수 값
App.cameraEffectParam1: 카메라 이펙트 효과의 범위 값
화면의 줌을 컨트롤 하는 값 (기본 값: 1)
스페이스 내의 App 데이터의 저장공간 입니다. (스페이스 한정) Storage 페이지를 참고해주세요.
App의 따라가기 기능 활성화 여부 값 입니다. (기본 값 : false)
플레이어의닉네임 숨김여부 값 입니다. (기본 값: true)
앱의 HashID를 가져옵니다.
App.setStorage 함수로 App storage를 저장하고, 앱을 사용중인 다른 맵의 App storage 변경사항이 있을 경우 App.getStorage 함수로 App storage를 동기화 할 수 있습니다.
App.getStorage 함수는 비동기 함수이기 때문에 App.getStorage 함수 다음 라인에 App.storage를 사용하는 코드를 작성할 경우 동기화를 보장할 수 없습니다.
플레이어가 채팅을 입력할 때 동작합니다.
캐릭터들끼리 충돌할 때 동작합니다.
캐릭터가 오브젝트와 충돌할 때 동작합니다.
️ 캐릭터가 키 값을 가진 오브젝트와 충돌할 때 동작합니다.
플레이어가 공격 키(Z)로 다른 캐릭터를 공격할 때 동작합니다.
플레이어가 공격 키(Z)로 오브젝트를 공격할 때 동작합니다.
플레이어가 사이드바 앱을 클릭(터치) 할 때 동작합니다.
오브젝트와 F 상호작용 시 동작하는 함수입니다.
플레이어가 공격 키(Z)로 키 값을 가진 오브젝트를 공격할 때 동작합니다.
time(초) 후에 callback 함수를 실행합니다.
지정한 x, y좌표에 플레이어가 도착할 경우 callback 함수를 실행합니다.
플레이어가 맵에디터에서 지정한 ‘지정영역’에 도착했을 때 callback 함수를 실행합니다.
플레이어가 지정된 키를 눌렀을 때 callback 함수를 실행합니다.
time(ms) 후에 callback 함수를 실행합니다.
time(ms) 간격으로 callback 함수를 실행합니다.
모바일 환경에서 커스텀 모바일 버튼을 추가하고, 버튼을 눌렀을 때 동작하는 함수를 지정합니다.
enable이 true이면 모바일 환경에서 펀치 버튼이 추가됩니다.
로드한 이미지로 펀치 버튼을 만들어 추가합니다.
스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
text 부분에 span
태그를 넣어 텍스트를 꾸밀 수 있습니다.
채팅창에 text 내용을 출력합니다.
Staff이상 권한의 유저 채팅창에 text 내용을 출력합니다.
모든 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.
링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.
playerID 에 해당하는 플레이어를 tileX, tileY 좌표로 이동시키는 함수입니다.
playerID 에 해당하는 플레이어를 추방하는 함수입니다.
미니게임 앱을 강제 종료하는 함수입니다.
모든 채팅 내용을 삭제하는 함수입니다.
id 에 해당하는 플레이어를 반환하는 함수입니다.
모든 플레이어에게 사운드를 재생하는 함수입니다.
모든 플레이어에게 사운드를 재생하는 함수입니다.
재생되고 있는 사운드를 멈추는 함수입니다.
http get 요청을 보내는 함수입니다.
Form-Data 형태의 http post 요청을 보내는 함수 입니다.
Json 형태의 http post 요청을 보내는 함수 입니다.
App 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
찌르기(Z키) 공격 효과음을 변경하는 함수입니다.
맵의 너비와 높이 값을 가져옵니다.
지정한 좌표에 타일 효과를 적용하는 함수입니다.
지정한 좌표에 오브젝트를 놓는 함수입니다. (기준 좌표: Left Top) → 기준 좌표란?
오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다. 이 기능을 사용하면 한 번에 많은 오브젝트를 설치할 경우 부하를 줄이는 효과를 얻을 수 있습니다.
지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다. (기준 좌표: Left Top)
해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.
해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
해당 좌표 지점에 Map.putObject 함수가 선행되어야합니다.
key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
x, y 좌표에 위치한 오브젝트를 targetX, targetY로 time 초 동안 움직이는 함수입니다.
해당 좌표 지점에 Map.putObject 함수가 선행되어야합니다.
key 값을 가진 오브젝트를 targetX, targetY로 움직이는 함수입니다.
ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.
해당하는 레이어의 x, y 좌표에 있는 타일의 타입 값을 리턴, 타일이 없으면 -1을 리턴합니다.
파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.
파라미터로 전달한 로케이션이 2개이상 존재하는 경우, 무작위로 선택하여 로케이션 설치 좌표를 리턴합니다.
맵에 해당 로케이션이 있는지 체크하여 true/false 값을 리턴합니다.
Type에 해당하는 오브젝트들을 리턴하는 함수입니다.
Type에 해당하는 상단오브젝트들을 리턴하는 함수입니다.
key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.
플레이어의 id, 닉네임 값을 가져옵니다.
title은 캐릭터 닉네임 위에 노란색으로 노출되는 텍스트입니다.
role은 플레이어의 권한을 나타내는 숫자 값 입니다. 플레이어의 role에 따라 다음과 같은 값을 출력합니다.
게스트
-1
스태프
2000
멤
0
관리자
3000
에디터
1000
맵소유자
3001
플레이어의 캐릭터가 서있는 X 좌표 값과 Y 좌표 값입니다.
플레이어의 캐릭터가 바라보고 있는 방향입니다.
캐릭터가 바라보고 있는 방향에 따라 다음과 같은 값을 출력합니다.
플레이어의 이동속도 값입니다. (기본 값: 80)
플레이어 캐릭터의 스프라이트 이미지입니다. (null 입력 시 기본 아바타 이미지로 초기화)
tag를 사용해 플레이어에게 필요한 속성 값을 부여 할 수 있습니다.
hidden 값이 true 이면, 해당 플레이어는 다른 플레이어에게 보이지 않습니다.
플레이어의 스팟 라이트 기능 활성화 여부입니다.
플레이어의 공격(Z키) 타입입니다. (기본: 0)
공격(Z키) 이미지가 날아가는 거리 속성입니다. 공격 가능 거리는 늘어나지 않습니다.
공격 가능 거리 속성입니다. attackType이 원거리 공격으로 설정 된 경우에만 유효합니다.
공격(Z키) 이미지 속성입니다.
플레이어의 전자지갑 주소입니다.
스페이스 내의 Player 값 저장 공간 입니다 (스페이스 한정)
플레이어의 모바일 접속 여부를 true/false 로 출력합니다.
플레이어가 움직이고 있으면 True, 아니면 False를 반환합니다.
플레이어가 점프하고 있으면 True, 아니면 False를 반환합니다.
URL 쿼리스트링으로 전달 받은 값을 저장하는 필드입니다. 🔥 URL 쿼리스트링 활용하기
플레이어화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )
플레이어의 타이틀 색상을 읽거나 수정 할 수 있습니다.
플레이어의 이메일 Hash 값을 가져옵니다.
비로그인 플레이어인 경우 true 값을 가집니다.
5분 이상 비활성화된 유저인 경우 true
값을 가집니다.
해당 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
text 부분에 span
태그를 넣어 텍스트를 꾸밀 수 있습니다.
해당 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.
플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다. payToSpaceOwner 옵션이 false인 경우 앱 소유자에게 수익이 전달되며, true인 경우에는 앱이 설치된 맵의 소유자에게 수익이 전달됩니다.
플레이어의 구매 위젯을 닫습니다.
유저 개인에게 채팅 메시지를 보내는 함수입니다.
플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.
플레이어에게 확인창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다. cancel을 누를 경우에는 callback 함수가 동작하지 않습니다.
플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.
위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의 하여 위젯을 표시합니다.
화면의 크기가 여백을 포함한 위젯 영역보다 작아질 경우, 위젯의 크기가 비례하여 작아집니다
플레이어에게 웹 URL을 새 창이나 팝업 창으로 표시합니다.
해당 플레이어에게 지정된 align의 위치에 url 임베드 화면을 표시하는 함수입니다.
플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.
플레이어에게 텍스트 창을 보여주는 함수입니다.
해당 플레이어의 이메일이 파라미터 값과 같다면 true, 아니면 false를 리턴합니다.
플레이어가 서있는 지정영역의 이름을 출력합니다.
플레이어의 캐릭터를 tileX, tileY 좌표로 지정한 방향을 바라보게 이동시킵니다.
플레이어의 캐릭터를 name에 해당하는 지정 영역으로 지정한 방향을 바라보게 이동시킵니다.
플레이어를 해당 스페이스 해당 맵으로 이동시킵니다.
[1] 플레이어의 시점을 지정된 좌표로 중심 이동시킵니다.
[2] 플레이어의 시점을 특정 오브젝트로 중심 이동시킵니다.
플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.
해당 플레이어에게 사운드를 재생하는 함수입니다.
모든 플레이어에게 사운드를 재생하는 함수입니다.
App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
App, Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.
위젯의 아이디 값을 가져옵니다.
위젯에서 App으로 메시지를 보내면 callback 함수가 동작합니다.
App에서 위젯으로 데이터를 보냅니다.
위젯을 삭제하는 함수입니다.
ZEP 서버의 현재 시간을 milliseconds 단위의 값으로 리턴합니다.
현재 UTC 시간을 milliseconds 단위의 값으로 리턴합니다.
timeB - timeA
를 계산하고, 그 결과를 지정한 returnType
으로 반환합니다.
화면에 UI를 표시, 유저 이동 또는 강퇴, 사운드 재생 등 편리한 기능을 제공하는 함수들 입니다.
Methods 함수는 용도에 따라 UI , User Control, Sound, 통신, 공통 메소드로 나눌 수 있습니다.
loadSpritesheet
스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.
showCenterLabel
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
showCustomLabel
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다. text 부분에 span 태그를 넣어 텍스트를 꾸밀 수 있습니다.
sayToAll
모든 플레이어의 채팅창에 text 를 출력하는 함수입니다.
sayToStaffs
Staff 권한 이상의 플레이어의 채팅창에 text 를 출력하는 함수입니다.
showWidget
모든플레이어에게 지정된 align의 위치에 html파일을 위젯으로 불러오는 함수입니다.
showYoutubeWidget
링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.
spawnPlayer
플레이어를 지정한 x, y 좌표로 이동 시키는 함수입니다.
kickPlayer
플레이어를 추방하는 함수입니다.
forceDestroy
미니게임 앱을 강제 종료하는 함수입니다.
clearChat
모든 채팅 내용을 삭제하는 함수입니다.
getPlayerByID
id 에 해당하는 플레이어를 반환하는 함수입니다.
playSound
사운드 파일을 재생하는 함수입니다.
playSoundLink
사운드 URL을 재생하는 함수입니다.
stopSound
재생 중인 모든 사운드를 멈추는 함수입니다.
httpGet
http get 요청을 보내는 함수입니다.
httpPost
Form-Data 형태의 http post 요청을 보내는 함수 입니다.
httpPostJson
JSON 형태의 http post 요청을 보내는 함수입니다.
sendUpdated
App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
UI 함수 한 눈에 보기
// 스프라이트 시트 그림 파일을 읽어 객체화
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
스프라이트 시트 그림 파일을 읽어 객체화하는 함수입니다.
ScriptDynamicResource에 대한 이해를 돕는 스프라이트시트 이해하기 문서를 확인해보세요!
파라미터
fileName
String
불러올 파일의 이름
frameWidth frameHeight
number
한 프레임의 가로, 세로 픽셀 크기
anims
Array
애니메이션으로 지정할 frame 이미지 번호 배열
frameRate
number
프레임 하나 당 데이터를 표시하는 속도 frameRate: 8 → 1초에 8개의 이미지를 보여줌
예제
페인트맨 - 블루맨 스프라이트 이미지 적용해보기
// 한 프레임의 사이즈 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();
});
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
파라미터
text
String
라벨에 출력할 텍스트
color
Uint
출력할 글씨의 색 (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
bgColor
Uint
메시지가 출력되는 라벨의 배경색 값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.
offset
number
offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다. 값을 입력하지 않을 경우, 0으로 지정됩니다.
time
number
라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )
예제
노란색 배경 입장 메시지 라벨을 2초간 출력해보기
App.onJoinPlayer.Add(function(player){
App.showCenterLabel(`${player.name}님이 입장하셨습니다.`, 0x000000, 0xFFFF00, 200, 2000); // 노란색 배경, 검정색 글씨로 표시하기
});
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
text 부분에 span
태그를 넣어 텍스트를 꾸밀 수 있습니다.
파라미터
text
String
라벨에 출력할 텍스트 ( span 태그 허용 )
color
Uint
출력할 글씨의 색 (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다.
bgColor
Uint
메시지가 출력되는 라벨의 배경색 값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다. ➡️
offset
number
offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다. 값을 입력하지 않을 경우, 0으로 지정됩니다.
width
number
라벨의 너비를 n%로 설정하는 값 입니다. (기본 값 100)
opacity
number
라벨의 배경 투명도를 설정하는 값 입니다. (기본 값 0.6, 범위 0 ~ 1)
time
number
라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )
옵션
key
String
라벨에 키 값을 할당하여, 서로 다른 키 값을 가진 라벨은 동시에 표시할 수 있습니다.
borderRadius
String
라벨의 모서리에 둥글기를 설정할 수 있습니다. ex) borderRadius: "8px"
fontOpacity
boolean
false로 설정시 폰트에 투명도가 적용되지 않습니다.
padding
String
라벨 내부에 padding 값을 지정 할 수 있습니다. ex) padding: "8px"
예제
커스텀 라벨 예제 코드 페이지를 참고해주세요
채팅창에 text 내용을 출력합니다.
파라미터
text
String
채팅창에 출력할 텍스트
color
Uint
출력할 글씨의 색을 지정합니다. (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
예제
입장메시지를 하늘색으로 출력해보기
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.sayToAll(`[info]${player.name} has entered.`, 0x00ffff); // 하늘색으로 표시하기
});
Staff이상 권한의 유저 채팅창에 text 내용을 출력합니다.
파라미터
text
String
채팅창에 출력할 텍스트
color
Uint
출력할 글씨의 색을 지정합니다. (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
예제
입장메시지를 하늘색으로 출력해보기
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.sayToStaffs(`[Staff] ${player.name} has entered.`, 0x00ffff); // 하늘색으로 표시하기
});
모든 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.
파라미터
fileName
String
불러올 파일의 이름
align
String
위젯을 표시할 위치 ’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’
width height
number
위젯을 표시할 영역의 가로, 세로 크기(px)
예제
초성퀴즈 위젯 따라해보기
let _widget = null;
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
_widget = App.showWidget("widget.html", "top", 200, 300); // 화면 상단, 200x300 영역에 위젯을 보여줌
_widget.sendMessage({
timer: 15,
answer: "ㅅㅍㅋ",
});
});
링크에 해당하는 Youtube 컨텐츠를 위젯으로 불러옵니다.
파라미터
link
String
Youtube 컨텐츠 url
align
String
위젯을 표시할 위치 ’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’
width height
number
위젯을 표시할 영역의 가로, 세로 크기(px)
예제
Youtube 위젯 표시해보기
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.showYoutubeWidget("https://www.youtube.com/watch?v=SXnMGIR8cjY","top",600,500);
});
Control 함수 한 눈에 보기
// 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);
playerID 에 해당하는 플레이어를 tileX, tileY 좌표로 이동시키는 함수입니다.
파라미터
playerID
String
플레이어의 ID 값
tileX tileY
number
플레이어를 이동시킬 x, y 좌표 값
예제
입장하는 플레이어를 지정 좌표로 이동시키기
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.spawnPlayer(player.id, 5, 5); // 플레이어를 5,5 위치로 이동시키기
});
playerID 에 해당하는 플레이어를 추방하는 함수입니다.
파라미터
playerID
String
플레이어의 ID 값
예제
강퇴 명령어 만들어보기
⛔ 강퇴 당한 참가자는 24시간 동안 해당 스페이스에 접속하지 못합니다.
// 플레이어가 채팅을 입력할 때 실행
// 명령어 형식 '!강퇴 닉네임'
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;
}
}
}
});
미니게임 앱을 강제 종료하는 함수입니다.
예제
미니게임 앱을 강제 종료시키기
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
App.forceDestroy();
});
모든 채팅 내용을 삭제하는 함수입니다.
예제
Q를 눌러 채팅 내용 지우기
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
App.clearChat();
});
id 에 해당하는 플레이어를 반환하는 함수입니다.
예제
App.getPlayerByID 사용 방법
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
const myPlayer = App.getPlayerByID(player.id);
});
Sound 함수 한 눈에 보기
// 모든 플레이어에게 사운드를 재생
App.playSound(fileName: string, loop: boolean = false, overlap: boolean = false)
// 모든 플레이어에게 링크에 해당하는 사운드를 재생
App.playSoundLink(link: string, loop: boolean = false)
// 모든 재생되는 사운드를 멈춤
App.stopSound()
// 찌르기(Z키) 공격 효과음을 변경하는 함수입니다.
App.changeAttackSound(fileName:string)
모든 플레이어에게 사운드를 재생하는 함수입니다.
파라미터
fileName
String
불러올 파일의 이름
loop
boolean
true: 반복 재생 false: 1회 재생
overlap
boolean
사운드 오버랩(겹침) 재생 가능 여부
예제
플레이어가 입장할 때 입장음 적용해보기 ( 파일 )
//플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.playSound("join.mp3", false, true);
});
모든 플레이어에게 사운드를 재생하는 함수입니다.
💡 올바른 링크를 입력했는데 재생이 되지 않는 경우
CORS 정책을 위반한 경우일 가능성이 높습니다. CORS 정책을 맞출 수 없는 경우에는 playSoundLink 대신 음악 파일을 업로드 하여 playSound 함수를 사용하는 것을 권장드립니다.
파라미터
link
String
사운드 url
loop
boolean
true: 반복 재생 false: 1회 재생
예제
플레이어가 입장할 때 입장음 적용해보기 ( 사운드 url )
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
App.playSoundLink("https://zep.us/assets/sounds/ring.mp3",false);
});
재생되고 있는 사운드를 멈추는 함수입니다.
파라미터
없음
예제
q를 누르면 사운드가 멈추는 기능 만들어보기
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81,function(p){
App.stopSound();
})
찌르기(Z키) 공격 효과음을 변경하는 함수입니다.
파라미터
fileName
String
적용할 사운드 파일명
예제
changeAttackSound 사용 방법
App.onStart.Add(function(){
App.changeAttackSound("attack.mp3");
})
통신 함수 한 눈에 보기
// 해당 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))
http get 요청을 보내는 함수입니다.
파라미터
url
String
요청을 보낼 주소
headers
Object
요청 헤더
res
String
요청에 대한 응답
예제
한국어 별명 생성기 API를 이용해 입장하는 플레이어의 닉네임을 바꿔보기
// 플레이어가 입장할 때 동작하는 함수
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();
}
);
});
Form-Data 형태의 http post 요청을 보내는 함수 입니다.
파라미터
url
String
요청을 보낼 주소
headers
Object
요청 헤더, 요청 헤더가 없으면 { } 를 입력.
body
Object
요청 바디 ( Form data )
res
String
요청에 대한 응답
예제
앱에서 보낸 헤더와 데이터를 응답으로 받아 채팅창에 출력해보기.
예제와 같이 key, value 값을 string 형태로 작성해야하며, 요청 서버에서 폼데이터를 받아 처리가 가능해야합니다.
// 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);
}
);
});
Json 형태의 http post 요청을 보내는 함수 입니다.
파라미터
url
String
요청을 보낼 주소
headers
Object
요청 헤더, 요청 헤더가 없으면 { } 를 입력.
body
Object
요청 바디 ( JSON data )
res
String
요청에 대한 응답
예제
앱에서 보낸 데이터를 응답으로 받아 채팅창에 출력해보기.
// 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);
}
);
});
공통 함수 한 눈에 보기
// App 관련 필드값이 변경되면 변경값을 적용함
App.sendUpdated()
App 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
파라미터
없음
소개
Map에 타일 효과나 오브젝트 설치 등 맵과 관련된 편리한 기능을 제공하는 함수들 입니다.
putTileEffect
지정한 좌표에 타일 효과를 적용하는 함수입니다
putObject
지정한 좌표에 오브젝트를 놓는 함수입니다.
putObjectMultiple
오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다.
putObjectWithKey
지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다.
getObjectWithKey
해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.
playObjectAnimation
해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
playObjectAnimationWithKey
key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
moveObject
x, y 좌표에 위치한 오브젝트를 지정한 x, y 좌표로 time 초 동안 움직이는 함수입니다.
moveObjectWithKey
키 값을 가진 오브젝트를 지정한 x,y 좌표로 움직이는 함수입니다.
clearAllObjects
ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.
getTile
해당하는 레이어의 x,y 좌표에 있는 타일의 enum 값을 반환하는 함수입니다.
hasLocation
맵에 해당 로케이션이 있는지 체크하여 true/false를 반환하는 함수입니다.
getObjectsByType
Type에 해당하는 오브젝트들을 반환하는 함수입니다.
getTopObjectsByType
Type에 해당하는 상단 오브젝트들을 반환하는 함수입니다.
sayObjectWithKey
key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.
getLocation
파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.
getLocationRandom
파라미터로 전달한 로케이션이 2개이상 존재하는 경우, 무작위로 선택하여 해당 로케이션의 설치 좌표를 리턴합니다.
getLocationList
파라미터로 전달된 로케이션 이름과 일치하는 모든 로케이션의 정보를 배열 형태로 반환합니다
Methods 함수 한 눈에 보기
// 지정된 좌표에 타일효과를 적용
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 )
지정한 좌표에 타일 효과를 적용하는 함수입니다.
파라미터
TileEffectType에 대한 자세한 설명은 TileEffectType 상세 설명 페이지를 참고해주세요.
x, y
Number
타일 효과를 설치할 x, y 좌표
tileID
TileEffectType
• TileEffectType.NONE : 없음 •TileEffectType.IMPASSABLE : 통과 불가 • TileEffectType.SPAWN : 스폰
• TileEffectType.PORTAL : 포털 • TileEffectType.PRIVATE_AREA : 프라이빗 공간 • TileEffectType.SPOTLIGHT : 스포트라이트 • TileEffectType.EMBED : 웹 링크 • TileEffectType.LOCATION : 지정 영역 • TileEffectType.AMBIENT_SOUND : 배경 음악 • TileEffectType.TILE_EMBED : 웹 임베드 • TileEffectType.WEB_PORTAL : 웹 포털 • TileEffectType.SPACE_PORTAL : 스페이스 포털
예제
IMPASSABLE 타일 효과 설치해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
// 5, 5 좌표에 IMPASSABLE 타일 효과 설치
Map.putTileEffect(5, 5, TileEffectType.IMPASSABLE);
});
지정한 좌표에 오브젝트를 놓는 함수입니다. ( 기준 좌표: Left Top ) → 기준 좌표란?
ScriptDynamicResource에 대한 이해를 돕는 스프라이트시트 이해하기 문서를 확인해보세요!
null 값을 파라미터로 전달하여 스크립트로 설치한 오브젝트를 삭제 할 수 있습니다.
Map.putObject(x, y, null);
파라미터
x, y
Number
오브젝트를 놓을 x, y 좌표
dynamicResource
ScriptDynamicResource
App.loadSpritesheet 함수로 로드한 이미지
option
Object
onObjectTouched, onObjectAttacked 와 같은 App EventListener에서 오브젝트를 인식하려면 파라미터 란에 { overlap: true } 입력해야 합니다.
예제
블루맨 오브젝트 생성해보기
// 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);
});
오브젝트를 배치할 좌표들을 2차원 배열로 입력하여 한 번에 오브젝트를 설치하는 기능입니다. 이 기능을 사용하면 한 번에 많은 오브젝트를 설치할 경우 부하를 줄이는 효과를 얻을 수 있습니다.
파라미터
tileArray
Array
오브젝트를 배치할 좌표들을 정의할 2차원 배열을 입력할 수 있습니다. (최대 길이 10 제한)
type
PutObjectType
PutObjectType.STROKE
tileArray 배열에 정의된 좌표들을 순서대로 연결하여 경로를 생성한 다음, 생성된 경로 상의 모든 좌표들에 오브젝트를 배치합니다.
dynamicResource
ScriptDynamicResource
App.loadSpritesheet 함수로 로드한 이미지
option
Object
onObjectTouched, onObjectAttacked 와 같은 App EventListener에서 오브젝트를 인식하려면 파라미터 란에 { overlap: true } 입력해야 합니다.
예제
사각형, 원 모양으로 오브젝트 배치하기
PutObjectType.STROKE
사각형 모양으로 배치PutObjectType.STROKE
원 모양으로 배치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 });
});
지정한 좌표에 키 값을 가진 오브젝트를 놓는 함수입니다. ( 기준 좌표: Left Top )
dynamicResource
파라미터에 null
입력시 해당 좌표의 오브젝트를 삭제할 수 있습니다.
관련 문서: 오브젝트 npcProperty
파라미터
x, y
Number
오브젝트를 놓을 x, y 좌표
dynamicResource
ScriptDynamicResource
App.loadSpritesheet 함수로 로드한 이미지
옵션 (option)
값을 입력하지 않아도 Default 값이 적용됩니다
key
String
오브젝트의 키 값 (Default: null)
moveSpeed
Number
오브젝트의 이동속도
(Default: 80)
useDirAnim
Boolean
방향을 인지해서 애니메이션을 재생하는 옵션 (Default: false)
impassable
Boolean
오브젝트 통과불가 옵션 (Default: false)
offsetX
Number
오브젝트 이미지의 배치 기준점의 X좌표 (Default: 0)
offsetY
Number
오브젝트 이미지의 배치 기준점의 Y좌표 (Default: 0)
npcProperty
Object
npcProperty 관련 내용은 를 참고해주세요. (Default: null)
topObject
Boolean
true 로 설정하면 오브젝트가 상단오브젝트로 설치됩니다. (Default: false)
예제
키 값을 가진 블루맨 오브젝트 생성해보기
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", // 키 값
});
});
해당 키 값을 가지고 있는 오브젝트의 정보를 가져옵니다.
파라미터
key
String
정보를 가져올 오브젝트의 key 값
예제
키 값을 가진 오브젝트를 생성하고 관련 데이터를 채팅창에 출력해보기.
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]}`)
}
})
해당 좌표에 있는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
해당 좌표 지점에 Map.putObject 함수가 선행되어야합니다.
스프라이트 이미지를 처음 들어보신다면 스프라이트시트 이해하기 문서를 확인해보세요!
파라미터
x, y
Number
타일 효과를 적용할 x, y 좌표
name
String
let 변수 = App.loadSpritesheet(...) 스프라이트를 저장한 변수 이름을 다음과 같이 입력해야 합니다. → ‘#’ + 변수.id
loop
number
애니메이션 반복 횟수 ( -1: 계속반복)
예제
춤추는 블루맨 오브젝트 설치해보기
// 한 프레임의 사이즈 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);
});
key 값이 일치하는 오브젝트의 스프라이트 애니메이션을 실행시키는 함수입니다.
Map.putObjectWithKey
함수가 선행되어야합니다.
스프라이트 이미지를 처음 들어보신다면 스프라이트시트 이해하기 문서를 확인해보세요!
파라미터
key
String
오브젝트의 키값
animName
String
플레이할 애니메이션의 이름
repeatCount
Number
애니메이션 반복 횟수 ( -1 입력시 무한반복 )
예제
춤추는 블루맨 오브젝트 설치해보기
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);
});
x, y 좌표에 위치한 오브젝트를 targetX, targetY로 time 초 동안 움직이는 함수입니다.
해당 좌표 지점에 Map.putObject 함수가 선행되어야합니다.
스프라이트 이미지를 처음 들어보신다면 스프라이트시트 이해하기 문서를 확인해보세요!
파라미터
x, y
Number
오브젝트가 위치한 x, y 좌표
targetX, targetY
Number
목적지 x, y 좌표
time
Number
목적지까지 걸리는 시간(초)
예제
블루맨 이동시켜보기
// 한 프레임의 사이즈 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초 동안 이동
});
key 값을 가진 오브젝트를 targetX, targetY로 움직이는 함수입니다.
💡 path 파라미터가 true인 경우, 목표 지점이 impassable 타일이거나 도달 할 수 없는 좌표이면 오브젝트가 움직이지 않습니다.
파라미터
key
String
오브젝트의 key 값
targetX, targetY
Number
목적지 x, y 좌표
usePath
Boolean
true일 경우 Impassable 타일을 통과하지 못합니다. false일 경우 Impassable 타일을 무시하고 지나갑니다.
예제
moveObejctWithKey 동작 방식
// 한 프레임의 사이즈 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);
});
ZEP 스크립트로 생성된 모든 오브젝트를 제거하는 함수입니다.
파라미터
없음
예제
생성된 모든 오브젝트 삭제하기
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();
});
해당하는 레이어의 x, y 좌표에 있는 타일의 타입 값을 리턴, 타일이 없으면 -1을 리턴합니다.
파라미터
layer
Number
레이어에 해당하는 값 0 = Floor (바닥타일), 1 = WALL (벽타일), 2 = TileEffect (타일효과), 3 = Object (오브젝트), 5 = TopObject (상단오브젝트),
x, y
Number
x, y 좌표
예제
맵에 있는 모든 오브젝트의 타입을 조회하기
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}`);
}
}
}
});
파라미터로 전달한 로케이션이 존재하는 경우, 로케이션 설치 좌표를 리턴합니다.
파라미터
locationName
String
로케이션 이름
예제
맵 에디터에서 타일효과 > 지정 영역 선택 후 영역 이름을 "testLocation" 으로 지정해 맵에 설치한 후 예제 코드를 실행해보세요.
// 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!`)
}
});
파라미터로 전달한 로케이션이 2개이상 존재하는 경우, 무작위로 선택하여 해당 로케이션의 설치 좌표를 리턴합니다.
파라미터
locationName
String
로케이션 이름
예제
맵 에디터에서 타일효과 > 지정 영역 선택 후 영역 이름을 "testLocation" 으로 지정해 맵에 여러개 설치한 후 예제 코드를 실행해보세요.
// 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!`)
}
});
파라미터로 전달된 로케이션 이름과 일치하는 모든 로케이션의 정보를 배열 형태로 반환합니다. 각각의 로케이션 정보는 로케이션 설치 좌표(x, y), 크기(width, height), 그리고 라벨 값(label)을 포함하고 있습니다.
파라미터
locationName
String
로케이션 이름
예제
맵 에디터에서 타일효과 > 지정 영역 선택 후 영역 이름을 "testLocation" 으로 지정해 맵에 여러개 설치한 후 예제 코드를 실행해보세요.
// 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!`);
}
});
맵에 해당 로케이션이 있는지 체크하여 true/false 값을 리턴합니다.
파라미터
locationName
String
로케이션 이름
예제
로케이션이 설치되어있는지 체크하는 키 함수 만들어보기
// q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
if(Map.hasLocation("test")){
App.sayToAll("test 로케이션이 설치되어있습니다.")
} else {
App.sayToAll("test 로케이션이 설치되어있지 않습니다.")
}
});
Type에 해당하는 오브젝트들을 리턴하는 함수입니다.
파라미터
type
Number
오브젝트의 type 값
예제
모든 타입의 오브젝트 조회하기
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]}`);
}
}
}
})
Type에 해당하는 상단오브젝트들을 리턴하는 함수입니다.
파라미터
type
Number
오브젝트의 type 값
예제
모든 타입의 상단오브젝트 조회하기
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]}`);
}
}
}
})
key 값을 가진 오브젝트 위에 말풍선을 표시하는 함수입니다.
파라미터
key
String
대상오브젝트의 키 값
message
String
말풍선에 표시할 메시지
예제
key 값을 가진 오브젝트 위에 말풍선 표시하기
const objectKey = "TestBlueMan";
// Q를 누르면 동작하는 함수
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!`)
})
플레이어와 관련된 속성 값들 입니다.
플레이어의 닉네임(name), 위치(tileX / tileY) 등을 조회하거나 플레이어에게 스팟라이트(spotlight), 안보임(hidden) 기능을 활성화 할 수 있으며, 캐릭터의 이동속도(moveSpeed), 이미지(sprite)를 변경하거나 플레이어의 저장공간(storage)을 활용할 수도 있습니다.
🔒 아이콘이 있는 필드는 수정이 불가능한 읽기 전용 필드입니다.
🔒 id
플레이어의 id 값
name
플레이어의 닉네임 값
title
아바타 닉네임 위에 노란색으로 노출되는 텍스트
🔒 role
플레이어의 권한을 나타내는 숫자 값
🔒 tileX / tileY
아바타가 서있는 X 좌표 값과 Y 좌표 값
🔒 dir
아바타가 바라보고 있는 방향 값
moveSpeed
플레이어의 이동속도 값
sprite
아바타의 스프라이트 이미지 값
tag
필요한 속성 값을 부여 가능한 값 저장 공간
hidden
값이 true 이면, 다른 플레이어에게 보이지 않습니다.
spotlight
플레이어의 스팟라이트 기능 활성화 여부
attackType
플레이어의 공격(Z키) 타입
attackSprite
플레이어의 공격(Z키) 이미지 값
attackParam1
공격 이미지가 날아가는 거리 값
attackParam2
공격 가능 거리 값 attackType이 2(원거리 공격)으로 설정 된 경우에만 유효
🔒 walletAddress
플레이어의 전자지갑 주소 값
storage
스페이스 내의 Player 값 저장 공간(스페이스 한정)
🔒 isMobile
플레이어의 모바일 접속여부
🔒 isMoving
플레이어가 움직이고 있으면 True, 아니면 False를 반환
🔒 isJumping
플레이어가 점프하고 있으면 True, 아니면 False를 반환
🔒 customData
URL 쿼리 스트링을 읽어 값을 저장 할 수 있습니다.
displayRatio
플레이어의 화면 줌 비율을 조절 할 수 있습니다.
titleColor
플레이어의 타이틀 색상
🔒 emailHash
플레이어의 이메일 Hash 값
🔒 isGuest
비로그인 플레이어인 경우 true 값을 가집니다.
🔒 language
플레이어의 브라우저에서 사용하는 언어 설정 값
🔒 away
5분 이상 비활성화된 유저인 경우 true
값을 가집니다.
enableFreeView
맵 둘러보기 허용 여부를 설정할 수 있습니다.
플레이어의 id, 닉네임 값을 가져옵니다.
예제
플레이어가 입장 할 때 플레이어의 id, name 값 출력해보기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
App.sayToAll(`id: ${player.id} name: ${player.name}`);
})
title은 캐릭터 닉네임 위에 노란색으로 노출되는 텍스트입니다.
예제
플레이어가 입장 할 때 title 설정해보기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
player.title = "타이틀";
player.sendUpdated();
})
role은 플레이어의 권한을 나타내는 숫자 값 입니다.
플레이어의 role에 따라 다음과 같은 값을 출력합니다.
멤버
0
관리자
3000
에디터
1000
맵소유자
3001
예제
권한 값을 채팅 창에 표시해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
App.sayToAll(`${player.name}님의 권한: ${player.role}`)
})
플레이어의 캐릭터가 서있는 X 좌표 값과 Y 좌표 값입니다.
예제
내 캐릭터의 x, y 좌표 출력해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
App.sayToAll(`현재 좌표: (${player.tileX}, ${player.tileY})`)
})
플레이어의 캐릭터가 바라보고 있는 방향입니다.
캐릭터가 바라보고 있는 방향에 따라 다음과 같은 값을 출력합니다.
예제
캐릭터가 바라보고 있는 방향을 출력해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
App.sayToAll(`바라보고 있는 방향: ${player.dir}`)
})
플레이어의 이동속도 값입니다.( 기본 값: 80 )
이동속도 값이 0이면 움직일 수 없습니다.
예제
q 키를 누르면 이동속도가 빨라지는 함수 만들어보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
player.moveSpeed = 150;
player.sendUpdated();
})
플레이어 캐릭터의 스프라이트 이미지입니다. ( null 입력 시 기본 아바타 이미지로 초기화 )
스프라이트 이미지를 처음 들어보신다면 스프라이트시트 이해하기 문서를 확인해보세요!
예제
페인트맨 - 블루맨 이미지를 캐릭터 이미지로 적용해보기
// 한 프레임의 사이즈 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를 사용해 플레이어에게 필요한 속성 값을 부여 할 수 있습니다.
예제
플레이어에게 ‘alive’ 속성 값 부여해보기. ‘alive’ 속성 값은 임의로 생성한 속성 입니다.
쓰이지 않는다면 아무 의미 없는 속성 값 이지만, 게임을 만들 때 플레이어의 생존 여부를 체크하는 중요한 속성으로 사용할 수 있습니다.
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function (player) {
player.tag = {
alive: true,
};
player.sendUpdated();
App.sayToAll(`alive: ${player.tag.alive}`);
});
hidden 값이 true 이면, 해당 플레이어는 다른 플레이어에게 보이지 않습니다.
⚠️ hidden인 상태에서 모습은 보이지 않지만, 오디오와 비디오 연결은 됩니다.
예제
캐릭터에게 hidden 속성을 부여해서 다른 플레이어에게 안 보이게 해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
player.hidden = true;
player.sendUpdated();
});
플레이어의 스팟 라이트 기능 활성화 여부입니다.
예제
q 키를 누르면 스팟 라이트 기능을 ON/OFF 하는 함수 만들어보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
if(player.spotlight){
player.spotlight = false;
}
else{
player.spotlight = true;
}
player.sendUpdated();
});
플레이어의 공격(Z키) 타입입니다. ( 기본: 0 )
0
attackType을 설정 하지 않았을 때 기본 공격 타입을 의미합니다.
2
원거리 공격 타입입니다. attackParam2와 함께 설정할 때 유효합니다.
예제
캐릭터의 attackType 변경해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
player.attackType = 0;
App.sayToAll(`attackType: ${player.attackType}`);
player.sendUpdated();
});
공격(Z키) 이미지가 날아가는 거리 속성입니다. 공격 가능 거리는 늘어나지 않습니다.
예제
attackParam1 변경해보기
// 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();
});
공격 가능 거리 속성입니다. attackType이 원거리 공격으로 설정 된 경우에만 유효합니다.
예제
attackParam2 이용해 원거리 공격 설정 해보기.
// 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();
});
공격(Z키) 이미지를 지정할 수 있습니다.
예제
공격 이미지 적용해보기
// 단일 이미지의 경우
let redBoxing = App.loadSpritesheet("redBoxing.png");
App.onJoinPlayer.Add(function (player) {
player.attackSprite = redBoxing;
player.sendUpdated();
});
// 연속된 이미지의 경우
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();
});
플레이어의 전자지갑 주소입니다.
예제
전자지갑 주소 출력해보기 ( 전자지갑 주소가 없을 경우 null이 출력 )
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81, function (player) {
App.sayToAll(`${player.walletAddress}`)
});
스페이스 내의 Player 값 저장 공간 입니다 (스페이스 한정)
예제
플레이어 storage에 데이터를 저장하고, 확인해보기
💡 앱을 종료했다가 다시 켜도 저장된 값이 사라지지 않습니다.
// 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에 저장된 값을 채팅창에 출력
})
플레이어의 모바일 접속 여부를 true/false 로 출력합니다.
예제
플레이어가 입장 할 때 입장메시지에 모바일/PC 표시해보기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
if(player.isMobile){
App.sayToAll(`${player.name}님이 모바일에서 접속했습니다.`)
} else{
App.sayToAll(`${player.name}님이 PC에서 접속했습니다.`)
}
});
플레이어가 움직이고 있으면 True, 아니면 False를 반환합니다.
예제
플레이어의 움직임을 감지해서 메시지 출력해보기.
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}님이 움직이는 중..`);
}
}
});
플레이어가 점프하고 있으면 True, 아니면 False를 반환합니다.
예제
플레이어의 점프를 감지해서 메시지 출력해보기.
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}님이 점프 중..`);
}
}
});
URL 쿼리스트링으로 전달 받은 값을 저장하는 필드입니다.
예제
플레이어화면의 줌을 컨트롤 하는 값 ( 기본 값: 1 )
예제
화면의 줌을 컨트롤 하는 키 만들어보기
// q 키를 누르면 동작하는 함수
// 한 번 누르면 화면의 줌 값이 커지고, 한 번 더 누르면 원래대로 돌아오는 키 함수
App.addOnKeyDown(81,function(player){
if(player.displayRatio == 1){
player.displayRatio = 5;
}else{
player.displayRatio = 1;
}
player.sendUpdated(); //* player의 Field값이 변경되면 player.sendUpdated()로 변경값을 적용
})
플레이어의 타이틀 색상을 읽거나 수정 할 수 있습니다.
Enum 값 또는 컬러 Hex Code 값을 입력할 수 있습니다.
사용 가능한 Enum ColorType
{ WHITE, BLACK, RED, GREEN, BLUE, ORANGE, PURPLE, GRAY, YELLOW, MAGENTA, CYAN }
예제
타이틀 색상 바꿔보기
// 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()로 변경값을 적용
});
플레이어의 이메일 Hash 값을 가져옵니다.
예제
플레이어의 이메일 Hash 값 출력하기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
App.sayToAll(`name: ${player.name} emailHash: ${player.emailHash}`);
})
비로그인 플레이어인 경우 true 값을 가집니다.
예제
비로그인 유저 입장시 타이틀에 "GUEST" 표시하기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
if(player.isGuest){
player.title = "GUEST";
player.sendUpdated();
}
})
브라우저에서 사용하는 언어 설정값에 따라 다음과 같은 값을 가집니다.
한국어 "ko", 일본어: "ja", 영어: "en"
예제
유저 입장시 브라우저에서 사용 하는 언어 설정 값 표시하기
// 플레이어가 입장할 때 동작하는 함수
App.onJoinPlayer.Add(function(player){
App.sayToAll(player.language);
})
장시간 비활성화된 유저인 경우 true
값을 가집니다.
플레이어의 맵 둘러보기 허용 여부를 설정할 수 있습니다.
예제
단축키로 플레이어의 맵 둘러보기 허용 여부 설정하기
// 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);
});
UI, 유저 컨트롤, 사운드 등 ZEP에서 일어날 수 있는 전반적인 기능을 제공하는 함수입니다.
플레이어 개인 화면에 UI를 표시, 플레이어를 이동, 플레이어 개인에게 사운드 재생 등 편리한 기능을 제공합니다.
showCenterLabel
플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
showCustomLabel
플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다. text 부분에 span 태그를 넣어 텍스트를 꾸밀 수 있습니다.
showWidget
플레이어에게 지정된 위치에 위젯을 불러오는 함수입니다.
showBuyAlert
플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.
hideBuyAlert
플레이어의 구매 위젯을 숨깁니다.
sendMessage
유저 개인에게 채팅 메시지를 보냅니다.
showPrompt
플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.
showConfirm
플레이어에게 확인창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 콜백함수를 작성할 수 있습니다.
showAlert
플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.
showImageModal
플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.
showNoteModal
플레이어에게 텍스트 창을 보여주는 함수입니다.
showWidgetResponsive
위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의하여 위젯을 표시합니다.
openWebLink
플레이어에게 웹 URL을 새 창이나 팝업 창으로 열어 보여줍니다.
showEmbed
URL을 임베드 형태로 표시합니다.
크기와 위치를 설정할 수 있습니다.
isEmail
플레이어의 이메일을 비교하는 함수입니다.
getLocationName
플레이어가 서있는 지정 영역의 이름을 출력합니다.
spawnAt
플레이어의 캐릭터를 지정한 좌표로 이동시키는 함수입니다.
spawnAtLocation
플레이어의 캐릭터를 지정 영역으로 이동시키는 함수입니다.
spawnAtMap
플레이어를 다른 스페이스 또는 맵으로 이동시키는 함수입니다.
setCameraTarget
플레이어의 시점을 지정된 좌표로 중심 이동시킵니다.
setCameraTargetWithKey
플레이어의 시점을 특정 오브젝트로 중심 이동시킵니다.
setEffectSprite
플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.
playEffectSprite
플레이어에게 애니메이션 효과를 입력 횟수만큼 반복 재생하고 사라지는 효과를 적용합니다.
disappearObject
플레이어 개인 화면 상에서 key 값을 가지는 오브젝트를 사라지게하는 함수입니다.
playSound
플레이어에게 사운드 파일을 재생하는 함수입니다.
playSoundLink
플레이어에게 사운드 URL을 재생하는 함수입니다.
stopSound
플레이어에게 재생중인 사운드를 중지시키는 함수입니다.
sendUpdated
Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
save
Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.
UI 함수 한 눈에 보기
// 플레이어에게 지정된 위치에 해당 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
// 플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.
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)
해당 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
파라미터
text
String
라벨에 출력할 텍스트
color
Uint
출력할 글씨의 색을 지정합니다. (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
bgColor
Uint
메시지가 출력되는 라벨의 배경색을 지정합니다. 값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.
offset
number
offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다. 값을 입력하지 않을 경우, 0으로 지정됩니다.
time
number
라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )
예제
노란색 라벨을 2초간 출력해보기
App.onJoinPlayer.Add(function(player){
player.showCenterLabel(`${player.name}님 환영합니다.`, 0x000000, 0xFFFF00, 500, 2000); // 노란색 배경, 검정색 글씨로 표시하기
});
모든 플레이어에게 지정된 위치에 text를 3초간 표시하는 함수입니다.
text 부분에 span
태그를 넣어 텍스트를 꾸밀 수 있습니다.
파라미터
text
String
라벨에 출력할 텍스트 ( span 태그 허용 )
color
Uint
출력할 글씨의 색 (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
bgColor
Uint
메시지가 출력되는 라벨의 배경색 값을 입력하지 않을 경우, 검은색(0x000000)으로 적용됩니다.
offset
number
offset 값이 클수록 표시되는 위치가 화면 아래쪽 방향으로 가까워집니다. 값을 입력하지 않을 경우, 0으로 지정됩니다.
width
number
라벨의 너비를 n%로 설정하는 값 입니다. (기본 값 100)
opacity
number
라벨의 배경 투명도를 설정하는 값 입니다. (기본 값 0.6, 범위 0 ~ 1)
time
number
라벨 표시 시간 (ms), 기본 값 3000 ( 3초 )
옵션
key
String
라벨에 키 값을 할당하여, 서로 다른 키 값을 가진 라벨은 동시에 표시할 수 있습니다.
borderRadius
String
라벨의 모서리에 둥글기를 설정할 수 있습니다. ex) borderRadius: "8px"
fontOpacity
boolean
false로 설정시 폰트에 투명도가 적용되지 않습니다.
padding
String
라벨 내부에 padding 값을 지정 할 수 있습니다. ex) padding: "8px"
예제
커스텀 라벨 예제 코드 페이지를 참고해주세요
해당 플레이어에게 지정된 align의 위치에 해당 html파일을 위젯으로 불러오는 함수입니다.
파라미터
fileName
String
불러올 파일의 이름
align
String
위젯을 표시할 위치 ’popup’, ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’
width height
number
위젯을 표시할 영역의 가로, 세로 크기(px)
예제
초성퀴즈 위젯 따라해보기
let _widget = null;
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
_widget = player.showWidget("widget.html", "top", 200, 300); // 화면 상단, 200x300 영역에 위젯을 보여줌
_widget.sendMessage({
timer: 15,
answer: "ㅅㅍㅋ",
});
});
플레이어에게 구매 위젯을 표시하고, 구매 후 동작하는 콜백함수를 작성할 수 있습니다.
소모된 ZEM은 앱 제작자에게 돌아가며, 내 후원 내역 페이지에서 내역을 확인 할 수 있습니다.
ZEM 정산 관련 내용은 정산가이드 페이지에서 확인하실 수 있습니다.
파라미터
itemName
String
구매창에 표시할 아이템의 이름
price
number
아이템의 가격 (화폐단위: ZEM)
callback
function
구매 성공시 동작할 콜백함수
구매 성공 여부(success
)와 구매정보(buyAlertResult
) 데이터를 전달 받으며, buyAlertResult는 환불 기능에 사용됩니다.
payToSpaceOwner
Boolean
기본 값은 false로 설정되며 false인 경우 앱 소유자에게 수익이 전달되고,
true인 경우 맵 소유자에게 수익이 전달됩니다.
option
Object
다음 옵션들을 설정 할 수 있습니다.
message
: 구매창에 표시할 텍스트를 설정 할 수 있습니다.
timer
: 구매창을 표시할 시간(ms)을 설정할 수 있습니다.
예제
구매정보 저장 및 환불 기능 예제
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();
}
})
플레이어의 구매 위젯을 닫습니다.
파라미터
없음
유저 개인에게 채팅 메시지를 보내는 함수입니다.
파라미터
text
String
라벨에 출력할 텍스트
color
Uint
출력할 글씨의 색을 지정합니다. (HexCode) 값을 입력하지 않을 경우, 흰색(0xFFFFFF)으로 적용됩니다. ➡️
예제
플레이어 개인에게만 보이는 환영메시지 출력하기.
App.onJoinPlayer.Add(function(player){
player.sendMessage(`${player.name}님 어서오세요!\nhttps://docs-kr.zep.us/ 링크 클릭시 가이드로 연결됩니다.`,0xffffff);
});
플레이어에게 입력창을 보여주고, 플레이어의 응답에 따라 동작하는 callback 함수를 작성할 수 있습니다.
파라미터
title
String
입력창의 타이틀
inputText
String
플레이어가 입력한 텍스트
옵션
값을 입력하지 않아도 default 값이 적용됩니다.
content
String
입력창 위에 출력할 텍스트 (Default: null)
confirmVariant
'primary' | 'alert'
confirm 버튼의 색상 (Default: "primary") - 'primary': 푸른색, - 'alert': 붉은색
cancelText
String
cancel 버튼의 텍스트 (Default: "취소")
confirmText
String
confirm 버튼의 텍스트 (Default: "확인")
placeholder
String
input placeholder 텍스트 (Default: null)
textType
'text' | 'password'
입력 타입 (Default: "text")
- 'text': 입력 값을 텍스트로 표시 - 'password': 입력 값을 *로 표시
예제
"1234"를 입력하는 경우 "Correct" 메시지 출력해보기
// 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'
}
);
});
플레이어에게 확인창을 보여주고, 플레이어가 확인 버튼을 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다. cancel을 누를 경우에는 callback 함수가 동작하지 않습니다.
파라미터
text
String
확인창에 출력할 텍스트
result
Boolean
플레이어가 OK를 누르는 경우 true
옵션
값을 입력하지 않아도 default 값이 적용됩니다.
content
String
content 영역에 출력할 텍스트 (Default: null)
confirmVariant
'primary' | 'alert'
confirm 버튼의 색상 (Default: 'primary') - primary: 푸른색, - alert: 붉은색
cancelText
String
cancel 버튼의 텍스트 (Default: "취소")
confirmText
String
confirm 버튼의 텍스트 (Default: "확인")
예제
확인 버튼을 누른 경우 채팅창에 텍스트 출력하기
// 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',// 확인 버튼 텍스트
}
);
});
플레이어에게 경고창을 보여주고, 플레이어가 OK를 눌렀을 때 동작하는 callback 함수를 작성할 수 있습니다.
파라미터
text
String
경고창에 출력할 텍스트
옵션
값을 입력하지 않아도 default 값이 적용됩니다.
content
String
content 영역에 출력할 텍스트 (Default: null)
confirmText
String
confirm 버튼의 텍스트 (Default: "확인")
예제
버튼을 누른 경우 채팅창에 텍스트 출력하기
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
player.showAlert("Alert", function () {
App.sayToAll("ok");
},
{
content: 'Description', // 설명
confirmText: 'custom confirm',// 확인 버튼 텍스트
}
);
});
위젯의 상/하/좌/우 여백을 화면 크기에 대한 %비율로 정의하여 위젯을 표시합니다.
화면의 크기가 여백을 포함한 위젯 영역보다 작아질 경우, 위젯의 크기가 비례하여 작아집니다.
파라미터
fileName
String
불러올 파일의 이름
margin top/left/right/bottom
String
상/하/좌/우 여백의 % 값
예제
화면 크기를 줄이는 경우 위젯의 크기 변화
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;
}
});
});
플레이어에게 웹 URL을 새 창이나 팝업 창으로 표시합니다.
파라미터
url
String
연결할 웹 url 주소
popup
boolean
true 인 경우, url 창을 팝업 형태로 표시합니다.
예제
openWebLink 팝업으로 여는 경우
// Q 키를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
player.openWebLink("https://docs-kr.zep.us", true);
});
해당 플레이어에게 지정된 align의 위치에 url 임베드 화면을 표시하는 함수입니다.
파라미터
url
String
웹 url 주소
align
String
임베드를 표시할 위치 ‘sidebar’, ‘top’, ‘topleft’, ‘topright’, ‘middle’, ‘middleleft’, ‘middleright’, ‘bottom’, ‘bottomleft’, ‘bottomright’
width height
number
임베드 영역의 가로, 세로 크기(px)
hasBackdrop
boolean
true일 경우 임베드의 바깥 배경에 그림자를 표시합니다.
예제
url 임베드창 표시하기
// Q를 누르면 동작하는 함수
App.addOnKeyDown(81, function (player) {
player.showEmbed("https://youtu.be/ztuTrpXJyks", "middle", 900, 600, true);
});
플레이어에게 입력한 이미지 주소에 해당하는 이미지를 표시합니다.
파라미터
url
String
표시할이미지 url
예제
이미지 모달창 표시하기
// Q를 누르면 동작하는 함수
App.addOnKeyDown(KeyCodeType.Q, function (player) {
player.showImageModal("https://cdn-static.zep.us/static/images/thumbnail.png");
});
플레이어에게 텍스트 창을 보여주는 함수입니다.
파라미터
text
String
표시할 텍스트
예제
텍스트 창 표시하기
// 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 함수 한 눈에 보기
// 지정한 이메일과 플레이어의 이메일을 비교
player.isEmail(email: string): boolean
// 플레이어가 서있는 구역이름을 호출
player.getLocationName(): string
해당 플레이어의 이메일이 파라미터 값과 같다면 true, 아니면 false를 리턴합니다.
파라미터
String
비교할 이메일 텍스트
예제
플레이어의 이메일이 지정한 텍스트와 같은지 비교해보기
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
let check = player.isEmail("[email protected]");
App.sayToAll(`이메일 일치 여부: ${check}`)
})
플레이어가 서있는 지정 영역의 이름을 출력합니다.
지정 영역은 ‘맵에디터 > 타일효과’ 에서 설정 할 수 있습니다.
파라미터
없음
예제
캐릭터가 서있는 타일의 영역이름 출력해보기
→ 지정 영역 설정이 안되있다면 공백으로 출력됩니다
// q 키를 누르면 동작하는 함수
// App.addOnKeyDown
App.addOnKeyDown(81,function(player){
App.sayToAll(`현재 서있는 구역: ${player.getLocationName()}`)
})
User Control 함수 한 눈에 보기
// 플레이어를 해당 좌표로 소환
player.spawnAt(tileX: int ,tileY: int, dir: int = 0)
// 플레이어를 해당 구역으로 소환
player.spawnAtLocation(name: string ,dir:int = 0)
// 플레이어를 해당 스페이스 해당 맵으로 이동시키기
player.spawnAtMap(spaceHashID string, mapHashID:string = null)
플레이어의 캐릭터를 tileX, tileY 좌표로 지정한 방향을 바라보게 이동시킵니다.
파라미터
tileX tileY
number
플레이어를 이동시킬 x, y 좌표 값
dir
number
- 캐릭터가 바라볼 방향 • 왼쪽 : 1 • 위쪽 : 2 • 오른쪽 : 3 • 아래쪽 : 4 • 왼쪽위 : 5 • 왼쪽아래 : 6 • 오른쪽위: 7 • 오른쪽아래: 8
예제
입장하는 플레이어를 지정한 좌표로 이동시키기
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
player.spawnAt(5, 5, 1); // 플레이어를 5,5 위치로 왼쪽 방향을 바라보게 이동시키기
});
플레이어의 캐릭터를 name에 해당하는 지정 영역으로 지정한 방향을 바라보게 이동시킵니다.
파라미터
name
String
플레이어를 이동시킬 지정 영역의 이름
dir
number
- 캐릭터가 바라볼 방향 • 왼쪽 : 1 • 위쪽 : 2 • 오른쪽 : 3 • 아래쪽 : 4 • 왼쪽위 : 5 • 왼쪽아래 : 6 • 오른쪽위: 7 • 오른쪽아래: 8
예제
입장하는 플레이어를 지정 영역으로 이동시키기
⚠️ 같은 이름의 지정 영역이 여러 곳 있다면 해당 영역들 중 한 곳으로 랜덤 이동합니다.
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
// 플레이어를 "test"라는 이름의 지정영역으로 왼쪽 방향을 바라보게 소환하기
player.spawnAtLocation("test", 1);
});
플레이어를 해당 스페이스 해당 맵으로 이동시킵니다.
파라미터
spaceHashID
String
이동할 스페이스의 spaceHashID
mapHashID
String
이동할 맵의 mapHashID
예제
입장하는 플레이어를 ZEP 튜토리얼 맵으로 이동시키기 ( 스페이스와 맵 이해하기 )
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
// 플레이어를 ZEP 튜토리얼 맵으로 이동시키기
player.spawnAtMap("65jeBA", "2YvXMJ");
});
[1] 플레이어의 시점을 지정된 좌표로 중심 이동시킵니다.
[2] 플레이어의 시점을 특정 오브젝트로 중심 이동시킵니다.
파라미터
tileX
Number
x좌표
tileY
Number
y좌표
key
String
오브젝트의 키 값
time
Number
시점이 목표 지점까지 이동하는데 걸리는 시간(초)
예제
[1] 플레이어가 보고 있는 화면의 중심을 입력한 좌표로 이동 및 초기화 시키기
// 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] 플레이어의 시점을 오브젝트로 이동 및 초기화 시키기
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();
});
플레이어의 배경 또는 전경 이미지를 설정 할 수 있습니다.
파라미터
resource
ScriptDynamicResource
스크립트에 로드한 이미지 객체
offsetX
Number
px 단위로 x 축 방향의 오프셋을 설정할 수 있는 속성
offsetY
Number
px 단위로 y 축 방향의 오프셋을 설정할 수 있는 속성
type
0
| 1
설정타입
- 0
: 배경 설정
- 1
: 전경 설정
사용 가능한 이펙트 애니메이션 키 값
캐릭터의 움직임에 맞춰 애니메이션을 재생할 수 있도록 설정이 가능합니다. (참고 문서)
캐릭터의 움직임에 대응하는 애니메이션이 정의되어 있지 않은 경우 애니메이션이 재생되지 않습니다.
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 // 위쪽 방향 공격 모션
예제
플레이어 배경이미지 설정해보기
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();
});
플레이어에게 애니메이션 효과를 repeatNum
횟수만큼 반복 재생하고 사라지는 효과를 적용합니다.
파라미터
resource
ScriptDynamicResource
스크립트에 로드한 이미지 객체
repeatNum
Number
애니메이션 재생을 반복할 횟수
offsetX
Number
px 단위로 x 축 방향의 오프셋을 설정할 수 있는 속성
offsetY
Number
px 단위로 y 축 방향의 오프셋을 설정할 수 있는 속성
1회 재생 후 사라지는 이펙트 효과
// 단일 애니메이션으로 정의해 사용합니다.
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);
});
플레이어 개인 화면 상에서 key 값을 가지는 오브젝트를 사라지게하는 함수입니다.
파라미터
key
String
사라지게할 오브젝트의 key 값
오브젝트와 상호작용 시 개인에게 사라지게 하기
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);
}
});
지정한 좌표에 플레이어에게만 보이는 오브젝트를 설치하는 함수입니다. ( 기준 좌표: Left Top )
dynamicResource
파라미터에 null
입력시 해당 좌표의 오브젝트를 삭제할 수 있습니다.
관련 문서: 오브젝트 npcProperty
파라미터
x, y
Number
오브젝트를 놓을 x, y 좌표
dynamicResource
ScriptDynamicResource
App.loadSpritesheet 함수로 로드한 이미지
옵션 (option)
값을 입력하지 않아도 Default 값이 적용됩니다
key
String
오브젝트의 키 값 (Default: null)
moveSpeed
Number
오브젝트의 이동속도
(Default: 80)
useDirAnim
Boolean
방향을 인지해서 애니메이션을 재생하는 옵션 (Default: false)
impassable
Boolean
오브젝트 통과불가 옵션 (Default: false)
offsetX
Number
오브젝트 이미지의 배치 기준점의 X좌표 (Default: 0)
offsetY
Number
오브젝트 이미지의 배치 기준점의 Y좌표 (Default: 0)
npcProperty
Object
npcProperty 관련 내용은 를 참고해주세요. (Default: null)
topObject
Boolean
true 로 설정하면 오브젝트가 상단오브젝트로 설치됩니다. (Default: false)
예제
키 값을 가진 블루맨 오브젝트 생성해보기
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 함수 한 눈에 보기
// 플레이어에게 사운드를 재생
player.playSound(fileName: string, loop: boolean = false)
// 플레이어에게 링크에 해당하는 사운드를 재생
player.playSoundLink(link: string, loop: boolean = false)
해당 플레이어에게 사운드를 재생하는 함수입니다.
파라미터
fileName
String
불러올 파일의 이름
loop
boolean
true: 반복 재생 false: 1회 재생
overlap
boolean
사운드 오버랩(겹침) 재생 가능 여부
key
string
재생하는 사운드를 식별하는 데 사용되는 문자열입니다. 기본 값은 "ambient"로 설정되어 있습니다.
volume
number
사운드의 볼륨을 조절하는 데 사용되는 숫자입니다. 값의 범위는 0에서 1까지이며, 0은 소리가 없음을 나타내고, 1은 최대 볼륨을 나타냅니다.
예제
입장음 설정해보기(파일)
// 플레이어가 입장할 때 실행
App.onJoinPlayer.Add(function (player) {
player.playSound("join.mp3",false, true, "join", 0.5);
});
모든 플레이어에게 사운드를 재생하는 함수입니다.
💡 올바른 링크를 입력했는데 재생이 되지 않는 경우
CORS 정책을 위반한 경우일 가능성이 높습니다. CORS 정책을 맞출 수 없는 경우에는 playSoundLink 대신 음악 파일을 업로드 하여 playSound 함수를 사용하는 것을 권장 드립니다.
파라미터
link
String
사운드 url
loop
boolean
true: 반복 재생 false: 1회 재생
overlap
boolean
사운드 오버랩(겹침) 재생 가능 여부
key
string
재생하는 사운드를 식별하는 데 사용되는 문자열입니다. 기본 값은 "ambient"로 설정되어 있습니다.
volume
number
사운드의 볼륨을 조절하는 데 사용되는 숫자입니다. 값의 범위는 0에서 1까지이며, 0은 소리가 없음을 나타내고, 1은 최대 볼륨을 나타냅니다.
예제
입장음 설정해보기(사운드 url)
// 플레이어가 입장할 때 실행
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);
});
key에 해당하는 사운드의 재생을 중지하는 함수입니다.
key
string
중지하려는 사운드의 키 값
재생 중인 사운드 중지 시키기 아래 예제 코드 실행 후 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 함수 한 눈에 보기
// 플레이어 필드값을 수정한 후 업데이트
player.sendUpdated()
// 플레이어 스토리지값을 저장
player.save()
App, Player 관련 필드 값이 변경되면 변경 값을 적용하는 함수입니다.
파라미터
없음
App, Player storage 값이 변경되면 변경 값을 적용하는 함수입니다.
파라미터
없음