Best Rojo Use for Multi-Placed Game

Hello Developers!

Recently, I have started using Rojo for developing my games, and so far I am very satisfied with the experience of it. However, there are still few questions I have regarding Rojo usage, and one of them is the best way to integrate Rojo for Multi-Placed Game as I like to use around same files across place, which becomes tedious work if I start to copy-paste scripts across all rojo files when I make slight change.

Currently, I can come up with 3 ways to use Rojo across places, however, if anyone has better method I would like to know :slight_smile:

Way 1
(All Files in Single Project)
Develop all of the scripts within single rojo project, and activate / deactivate the scripts depending on place id. With this method, I will be able to access scripts from every place with single rojo project, which makes my workflow very fluid as everything is within single project right in my eyes. However I can imagine this becoming pretty messy as I keep adding more places to the game.

Way 2
(Utilizing Roblox Package)
(This is the way I currently work with, which I don’t find the most fluid workflow)
For each places, have a separate rojo file. However, to sync up some necessary scripts that is used across multiple places, open up a new place specifically to work with this files and share it via package. On other places, add this package to place directly. The upside of this is that its more organized. However, there are many downsides, such as not being able to access the file directly from external code editor as package won’t show up outside of roblox studio.

Way 3
(Utilizing Git Submodule)
This is a somewhat similar to Way2, except it uses git to make a submodules instead of package. I have tried this before, and it works somewhat well except again, I am needed to open a separate code editor just to edit shared files which I can deal with but is willing to avoid as much as possible.

Thank you for any helps in advance :smiley:

22 Likes

Hello Developers Once Again! Its been around 15 days since this post, and I think I have finally figured out the good way of managing multi-placed game with some help of Noble_Draconian on Discord!

So to work with games where multiple places takes spot, the best setup of the rojo File will be something like this:

image
Basically, Have 1 Server, Shared, and Client to store scripts that is shared across every place. This will be synced into every place file via Rojo.

Meanwhile, for the place-specific scripts you will need to setup a individual folder representing each place.
Naming Convention Example: “server” … place name, “client” … place name

Finally, to the most important part which is to create a Project JSON file.
By having a individual json file, you will be able to sync a specific files dedicated to each place.

Example of JSON File:

--(JSON File for Place1)
{
    "name": "Place1",
    "servePort": number --(Have different number for serveport, so u can sync multiple project files without being mixed up
    "servePlaceIds": [place id]  --(rojo will avoid syncing if the place id does not match with the id here)
    "tree": {
        "$className": "DataModel",

        "ReplicatedStorage": {
          "$className": "ReplicatedStorage",

          "Shared": { --The Folder will be available as ReplicatedStorage.Shared
            "$path": "src/shared"
          }
        },

        "ServerScriptService": {
          "$className": "ServerScriptService",

          "ServerShared": { --Will Be Available As ServerScriptService.ServerShared. This is where you want to store server scripts that is shared across all place
            "$path": "src/server"
          },

          "ServerLocal": { --Will Be Available As ServerScriptService.ServerLocal. This is where u want to store place1-specific scripts
            "$path": "src/serverPlace1"
          }
        },

        "StarterPlayer": {
          "$className": "StarterPlayer",

          "StarterPlayerScripts": {
            "$className": "StarterPlayerScripts",

            "ClientShared": { --Will be available as LocalPlayer.PlayerScripts.ClientShared. Store Client Scripts That Needs to be Shared Across All Places
              "$path": "src/client"
            },

            "ClientLocal": { --Will Be Available as LocalPlayer.PlayerScripts.ClientLocal. Store Client Scripts That is Place1-Specific.
              "$path": "src/clientPlace1"
            }
          }
        }
    }
}

--Place File for Place2
{
    "name": "Place2",
    "servePort": number --(Have different number for serveport, so u can sync multiple project files without being mixed up
    "servePlaceIds": [place id]  --(rojo will avoid syncing if the place id does not match with the id here)
    "tree": {
        "$className": "DataModel",

        "ReplicatedStorage": {
          "$className": "ReplicatedStorage",

          "Shared": { --The Folder will be available as ReplicatedStorage.Shared
            "$path": "src/shared"
          }
        },

        "ServerScriptService": {
          "$className": "ServerScriptService",

          "ServerShared": { --Will Be Available As ServerScriptService.ServerShared. This is where you want to store server scripts that is shared across all place
            "$path": "src/server"
          },

          "ServerLocal": { --Will Be Available As ServerScriptService.ServerLocal. This is where u want to store place2-specific scripts
            "$path": "src/serverPlace2"
          }
        },

        "StarterPlayer": {
          "$className": "StarterPlayer",

          "StarterPlayerScripts": {
            "$className": "StarterPlayerScripts",

            "ClientShared": { --Will be available as LocalPlayer.PlayerScripts.ClientShared. Store Client Scripts That Needs to be Shared Across All Places
              "$path": "src/client"
            },

            "ClientLocal": { --Will Be Available as LocalPlayer.PlayerScripts.ClientLocal. Store Client Scripts That is Place2-Specific.
              "$path": "src/clientPlace2"
            }
          }
        }
    }
}

Now that all the project files is set up, its Time to sync it into roblox places!
In order to sync the place project file, you will need to specify the JSON file we made above so rojo can sync from there. Shift + Right Click in the project folder (shortcut to open powershell from the directory you are in), and type following to start Syncing!

Edit:
I also found that you can press ctrl + @ to open the powershell within VSCode

--For Place1
rojo serve Place1.project.json

--For Place2
rojo serve Place2.project.json

Example Project File: https://github.com/Yuuwa0519/Multi-Place-Template/

33 Likes

This is a really nice way of managing many places. I had a question with the last part, Rojo keeps telling me that ‘default.project.json’ doesn’t exist and so it cannot sync. I’m new to Rojo and VS Code and i need help. :slight_smile: Thank you.

2 Likes

Hello! Since this project format is dedicated to sync in specific files into specific places, you need to specify the file path of each Json files.

If you are directly using the project from the github repo, you should be able to sync in files by doing: rojo serve Place1.project.json or rojo serve Place2.project.json or other files if you have made more of it.

However, if you have your own project running, make sure that you have created the Json file following similar format to the example project, and start syncing it by telling the file path :slight_smile:

(I hope my explanation makes sense cause i ain’t the best lol)

Yeah, but where do I enter those commands - It doesn’t work in cmd or powershell (says that the command doesn’t exist). Where do I find the rojo command line? Thanks again!

Just to verify, type rojo --version in powershell. If it doesn’t return anything, you will need to install rojo into you system (windows, linux or mac os) and add Rojo software to the global path

You can follow this article (you can ignore git part if you don’t want to, but i would recommend doing it for version controlling :P) for better instructions of how to properly install rojo

2 Likes

Yeah, thank you so much. Worked perfectly. :smiley:

2 Likes

Thanks so much for posting your github link.
I am trying to figure out these same architecture issues myself!

2 Likes

Thanks for posting this and for following up with your findings after testing the different approaches. I am looking into doing something like this myself. It’s now been a little more than 18 months since your last post. Do you have any new insights or best practices to share on the topic of a multi-place setup?

Hello, thank you for bringing up the topic to me :slight_smile:

As for syncing details, I did find a few different ways that helped me a lot with organizing my rojo projects that has multiple places, but the core structure pretty much remains the same, with few things jumbled around.
I also now use git submodules to sync in some of the modules that I use in almost every projects, allowing me to manage them from 1 repository.

My json files nowadays looks like this:

{
  "name": "NameOfMyGame",
  "servePlaceIds": [0], --place id goes here to prevent syncing it into wrong place :P
  "globIgnorePaths": ["node_modules/@quenty/loader", "**/.package-lock.json"], --files that has a name that matches with this will be ignored when syncing
  "tree": {
    "$className": "DataModel",

    "ReplicatedStorage": {
      "Game": {
        "$className": "Folder",

        "Modules": {
            "$path": "src/shared/Modules", --Modules inside replicatedstorage. available as ReplicatedStorage/Game/Modules

            "Library": {
                "$path": "YuuwaPackages/src/library" --this is the submodule i have :D
            }
        }
      },

      --this is a wally package, for more info visit https://wally.run
      "Packages": {
          "$className": "Folder",
          "$path": "Packages"
      }
    },

    "ServerScriptService": {
      "Internal": {
        "$path": "YuuwaPackages/src/internal/internal.server.lua" --Available as ServerScriptService/Internal. This is an initter script (Because I use Knit).
      },

      "Game": {
          "$className": "Folder",
          "Main": {
              "$path": "src/server/Main" --Available as ServerScriptService/Game/Main. This is where stuff that is shared across every place goes.
          },
          "Game": {
              "$path": "src/server/Game" --Available as ServerScriptService/Game/Game. This is where stuff that is related to game system goes (e.g. if its a racing game, modules related to car goes here)
          },
          "Local": {
              "$path": "src/server/Place1" --Available as ServerScriptService/Game/Local. This is the part that stores per-place modules!! e.g. src/server/Place2, src/server/Lobby, etc... 
          }
      }
    },

    "StarterPlayer": {
      "$className": "StarterPlayer",
  
      "StarterPlayerScripts": {
          "$className": "StarterPlayerScripts",
          "Internal": {
              "$path": "YuuwaPackages/src/internal/internal.client.lua" --Available as StarterPlayer/StarterPlayerScripts/Internal. This is an initter script (Because I use Knit).
          },

          "Game": {
              "$className": "Folder", 
              "Main": {
                  "$path": "src/client/Main" --Available as StarterPlayer/StarterPlayerScripts/Game/Main. This is where stuff that is shared across every place goes.
              },
              "Game": {
                  "$path": "src/client/Game" --Available as StarterPlayer/StarterPlayerScripts/Game/Game. This is where stuff that is related to game system goes (e.g. if its a racing game, modules related to car goes here)
              },
              "Local": {
                  "$path": "src/client/Place1" --Available as StarterPlayer/StarterPlayerScripts/Game/Local. This is the part that stores per-place modules!! e.g. src/client/Place2, src/client/Lobby, etc... 
              }
          }
      }
    }
  }
}

For example, one of my current project looks like:

Which will result as:

3 Likes

Do you know any more “cleaner” implementations of this? With it now, I would have src/clientPlace21 with how my experience is structured.

So they are multiple places like this:

  • Main Menu (Starter Place)
    • Game mode 1 (Not a place)
      • Map 1 (Place)
      • Map 2 (Place)
      • Map 3 (Place)
    • Game mode 2 (Place)
    • Game mode 3 (Place)

This example is really shortened but you get the point that we have a lot of places. Each game mode is needs it’s own unique code and I would prefer to have each game mode separated by folders in src but I’m confused on how to setup the project files.

1 Like

Hello,

While I still go with the folder structure as shown here, I found out that ultimately the way files are organized is not too important. Rather, its the project.json files that dictates the sync details, meaning you can basically organize your files in any ways you want.

In your case, I think its fine to mimic how you described the structure of your game, since it makes logical sense to group up the 3 maps that runs same game mode apart from the other 2 maps with different game modes each.

Folder structure:

  • common
    • server
    • client
    • shared
  • mainMenu
    • client
    • server
    • shared
  • gameMode1
    • map1
      • client
      • server
      • shared
    • map2
      • client
      • server
      • shared
    • map3
      • client
      • server
      • shared
  • gameMode2
    • client
    • server
    • shared
  • gameMode3
    • client
    • server
    • shared

Then, in project.json file, you can simply reference the necessary folders that should be synced

-- map 1 of game mode 1
"tree": {
    "$className": "DataModel",
    "ReplicatedStorage": {
        "$className": "ReplicatedStorage",
        "common": {"$path": "src/common/shared"}
        "local": {"$path": "src/gameMode1/map1/shared"}
    },
    "ServerScriptService": {
        "$className": "ServerScriptService"
        "common": {"$path": "src/common/server"},
        "local": {"$path": "src/gameMode1/map1/server"}
    },
    "StarterPlayer": {
        "$className": "StarterPlayer",
        "StarterPlayerScripts": {
            "$className": "StarterPlayerScripts"
            "common": {"$path": "src/common/client"},
            "local": {"$path": "src/gameMode1/map1/client"}
        }
    }
}
1 Like