WIL12주차
새로운 프로젝트 시작
유니티 클라이언트를 받아서 그걸 기반으로 서버를 만드는 프로젝트를 시작했다.
이번 게임은 타워 디펜스 게임으로 클라이언트 서버 모델을 차용한 간단한 형태이다. 초반은 클라이언트를 수정하지 않는다는 전제 하에 작업하게 되었다.
- 1월 20일
사용할 패킷을 분석하고 각자 역할을 분담 받았다. 나는 몬스터 생성, 사망, 공격 등의 패킷 핸들러를 맡았다.
monsterAttackBasehandler 몬스터가 플레이어의 타워를 공격했을 때 발생하는 이벤트를 처리함 monsterDeathNotificationhandler 몬스터가 사망했을 때를 처리함 spawnMonsterhandler 몬스터 생성 이벤트를 처리함 - 1월 21일
처음 정했던 함수들의 기본 로직은 구현하였다.
spawnMonsterNotification라는 패킷이 있어 그걸 구현하려고 시도했으나 알고 보니 서버에서 클라이언트로 보낼 때 쓰는 패킷이었다. - 1월 22일
기존에는 동기화를 각각의 핸들러가 발생할 때만 보냈으나 동기화는 실행되는 내내 보내야 한다는 생각으로 update 함수를 만들어서 관리해보기로 했다.
- 1월 23일
업데이트를 통한 동기화 관리 시도 자체는 좋았으나 게임이 실행되기도 전에 패킷을 보내 오류를 일으키는 문제가 있어 가능한 한 패킷의 딜레이를 거는 작업을 했다.
클라이언트를 수정하여 현재 게임이 작동하고 있다는 걸 전하는 패킷을 만드는 방법도 있으나 클라이언트를 수정하지 않는 방법으로 변형하였다.
실험 결과 1초 간격의 동기화 정도면 문제 없이 가는 걸 확인했다. 현재는 소수의 플레이어를 관리하는 것을 위주로 하고 있기에 별도의 update 함수를 만들어 관리하고 있으나 각각의 gameSession이 update를 굴리도록 하는 방법 또한 고려해볼 필요가 있어 보인다. - 1월 24일
다른 팀원과 마찬가지로 필요한 로직은 구현되었다.
monsterAttackBasehandler
const monsterAttackBaseHandler = async ({ socket, sequence, payload }) => {
try {
const { damage } = payload; //소켓으로 유저 찾아서 매칭.
const user = getUserBySocket(socket);
if(!user)
{
return;
}
// 유저에게 저장된 상대 유저의 소켓으로 상대 유저를 찾습니다.
const enemyUser = getUserBySocket(user.getMatchingUsersocket());
if(!enemyUser)
{
return;
}
//음수값의 수치가 있을 수 있으니까?
if (damage < 0) {
damage = 0;
}
user.updateBase(user.base.hp - damage);
//충돌했을 때 돈은 분명 더 줘야지 제대로 처리할 수 있을 것으로 생각된다.
user.updateGold(user.getGold() + user.pointMultiplier(30));
//충돌 시의 포인트 증가는 있으면 좋되 default 값으로 증가하도록 해보자.
user.updateScore(user.getScore() + 30);
//user.stateSyn(); //--> 추가해서 이거 쓰면 개인을 동기화 합니다.
if (user.base.hp <= 0) { //이때 베이스 체력이 0보다 낮아진다면.
// 유저(지금 보내고 있는)의 베이스가 0이므로 패배 처리를 해줍니다.isWin이 false면 패배입니다.
const gameOverNotificationpayload = {
isWin: false,
}
const packetType = PacketType.GAME_OVER_NOTIFICATION;
const sgameOverNotificationResponse = createResponse(packetType, gameOverNotificationpayload, sequence);
user.socket.write(sgameOverNotificationResponse);
//그 뒤 상대 유저에게는 승리 처리를 해줍니다. isWin이 true면 승리입니다.
const enemygameOverNotificationpayload = {
isWin: true,
}
const enemysgameOverNotificationResponse = createResponse(packetType, enemygameOverNotificationpayload, sequence);
//상대 유저 소캣으로 보내줍니다.
enemyUser.socket.write(enemysgameOverNotificationResponse);
} else { //베이스가 0이 아니라면 업데이트 시켜줍니다. isOpponent이 false 면 유저의 베이스의 체력이 업데이트 됩니다. 모르겠으면 true로 바꿔봅시다.
const updateBaseHPNotificationpayload = {
isOpponent: false,
baseHp: user.base.hp,
};
const packetType = PacketType.UPDATE_BASE_HP_NOTIFICATION;
const updateBaseHPNotificationResponse = createResponse(packetType, updateBaseHPNotificationpayload, sequence);
socket.write(updateBaseHPNotificationResponse);
// 상대 유저에게 보내주는 겁니다. 상대 유저의 화면에 보이는 베이스의 체력을 업데이트 시켜 줍니다.
const enemyupdateBaseHPNotificationpayload = {
isOpponent: true,
baseHp: user.base.hp,
};
const enemyupdateBaseHPNotificationResponse = createResponse(packetType, enemyupdateBaseHPNotificationpayload, sequence);
//상대 유저 소캣으로 보내줍니다.
enemyUser.socket.write(enemyupdateBaseHPNotificationResponse);
}
//일단 동기화를 이렇게 처리하도록 하자
notificationGameSessionsBySocket(socket);
} catch (error) {
console.error(error);
}
};
monsterDeathNotificationHandler
const monsterDeathNotificationHandler = async ({ socket, sequence, payload }) => {
try {
let { monsterId } = payload; //소켓으로 유저 찾아서 매칭.
//어떤 타워가 죽였는지에 따라 번호가 다릅니다. 100~999는 1, 1000~1999는 2, 2000~2999는 3, 3000~3999는 4로 나옵니다.
const prefix = Math.floor(monsterId / 100000);
const user = getUserBySocket(socket);
if(!user)
{
return;
}
monsterId = monsterId % 100000;
//유저가 가지고 있는 몬스터중 같은 아이디의 몬스터를 삭제시킵니다.
//pointMuitiplier : 레벨 값에 따라서 현재 얻을 포인트 값을 곱해서 리턴한다.
user.removeMonster(monsterId);
//타워의 타입에 따라서 골드와 스코어를 올리는 방법을 분리해보자.
switch (prefix) {
case 1://기본 타워
user.updateGold(user.getGold() + user.pointMultiplier(10));
user.updateScore(user.getScore() + user.pointMultiplier(10));
break;
case 2://골드 더 주는 타워'
user.updateGold(user.getGold() + user.pointMultiplier(30));
user.updateScore(user.getScore() + user.pointMultiplier(10));
break;
case 3://스코어 더 주는 타워
user.updateGold(user.getGold() + user.pointMultiplier(10));
user.updateScore(user.getScore() + user.pointMultiplier(30));
break;
case 4://둘 다 더 주는 타워
user.updateGold(user.getGold() + user.pointMultiplier(20));
user.updateScore(user.getScore() + user.pointMultiplier(20));
break;
}
//const gameSession = getJoinGameSessions(user);
const enemyUser = getUserBySocket(user.getMatchingUsersocket());
if(!enemyUser)
{
return;
}
const enemyMonsterDeathNotificationpayload = {
monsterId: monsterId,
};
const packetType = PacketType.ENEMY_MONSTER_DEATH_NOTIFICATION;
const enemyMonsterDeathNotificationResponse = createResponse(
packetType,
enemyMonsterDeathNotificationpayload,
sequence,
);
//user.socket.write(enemyMonsterDeathNotificationResponse);
enemyUser.socket.write(enemyMonsterDeathNotificationResponse);
user.stateSyn();
enemyUser.stateSyn();
// notificationGameSessionsBySocket(socket);
} catch (error) {
console.error(error);
}
};
spawnMonsterhandler
const spawnMonsterHandler = async ({ socket, sequence, payload }) => {
try {
const { } = payload;
const user = getUserBySocket(socket);
if(!user)
{
return;
}
//유저가 참여하고 있는 게임 세션을 가져옵니다.
const gameSessions = getJoinGameSessions(user);
if(gameSessions.length === 0)
{
return;
}
//게임 세션의 몬스터 스폰 카운트를 가져옵니다.
//이 함수는 가져올때마다 세션의 몬스터 카운트가 하나씩 증가해 중복되지 않게 해줍니다.
const monsterId = gameSessions.getSpawnMonsterCounter();
//1부터 5까지라면
const monsterNumber = Math.ceil(Math.random()*(5));
//몬스터 객체를 만들어 줍니다. 반드시 이 형태로 만들어야 합니다.
const monster = { monsterId , monsterNumber, level:gameSessions.monsterLevel };
//몬스터를 유저에게 넣어줍니다.
user.addMonster(monster);
//그뒤 클라에게 몬스터 스폰을 줍니다.
const spawnMonsterpayload = {
monsterId,
monsterNumber,
};
let packetType = PacketType.SPAWN_MONSTER_RESPONSE;
const spawnMonsterResponse = createResponse(packetType, spawnMonsterpayload, sequence);
socket.write(spawnMonsterResponse);
//상대한테 내 몬스터 나왔다고 보네기.
const enemyUser = getUserBySocket(user.getMatchingUsersocket());
const spawnEnemyMonsterNotificationpayload = {
monsterId,
monsterNumber,
}
packetType = PacketType.SPAWN_ENEMY_MONSTER_NOTIFICATION;
const spawnEnemyMonsterNotificationResponse = createResponse(packetType, spawnEnemyMonsterNotificationpayload, sequence);
enemyUser.socket.write(spawnEnemyMonsterNotificationResponse);
notificationGameSessionsBySocket(socket);
} catch (error) {
console.error(error);
}
};