Express에서 게임 행동력 구현 해보기
2020-11-01 18:20:42

예로 애니팡 같은 게임의 하트 등 행동력 채우기 로직 구현 정리

서버

  1. 클라에서 소모를 요청 시 그 당시의 시간(클라에서 소모한 시간, 단위는 ms)을 기록해 둔다.

  2. 일정 시간이 지나면 요청이나 리스트 통신이 올 시 (현재 시간 - 이전에 기록한 시간) / 충전 쿨타임 으로 충전해줘야 하는 개수를 구한다.

  3. 충전할 개수가 있으면 충전 해주고 시간은 서버에서 충전해준 시간으로 갱신을 한다 충전되지 않을 시에는 패스.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const express = require("express");
const mysql = require("mysql2");
const cors = require("cors");

const app = express();

app.use(cors());

const connection = mysql.createConnection({
host: "localhost",
user: "root",
database: "test",
});

const USER_ID = "1q2w3e";
const GET_USER_QUERY = "SELECT * FROM UserActivePoint WHERE user_id = ?";
const UPDATE_USER_QUERY =
"UPDATE UserActivePoint SET last_update_ts = ?, active_point = ? WHERE user_id = ?";
const CHARGE_TIME = 60 * 1000; // 충전 쿨타임 단위는 ms
const MAX_ACTIVE_POINT = 5;

app.get("/", (req, res) => {
const now = Date.now();
connection.query(GET_USER_QUERY, [USER_ID], (err, results) => {
if (err) throw err;
if (results.length === 0) {
return connection.query(
"INSERT INTO UserActivePoint(user_id, last_update_ts, active_point) VALUES (?, ?, ?)",
[USER_ID, 0, 5],
(err) => {
if (err) throw err;
return res.send(
JSON.stringify({
code: 200,
active_point: 5,
last_update_ts: 0,
server_ts: now,
})
);
}
);
}
const activePointCalculate = activePointMiddleWare(
results[0].active_point,
results[0].last_update_ts
);
return connection.query(
UPDATE_USER_QUERY,
[
activePointCalculate.last_update_ts,
activePointCalculate.active_point,
USER_ID,
],
(err) => {
if (err) throw err;
const now = Date.now();
return res.send(
JSON.stringify({
code: 200,
active_point: activePointCalculate.active_point,
last_update_ts: activePointCalculate.last_update_ts,
server_ts: now,
})
);
}
);
});
});

app.get("/use", (req, res) => {
connection.query(GET_USER_QUERY, [USER_ID], (err, results) => {
if (err) throw err;
if (results[0].active_point === 0)
return res.send(JSON.stringify({ code: 400 }));
const now = Date.now();
const activePointCalculate = activePointMiddleWare(
results[0].active_point,
results[0].last_update_ts > 0 ? results[0].last_update_ts : now
);
connection.query(
UPDATE_USER_QUERY,
[
activePointCalculate.last_update_ts,
activePointCalculate.active_point - 1,
USER_ID,
],
(err) => {
if (err) throw err;
return res.send(
JSON.stringify({
code: 200,
active_point: activePointCalculate.active_point - 1,
last_update_ts: activePointCalculate.last_update_ts,
server_ts: now,
})
);
}
);
});
});

/**
* 서버에서 충전한 시간 또는 클라에서 사용한 시간을 이용하여 충전해줘야 할 행동력을 계산
* @param {Number} active_point 보유한 행동력
* @param {Number} last_update_ts 사용했었던 시간 또는 충전해줬었던 시간 (=단위는 ms)
*/
function activePointMiddleWare(active_point, last_update_ts) {
const now = Date.now();
const results = {
active_point,
last_update_ts,
};
const chargeActionCount = Math.floor(
(now - results.last_update_ts) / CHARGE_TIME
); // 충전 해줘야 하는 행동력 개수
if (chargeActionCount > 0) {
results.active_point += chargeActionCount;
results.last_update_ts += chargeActionCount * CHARGE_TIME;
}
if (chargeActionCount > 0 && results.active_point >= MAX_ACTIVE_POINT) {
// 충전했을 시 최대 개수가 넘으면
results.active_point = MAX_ACTIVE_POINT;
results.last_update_ts = 0;
}
// console.log("results", results);
return results;
}

app.listen(3000, () => console.log("서버 동작중"));

클라이언트

이전에 구현을 했을 때 게임 클라이언트 프로그래머가 어떻게 구현했는지는 잘 모르겠으나

나는 유니티는 모르니.. 일단 생각 해본대로 HTML에서 구현을 해 보았다 😨

  1. 서버에 현재 유저가 보유한 행동력, 클라에서 소모한 시간 또는 서버에서 충전한 시간을 받는다.

  2. 화면에 그리고 받은 시간을 기반으로 분, 초로 변환한 다음에 loop를 통해 시간을 계속 -

  3. 한 개의 충전 시간이 지나면 화면에서만 행동력을 + 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>행동력 구현 페이지</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.min.css"
/>
</head>

<body>
<div class="container">
<table>
<thead>
<tr>
<th>유저 ID</th>
<th>행동력 개수</th>
<th>남은 충전 시간</th>
</tr>
</thead>
<tbody>
<tr>
<td id="userId"></td>
<td id="actionCount"></td>
<td id="lastUpdateTime"></td>
</tr>
</tbody>
</table>

<button class="button button-outline" id="use">소모</button>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
charset="utf-8"
></script>
<script type="text/javascript">
var min = 0;
var sec = 60;
var maxActionCount = 5;
var charge_time = 60 * 1000;
var userId = "1q2w3e";
var intervalVar;

$(function () {
$.ajax({
// 진입 시 서버에서 현재 보유한 행동력과 서버에서 충전한 시간(또는 클라에서 소모한 시간)을 가지고 옴.
url: "http://localhost:3000",
type: "get",
success: function (data) {
console.log(data);
data = JSON.parse(data);
$("#userId").html(userId);
$("#actionCount").html(data.active_point);
if (data.last_update_ts === 0) {
// 0인 경우 풀 행동력이라 X
$("#lastUpdateTime").html("0:00");
} else {
init(data.server_ts, data.last_update_ts, function (isRun) {
if (isRun) actionCoolTimeLoop();
});
}
},
error: function (request, status, error) {
console.log(
"code:" +
request.status +
"\n" +
"message:" +
request.responseText +
"\n" +
"error:" +
error
);
},
});

$("#use").click(function (e) {
e.preventDefault();
if (parseInt($("#actionCount").html()) <= 0) {
alert("행동력을 모두 소진하였습니다!");
return;
} else {
$.ajax({
url: "http://localhost:3000/use",
type: "get",
success: function (data) {
console.log("data", data);
data = JSON.parse(data);
$("#actionCount").html(data.active_point);
stopInterval();
actionCoolTimeLoop();
},
error: function (request, status, error) {
console.log(
"code:" +
request.status +
"\n" +
"message:" +
request.responseText +
"\n" +
"error:" +
error
);
},
});
}
});
});

/**
* 실행 초기 함수
* @param {Number} serverTime 서버에서 주는 현재 시간
* @param {Number} lastUpdateTime 서버에서 충전한 시간 또는 클라에서 소모했었던 시간(단위는 ms)
*/
function init(serverTime, lastUpdateTime, callback) {
var remainMs = lastUpdateTime + charge_time - serverTime; // 남은 시간
if (remainMs < 0) return callback(false);
if (remainMs > 0) {
// ms to min, sec 계산법은 스택오브플로우에서 참고함
min = Math.floor(remainMs / 60000);
sec = ((remainMs % 60000) / 1000).toFixed(0);
}
var result;
if (sec === 60) {
result = min + ":" + "00";
} else {
result = min + ":" + (sec < 10 ? "0" : "") + sec;
}
$("#lastUpdateTime").html(result);
return callback(true);
}

function actionCoolTimeLoop() {
intervalVar = setInterval(function () {
loop();
}, 1000);
}

function loop() {
sec--;
var result;
if (sec === 0 && min > 0) {
min = min - 1;
sec = 60;
} else if (
sec === 0 &&
min === 0 &&
maxActionCount !== parseInt($("#actionCount").html())
) {
$("#actionCount").html(parseInt($("#actionCount").html()) + 1);
if (maxActionCount === parseInt($("#actionCount").html())) {
sec = 0;
min = 0;
stopInterval();
} else {
sec = 60;
min = 0;
}
}

if (sec === 60 && min === 0) {
result = 1 + ":" + "00";
} else if (sec === 60 && min === 0) {
result = 1 + ":" + "00";
} else if (sec === 60 && min > 0) {
result = min + 1 + ":" + "00";
} else {
result = min + ":" + (sec < 10 ? "0" : "") + sec;
}

$("#lastUpdateTime").html(result);
}

function stopInterval() {
clearInterval(intervalVar);
}
</script>
</body>
</html>

일단 충전 쿨타임을 1분으로 주고 몇 번 돌려봤을 때 시간이 뭔가 어긋나거나 그러진 않았다.

서버쪽은 이전에 몇 번 해봤었던 작업인데 막상 다시 짜볼라니 생각이 좀 많이 안 들었고..

클라이언트쪽은 쿨타임 남은 시간이 뭔가 계속 안 맞아서 삽질을 좀 한 거 같다.

역시 뭔가를 했을 때 기록을 해둬야 하는걸 또 한 번 느끼는 하루였다.