Skip to main content
πŸŽ‰ GP CONF 2026: WEB Games Market Conference πŸ‘Ύ September 23 | Details

Multiplayer in Construct 3

The GamePush plugin for Construct 3 includes an auto-sync engine built on top of GamePush multiplayer. You register the objects you want to synchronize once, start auto-sync β€” and the plugin does everything else:

  • builds the state schema from registered objects;
  • sends the state of your objects every tick;
  • creates, updates and destroys copies of other players' objects;
  • creates world objects controlled by the host for all players;
  • survives host migration without losing objects.

No code is required β€” everything is configured with actions and conditions in the event sheet. If you need low-level access, the full SDK is available in scripts via runtime.GamePush.multiplayer.

The general multiplayer concepts (host and peers, sync modes, interpolation) are described in the Multiplayer section β€” they fully apply to Construct 3 as well.

How it works​

Just like in the SDK, the plugin provides three synchronization mechanisms. In Construct 3 they look like this:

MechanismPlugin actionsPurpose
Player stateRegister object for sync with scope Player statePlayer avatar: position, health, skin. Each player controls their own
Global stateRegister object for sync with scope Global stateEnemies, items, environment. Only the host simulates them, others see copies
MessagesSend message / On message conditionOne-off events: a shot, a death, an item pickup

Key terms:

  • Tag β€” a string identifier of an object type in the state (e.g. "player", "enemy"). Each registered object has its own unique tag.
  • Local instance β€” your own instance of a Player state object. You move it, everyone else receives updates.
  • Proxy β€” an instance the plugin automatically created on your side to display another player's object.
  • Entity β€” a global state object with a network ID (netId). It exists "for real" on the host and as a synchronized copy everywhere else.

Quick start​

The developer workflow:

  1. Build a lobby with channels: all players join the same channel.
  2. Connect to multiplayer with the Connect action.
  3. On the start of the game layout, register your objects and start auto-sync.

Connecting from the lobby (for example, the host presses "Start" and everyone goes to the game layout):

Event sheet: Lobby
β†’Button_Start
On clicked
GP_Channels
Multiplayer: Connect to channel Channels.ChannelID
β†’GP_Channels
Multiplayer: On connect
System
Go to Game
LegendButton_StartGP_ChannelsSystem
  • Button_Start β€” Object Button (Button)
  • GP_Channelsβ€ΊGamePush Channels plugin
  • Systemβ€ΊConstruct 3 System object

Minimal sync setup on the game layout:

Event sheet: Game
β†’System
On start of layout
GP_Channels
Multiplayer: Register MP_player as "player" in Player state state (transform: Position (x, y), interpolate: True, layer: "game")
GP_Channels
Multiplayer: Sync variable hp of MP_player (interpolate: False)
GP_Channels
Multiplayer: Set mode Fast (60 tick)
GP_Channels
Multiplayer: Start auto-sync
LegendSystemGP_ChannelsMP_player

This is enough for:

  • your MP_player to automatically send its position and hp every tick;
  • every player to get copies (proxies) of all other players;
  • a player's proxy to be destroyed automatically when they leave.
Order matters

Call all Register ... and Sync ... actions before Start auto-sync. When auto-sync starts, the plugin builds the state schema from everything that has been registered. Connect can be called before or after β€” synchronization starts once both are done.

Synchronizing the player​

Registering an object​

The Multiplayer β–Έ Register object for sync action links an object type to multiplayer:

ParameterValue
ObjectThe object type (Sprite etc.)
TagA unique tag in the state, e.g. "player"
ScopePlayer state β€” each player controls their own instance; Global state β€” the host controls all instances
TransformNone, Position (x, y), Position + angle or Full (position, angle, size, opacity, visibility)
InterpolateSmoothly interpolate numeric transform fields (position, angle). Turn it off for teleports and instant movement
LayerThe layer to create copies on for other players. Empty β€” the first layer

Object variables​

The Multiplayer β–Έ Sync variable action adds an instance variable to synchronization. The variable is picked from a dropdown β€” no typos possible, and the event updates itself if the variable is renamed.

Enable Interpolate only for numbers that should change smoothly. Leave strings and "instant" values (health, item ID) without interpolation.

β†’System
On start of layout
GP_Channels
Multiplayer: Sync variable hp of MP_player (interpolate: False)
GP_Channels
Multiplayer: Sync variable weapon of MP_player (interpolate: False)
GP_Channels
Multiplayer: Sync variable skin of MP_player (interpolate: False)
LegendSystemGP_ChannelsMP_player

Variables are synchronized automatically in both directions: you change a variable on your object β€” it updates on your proxy for everyone else.

Appearance, animations and mesh​

Three extra actions extend synchronization of a registered object:

  • Sync appearance β€” checkboxes: blend mode, color, flipped, mirrored, sampling, opacity, visibility.
  • Sync animations β€” checkboxes: animation name, frame, speed, repeat-to frame. In most games the name and speed are enough β€” sync the frame only if you need exact frame-by-frame matching.
  • Sync mesh β€” the mesh grid size and all its points (position and texture coordinates).
β†’System
On start of layout
GP_Channels
Multiplayer: Sync appearance of MP_player (blend: False, color: False, flipped: False, mirrored: True, sampling: False, opacity: True, visible: True)
GP_Channels
Multiplayer: Sync animations of MP_player (name: True, frame: False, speed: True, repeat-to: False)
LegendSystemGP_ChannelsMP_player
Sync mesh and traffic

The amount of mesh data grows with the number of points: each point is 4 numbers sent every tick when changed. Use small meshes (e.g. 4Γ—4) and only where you really need them.

Your instance and other players' proxies​

When auto-sync starts, the plugin binds the first existing instance of each player object as your local one. If there are several instances or the object is created later, bind it explicitly with the Bind local instance action (for example, after switching to a new layout).

You can tell your own player apart from proxies by UID using the MultiplayerLocalPlayerUID expression:

MP_player
Pick instance with UID GamePushChannels.MultiplayerLocalPlayerUID("player")
System
Scroll to MP_player
LegendMP_playerSystem
  • MP_player β€” Object Sprite (Sprite)
  • Systemβ€ΊConstruct 3 System object

Use the same trick to apply controls only to your own character.

The plugin creates and destroys other players' proxies by itself. If you need to attach extra logic to them (a nickname, a health bar), use the triggers:

β†’GP_Channels
Multiplayer: On player proxy created
+ Add action
MP_player
Pick instance with UID GamePushChannels.MultiplayerProxyUID
System
Create object NameTag on layer "ui" at (MP_player.X, MP_player.Y - 40)
LegendGP_ChannelsMP_playerSystemNameTag
  • GP_Channelsβ€ΊGamePush Channels plugin
  • MP_player β€” Object Sprite (Sprite)
  • Systemβ€ΊConstruct 3 System object
  • NameTag β€” Object Text (Text)

Inside the trigger the MultiplayerProxyPlayerID, MultiplayerProxyTag and MultiplayerProxyUID expressions are available.

Spawning players at spawn points​

Place MP_SpawnPoint objects on the layout and position players on them at the start. Each player sets the position themselves β€” for their own local instance:

β†’System
On start of layout
+ Add action
MP_SpawnPoint
Pick random instance
MP_player
Pick instance with UID GamePushChannels.MultiplayerLocalPlayerUID("player")
MP_player
Set position to (MP_SpawnPoint.X, MP_SpawnPoint.Y)
LegendSystemMP_SpawnPointMP_player
  • Systemβ€ΊConstruct 3 System object
  • MP_SpawnPoint β€” Object Sprite (Sprite)
  • MP_player β€” Object Sprite (Sprite)

Synchronizing the environment from the host​

World objects β€” enemies, items, crates β€” are registered with the Global state scope. Only the host controls them:

  • instances placed on the layout automatically become global state entities on the host;
  • on peers, the pre-placed copies are replaced with synchronized entities β€” no duplicates;
  • objects the host creates during the game automatically appear for everyone;
  • objects the host destroys disappear for everyone;
  • on host migration, the new host picks up the latest world snapshot and continues the simulation β€” nothing to do on your side.
β†’System
On start of layout
GP_Channels
Multiplayer: Register MP_enemy as "enemy" in Global state state (transform: Position (x, y), interpolate: True, layer: "game")
GP_Channels
Multiplayer: Sync variable hp of MP_enemy (interpolate: False)
GP_Channels
Multiplayer: Register MP_armor as "armor" in Global state state (transform: Position (x, y), interpolate: True, layer: "game")
LegendSystemGP_ChannelsMP_enemyMP_armor
  • Systemβ€ΊConstruct 3 System object
  • GP_Channelsβ€ΊGamePush Channels plugin
  • MP_enemy β€” Object Sprite (Sprite)
  • MP_armor β€” Object Sprite (Sprite)

Run world logic (enemy AI, moving platforms) on the host only β€” wrap it in the Is host condition:

GP_Channels
Multiplayer: Is host
+ Add action
System
For each MP_enemy
MP_enemy
Simulate Platform pressing Left
LegendGP_ChannelsSystemMP_enemy

These events simply won't run on peers, and enemy positions arrive over the network already interpolated.

React to entities appearing and disappearing with the triggers:

β†’GP_Channels
Multiplayer: On entity created
+ Add action
MP_enemy
Pick instance with UID GamePushChannels.MultiplayerEntityUID
MP_enemy
Start Fade behavior
LegendGP_ChannelsMP_enemy

Messages: shooting, item pickups, deaths​

One-off events don't need state β€” use Send message and the On message condition. Send parameters:

  • Event name β€” the event name, e.g. "shot";
  • Data β€” a data string (join values with & and a separator);
  • Target player ID β€” 0 for everyone, a player ID for a specific recipient, MultiplayerHostPlayerID β€” host only;
  • Echo β€” whether to receive your own message (handy for handling a shot with a single event for everyone, including the shooter).

Shooting​

The shooter sends coordinates and an angle, everyone (including the shooter, echo: Yes) creates a bullet:

β†’Mouse
On Left button Clicked
+ Add action
MP_player
Pick instance with UID GamePushChannels.MultiplayerLocalPlayerUID("player")
GP_Channels
Multiplayer: Send "shot" with data MP_player.X & ":" & MP_player.Y & ":" & MP_player.Angle to 0, echo Yes
β†’GP_Channels
Multiplayer: On message "shot"
System
Create object Bullet on layer "game" at (float(tokenat(GamePushChannels.MultiplayerCustomEventData, 0, ":")), float(tokenat(GamePushChannels.MultiplayerCustomEventData, 1, ":")))
Bullet
Set Bullet angle of motion to float(tokenat(GamePushChannels.MultiplayerCustomEventData, 2, ":")) degrees
LegendMouseMP_playerGP_ChannelsSystemBullet
  • Mouse β€” Object Mouse (Mouse)
  • MP_player β€” Object Sprite (Sprite)
  • GP_Channelsβ€ΊGamePush Channels plugin
  • Systemβ€ΊConstruct 3 System object
  • Bullet β€” Object Sprite (Sprite)

Who fired is determined by the MultiplayerCustomEventSenderID expression. The bullet itself doesn't need to be synchronized β€” every client simulates its flight locally, and the damage is authoritatively resolved by the target's owner or the host.

Item pickup​

An item is a global entity, so only the host can destroy it. The player sends the host a request with the item's netId, the host destroys the item, and it disappears for everyone:

β†’MP_player
On collision with MP_armor
System
Compare: MP_player.UID = GamePushChannels.MultiplayerLocalPlayerUID("player")
GP_Channels
Multiplayer: Send "pickupArmor" with data GamePushChannels.MultiplayerEntityIdForUID(MP_armor.UID) to GamePushChannels.MultiplayerHostPlayerID, echo Yes
β†’GP_Channels
Multiplayer: On message "pickupArmor"
GP_Channels
Multiplayer: Is host
+ Add action
MP_armor
Pick instance with UID GamePushChannels.MultiplayerEntityUIDForId(GamePushChannels.MultiplayerCustomEventData)
MP_armor
Destroy
LegendMP_playerMP_armorSystemGP_Channels
  • MP_player β€” Object Sprite (Sprite)
  • MP_armor β€” Object Sprite (Sprite)
  • Systemβ€ΊConstruct 3 System object
  • GP_Channelsβ€ΊGamePush Channels plugin

echo: Yes covers the case when the picking player is the host themselves.

Death and respawn​

Each player is authoritative for their own hp. When health runs out β€” notify the others and respawn:

MP_player
Pick instance with UID GamePushChannels.MultiplayerLocalPlayerUID("player")
MP_player
hp ≀ 0
GP_Channels
Multiplayer: Send "death" with data "" to 0, echo No
MP_player
Set hp to 100
MP_player
Set position to (MP_SpawnPoint.X, MP_SpawnPoint.Y)
β†’GP_Channels
Multiplayer: On message "death"
+ Add action
MP_player
Pick instance with UID GamePushChannels.MultiplayerPlayerProxyUID(GamePushChannels.MultiplayerCustomEventSenderID, "player")
System
Create object DeathFX on layer "game" at (MP_player.X, MP_player.Y)
LegendMP_playerGP_ChannelsMP_SpawnPointSystemDeathFX
  • MP_player β€” Object Sprite (Sprite)
  • GP_Channelsβ€ΊGamePush Channels plugin
  • MP_SpawnPoint β€” Object Sprite (Sprite)
  • Systemβ€ΊConstruct 3 System object
  • DeathFX β€” Object Sprite (Sprite)

Reference: Actions​

ActionWhat it doesUsage exampleImportant notes
Multiplayer β–Έ ConnectConnect to a multiplayer room by channel IDIn the lobby after joining a channel: Connect to channel Channels.ChannelIDThe player must be a channel member. Host selection takes a few seconds after connecting
Multiplayer β–Έ DisconnectDisconnect from the roomWhen leaving to the menuOther players' proxies will be destroyed
Multiplayer β–Έ Set modeChoose the sync mode: Fast (60 tick) or Smooth (20 tick)Fast β€” for shooters and racing, Smooth β€” for strategies and slower gamesSet it before Start auto-sync. Default is Smooth
Multiplayer β–Έ Send messageSend an event to other players: name, data, target (0 β€” everyone, ID β€” a specific player), echoA shot: Send "shot" with data X & ":" & Y to 0, echo YesDon't send constantly changing coordinates via messages β€” that's what state is for
Multiplayer β–Έ Register object for syncLink an object type to sync: tag, scope (Player/Global state), transform preset, interpolation, layerRegister MP_player as "player" in Player stateCall before Start auto-sync. The tag must be unique
Multiplayer β–Έ Sync variableAdd an instance variable to sync (picked from a dropdown)Sync variable hp of MP_player (interpolate: False)After Register object, before Start auto-sync. Interpolate only smoothly changing numbers
Multiplayer β–Έ Sync appearanceSync appearance: blend mode, color, flipped, mirrored, sampling, opacity, visibilityHide a picked-up weapon for everyone: Opacity and Visible checkboxesOpacity and visibility are already part of the Full transform preset β€” don't duplicate them
Multiplayer β–Έ Sync animationsSync sprite animation: name, frame, speed, repeat-to frameRun/jump animations of a character: Name and Speed checkboxesEnable the frame only for exact frame-by-frame sync β€” it's extra traffic
Multiplayer β–Έ Sync meshSync mesh distortion: grid size and all pointsA waving flag, deformable jellyTraffic grows with mesh size β€” keep meshes small
Multiplayer β–Έ Start auto-syncBuild schemas from registered objects and start automatic syncThe last action in the setup block on layout startNew Register ... calls after the start won't make it into the schema
Multiplayer β–Έ Stop auto-syncStop the automatic sync loopsWhen leaving the game layout to the menuThe room connection is kept
Multiplayer β–Έ Bind local instanceExplicitly bind the picked instance as the player's local objectAfter switching to a new layout, or when there are several instancesBy default the first instance is bound automatically

Reference: Conditions​

ConditionTypeWhat it checks / when it firesImportant notes
Multiplayer β–Έ On connectTriggerConnected to the roomThe host may not be selected yet at this point
Multiplayer β–Έ On connect errorTriggerConnection failedThe error text is in the GamePushChannels.LastError expression
Multiplayer β–Έ On disconnectTriggerDisconnected from the room
Multiplayer β–Έ On disconnect errorTriggerDisconnect failed
Multiplayer β–Έ Is connectedRegularThe player is connected to the room
Multiplayer β–Έ Is hostRegularThe current player is the hostUse it as a gate for AI and world logic. The role can change at any moment
Multiplayer β–Έ On became hostTriggerThe current player became the hostFires both on the initial host selection and on migration
Multiplayer β–Έ On became peerTriggerThe current player became a peer
Multiplayer β–Έ On host migratedTriggerThe room host changedAuto-sync survives migration by itself; usually nothing to do
Multiplayer β–Έ On player joinedTriggerA player joined the roomThe new player's proxy is created automatically once their state arrives
Multiplayer β–Έ On player leftTriggerA player left the roomTheir proxy is destroyed automatically
Multiplayer β–Έ On players updatedTriggerThe connected players list updatedFor player lists in the UI
Multiplayer β–Έ Each connected playerLoopLoop through connected playersMultiplayerCurPlayerID, MultiplayerCurPlayerPing etc. are available inside
Multiplayer β–Έ On messageTriggerA message was received. Filter by event name; empty name β€” any messageData β€” MultiplayerCustomEventData, sender β€” MultiplayerCustomEventSenderID
Multiplayer β–Έ On tickTriggerEvery SDK network tick (20 or 60 per second)For fixed-step game logic. Delta β€” MultiplayerLastTickDelta
Multiplayer β–Έ On send state errorTriggerSending the state failed
Multiplayer β–Έ Is auto-sync activeRegularAuto-sync is running (Start auto-sync was executed)
Multiplayer β–Έ On entity createdTriggerA global entity appeared (created by the host)Pick the instance by MultiplayerEntityUID
Multiplayer β–Έ On entity destroyedTriggerA global entity was removedThe instance is destroyed right after the trigger β€” create your effects in time
Multiplayer β–Έ On player proxy createdTriggerAnother player's object was materialized locallyPick by MultiplayerProxyUID; the player ID β€” MultiplayerProxyPlayerID
Multiplayer β–Έ On player proxy destroyedTriggerAnother player's object is about to be destroyed
Multiplayer β–Έ On global state updatedTriggerA global state update arrived (peers only)Advanced scenario: auto-sync already applies the state to instances by itself
Multiplayer β–Έ For each entityLoopLoop through synced entities with a tagMultiplayerEntityId, MultiplayerEntityUID, MultiplayerEntityField are available inside

Reference: Expressions​

All expressions are called via the plugin object, e.g. GamePushChannels.MultiplayerMyPlayerID.

Connection and players​

ExpressionReturnsExample
MultiplayerMyPlayerIDCurrent player IDA label above your own character
MultiplayerHostPlayerIDID of the current host (0 if the host is not selected)Send message target to message the host only
MultiplayerConnectedPlayersCountNumber of connected players"Players: " & GamePushChannels.MultiplayerConnectedPlayersCount
MultiplayerConnectedPlayersAsJSONConnected players array as a JSON stringFor parsing with the JSON object
MultiplayerNetworkPingCurrent ping in msConnection quality indicator
MultiplayerTickRateCurrent tick rate (20 or 60)
MultiplayerBufferSizeInterpolation buffer size (number of snapshots)Debugging
MultiplayerBufferDelayInterpolation buffer delay in msDebugging
MultiplayerLastTickDeltaLast tick delta in msFixed-step movement inside On tick

Inside the Each connected player loop​

ExpressionReturns
MultiplayerCurPlayerIDID of the player in the loop
MultiplayerCurPlayerState("key")A state field of the player in the loop
MultiplayerCurPlayerIsHost1 if the player in the loop is the host, otherwise 0
MultiplayerCurPlayerPingPing of the player in the loop, ms
MultiplayerCurPlayerConnectionStabilityConnection stability 0–1
MultiplayerCurPlayerSessionDurationThe player's time in the room, ms

State​

ExpressionReturnsExample
MultiplayerMyStateCurrent player state as a JSON stringDebugging
MultiplayerMyStateKey("key")A field of your own state. Supports dot pathsMultiplayerMyStateKey("hp")
MultiplayerPlayerState(id, "key")A state field of any player by IDMultiplayerPlayerState(2, "hp")
MultiplayerPlayersStateAsJSONFull players state map as a JSON stringDebugging
MultiplayerGlobalStateAsJSONFull global state as a JSON stringDebugging

Messages (inside On message)​

ExpressionReturns
MultiplayerCustomEventNameName of the last message
MultiplayerCustomEventSenderIDSender ID of the last message
MultiplayerCustomEventDataData of the last message

Entities and proxies​

ExpressionReturnsExample
MultiplayerEntityIdnetId of the current entity (in For each entity / On entity created)
MultiplayerEntityUIDUID of the current entity instanceWith the Pick instance with UID condition
MultiplayerEntityTagTag of the current entity
MultiplayerEntityTypeObject type name of the current entity
MultiplayerEntityField("key")A field of the current entityMultiplayerEntityField("hp")
MultiplayerEntityIdForUID(uid)netId of an entity by instance UIDData for an item pickup request
MultiplayerEntityUIDForId("netId")Instance UID by netIdPick an item on the host by the netId from a message
MultiplayerEntityCount("tag")Number of entities with a tagMultiplayerEntityCount("enemy")
MultiplayerLocalPlayerUID("tag")UID of your local instance for a tagTell your own character apart: Pick instance with UID
MultiplayerPlayerProxyUID(playerId, "tag")UID of a player's proxy instanceFind a player's character by their ID
MultiplayerProxyPlayerIDPlayer ID of the current proxy (in On player proxy created/destroyed)
MultiplayerProxyTagTag of the current proxy
MultiplayerProxyUIDUID of the current proxyWith the Pick instance with UID condition

Stay in Touch​

Other documents of this chapter are available here. To get started with GamePush, see the Tutorials chapter.

GamePush Community Telegram: @gs_community.

For your suggestions e-mail: official@gamepush.com

We Wish you Success!