Bonjour
J'essaie de faire déplacer un joueur selon les ordres d'un serveur. Le mouvement est carré, on part du point A (20,7), puis le B, le C, et quand on retourne au A on repart au B, et on fait des boucles.
L'idée c'est que le serveur doit distribuer beaucoup de chemin (bien qu'à une seule entité pour l'instant), donc pour épargner la mémoire il les envoie dans des arrays contenant les points, et le client execute le chemin envoyé par le serveur.
Client et serveur executent en même temps une loop de mise à jour de la position. Quand le serveur est retourné au point A, il renvoie de nouveau le chemin [B,C,D,A], et en parallèle le client fait une prédiction et va direct au point B, en attendant que le serveur fasse autorité et lui renvoie son chemin.
Je fais en sorte que la position de mon client soit à tout moment exactement celle du serveur, ce qui implique qu'au connect du client, au lieu que le serveur lui envoie la position courante, il lui envoie plutôt un object contenant le dernier edge, le time au moment du passage à cet edge, et la direction. A charge au client de calculer la position courante, qui si le serveur et le client ont la même horloge, est la même
Malgré toutes ces précautions, lorsque (client ou serveur ?) l'entité revient au point A, elle jump un peu dans son chemin vers le point B, le passage n'est pas fluide, comme si la position calculée par le client avait du retard par rapport aux nouvelles infos que lui envoie le serveur. J'ai beau chercher, j'ai mis plein de garde fou pour éviter ça, mis ça ne veut toujours pas.
Des idées ?
Je vous préviens il y a un peu de lecture, mais c'est du copy and play :
index.js :
index.html :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 // pour les echanges sockets const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); // server intiialization on port 5000 app.get('/', (req, res) => // fold { res.sendFile('ploum ploum ploum/index.html'); }); http.listen(5000, () => // fold { }) let world = // fold { _entities : [], _sockets : [], _currentExactTime : undefined, addClient:function(socket) { world._sockets[socket.id] = socket; for (let e in world._entities) world._entities[e].initiateSocket(socket.id); }, deleteClient:function(socket) { delete world._sockets[socket.id]; }, loop:function() { let time = new Date(); world._currentExactTime = time; for (let e in world._entities) world._entities[e].update(); setImmediate(world.loop); } } class Movable // fold { constructor() { this._path = []; this._left = 20; this._top = 7; this._nextPoint = undefined; this._jamaisParti = true; } distance(ax,ay,bx,by) // fold { return Math.sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by)); } shift(dx, dy) { this._nextPoint = this._path.pop(); if (this._nextPoint == undefined) { this._path = [{'x':20,'y':7},{'x':25,'y':7},{'x':25,'y':26},{'x':20,'y':26}]; this._nextPoint = this._path.pop(); let error = this.distance(dx,dy,0,0); // distance parcourue en trop depuis dernier frame this._left = Math.round(this._left); this._top = Math.round(this._top); let dist = this.distance(this._nextPoint.x,this._nextPoint.y,this._left,this._top); this._dirX = ((this._nextPoint.x - this._left) / dist); this._dirY = ((this._nextPoint.y - this._top) / dist); this._dirX = [-1, -0.71, 0, 0.71, 1].reduce((a, b) => { return Math.abs(b - this._dirX) < Math.abs(a - this._dirX) ? b : a; }); this._dirY = [-1, -0.71, 0, 0.71, 1].reduce((a, b) => { return Math.abs(b - this._dirY) < Math.abs(a - this._dirY) ? b : a; }); this._dirX /= 300; this._dirY /= 300; this._left += this._dirX * error; this._top += this._dirY * error; this._start = {'x':this._left,'y':this._top}; this._time = world._currentExactTime; let obj = { path : this._path.concat([this._nextPoint]), pos : { start:{'x':this._start.x,'y':this._start.y}, dir:{'x':this._dirX,'y':this._dirY}, startTime:this._time.getTime(), }, emitTime:world._currentExactTime.getTime(), }; io.emit('newpath', obj); } // si je ne suis pas à la fin de _path else /* (this._nextPoint != undefined) */ { let error = this.distance(dx,dy,0,0); // distance parcourue en trop depuis dernier frame this._left = Math.round(this._left); this._top = Math.round(this._top); let dist = this.distance(this._nextPoint.x,this._nextPoint.y,this._left,this._top); this._dirX = ((this._nextPoint.x - this._left) / dist); this._dirY = ((this._nextPoint.y - this._top) / dist); this._dirX = [-1, -0.71, 0, 0.71, 1].reduce((a, b) => { return Math.abs(b - this._dirX) < Math.abs(a - this._dirX) ? b : a; }); this._dirY = [-1, -0.71, 0, 0.71, 1].reduce((a, b) => { return Math.abs(b - this._dirY) < Math.abs(a - this._dirY) ? b : a; }); this._dirX /= 300; this._dirY /= 300; this._left += this._dirX * error; this._top += this._dirY * error; this._start = {'x':this._left,'y':this._top}; this._time = world._currentExactTime; } } update() { if (this._nextPoint != undefined) { let dx = (this._nextPoint.x - this._left); let dy = (this._nextPoint.y - this._top); // si ma dir n'est plus la bonne if ((dx * this._dirX < 0) || (dy * this._dirY < 0)) { // (je suis allé trop loin) this.shift(dx,dy); } else { let elapsed = world._currentExactTime - this._time; this._left = this._start.x + elapsed * this._dirX; this._top = this._start.y + elapsed * this._dirY; } // s'il n'est pas en mouvement, dir = prochain point dans _path. } else { this.shift(0,0); } } initiateSocket(socketId) { let pathToEmit = this._path.concat([this._nextPoint]); let obj = { path : pathToEmit, pos : { start:{'x':this._start.x,'y':this._start.y}, dir:{'x':this._dirX,'y':this._dirY}, startTime:this._time.getTime(), }, emitTime:world._currentExactTime.getTime(), }; io.emit('newpath', obj); } } world._entities.push(new Movable()); world.loop(); io.on('connection', (socket) => // fold { world.addClient(socket); socket.on('disconnect', function() { world.deleteClient(socket); }); });
Code HTML : Sélectionner tout - Visualiser dans une fenêtre à part
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <style> /* canvas multiples. les margisn sont ajustées pour que le clic soit exact */ canvas { position:absolute; margin-left:-8px; margin-top:-8px; } </style> </head> <body> <script src="/socket.io/socket.io.js"></script> <canvas id='layer1' style="z-index:1"></canvas> <!-- ground --> <canvas id='layer2' style="z-index:2"></canvas> <!-- movables --> <script> let isometric = // fold { _tileWidth:58, _tileHeight:30, // 30 ??? absoluteToScreen:function(x,y) { return {'x': (x+y) * this._tileWidth/2 - 580, 'y' : (y-x) * this._tileHeight/2 + 300}; // 580 = }, screenToAbsolute:function(x,y) { x = x + 580; y = y - 300; x = x - this._tileWidth + this._tileHeight; var X = x/this._tileWidth - y/this._tileHeight; var Y = x/this._tileWidth + y/this._tileHeight; return {'x':parseInt(X+(X>0)), 'y':parseInt(Y)}; }, highlightMultiple:function(x,y,color,symbol,ctx,width,height,delta) { let position = this.absoluteToScreen(x,y); world._contexts[ctx].fillStyle = color; world._contexts[ctx].beginPath(); // left, nort, east, south let topLeft = [position.x, position.y + this._tileHeight / 2]; let topRight = [topLeft[0] + (this._tileWidth * width / 2), topLeft[1] - (this._tileHeight * width / 2)]; let bottomRight = [topRight[0] + (this._tileWidth * height / 2), topRight[1] + (this._tileHeight * height / 2)]; let bottomLeft = [bottomRight[0] - (this._tileWidth * width / 2), bottomRight[1] + (this._tileHeight * width / 2)]; // +1 et -1 pour réduire légèrement la forme et laisser un espace blanc plus visible entre elles //ctx.moveTo(topLeft[0]+1, topLeft[1]); //ctx.lineTo(topRight[0], topRight[1]+1); //ctx.lineTo(bottomRight[0]-1, bottomRight[1]); //ctx.lineTo(bottomLeft[0], bottomLeft[1]-1); world._contexts[ctx].moveTo(topLeft[0]+delta, topLeft[1]); world._contexts[ctx].lineTo(topRight[0], topRight[1]+delta); world._contexts[ctx].lineTo(bottomRight[0]-delta, bottomRight[1]); world._contexts[ctx].lineTo(bottomLeft[0], bottomLeft[1]-delta); world._contexts[ctx].fill(); world._contexts[ctx].fillStyle = 'black'; world._contexts[ctx].font = "15px Courier"; world._contexts[ctx].fillText(symbol, (topLeft[0]+bottomRight[0])/2 - 6, (topRight[1]+bottomLeft[1])/2 + 2); } }; let roamer1 = // fol { _left : undefined, _top : undefined, _path : [], _nextPoint : undefined, _dirX : undefined, _dirY : undefined, _start : {}, _jamaisParti : true, distance:function(ax,ay,bx,by) // fold { return Math.sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by)); }, shift:function(dx,dy) // fol { this._nextPoint = this._path.pop(); if (this._nextPoint == undefined && this._jamaisParti == false) this._nextPoint = {'x':20,'y':26}; // si je ne suis pas à la fin de _path if (this._nextPoint != undefined) { let error = this.distance(dx,dy,0,0); // distance parcourue en trop deouis le dernier frame this._left = Math.round(this._left); this._top = Math.round(this._top); let dist = this.distance(this._nextPoint.x,this._nextPoint.y,this._left,this._top); this._dirX = ((this._nextPoint.x - this._left) / dist); this._dirY = ((this._nextPoint.y - this._top) / dist); this._dirX = [-1, -0.7, 0, 0.7, 1].reduce((a, b) => { return Math.abs(b - this._dirX) < Math.abs(a - this._dirX) ? b : a; }); this._dirY = [-1, -0.7, 0, 0.7, 1].reduce((a, b) => { return Math.abs(b - this._dirY) < Math.abs(a - this._dirY) ? b : a; }); this._dirX /= 300; this._dirY /= 300; this._left += this._dirX * error; this._top += this._dirY * error; this._start = {'x':this._left,'y':this._top}; this._startTime = world._currentExactTime; } }, update:function() // fol { if (this._nextPoint != undefined) { let dx = (this._nextPoint.x - this._left); let dy = (this._nextPoint.y - this._top); // si ma dir n'est plus la bonne if ((dx * this._dirX < 0) || (dy * this._dirY < 0)) { // (je suis allé trop loin) this.shift(dx,dy); } else { let elapsed = world._currentExactTime - this._startTime; this._left = this._start.x + elapsed * this._dirX; this._top = this._start.y + elapsed * this._dirY; } // s'il n'est pas en mouvement, dir = prochain point dans _path. } else { this.shift(0,0); } isometric.highlightMultiple(this._left, this._top, 'rgba(200,200,200,0)', 'F', 1, 1, 1, 1); } }; let world = // fol { _currentTime : undefined, _contexts : [], initialize:function(width,height) // fold { let canvas1 = document.getElementById('layer1'); let canvas2 = document.getElementById('layer2'); world._contexts = [ canvas1.getContext("2d"), canvas2.getContext("2d") ]; canvas1.width = window.innerWidth; canvas1.height = window.innerHeight; canvas2.width = window.innerWidth; canvas2.height = window.innerHeight; world._contexts[0].fillStyle = 'black'; world._contexts[0].font = "15px Courier"; world._contexts[1].fillStyle = 'black'; world._contexts[1].font = "15px Courier"; }, clearContext:function(ctx) // fold { ctx.clearRect(0, 0, window.innerWidth, window.innerHeight) }, update:function() // fold { world.clearContext(world._contexts[1]); // clear canvas des movables roamer1.update(); }, loop:function(time) // fol { world._currentExactTime = new Date().getTime(); world.update(); requestAnimationFrame(world.loop); }, }; world.initialize(40, 40); var socket = io(); socket.on('newpath', function(msg) // fol { let elapsed = world._currentExactTime - msg.pos.startTime; roamer1._left = msg.pos.start.x + elapsed * msg.pos.dir.x; roamer1._top = msg.pos.start.y + elapsed * msg.pos.dir.y; roamer1._dirX = msg.pos.dir.x; roamer1._dirY = msg.pos.dir.y; roamer1._path = msg.path; roamer1._start.x = msg.pos.start.x; roamer1._start.y = msg.pos.start.y; roamer1._nextPoint = roamer1._path.pop(); roamer1._startTime = world._currentExactTime - elapsed; roamer1._jamaisParti = false; }); world.loop(); </script> </body> </html>
Partager