[Korean] HaxBall Headless Host - dapucita/haxbotron GitHub Wiki

HaxBall Headless Host by dapucita (2021.01.24.)

이 문서는 헥스볼 게임에서 Headless Host 기능을 활용하는 방법을 설명합니다.

dapucita가 작성하였으며, Headless HostAPI 명세헥스볼의 제작자인 basro가 공개한 영어 문서(2019년 3월 20일자)를 한국어로 번역하고 보충하였습니다.

이 문서의 내용을 따라하고 활용하기 위해서는 프로그래밍Javascript에 대한 배경지식이 있어야 합니다.

당장 이해하기 어렵거나 필요하지 않은 내용은 일단 넘어가는 것이 좋을 수도 있습니다.

공식 명세가 변경되는 경우 내용 상의 차이가 발생할 수 있으며 최신 명세를 준수하기 위해서는 원본 문서를 따라야 합니다.

들어가며

헥스볼Haxe 언어로 개발되었으며 다양한 커스텀 맵을 지원하는 온라인 게임입니다.

과거 Adobe Flash 상에서 실행되었지만 HTML5로 전환되었기에 웹표준을 준수하는 크롬, 파이어폭스 등의 웹브라우저만 있으면 쉽게 즐길 수 있습니다.

헥스볼은 WebRTC를 이용해 P2P로 연결되는 멀티 플레이어 게임이기에 다른 사람과 같이 즐기기 위해서는 방을 개설해야 하며, 이때 웹브라우저를 종료하면 방도 같이 종료된다는 문제가 있습니다.

따라서 Headless Host를 활용하여 방을 개설하면 방장이 상주하지 않더라도 방을 유지하고 게임을 즐길 수 있기에 유용합니다.

JavascriptHeadless Host를 구현할 수 있으며, 그래픽과 음향을 요구하지 않기 때문에 서버 환경에서도 실행할 수 있습니다.

실행하기

API를 활용하여 구현체를 완성했다면 기본적으로는 웹브라우저를 통해 이를 실행할 수 있습니다.

이 페이지에 접속하여 개발자 도구를 열고, 소스코드를 붙여넣으면 됩니다.

크롬의 경우 F12 키를 누르면 개발자 도구를 열 수 있습니다.

소스코드가 웹브라우저에 의해 실행되고 로봇 방지 검증 과정을 통과한 이후에 정상적으로 작동할 것입니다.

자동화하기

방을 개설하기 위해 웹브라우저를 켜고 소스코드를 붙여넣는 것을 반복하는 일은 무척이나 따분하고 원래의 취지에도 맞지 않습니다.

다행히도 Headless Host를 구현한 소스코드와 웹브라우저를 자동으로 실행하는 소스코드를 결합한다면 방을 개설하고 운영하는 과정을 편리하게 자동화할 수 있습니다.

따라서 웹브라우저를 자동으로 실행하고 제어하기 위해서는 Headless WebBrowser를 활용해야 합니다.

예를 들어 Puppeteer를 통해 Node.js 환경에서 JavascriptChromium WebBrowser를 조작하고 제어할 수 있습니다.

Node.js 환경으로 전환할 때의 장점은 Javascript API가 제한된 웹브라우저 환경에서 벗어나 보다 다양한 기능을 활용할 수 있다는 것입니다.

이 문서의 작성자인 dapucita가 개발한 Haxbotron 역시 이런 과정을 통해 프로그램을 자동으로 실행하고 관리하며 게임을 즐길 수 있게 해줍니다. 참고하세요.

접속 문제 해결하기

VPS 등의 서버 환경에서 Headless Host를 구동하는 경우 일부 플레이어들이 접속하지 못하는 문제가 발생할 수도 있습니다.

이러한 문제는 크롬 웹브라우저 버전 78 이상을 활용하여 구동할 때 발생할 수 있으며, WebRTC에 관한 보안 설정에 기인하는 문제입니다.

따라서 로컬IP에 대한 WebRTC 익명화 옵션을 비활성화해야 하며 크롬 웹브라우저를 실행할 때 커맨드 옵션으로 --disable-features=WebRtcHideLocalIpsWithMdns를 붙여주면 됩니다.

보안 설정하기

Headless Host은 분명 편리한 기능이지만 불행히도 헥스볼의 태생적인 취약점에 노출되어 있습니다.

헥스볼은 온라인 연결을 지원하기 위해 WebRTC를 활용하며, 따라서 악의적인 시도에 의해 IP가 유출되고 네트워크 공격을 받을 수 있습니다.

소위 핑폭 현상은 주로 서비스거부공격(DoS), 디도스(DDoS, 분산서비스거부공격)에 의해 발생하는 것으로, 방을 개설한 컴퓨터에 과도한 부하를 주어 게임 이용을 방해하는 악의적인 공격에 의해 발생합니다.

따라서 이러한 공격을 방어하기 위해서는 서버 운용 시에 활용되는 여러 보안 정책을 적용할 필요가 있습니다.

일부 서버 제공 업체에서는 자체적인 방어 체계를 갖추고 있기 때문에 이러한 네트워크 공격을 원천적으로 방지하는데 도움을 받을 수 있습니다.

서버 환경에서는 모든 네트워크 접근을 기록할 수 있으며 따라서 민/형사법상 대응을 위한 증빙 자료로 제출할 수 있습니다.

다른 프로그래밍 언어 사용하기

Headless Host를 구현하기 위해서 반드시 Javascript만을 사용해야 하는 것은 아닙니다.

기본적인 원리는 같으므로 Javascript로 컴파일되거나, 혹은 웹 컨테이너 래퍼를 제공하는 다른 프로그래밍 언어를 사용할 수 있습니다.

가령 Typescript와 같은 언어는 Javascript로 트랜스파일링되기 때문에 Javascript를 대체할 수 있습니다.

혹은 C# 혹은 Python 등의 다른 언어를 사용해 웹 객체를 조작할 수 있으므로 Javascript의 대안이 될 수 있습니다.

API 명세

Headless Host를 활용하기 위해서는 먼저 방(Room)을 개설하여야 하며, 이를 위한 HBInit 메서드는 window 전역 객체에 포함되어 있습니다.

따라서 window.HBInit 메서드를 호출하여 Room 객체를 초기화할 수 있으며 이후에 window.onHBLoaded 메서드가 호출됩니다.

한편 게임 상에서의 상태(state)를 변화시키는 모든 API 기능은 비동기로 실행됩니다.

가령 roomObject.getPlayerList 메서드를 통해 플레이어 목록을 가져온 이후에 roomObject.setPlayerTeam 메서드를 사용하여 플레이어를 다른 팀으로 옮기는 경우, 이미 받아온 플레이어 목록에는 이러한 변화가 반영되어 있지 않을 것입니다.

이러한 동기비동기에 대한 차이를 숙지하여야 합니다.

Typescript와 같은 강타입 언어로 개발하려는 경우, Headless Host에 대한 타입 선언을 활용할 수 있습니다. Haxbotron에는 이러한 타입을 구현해놓은 인터페이스가 포함되어 있습니다. 참고하세요.


📦 Window 전역 객체

🗃️ HBInit 메서드

이름 역할 반환 타입
HBInit 방(Room) 객체를 초기화하여 반환합니다. RoomObject
매개변수 매개변수 타입 역할
roomConfig RoomConfigObject 방(Room)의 세부 사항을 설정합니다.

방(Room)를 처음 만들기 위해 사용하며 반환된 객체를 통해 조작하고 제어할 수 있습니다.

이 메서드를 호출하면 Headless Host 페이지 상에 로봇 방지 검증 과정이 시작되며, 이를 통과해야 정상적으로 기능을 수행할 수 있습니다.

let room = HBInit({
	roomName: "My room", // 방의 이름입니다.
	maxPlayers: 16, // 들어올 수 있는 최대 인원수입니다.
	noPlayer: true // 호스트(봇 플레이어) 상주 여부입니다. (true로 설정할 것을 추천합니다)
});
room.setDefaultStadium("Huge"); // Huge 맵을 선택합니다.
room.setScoreLimit(3); // 점수 제한을 5점으로 설정합니다.
room.setTimeLimit(3); // 시간 제한을 3분으로 설정합니다.

// 어드민(방장) 권한을 가진 플레이어가 아무도 없다면 갱신하여 새로 부여합니다.
function updateAdmins() { 
  let players = room.getPlayerList(); // 모든 플레이어 목록을 가져옵니다.
  if ( players.length == 0 ) return; // 아무도 접속중이지 않다면 작동하지 않습니다.
  if ( players.find((player) => player.admin) != null ) return; // 어드민이 있다면 작동하지 않습니다.
  room.setPlayerAdmin(players[0].id, true); // 목록 상의 첫 번째 플레이어에게 어드민 권한을 부여합니다.
}

room.onPlayerJoin = function(player) { // onPlayerJoin는 플레이어가 접속할 때 실행되는 이벤트 메서드입니다.
  updateAdmins(); // 어드민(방장) 권한을 가진 플레이어가 아무도 없다면 갱신하여 새로 부여합니다.
}

room.onPlayerLeave = function(player) { // onPlayerLeave는 플레이어가 나갈 때 실행되는 이벤트 메서드입니다.
  updateAdmins(); // 어드민(방장) 권한을 가진 플레이어가 아무도 없다면 갱신하여 새로 부여합니다.
}

📦 RoomConfigObject 객체

Window 전역객체의 HBInit 메서드는 RoomConfigObject 객체를 바탕으로 방(Room) 객체를 초기화합니다.

RoomConfigObject 객체의 모든 멤버 변수는 선택적으로 필요한 값만 설정할 수 있습니다.

멤버변수 멤버변수 타입 역할
roomName string 방의 이름(제목)
playerName string 호스트의 이름
password string 방의 비밀번호
maxPlayers int 최대 접속 가능 인원수
public bool 방 목록에의 노출 여부
geo object literal 지역 정보 덮어쓰기
token string 인증 토큰
noPlayer bool 호스트 표시 여부

📇 geo 멤버변수

프로퍼티 프로퍼티 타입 역할
code string 국가 코드
lat float 위도
lon float 경도

📇 token 멤버변수

Headless Host를 여는 경우 reCaptcha 인증이 요구될 수 있습니다.

이 때 token 인증 토큰을 함께 보내면 이 과정을 생략할 수 있으며, 여기에서 발급받을 수 있습니다.

인증 토큰은 수 분 후에 만료됩니다.

📇 noPlayer 멤버변수

이 멤버변수의 값을 true로 설정하는 경우, 방(Room)을 열 때 봇 호스트(방장)가 표시되지 않습니다.

기본값은 하위 호환성을 위해 false로 지정되어 있지만, true로 설정할 것을 권장합니다.

봇 호스트에 의해 게임 이벤트가 발생하는 경우 byPlayer 매개변수의 값은 null이 되므로 타입 검사가 필요할 수 있습니다.


📦 RoomObject 객체

RoomObject 객체를 사용하여 방(Room)을 조작하고 게임 이벤트를 처리할 수 있습니다.

🗃️ sendChat 메서드

이름 역할 반환 타입
sendChat 봇 호스트를 통해 채팅 메시지를 보냅니다. void
매개변수 매개변수 타입 역할
message string 채팅 메시지의 본문
targetId? int 채팅 메시지를 받을 플레이어의 아이디(id)

targetId 매개변수가 null 값을 가지는 경우 모든 플레이어에게 채팅 메시지가 전송됩니다.

room.sendChat("안녕하세요!", null); // 모든 플레이어에게 보냅니다.
room.sendChat("반갑습니다!", 1); // 아이디가 1인 플레이어에게 보냅니다.

🗃️ setPlayerAdmin 메서드

이름 역할 반환 타입
setPlayerAdmin 해당 플레이어의 방장(어드민) 권한을 통제합니다. void
매개변수 매개변수 타입 역할
playerID int 대상 플레이어의 아이디
admin bool 권한 부여 혹은 박탈

admin 매개변수가 true 값을 가지는 경우 해당 플레이어에게 권한을 부여하며, false 값을 가지는 경우 반대로 권한을 박탈합니다.

🗃️ setPlayerTeam 메서드

이름 역할 반환 타입
setPlayerTeam 해당 플레이어의 팀을 이동합니다. void
매개변수 매개변수 타입 역할
playerID int 대상 플레이어의 아이디
team TeamID 팀 아이디

🗃️ kickPlayer 메서드

이름 역할 반환 타입
kickPlayer 해당 플레이어를 추방합니다. void
매개변수 매개변수 타입 역할
playerID int 대상 플레이어의 아이디
reason string 사유
ban bool 영구추방 여부

ban 매개변수가 true 값을 가지는 경우 영구추방(밴)하며, 반대로 false 값을 가지는 경우 일반 추방(킥)을 합니다.

영구추방은 일반적으로 호스트가 인게임 명령어(/clear_bans)를 사용하거나, 방(Room)을 재시작해야 해제할 수 있습니다. 다만 Headless Host에서 호스트는 봇이므로 인게임 명령어(/clear_bans)를 사용하여 해제하는 것은 불가합니다. 혹은 clearBan이나 clearBans와 같은 Headless Host 메서드를 사용하면 봇을 통해 해제할 수 있습니다.

🗃️ clearBan 메서드

이름 역할 반환 타입
clearBan 해당 플레이어의 영구추방을 해제합니다. void
매개변수 매개변수 타입 역할
playerID int 대상 플레이어의 아이디

🗃️ clearBans 메서드

이름 역할 반환 타입
clearBans 모든 영구추방을 해제합니다. void

🗃️ setScoreLimit 메서드

이름 역할 반환 타입
setScoreLimit 게임의 점수 제한을 설정합니다. void
매개변수 매개변수 타입 역할
limit int 제한 점수

이 메서드는 게임이 진행 중인 경우에는 동작하지 않습니다.

🗃️ setTimeLimit 메서드

이름 역할 반환 타입
setTimeLimit 게임의 시간 제한 (분 단위)을 설정합니다. void
매개변수 매개변수 타입 역할
limitInMinutes int 제한 시간 (분 단위)

이 메서드는 게임이 진행 중인 경우에는 동작하지 않습니다.

🗃️ setCustomStadium

이름 역할 반환 타입
setCustomStadium 사용자 정의(커스텀) 맵을 불러옵니다. void
매개변수 매개변수 타입 역할
stadiumFileContents string 맵 파일 정의

.hbs 맵 파일 정의를 해석하여 맵으로 불러옵니다.

.hbs 맵 파일은 JSON 규격으로 정의된 텍스트 파일이며, 따라서 Headless Host에서 사용할 때는 텍스트 내용을 지정해주어야 합니다.

이 메서드는 게임이 진행 중인 경우에는 동작하지 않습니다.

setCustomStadium(stadiumFileContents : string) : void

let stadiumFileText = `...` // 여기에 맵 파일 정의를 삽입하세요.
let room = HBInit({}); // 방을 엽니다.
room.setCustomStadium(stadiumFileText); // 사용자 정의 맵을 불러옵니다.

🗃️ setDefaultStadium 메서드

이름 역할 반환 타입
setDefaultStadium 기본 맵을 불러옵니다. void
매개변수 매개변수 타입 역할
stadiumName string 맵 이름

Haxball 기본 맵을 불러옵니다. 이름은 대소문자를 구분합니다.

이 메서드는 게임이 진행 중인 경우에는 동작하지 않습니다.

🗃️ setTeamsLock 메서드

이름 역할 반환 타입
setTeamsLock 팀 이동을 제한합니다. void
매개변수 매개변수 타입 역할
locked bool 제한 여부

locked 매개변수가 true 값을 가지면 일반 플레이어의 팀 이동이 제한되며, 반대로 false 값을 가지면 팀 이동이 허용됩니다. 방장(어드민)은 다른 플레이어를 수동으로 옮길 수 있습니다.

🗃️ setTeamColors 메서드

이름 역할 반환 타입
setTeamColors 팀 무늬(색상)를 설정합니다. void
매개변수 매개변수 타입 역할
team TeamID 팀 아이디
angle float 줄무늬 각도
textColor int 플레이어 아바타 색상
colors int[] 팀 색상

textColorcolors 매개변수는 0xFF0000(빨강)처럼 정수 값으로 색상을 지정합니다.

colors 매개변수는 정수 값의 배열을 지정하며 3개의 요소를 갖습니다. 첫 번째 요소는 팀의 기본 색상, 두 번째 요소와 세 번째 요소는 보조 색상입니다.

두 번째와 세 번째 요소는 생략할 수 있으며, 이 때 줄무늬도 사라집니다.

🗃️ startGame 메서드

이름 역할 반환 타입
startGame 게임을 시작합니다. void

이 메서드는 게임이 진행 중인 경우에는 동작하지 않습니다.

🗃️ stopGame 메서드

이름 역할 반환 타입
stopGame 게임을 종료합니다. void

이 메서드는 게임이 진행 중이지 않은 경우에는 동작하지 않습니다.

🗃️ pauseGame 메서드

이름 역할 반환 타입
pauseGame 게임을 일시정지합니다. void
매개변수 매개변수 타입 역할
pauseState bool 일시정지 여부

pauseState 매개변수가 true 값을 가지면 일시정지하며, false 값을 가지면 다시 재개합니다.

게임을 재개하는 경우 약간의 대기 시간을 거친 후에 경기가 이어집니다.

🗃️ getPlayer 메서드

이름 역할 반환 타입
getPlayer 특정 플레이어의 PlayerObject 객체를 가져옵니다. PlayerObject
매개변수 매개변수 타입 역할
playerId int 대상 플레이어의 아이디

해당 플레이어가 존재하지 않는(접속 중이지 않은) 경우 null 값을 반환합니다.

🗃️ getPlayerList 메서드

이름 역할 반환 타입
getPlayer 모든 플레이어의 PlayerObject 객체 배열을 가져옵니다. PlayerObject[]

방(Room)에 접속 중인 모든 플레이어의 객체 배열을 반환합니다.

🗃️ getScores 메서드

이름 역할 반환 타입
getScores 경기의 점수와 시간 정보를 담은 ScoresObject 객체를 가져옵니다. ScoresObject

이 메서드는 게임이 진행 중이지 않은 경우에는 null을 반환합니다.

🗃️ getBallPosition 메서드

이름 역할 반환 타입
getBallPosition 공의 좌표를 담은PositionObject 객체를 가져옵니다. PositionObject

이 메서드는 게임이 진행 중이지 않은 경우에는 null을 반환합니다.

🗃️ startRecording 메서드

이름 역할 반환 타입
startRecording 경기 녹화를 시작합니다. void

이 메서드를 사용한 후에는 반드시 녹화를 종료하는 stopRecording 메서드를 사용해야 불필요한 낭비를 방지할 수 있습니다.

🗃️ stopRecording 메서드

이름 역할 반환 타입
stopRecording 경기 녹화를 종료합니다. Uint8Array

이 메서드는 경기 녹화가 진행 중이지 않은 경우에는 null을 반환합니다.

녹화 데이터는 일반적으로 .hbr2 파일로 저장합니다. 여기에서 활용법을 참고하세요.

Uint8Array 타입에 대한 자세한 정보는 여기에서 JavaScript 형식화 배열ArrayBuffer를 참고하세요.

🗃️ setPassword 메서드

이름 역할 반환 타입
setPassword 방의 비밀번호를 설정하거나 해제합니다. void
매개변수 매개변수 타입 역할
pass? string 비밀번호

pass 매개변수가 null 값을 가지는 경우 방의 비밀번호를 해제합니다.

🗃️ setRequireRecaptcha 메서드

이름 역할 반환 타입
setRequireRecaptcha 방에 접속할 때 reCaptcha 인증을 요구하거나 면제합니다. void
매개변수 매개변수 타입 역할
required bool 인증 요구 여부

required 매개변수가 true 값을 가지면 인증을 요구하며, 반대로 false 값을 가지면 인증을 요구하지 않습니다.

🗃️ reorderPlayers 메서드

이름 역할 반환 타입
reorderPlayers 방에 접속할 때 reCaptcha 인증을 요구하거나 면제합니다. void
매개변수 매개변수 타입 역할
playerIdList int[] 대상 플레이어들의 아이디
moveToTop bool 최상단 이동 여부

playerIDList 매개변수는 대상 플레이어들의 아이디 배열을 지정합니다. 배열로 지정된 플레이어들은 우선 플레이어 목록에서 지워지며, 배열 요소의 순서에 따라 다시 표시됩니다.

moveToTop 매개변수가 true 값을 가지면 플레이어 목록의 위에서부터 표시하며, 반대로 false 값을 가지면 아래에서부터 표시합니다.

🗃️ sendAnnouncement 메서드

이름 역할 반환 타입
sendAnnouncement 시스템 메시지를 보냅니다. void
매개변수 매개변수 타입 역할
msg string 메시지 본문
targetId? int 대상 플레이어의 아이디
color? int 메시지 색상
style? string 메시지 서식
sound? int 알람 소리 단계

이 메서드는 sendChat 메서드보다 많은 양의 채팅 메시지를 한 번에 전송할 수 있으며, 이 때 봇 호스트는 보낸 사람으로 표시되지 않습니다.

targetID 매개변수가 null 값을 가지면 모든 플레이어에게 메시지를 전송합니다.

color 매개변수는 0xFF0000(빨강)처럼 정수 값으로 색상을 지정하며, null 값을 가지면 일반 채팅 메시지와 같은 기본 색상으로 표시됩니다.

color 매개변수는 "normal", "bold", "italic", "small", "small-bold", "small-italic" 중 하나의 값을 가질 수 있으며, null 값을 가지면 "normal" 값을 가지는 것과 동일한 서식을 가집니다.

sound 매개변수는 0, 1, 2 중 하나의 값을 가질 수 있습니다. 0으로 지정하면 알람 소리를 내지 않으며 1로 지정해야 일반 채팅 메시지와 같은 알람 소리를 냅니다. 2로 지정하는 경우 경고 알람 소리를 냅니다.

color 매개변수의 가능한 각 값은 워드 프로세서오와 마찬가지로 일반, 강조, 기울인 글씨, 작은 글씨, 작고 강조한 글씨, 작고 기울인 글씨를 의미합니다.

🗃️ setKickRateLimit 메서드

이름 역할 반환 타입
setKickRateLimit kick rate를 제한합니다. void
매개변수 매개변수 타입 역할
min int kick 사이 허용되는 프레임 수
rate int kick 제한 수
burst int kick 비축 수

이 메서드는 kick(킥, 공차기)과 관련된 kick rate를 설정하고 제한합니다.

min 매개변수는 두 kick 사이의 최소 논리프레임 수를 지정합니다. 이 매개변수의 값보다 빠르게 공을 찰 수는 없으며 기본값은 2입니다.

rate 매개변수는 min 매개변수와 비슷하지만, burst 매개변수의 값에 따라 플레이어로 하여금 나중에 쓸 여분의 kick을 모을 수 있게 해주며 기본값은 0입니다.

burst 매개변수는 플레이어가 여분의 kick을 얼마만큼 모아둘 수 있는 지를 지정하며, 기본값은 0입니다.

// 만약 처음 4번의 킥을 2프레임동안 했으면, 이후에는 15프레임마다 1번의 킥만이 허용됩니다. (1초에 60프레임이므로 4번 가능합니다.)
// 1초 동안 킥을 하지 않으면 킥을 비축하여 4번 가능하게 됩니다.
room.setKickRateLimit(2, 15, 3);

🗃️ setPlayerAvatar 메서드

이름 역할 반환 타입
setPlayerAvatar 플레이어의 아바타를 덮어씁니다. void
매개변수 매개변수 타입 역할
playerId int 대상 플레이어의 아이디
avatar string 아바타

avatar 매개변수가 null 값을 가지면 덮어쓰기가 취소되고, 플레이어가 스스로 아바타를 지정하는 것이 다시 허용됩니다.

🗃️ setDiscProperties 메서드

이름 역할 반환 타입
setDiscProperties 해당 디스크(원반)의 속성을 설정합니다. void
매개변수 매개변수 타입 역할
discIndex int 대상 디스크(원반)의 아이디
properties DiscPropertiesObject 디스크 속성

properties 매개변수는 DiscPropertiesObject 객체를 값으로 가지며 해당 객체의 null 값을 가지는 각 프로퍼티에 대해서는 기존의 설정이 유지됩니다.

room.setDiscProperties(0, {x: 0, y: 0}); // 0번 디스크의 좌표계를 (0,0)으로 지정하지만 다른 설정은 그대로 유지합니다.

🗃️ getDiscProperties 메서드

이름 역할 반환 타입
getDiscProperties 해당 디스크(원반)의 DiscPropertiesObject 객체를 가져옵니다. DiscPropertiesObject
매개변수 매개변수 타입 역할
discIndex int 대상 디스크(원반)의 아이디

discIndex 매개변수의 값이 유효하지 않은 경우 null을 반환합니다.

🗃️ setPlayerDiscProperties 메서드

이름 역할 반환 타입
setPlayerDiscProperties 해당 플레이어의 디스크(원반) 속성을 설정합니다. void
매개변수 매개변수 타입 역할
playerId int 대상 플레이어의 아이디
properties DiscPropertiesObject 디스크 속성

이 메서드는 setDiscProperties 메서드와 달리 플레이어를 대상으로 합니다.

🗃️ getPlayerDiscProperties 메서드

이름 역할 반환 타입
getPlayerDiscProperties 해당 플레이어의 DiscPropertiesObject 디스크(원반) 객체를 가져옵니다. DiscPropertiesObject
매개변수 매개변수 타입 역할
playerId int 대상 플레이어의 아이디

이 메서드는 getDiscProperties 메서드와 달리 플레이어를 대상으로 합니다.

🗃️ getDiscCount 메서드

이름 역할 반환 타입
getDiscCount 게임 내 공과 플레이어를 포함한 모든 디스크(원반)의 개수를 구합니다. int

📦 CollisionFlags 멤버객체

방(Room)을 제어하기 위해 사용하는 RoomObject 객체에는 CollisionFlags 멤버객체가 포함되어 있습니다.

CollisionFlags 객체는 CollisionFlagsObject 객체 타입이며, cMaskcGroup 디스크(원반) 프로퍼티(속성)로 구성된 충돌 플래그 상수(collision flags constants)를 담고 있습니다.

사용할 수 있는 충돌 플래그 상수ball, red, blue, redKO, blueKO, wall, all, kick, score, c0, c1, c2, c3과 같습니다.

자세한 정보는 여기에서 볼 수 있습니다.

// 4번 디스크가 'ball' cGroup 속성을 갖는지 확인합니다.
let discProps = room.getDiscProperties(4);
let hasBallFlag = (discProps.cGroup & room.CollisionFlags.ball) != 0;

// 5번 디스크에 'wall' cMask 속성을 부여하며 다른 속성은 그대로 유지합니다.
let discProps = room.getDiscProperties(5);
room.setDiscProperties(5, {cMask: discProps.cMask | room.CollisionFlags.wall});

📡 게임 이벤트

RoomObject 객체를 사용하여 방(Room)을 조작하고 게임 이벤트를 처리할 수 있습니다.

이벤트 메서드를 만들고 각 이벤트에 이벤트 리스너로 연결하여 다양한 상황에 대응할 수 있습니다.

room.onPlayerJoin = function(player) { // onPlayerJoin는 플레이어가 접속할 때 실행되는 이벤트 메서드입니다.
  // 여기에 이벤트 처리를 구현합니다.
  // 이 이벤트에는 player 매개변수가 주어지므로 이를 활용할 수 있습니다.
  room.sendChat(`${player.name}님을 환영합니다.`, player.id);
}

📡 onPlayerJoin 이벤트

이름 역할 반환 타입
onPlayerJoin 새 플레이어가 접속하였을 때 호출됩니다. void
매개변수 매개변수 타입 역할
player PlayerObject 대상 플레이어의 PlayerObject 객체

📡 onPlayerLeave 이벤트

이름 역할 반환 타입
onPlayerLeave 해당 플레이어가 나갔을 때 호출됩니다. void
매개변수 매개변수 타입 역할
player PlayerObject 대상 플레이어의 PlayerObject 객체

📡 onTeamVictory 이벤트

이름 역할 반환 타입
onTeamVictory 경기 결과가 정해진 후 호출됩니다. void
매개변수 매개변수 타입 역할
scores ScoresObject 경기가 끝난 직후의 결과를 담은 ScoresObject 객체

Haxball 게임의 각 경기는 점수 차에 의해 승리 메시지가 표시된 후에야 완전히 종료됩니다. 따라서 onTeamVictory 이벤트가 호출된 후 onGameStop 이벤트가 연이어 호출됩니다.

📡 onPlayerChat 이벤트

이름 역할 반환 타입
onPlayerChat 플레이어가 채팅 메시지를 보낼 때 호출됩니다. bool
매개변수 매개변수 타입 역할
player PlayerObject 해당 플레이어의 PlayerObject 객체
message string 채팅 메시지 본문

이 이벤트는 true를 반환할 때 채팅 창에 채팅 메시지를 표시하며, 반대로 false를 반환하면 채팅 메시지를 실제로는 표시하지 않습니다.

📡 onPlayerBallKick 이벤트

이름 역할 반환 타입
onPlayonPlayerBallKick 플레이어가 공을 차면 호출됩니다. void
매개변수 매개변수 타입 역할
player PlayerObject 해당 플레이어의 PlayerObject 객체

플레이어가 키(슛 혹은 패스)를 눌러 실제로 공을 차는 경우에만 이 이벤트가 호출되며, 단지 몸으로 밀기만 하는 경우에는 호출되지 않습니다.

📡 onTeamGoal 이벤트

이름 역할 반환 타입
onTeamGoal 골이 들어가면 호출됩니다. void
매개변수 매개변수 타입 역할
team TeamID 골을 넣은 팀의 아이디

자책골을 넣는 경우에도 이 이벤트가 호출됩니다.

📡 onGameStart 이벤트

이름 역할 반환 타입
onGameStart 경기를 시작하면 호출됩니다. void
매개변수 매개변수 타입 역할
byPlayer? PlayerObject 경기를 시작한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 경기가 시작되는 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onGameStop 이벤트

이름 역할 반환 타입
onGameStop 경기가 종료되면 호출됩니다. void
매개변수 매개변수 타입 역할
byPlayer? PlayerObject 경기를 종료한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 경기가 종료된 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onPlayerAdminChange 이벤트

이름 역할 반환 타입
onPlayerAdminChange 대상 플레이어의 방장(어드민) 권한이 변경되면 호출됩니다. void
매개변수 매개변수 타입 역할
changedPlayer PlayerObject 권한이 변경된 해당 플레이어의 PlayerObject 객체
byPlayer? PlayerObject 해당 플레이어에 대해 권한을 변경한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 권한이 변경된 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onPlayerTeamChange 이벤트

이름 역할 반환 타입
onPlayerTeamChange 대상 플레이어의 팀이 변경되면 호출됩니다. void
매개변수 매개변수 타입 역할
changedPlayer PlayerObject 팀이 변경된 해당 플레이어의 PlayerObject 객체
byPlayer? PlayerObject 해당 플레이어에 대해 팀을 변경한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 팀이 변경된 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onPlayerKicked 이벤트

이름 역할 반환 타입
onPlayerKicked 플레이어가 추방되면 호출됩니다. void
매개변수 매개변수 타입 역할
kickedPlayer PlayerObject 추방당한 해당 플레이어의 PlayerObject 객체
reason string 추방 사유
ban bool 영구추방 여부
byPlayer? PlayerObject 해당 플레이어를 추방한 플레이어의 PlayerObject 객체

영구추방인 경우 ban 매개변수가 true 값을 가집니다.

플레이어가 아닌 봇에 의해 추방된 경우 byPlayer 매개변수가 null 값을 가집니다.

onPlayerLeave 이벤트가 호출된 후에야 onPlayerKicked 이벤트가 호출됩니다.

📡 onGameTick 이벤트

이름 역할 반환 타입
onGameTick 경기 중에 매 틱(Tick)마다 호출됩니다. void

경기 중인 경우 1초에 60회씩 틱(Tick)이 발생합니다.

경기 중이 아니거나 일시정지한 경우에는 이 이벤트가 호출되지 않습니다.

이 이벤트는 플레이어와 공의 위치를 감독하는 데 유용합니다.

📡 onGamePause 이벤트

이름 역할 반환 타입
onGamePause 경기가 일시정지되면 호출됩니다. void
매개변수 매개변수 타입 역할
byPlayer? PlayerObject 경기를 일시정지한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 경기가 일시정지된 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onGameUnpause 이벤트

이름 역할 반환 타입
onGameUnpause 경기가 재개되면 호출됩니다. void
매개변수 매개변수 타입 역할
byPlayer? PlayerObject 경기를 재개한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 경기가 재개된 경우 byPlayer 매개변수가 null 값을 가집니다.

경기는 약간의 대기 시간을 거친 후에야 완전히 재개되므로, 실질적으로 재개된 시점을 감지하기 위해서는 onGameUnpause 이벤트가 호출된 후 첫 번째로 발생하는 onGameTick 이벤트를 활용할 수 있습니다.

📡 onPositionsReset 이벤트

이름 역할 반환 타입
onPositionsReset 골 이후 플레이어와 공의 좌표가 초기화(킥오프)되면 호출됩니다. void

📡 onPlayerActivity 이벤트

이름 역할 반환 타입
onPlayerActivity 플레이어가 활동할 때마다 호출됩니다. void
매개변수 매개변수 타입 역할
player PlayerObject 대상 플레이어의 PlayerObject 객체

플레이어가 키를 누르는 것과 같은 활동을 의미하므로 이 이벤트는 부재 중인 플레이어를 감지할 때 유용합니다.

📡 onStadiumChange 이벤트

이름 역할 반환 타입
onStadiumChange 맵이 열리면 호출됩니다. void
매개변수 매개변수 타입 역할
newStadiumName string 열린 맵의 이름
byPlayer? PlayerObject 맵을 불러온 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 맵이 열린 경우 byPlayer 매개변수가 null 값을 가집니다.

📡 onRoomLink 이벤트

이름 역할 반환 타입
onRoomLink 방 주소(링크)가 발급되면 호출됩니다. void
매개변수 매개변수 타입 역할
url string 방의 URL 주소(링크)

📡 onKickRateLimitSet 이벤트

이름 역할 반환 타입
onKickRateLimitSet kick rate가 바뀔 때 호출됩니다. void
매개변수 매개변수 타입 역할
min int kick 사이 허용되는 프레임 수
rate int kick 제한 수
burst int kick 비축 수
byPlayer? PlayerObject 변경한 플레이어의 PlayerObject 객체

플레이어가 아닌 봇에 의해 바뀐 경우 byPlayer 매개변수가 null 값을 가집니다.


📦 PlayerObject 객체

PlayerObject 객체는 플레이어의 정보를 담고 있습니다.

멤버변수 멤버변수 타입 역할
id int 해당 플레이어의 아이디
name string 해당 플레이어의 이름
team TeamID 해당 플레이어의 팀
admin bool 방장(어드민) 권한의 여부
position PositionObject 해당 플레이어의 좌표(위치)
auth string 해당 플레이어의 고유 인증값
conn string 해당 플레이어의 고유 연결값

id 멤버변수는 개별 플레이어의 아이디로 고유한 양의 정수 값을 가집니다. 이 값은 변경될 수 없으며, 플레이어가 새로 접속하는 경우 최근에 접속한 플레이어에게 발급된 아이디보다 1만큼 증가한 새 아이디를 갖게 됩니다.

position 멤버변수는 해당 플레이어가 경기장 내에 위치하지 않는 경우 null 값을 가집니다.

auth 멤버변수는 해당 플레이어에게 주어지는 고유의 문자열 인증값을 가집니다. 플레이어가 동일한 컴퓨터를 사용하더라도 다른 브라우저를 사용하면 이 값은 달라집니다. 따라서 이 멤버변수는 다른 일반적인 게임에서 사용자 계정을 구분하는 것과 마찬가지의 용도로 사용할 수 있습니다. 다만 자동 추방 시스템을 구현하기에는 적절하지 않습니다. onPlayerJoin 이벤트를 통해서만 이 멤버변수의 값이 설정되며, 플레이어 인증의 유효성 검사에 실패하는 경우에는 null 값을 가질 수 있습니다. 플레이어가 인증값을 직접 알고 싶다면 여기에서 Public ID를 확인하면 됩니다.

conn 멤버변수는 해당 플레이어에게 주어지는 고유의 문자열 연결값을 가집니다. 연결값은 각 네트워크 연결마다 고유한 값을 갖게 됩니다. 따라서 플레이어가 동일한 네트워크를 사용하는 경우 이 값이 같게 되므로 중복 접속을 감지할 때 유용합니다. 이 멤버변수는 onPlayerJoin 이벤트를 통해서만 값이 설정됩니다.

이중으로 접속하는 것을 방지하기 위해서는 conn 멤버변수가 같은 플레이어가 이미 접속 중인지 검사하면 됩니다. 전적(스탯) 저장 시스템을 구현한 경우, 이전의 전적 기록을 불러오고 싶다면 auth 멤버변수가 같은 기존 기록을 불러오면 됩니다.


📦 ScoresObject 객체

ScoresObject 객체는 경기의 점수와 시간 정보를 담고 있습니다.

멤버변수 멤버변수 타입 역할
red int 빨강 팀(레드)의 점수
blue int 파랑 팀(블루)의 점수
time float 경과한 시간 (초 단위)
scoreLimit int 점수 제한
timeLimit float 시간 제한

time 매개변수의 시간 값은 게임이 멈춘 상태에서는 변화하지 않습니다.


📦 TeamID 열거형

TeamID 열거형은 정수로 표현한 팀 아이디의 값을 의미합니다.

소스코드의 가독성을 위해 열거형을 사용하며, 혹은 간편하게 정수 값만을 사용해도 됩니다.

팀 이름 정수 값
대기 팀(스펙) 0
레드 팀(빨강) 1
블루 팀(블루) 2

📦 PositionObject 객체

PositionObject 객체는 플레이어 혹은 공의 위치 좌표를 담고 있습니다.

멤버변수 멤버변수 타입 역할
x float X축 좌표
y float Y축 좌표

📦 DiscPropertiesObject 객체

DiscPropertiesObject 객체는 플레이어나 공과 같은 게임 내의 물리 디스크(원반)의 정보를 담고 있습니다.

멤버변수 멤버변수 타입 역할
x float X축 좌표
y float Y축 좌표
xspeed float 속력벡터(speed vector)의 X축 좌표
yspeed float 속력벡터(speed vector)의 Y축 좌표
xgravity float 중력벡터(gravity vector)의 X축 좌표
ygravity float 중력벡터(gravity vector)의 Y축 좌표
radius float 디스크(원반) 반경(반지름)
bCoeff float 반발계수(bouncing coefficient)
invMass float 디스크(원반) 질량의 역수
damping float 감쇠상수(damping factor)
color int 디스크(원반) 색상
cMask int 디스크(원반)의 충돌마스크(collision mask)
cGroup int 디스크(원반)의 충돌그룹(collision groups)

color 매개변수는 0xFF0000(빨강)처럼 정수 값으로 색상을 지정하며, -1 값을 가지는 경우 투명 속성을 가집니다.

cMask 매개변수는 해당 디스크(원반)이 충돌할 수 있는 그룹을 나타냅니다.

활용하기

Headless Host를 개발하기 위해서는 프로그래밍 언어를 사용해 아이디어를 구현해야 합니다.

다양한 기능을 구현할 수 있으며 이 문서에서는 몇 가지의 핵심적인 예시를 설명합니다.

자동으로 방장(어드민) 임명하기

방장(어드민) 권한을 자동으로 부여하여 관리 상의 공백을 막아야 할 수 있습니다.

getPlayerList 메서드를 통해 현재 접속 중인 플레이어 목록을 모두 받아온 후, admin 값을 검사하여 한 명이라도 true 값을 갖지 않는다면 이 때 임명하면 됩니다.

admin 값을 검사하는 시점은 onPlayerJoin, onPlayerLeave, onPlayerAdminChange 이벤트 등이 호출되었을 때가 적절합니다.

중복 접속 방지하기

플레이어가 이중으로 접속하여 적절하지 못한 행태를 보이는 것을 막아야 할 수 있습니다.

PlayerObject 객체의 conn 값을 비교하면 이미 접속한 플레이어가 있는지 검사할 수 있습니다.

onPlayerJoin 이벤트 내에서 getPlayerList 메서드를 통해 현재 접속 중인 플레이어 목록을 모두 받아온 후, conn 값을 비교합니다.

같은 conn 값을 가지는 플레이어가 존재한다면, 새로 접속한 플레이어를 추방합니다.

맵 변경 방지하기

방장(어드민) 권한을 가진 플레이어가 적절하지 않은 맵을 불러와 정상적인 게임 진행을 방해하는 것을 막아야 할 수 있습니다.

onStadiumChange 이벤트가 발생할 때마다 다시 맵을 불러와 사실상 맵을 고정시킬 수 있습니다.

onStadiumChange 내에서 setCustomStadium 메서드 혹은 setDefaultStadium 메서드를 통해 원하는 맵을 불러오도록 합니다.

플레이어 정보 확장하기

여러 기능을 지원하기 위해 플레이어 정보를 다양하게 관리해야 할 수 있습니다.

별도의 자료 구조를 활용하여 PlayerObject 객체보다 많은 정보를 담을 수 있습니다.

배열 혹은 Map 객체를 활용하여 접속 시간, 종료 시간, 공격 포인트 등을 기록합니다.

또는 외부의 DB 서버를 활용하도록 합니다.

플레이어 정보 보존하기

플레이어 정보를 유실 없이 보존해야 할 수 있습니다.

HTML5 localStorage API나 외부의 DB 서버를 활용하여 정보를 보관할 수 있습니다.

휘발성 메모리에 정보를 담은 이후 보존하고자 하는 내용을 선별하여 외부에 기록합니다.

플레이어가 다시 접속하였을 때는 PlayerObject 객체의 auth 값을 비교한 후 해당하는 플레이어의 정보를 불러오도록 합니다.

명령어 구현하기

기능을 확장하기 위해 명령어를 지원해야 할 수 있습니다.

플레이어가 채팅 메시지를 보내면 그것을 처리하여 명령어로 인식할 수 있습니다.

채팅 메시지의 맨 앞에 올 명령어 접두사를 정합니다.

명령어 접두사가 인식되면 해당 명령어를 해석(파싱)하고 기능을 불러오도록 합니다.

마치며

Headless Host를 구현하며 발생하는 문제는 Haxball GitHub에 문의하면 됩니다. 여기에서 제보하면 되며 해당 사이트는 전 세계 사람들이 이용하는 곳이므로 의사소통을 위해 반드시 영어로 내용을 작성하여야 합니다.

또한 Headless Host의 명세는 예고 없이 변경될 수 있으므로 적절히 변경점을 확인하여야 합니다. Haxball 게임의 News 혹은 Haxball GitHub를 통해 새 소식을 주시하면 됩니다.

악의적인 네트워크 공격에 대응하기 위해서는 기존에 공개된 다양한 네트워크 보안 자료를 참고하는 것이 좋습니다. 네트워크 보안은 번거롭고 쉽지 않은 일이며 심지어는 자금의 소모를 야기합니다.

지속적으로 Headless Host를 운영하고자 하는 경우 서버 호스팅을 이용하는 것이 유용할 수 있습니다. 아마존, 구글, 마이크로소프트 등 다양한 기업에서 유료 서비스를 제공하고 있으므로 관련 자료를 참고하면 됩니다.

일부 서비스 제공 업체에서는 무료로 체험할 수 있는 크레딧을 지급하기도 합니다.

주변에 Headless Host 개발을 경험하거나 프로그래밍이 가능한 사람이 있다면 조언을 구할 수 있습니다. 학습 방향과 아이디어에 대한 지도를 부탁할 수 있지만 이것은 당연한 권리가 아닌 호의임을 잊어서는 안됩니다. 직접 구현을 요구하는 것은 그다지 바람직하지 못합니다. 다른 사람에게 부담을 지우기 보다는 스스로 부딪혀보며 문제 해결 능력을 기르는 것이 장기적으로도 옳은 일입니다.

프로그래밍은 시간과 노력의 싸움입니다. 정진하세요!

참고자료

라이센스

Haxball 게임과 Haxball Headless Host의 본질적인 내용의 저작권은 원저작자인 마리오 카르바할(Mario Carbajal; Basro)에게 있습니다.

이 문서의 고유한 내용의 저작권은 dapucita에게 있으며 사용권은 MIT Licence에 준합니다.

기타 내용물의 저작권은 해당 원저작자에게 있습니다.