game.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import * as CANNON from './cannon-es.js'
  2. import Entity from './Entity.js'
  3. Object.defineProperty(Array.prototype, 'shuffle', {
  4. value: function () {
  5. for (let i = this.length - 1; i > 0; i--) {
  6. const j = Math.floor(Math.random() * (i + 1));
  7. [this[i], this[j]] = [this[j], this[i]];
  8. }
  9. return this;
  10. }
  11. });
  12. //TODO
  13. //connect with server
  14. //download last 20(?) frames
  15. //simulate next 10(?) frames
  16. // frames 1-30 exist
  17. //start drawing frame 23 (last + input delay)
  18. //each update add new "future" frame
  19. //when player pressed input, apply change to proper future frames
  20. //current frame 23
  21. //player presses go forward - add acceleration to frame 26
  22. //when server info comes in, look at where entity should be by frame and adjust values
  23. //current frame 23, simulated out to 30
  24. //server frame 20 comes in, entity should be moving
  25. //cannot edit past frames, but can make changes to future frames
  26. //set entity position as computed for frame 24
  27. var socket
  28. var scheme = document.location.protocol === "https:" ? "wss" : "ws"
  29. var host = document.location.hostname
  30. var port = document.location.port ? (":" + document.location.port) : ":80"
  31. const userId = `player` + `${Math.floor(255 * Math.random())}`.padStart(3, "0");
  32. const playerColor = ["red", "orange", "black", "green", "blue", "violet"].shuffle()[0]
  33. const gameSocketUrl = scheme + "://" + host + port + "/game"
  34. // const gameSocketUrl = "ws://game.bubblesocket"
  35. var canvas = null;
  36. var context = null;
  37. var movements = []
  38. var hasInitialFrame = false
  39. var sessionId = null;
  40. var entityId = null;
  41. // var frictionCoefficient = 0.05;
  42. const keys = {}
  43. var simulationData = {
  44. entityFrames: [],
  45. inputFrames: [],
  46. currentFrame: 0,
  47. displayFrame: 0,
  48. baseFrame: 0,
  49. serverFrame: 0,
  50. }
  51. const FRAME_INPUT_DELAY = 3;
  52. const simulationComputeBuffer = 30;
  53. // var inLockStep = false
  54. // var lockedFrame = 0
  55. var cannonWorld
  56. var lastTime = new Date().getTime()
  57. var fixedTimeStep = 1.0 / 60.0 //60 fps
  58. var maxSubSteps = 3
  59. var playerEntity
  60. const InputList = {
  61. THRUST: 1,
  62. TURNLEFT: 2,
  63. TURNRIGHT: 3,
  64. }
  65. const SimulationActionType = {
  66. UNKNOWN: "00",
  67. //sent from client
  68. Connect: "01", //authenticate and get initial state
  69. Disconnect: "02", //disconnect socket
  70. JoinChannel: "03", //request to join channel
  71. LeaveChannel: "04", //leave channel
  72. SendInputs: "05", //send inputs to simulation
  73. //sent to client
  74. OtherJoinChannel: "06", //received when another joins current channel
  75. OtherLeaveChannel: "07", //received when another leaves current channel
  76. OtherSendInputs: "08", //received when another pushes inputs to those in channel
  77. Resync: "09",//request full channel simulation state
  78. }
  79. document.addEventListener("DOMContentLoaded", () => {
  80. canvas = document.createElement("canvas")
  81. canvas.style.width = "300px"
  82. canvas.style.height = "150px"
  83. canvas.width = 300
  84. canvas.height = 150
  85. context = canvas.getContext('2d')
  86. context.width = 300
  87. context.height = 150
  88. const connectButton = document.getElementById("connect")
  89. document.getElementById("container").appendChild(canvas)
  90. connectButton.addEventListener("click", () => {
  91. if (socket) {
  92. socketSend(SimulationActionType.Disconnect, {})
  93. socket.close(1000, "Closing from client");
  94. connectButton.innerHTML = "Connect"
  95. return
  96. }
  97. socket = new WebSocket(gameSocketUrl);
  98. socket.onopen = (event) => {
  99. let token = getCookie('token')
  100. socketSend(SimulationActionType.Connect, [token])
  101. connectButton.innerHTML = "Disconnect"
  102. }
  103. socket.onclose = (event) => {
  104. if (!event.wasClean) {
  105. console.log(`socket did not close cleanly:`, event)
  106. connectButton.innerHTML = "Connect"
  107. }
  108. socket = null
  109. }
  110. socket.onerror = (event) => {
  111. console.error(`socket error:`, event)
  112. socket = null
  113. }
  114. socket.onmessage = (event) => {
  115. socketReceive(event)
  116. }
  117. connectButton.innerHTML = "Connecting..."
  118. })
  119. document.getElementById("login").addEventListener("click", () => {
  120. let data = {
  121. "Username": document.getElementById("username").value,
  122. "Password": document.getElementById("password").value,
  123. }
  124. fetch(`//${host}${port}/jwt/login`, {
  125. method: 'POST',
  126. body: JSON.stringify(data),
  127. headers: {
  128. 'Content-Type': 'application/json'
  129. }
  130. }).then(res => res.json())
  131. .then(res => {
  132. setCookie('token', res['token'], res['expiration'])
  133. document.getElementById("login-section").style.display = "none"
  134. document.getElementById("canvas-section").style.display = "block"
  135. });
  136. })
  137. function setCookie(key, value, expiration) {
  138. document.cookie = key + "=" + value + ";" + "expires=" + new Date(expiration).toUTCString() + ";path=/";
  139. }
  140. function getCookie(cname) {
  141. let name = cname + "=";
  142. let decodedCookie = decodeURIComponent(document.cookie);
  143. let ca = decodedCookie.split(';');
  144. for (let i = 0; i < ca.length; i++) {
  145. let c = ca[i];
  146. while (c.charAt(0) == ' ') {
  147. c = c.substring(1);
  148. }
  149. if (c.indexOf(name) == 0) {
  150. return c.substring(name.length, c.length);
  151. }
  152. }
  153. return "";
  154. }
  155. window.addEventListener("keydown", (e) => {
  156. keys[e.code] = true
  157. })
  158. window.addEventListener("keyup", (e) => {
  159. keys[e.code] = false
  160. })
  161. window.requestAnimationFrame(animate)
  162. setInterval(() => {
  163. let time = new Date().getTime()
  164. update((time - lastTime) / 1000)
  165. lastTime = time
  166. }, parseInt(fixedTimeStep * 1000))
  167. setInterval(broadcast, 32) //30fps
  168. })
  169. function animate() {
  170. draw(context)
  171. window.requestAnimationFrame(animate)
  172. }
  173. function update(dt) {
  174. if (!socket || !hasInitialFrame) {
  175. return
  176. }
  177. }
  178. function broadcast() {
  179. if (!socket || !hasInitialFrame) {
  180. return;
  181. }
  182. }
  183. function socketSend(simulationActionType, payload) {
  184. if (!socket || socket.readyState !== WebSocket.OPEN) {
  185. return
  186. }
  187. let blobData = [simulationActionType, JSON.stringify(payload)]
  188. let blob = new Blob(blobData, { type: 'application/json' });
  189. socket.send(blob)
  190. }
  191. async function socketReceive(packet) {
  192. let response = await packet.data.text()
  193. let simulationActionType = response.substr(0, 2)
  194. let messageData = response.substr(2, response.length - 2)
  195. let payload = JSON.parse(messageData);
  196. switch (simulationActionType) {
  197. case SimulationActionType.Connect:
  198. let characterName = payload.characters[0].Name
  199. let channel = payload.characters[0].Channel
  200. console.log("connect", characterName, channel)
  201. socketSend(SimulationActionType.JoinChannel, [characterName, channel])
  202. break;
  203. case SimulationActionType.Disconnect:
  204. console.log("disconnect", payload)
  205. break;
  206. case SimulationActionType.JoinChannel:
  207. console.log("channel join response", payload)
  208. break;
  209. case SimulationActionType.Resync:
  210. console.log("resync", payload)
  211. break
  212. default:
  213. console.warn("unknown message type", simulationActionType, payload)
  214. break
  215. }
  216. }
  217. // var acceleration = {x: 0, y: 0}
  218. // var velocity = {x: 0, y: 0}
  219. // var position = {x: 20, y: 20}
  220. function drawPlayer(ctx, playerData) {
  221. ctx.beginPath()
  222. ctx.arc(playerData.position.x, playerData.position.y, 4, 0, 2 * Math.PI)
  223. ctx.fill()
  224. }
  225. function draw(ctx) {
  226. context.clearRect(0, 0, canvas.width, canvas.height)
  227. if (!socket) {
  228. ctx.fillStyle = "black"
  229. ctx.fillText("Disconnected", 50, 50)
  230. } else if (socket && !hasInitialFrame) {
  231. ctx.fillStyle = "black"
  232. ctx.fillText("Syncing with server", 50, 50)
  233. } else if (socket && hasInitialFrame) {
  234. // playerEntity.draw(ctx)
  235. // let player = simulationData.entityFrames[simulationData.displayFrame][entityId]
  236. let currentEntities = Object.values(simulationData.entityFrames[simulationData.serverFrame])
  237. currentEntities.forEach(entity => {
  238. drawPlayer(ctx, entity);
  239. })
  240. }
  241. }