I’m not sure if there’s already been a post on this type of stuff, but I’m just gonna post this anyway.
Hey Devforum! For the longest time, I’ve been trying to create a Real-Time Strategy Game (or RTS Game), but I’m lowkey just too lazy to actually finish one. Also, for me, it was pretty hard to get all of this stuff, so I’m going to put all of my Scripts and Models here for anybody who wants it.
World Cities Data ModuleScript
For anybody who doesn’t know what the World Cities Database is, it’s just data on (almost) every single city on Earth. By data, I’m talking about the city’s real-world position, its ruling country, etc.
Basically, I made a free model for the World Cities Database (free version) converted into a ModuleScript. You can get it here.
Country Capitals ModuleScript
This ModuleScript has (I think) every city’s capital. This ModuleScript was made with ChatGPT, so it also might not match with World Cities Database, but when I ran it, it was pretty good for the most part. Just thought I might include this ModuleScript too.
You can get the ModuleScript Model here.
Inserting the World Cities onto a Globe
I used ChatGPT to make this Script (because I have absolutely no idea how to actually do this myself), and it works pretty well.
If you want, you can uncomment the bottom part of the Script that creates a ClickDetector. The ClickDetector just prints out whatever city you clicked on and its properties (like population and stuff).
--// I highly recommend putting this into a Script inside of the ServerScriptService. You can delete this line if you want to.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local WorldCities = require(--// The Path to your WorldCities ModuleScript. (example: ReplicatedStorage:WaitForChild("WorldCities"))
local CountryCapitals = require(--// The Path to your CountryCapitals ModuleScript. (example: ReplicatedStorage:WaitForChild("CountryCapitals"))
local globe = workspace:WaitForChild("Earth"):WaitForChild("Earth_Model"):WaitForChild("Earth") --// If you don't have an Earth Globe Model, you can use the one listed below.
local radius = globe.Size.X / 2
local longitudeOffset = 191.5
local CountryParts = Instance.new("Folder")
CountryParts.Name = "CountryParts"
CountryParts.Parent = workspace
local customRotation = {
X = 0,
Y = 90,
Z = 90
}
local function latLonToPosition(lat, lon, radius)
local latRad = math.rad(lat)
local lonRad = math.rad(lon + longitudeOffset)
local x = radius * math.cos(latRad) * math.sin(lonRad)
local y = radius * math.sin(latRad)
local z = radius * math.cos(latRad) * math.cos(lonRad)
return Vector3.new(-x, y, -z)
end
-- Create city markers
for _, city in ipairs(WorldCities) do
if city.population then
local marker = Instance.new("Part")
marker.Name = city.name
marker.Anchored = true
marker.CanCollide = false
marker.Material = Enum.Material.Neon
local isCapital = (CountryCapitals[city.country] == city.name)
marker.Shape = Enum.PartType.Cylinder
marker.BrickColor = BrickColor.new("Bright orange")
if city.population >= 1000000 then
marker.Size = Vector3.new(0.5, 1.2, 0.4)
elseif city.population >= 300000 then
marker.Size = Vector3.new(0.5, 0.9, 0.3)
else
marker.Size = Vector3.new(0.5, 0.6, 0.2)
end
local localPosition = latLonToPosition(city.lat, city.lon, radius + 0.1)
local worldPosition = globe.Position + localPosition
local up = (worldPosition - globe.Position).Unit
local forward = up:Cross(Vector3.new(0, 1, 0)).Unit
if forward.Magnitude == 0 then forward = Vector3.new(1, 0, 0) end
local right = forward:Cross(up).Unit
local baseCFrame = CFrame.fromMatrix(worldPosition, right, up)
local rotationOffset = CFrame.Angles(
math.rad(customRotation.X),
math.rad(customRotation.Y),
math.rad(customRotation.Z)
)
local finalCFrame = baseCFrame * rotationOffset
local latMultiplier = math.abs(city.lat) / 90
local outwardOffset = 0.5 + (latMultiplier * 0.5)
local adjustedPosition = worldPosition + up * outwardOffset
local baseCFrame = CFrame.fromMatrix(adjustedPosition, right, up)
marker.CFrame = baseCFrame * CFrame.Angles(
math.rad(customRotation.X),
math.rad(customRotation.Y),
math.rad(customRotation.Z)
)
local RulingCountryFolder = CountryParts:FindFirstChild(city.country)
if not RulingCountryFolder then
RulingCountryFolder = Instance.new("Folder")
RulingCountryFolder.Name = city.country
RulingCountryFolder.Parent = CountryParts
end
marker.Parent = RulingCountryFolder
--[[local clickDetector = Instance.new("ClickDetector")
clickDetector.MaxActivationDistance = 999999999
clickDetector.Parent = marker
clickDetector.MouseClick:Connect(function(player)
print("City Name:", city.name)
print("Ruling Country:", city.country)
print("Population:", city.population)
if isCapital then
print("Capital")
end
end)]]--
end
end
Inserting the World Cities onto a Flat Map
I used ChatGPT for this, too, because I also don’t really know how to script this myself. It works fine, though.
--// I highly recommend putting this into a Script inside of the ServerScriptService. You can delete this line if you want to.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local WorldCities = require(--// The path to your WorldCities ModuleScript. (example: ReplicatedStorage:WaitForChild("WorldCities"))
local CountryCapitals = require(--// The path to your CountryCapitals ModuleScript. (example: ReplicatedStorage:WaitForChild("CountryCapitals"))
local mapModel = workspace:WaitForChild("Map") --// If you don't have a Map Model, create a Model in the Workspace named "Map", then insert an Anchored Part with the size of whatever you want the Map to be.
task.wait()
-- Create a folder in Workspace to hold all countries
local countriesFolder = Instance.new("Folder")
countriesFolder.Name = "Countries"
countriesFolder.Parent = workspace
-- Create folders for each country based on CountryColors
local countryFolders = {}
-- Function to calculate map size properly
local function GetTrueBoundingBox(model)
local minVec = Vector3.new(math.huge, math.huge, math.huge)
local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
for _, part in ipairs(model:GetDescendants()) do
if part:IsA("BasePart") then
local cf = part.CFrame
local size = part.Size
local corners = {
cf * Vector3.new(-size.X/2, -size.Y/2, -size.Z/2),
cf * Vector3.new(-size.X/2, -size.Y/2, size.Z/2),
cf * Vector3.new(-size.X/2, size.Y/2, -size.Z/2),
cf * Vector3.new(-size.X/2, size.Y/2, size.Z/2),
cf * Vector3.new( size.X/2, -size.Y/2, -size.Z/2),
cf * Vector3.new( size.X/2, -size.Y/2, size.Z/2),
cf * Vector3.new( size.X/2, size.Y/2, -size.Z/2),
cf * Vector3.new( size.X/2, size.Y/2, size.Z/2),
}
for _, corner in ipairs(corners) do
minVec = Vector3.new(
math.min(minVec.X, corner.X),
math.min(minVec.Y, corner.Y),
math.min(minVec.Z, corner.Z)
)
maxVec = Vector3.new(
math.max(maxVec.X, corner.X),
math.max(maxVec.Y, corner.Y),
math.max(maxVec.Z, corner.Z)
)
end
end
end
local center = (minVec + maxVec) / 2
local size = maxVec - minVec
return CFrame.new(center), size
end
local mapCFrame, mapSize = GetTrueBoundingBox(mapModel)
local mapCenter = mapCFrame.Position
-- Coordinate bounds for the map
local lonMin, lonMax = -200, 160
local latMin, latMax = -70, 74
-- Population thresholds
local populationThreshold = 1000000000 -- Cities above this are large
local minPopulation = 50000 -- Cities below this are ignored
-- Size scaling
local smallSize = 0.5
local mediumSize = 1
local largeSize = 1.5
-- Place cities
for _, city in ipairs(WorldCities) do
local lat = city.lat
local lon = city.lon
local population = city.population
if population < minPopulation then
continue
end
local xRatio = (lon - lonMin) / (lonMax - lonMin)
local zRatio = (latMax - lat) / (latMax - latMin)
local xOffset = (xRatio - 0.5) * mapSize.X
local zOffset = (zRatio - 0.5) * mapSize.Z
local yOffset = 1
local X_TWEAK = -(80)
local Z_TWEAK = 19
local Y_TWEAK = -(0.5)
local X_TWEAK = -80
local Z_TWEAK = 19
local Y_TWEAK = -0.5
local position = mapCenter + Vector3.new(xOffset + X_TWEAK, yOffset + Y_TWEAK, zOffset + Z_TWEAK)
local dot
local sizeMultiplier = smallSize
if population >= populationThreshold then
sizeMultiplier = largeSize
dot = Instance.new("Part")
dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
dot.Shape = Enum.PartType.Block
elseif population >= 1000000 then
sizeMultiplier = mediumSize
dot = Instance.new("Part")
dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
dot.Shape = Enum.PartType.Block
else
dot = Instance.new("Part")
dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
dot.Shape = Enum.PartType.Block
end
dot.Position = position
dot.Anchored = true
dot.Material = Enum.Material.Neon
dot.Name = city.name
local countryColor = BrickColor.new("Bright orange")
dot.BrickColor = countryColor
-- Assign to country folder if it exists
local countryFolder = countryFolders[city.country]
if countryFolder then
dot.Parent = countryFolder
else
dot.Parent = countriesFolder -- fallback
end
-- Check if the city is a capital and make the part a square and bright orange with smaller size
if CountryCapitals[city.country] == city.name then
dot.Size = Vector3.new(2, 2, 2) -- Reduced size for capitals
dot.BrickColor = BrickColor.new("Bright orange") -- Set color to bright orange
dot.Shape = Enum.PartType.Block -- Ensure it's a block shape
end
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = dot
clickDetector.MaxActivationDistance = 9999999999
clickDetector.MouseClick:Connect(function()
if CountryCapitals[city.country] == city.name then
print("You clicked on:", city.name .. ", the Capital of " .. city.country .. ", with a Population of about " .. city.population)
else
print("You clicked on:", city.name .. ", " .. city.country .. ", with a Population of about " .. city.population)
end
end)
end
Earth Globe Model
Here is a Model of Earth I made a while ago. To insert cities on this Globe Model, you need to use the “Inserting the World Cities onto a Globe” section of this post.
If you have any questions/comments, feel free to comment them! Thanks for checking out my post!