[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/Welcome.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/Welcome.md "View source of this page")

# Welcome to the Upside Engine Tutorial Section!¶

## Explore, Learn, and Create!¶

We are thrilled to welcome you to the tutorial section of Upside Engine, our advanced yet easy-to-use 2D engine for Roblox. Here, you'll find a series of detailed guides designed to help you make the most powerful 2d games on the platform.

## Community Forum¶

Join [our community](https://discord.com/invite/pE3svUvmnu) of passionate developers. Here, you can share your projects, receive feedback, collaborate on new concepts, and solve doubts with the help of other members and experts in Upside Engine.

## What You'll Find in This Section¶

  * ### Step-by-Step Tutorials¶

Our tutorials are crafted to guide you from the basics to the most advanced techniques in 2D game development using Upside Engine in Roblox. Each tutorial is structured to provide progressive learning, with practical examples and clear explanations.

  * ### User Graphic Interface¶

Our user interface is designed to be intuitive and user-friendly, making it easy for developers to create their games. Learn how to navigate and use the Upside Engine UI to create the most fun experiences.

  * ### Code Examples¶

We include Luau code examples that you can use as references or starting points for your own projects. These examples will show you how to implement various functionalities, from basic physics to advanced visual effects.

Thank you for joining us on this exciting adventure! We look forward to seeing the amazing games you create and hope you enjoy using Upside Engine as much as we enjoy developing it.

Happy Developing!

##### — The Upside Engine Development Team¶

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/datatypes/UpsideEngineInput.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/datatypes/UpsideEngineInput.md "View source of this page")

# UpsideEngineInput¶

An UpsideEngineInput represents a single user input, such as joystick movement, key presses, mobile actions and more

# Properties¶

        Action -> string,
        Position -> Vector2,
        KeyCode -> KeyCode,

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Sound.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Sound.md "View source of this page")

# [Extended from BaseObject](https://notreux.github.io/UpsideEngine/documentation/autogen/BaseObject.html) Sound¶

This class is used to play sounds

# Properties¶

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Range¶

This is the SoundEnvironment of the scene

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) MaxVolume¶

This is the ParticleEnvironment of the scene

## [boolean](https://notreux.github.io/UpsideEngine/documentation/autogen/boolean.md) DistanceFading¶

The volume will depend on the player distance

## [Character](https://notreux.github.io/UpsideEngine/documentation/autogen/Character.html) Subject¶

This table stores all the objects in the scene

## [SubjectDestroyConnection](https://notreux.github.io/UpsideEngine/documentation/autogen/SubjectDestroyConnection.md) SubjectDestroyConnection¶

# Methods¶

## [void](https://create.roblox.com/docs/scripting/luau/nil) SetSubject(`subject: Character, useSceneSoundGroup: boolean?`)¶

Sets the provided character as subject, if is provided a sound group, this will be the new sound group of the sound

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/BaseObject.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/BaseObject.md "View source of this page")

# [Extended from EventEmitter](https://notreux.github.io/UpsideEngine/documentation/autogen/EventEmitter.html) BaseObject¶

This class is the base class of the majority of classes

# Properties¶

## [Instance](https://notreux.github.io/UpsideEngine/documentation/autogen/Instance.md) Instance¶

The object instance

## [string](https://notreux.github.io/UpsideEngine/documentation/autogen/string.md) Scene¶

The object scene ID

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Tags¶

This table stores all the tags of the object

# Methods¶

## [void](https://create.roblox.com/docs/scripting/luau/nil) SetScene(`scene: Scene`)¶

Sets the object scene

## [void](https://create.roblox.com/docs/scripting/luau/nil) AddTag(`tag: string`)¶

Adds a tag to the object

## [boolean](https://create.roblox.com/docs/scripting/luau/booleans) HasTag(`tag: string`)¶

Checks if the object contain the specified tag

## [void](https://create.roblox.com/docs/scripting/luau/nil) RemoveTag(`tag: string`)¶

Removes a tag from the object

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/AuthorityService.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/AuthorityService.md "View source of this page")

# [Extended from EventEmitter](https://notreux.github.io/UpsideEngine/documentation/autogen/EventEmitter.html) AuthorityService¶

This service manages authority assignments for objects in the game. Authority determines which client or server has control over specific objects. This is essential for network synchronization and preventing conflicts in multiplayer environments. Only the server can manage authority assignments.

    Example usage:

`lua -- Set authority for an object to server AuthorityService:SetAuthority(myObject, "Server") -- Check authority local authority = AuthorityService:GetAuthority(myObject) if authority then print("Authority is assigned to:", authority) end`

# Properties¶

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) AuthorityAssignments¶

A table that stores the authority assignments for objects, indexed by object Id

# Methods¶

## [void](https://notreux.github.io/UpsideEngine/documentation/autogen/void.md) SetAuthority(`object: BaseObject, authorityType: AuthorityType`)¶

Assigns authority for a specific object to a given authority type. This determines who has control over the object's state and behavior in the networked environment. AuthorityType can be "Client" or "Server".

## [AuthorityType](https://notreux.github.io/UpsideEngine/documentation/autogen/AuthorityType.md) GetAuthority(`object: BaseObject`)¶

Retrieves the current authority assignment for a specific object. Returns the authority type that has control over the object, defaulting to "Server" authority when not explicitly set.

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/replication-guide/ClientSetup.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/replication-guide/ClientSetup.md "View source of this page")

# Client Setup¶

Now that you understand how replication works, let's set up the client side of your multiplayer game.

## Basic client structure¶

Here's a complete client setup with all the essentials:

Client Script

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")

    local packages = ReplicatedStorage.Packages
    local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")

    -- Load Upside Engine
    local UpsideEngine = require(packages.UpsideEngine)

    -- Get required services
    local networkingService = UpsideEngine.GetService("NetworkingService")
    local crossPlatformService = UpsideEngine.GetService("CrossPlatformService")

    -- Create the screen container
    local screen = Instance.new("ScreenGui")
    screen.Name = "MultiplayerGame"
    screen.IgnoreGuiInset = true
    screen.Parent = playerGui

    -- Create and configure the scene
    local scene = UpsideEngine.new("Scene")
    scene.Instance.Parent = screen
    scene:SetName("GameScene")
    scene:Enable()

## Creating the game environment¶

Let's create a simple environment for our game:

Create Terrain

    -- Create a terrain/background
    local terrain = UpsideEngine.new("StaticObject")
    terrain:SetScene(scene)

    local terrainInstance = terrain.Instance
    terrainInstance.Position = UDim2.fromOffset(0, 0)
    terrainInstance.Size = UDim2.fromOffset(800, 800)
    terrainInstance.Image = "rbxassetid://yourTerrainId"
    terrainInstance.ZIndex = 1

## Creating your player character¶

Now let's create the player character that will be replicated:

Create Character

    local character = UpsideEngine.new("Character")
    character:SetScene(scene)

    -- Basic properties
    local size = 256
    character.Instance.Position = UDim2.fromOffset(400, 300)
    character.Instance.Size = UDim2.fromOffset(size, size)
    character.Instance.ImageRectSize = Vector2.new(64, 64)
    character.Instance.ZIndex = 100

    -- Physics properties
    character.Mass = 0
    character.WalkSpeed = 200
    character.SecondsPerFrame = 1 / 10

    -- Set up sprite sheets for different animations
    character:SetSpriteSheet("idle_down", "rbxassetid://yourIdleDownId", Vector2.new(4, 1))
    character:SetSpriteSheet("walk_down", "rbxassetid://yourWalkDownId", Vector2.new(4, 1))
    character:SetSpriteSheet("idle_up", "rbxassetid://yourIdleUpId", Vector2.new(4, 1))
    character:SetSpriteSheet("walk_up", "rbxassetid://yourWalkUpId", Vector2.new(4, 1))
    -- Add more sprite sheets as needed...

    -- Start with an idle animation
    character:Play("idle_down")

    -- Configure player controls and camera
    crossPlatformService.SideView = false
    crossPlatformService:SetPlayerCharacter(character)
    scene.Camera:SetSubject(character)

## Setting up replication¶

Now comes the important part - telling Upside Engine to replicate this character:

Enable Replication

    -- Start replicating the character
    -- This will send replication data to the server
    networkingService:ReplicateOnChange(character)

What happens when you call `ReplicateOnChange()`?

  1. The object's initial state is sent to the server as a `ReplicationRequest`
  2. The server must accept it with `request:Accept()` to create the object
  3. The server should grant authority with `SetAuthority(object, "Client")`
  4. Once authority is granted, every property change sends a new `ReplicationRequest`
  5. The server must call `Accept()` on each request to apply the changes
  6. You don't need to manually trigger updates - they happen automatically!

## Handling other players' characters¶

When other players join, their characters need to be displayed on your client:

Handle Build Event

    -- The Build event fires when an object from another player is created on your client
    networkingService:On("Build", function(object)
        -- Add the object to your scene so it becomes visible
        object:SetScene(scene)

        -- You can also do additional setup here if needed
        -- For example, you might want to set a specific ZIndex or configure visuals
    end)

Important notes about the Build event

  * Fires once per object when it's first created on your client
  * The object is already fully created, you just need to add it to your scene
  * Some properties might not be replicated (this depends on the property type)

## Complete client example¶

Here's a complete working example:

Complete Client Script

    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local packages = ReplicatedStorage.Packages
    local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")

    local UpsideEngine = require(packages.UpsideEngine)
    local networkingService = UpsideEngine.GetService("NetworkingService")
    local crossPlatformService = UpsideEngine.GetService("CrossPlatformService")

    -- Setup screen and scene
    local screen = Instance.new("ScreenGui")
    screen.Name = "MultiplayerGame"
    screen.IgnoreGuiInset = true
    screen.Parent = playerGui

    local scene = UpsideEngine.new("Scene")
    scene.Instance.Parent = screen
    scene:Enable()

    -- Create environment
    local terrain = UpsideEngine.new("StaticObject")
    terrain:SetScene(scene)
    terrain.Instance.Position = UDim2.fromOffset(0, 0)
    terrain.Instance.Size = UDim2.fromOffset(800, 800)
    terrain.Instance.Image = "rbxassetid://yourTerrainId"

    -- Create player character
    local character = UpsideEngine.new("Character")
    character:SetScene(scene)
    character.Instance.Position = UDim2.fromOffset(400, 300)
    character.Instance.Size = UDim2.fromOffset(256, 256)
    character.Instance.ImageRectSize = Vector2.new(64, 64)
    character.Mass = 0
    character.WalkSpeed = 200

    character:SetSpriteSheet("idle", "rbxassetid://yourIdleId", Vector2.new(4, 1))
    character:Play("idle")

    crossPlatformService.SideView = false
    crossPlatformService:SetPlayerCharacter(character)
    scene.Camera:SetSubject(character)

    -- Start replication
    networkingService:ReplicateOnChange(character)

    -- Handle other players' characters
    networkingService:On("Build", function(object)
        object:SetScene(scene)
    end)

## What gets replicated?¶

Not all properties are replicated automatically. The replication system only syncs properties marked as `Replicable`. Common replicated properties include:

  * Position
  * Size
  * Rotation
  * Velocity
  * Active sprite sheet
  * Custom properties you define

Properties like visual effects, some physics properties, or local-only data might not be replicated.

## Best practices¶

Best Practices

  1. **Create objects on the client**: Each player should create their own character locally
  2. **Call ReplicateOnChange() immediately**: Start replication as soon as the object is ready
  3. **Keep the Build handler simple**: Just add objects to the scene, don't do heavy processing
  4. **Don't replicate everything**: Only replicate objects that other players need to see

**Next:** [Server Setup](https://notreux.github.io/UpsideEngine/tutorials/replication-guide/ServerSetup.html)

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/get-started/Music.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/get-started/Music.md "View source of this page")

# Music¶

We'll create a new script called `radio.luau` in the `src/client` folder. We are going to add inmersive background music, for this we are going to create a Sprite that represents a radio that is going to have a song gets louder when you get closer to it

    -- Get necessary services
    local replicatedStorage = game:GetService("ReplicatedStorage")
    local packages = replicatedStorage.packages

    -- Require the upside engine module
    local upsideEngine = require(packages.UpsideEngine)
    local sceneManager = upsideEngine.GetService("SceneManager")
    local scene = sceneManager:FindByName("MyFirstScene")

    -- Create the radio
    local radio = upsideEngine.new("Sprite")
    radio.Instance.ImageRectSize = Vector2.new(37, 64)
    radio.TrackCollisions = false
    radio:SetScene(scene)
    radio:SetSpriteSheet("default", "rbxassetid://12908065852", Vector2.new(14, 1)) -- We pass 14, 1 to say we have 1 row and 14 columns
    radio:Play("default")

    local radioInstance = radio.Instance
    radioInstance.Size = UDim2.fromOffset(128, 128)
    radioInstance.Position = UDim2.fromOffset(1540, 865)
    radioInstance.ZIndex = 1

    -- Create a new sound
    local music = upsideEngine.new("Sound")
    music.Instance.SoundId = "rbxassetid://1844102827"
    music.Instance:Play()
    music.Range = 1500
    music:SetScene(scene)
    music:SetSubject(radio)

Bug

Remember that you are using a module script, don't forget to return a value at the end of the script!

    return {}

You can view and edit the finished project [here](https://www.roblox.com/games/13021482729/Upside-Engine-Getting-Started).

##### Great job! You have successfully learned the fundamentals of the Upside Engine framework 🎉🎉
The following pages are dedicated to interesting information that you may want to know¶

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Request.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Request.md "View source of this page")

# [Extended from EventEmitter](https://notreux.github.io/UpsideEngine/documentation/autogen/EventEmitter.html) Request¶

This class is used for the client replication

# Properties¶

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Content¶

The content of the request

## [string](https://notreux.github.io/UpsideEngine/documentation/autogen/string.md) ClientId¶

The UserId of the client which sent the request if exists

# Methods¶

## [void](https://create.roblox.com/docs/scripting/luau/nil) Send(`content: Dictionary<string, any>`)¶

Sends the request to the server

## [void](https://create.roblox.com/docs/scripting/luau/nil) Approve()¶

The request is approved and its replicated all the clients

## [BaseObject](https://notreux.github.io/UpsideEngine/documentation/autogen/BaseObject.html) Accept()¶

Accepts the request and builds the object to be replicated

## [Player](https://create.roblox.com/docs/reference/engine/classes/Player) GetClient()¶

Returns the player which sent the request

# Events¶

Name | Description
Build | Params -> [BaseObject](https://notreux.github.io/UpsideEngine/documentation/autogen/BaseObject.html)
Fired when a request is accepted and the object to be replicated is being built

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Connection.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Connection.md "View source of this page")

# [Extended from BaseClass](https://notreux.github.io/UpsideEngine/documentation/autogen/BaseClass.md) Connection¶

This class is used in the event emitter class, its used to manage a listener

# Properties¶

## [boolean](https://notreux.github.io/UpsideEngine/documentation/autogen/boolean.md) Active¶

Defines if the connection is active or not

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) EventId¶

The id of the linked event

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Event¶

# Methods¶

## [thread?](https://create.roblox.com/docs/reference/engine/libraries/coroutine) Wait(`seconds: number?`)¶

Wait until the event gets fired, if seconds were specified, once the specified seconds have elapsed, it will stop waiting

## [void](https://create.roblox.com/docs/scripting/luau/nil) Disconnect()¶

Deletes the connection and the listener

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Particle.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Particle.md "View source of this page")

# [Extended from BaseObject](https://notreux.github.io/UpsideEngine/documentation/autogen/BaseObject.html) Particle¶

Warning

Currently it's recommended to use sprites to make particles/vfx, this class is in experimental state and can change a lot

This class is used for vfx

# Properties¶

## [Vector2](https://notreux.github.io/UpsideEngine/documentation/autogen/Vector2.md) Angle¶

Depending on the value the particles will be more dispersed

## [TweenInfo](https://notreux.github.io/UpsideEngine/documentation/autogen/TweenInfo.md) Info¶

Is the tween info of the tween which is going to be used to move the particles

## [boolean](https://notreux.github.io/UpsideEngine/documentation/autogen/boolean.md) Enabled¶

When its enabled new particles can be emitted

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Rotation¶

Rotation in degrees applied to the emission direction (offset for theta)

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) MaxRate¶

Is the maximum amount of particles that can exist at the same time

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Units¶

Is the amount of particles that are existing at this moment

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Range¶

Is the distance that can be traveled by each particle

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) LifeTime¶

The number of seconds the particle will be active before being destroyed

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Clock¶

Used for internal purposes

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Rate¶

Is the amount of particles which is going to be generated

## [Character](https://notreux.github.io/UpsideEngine/documentation/autogen/Character.html) Subject¶

The subject which is going to be the center of emission of the particle, a character, sprite, etc...

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Properties¶

Is a table with the initial properties of the particle which is going to be generated

        ["Image"]: string,

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Goals¶

Is a table with the goals of the particles

        ["Size"]: UDim2,
        ["ImageTransparency"]: number,

# Methods¶

## [void](https://notreux.github.io/UpsideEngine/documentation/autogen/void.md) SetSubject(`subject: Character`)¶

Sets the subject property

## [void](https://notreux.github.io/UpsideEngine/documentation/autogen/void.md) SetAngle(`Angle: Vector2`)¶

Sets the angle property of the particle

## [void](https://notreux.github.io/UpsideEngine/documentation/autogen/void.md) SetMaxRate(`maxRate: number`)¶

Sets the maximum amount of particles that can exist at the same time

## [void](https://notreux.github.io/UpsideEngine/documentation/autogen/void.md) SetRotation(`rotation: number`)¶

## [void](https://create.roblox.com/docs/scripting/luau/nil) Emit(`rate: number?`)¶

Emits the specified amount of particles, if none is provided then will use as amount the Rate property

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Fluid.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Fluid.md "View source of this page")

# [Extended from PhysicalObject](https://notreux.github.io/UpsideEngine/documentation/autogen/PhysicalObject.html) Fluid¶

This class is used to simulate fluids

# Properties¶

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Density¶

Indicates how much dense the fluid should be. Higher values indicate a denser fluid, affecting buoyancy and movement of objects within the fluid

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Viscosity¶

Determines the flow resistance of the fluid. Higher values indicate a thicker fluid, affecting the speed at which objects can move through it

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) WavesAmplitude¶

Specifies the magnitude of the fluid waves. These waves are not visible but influence buoyancy and object stability.

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) WavesSpeed¶

Determines how fast the waves should move

# Methods¶

There is no methods for this class

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/SceneManager.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/SceneManager.md "View source of this page")

# [Extended from EventEmitter](https://notreux.github.io/UpsideEngine/documentation/autogen/EventEmitter.html) SceneManager¶

This class save and build the engine data

# Properties¶

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) Scenes¶

## [table](https://notreux.github.io/UpsideEngine/documentation/autogen/table.md) ActiveScenes¶

This dictionary stores all the active scenes

# Methods¶

## [Scene?](https://create.roblox.com/docs/scripting/luau/nil) FindByName(`name: string`)¶

Finds a scene by the name

## [Scene](https://notreux.github.io/UpsideEngine/documentation/autogen/Scene.html) Get(`Id: string`)¶

Returns the scene with the specified Id

# Events¶

Name | Description
SceneLoaded | Params -> [Scene](https://notreux.github.io/UpsideEngine/documentation/autogen/Scene.html)
Fired when a scene is loaded

SceneUnloaded | Params -> [Scene](https://notreux.github.io/UpsideEngine/documentation/autogen/Scene.html)
Fired when a scene is unloaded

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/get-started/GraphicInterface.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/get-started/GraphicInterface.md "View source of this page")

# Upside Engine Plugin¶

Upside Engine GUI is a powerful and accessible tool designed for creating 2D games in Roblox. This plugin provides an intuitive graphical interface that significantly simplifies the development process, allowing developers to focus on creativity and design.

[Get the Plugin](https://create.roblox.com/marketplace/asset/11645918543/Upside-Engine-GUI)

Warning

By playing this video, you agree youtube cookies

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/shader-guide/IntegratedFunctions.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/shader-guide/IntegratedFunctions.md "View source of this page")

# Integrated Functions¶

On this page, you will explore the functions that the Upside Engine passes as parameters to your shading function, enabling you to create interesting effects.

# Shader Functions¶

## [r, g, b, a](https://create.roblox.com/docs/luau/numbers) texture(`source: ImageLabel`, `position: Vector2`)¶

  * source  The image from which the specified pixel is read.

  * position  It's the position of the pixel to read.

  * returns  Returns the RGBA values of a pixel at a specified position and returns them as numbers.

## [x, y](https://create.roblox.com/docs/luau/numbers) rotate(`centre: Vector2`, `position: Vector2`, `degrees: number`)¶

  * centre  The position that will be used as the center when rotating the other pixels.

  * position  The position of the pixel to be rotated.

  * degrees  The number of degrees you want to rotate that pixel.

  * returns  Returns the rotated position of the pixel as numbers.

## Example¶

Let's apply the image from a camera to our water texture. Initially, the camera image will appear in the top left corner, but we can adjust its position to a more suitable location. To do this, we will modify its position by subtracting an offset, which is a Vector2 that allows us to move the camera image across the texture.

    local replicatedStorage = game:GetService("ReplicatedStorage")
    local packages = replicatedStorage:WaitForChild("packages")

    local upsideEngine = require(packages:WaitForChild("UpsideEngine"))
    local source = Instance.new("ImageLabel")
    source.Image = "rbxassetid://cameraId"

    @native
    local function shadingFunction(params: upsideEngine.ShadingParams)
        local offset = Vector2.new(60, 80)
        local position = Vector2.new(params.x, params.y)
        local r, g, b, a = params.texture(source, position - offset)

        params.red += r
        params.green += g
        params.blue += b
        params.opacity += a
    end

    return shadingFunction

As you can see, the camera image appears in the water:

But it has a bluish tint. This happens because we are blending the water's pixel colors with the camera's. If we want to display the pure camera pixels, we can do the following:

    local replicatedStorage = game:GetService("ReplicatedStorage")
    local packages = replicatedStorage:WaitForChild("packages")

    local upsideEngine = require(packages:WaitForChild("UpsideEngine"))
    local source = Instance.new("ImageLabel")
    source.Image = "rbxassetid://cameraId"

    @native
    local function shadingFunction(params: upsideEngine.ShadingParams)
        local offset = Vector2.new(60, 80)
        local position = Vector2.new(params.x, params.y)
        local r, g, b, a = params.texture(source, position - offset)

        if a == 0 then
            return
        end

        params.red = r
        params.green = g
        params.blue = b
        params.opacity = a
    end

    return shadingFunction

As you can see, now the camera looks normal:

Perfect, now let's use the `rotate` function. With this function, we'll rotate the water image around itself. Since the image resolution is 128x128, we'll use the position 64, 64 as the center. Additionally, we'll use `clock` to make our image rotate continuously, multiplying it by the desired rotational speed.

    local replicatedStorage = game:GetService("ReplicatedStorage")
    local packages = replicatedStorage:WaitForChild("packages")
    local upsideEngine = require(packages:WaitForChild("UpsideEngine"))

    @native
    local function shadingFunction(params: upsideEngine.ShadingParams)
        local clock = os.clock()
        local speed = 25

        local centre = Vector2.new(64, 64)
        local position = Vector2.new(params.x, params.y)

        local rx, ry = params.rotate(centre, position, clock * speed)
        params.x = rx
        params.y = ry
    end

    return shadingFunction

final result:

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/UpsideEngine.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/UpsideEngine.md "View source of this page")

# Upside Engine¶

On this page you will find information about the object that is returned when you access the engine. In this object you have access to all classes and services of the engine

Info

This object was extended from [EventEmitter](https://notreux.github.io/UpsideEngine/documentation/Classes/EventEmitter.md)

# Properties¶

## [string](https://create.roblox.com/docs/datatypes/string) _Version_¶

The current installed upside engine version

# Methods¶

## [void](https://notreux.github.io/UpsideEngine/documentation/UpsideEngine.html) `new(ClassName: string)`¶

This method creates any of these classes:

  * [Scene](https://notreux.github.io/UpsideEngine/documentation/autogen/Scene.html)
  * [Environment](https://notreux.github.io/UpsideEngine/documentation/autogen/Environment.html)
  * [Particle](https://notreux.github.io/UpsideEngine/documentation/autogen/Particle.html)
  * [Light](https://notreux.github.io/UpsideEngine/documentation/autogen/Light.html)
  * [Sound](https://notreux.github.io/UpsideEngine/documentation/autogen/Sound.html)
  * [Shader](https://notreux.github.io/UpsideEngine/documentation/autogen/Shader.html)
  * [StaticObject](https://notreux.github.io/UpsideEngine/documentation/autogen/StaticObject.html)
  * [PhysicalObject](https://notreux.github.io/UpsideEngine/documentation/autogen/PhysicalObject.html)
  * [Sprite](https://notreux.github.io/UpsideEngine/documentation/autogen/Sprite.html)
  * [Character](https://notreux.github.io/UpsideEngine/documentation/autogen/Character.html)
  * [Fluid](https://notreux.github.io/UpsideEngine/documentation/autogen/Fluid.html)
  * [Parallax](https://notreux.github.io/UpsideEngine/documentation/autogen/Parallax.html)

## [void](https://notreux.github.io/UpsideEngine/documentation/UpsideEngine.html) `GetService(ServiceName: string)`¶

This method returns any of these services:

  * [SceneManager](https://notreux.github.io/UpsideEngine/documentation/autogen/SceneManager.html)
  * [NetworkingService](https://notreux.github.io/UpsideEngine/documentation/autogen/NetworkingService.html)
  * [CrossPlatformService](https://notreux.github.io/UpsideEngine/documentation/autogen/CrossPlatformService.html)
  * [PluginSupportService](https://notreux.github.io/UpsideEngine/documentation/autogen/PluginSupportService.html)

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/documentation/autogen/Camera.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/documentation/autogen/Camera.md "View source of this page")

# [Extended from EventEmitter](https://notreux.github.io/UpsideEngine/documentation/autogen/EventEmitter.html) Camera¶

This class is included on every scene, it's used to move you around the scene

# Properties¶

## [UDim2](https://notreux.github.io/UpsideEngine/documentation/autogen/UDim2.md) OffsetPosition¶

This property serves to move as many pixels as you want the camera to adjust it to a desired position

## [Vector2](https://notreux.github.io/UpsideEngine/documentation/autogen/Vector2.md) LocalPosition¶

This property is used to move the camera internally

## [Vector2](https://notreux.github.io/UpsideEngine/documentation/autogen/Vector2.md) Limits¶

This property marks the limits to move the camera, for example, if you set `Vector2.new(0.5, 0.5)` the camera will move only when it reaches the limit

## [boolean](https://notreux.github.io/UpsideEngine/documentation/autogen/boolean.md) FollowSubject¶

This property defines if the camera is going to follow the defined subject

## [string](https://notreux.github.io/UpsideEngine/documentation/autogen/string.md) Scene¶

This is the Scene Id of the camera

## [number](https://notreux.github.io/UpsideEngine/documentation/autogen/number.md) Smoothness¶

This property defines the smoothness with which the camera will move, it only works in a range between 0 and 1

## [Character](https://notreux.github.io/UpsideEngine/documentation/autogen/Character.html) Subject¶

This property defines the object which is going to follow the camera

## [SubjectDestroyConnection](https://notreux.github.io/UpsideEngine/documentation/autogen/SubjectDestroyConnection.md) SubjectDestroyConnection¶

# Methods¶

## [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) GetPosition()¶

Gets the camera position

## [void](https://create.roblox.com/docs/scripting/luau/nil) SetPosition(`udim2: UDim2`)¶

Sets the camera position

## [void](https://create.roblox.com/docs/scripting/luau/nil) SetSubject(`subject: Character`)¶

Sets the camera subject

## [void](https://create.roblox.com/docs/scripting/luau/nil) LookTo(`object: StaticObject`)¶

Position the camera in a centered location relative to the provided object

# Events¶

There is no events for this class

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/index.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/index.md "View source of this page")

# Home

### Upside Engine

Upside Engine is the ultimate 2d framework to make your own 2d games!

[Get Started](https://notreux.github.io/UpsideEngine/tutorials/get-started/Installation.html) [Documentation](https://notreux.github.io/UpsideEngine/documentation/Welcome.html)

Upside Engine helps you creating immersive 2D lighting effects. With Upside Engine, you can easily create lighting environments for your roblox videogames.

Upside Engine Let's you create immersive 2D particles for your games. Whether you need fire, smoke, sparks, or magic effects, Upside Engine has you covered.

Upside Engine, gives you the freedom and flexibility to make your 2D games as fun and immersive, add physics to your character and bring it to life.

Back to top


[ ](https://github.com/notreux/UpsideEngine/edit/docs/docs/tutorials/replication-guide/PracticalExample.md "Edit this page") [ ](https://github.com/notreux/UpsideEngine/raw/docs/docs/tutorials/replication-guide/PracticalExample.md "View source of this page")

# Practical Example: Complete Multiplayer Game¶

Now let's put everything together and build a complete multiplayer game! This example will show you how to structure your code properly for a real project.

[Get Multiplayer Example ](https://github.com/notreux/UpsideEngineMultiplayerDemo)

## What you should see¶

When everything works correctly:

Expected Behavior

  * Each player sees their own character
  * Each player sees other players' characters
  * Movement is synchronized
  * Console shows "Player joined the game" messages
  * No errors in the output

## Extending this example¶

Now that you have a working multiplayer base, you can add:

Extension Ideas

  1. **Chat system**: Add a multiplayer chat using TextLabels
  2. **Items and inventory**: Replicate item pickups and inventory state
  3. **NPCs**: Create server-controlled characters with server authority
  4. **Animations**: Add more sprite sheets for different actions

## Summary¶

This example demonstrates:

What You Learned

  * Proper project structure with modules
  * Separation of client and server logic
  * Correct replication setup
  * Authority management
  * Player tracking and cleanup
  * Clean code organization

You now have a solid foundation for building multiplayer games with Upside Engine!

Congratulations! You've completed the replication guide!

You now understand:

  * How replication works in Upside Engine
  * The authority system and why it's important
  * How to set up both client and server properly
  * How to structure a multiplayer project
  * Common issues and how to solve them

Happy game development!

Back to top


## Quick Start

- [**Documentation**]
- [**Basic Use**]
- [**To-do**]
- **Getting Started**
    - [**Routes**]
    - [**Middleware**]
    - [**Hooks**]
- **Setup Guides**
    - [**Matter**]
    - [**Other**]
- [**Support & Contribution**]
- **Installation**
    - [**Wally**]
    - [**NPM**]

[**Documentation**]: https://yetanotherclown.github.io/YetAnotherNet/
[**Basic Use**]: #basic-usage
[**To-do**]: #to-do
[**Routes**]: https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/routes
[**Middleware**]: https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/middleware
[**Hooks**]: https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/hooks
[**Matter**]: https://yetanotherclown.github.io/YetAnotherNet/docs/setup/matter
[**Other**]: https://yetanotherclown.github.io/YetAnotherNet/docs/setup/other
[**Support & Contribution**]: #support-and-contribution
[**Wally**]: #wally
[**NPM**]: #npm-for-roblox-typescript

## About

YetAnotherNet is a Networking Library for Roblox, which wraps around Roblox's RemoteEvents to improve developer experience, offer efficient networking, and provide other tools, utilities, and features relating to Networking on Roblox.

### Features

- Hassle-free Buffer Compression
- Complete Typechecking & Intellisense
- No Overhead from RemoteEvents
- Ordered Networking
- Middleware
- Data-Driven Design
- Simple Integration & API
- Hot-reloading Support with [Rewire]

[Rewire]: https://github.com/sayhisam1/Rewire

### Data-driven by design

One thing that separates YetAnotherNet from other networking libraries on Roblox is this key design choice. YetAnotherNet was made to work with ECS, a Data-driven approach to game design, which has influenced the design of the library largely and makes it unique from the rest.

With inspiration from [Bevy_Renet], a Networking crate for use with the Bevy ECS in Rust, and another networking library on Roblox, [BridgeNet2], YetAnotherNet pushes to provide similar functionality to these two libraries for ECS on Roblox.

[Bevy_Renet]: https://github.com/lucaspoffo/renet/tree/master/bevy_renet
[BridgeNet2]: https://ffrostfall.github.io/BridgeNet2/

#### Why go for Data-Driven Design?

Data-driven design opposes Event-driven design, which is what you can commonly see on Roblox, such as RemoteEvents themselves. Event-driven design has it's woes, which is why we opt for ECS and generally Data-driven design.

Event-driven design is sensitive to ordering, this makes it difficult to know when you might get data from an event. To solve this, YetAnotherNet does not use Events, it encourages you to query and send data in an ordered manner frame-by-frame, preferably with ECS.

Since it's encouraged to use ECS with YetAnotherNet, though not required, we suggest reading [Matter — Why ECS?] by Evaera.

[Matter — Why ECS?]: https://matter-ecs.github.io/matter/docs/WhyECS

## To-do

Tasks to complete before version 1.0.0 is released.

- [x] Basic Functionality
- [x] Stable Core API
- [x] Strict Typing
- [x] Unreliable Channel
- [X] Middleware
- [X] Typescript Support
- [X] Automatic Buffer Compression
- [x] Unit + Integration Tests w/ Jest
- [ ] Rate limiting
- [ ] Debugger

Other Tasks
- [ ] Support more [Unsupported Datatypes] in internal Ser/Des library
- [ ] More extensive tests with Jest
- [ ] Minimal Example
- [ ] ECR Setup Guide
- [ ] ECR Example
- [ ] Flamework Binary Serializer Example
- [ ] Docs Page for Technical Details

[Unsupported Datatypes]: https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/buffer-compression#unsupported-datatypes

## Basic Usage

> [!TIP]
> See full documentation on [How to Use Routes] and [How to Setup with Matter] on the Documentation Site.

[How to Use Routes]: https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/routes
[How to Setup with Matter]: https://yetanotherclown.github.io/YetAnotherNet/docs/setup/matter

### Setup

Basic Setup for Routes
```lua
local YetAnotherNet = require("@packages/YetAnotherNet")

local Route = YetAnotherNet.Route
type Route<U...> = YetAnotherNet.Route<U...>;

-- You can specify the type(s) between the <> for Typechecking + Intellisense
local ExampleRoute: Route = Route.new()

return {
    ExampleRoute = ExampleRoute

### Example Use

Example of how to use YetAnotherNet in a [Matter] System
```lua
local routes = require("@shared/routes")
local ExampleRoute = routes.ExampleOne

local function exampleSystem(world)
    -- Query through every networking call that frame on the Server
    for i, player, ...data in ExampleRoute:query() do
        -- Do something
    end

    -- Query through every networking call that frame on the Client
    for i, _, ...data in ExampleRoute:query() do
        -- Do something
    end

    -- Send data from the Client to the Server
    ExampleRoute:send(...)

    -- Send data to a Client from the Server
    ExampleRoute:send(...):to(Player)
end

[Matter]: https://github.com/matter-ecs/matter

## Support and Contribution

### Discord

We have a Project Thread in the [Roblox OSS Community Server](https://quenty.org/oss/conduct), please read the Code of Conduct on that link before joining.
You can find the Project thread under the [#Projects](https://discord.com/channels/385151591524597761/1019724676265676930) threads channel titled as "YetAnotherNet," or you can follow [this link](https://discord.com/channels/385151591524597761/1179944163844825209).

**Please be mindful that the Roblox OSS Community is a professional workspace and not a general help desk, please keep questions and discussions about YetAnotherNet in our Project Thread and keep any other appropriate conversations in their respective channels.**

Please also be patient when awaiting a response in the thread, I will get to it when I can.

### Contributing

See the [CONTRIBUTING.md](CONTRIBUTING.md) file for a detailed guide on contributing. If you need any assistance, don't hesitate to ping me in the project thread or on GitHub.

## Installation

### Wally

Add YetAnotherNet to your project with [Wally] by adding the following to your ``wally.toml`` file:
```toml
[dependencies]
YetAnotherNet = "yetanotherclown/yetanothernet@0.10.0-alpha.5"

> [!NOTE]
> Wally does not export types automatically, if you wish to use Strict Typing with YetAnotherNet, install [Wally Package Types] with Aftman.

[Wally]: https://github.com/UpliftGames/wally
[Wally Package Types]: https://github.com/JohnnyMorganz/wally-package-types

### NPM for Roblox Typescript

You can find YetAnotherNet on [NPM], or install it with the command line:

npm i @rbxts/yetanothernet

[NPM]: https://www.npmjs.com/package/@rbxts/yetanothernet

## Building

To build yourself with Rojo, use:
```bash
./scripts/build.sh

For more help, check out [the Rojo documentation].

[the Rojo documentation]: https://rojo.space/docs

## Derived Works

The Darklua setup and Github Workflows are inspired and built off of the work of **grilme99**'s [roblox-project-template](https://github.com/grilme99/roblox-project-template).


tip

Check out the adapted version of the Matter Example game to see how it's used in a real game.

See all of YetAnotherNet's [example projects](https://github.com/YetAnotherClown/YetAnotherNet/tree/main/examples) in the repository.

  * Luau
  * Typescript

Being initially made for the Matter ECS, YetAnotherNet provides a simple function for scheduling your Routes to run on your Matter Loop.

Firstly, create a `routes.luau` ModuleScript in ReplicatedStorage to strictly declare your Routes.

shared/routes.luau

    local YetAnotherNet = require("@shared/routes")

    local Route = YetAnotherNet.Route
    type Route<U...> = YetAnotherNet.Route<U...>;

    local defaultConfiguration = {
      Channel = "Reliable",
      Event = "default",

    -- Payload for replicating Entities
    type EntityPayload = {
        [string]: { -- EntityId
            [string]: { -- Component name
                data: ComponentInstance<T>

    -- Replicate Matter Components
    local MatterReplication: Route<EntityPayload> = Route.new(defaultConfiguration)

    -- Signal that the Player has loaded
    local PlayerLoaded: Route<boolean> = Route.new(defaultConfiguration)

    return {
      MatterReplication = MatterReplication,
      PlayerLoaded = PlayerLoaded,

And now in the same script where you create your Matter Loop, you can run the `YetAnotherNet.start(Loop, { Route })` function to schedule your Routes to run on Matter's Middleware.

init.server.luau / init.client.luau

    local RunService = game:GetService("RunService")

    local Matter = require("@packages/Matter")
    local World = Matter.World
    local Loop = Matter.Loop

    local YetAnotherNet = require("@packages/YetAnotherNet")
    local routes = require("@shared/routes")

    local world = World.new()
    local loop = Loop.new(world)

    -- Schedules your Routes
    YetAnotherNet.start(loop, routes)

    local systems = {}
    for _, child in script.systems:GetChildren() do
        if child:IsA("ModuleScript") then
            table.insert(systems, require(child))
        end
    end

    loop:scheduleSystems(systems) -- Schedule systems after running ``YetAnotherNet.start()``

    -- Begin the loop and make sure the ``Event`` key in your Routes configuration are added here
    loop:begin({
        default = RunService.Heartbeat
    })

Finally, in a Matter System we can use our `routes.luau` ModuleScript to access our Routes and use them within our Systems.

systems/exampleSystem.luau

    local routes = require("@shared/routes")
    local PlayerLoaded = routes.PlayerLoaded

    local function exampleSystem(world)
        -- Query through every networking call that frame on the Server
        for i, player, ...data in PlayerLoaded:query() do
            -- Do something
        end

        -- Query through every networking call that frame on the Client
        for i, _, ...data in PlayerLoaded:query() do
            -- Do something
        end

        -- Send data from the Client to the Server
        PlayerLoaded:send(...data)

        -- Send data to a Client from the Server
        PlayerLoaded:send(...data):to(Player)
    end

Being initially made for the Matter ECS, YetAnotherNet provides a simple function for scheduling your Routes to run on your Matter Loop.

Firstly, create a `routes.ts` ModuleScript in ReplicatedStorage to strictly declare your Routes.

routes.ts

    import { Route, Configuration } from "@rbxts/yetanothernet";

    const defaultConfiguration: Configuration = {
      Channel: "Reliable",
      Event: "default",
    };

    // Replicate Matter Components
    const MatterReplication = new Route(defaultConfiguration);

    // Signal that the Player has loaded
    const PlayerLoaded: Route<[boolean]> = new Route(defaultConfiguration);

    export = {
      MatterReplication: MatterReplication,
      PlayerLoaded: PlayerLoaded,
    };

And now in the same script where you create your Matter Loop, you can run the `Net.start(Loop, { route: Route })` function to schedule your Routes to run on Matter's Middleware.

main.server.ts / main.client.ts

    import { RunService } from "@rbxts/services";

    import { Loop, System } from "@rbxts/matter";

    import Net from "@rbxts/yetanothernet";
    import routes from "shared/routes";

    const loop = new Loop();

    // Schedules your Routes
    Net.start(loop, routes);

    // Schedule systems
    const systems: System<[]>[] = [];
    loop.scheduleSystems(systems);

    // Begin the loop and make sure the ``Event`` key in your Routes configuration are added here
    loop.begin({
        default: RunService.Heartbeat
    });

Finally, in a Matter System we can use our `routes.ts` ModuleScript to access our Routes and use them within our Systems.

systems/exampleSystem.ts

    import routes from "shared/routes";

    const PlayerLoaded = routes.PlayerLoaded;

    function exampleSystem(world) {
        // Query through every networking call that frame on the Server
        for (const [pos, player, bool] of PlayerLoaded.query()) {
            // Do something

        // Query through every networking call that frame on the Client
        for (const [pos, _, bool] of PlayerLoaded.query()) {
            // Do something

        // Send data from the Client to the Server
        PlayerLoaded.send(true);

        // Send data to a Client from the Server
        PlayerLoaded.send(true).to(Player);

    export = {
        system: exampleSystem


### Cuts the header from RemoteEvents

BridgeNet2 cuts down successive remote event call size from 9 bytes to 2 bytes

### High quality logging

BridgeNet2 makes logging network traffic incredibly easy in an extremely readable manner

### Oriented around performance

If your game is having performance issues related to networking, switching to BridgeNet2 will help.


* Behavior is declarative. Systems run every frame, and declare what the state of the world should be right now. This makes code self-healing and more resilient to game-breaking bugs than in an event-driven model where reacting to something happening only happens once.
  * Game state and behavior are entirely decoupled and all game state is accessible to any system.
  * Game state is structured and easy to reason about. We [reconcile state into the data model](https://matter-ecs.github.io/matter/docs/BestPractices/Reconciliation) so we have one source of truth.
  * Systems are self-contained and adding new behaviors to your game is as simple as adding a new system that declares something about how the world should be.
  * Reusing code is easy because an entity can have many different types of components. Existing systems can affect new types of entities, and new systems automatically affect all related entities.
  * All system code runs contiguously and in a fixed order, every frame. Event-driven code can be sensitive to ordering, because doing anything can trigger an event that jumps into another code path, which in turn could do something else that triggers an event. With systems, your code always runs in the same, fixed order, which makes it much more predictable and resilient to new behaviors caused from refactors.


# A Type-safe Networking Library

YetAnotherNet is strictly-typed, giving you full auto-completion and type-safety.

Send arguments and Query return values of your Routes are fully typed, giving you auto-completion and type-checking when writing your Networking code.

# Hassle-free Compression Included

YetAnotherNet works behind the scenes to automatically compress the data you send over the network. It features an internal Ser/Des library to serialize all Luau Datatypes and most Roblox Datatypes.

This process requires no work on the user's end besides making sure to avoid any [Unsupported Datatypes](https://yetanotherclown.github.io/YetAnotherNet/docs/getting-started/buffer-compression#unsupported-datatypes).

# Data-driven by Design

With it's roots in ECS, YetAnotherNet was designed to promote a Data-driven design.
YetAnotherNet's API integrates seamlessly into ECS Libraries like Matter to bring Data-driven Networking in ECS architectures.

Read more on why you should use an ECS [here](https://matter-ecs.github.io/matter/docs/WhyECS).

# Available for Roblox Typescript

We provide Typings for Roblox Typescript users, you can get them on NPM [here](https://www.npmjs.com/package/@rbxts/yetanothernet), or you can install using `npm i @rbxts/yetanothernet`.

# Routes

Routes are the way you send and receive data through YetAnotherNet. They are uniquely identified so you're encouraged to create as many as you need as if you were creating individual RemoteEvents.

Routes can be Reliable or Unreliable. Reliable events are never dropped and are always in order per frame. Unreliable events might be dropped on bad connections, but they are always received in order per frame.

You can also strictly type Routes to get autocompletion and typechecking when Sending and Querying packets.

# Middleware

Middleware is another powerful feature of YetAnotherNet, allowing you to validate types before they reach your code.

To create Middleware, you set a function in your Route's Middleware that will give you the event `"send" | "receive"` and the data that is about to be processed `U...`, the types you specify in your type annotation `Net<U...>`

In the Middleware function, you can validate your types. If your data does not match your types, you can do `return nil` to drop the packet. Dropped packets will never reach your code, meaning you can ensure that the types your code receives are always the types you expect.

You may want to use Middleware to Compress or Decompress your data, but YetAnotherNet has built-in buffer compression!

# Hooks

Hooks are a simple and versatile way to integrate YetAnotherNet into any code architecture.

Hooks allow you to run your Route's scheduling whenever you want, such as a specific event. A Hook is simply a function that you can call which will run each Route's scheduling code to process the current frame. Data is not sent over the Network until your hook is called.

YetAnotherNet also provides a simple function that hooks your Routes to your Matter Middleware to run before/after your systems. This allows for a simple setup when using the Matter ECS, leaving you to not worry about scheduling your Routes.


Hooks allow you full control of scheduling logic, allowing YetAnotherNet to be used for any game structure whether ECS or not.

When you use `createHook`, it will return a `beginFrame` and `endFrame` function which should be called at the beginning and end of each frame respectively.

It's expected, and recommended that you still run your scheduling code on `RunService.Heartbeat`, otherwise you may run into unexpected behavior. If you know what you're doing, you can ignore this warning.

  * Luau
  * Typescript

    local RunService = game:GetService("RunService")

    local YetAnotherNet = require("@packages/YetAnotherNet")
    local routes = require("@shared/routes")

    local myRoute = routes.myRoute

    local beginFrame, endFrame = YetAnotherNet.createHook({ Route })
    RunService.Heartbeat:Connect(function()
        beginFrame()

        myRoute:send(...)
        for i, player, data in myRoute:query() do
            -- Do something
        end

        endFrame()
    end)

    import { RunService } from "@rbxts/services";

    import Net from "@rbxts/yetanothernet";
    import routes from "shared/routes";

    const myRoute = routes.myRoute;

    const [beginFrame, endFrame] = Net.createHook(routes);
    RunService.Heartbeat.Connect(() => {
        beginFrame();

        myRoute.send(...)
        for (const [pos, sender, ...] of myRoute.query()) {
            // Do something

        endFrame();
    });


* Luau
  * Typescript

You can integrate YetAnotherNet into whatever game architecture you want by creating a Hook using `YetAnotherNet.createHook({ Route })` which is identical to the `YetAnotherNet.start(loop, { Route })` function. This function will return another function which you can call whenever you want to process your Routes' queues and send/receive your Packets on the Server or Client.

Below is a simple example of creating custom scheduling behavior using `YetAnotherNet.createHook({ Route })`,

    local RunService = game:GetService("RunService")

    local YetAnotherNet = require("@packages/YetAnotherNet")
    local routes = require("@shared/routes")

    local myRoute = routes.myRoute

    local beginFrame, endFrame = YetAnotherNet.createHook({ Route })
    RunService.Heartbeat:Connect(function()
        beginFrame()

        myRoute:send(...)
        for i, player, data in myRoute:query() do
            -- Do something
        end

        endFrame()
    end)

You can integrate YetAnotherNet into whatever game architecture you want by creating a Hook using `Net.createHook({ route: Route })` which is identical to the `Net.start(loop, { route: Route })` function. This function will return another function which you can call whenever you want to process your Routes' queues and send/receive your Packets on the Server or Client.

Below is a simple example of creating custom scheduling behavior using `Net.createHook({ route: Route })`,

    import { RunService } from "@rbxts/services";

    import Net from "@rbxts/yetanothernet";
    import routes from "shared/routes";

    const myRoute = routes.myRoute;

    const [beginFrame, endFrame] = Net.createHook(routes);
    RunService.Heartbeat.Connect(() => {
        beginFrame();

        myRoute.send(...)
        for (const [pos, sender, ...] of myRoute.query()) {
            // Do something

        endFrame();
    });


* Luau
  * Typescript

Middleware is a powerful feature of YetAnotherNet that allows you to validate types before they are processed when sending and receiving data.

To create Middleware, you can specify a function in either `Route:addIncomingMiddleware()` or `Route:addOutgoingMiddleware()`.

    local YetAnotherNet = require("@packages/YetAnotherNet")

    local Route = YetAnotherNet.Route
    type Route<U...> = YetAnotherNet.Route<U...>;

    local route: Route<number, string, boolean> = Route.new()

    -- Sets a function to be ran on Incoming packets before they are received over the network
    route:addIncomingMiddleware(function(number: unknown, string: unknown, boolean: unknown)
        -- Do something, e.g., Validate Types

        return number, string, boolean
    end)

    -- Sets a function to be ran on Outgoing packets before they are sent over the network
    route:addOutgoingMiddleware(function(number: number, string: string, boolean: boolean)
        -- Do something

        return number, string, boolean
    end)

To validate your types, you can return the values in order to allow it to be processed, or you can drop the packet by returning `nil`. Dropped packets will never reach your code, meaning you can ensure that the types your code receives are always the types you expect.

Middleware is a powerful feature of YetAnotherNet that allows you to validate types before they are processed when sending and receiving data.

To create Middleware, you can specify a function in either `Route.addIncomingMiddleware()` or `Route.addOutgoingMiddleware()`.

    import { Route } from "@rbxts/yetanothernet";

    const route: Route<[number, boolean, string]> = new Route({
      Channel: "Reliable",
      Event: undefined,
    });

    // Sets a function to be ran on Incoming packets before they are received over the network
    route.addIncomingMiddleware(function (number, string, boolean) {
        // Do something, e.g., Validate Types

        return $tuple(number, string, boolean);
    });

    // Sets a function to be ran on Outgoing packets before they are sent over the network
    route.addOutgoingMiddleware(function (number, string, boolean) {
        // Do something

        return $tuple(number, string, boolean);
    });

To validate your types, you can return the values in order to allow it to be processed, or you can drop the packet by returning `undefined`. Dropped packets will never reach your code, meaning you can ensure that the types your code receives are always the types you expect.


As a pure ECS first and foremost, Matter provides no special functionality for CollectionService tags out of the box. However, it's rather simple to implement this yourself. Here's an example taken from the official [Matter example game](https://github.com/matter-ecs/matter/tree/main/example/src/server).

    local CollectionService = game:GetService("CollectionService")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local Components = require(ReplicatedStorage.Game.components)

    local boundTags = {
        Spinner = Components.Spinner,

    local function setupTags(world)
        local function spawnBound(instance, component)
            local id = world:spawn(
                component(),
                Components.Model({
                    model = instance,
                }),
                Components.Transform({
                    cframe = instance.PrimaryPart.CFrame,
                })
            )

            instance:SetAttribute("entityId", id)
        end

        for tagName, component in pairs(boundTags) do
            for _, instance in ipairs(CollectionService:GetTagged(tagName)) do
                spawnBound(instance, component)
            end

            CollectionService:GetInstanceAddedSignal(tagName):Connect(function(instance)
                spawnBound(instance, component)
            end)

            CollectionService:GetInstanceRemovedSignal(tagName):Connect(function(instance)
                local id = instance:GetAttribute("entityId")
                if id then
                    world:despawn(id)
                end
            end)
        end
    end

    return setupTags

This example can be modified to meet your game's needs as you see fit.


Oftentimes, games will have state that needs to be affected by multiple, distinct gameplay systems.

For example, imagine you had a game where equipping a heavy weapon lowered your walk speed. There might be other things that affect your walk speed too, like being Poisoned!

So, let's say both equipping the heavy weapon and being poisoned both need to lower your player's walk speed.

Instead of directly controlling the walk speed in the weapon system and then again in the poison system, we should make a dedicated system to manage walk speed.

Let's say that whenever a player is poisoned or has a heavy weapon equipped, our game adds the `Poison` or `HeavyWeapon` components to the player entity. For the sake of this example, we can imagine that each one lowers walk speed by half.

    local affectsWalkSpeed = {Poison, HeavyWeapon}

    local function walkSpeed(world)
        for id, player in world:query(Player) do

            local results = {world:get(id, unpack(affectsWalkSpeed))}

            -- NOTE: You can't be tricky by just checking the length of this table!
            -- We MUST iterate over it because the Lua length operator does not work
            -- as you might expect when a table has nil values in it.
            -- See for yourself: Lua says #{nil,nil,nil,1,nil} is 0!

            local modifier = 1

            for _ in results do
                -- For each result, reduce speed by half.
                modifier *= 0.5
            end

            -- The default Roblox walk speed is 16
            local speed = 16 * modifier

            world:insert(id, WalkSpeed({
                speed = speed,
                modifier = modifier,
            }))
        end
    end

    return walkSpeed

By listing out everything that can affect the walk speed in this system, we've created one source of truth for the player's walk speed. Any time there's a bug or something wrong with player movement, just check this one file. It's much easier to track down changes when everything that can affect something is in one place.

The value of the `WalkSpeed` component we use here is completely derived from the state of other components on the entity. This is _derived state_!

Maybe in a separate system, we could update anything with a Model and a WalkSpeed component, perhaps:

    -- Update anything with WalkSpeed and Model (this could be a separate system)
    for id, walkSpeed, model in world:query(WalkSpeed, Model) do
        if model.model:FindFirstChild("Humanoid") then
            model.model.Humanoid.WalkSpeed = walkSpeed.speed
        end
    end


# ColorPane

ColorPane is a suite of color tools for Roblox Studio plugins. Some of the tools included are:

- A color editor with a color wheel, several types of sliders, and various color palettes, with the ability to create, import, and export your own palettes.
- A gradient editor, similar to the Studio editor, with some quality-of-life changes including keypoint snapping, buttons to swap keypoint colors around, and a gradient palette.

  <source media="(prefers-color-scheme: dark)" src="docs/images/color-editor-dark.png">
  <source media="(prefers-color-scheme: light)" src="docs/images/color-editor-light.png">

  <source media="(prefers-color-scheme: dark)" src="docs/images/gradient-editor-dark.png">
  <source media="(prefers-color-scheme: light)" src="docs/images/gradient-editor-light.png">

## Installing

**ColorPane comes in 2 parts. Install the one that applies to your situation.**

### Library

If you want to use these color tools in your own plugin, you'll want to install the ColorPane library:

[![Get it on Roblox](https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/roblox_dev.svg)](https://create.roblox.com/store/asset/17844182825) [![Get it on GitHub](https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/github.svg)](https://github.com/Blupo/ColorPane/releases/latest)

If you use [Rojo](https://rojo.space), you can add the [source repository](https://github.com/Blupo/ColorPane) as a submodule. Take a look at the [Integration](https://blupo.github.io/ColorPane/0.5/developer-guide/integration) page to learn how to put these tools in your plugin.

### Companion

If you're looking to try out ColorPane to see if it's right for you, or you use a plugin with ColorPane and want to unlock it's full capabilities, you'll want to install the Companion plugin.

[![Get it on Roblox](https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/roblox_dev.svg)](https://create.roblox.com/store/asset/6474565567) [![Get it on GitHub](https://gist.githubusercontent.com/cxmeel/0dbc95191f239b631c3874f4ccf114e2/raw/github.svg)](https://github.com/Blupo/ColorPane/releases/latest)

These additional capablities include:

* Creating and editing palettes
* Modifying settings
* Editing [color properties](https://blupo.github.io/ColorPane/0.5/user-guide/color-properties)

Take a look at the [User Guide](https://blupo.github.io/ColorPane/0.5/user-guide/color-editor) to learn how to use the color tools.

## Contributing

Found a bug? Want to request a new feature? Interested in translating ColorPane? Read the [Contributing](https://blupo.github.io/ColorPane/0.5/contributing) page for guidelines on contributing to the project!

If you like ColorPane, consider [donating](https://ko-fi.com/blupo)!


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/developer-guide/integration.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/developer-guide/integration.md "View source of this page")

# Integration¶

Note

The pages in the Developer Guide are for plugin developers who want to integrate ColorPane in their projects. If you simply want to use the color tools, you're looking for the [the user guide](https://blupo.github.io/ColorPane/0.5/user-guide/color-editor/).

First, you'll need to grab the module from the [Creator Store](https://create.roblox.com/store/asset/17844182825). If you use [Rojo](https://rojo.space), you can alternatively add the [GitHub repo](https://github.com/Blupo/ColorPane) as a submodule.

## Initialisation¶

Before we can start getting colors and gradients, we need to initialise the API. To do that, we'll simply call the function returned by the library script, which will give us the API.

    local ColorPaneInit = require(path.to.library)
    local ColorPane = ColorPaneInit(plugin, "MyProjectId")

The parameters for the initialisation function are (1) a [Plugin](https://create.roblox.com/docs/reference/engine/classes/Plugin) object and (2) a unique identifier, used to make sure each instance of ColorPane has its own plugin windows.

## Getting Colors¶

Note

Familiarity with the promise pattern, as well as the specific Promises from evaera's [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, is recommended. While some explanation is given here, the additional reading may help you.

To prompt the user for colors, you will use [`PromptForColor`](https://blupo.github.io/ColorPane/0.5/developer-guide/api-reference/#promptforcolor).

    local colorPromise = ColorPane.PromptForColor({
        PromptTitle = "Pick a color!"
        InitialColor = Color3.new(0.5, 0.5, 0.5),
    })

To customise the prompt, you can pass a table of options. The two options specified here are the most common. `PromptTitle` sets what the window text says, and `InitialColor` sets what color the user starts with (grey in this example).

The API will return a Promise. The basic idea is that a Promise represents a value that will be given _in the future_. That Promise will either be fulfilled (resolved), or broken (rejected). In this example, if the Promise resolves, it will resolve with a [Color3](https://create.roblox.com/docs/reference/engine/datatypes/Color3), and if it rejects, it will reject with an error object or message, depending on where the rejection came from.

We can attach callbacks onto the Promise to handle resolutions and rejections with `Promise:andThen()`.

    colorPromise
        :andThen(function(color)
            -- This function is called when the Promise resolves
        end, function(err)
            -- This function is called when the Promise rejects
        end)

The Promise may reject for a variety of reasons, including:

  * You passed some bad options into the function (e.g. setting `InitialColor` to a string)
  * The color editor is already open
  * The user decides to close the prompt without selecting a color

If you don't want or need to use the Promise pattern, you can use `Promise:await()` to turn it into a synchronous function (a yielding function, like [`task.wait`](https://create.roblox.com/docs/reference/engine/libraries/task#wait)). The function will return values in the same manner as [`pcall`](https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#pcall): the first value tells you if the Promise resolved, and then any other values the Promise resolves/rejects with.

    local resolved, value = colorPromise:await()

    if (resolved) then
        -- do stuff with the color
    else
        -- do stuff with the error
    end

## Getting Gradients¶

The same rules for Promises apply here as they do for getting colors. To prompt the user for gradients, you'll use [`PromptForGradient`](https://blupo.github.io/ColorPane/0.5/developer-guide/api-reference/#promptforgradient). The configuration options are slightly different here, and in this example, the Promise will resolve with a [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence).

    local gradientPromise = ColorPane.PromptForGradient({
        PromptTitle = "Pick a gradient!",
        InitialGradient = ColorSequence.new(
            Color3.new(0, 0, 0),
            Color3.new(1, 1, 1)
        )
    })

    gradientPromise:andThen(function(gradient)
        -- resolved
    end, function(err)
        -- rejected
    end)

## Advanced¶

### Previewing Changes¶

An additional configuration option, `OnColorChanged` for [`PromptForColor`](https://blupo.github.io/ColorPane/0.5/developer-guide/api-reference/#promptforcolor) and `OnGradientChanged` for [`PromptForGradient`](https://blupo.github.io/ColorPane/0.5/developer-guide/api-reference/#promptforgradient), lets you "preview" color changes. This option is a callback that will be called every time the user makes a modification to the color or gradient. This is useful for letting the user see their changes before committing to them.

The callbacks you pass **must not** yield, meaning that you can't use [`task.wait()`](https://create.roblox.com/docs/reference/engine/libraries/task#wait), [`RBXScriptSignal:Wait()`](https://create.roblox.com/docs/reference/engine/datatypes/RBXScriptSignal#Wait), [`Instance:WaitForChild()`](https://create.roblox.com/docs/reference/engine/classes/Instance#WaitForChild) or any other function that yields or suspends a thread.

    ColorPane.PromptForColor({
        InitialColor = Color3.new(1, 1, 1),

        OnColorChanged = function(color)
            -- your code here
        end,
    })

    ColorPane.PromptForGradient({
        InitialGradient = ColorSequence.new(
            Color3.new(0, 0, 0),
            Color3.new(1, 1, 1)
        ),

        OnGradientChanged = function(gradient)
            -- your code here
        end,
    })

Warning

The values passed to `OnColorChanged`/`OnGradientChanged` are for **temporary use only**. If the user cancels color selection, anything you've changed with the preview colors should be changed back to their original values.

### Alternate Color Objects¶

If you're familiar with the [Color](https://blupo.github.io/Color/) library, the API also allows you to prompt for these types of colors instead of [Color3s](https://create.roblox.com/docs/reference/engine/datatypes/Color3) and [ColorSequences](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence). Note that this will also affect the types of the values passed to `OnColorChanged`/`OnGradientChanged`.

    ColorPane.PromptForColor({
        ColorType = "Color",
    })  -- Will resolve with a Color instead of a Color3

    ColorPane.PromptForGradient({
        GradientType = "Gradient",
    })  -- Will resolve with a Gradient instead of a ColorSequence

For Gradients, you can also specify the options used for generating intermediate colors:

    ColorPane.PromptForGradient({
        GradientType = "Gradient",
        InitialGradient = Gradient.fromColors(
            Color.new(0, 0, 0),
            Color.new(1, 1, 1)
        ),

        InitialColorSpace = "Lab",
        InitialHueAdjustment = "Longer",
        InitialPrecision = 0,
    })

## Plugin Permissions¶

ColorPane includes some functionality that requires certain plugin permissions. These permissions are not required, and the core functionality of ColorPane will still work without them. Your plugin itself, however, may need these permissions to work, and ColorPane will also be able to use them if granted.

  * Script injection
    * Exporting palettes into the DataModel as a ModuleScript
  * HTTP requests
    * The Picular palette sends HTTP requests to [backend.picular.co](https://backend.picular.co)
    * Importing palettes via URL, which can be from _any_ domain


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/user-guide/color-editor.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/user-guide/color-rawor.md "View source of this page")

# The Color Editor¶

## Color Wheel¶

The buttons below the wheel let you select [color harmonies](https://en.wikipedia.org/wiki/Harmony_\(color\)), which will be marked on the wheel using squares, while the main color marker will be a circle.

## Sliders¶

The editor includes several sliders:

  * RGB
  * CMYK
  * HSB/L
  * Monochrome
  * Temperature

## Palettes¶

Warning

If you don't have the Companion plugin installed, you won't be able to create, edit, or import palettes.

Palettes let you store lists of colors. For most palettes, you will see (from top to bottom, left to right): a search bar, buttons to switch between grid or list layout, a button to add the current color to the palette, and the list of colors.

There are several built-in palettes, some of which have custom layouts:

  * BrickColors
  * ColorBrewer
  * Picular
  * Web Colors

The overflow menu (accessed using the  button) has several options such as creating new palettes, deleting existing palettes, and importing palettes.

### Layouts¶

The grid layout (pictured above) is the default layout, and allows for quick access to colors. Clicking on a color will select it, which then allows you to set the current color, remove the color, change its position in the palette, or rename it.

The list layout (pictured below) is useful for palettes where color names are important.

### Import and Export¶

Palettes can be imported and exported, for uses such as sharing them with collaborators or backing them up. You can access the import/export options from the overflow menu (the Export option does not appear for built-in palettes).

There are various ways to import palettes, but the UI will look similar for each method. After you extract the palette from the method you choose and it's in the [correct format](https://blupo.github.io/ColorPane/0.5/developer-guide/palette-format/), the Import button will be enabled. You can also change the name of the palette before you import it, and the name input will be pre-filled with the name from the palette file/object.

Note

When importing via URL, you may be prompted by Studio to allow HTTP requests to the domain you're importing from.

Palettes can be exported as ModuleScripts or StringValues to ServerStorage, and the exported object will contain the JSON string for the palette (as well as some metadata for ModuleScripts).

Note

When exporting to a ModuleScript, you may be prompted by Studio to allow script injection.

Palettes cannot be exported to JSON files, as Studio does not have this capability. If you need a JSON file, you can copy the content of the JSON string into a file. For StringValue exports, you can copy the `Value` property. For ModuleScripts, copy the text inside the double-square-brackets (`[[...]]`).

### ColorBrewer Palette¶

[ColorBrewer](https://colorbrewer2.org) is a collection of color sets used in cartography. You can select the data type and number of classes, and the palette will display the various color sets that match the criteria.

### Picular Palette¶

[Picular](https://picular.co) is an online tool that lets you search "the color of anything". You can use the search bar to search for things you want the color of, e.g. "roblox". There is also a search history tab where you can view previous entries you have looked up.

## Color Tools¶

Various color tools are included, such as a color information tool, and a gradient picker.

### Color Info¶

The information tool shows the current color in different representations. You can copy text to paste somewhere else, and paste into the text boxes to change the current color (assuming what you paste follows the same format).

### Color Sorter¶

The sorter tool allows you to check how similar a list of colors is to a particular color, where the colors are listed from most to least similar after pressing Sort. The sorter uses [CIEDE2000](https://en.wikipedia.org/wiki/Color_difference#CIEDE2000).

### Color Variations¶

The variations tools shows various tints, tones, shades, and hue rotations of the current color, with an adjuster to change how many colors are shown.

### Gradient Pickers¶

The gradient pickers allow you to select a color from the different gradients in your gradient palette. Clicking anywhere on a gradient will set the current color to the color at that position. You can also click and drag to select colors as if it were a slider.

## Other Tools¶

The two colors in a large square at the bottom-left of the editor represent the current (top) and initial (bottom) colors. You can click on the initial color if you want to reset the current color. The hex input below it accepts either 3 (`ABC` = `AABBCC`) or 6 (`ABCDEF`) digits.

The list of colors to the right of the large square is the quick palette, and lets you temporarily store colors for quick access. You can add as many colors as you like, but you can't remove any colors. The number of colors that the quick palette displays depends on the size of the window (i.e. a larger window displays more colors).

The  button selects a random color.


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/user-guide/color-properties.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/user-guide/color-properties.md "View source of this page")

# Editing Color Properties¶

Note

This feature is exclusive to the Companion plugin and isn't provided in the ColorPane library.

The Color Properties window allows you to edit the color properties of objects in your projects.

Each property will have a little icon next to its color button to indicate the type of property it is (the Part class icon for BrickColors, a rainbow color wheel for Color3s, and a vertical black and white gradient for ColorSequences). When multiple properties with the same name are listed, their class names will also be listed. Clicking on the color button will allow you to edit the property's value.

Note

The Terrain material properties you will see when you select a Terrain object (e.g. "Asphalt Material", "Basalt Material", etc.) are not real properties, but you can still use them to modify Terrain colors.

## Activation¶

Color Properties will require manual activation the first time you use it. You will see a prompt when you open the window telling you that the API data has not been loaded (pictured below). On this screen you can load the API data and change the settings for automatic activation and caching (see the next section).

Note

API data is retrieved via HTTP requests to `setup.rbxcdn.com`. You may be prompted by Studio to allow HTTP requests to this domain when you use Color Properties. If you deny this permission, you will not be able to use this part of the Companion.

## Play-Testing¶

Since HTTP requests are not allowed from plugins during play-testing, trying to use Color Properties won't work. You can enable the _Cache Roblox API data_ setting to get around this, which will store the Roblox API data on your computer so that it can be used instead of having to make an HTTP request. Enabling this option may cause noticable pauses whenever the cache needs to be updated, since the size of the API data is quite large.


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/contributing.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/contributing.md "View source of this page")

# Contributing¶

## Feature Requests¶

Features requests should be submitted through GitHub issue. Feature requests should align with ColorPane's purpose of being a general-purpose suite of color tools. Please be as detailed as possible, and include images and/or video if necessary. New features should be fully discussed before contributing any code.

## Bug Reports¶

Bugs should be submitted via GitHub issue. Make sure that the bug you're reporting isn't already part of another issue.

Please include a detailed description and an image and/or video of the bug to make it easier to track down. If there's relevant error output in Studio, please include it.

## Documentation¶

If you find any errors or places for improvement in the documentation, feel free to open an issue or submit a pull request. ColorPane's documentation uses [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) (version 9.5.27 as of this writing).

## Translations¶

To reach as many developers as possible, one of ColorPane's goals is to be fully translated into several languages. If you would like to help translate ColorPane, you can contribute to the [Crowdin project](https://crowdin.com/project/colorpane). Translations for the documentation are currently not being accepted, but this may change in the future.

Currently, the targeted languages are:

  * [Simplifed Chinese](https://crowdin.com/project/colorpane/zh-CN) (`zh-CN`)
  * [Traditional Chinese](https://crowdin.com/project/colorpane/zh-TW) (`zh-TW`)
  * [French](https://crowdin.com/project/colorpane/fr) (`fr`)
  * [German](https://crowdin.com/project/colorpane/de) (`de`)
  * [Indonesian](https://crowdin.com/project/colorpane/id) (`id`)
  * [Italian](https://crowdin.com/project/colorpane/it) (`it`)
  * [Japanese](https://crowdin.com/project/colorpane/jp) (`jp`)
  * [Korean](https://crowdin.com/project/colorpane/ko) (`ko`)
  * [Portuguese](https://crowdin.com/project/colorpane/pt-PT) (`pt-PT`)
  * [Russian](https://crowdin.com/project/colorpane/ru) (`ru`)
  * [Spanish](https://crowdin.com/project/colorpane/es-ES) (`es-ES`)

## Code Contributions¶

Note

Code contributions should target the [develop](https://github.com/Blupo/ColorPane/tree/develop) branch, unless they're critical fixes.

If you have code you would like to contribute to ColorPane, please submit a pull request. ColorPane uses [Rojo](https://rojo.space) (version 7.4.3 as of this writing) for project management. When testing the plugin in Studio, serve `workspace.project.json` instead of the `default.project.json`. There will be 3 build objects in ServerStorage:

  * CPTest, which is a debug plugin that provides direct access to the ColorPane API script from the Workspace
  * Companion, the Companion plugin
  * ColorPane, the ColorPane library

## Donations¶

If you like ColorPane, consider [donating](https://ko-fi.com/blupo)!


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/index.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/index.md "View source of this page")

# ColorPane¶

ColorPane is a suite of color tools for Roblox Studio plugins. Some of the tools included are:

  * A color editor with a color wheel, several types of sliders, and various color palettes, with the ability to create, import, and export your own palettes.
  * A gradient editor, similar to the Studio editor, with some quality-of-life changes including keypoint snapping, buttons to swap keypoint colors around, and a gradient palette.

## Installing¶

**ColorPane comes in 2 parts. Install the one that applies to your situation.**

### Library¶

If you want to use these color tools in your own plugin, you'll want to install the ColorPane library:

[](https://create.roblox.com/store/asset/17844182825) [](https://github.com/Blupo/ColorPane/releases/latest)

If you use [Rojo](https://rojo.space), you can add the [source repository](https://github.com/Blupo/ColorPane) as a submodule. Take a look at the [Integration](https://blupo.github.io/ColorPane/latest/developer-guide/integration/) page to learn how to put these tools in your plugin.

### Companion¶

If you're looking to try out ColorPane to see if it's right for you, or you use a plugin with ColorPane and want to unlock its full capabilities, you'll want to install the Companion plugin.

[](https://create.roblox.com/store/asset/6474565567) [](https://github.com/Blupo/ColorPane/releases/latest)

These additional capablities include:

  * Creating and editing palettes
  * Modifying settings
  * Editing [color properties](https://blupo.github.io/ColorPane/latest/user-guide/color-properties/)

Take a look at the [User Guide](https://blupo.github.io/ColorPane/latest/user-guide/color-editor/) to learn how to use the color tools.

## Contributing¶

Found a bug? Want to request a new feature? Interested in translating ColorPane? Read the [Contributing](https://blupo.github.io/ColorPane/latest/contributing/) page for guidelines on contributing to the project!


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/developer-guide/palette-format.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/developer-guide/palette-format.md "View source of this page")

# Palette Format

When [importing](https://blupo.github.io/ColorPane/0.5/user-guide/color-editor/#import-and-export) palettes, they must follow this JSON format:

        "name": "Palette Name",

        "colors": [
                "name": "Color Name",
                "color": [0, 0, 0]
            },

                "name": "Another Color Name",
                "color": [0.5, 0.5, 0.5]
            },

                "name": "Yet Another Color Name",
                "color": [1, 1, 1]

The color array of each color object is a 3-element array, with the elements representing the 3 sRGB channels, which typically range from [0, 1]. No two colors can share the same name, however any number of colors have have the same color array.

If importing from a ModuleScript, the palette can also be a Lua table in the same format, e.g.

        name = "Palette Name",

        colors = {
                name = "Color Name",
                color = {0, 0, 0}
            },

                name = "Another Color Name",
                color = {0.5, 0.5, 0.5}
            },

                name = "Yet Another Color Name",
                color = {1, 1, 1}


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/attribution.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/attribution.md "View source of this page")

# Attribution¶

This page gives attribution to the various open-source projects used in ColorPane, as well as whatever else seems appropriate.

## ColorBrewer¶

This product includes color specifications and designs developed by Cynthia Brewer (<http://colorbrewer.org/>).

    Apache-Style Software License for ColorBrewer software and ColorBrewer Color
    Schemes

    Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State
    University.

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License. You may obtain a copy of
    the License at

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    License for the specific language governing permissions and limitations under
    the License.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions as source code must retain the above copyright notice, this
    list of conditions and the following disclaimer.

    2. The end-user documentation included with the redistribution, if any, must
    include the following acknowledgment: "This product includes color
    specifications and designs developed by Cynthia Brewer
    (http://colorbrewer.org/)." Alternately, this acknowledgment may appear in the
    software itself, if and wherever such third-party acknowledgments normally
    appear.

    4. The name "ColorBrewer" must not be used to endorse or promote products
    derived from this software without prior written permission. For written
    permission, please contact Cynthia Brewer at cbrewer@psu.edu.

    5. Products derived from this software may not be called "ColorBrewer", nor
    may "ColorBrewer" appear in their name, without prior written permission of
    Cynthia Brewer.

## color-temperature¶

The implementation of the Kelvin color module is based on Neil Bartlett's [color-temperature](https://github.com/neilbartlett/color-temperature), which is licensed under the MIT License.

## Markdown Buttons¶

[@cxmeel](https://github.com/cxmeel)'s [Material Buttons](https://gist.github.com/cxmeel/b3af232eba0ace022e2fba8b7b286520) are cool and you should know about them.

## Material Icons¶

Google's [Material Icons](https://fonts.google.com/icons) are licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).

## roact, rodux, roact-rodux¶

[@roblox](https://github.com/Roblox)'s [roact](https://github.com/Roblox/roact), [rodux](https://github.com/Roblox/rodux), and [roact-rodux](https://github.com/Roblox/roact-rodux) are licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).

## roblox-lua-promise¶

[@evaera](https://github.com/evaera)'s [roblox-lua-promise](https://github.com/evaera/roblox-lua-promise) is licensed under the [MIT License](https://github.com/evaera/roblox-lua-promise/blob/master/LICENSE).

## t¶

[@osyrisrblx](https://github.com/osyrisrblx)'s [t](https://github.com/osyrisrblx/t) is licensed under the [MIT License](https://github.com/osyrisrblx/t/blob/master/LICENSE).

## Translations¶

  * French (`fr`) translation:
    * [Clem Gaming (clmntfeugere)](https://crowdin.com/profile/clmntfeugere)
    * [CROISSANT0011](https://crowdin.com/profile/croissant0011)
  * Korean (`ko`) translation:
    * [CoouDo](https://crowdin.com/profile/cooudo)
  * Russian (`ru`) translation:
    * [Artemiy Zaitsev (ztsff)](https://crowdin.com/profile/ztsff)


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/user-guide/gradient-editor.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/user-guide/gradient-rawor.md "View source of this page")

# The Gradient Editor¶

## Editing Keypoints¶

Keypoints can be added by clicking anywhere in the gradient view (as long as the cursor isn't snapped to another keypoint). Clicking on a keypoint will select it, which allows you to delete it, change its color or position, and use the left  or right  buttons (_not_ the arrow keys) to swap its color with the keypoint on the left or right, respectively. You can also change the position by dragging it around the gradient view.

## Other Controls¶

  * The  button reverses the order of the colors in the gradient
  * The _Snap_ input can be used to modify the interval at which keypoints snap
  * The Reset button resets the gradient to its initial value

The  button toggles showing the ColorSequence code for the gradient, if you wish to copy it. You'll probably have to increase the size of the window to view the whole code, but clicking on the text will select all of it, for quick copying.

## Gradient Palette¶

You can open the gradient palette using the  button. Similar to [color palettes](https://blupo.github.io/ColorPane/0.5/user-guide/color-editor/#palettes), you can store gradients for later use. The first 3 gradients in the palette are built-in, so you cannot modify or delete them.

## Gradient Info¶

You can access gradient info with the  button. Editing gradient information is an advanced feature that allows you to create gradients with interpolation in different color spaces. The available options for the color space and hue interpolation are the same as those used in [Color.mix](https://blupo.github.io/Color/api/color/#colormix).

Increasing precision allows you to get a more visually-accurate gradient for the specified color space, but at the cost of the number of keypoints that you're allowed to insert.

For non-RGB color spaces, the precision should be at least 1, otherwise the gradient will not look any different from itself in RGB space. For RGB space, the precision should always be 0, since ColorSequences already use RGB interpolation.


[ ](https://github.com/Blupo/ColorPane/edit/develop/docs/index.md "Edit this page") [ ](https://github.com/Blupo/ColorPane/raw/develop/docs/index.md "View source of this page")

# ColorPane¶

ColorPane is a suite of color tools for Roblox Studio plugins. Some of the tools included are:

  * A color editor with a color wheel, several types of sliders, and various color palettes, with the ability to create, import, and export your own palettes.
  * A gradient editor, similar to the Studio editor, with some quality-of-life changes including keypoint snapping, buttons to swap keypoint colors around, and a gradient palette.

## Installing¶

**ColorPane comes in 2 parts. Install the one that applies to your situation.**

### Library¶

If you want to use these color tools in your own plugin, you'll want to install the ColorPane library:

[](https://create.roblox.com/store/asset/17844182825) [](https://github.com/Blupo/ColorPane/releases/latest)

If you use [Rojo](https://rojo.space), you can add the [source repository](https://github.com/Blupo/ColorPane) as a submodule. Take a look at the [Integration](https://blupo.github.io/ColorPane/0.5/developer-guide/integration/) page to learn how to put these tools in your plugin.

### Companion¶

If you're looking to try out ColorPane to see if it's right for you, or you use a plugin with ColorPane and want to unlock its full capabilities, you'll want to install the Companion plugin.

[](https://create.roblox.com/store/asset/6474565567) [](https://github.com/Blupo/ColorPane/releases/latest)

These additional capablities include:

  * Creating and editing palettes
  * Modifying settings
  * Editing [color properties](https://blupo.github.io/ColorPane/0.5/user-guide/color-properties/)

Take a look at the [User Guide](https://blupo.github.io/ColorPane/0.5/user-guide/color-editor/) to learn how to use the color tools.

## Contributing¶

Found a bug? Want to request a new feature? Interested in translating ColorPane? Read the [Contributing](https://blupo.github.io/ColorPane/0.5/contributing/) page for guidelines on contributing to the project!