Dynamic place creation and saving

(Redirected from CreatePlace and SavePlace)

ROBLOX is introducing a new API allowing users to create and save places through game scripts. With the introduction of data persistence features, builders will now be able to make games that automatically create places that belong to the builder's game. This opens up possibilities to custom building games, and even MMORPG-style player housing.

Usage[edit]

Using the create place calls in AssetService copies an existing place in game and saves it into either the developer's inventory or the player's inventory. This new place is still part of the original game, so it has access to the same data stores.

Enabling place creation and saving[edit]

Before a place can be used as a template with CreatePlace or saved with SavePlace, these features need to be enabled for your place. To do this, log into ROBLOX and click on the Develop tab. Then, select Places from the list on the left. This will open a list of all the places in your inventory. Click on the Configure PluginEditButton.png button to the right of the place you want to use as a template and then select Games on the left. You can then set whether the place can be used as a template and if the place can be saved. CreatePlaceConfigure.png

Adding a template place[edit]

To add a place as a template for a game, log into the ROBLOX site, click on the Develop tab and then select "Games". Next to the Start Place for your game, select the configure PluginEditButton.png button. From there, select Places and then click Add Place. This will allow you to add any of your other places into the game, which will allow it to be used as a template for CreatePlace. Note that the place must be enabled before it can be created in your game. Game-and-a-Place.png

Limitations[edit]

Both SavePlace and CreatePlace are throttled mechanics. To use these events properly you must have something that catches exceptions and acts accordingly. The exceptions are as follows:

Game:CreatePlace requests limit reached

  • Create Place has a limit of (5 + #ofPlayers)/minute requests and you will receive this exception if you exceed this amount.
GameCreatePlaceLimit.PNG

Game:SavePlace requests limit reached

  • Save Place has a limit of 10/minute requests and you will receive this exception if you exceed this amount.
GameSavePlaceLimit.PNG

Key API[edit]

Methods[edit]

CreatePlaceAsync(name, templatePlaceId, description)[edit]

Creates a new place as a copy of the place with place Id of templatePlaceId and applies the provided name and description to the new place. Returns place Id of new place (which can be used with Teleport Service). This place is stored in the inventory of the developer.

CreatePlaceInPlayerInventoryAsync(owningPlayer, name, templatePlaceId, description)[edit]

Note: Places created using this method can be edited by the user whose inventory it's added to, allowing them to copy the place regardless of the copylocked setting on the template place used.That player can open the place in studio, but not actually save it online. (You can't publish it to the same place)

Creates a new place as a copy of the place with place Id of templatePlaceId and applies the provided name and description to the new place. Returns place ID of new place, which can be used with teleport service. This place is stored in the inventory of the Player designated by owningPlayer, assuming that the specified player gives permission for a place to be put in their inventory when prompted.

SavePlaceAsync()[edit]

Saves the state of the current place. This only works for places created with CreatePlaceAsync() and CreatePlaceInPlayerInventoryAsync().

Sample Place[edit]

Note: This example assumes you are familiar with how to connect Lua functions to Roblox events, along with general Lua Scripting.

A simple example of implementing these services could be a building game where there is a common lobby for all players to meet, but every player has an individual building zone where they have freedom to build whatever they want without interruption from other players. A way to implement such a game on a basic level would leverage the use of the CreatePlace and SavePlace functions. A game like this would require the Developer to create a lobby for all users to join at the beginning of the game, but also have a system where a new player zone is created for every new user that wants to participate in building. In order to implement this properly the Developer would need two places: A Lobby, and a Template Player Zone.

The lobby would be in charge of creating a new zone for every new player that wants to have their own building zone. With the use of DataStores and CreatePlace, a Developer could create a system where because of a certain event a user triggers a DataStore check of whether they played this game before and either creates a new place to build in, or fetches the one they already started playing on before. This type of Lobby would utilize the new DataStore, CreatePlace, and TeleportService features.

This whole sample is available on ROBLOX. Here is the Lobby Zone, and here is the Player Zone. Neither are code locked, so feel free to edit the samples to see how they work.

Lobby: Create/Teleport To Personal Place Pad[edit]

Basic example of a lobby, including: a blue spawn pad, and a red CreatePlace/Teleport pad.

This Lobby could have a red teleportation Pad with the following script as its child.

game:GetService('TeleportService').CustomizedTeleportUI = true
 
ds = game:GetService("DataStoreService"):GetGlobalDataStore()
 
local Trigger = script.Parent
 
-- debouncer that prevents multiple trigger events by the player who is standing nearby.
function debounce(func)
    local isRunning = false
    return function(...)
        if not isRunning then
            isRunning = true
            func(...)
            isRunning = false
        end
    end
end
 
function getPlayer(Part)
	local Humanoid = Part.Parent:FindFirstChild('Humanoid')
	if (Humanoid ~= nil) then
		local Character = Humanoid.Parent
		if (Character ~= nil) then
			return game:GetService('Players'):GetPlayerFromCharacter(Character)		
		end
	end
end
 
function _sendPlayerToBuildZone(player)
	local playerIdentity = player.Name .. '(' .. player.userId .. ')'
 
	local playerKey = 'player_' .. player.userId
	local playerData = ds:GetAsync(playerKey)
 
	if playerData then
		if playerData.personalPlaceId <= 0 then
			playerData = nil
		end	
	end
 
	if not playerData then				
		newPlaceId = game:GetService(“AssetService”):CreatePlaceAsync('Building zone for ' .. playerIdentity, 92697995)
 
		playerData = {
			personalPlaceId = newPlaceId					
		}
 
		ds:SetAsync(playerKey, playerData)	
	end			
 
	if playerData and playerData.personalPlaceId then	
		game:GetService('TeleportService'):Teleport(playerData.personalPlaceId, player)			
	end	
end
local sendPlayerToBuildZone = debounce(_sendPlayerToBuildZone)
 
-- Touch event function
function onTouch(Source)
	local player = getPlayer(Source)	
	if player then
		if player.userId > 0 then		
			sendPlayerToBuildZone(player)
		end		
	end	
end
 
Trigger.Touched:connect(onTouch)

Now the problem with this code alone is that it doesn't do very much without a template to create a Player Zone from. If you look at the line newPlaceId = game:CreatePlace('Building zone for ' .. playerIdentity, 92697995), you will notice that this lobby uses the place with ID 92697995 to create personal player zones. This means that either 92697995 is the ID of our Player Zone template, or that we should create a template zone and change the ID in the above script to our new place's ID. Since we haven't created a template zone yet, we have to go with option number two.

Player Zone: SavePlace Pad[edit]

Basic example of a Player Zone, including: A green SavePlace pad, a red spawn pad, and a blue Return to Lobby Pad

The template zone has to serve three basic functions: Allowing the user to build, letting the user then save what they have built, and then also having a way to return to the lobby. To make our example work we can create a template place that has a Blue Pad that takes you back to the Lobby, a Green Pad that saves the place, and also a Red Pad that serves as the Teleporter target for when you first Teleport into the place from the lobby.

The save place pad can be an arbitrary part with a child script that has three primary functions: Debouncing (prevent multiple touch events), asserting that the parent of the touch event is a player, and finally the saver. You can imagine with a few extra features added, the script could look like this.

local Trigger = script.Parent
 
function debounce(func)
    local isRunning = false
    return function(...)
        if not isRunning then
            isRunning = true
            func(...)
            isRunning = false
        end
    end
end
 
function getPlayer(Part)
	local Humanoid = Part.Parent:FindFirstChild('Humanoid')
	if (Humanoid ~= nil) then
		local Character = Humanoid.Parent
		if (Character ~= nil) then
			return game:GetService('Players'):GetPlayerFromCharacter(Character)		
		end
	end
end
 
function _savePlace(player)
	game:GetService(“AssetService”):SavePlaceAsync()
end
local savePlace = debounce(_savePlace)
 
function onTouch(Source)
	local player = getPlayer(Source)	
	if player then
		if player.userId > 0 then	
			local success, error = ypcall(function() savePlace(player) end)
		end		
	end	
end
 
Trigger.Touched:connect(onTouch)

Player Zone: Return to Lobby Pad[edit]

This pad is a bit simpler than the red pad that brings the user to the building zone. This pad never has to query DataStore in order to know where to direct the place, it only ever points to one place, the lobby. With some basic debouncing and asserting that the parent of the touch event is a player, the script can look something like this.

game:GetService('TeleportService').CustomizedTeleportUI = true
 
local Trigger = script.Parent
 
function debounce(func)
    local isRunning = false
    return function(...)
        if not isRunning then
            isRunning = true
            func(...)
            isRunning = false
        end
    end
end
 
function getPlayer(Part)
	local Humanoid = Part.Parent:FindFirstChild('Humanoid')
	if (Humanoid ~= nil) then
		local Character = Humanoid.Parent
		if (Character ~= nil) then
			return game:GetService('Players'):GetPlayerFromCharacter(Character)		
		end
	end
end
 
function _sendPlayerToLobby(player)
	local playerIdentity = player.Name .. '(' .. player.userId .. ')'
	game:GetService('TeleportService'):Teleport(92697964, player)	
end
local sendPlayerToLobby = debounce(_sendPlayerToLobby)
 
function onTouch(Source)
	local player = getPlayer(Source)	
	if player then
		if player.userId > 0 then	
			sendPlayerToLobby(player)
		end		
	end	
end
 
Trigger.Touched:connect(onTouch)

Just like we had to make sure that the templateID was pointing to the right place, with this teleport pad we have to make sure that the teleporter points back to the lobby. Since we can assume you have already uploaded a lobby, you must replace the ID in game:GetService('TeleportService'):Teleport(92697964, player) with your lobby's ID.