multyx-client 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1611 @@
1
+ # Multyx
2
+
3
+ A Typescript framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling.
4
+
5
+ ***
6
+
7
+ ## Table of Contents
8
+
9
+ - [What is Multyx?](#what-is-multyx)
10
+ - [Why Multyx?](#why-multyx)
11
+ - [Shared Data between Client and Server](#shared-data-between-client-and-server)
12
+ - [Input Controller](#input-controller)
13
+ - [Helpful Functionalities](#helpful-functionalities)
14
+ - [Overview](#overview)
15
+ - [Shared State](#shared-state)
16
+ - [Teams and Clients](#teams-and-clients)
17
+ - [Installation](#installation)
18
+ - [Server-Side Documentation](#server-side-documentation)
19
+ - [MultyxServer](#multyxserver)
20
+ - [Events](#events)
21
+ - [Event](#event)
22
+ - [MultyxItem](#multyxitem)
23
+ - [MultyxValue](#multyxvalue)
24
+ - [MultyxObject](#multyxobject)
25
+ - [MultyxList](#multyxlist)
26
+ - [Agent](#agent)
27
+ - [Client](#client)
28
+ - [Controller](#controller)
29
+ - [ControllerState](#controllerstate)
30
+ - [Input](#input)
31
+ - [MultyxTeam](#multyxteam)
32
+ - [Client-Side Documentation](#client-side-documentation)
33
+ - [Multyx (client)](#multyx-client)
34
+ - [Controller (Client)](#controller-client)
35
+ - [MultyxClientItem](#multyxclientitem)
36
+ - [MultyxClientValue](#multyxclientvalue)
37
+ - [MultyxClientObject](#multyxclientobject)
38
+ - [MultyxClientList](#multyxclientlist)
39
+ - [Starter Project Walkthrough](#starter-project-walkthrough)
40
+ - [Common Mistakes and Best Practices](#common-mistakes-and-best-practices)
41
+
42
+ ***
43
+
44
+ ## What is Multyx?
45
+
46
+ Multyx is a framework designed to simplify the creation of multiplayer browser games by addressing the complexities of managing server-client communication, shared state, and input handling. It provides developers with tools to efficiently synchronize data between the server and clients, enforce security and validation, and streamline the handling of user inputs and interactions. By abstracting many of the tedious aspects of multiplayer game development, Multyx enables developers of all skill levels to build robust, functional, and secure multiplayer experiences with minimal friction.
47
+
48
+ ## Why Multyx?
49
+
50
+ Focused on ease of use and a good developer experience, Multyx turns the difficulty and complexity of making a multiplayer browser game into a simpler process that anyone can jump into.
51
+
52
+ ### Shared Data between Client and Server
53
+
54
+ Being able to communicate changes in data is necessary for a functional multiplayer game, but due to needing server-side verification, consistency across websocket endpoints, and a lack of type validation, it can be difficult to bridge the gap between the client and server data transfer. Multyx streamlines this process by including a shared state between the server and client, along with
55
+
56
+ - Control over which data is public to the client
57
+ - The ability to constrain, disable, and verify data changed by the client
58
+ - Client knowledge of constraints to reduce redundancies
59
+ - The ability to allow or disallow the client to alter data
60
+ - Consistency in data between the client and the server
61
+ - The ability to share a public state between groups of clients
62
+
63
+ With the use of MultyxObjects, Multyx can integrate seamlessly into projects, letting changes in data be relayed across both endpoints without the need for any extra code to be written.
64
+
65
+ ### Input Controller
66
+
67
+ Client interactivity is a necessity in any game, but with it being hard to standardize mouse position across varying screen sizes, and manage the state of inputs, it can be tricky to implement an input system. Multyx allows you to
68
+
69
+ - Pick which client inputs to listen to from the server
70
+ - Map the client's mouse location to canvas coordinates
71
+ - View the state of inputs from both the server and client
72
+ - Add event listeners on the server for client inputs
73
+
74
+ ### Helpful Functionalities
75
+
76
+ Building a functional, efficient, and secure multiplayer game is notoriously a tedious process, with each project having its own needs. Multyx simplifies this process by offering a variety of helpful functionalities such as
77
+
78
+ - Predictive and non-predictive interpolation between values
79
+ - Teams to manage public state across groups of clients
80
+ - Options for changing websocket connection and runtime settings
81
+
82
+ ***
83
+
84
+ ## Overview
85
+
86
+ Its generally easier to understand through examples and actually looking at the code, so this section gives a slightly more in-depth understanding of what Multyx does.
87
+
88
+ ### Shared State
89
+
90
+ Having a shared read/write state is easily the most important aspect of Multyx, so a lot is put into making it seamless and worthwhile. Shared state in Multyx is made through the [`MultyxItem`](#multyxitem) type, which includes the classes [`MultyxObject`](#multyxobject), [`MultyxList`](#multyxlist), and [`MultyxValue`](#multyxvalue). These are Multyx's shared-state versions of objects, arrays, and primitives, respectively. Each of these acts like their original counterpart, for instance:
91
+
92
+ ```js
93
+ client.self.object = {
94
+ x: 3, y: 2, z: 1
95
+ };
96
+ console.log(client.self.object.z); // outputs 1
97
+
98
+ client.self.array = ["fizz", "buzz", "bog"]; // MultyxList
99
+ client.self.array[3] = "asdf"; // ["fizz", "buzz", "bog", "asdf"]
100
+ client.self.array.splice(2, 1); // ["fizz", "buzz", "asdf"]
101
+
102
+ client.self.y = client.self.x + 3; // no errors
103
+ ```
104
+
105
+ However, they also have their own properties and methods.
106
+
107
+ ```js
108
+ // server.js
109
+ client.self.x = 3;
110
+ client.self.x.min(-10).max(10);
111
+
112
+ client.self.array.allowItemAddition = false;
113
+
114
+ client.self.addPublic(Multyx.all);
115
+ ```
116
+
117
+ The purpose of having these MultyxItems is that when the value is changed, Multyx relays that change to any clients that should see it.
118
+
119
+ Along with special objects on the server side, any shared data on the client side sits in a [`MultyxClientItem`](#multyxclientitem).
120
+
121
+ These are similar to the server-side [`MultyxItem`](#multyxitem) in the sense that any changes get relayed to the server to process, but they do not have the same properties or methods. Rather, they have methods that would be helpful to client prediction.
122
+
123
+ ```js
124
+ // client.js
125
+ Multyx.self.x = 9;
126
+
127
+ Multyx.clients.forAll(client => {
128
+ client.x.Lerp();
129
+ client.y.Lerp();
130
+ client.timeAlive.PredictiveLerp();
131
+ });
132
+ ```
133
+
134
+ ***
135
+
136
+ ### Teams and Clients
137
+
138
+ When a [`MultyxItem`](#multyxitem) gets changed, the Multyx server needs to know which clients to send the information to, along with which clients have which permissions. This is all handled through the [`MultyxTeam`](#multyxteam) class.
139
+
140
+ A [`MultyxTeam`](#multyxteam) is at its core a list of [`Client`](#client) classes representing all clients that are part of that team, along with a [`MultyxObject`](#multyxobject) describing the shared data of that team. This [`MultyxObject`](#multyxobject) is public to all clients that are within the team, and by default has the ability to be edited by any [`Client`](#client) in the team, though this can be disabled.
141
+
142
+ ```js
143
+ // server.js
144
+ const players = new MultyxTeam('players');
145
+ players.self.messages = ["hello world"];
146
+
147
+ multyx.on('join game', (client, name) => {
148
+ client.self.name = name;
149
+ players.addClient(client);
150
+ players.self.messages.push(name + ' just joined');
151
+
152
+ return 'success!';
153
+ });
154
+ ```
155
+
156
+ ```js
157
+ // client.js
158
+ console.log(multyx.teams.players.messages); // Error: "messages" doesn't exist on undefined
159
+
160
+ const joinStatus = await multyx.send('join game', 'player1');
161
+
162
+ console.log(joinStatus); // success!
163
+ console.log(multyx.teams.players.messages); // ["hello world", "player1 just joined"]
164
+ ```
165
+
166
+ In Multyx, clients do not interact with each other. The [`MultyxTeam`](#multyxteam) class is the only way to share state between clients, as Client1 cannot edit the state of Client2. There is a difference, however, between being able to edit state, and being able to view it. This is achieved by making a [`MultyxItem`](#multyxitem) public.
167
+
168
+ Although clients are only able to edit themselves and any teams they are in, they are able to view any client data made public to a team that they are in.
169
+
170
+ ```js
171
+ // server.js
172
+ import Multyx from 'multyx';
173
+
174
+ const multyx = new Multyx.MultyxServer();
175
+
176
+ multyx.on(Multyx.Events.Connect, client => {
177
+ client.self.role = 'imposter';
178
+ client.self.x = 100;
179
+ client.self.y = 300;
180
+ client.self.x.addPublic(multyx.all);
181
+ client.self.y.addPublic(multyx.all);
182
+ });
183
+ ```
184
+
185
+ ```js
186
+ // client.js
187
+ const multyx = new Multyx();
188
+
189
+ multyx.on(multyx.Start, () => {
190
+ for(const uuid in multyx.all) {
191
+ console.log(multyx.clients[uuid]); // { x: 100, y: 300 }
192
+ console.log(multyx.clients[uuid].role); // undefined
193
+ }
194
+ });
195
+ ```
196
+
197
+ Using the `multyx.all` team that gets provided natively by the MultyxServer class, clients can share data to anyone connected to the server.
198
+
199
+ ***
200
+
201
+ ## Installation
202
+
203
+ Install from NPM: `npm install multyx`
204
+
205
+ ```js
206
+ // server.js
207
+ import Multyx from 'multyx';
208
+ ```
209
+
210
+ ***
211
+
212
+ ## Server-Side Documentation
213
+
214
+ ***
215
+
216
+ ```js
217
+ // server.js
218
+ import Multyx from 'multyx';
219
+ ```
220
+
221
+ ```js
222
+ // multyx/index.ts
223
+ export {
224
+ Client,
225
+ Input,
226
+ Controller,
227
+ ControllerState,
228
+ Events,
229
+
230
+ MultyxValue,
231
+ MultyxList,
232
+ MultyxObject,
233
+ MultyxTeam,
234
+ MultyxServer,
235
+
236
+ Options,
237
+ RawObject
238
+ };
239
+ ```
240
+
241
+ ***
242
+
243
+ ### MultyxServer
244
+
245
+ A MultyxServer is initialized as follows. If options are omitted, the first parameter can be the callback.
246
+
247
+ ```js
248
+ const multyx = new Multyx.MultyxServer(options?: Multyx.Options | () => void, callback?: () => void);
249
+ ```
250
+
251
+ Initializing a MultyxServer creates a WebsocketServer using the node 'ws' module.
252
+
253
+ ```ts
254
+ export type Options = {
255
+ tps?: number,
256
+ port?: number,
257
+ server?: Server,
258
+ removeDisconnectedClients?: boolean,
259
+ respondOnFrame?: boolean,
260
+ sendConnectionUpdates?: boolean,
261
+ websocketOptions?: ServerOptions,
262
+ };
263
+
264
+ export const DefaultOptions: Options = {
265
+ tps: 10,
266
+ port: 443,
267
+ removeDisconnectedClients: true,
268
+ respondOnFrame: true,
269
+ sendConnectionUpdates: true,
270
+ websocketOptions: {
271
+ perMessageDeflate: false // Often causes backpressure on client
272
+ },
273
+ };
274
+ ```
275
+
276
+ #### `Options.tps`
277
+
278
+ Number of times per second to relay updates to clients. Utilizes NanoTimer to precisely loop update sending every 1/tps seconds.
279
+
280
+ #### `Options.port`
281
+
282
+ Port to start WebsocketServer from, defaults to 443.
283
+
284
+ #### `Options.server`
285
+
286
+ Server to start WebsocketServer on. Will override the Options.port argument if included.
287
+
288
+ #### `Options.removeDisconnectedClients`
289
+
290
+ If true, disconnected clients will be removed from any MultyxTeam they are part of. If false, disconnected clients will remain in any MultyxTeam. All clients will still receive a DisconnectionUpdate. It is recommended to keep this set to true, as retaining the disconnected clients can lead to memory leaks if the server is running for long enough.
291
+
292
+ #### `Options.respondOnFrame`
293
+
294
+ If true, responses to manual Websocket events such as `const a = await Multyx.send();` will be added to the update queue and sent in frame with the rest of the updates. If false, responses will be immediately sent after processing. False not recommended unless there is a use-case, since updates will not have been relayed to the client.
295
+
296
+ ```js
297
+ // server.js
298
+ const multyx = new MultyxServer({ respondOnFrame: false });
299
+ multyx.on('setBlue', client => client.self.color = 'blue');
300
+ ```
301
+
302
+ ```js
303
+ // client.js
304
+ await multyx.send('setBlue');
305
+ console.log(multyx.self.color); // undefined
306
+ ```
307
+
308
+ #### `Options.sendConnectionUpdates`
309
+
310
+ If true, client connection and client disconnection will send an update to all clients. If false, client connection or disconnection is hidden.
311
+
312
+ #### `Options.websocketOptions`
313
+
314
+ List of server options to be dropped directly into the WebsocketServer constructor parameter. If port or server is defined in websocketOptions, `Options.port` or `Options.server` will be overridden.
315
+
316
+ #### `MultyxServer.on(event: EventName, callback): Event`
317
+
318
+ Create an event listener for any Multyx.Events or manual events. Event name can be type defined by `Multyx.Events` for event listeners on native Multyx processes, or string for event listeners on custom events sent by client. The callback will have 2 arguments passed: the [`Client`](#client) object, and any extra data specific to the event. If event is a custom event, the second argument will be passed from the client.
319
+
320
+ #### `MultyxServer.forAll(callback: (client: Client) => void)`
321
+
322
+ Apply a callback function to any connected clients, along with any clients who will connect in the future.
323
+
324
+ ***
325
+
326
+ ### Events
327
+
328
+ This is the list of all events for native Multyx processes.
329
+
330
+ ```ts
331
+ export const Events = {
332
+ Connect: Symbol('connect'), // new client connects
333
+ Disconnect: Symbol('disconnect'), // client disconnects
334
+ Update: Symbol('update'), // before each frame
335
+ PostUpdate: Symbol('postupdate'), // after each frame
336
+ Edit: Symbol('edit'), // MultyxItem edited by client
337
+ Input: Symbol('input'), // client applies an input being listened to
338
+ Any: Symbol('any'), // any other event gets called
339
+ Native: Symbol('native'), // any native event gets called
340
+ Custom: Symbol('custom') // any custom event gets called
341
+ };
342
+
343
+ export type EventName = typeof Events[keyof typeof Events] | string;
344
+ ```
345
+
346
+ ### Event
347
+
348
+ Event is the object that gets returned from `MultyxServer.on`. It has some methods such as deleting itself along with a history of previous times the event has been called.
349
+
350
+ ```ts
351
+ export class Event {
352
+ eventName: string;
353
+
354
+ callback: (client: Client | undefined, data: any) => any;
355
+ history: { time: number, client: Client | undefined, data: any, result: any }[];
356
+
357
+ public call(client: Client | undefined = undefined, data: any = {}) {}
358
+ public delete() {}
359
+ }
360
+ ```
361
+
362
+ #### `Event.eventName`
363
+
364
+ Name of the event being called.
365
+
366
+ #### `Event.delete()`
367
+
368
+ Delete the event listener. The previous callback function will not be called again even if the Event occurs.
369
+
370
+ #### `Event.saveHistory`
371
+
372
+ Boolean representing whether to store the history of event listener calls and their data. Set to false by default. This can be extremely memory intensive if set to true, so use with caution.
373
+
374
+ #### `Event.history`
375
+
376
+ View the history of event listener calls, including the time, the client it relates to, the data sent, and the result that the event listener callback returned.
377
+
378
+ #### `Event.call()`
379
+
380
+ Simulate a call of the event using a client and some data. I don't know why you would ever use this.
381
+
382
+ ***
383
+
384
+ ### MultyxItem
385
+
386
+ A `MultyxItem` is the base of shared state in Multyx. A `MultyxItem` is merely a union type between the following, [`MultyxValue`](#multyxvalue), [`MultyxObject`](#multyxobject), and [`MultyxList`](#multyxlist). When a property is changed, deleted, or set inside a `MultyxItem`, that change gets relayed to all clients that have been provided visibility.
387
+
388
+ Any assignments to a `MultyxItem` or the creation of a child on a `MultyxItem`, such as setting `client.self.position = { x: 3, y: 2 };` will turn the value of the assignment into a `MultyxItem` itself.
389
+
390
+ MultyxItem objects cannot be directly created through the server-side code, as they require an owner and references to the MultyxServer. They do, however, exist on the `Client.self` or `MultyxTeam.self` properties.
391
+
392
+ #### `MultyxItem.value`
393
+
394
+ The original representation of the MultyxItem, or the MultyxItem's respective primitive, object, or array.
395
+
396
+ ```js
397
+ // server.js
398
+ team.self.position = { x: 30, y: 100 };
399
+
400
+ console.log(team.self.position); // MultyxObject { x: 30, y: 100 }
401
+ console.log(team.self.position.value); // { x: 30, y: 100 }
402
+ ```
403
+
404
+ #### `MultyxItem.disable()`
405
+
406
+ Disable the MultyxItem and any of its children from being edited by the client.
407
+
408
+ Returns same MultyxItem
409
+
410
+ #### `MultyxItem.enable()`
411
+
412
+ Allow the MultyxItem and any of its children to be edited by the client.
413
+
414
+ Returns same MultyxItem
415
+
416
+ #### `MultyxItem.disabled`
417
+
418
+ Boolean representing whether or not the MultyxItem is disabled, or able to be edited by the client.
419
+
420
+ #### `MultyxItem.relay()`
421
+
422
+ Relay any changes on the value of this MultyxItem to the client, along with any other public clients able to view it. Calling this after a client connection has already been established can be memory-inefficient, as this MultyxItem along with any children will all relay their values along with their constraints.
423
+
424
+ Returns same MultyxItem
425
+
426
+ #### `MultyxItem.unrelay()`
427
+
428
+ Stop sending any changes on the value of this MultyxItem to the client, along with any other public clients able to view it. This does not change whether or not the client can edit or view this MultyxItem, and the Multyx server will still send the value of the MultyxItem to the agent along with any public clients on MultyxItem instantiation.
429
+
430
+ Returns same MultyxItem
431
+
432
+ #### `MultyxItem.relayed`
433
+
434
+ Boolean representing whether or not changes in the value of the MultyxItem are being relayed to the client and any other public clients.
435
+
436
+ #### `MultyxItem.removePublic(team: MultyxTeam)`
437
+
438
+ Hide the MultyxItem from a specified team. If clients on that team have visibility of the MultyxItem through another team, this does not hide the MultyxItem from those clients.
439
+
440
+ Returns same MultyxItem
441
+
442
+ #### `MultyxItem.addPublic(team: MultyxTeam)`
443
+
444
+ Make the MultyxItem visible to all clients on a specified MultyxTeam. This does not allow clients of the MultyxTeam to be able to edit the MultyxItem, but allows them to view the data inside the MultyxItem.
445
+
446
+ Returns same MultyxItem
447
+
448
+ #### `MultyxItem.isPublic(team: MultyxTeam)`
449
+
450
+ Returns whether or not the MultyxItem is visible to a specified MultyxTeam.
451
+
452
+ #### `MultyxItem.agent`
453
+
454
+ The agent, Client or MultyxTeam, that is the sole editor of this MultyxItem. This agent has the ability to change, alter, delete, or set any properties of this MultyxItem, granted the server allows it. If the agent is a team, any client on that team has that same ability.
455
+
456
+ #### `MultyxItem.propertyPath`
457
+
458
+ An array describing the path from head to tail of this MultyxItem.
459
+
460
+ ```js
461
+ // server.js
462
+ client.self = {
463
+ inventory: [{ name: 'apple' }]
464
+ };
465
+
466
+ const apple = client.self.inventory[0].name;
467
+ console.log(apple.propertyPath); // ['ac942ed2', 'inventory', '0', 'name']
468
+ ```
469
+
470
+ ***
471
+
472
+ ### MultyxValue
473
+
474
+ A `MultyxValue` is the [`MultyxItem`](#multyxitem) representation of primitive values, which includes strings, numbers, and booleans. These are the fundamental blocks of MultyxItems, which [`MultyxObject`](#multyxobject) and [`MultyxList`](#multyxlist) classes are made up of.
475
+
476
+ #### `MultyxValue.set(value: Value | MultyxValue)`
477
+
478
+ Set the value of the MultyxValue. This will run the requested value through all of the constraints and, if accepted, will relay the change to any clients with visibility.
479
+
480
+ This generally doesn't need to be used, since the value of MultyxValue can be set explicitly with native operators.
481
+
482
+ Returns boolean representing success of operation.
483
+
484
+ ```js
485
+ // server.js
486
+ client.self.x = 3; // does the same thing as below
487
+ client.self.x.set(3); // does the same thing as above
488
+ ```
489
+
490
+ #### `MultyxValue.min(value: Value | MultyxValue)`
491
+
492
+ Constrain the value of MultyxValue to have a minimum of `value`. Will relay a change to the client describing the new minimum value.
493
+
494
+ Returns same MultyxValue
495
+
496
+ #### `MultyxValue.max(value: Value | MultyxValue)`
497
+
498
+ Constrain the value of MultyxValue to have a maximum of `value`. Will relay a change to the client describing the new maximum value.
499
+
500
+ Returns same MultyxValue
501
+
502
+ #### `MultyxValue.ban(value: Value | MultyxValue)`
503
+
504
+ Disallow MultyxValue to have a specified value. Will revert to previous value if requested value is banned.
505
+
506
+ Returns same MultyxValue
507
+
508
+ #### `MultyxValue.constrain(fn: ((value: any) => Value | null))`
509
+
510
+ Create a custom constraint on MultyxValue. This will only be constrained server-side, since code should not be transmitted over network, meaning that there isn't client prediction of this constraint.
511
+
512
+ The parameter takes in a function that takes in the requested value and returns either null or an accepted value. If this function returns null, the value will not be accepted and the change will be reverted.
513
+
514
+ Returns same MultyxValue
515
+
516
+ #### `MultyxValue.constraints`
517
+
518
+ A Map object containing the list of constraints placed onto the MultyxValue, excluding any custom ones. The key of this map is the name of the constraint, and the value of this map is an object containing the arguments of the constraint along with the constraint function.
519
+
520
+ ```js
521
+ // server.js
522
+ client.self.x.min(-100);
523
+
524
+ console.log(client.self.x.constraints);
525
+ /*
526
+ Map {
527
+ 'min': {
528
+ args: [-100],
529
+ func: n => n >= value ? n : value
530
+ }
531
+ }
532
+ */
533
+ ```
534
+
535
+ #### `MultyxValue.manualConstraints`
536
+
537
+ An array containing all custom constraint functions placed onto the MultyxValue.
538
+
539
+ #### `MultyxValue.bannedValues`
540
+
541
+ A Set object containing the list of all banned values of this MultyxValue
542
+
543
+ ***
544
+
545
+ ### MultyxObject
546
+
547
+ A `MultyxObject` is the [`MultyxItem`](#multyxitem) representation of JavaScript objects, or key-value pairs. These consist of strings representing properties of the original object, corresponding to MultyxItem objects representing the values of the original object. The original object is the JavaScript object that MultyxObject is mirroring. These can be nested arbitrarily deep, and child elements can host any type of [`MultyxItem`](#multyxitem).
548
+
549
+ Generally, within a `MultyxObject`, any changes to the visibility of editability of the object, such as `.disable()` or `.addPublic()` will propogate throughout all children, overwriting any settings that they had prior.
550
+
551
+ #### `MultyxObject.has(property: string): boolean`
552
+
553
+ Returns a boolean that is true if `property` is in the MultyxObject representation, false otherwise. Synonymous with the `in` keyword on a JavaScript object.
554
+
555
+ #### `MultyxObject.get(property: string): MultyxItem`
556
+
557
+ Returns the MultyxItem value of a property. This generally does not need to be used, since properties can be accessed directly from the object. This does have to be used, however, if the property has a name that is already a native MultyxObject property or method. It is best practice not to set properties of a MultyxObject that conflicts with the native MultyxObject implementation.
558
+
559
+ ```js
560
+ // server.js
561
+ console.log(client.self.x); // does same as below
562
+ console.log(client.self.get('x')); // does same as above
563
+
564
+ console.log(client.self.data); // logs native Multyx stuff
565
+ console.log(client.self.get('data')); // logs MultyxItem data
566
+ ```
567
+
568
+ #### `MultyxObject.set(property: string, value: any): MultyxObject | false`
569
+
570
+ Set the value of a property, and if accepted, relay the change to any clients with visibility. This will parse `value` and create a MultyxItem representation that mirrors `value`. If setting a value whose property already exists, for instance `client.self.x = 3; client.self.x = 5;`, the MultyxObject will not create a new MultyxValue instance, but merely change the value inside the MultyxValue by calling `.set()`.
571
+
572
+ This generally does not need to be used, since properties can be assigned directly from the object. This does have to be used, however, if the property has a name that is already a native MultyxObject property or method. It is best practice not to set properties of a MultyxObject that conflicts with the native MultyxObject implementation.
573
+
574
+ Returns same MultyxObject if change accepted, false otherwise.
575
+
576
+ #### `MultyxObject.delete(property: string): MultyxObject | false`
577
+
578
+ Delete the property from the object and relay the change to any clients with visibility.
579
+
580
+ Returns same MultyxObject
581
+
582
+ #### `MultyxObject.data`
583
+
584
+ This is the MultyxObject representation of the original object. It is a key-value pair between strings representing properties of the object, and MultyxItem objects representing the values of the object.
585
+
586
+ ***
587
+
588
+ ### MultyxList
589
+
590
+ A `MultyxList` is the [`MultyxItem`](#multyxitem) representation of JavaScript arrays. They are inherited from the `MultyxObject` class, so any method or property inside `MultyxObject` is equally valid inside `MultyxList`. They can be indexed directly for assignment and retrieval, and are made up of `MultyxItem` objects that can be nested arbitrarily deep.
591
+
592
+ Generally, within a `MultyxList`, any changes to the visibility of editability of the object, such as `.disable()` or `.addPublic()` will propogate throughout all children, overwriting any settings that they had prior.
593
+
594
+ Along with having the methods and properties inherited from `MultyxObject`, `MultyxList` objects have similar properties and methods to Array objects. Unlike Array objects, however, in methods such as `.map()` or `.splice()`, MultyxList will not create a new list, but edit in-place the already existing MultyxList.
595
+
596
+ #### `MultyxList.length`
597
+
598
+ Just like Array.length, the `.length` property represents the number of elements in the `MultyxList`.
599
+
600
+ The slight difference between `Array.length` is the exclusion of `<empty item>`. `MultyxList` does not have a method or property for including `<empty item>` like in `Array` objects, so missing elements are denoted with `undefined` values. The length of a `MultyxList` is calculated by taking the index of the last defined element plus one, so any `<empty item>` elements that would count towards the length in the `Array` object do not in the `MultyxList` object.
601
+
602
+ This can possibly lead to discrepancies between the client and server, so keep this in mind.
603
+
604
+ ```js
605
+ // server.js
606
+ client.self.array = ['a'];
607
+ client.self.array[5] = 'g';
608
+ client.self.array.pop();
609
+ console.log(client.self.array.length); // 1
610
+
611
+ const array = ['a'];
612
+ array[5] = 'g';
613
+ array.pop();
614
+ console.log(array.length); // 5
615
+ ```
616
+
617
+ #### `MultyxList.deorder(): MultyxItem[]`
618
+
619
+ Creates an array containing values of all defined items in MultyxList. This method allows the looping of MultyxList elements without undefined elements. This is a useful method if incorporating client-side interpolation functions. When elements in arrays are shifted over to other indices, the changes in value are sent to the client rather than informing the client of a shift. This means that interpolation skips a frame, as a new [`MultyxClientValue`](#multyxclientvalue) and interpolation function get instantiated. This can be countered by not utilizing Array methods that shift elements' indices, along with utilizing the `MultyxList.deorder()` method.
620
+
621
+ Instead of using shifting methods such as `MultyxList.splice()` or `MultyxList.shift()` to delete elements of the `MultyxList`, the method `MultyxList.delete()` can be used, and `MultyxList.deorder()` can be iterated over, skipping the deleted element. This allows the `MultyxClientObject` on the client-side to retain index-element pairs, making interpolation functions run smoothly.
622
+
623
+ ```js
624
+ // server.js
625
+ client.self.array = ['a', 'b', 'c', 'd', 'e'];
626
+ client.self.array.delete(2);
627
+ console.log(client.self.array); // ['a', 'b', undefined, 'd', 'e']
628
+ console.log(client.self.array.deorder()); // ['a', 'b', 'd', 'e']
629
+ ```
630
+
631
+ #### `MultyxList.deorderEntries(): [number, MultyxItem][]`
632
+
633
+ Return an array of entries of all defiend elements inside MultyxList.
634
+
635
+ ```js
636
+ // server.js
637
+ client.self.array = ['a', 'b', 'c', 'd'];
638
+ client.self.array.delete(2);
639
+ console.log(client.self.array.deorderEntries()); // [[0, 'a'], [1, 'b'], [3, 'd']]
640
+ ```
641
+
642
+ #### `MultyxList.push(...items: any[]): number`
643
+
644
+ Appends all items to the end of the `MultyxList`
645
+
646
+ Returns length of `MultyxList`
647
+
648
+ #### `MultyxList.pop(): MultyxItem | undefined`
649
+
650
+ Removes and returns the last item in the MultyxList. If the list is empty, it returns undefined.
651
+
652
+ #### `MultyxList.unshift(...items: any[]): number`
653
+
654
+ Adds one or more items to the beginning of the MultyxList and returns the new length of the list.
655
+
656
+ #### `MultyxList.shift(): MultyxItem | undefined`
657
+
658
+ Removes and returns the first item in the MultyxList. If the list is empty, it returns undefined.
659
+
660
+ #### `MultyxList.splice(start: number, deleteCount?: number, ...items: any[])`
661
+
662
+ Changes the contents of the MultyxList by removing or replacing existing elements and/or adding new elements at the specified start index. The deleteCount parameter specifies the number of elements to remove. It shifts elements as needed to accommodate additions.
663
+
664
+ If utilizing interpolation for values of MultyxList, it is recommended to push new elements and delete old ones instead, as shifted elements will retain unshifted value inside interpolation history, leading to slightly choppy interpolation movement.
665
+
666
+ #### `MultyxList.slice(start?: number, end?: number)`
667
+
668
+ Turns MultyxList into a portion of the array ranging from indices `start` to `end` (`end` not included). This does not return a new MultyxList or a reference to a MultyxList, but modifies the original MultyxList.
669
+
670
+ #### `MultyxList.filter(predicate: (value: any, index: number, array: MultyxList) => boolean)`
671
+
672
+ Creates a new MultyxList containing only the elements that pass the test implemented by the provided predicate function. The original list is modified to reflect the filtering operation.
673
+
674
+ #### `MultyxList.map(callbackfn: (value: any, index: number, array: MultyxList) => any)`
675
+
676
+ Transforms each element of the MultyxList using the provided callback function. The transformed values replace the original values in the list.
677
+
678
+ #### `MultyxList.flat()`
679
+
680
+ Flattens nested MultyxList structures by one level, appending the elements of any nested lists to the main list.
681
+
682
+ #### `MultyxList.reduce(callbackfn: (accumulator: any, currentValue: any, index: number, array: MultyxList) => any, startingAccumulator: any)`
683
+
684
+ Applies the provided callback function to reduce the MultyxList to a single value, starting with the given initial accumulator.
685
+
686
+ #### `MultyxList.reduceRight(callbackfn: (accumulator: any, currentValue: any, index: number, array: MultyxList) => any, startingAccumulator: any)`
687
+
688
+ Similar to reduce, but processes the elements of the MultyxList from right to left.
689
+
690
+ #### `MultyxList.reverse()`
691
+
692
+ Reverses the order of the elements in the MultyxList in place and returns same MultyxList.
693
+
694
+ #### `MultyxList.forEach()`
695
+
696
+ Executes a provided function once for each MultyxList element.
697
+
698
+ #### `MultyxList.some()`
699
+
700
+ Tests whether at least one element in the MultyxList passes the test implemented by the provided predicate function. Returns true if so, otherwise false.
701
+
702
+ #### `MultyxList.every()`
703
+
704
+ Tests whether all elements in the MultyxList pass the test implemented by the provided predicate function. Returns true if all pass, otherwise false.
705
+
706
+ #### `MultyxList.find()`
707
+
708
+ Returns the first element in the MultyxList that satisfies the provided predicate function. If no element satisfies the predicate, it returns undefined.
709
+
710
+ #### `MultyxList.findIndex()`
711
+
712
+ Returns the index of the first element in the MultyxList that satisfies the provided predicate function. If no element satisfies the predicate, it returns -1.
713
+
714
+ #### `MultyxList.entries()`
715
+
716
+ Returns an array of [value, index] pairs for each element in the MultyxList.
717
+
718
+ #### `MultyxList.keys()`
719
+
720
+ Returns an array of the keys (indices) for each element in the MultyxList.
721
+
722
+ ***
723
+
724
+ ### Agent
725
+
726
+ In server-side Multyx, and agent is either a [`Client`](#client) or a [`MultyxTeam`](#multyxteam). These host the interactions between the client-side and server-side. An Agent contains its own [`MultyxObject`](#multyxobject) along with a UUID, and reference to the server.
727
+
728
+ #### `Agent.self`
729
+
730
+ This is the shared state [`MultyxObject`](#multyxobject) on the server side. Any clients that are a part of this agent have the ability to edit and view the entire object.
731
+
732
+ #### `Agent.uuid`
733
+
734
+ This is the unique identifier that defines this Agent. It is by default 8 characters long and consists of the lowercase alphabet and numbers. It is the first property in `MultyxItem.propertyPath` to denote which agent the [`MultyxItem`](#multyxitem) belongs to.
735
+
736
+ #### `Agent.send(eventName: string, data: any)`
737
+
738
+ Send a custom message to all clients that the agent represents. Takes in an `eventName` to be referenced to on the client side, along with any JSON formatable data to send.
739
+
740
+ This does not send on frame, and will be sent over Websocket immediately.
741
+
742
+ #### `Agent.server`
743
+
744
+ Reference to the MultyxServer object hosting the agent.
745
+
746
+ #### `Agent.clients`
747
+
748
+ Array of all clients that the agent represents. If `Agent` is a `Client`, the array will have a length of 1 and the only element will be the `Client` itself. If `Agent` is a [`MultyxTeam`](#multyxteam), the array will be all clients that are a part of the team.
749
+
750
+ ***
751
+
752
+ ### Client
753
+
754
+ Because a Client is an Agent, all properties and methods that are a part of Agent are also valid inside Client.
755
+
756
+ #### `Client.controller`
757
+
758
+ The `controller` property is an instance of the [`Controller`](#controller) class, which manages client input and state synchronization. It enables clients to listen to specific input events (e.g., keyboard, mouse) and execute callbacks when those events occur.
759
+
760
+ #### `Client.joinTime`
761
+
762
+ The timestamp in milliseconds when the client was created.
763
+
764
+ #### `Client.teams`
765
+
766
+ A Set object containing all [`MultyxTeam`](#multyxteam) objects that the client is a part of.
767
+
768
+ This is meant to be read-only, adding a client to a team can be done with the `MultyxTeam.addClient()` method.
769
+
770
+ #### `Client.updateSize`
771
+
772
+ The size, in bytes, that Multyx is sending over the websocket in the previous update frame.
773
+
774
+ ***
775
+
776
+ ### Controller
777
+
778
+ The Controller class manages input events and state for clients. It tracks keyboard and mouse inputs, enabling real-time interactivity and customization of input handling.
779
+
780
+ #### listenTo(input: string | string[], callback?: (state: ControllerState) => void)
781
+
782
+ Listen to a specified input and trigger a callback when that input gets triggered.
783
+
784
+ The `input` parameter corresponds to the keyboard `event.key` and `event.code`, along with a variety of inputs that are part of the `Multyx.Input` enum for mouse inputs and special keys.
785
+
786
+ The callback parameter takes in a function and when triggered passes the current [`ControllerState`](#controllerstate) to the callback.
787
+
788
+ ```js
789
+ // server.js
790
+ client.controller.listenTo(['w', 's']);
791
+
792
+ client.controller.listenTo(Input.MouseDown, state => {
793
+ console.log('Going forward? ' + state.keys['w']);
794
+ console.log('Going backward? ' + state.keys['s']);
795
+ });
796
+ ```
797
+
798
+ ***
799
+
800
+ ### ControllerState
801
+
802
+ The controller state does not by default track any of the client's inputs. Only the inputs that are being listened to by the `Controller` object appear in the state.
803
+
804
+ ```ts
805
+ // multyx.ts
806
+ export type ControllerState = {
807
+ keys: { [key: string]: boolean },
808
+ mouse: { x: number, y: number, down: boolean }
809
+ }
810
+ ```
811
+
812
+ ### Input
813
+
814
+ ```ts
815
+ // multyx.ts
816
+ export enum Input {
817
+ MouseMove = "mousemove",
818
+ MouseDown = "mousedown",
819
+ MouseUp = "mouseup",
820
+
821
+ KeyDown = "keydown",
822
+ KeyHold = "keyhold",
823
+ KeyUp = "keyup",
824
+ KeyPress = "keypress",
825
+
826
+ Shift = "Shift",
827
+ Alt = "Alt",
828
+ Tab = "Tab",
829
+ Control = "Control",
830
+ Enter = "Enter",
831
+ Escape = "Escape",
832
+ Delete = "Delete",
833
+ Space = "Space",
834
+ CapsLock = "CapsLock",
835
+
836
+ LeftShift = "ShiftLeft",
837
+ RightShift = "ShiftRight",
838
+ LeftControl = "ControlLeft",
839
+ RightControl= "ControlRight",
840
+ LeftAlt = "AltLeft",
841
+ RightAlt = "AltRight",
842
+
843
+ UpArrow = "ArrowUp",
844
+ DownArrow = "ArrowDown",
845
+ LeftArrow = "ArrowLeft",
846
+ RightArrow = "ArrowRight",
847
+ }
848
+ ```
849
+
850
+ ***
851
+
852
+ ### MultyxTeam
853
+
854
+ A [`MultyxTeam`](#multyxteam) is at its core a list of [`Client`](#client) classes representing all clients that are part of that team, along with a [`MultyxObject`](#multyxobject) describing the shared data of that team. This [`MultyxObject`](#multyxobject) is public to all clients that are within the team, and by default has the ability to be edited by any [`Client`](#client) in the team, though this can be disabled.
855
+
856
+ In Multyx, clients do not interact with each other. The [`MultyxTeam`](#multyxteam) class is the only way to share state between clients, as Client1 cannot edit the state of Client2. There is a difference, however, between being able to edit state, and being able to view it. This is achieved by making a [`MultyxItem`](#multyxitem) public to a specified MultyxTeam.
857
+
858
+ Because a MultyxTeam is an Agent, all properties and methods that are a part of Agent are also valid inside MultyxTeam.
859
+
860
+ Using the `multyx.all` team that gets provided natively by the [`MultyxServer`](#multyxserver) class, clients can share data to anyone connected to the server.
861
+
862
+ #### `getClient(uuid: string)`
863
+
864
+ Retrieves the [`Client`](#client) object corresponding the given `uuid`.
865
+
866
+ #### `addClient(client: Client)`
867
+
868
+ Add a client to the MultyxTeam. Relays this change to all clients who are part of the team. This also relays all of the data in `MultyxTeam.self` to the client joining.
869
+
870
+ #### `removeClient(client: Client)`
871
+
872
+ Remove a client from the MultyxTeam. Relays this change to all clients who are part of the team. This also clears all data of the `MultyxTeam.self` from the client.
873
+
874
+ #### `addPublic(item: MultyxItem)`
875
+
876
+ Make a [`MultyxItem`](#multyxitem) visible to all clients in the team.
877
+
878
+ ```js
879
+ // server.js
880
+ client1.self.secret = 'amongus imposter';
881
+ team.addPublic(client1.self);
882
+ team.send('new player');
883
+ ```
884
+
885
+ ```js
886
+ // client2.js
887
+ const client1 = multyx.clients[uuidClient1];
888
+ console.log(client1); // undefined
889
+
890
+ multyx.on('new player', () => {
891
+ console.log(client1); // { secret: 'amongus imposter' }
892
+ });
893
+ ```
894
+
895
+ #### `removePublic(item: MultyxItem)`
896
+
897
+ Revoke item visibility of a [`MultyxItem`](#multyxitem) from a team. This does not necessarily revoke visibility from all clients in the team, since other clients may have visibility through another team.
898
+
899
+ ```js
900
+ // server.js
901
+ team.addClient(client1);
902
+ team.addClient(client2);
903
+ team.addPublic(client1.self);
904
+ team.addPublic(client2.self);
905
+
906
+ multyx.all.removePublic(client1.self);
907
+ multyx.all.send('removed');
908
+ ```
909
+
910
+ ```js
911
+ // client2.js
912
+ console.log(client1.imposter); // true
913
+
914
+ multyx.on('removed', () => {
915
+ console.log(client1.imposter); // true
916
+ });
917
+ ```
918
+
919
+ ```js
920
+ // client3.js
921
+ console.log(client1.imposter); // true
922
+
923
+ multyx.on('removed', () => {
924
+ console.log(client1.imposter); // undefined
925
+ });
926
+ ```
927
+
928
+ ***
929
+
930
+ ## Client-Side Documentation
931
+
932
+ ### Multyx (client)
933
+
934
+ A client-side Multyx object is initialized as follows.
935
+
936
+ ```js
937
+ const multyx = new Multyx(options?: MultyxOptions | Callback, callback?: Callback);
938
+ ```
939
+
940
+ Initializing a Multyx class creates a websocket using the built-in WebSocket API.
941
+
942
+ ```ts
943
+ export type Options = {
944
+ port?: number,
945
+ secure?: boolean,
946
+ uri?: string,
947
+ verbose?: boolean,
948
+ logUpdateFrame?: boolean,
949
+ };
950
+
951
+ export const DefaultOptions: Options = {
952
+ port: 443,
953
+ secure: false,
954
+ uri: 'localhost',
955
+ verbose: false,
956
+ logUpdateFrame: false,
957
+ };
958
+ ```
959
+
960
+ #### `Options.port` (Client)
961
+
962
+ Port to connect to WebsocketServer from, defaults to 443.
963
+
964
+ #### `Options.secure`
965
+
966
+ Boolean representing whether or not to attempt a secure websocket connection (wss://)
967
+
968
+ This will not work if `Options.uri` is set to localhost, such as in a development environment.
969
+
970
+ #### `Options.uri`
971
+
972
+ What URI to attempt to connect websocket at.
973
+
974
+ #### `Options.verbose`
975
+
976
+ Log errors if receiving faulty data from MultyxServer, along with logging any denied change requests to a MultyxClientItem. This can be useful if debugging code, to show where the client may be attempting to edit disabled data.
977
+
978
+ #### `Options.logUpdateFrame`
979
+
980
+ Logs the raw data sent from MultyxServer from each update frame.
981
+
982
+ #### `Multyx.uuid`
983
+
984
+ The UUID assigned to the client at server start.
985
+
986
+ #### `Multyx.joinTime`
987
+
988
+ The UNIX timestamp of server accepting new client connection.
989
+
990
+ #### `Multyx.ping`
991
+
992
+ The time it takes a Websocket packet to make a round trip from the client to the server and back.
993
+
994
+ Calculated by multiplying the update frame send time minus update frame receive time times 2.
995
+
996
+ #### `Multyx.clients`
997
+
998
+ An object representing the public data of all clients connected to the server. Keys of this object are client UUIDs and values of this object are [MultyxClientObject](#multyxclientobject) objects
999
+
1000
+ #### `Multyx.self`
1001
+
1002
+ A [MultyxClientObject](#multyxclientobject) representing the client's shared state with the server. This is the same as `Multyx.clients[Multyx.uuid]`
1003
+
1004
+ #### `Multyx.teams`
1005
+
1006
+ A [MultyxClientObject](#multyxclientobject) representing the shared state of all teams the client is a part of.
1007
+
1008
+ #### `Multyx.all`
1009
+
1010
+ A [MultyxClientObject](#multyxclientobject) representing the shared state of the team including all connected clients. This is the same object and same instance as `Multyx.teams['all']`.
1011
+
1012
+ #### `Multyx.controller`
1013
+
1014
+ The client [`Controller`](#controller-client) that processes client input and relays data to the server.
1015
+
1016
+ #### `Multyx Events`
1017
+
1018
+ ```ts
1019
+ export default class Multyx {
1020
+ static Start = Symbol('start'); // Client first establishes connection
1021
+ static Connection = Symbol('connection'); // New client connects to server
1022
+ static Disconnect = Symbol('disconnect'); // Other client disconnects from server
1023
+ static Edit = Symbol('edit'); // Server edits a MultyxClientItem
1024
+ static Native = Symbol('native'); // Any native Multyx event
1025
+ static Custom = Symbol('custom'); // Any custom Multyx event
1026
+ static Any = Symbol('any'); // Any Multyx event
1027
+ }
1028
+ ```
1029
+
1030
+ #### `Multyx.on(name: string | Symbol, callback: Callback)`
1031
+
1032
+ Listen to event sent by server. Will call `callback` with specific data from server.
1033
+
1034
+ If `name` is a Multyx Event, the argument sent to callback will be determined by which event is being listened to:
1035
+
1036
+ ```ts
1037
+ Multyx.Start // Raw update from MultyxServer
1038
+ Multyx.Connection // MultyxClientItem representing newly connected client
1039
+ Multyx.Disconnect // Raw object representing disconnected client
1040
+ Multyx.Edit // Raw update from MultyxServer
1041
+ Multyx.Native // Raw message from MultyxServer
1042
+ Multyx.Custom // Raw message from MultyxServer
1043
+ Multyx.Start // Raw message from MultyxServer
1044
+ ```
1045
+
1046
+ #### `Multyx.send(name: string, data: any, expectResponse: boolean = false)`
1047
+
1048
+ Send an event to the server with the event name `name` and any extra data.
1049
+
1050
+ Returns either a promise if `expectResponse` is true, undefined otherwise.
1051
+
1052
+ #### `Multyx.loop(callback: Callback, timesPerSecond?: number)`
1053
+
1054
+ Create a loop that calls the `callback` function every `1/timesPerSecond` seconds. If `timesPerSecond` is left undefined, Multyx will use `requestAnimationFrame` to repeatedly call the `callback`.
1055
+
1056
+ It is recommended for animation purposes, such as rendering the screen, to leave `timesPerSecond` undefined, as `requestAnimationFrame` is built to handle rendering loops.
1057
+
1058
+ #### `Multyx.forAll(callback: (client: MultyxClient) => void)`
1059
+
1060
+ Create a callback function that gets called for any current or future client
1061
+
1062
+ ***
1063
+
1064
+ ### Controller (Client)
1065
+
1066
+ The client controller is the class that watches for and relays changes in the client input.
1067
+
1068
+ #### `Controller.keys`
1069
+
1070
+ The state of key inputs stored in a key-value pair between keyboard event `event.key` or `event.code` to a boolean describing if the key is currently being pressed.
1071
+
1072
+ ```js
1073
+ // client.js
1074
+ console.log(multyx.controller.keys['a']); // true
1075
+ console.log(multyx.controller.keys['ShiftLeft']); // false
1076
+ ```
1077
+
1078
+ #### `Controller.mouse`
1079
+
1080
+ The state of mouse inputs.
1081
+
1082
+ ```ts
1083
+ // multyx.ts
1084
+ this.mouse = {
1085
+ x: NaN, // x-value of mouse in mouse coordinates
1086
+ y: NaN, // y-value of mouse in mouse coordinates
1087
+ down: false, // true if mouse is pressed, false otherwise
1088
+ centerX: 0, // translation factor of mouse x
1089
+ centerY: 0, // translation factor of mouse y
1090
+ scaleX: 1, // scaling factor of mouse x
1091
+ scaleY: 1 // scaling factor of mouse y
1092
+ };
1093
+ ```
1094
+
1095
+ #### `Controller.listening`
1096
+
1097
+ A Set object containing all input events to listen to and relay to client. This gets populated by the server so it is recommended to let this be read-only.
1098
+
1099
+ #### `Controller.mapCanvasPosition(canvas: HTMLCanvasElement, position: { top?: number, bottom?: number, left?: number, right?: number, anchor?: 'center' | 'left' | 'right' | 'top' | 'bottom' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright' })`
1100
+
1101
+ Maps the canvas coordinates to a specified position, using the `anchor` parameter to find where the origin is, along with the `position` parameter to scale the canvas properly.
1102
+
1103
+ Only the necessary position data is required. If partial data is entered, Multyx will attempt to calculate the values by assuming square coordinates (1 unit horizontal = 1 unit vertical).
1104
+
1105
+ ```js
1106
+ // client.js
1107
+
1108
+ // will make canvas coordinate (0, 0) correspond to bottom-left
1109
+ // and make top of canvas be 1000 units while keeping canvas scale square
1110
+ multyx.controller.mapCanvasPosition(canvas, { top: 1000 }, 'bottomleft');
1111
+
1112
+ // will map canvas coordinates to correspond to these positions
1113
+ multyx.controller.mapCanvasPosition(canvas, { top: 10, left: -10, bottom: -10, right: 10 });
1114
+
1115
+ // Error: Cannot include value for right if anchoring at right
1116
+ multyx.controller.mapCanvasPosition(canvas, { right: 500 }, 'right');
1117
+ ```
1118
+
1119
+ #### `Controller.mapMousePosition(centerX: number, centerY: number, anchor: HTMLElement = document.body, scaleX: number = 1, scaleY: number = scaleX)`
1120
+
1121
+ Maps the mouse coordinates to a the top-left corner of a specific `anchor` element.
1122
+
1123
+ #### `Controller.mapMouseToCanvas(canvas: HTMLCanvasElement)`
1124
+
1125
+ Maps the mouse coordinates exactly to the canvas coordinates. Very useful for most applications and makes calculations and animations simpler.
1126
+
1127
+ #### `Controller.setMouseAs(mouseGetter: () => { x: number, y: number })`
1128
+
1129
+ Relays the mouse coordinates as whatever the `mouseGetter` argument returns when called. This is useful if utilizing another library that already has mouse mapping. This does not change the client-side `Controller.mouse` coordinates, but it does relay the mouse coordinates retrieved from the `mouseGetter` argument. For accurate client-side mouse coordinates, use the mouse coordinates from the other library.
1130
+
1131
+ ***
1132
+
1133
+ ### MultyxClientItem
1134
+
1135
+ A `MultyxClientItem` is the base of shared state on the client side. A `MultyxClientItem` is merely a union type between the following, [`MultyxClientValue`](#multyxclientvalue), [`MultyxClientObject`](#multyxclientobject), and [`MultyxClientList`](#multyxclientlist). When a property is changed, deleted, or set inside a `MultyxClientItem`, that change gets relayed to the server.
1136
+
1137
+ Any assignments to a `MultyxClientItem` or the creation of a child on a `MultyxClientItem` property, such as setting `multyx.self.position = { x: 3, y: 2 };` will turn the value of the assignment into a `MultyxClientItem` itself.
1138
+
1139
+ `MultyxClientItem` objects cannot be directly created through the client-side code, as they require references to the MultyxServer. They do, however, exist on the `multyx.self`, `multyx.all` or `multyx.teams` properties.
1140
+
1141
+ #### `MultyxClientItem.value`
1142
+
1143
+ The original representation of the MultyxClientItem, or the MultyxClientItem's respective primitive, object, or array.
1144
+
1145
+ ```js
1146
+ // client.js
1147
+ multyx.self.position = { x: 30, y: 100 };
1148
+
1149
+ console.log(multyx.self.position); // MultyxObject { x: 30, y: 100 }
1150
+ console.log(multyx.self.position.value); // { x: 30, y: 100 }
1151
+ ```
1152
+
1153
+ #### `MultyxClientItem.editable`
1154
+
1155
+ A boolean representing whether or not the server is allowing the MultyxClientItem to be changed, altered or deleted. If true, any edits can be sent to the server where they will go through more verification and should be accepted. If false, any edits will be immediately canceled.
1156
+
1157
+ ***
1158
+
1159
+ ### MultyxClientValue
1160
+
1161
+ A `MultyxClientValue` is the [`MultyxClientItem`](#multyxclientitem) representation of primitive values, which includes strings, numbers, and booleans. These are the fundamental blocks of MultyxItems, which [`MultyxClientObject`](#multyxclientobject) and [`MultyxClientList`](#multyxclientlist) classes are made up of.
1162
+
1163
+ #### `MultyxClientValue.set(value: Value | MultyxClientValue)`
1164
+
1165
+ Set the value of the MultyxClientValue. This will run the requested value through all of the constraints and, if accepted, will relay the change to the server to request the change.
1166
+
1167
+ This generally doesn't need to be used, since the value of MultyxClientValue can be set explicitly with native operators.
1168
+
1169
+ Returns boolean representing success of operation.
1170
+
1171
+ ```js
1172
+ // client.js
1173
+ multyx.self.x = 3; // does the same thing as below
1174
+ multyx.self.x.set(3); // does the same thing as above
1175
+ ```
1176
+
1177
+ #### `MultyxClientValue.constraints`
1178
+
1179
+ An object containing the constraints placed onto the MultyxClientValue from the server, excluding any custom ones. The key of this object is the name of the constraint, and the value of this object is the constraint function.
1180
+
1181
+ ```js
1182
+ // server.js
1183
+ client.self.x.min(-100);
1184
+ ```
1185
+
1186
+ ```js
1187
+ // client.js
1188
+ console.log(multyx.self.x.constraints); // { 'min': n => n >= -100 ? n : -100 }
1189
+ ```
1190
+
1191
+ #### `MultyxClientValue.Lerp()`
1192
+
1193
+ Linearly interpolate the value of MultyxClientValue across updates. This will run 1 frame behind on average, since it uses the last two updates to interpolate between.
1194
+
1195
+ This interpolation method pretends that time is 1 frame behind, and will take the ratio between the time since last frame to the time between last 2 frames as the progress between values in each frame. This means that if the time between frames is not constant, this interpolation method will not work as smoothly as it could.
1196
+
1197
+ Since updates may happen sparsely instead of during each frame, this interpolator method assumes a maximum time between frames as 250 milliseconds, and will interpolate using that time frame.
1198
+
1199
+ ```js
1200
+ // client.js
1201
+ multyx.self.x = 0;
1202
+ multyx.self.x.Lerp();
1203
+
1204
+ await sleep(50);
1205
+ multyx.self.x = 10;
1206
+
1207
+ console.log(multyx.self.x); // 0
1208
+ await sleep(17);
1209
+ console.log(multyx.self.x); // 3.4
1210
+ await sleep(16);
1211
+ console.log(multyx.self.x); // 6.6
1212
+ await sleep(17);
1213
+ console.log(multyx.self.x); // 10
1214
+ ```
1215
+
1216
+ #### `MultyxClientValue.PredictiveLerp()`
1217
+
1218
+ Linearly interpolate between the past update and the expected value of the next update. This method is similar to the `MultyxClientValue.Lerp()` method, except instead of interpolating between the past two updates, Multyx predicts where the value will be on the next update.
1219
+
1220
+ Multyx does this by taking the change in the value between the previous updates and extrapolates that into the future.
1221
+
1222
+ This works very well on values that have a set change each frame, such as a projectile or object with a constant speed.
1223
+
1224
+ ```js
1225
+ multyx.self.x = 0;
1226
+ multyx.self.x.PredictiveLerp();
1227
+
1228
+ await sleep(50);
1229
+ multyx.self.x = 10;
1230
+
1231
+ console.log(multyx.self.x); // 10
1232
+ await sleep(17);
1233
+ console.log(multyx.self.x); // 13.4
1234
+ await sleep(16);
1235
+ console.log(multyx.self.x); // 16.6
1236
+ await sleep(17);
1237
+ console.log(multyx.self.x); // 20
1238
+ ```
1239
+
1240
+ ### MultyxClientObject
1241
+
1242
+ A `MultyxClientObject` is the [`MultyxClientItem`](#multyxclientitem) representation of JavaScript objects, or key-value pairs. These consist of strings representing properties of the original object, corresponding to MultyxItem objects representing the values of the original object. The original object is the JavaScript object that MultyxObject is mirroring. These can be nested arbitrarily deep, and child elements can host any type of [`MultyxClientItem`](#multyxclientitem).
1243
+
1244
+ #### `MultyxClientObject.has(property: string): boolean`
1245
+
1246
+ Returns a boolean that is true if `property` is in the MultyxClientObject representation, false otherwise. Synonymous with the `in` keyword on a JavaScript object.
1247
+
1248
+ #### `MultyxClientObject.get(property: string): MultyxClientItem`
1249
+
1250
+ Returns the MultyxClientItem value of a property. This generally does not need to be used, since properties can be accessed directly from the object. This does have to be used, however, if the property has a name that is already a native MultyxClientObject property or method. It is best practice not to set properties of a MultyxClientObject that conflicts with the native MultyxClientObject implementation.
1251
+
1252
+ ```js
1253
+ // server.js
1254
+ console.log(client.self.x); // does same as below
1255
+ console.log(client.self.get('x')); // does same as above
1256
+
1257
+ console.log(client.self.data); // logs native Multyx stuff
1258
+ console.log(client.self.get('data')); // logs MultyxItem data
1259
+ ```
1260
+
1261
+ #### `MultyxClientObject.set(property: string, value: any): boolean`
1262
+
1263
+ Set the value of a property, and if accepted by all the constraints, relay the change the server for verification. This will parse `value` and create a MultyxClientItem representation that mirrors `value`. If setting a value whose property already exists, for instance `client.self.x = 3; client.self.x = 5;`, the MultyxClientObject will not create a new MultyxClientValue instance, but merely change the value inside the MultyxClientValue by calling `.set()`.
1264
+
1265
+ This generally does not need to be used, since properties can be assigned directly from the object. This does have to be used, however, if the property has a name that is already a native MultyxClientObject property or method. It is best practice not to set properties of a MultyxClientObject that conflicts with the native MultyxClientObject implementation.
1266
+
1267
+ Returns true if change accepted on client-side, false otherwise.
1268
+
1269
+ #### `MultyxClientObject.delete(property: string): boolean`
1270
+
1271
+ Delete the property from the object and relay the change to server for verification.
1272
+
1273
+ Returns true if change accepted on client-side, false otherwise.
1274
+
1275
+ #### `MultyxClientObject.forAll(callbackfn: (key: any, value: any) => void)`
1276
+
1277
+ Creates a callback function that gets called for any current or future property in MultyxClientObject.
1278
+
1279
+ This does not immediately call the callback function on future property creation, as creation may be made in the middle of an update frame before the property was fully populated with the update. Therefore, the callback function will be called at the end of an update frame.
1280
+
1281
+ #### `MultyxClientObject.keys()`
1282
+
1283
+ Returns an array of the keys of the object representation. Functions the same as `Object.keys(MultyxClientObject.value)`.
1284
+
1285
+ #### `MultyxClientObject.values()`
1286
+
1287
+ Returns an array of the values of the object representation. Functions the same as `Object.values(MultyxClientObject.value)`.
1288
+
1289
+ #### `MultyxClientObject.entries()`
1290
+
1291
+ Returns an array of the entries of the object representation. Functions the same as `Object.entries(MultyxClientObject.value)`.
1292
+
1293
+ ### MultyxClientList
1294
+
1295
+ A `MultyxClientList` is the [`MultyxClientItem`](#multyxclientitem) representation of JavaScript arrays. They are inherited from the `MultyxClientObject` class, so any method or property inside `MultyxClientObject` is equally valid inside `MultyxClientList`. They can be indexed directly for assignment and retrieval, and are made up of `MultyxClientItem` objects that can be nested arbitrarily deep.
1296
+
1297
+ Along with having the methods and properties inherited from `MultyxClientObject`, `MultyxClientList` objects have similar properties and methods to Array objects. Unlike Array objects, however, in methods such as `.map()` or `.flat()`, MultyxClientList will not create a new list, but edit in-place the already existing MultyxClientList.
1298
+
1299
+ #### `MultyxClientList.length`
1300
+
1301
+ Just like Array.length, the `.length` property represents the number of elements in the `MultyxClientList`.
1302
+
1303
+ The slight difference between `Array.length` is the exclusion of `<empty item>`. `MultyxClientList` does not have a method or property for including `<empty item>` like in `Array` objects, so missing elements are denoted with `undefined` values. The length of a `MultyxClientList` is calculated by taking the index of the last defined element plus one, so any `<empty item>` elements that would count towards the length in the `Array` object do not in the `MultyxClientList` object.
1304
+
1305
+ This can possibly lead to discrepancies between the client and server, so keep this in mind.
1306
+
1307
+ ```js
1308
+ // client.js
1309
+ multyx.self.array = ['a'];
1310
+ multyx.self.array[5] = 'g';
1311
+ multyx.self.array.pop();
1312
+ console.log(multyx.self.array.length); // 1
1313
+
1314
+ const array = ['a'];
1315
+ array[5] = 'g';
1316
+ array.pop();
1317
+ console.log(array.length); // 5
1318
+ ```
1319
+
1320
+ #### `MultyxClientList.forAll(callbackfn: (value: any, index: number) => void)`
1321
+
1322
+ Equivalent to `MultyxClientObject.forAll` Creates a callback function that gets called for any current or future element in MultyxClientList.
1323
+
1324
+ This does not immediately call the callback function on future element creation, as creation may be made in the middle of an update frame before the element was fully populated with the update. Therefore, the callback function will be called at the end of an update frame.
1325
+
1326
+ #### `MultyxClientList.deorder(): MultyxClientItem[]`
1327
+
1328
+ Return an array of all defined elements inside MultyxClientList. View [`MultyxList.deorder()`](#multyxlistdeorder-multyxitem) for more information.
1329
+
1330
+ #### `MultyxClientList.deorderEntries(): [number, MultyxClientItem][]`
1331
+
1332
+ Return an array of entries of all defiend elements inside MultyxClientList.
1333
+
1334
+ ```js
1335
+ // client.js
1336
+ multyx.self.array = ['a', 'b', 'c', 'd'];
1337
+ multyx.self.array.delete(2);
1338
+ console.log(multyx.self.array); // ['a', 'b', undefined, 'd']
1339
+ console.log(multyx.self.array.deorder()); // ['a', 'b', 'd']
1340
+ console.log(multyx.self.array.deorderEntries()); // [[0, 'a'], [1, 'b'], [3, 'd']]
1341
+ ```
1342
+
1343
+ #### `MultyxClientList.push(...items: any[]): number`
1344
+
1345
+ Appends all items to the end of the `MultyxClientList`
1346
+
1347
+ Returns length of `MultyxClientList`
1348
+
1349
+ #### `MultyxClientList.pop(): MultyxItem | undefined`
1350
+
1351
+ Removes and returns the last item in the MultyxClientList. If the list is empty, it returns undefined.
1352
+
1353
+ #### `MultyxClientList.unshift(...items: any[]): number`
1354
+
1355
+ Adds one or more items to the beginning of the MultyxClientList and returns the new length of the list.
1356
+
1357
+ #### `MultyxClientList.shift(): MultyxItem | undefined`
1358
+
1359
+ Removes and returns the first item in the MultyxClientList. If the list is empty, it returns undefined.
1360
+
1361
+ #### `MultyxClientList.splice(start: number, deleteCount?: number, ...items: any[])`
1362
+
1363
+ Changes the contents of the MultyxClientList by removing or replacing existing elements and/or adding new elements at the specified start index. The deleteCount parameter specifies the number of elements to remove. It shifts elements as needed to accommodate additions.
1364
+
1365
+ #### `MultyxClientList.filter(predicate: (value: any, index: number, array: MultyxClientList) => boolean)`
1366
+
1367
+ Creates a new MultyxClientList containing only the elements that pass the test implemented by the provided predicate function. The original list is modified to reflect the filtering operation.
1368
+
1369
+ #### `MultyxClientList.map(callbackfn: (value: any, index: number, array: MultyxClientList) => any)`
1370
+
1371
+ Transforms each element of the MultyxClientList using the provided callback function. The transformed values replace the original values in the list.
1372
+
1373
+ #### `MultyxClientList.flat()`
1374
+
1375
+ Flattens nested MultyxClientList structures by one level, appending the elements of any nested lists to the main list.
1376
+
1377
+ #### `MultyxClientList.reduce(callbackfn: (accumulator: any, currentValue: any, index: number, array: MultyxClientList) => any, startingAccumulator: any)`
1378
+
1379
+ Applies the provided callback function to reduce the MultyxClientList to a single value, starting with the given initial accumulator.
1380
+
1381
+ #### `MultyxClientList.reduceRight(callbackfn: (accumulator: any, currentValue: any, index: number, array: MultyxClientList) => any, startingAccumulator: any)`
1382
+
1383
+ Similar to reduce, but processes the elements of the MultyxClientList from right to left.
1384
+
1385
+ #### `MultyxClientList.reverse()`
1386
+
1387
+ Reverses the order of the elements in the MultyxClientList in place and returns same MultyxClientList.
1388
+
1389
+ #### `MultyxClientList.forEach()`
1390
+
1391
+ Executes a provided function once for each MultyxClientList element.
1392
+
1393
+ #### `MultyxClientList.some()`
1394
+
1395
+ Tests whether at least one element in the MultyxClientList passes the test implemented by the provided predicate function. Returns true if so, otherwise false.
1396
+
1397
+ #### `MultyxClientList.every()`
1398
+
1399
+ Tests whether all elements in the MultyxClientList pass the test implemented by the provided predicate function. Returns true if all pass, otherwise false.
1400
+
1401
+ #### `MultyxClientList.find()`
1402
+
1403
+ Returns the first element in the MultyxClientList that satisfies the provided predicate function. If no element satisfies the predicate, it returns undefined.
1404
+
1405
+ #### `MultyxClientList.findIndex()`
1406
+
1407
+ Returns the index of the first element in the MultyxClientList that satisfies the provided predicate function. If no element satisfies the predicate, it returns -1.
1408
+
1409
+ #### `MultyxClientList.entries()`
1410
+
1411
+ Returns an array of [value, index] pairs for each element in the MultyxClientList.
1412
+
1413
+ #### `MultyxClientList.keys()`
1414
+
1415
+ Returns an array of the keys (indices) for each element in the MultyxClientList.
1416
+
1417
+ ***
1418
+
1419
+ ## Starter Project Walkthrough
1420
+
1421
+ Let's start out by making a simple game, where clients move around in a box. The full code for this example is in `/examples/squares/`
1422
+
1423
+ We need to start by setting up our file structure and basic HTML code.
1424
+
1425
+ Let's make 3 files, `index.html` to host our HTML, `script.js` for our client code, and `index.js` for our server code.
1426
+ ***
1427
+
1428
+ ```html
1429
+ <!DOCTYPE html>
1430
+ <html>
1431
+ <head>
1432
+ <title>My First Multyx Game</title>
1433
+ </head>
1434
+ <body>
1435
+ <canvas id="canvas" width="600px" height="600px"></canvas>
1436
+ <script src="https://cdn.jsdelivr.net/npm/multyx@0.1.0/client.js"></script>
1437
+ <script src="script.js"></script>
1438
+ </body>
1439
+ </html>
1440
+ ```
1441
+
1442
+ Here is the setup for our `index.html` file. This code will create a square canvas in our website with a width of 600px. We also include 2 scripts, the first one hosting all of Multyx, and the second one hosting our client code to interact with Multyx.
1443
+ ***
1444
+
1445
+ ```js
1446
+ // script.js
1447
+ const canvas = document.getElementById('canvas');
1448
+ const ctx = canvas.getContext('2d');
1449
+
1450
+ const multyx = new Multyx();
1451
+
1452
+ multyx.controller.mapCanvasPosition(canvas, { top: 1000, anchor: 'bottomleft' });
1453
+ multyx.controller.mapMouseToCanvas(canvas);
1454
+ ```
1455
+
1456
+ Here is the setup for our `script.js` file, or our client-side code. This code first gets the canvas element and stores it to the `canvas` variable, along with getting the 2d context `ctx` allowing us to draw on this canvas. We then initialize our Multyx client, connecting the websocket server and initializing all client and team information. It then uses two Multyx functions called `mapCanvasPosition` and `mapMouseToCanvas`. What these do is normalize the values of our mouse and our canvas, making it a lot easier to do math with these values and draw objects on the canvas.
1457
+
1458
+ What `multyx.controller.mapCanvasPosition` does is tell the canvas to have its origin in the bottom-left, meaning the point `(0, 0)` on our canvas will be at the bottom-left of the element on the screen. Multyx then tells the canvas to make the top have a y-value of 1000, meaning the point `(0, 1000)` on our canvas will be the top-left of the element on the screen. Multyx then tries to make the canvas coordinates a square, meaning that 1 unit vertically is the same number of pixels as 1 unit horizontally. Since we know our canvas is a square, and Multyx just made the canvas 1000 units tall, we know that the canvas is 1000 units across as well.
1459
+
1460
+ The `multyx.controller.mapMouseToCanvas` function tells our Multyx client to relay our mouse coordinates based on where our mouse is on the canvas. This basically links the mouse to the canvas, telling the Multyx server where on the canvas, in canvas coordinates, our mouse is hovering over. This will make calculations easier later.
1461
+
1462
+ We can now setup our `index.js` file.
1463
+
1464
+ ***
1465
+
1466
+ ```js
1467
+ // index.js
1468
+ const Multyx = require('../../server/dist/index').default;
1469
+
1470
+ const multyx = new Multyx.MultyxServer();
1471
+
1472
+ multyx.on(Multyx.Events.Connect, ({ self, controller }) => {
1473
+ self.color = '#'
1474
+ + Math.floor(Math.random() * 8)
1475
+ + Math.floor(Math.random() * 8)
1476
+ + Math.floor(Math.random() * 8);
1477
+
1478
+ self.direction = 0;
1479
+ self.x = Math.round(Math.random() * 600);
1480
+ self.y = Math.round(Math.random() * 600);
1481
+
1482
+ self.addPublic(multyx.all).disable();
1483
+ self.x.min(0).max(600);
1484
+ self.y.min(0).max(600);
1485
+
1486
+ controller.listenTo(Multyx.Input.MouseMove);
1487
+ });
1488
+ ```
1489
+
1490
+ Here is the setup for our `index.js` file, or our server-side code. What this does first is import the Multyx module, and then initializes our `MultyxServer`, creating the websocket server and allowing websocket connections to come in.
1491
+
1492
+ We then listen for a connection event by using `multyx.on` and listening for `Multyx.Events.Connect`, and create our callback to deal with our incoming client.
1493
+
1494
+ This callback function takes in the `Client` object representing our client who just connected, specifically taking the `Client.self`, and `Client.controller` properties.
1495
+
1496
+ Inside our callback function, we set a bunch of properties to share to the client, such as `color`, which we set to a random hex color between `#000` and `#888`, and `x` and `y`, which we set to a random number between 0 and 600.
1497
+
1498
+ We then make the `Client.self` object public to all clients through the `multyx.all` team, and disable it, meaning that the client cannot edit it. After this, we tell the controller to listen to the `Multyx.Input.MouseMove` event, which tells the client to send an update any time the mouse is moved.
1499
+
1500
+ This is all we need for the initialization in the server. Now that all of our client data is public, we can go back to our client and start rendering the players.
1501
+
1502
+ ```js
1503
+ // script.js
1504
+ const canvas = document.getElementById('canvas');
1505
+ const ctx = canvas.getContext('2d');
1506
+
1507
+ const multyx = new Multyx();
1508
+
1509
+ multyx.controller.mapCanvasPosition(canvas, { top: 600, anchor: 'bottomleft' });
1510
+ multyx.controller.mapMouseToCanvas(canvas);
1511
+
1512
+ multyx.loop(() => {
1513
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1514
+ for(const player of Object.values(multyx.clients)) {
1515
+ ctx.fillStyle = player.color;
1516
+ ctx.fillRect(player.x - 10, player.y - 10, 20, 20);
1517
+ }
1518
+ });
1519
+
1520
+ multyx.forAll(client => {
1521
+ client.x.Lerp();
1522
+ client.y.Lerp();
1523
+ });
1524
+ ```
1525
+
1526
+ In our updated client-side code, we added a `multyx.loop` function, which has a callback that renders everything. The `loop` method in Multyx will run the callback function on a cycle a specific number of times a second. This can be defined as the second argument passed to `multyx.loop`, but if it left empty, Multyx will default to using `requestAnimationFrame` to loop. This is the browsers built-in animation rendering caller.
1527
+
1528
+ We first clear the entire screen in order to draw the new frame onto the screen. We then loop through our clients, set the color to the current player's color, and make a rectangle on the screen at the player's coordinates.
1529
+
1530
+ The next part of the code calls the `multyx.forAll` function. This function will run on every client, as well as any clients that connect in the future. This is a useful function, allowing the client to apply an interpolation function or process other client's data as soon as they connect.
1531
+
1532
+ In this code, we apply an interpolation function called `Lerp`. This will ensure smooth animation and make the rendering look clean instead of choppy. We apply this to the `client.x` and `client.y` values, meaning that the position of our client on each render will move smoothly across.
1533
+
1534
+ All we have to do now is to calculate the client's position on the server, and everything is setup.
1535
+
1536
+ ```js
1537
+ // index.js
1538
+ const Multyx = require('../../server/dist/index').default;
1539
+
1540
+ const multyx = new Multyx.MultyxServer();
1541
+
1542
+ multyx.on(Multyx.Events.Connect, ({ self, controller }) => {
1543
+ self.color = '#'
1544
+ + Math.floor(Math.random() * 8)
1545
+ + Math.floor(Math.random() * 8)
1546
+ + Math.floor(Math.random() * 8);
1547
+
1548
+ self.direction = 0;
1549
+ self.x = Math.round(Math.random() * 600);
1550
+ self.y = Math.round(Math.random() * 600);
1551
+
1552
+ self.addPublic(multyx.all).disable();
1553
+ self.x.min(0).max(600);
1554
+ self.y.min(0).max(600);
1555
+
1556
+ controller.listenTo(Multyx.Input.MouseMove);
1557
+ });
1558
+
1559
+ multyx.on(Multyx.Events.Update, () => {
1560
+ for(const { self, controller } of multyx.all.clients) {
1561
+ // Set player direction to mouse direction
1562
+ self.direction = Math.atan2(
1563
+ controller.state.mouse.y - self.y,
1564
+ controller.state.mouse.x - self.x
1565
+ );
1566
+
1567
+ // Make the speed proportional to distance
1568
+ const distance = Math.hypot(
1569
+ controller.state.mouse.x - self.x,
1570
+ controller.state.mouse.y - self.y
1571
+ );
1572
+
1573
+ // Have a maximum speed of 200
1574
+ const speed = Math.min(200, distance);
1575
+
1576
+ // Move player in direction of mouse
1577
+ self.x += speed * Math.cos(self.direction) * multyx.deltaTime;
1578
+ self.y += speed * Math.sin(self.direction) * multyx.deltaTime;
1579
+ }
1580
+ });
1581
+ ```
1582
+
1583
+ What we have added is an update event listener. When the `Multyx.Events.Update` event gets called, which is right before each frame is sent, we loop through all clients, calculate their direction and speed, and calculate how much we need to move the client.
1584
+
1585
+ We use the `Math.atan2()` method to calculate the angle between the mouse and the player, and the `Math.hypot()` method to calculate the distance between the mouse and the player. We then make the speed equivalent to the distance, and cap it at 200.
1586
+
1587
+ We then update the client position to move in the direction of the mouse at the given speed. We use the `Math.cos()` and `Math.sin()` methods on our direction to get the x and y-values of our movement respectively, and multiply by our `deltaTime` to make sure the amount we move in that time is proportional to the time between frames.
1588
+
1589
+ ## Common Mistakes and Best Practices
1590
+
1591
+ Ensure you use the `==` operator rather than the `===` operator for checking equivalency on a `MultyxValue` or `MultyxClientValue`.
1592
+
1593
+ ```js
1594
+ // server.js
1595
+ client.self.value = 3;
1596
+ console.log(client.self.value == 3); // true, MultyxValue gets typecasted into a number
1597
+ console.log(client.self.value === 3); // false, MultyxValue does not get typecasted
1598
+ ```
1599
+
1600
+ Setting a property using a `MultyxValue` or `MultyxClientValue` only copies the value, not the properties of the Multyx item.
1601
+
1602
+ ```js
1603
+ // server.js
1604
+ client.self.x = 3;
1605
+ client.self.x.disable();
1606
+
1607
+ client.self.y = client.self.x;
1608
+
1609
+ console.log(client.self.x.disabled); // true
1610
+ console.log(client.self.y.disabled); // false
1611
+ ```