| title | Simple Animation |
|---|---|
| description | Learn to create multi-user shared animations and interactions with bouncing balls simulation |
This tutorial teaches you how to create multi-user shared animations and interactions using Multisynq. You'll build a simulation with 25 bouncing balls that can be stopped and started by clicking them. This demonstrates how the Model computes simulations and how the View displays and interacts with them.
<iframe src="https://codepen.io/multisynq/embed/qEErMbw?height=512&theme-id=37190&default-tab=result&editable=true" style={{ width: '100%', height: '512px', border: '2px solid #ccc', borderRadius: '8px', marginBottom: '24px' }} title="Simple Animation" allowFullScreen ></iframe>Click or scan the QR code above to launch a new CodePen instance of this session. Compare the two sessions - you'll see that the animated simulations are identical. The balls all move and bounce exactly the same way.
Click any ball to stop/start it - changes sync to all users The rounded rectangle bounces but ignores user actions How to create a model that runs physics simulations Building views that respond to user interactions Properly communicating between Model and ViewThis application uses two Multisynq Model subclasses: MyModel and BallModel. Both classes must be registered with Multisynq for proper synchronization.
The app uses Multisynq.Constants to ensure all users share the same configuration values:
const Q = Multisynq.Constants;
Q.BALL_NUM = 25; // how many balls do we want?
Q.STEP_MS = 1000 / 30; // bouncing ball tick interval in ms
Q.SPEED = 10; // max speed on a dimension, in units/sMyModel is the root model passed to Multisynq.Session.join(). It creates and stores the BallModel objects in the MyModel.children array.
Each BallModel represents a shaped, colored, bouncing ball. The model stores only the data needed for synchronization:
- Shape: String (
'circle'or'roundRect') - Color: Random color value
- Position: Current x,y coordinates
- Speed: Velocity vector
this.subscribe(this.id, 'touch-me', this.startStop);Each BallModel subscribes to the 'touch-me' event using its own ID as scope. This ensures that only the specific ball responds to touch events intended for it.
this.future(Q.STEP_MS).step();After initialization, each BallModel schedules its first step() method invocation. This creates a continuous simulation loop:
BallModel.step() {
if (this.alive) this.moveBounce();
this.future(Q.STEP_MS).step();
}BallModel.moveBounce() {
const [x, y] = this.pos;
if (x<=0 || x>=1000 || y<=0 || y>=1000)
this.speed = this.randomSpeed();
this.moveTo([x + this.speed[0], y + this.speed[1]]);
}The moveBounce() method updates ball position and handles wall collisions. When a ball hits a wall, it gets a new random speed vector.
randomSpeed() {
const xs = this.random() * 2 - 1;
const ys = this.random() * 2 - 1;
const speedScale = Q.SPEED / (Math.sqrt(xs*xs + ys*ys));
return [xs * speedScale, ys * speedScale];
}The View comprises two classes: MyView and BallView.
MyView is called when a session instance starts, receiving the MyModel object as an argument. It builds the visual representation and container for all balls.
model.children.forEach(child => this.attachChild(child));The MyView accesses the model's children collection and creates a BallView for each BallModel.
MyView.attachChild(child) {
this.element.appendChild(new BallView(child).element);
}MyView listens for browser "resize" events and adjusts the view scale accordingly. This ensures all users see the same scene regardless of their window size.
MyView.detach() {
super.detach();
let child;
while (child = this.element.firstChild) this.element.removeChild(child);
}When a session shuts down, the root view cleans up all child views and resources.
Each BallView tracks its associated BallModel and handles visual representation and user interaction.
this.subscribe(model.id, { event: 'pos-changed', handling: "oncePerFrame" }, this.move);The BallView subscribes to 'pos-changed' events from its specific model. The "oncePerFrame" handling ensures efficient rendering even with multiple position updates per frame.
BallView.enableTouch() {
const el = this.element;
if (TOUCH) el.ontouchstart = start => {
start.preventDefault();
this.publish(el.id, 'touch-me');
}; else el.onmousedown = start => {
start.preventDefault();
this.publish(el.id, 'touch-me');
};
}The BallView sets up touch/click handlers that publish 'touch-me' events. The corresponding BallModel subscribes to these events and toggles ball motion on and off.
Now that you understand basic animation and interaction patterns, you can explore:
Learn to handle user input and message synchronization Create smoother animations with interpolation techniques Store shared configuration in `Multisynq.Constants` Use object IDs to scope events to specific instances Use `"oncePerFrame"` handling for frequent updates Implement proper cleanup in `detach()` methodsThis tutorial demonstrates the power of Multisynq's deterministic synchronization for creating engaging multi-user interactive experiences. The same patterns can be applied to games, simulations, and collaborative tools.