Initial MVP: drivable RX-7 vertical slice + server-authoritative systems
CI / quality (push) Failing after 5m53s
CI / quality (push) Failing after 5m53s
- Rojo + Wally project; ProfileStore (loleris) for safe saves - Shared: GameConfig, Cars (RX-7 free starter), ProfileTemplate, Remotes - Server: DataManager, Economy, CarManager, RaceManager, MapBuilder, AntiCheat - Client: neon HUD + Garage/Dealership UI (buy/equip/upgrade), race/daily - Procedural Tokyo expressway (spawn pad, finish line, neon pylons, kill walls) - CI workflow (Gitea/GitHub), Makefile, selene+stylua clean, rojo builds - Docs: README, GDD, realistic solo ROADMAP
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
--!strict
|
||||
--[[
|
||||
MapBuilder.luau (SERVER)
|
||||
Builds the MVP "Tokyo expressway" vertical slice entirely from code so the
|
||||
game is playable with ZERO manual Studio building. This is a straight,
|
||||
banked-guardrail highway segment with a start pad, a finish line, neon-ish
|
||||
side pylons, and invisible kill walls so you can't drive off the edge.
|
||||
|
||||
Later you replace this with a proper modeled map; the rest of the game
|
||||
(spawns, races) reads named parts so it keeps working.
|
||||
]]
|
||||
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local MapBuilder = {}
|
||||
|
||||
local ROAD_WIDTH = 60
|
||||
local SEGMENT_LENGTH = 400
|
||||
local NUM_SEGMENTS = 6 -- total length = 2400 studs of highway
|
||||
local WALL_HEIGHT = 20
|
||||
|
||||
local function part(props: { [string]: any }): Part
|
||||
local p = Instance.new("Part")
|
||||
p.Anchored = true
|
||||
p.TopSurface = Enum.SurfaceType.Smooth
|
||||
p.BottomSurface = Enum.SurfaceType.Smooth
|
||||
for k, v in pairs(props) do
|
||||
(p :: any)[k] = v
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
function MapBuilder.build(): Folder
|
||||
local existing = Workspace:FindFirstChild("Map")
|
||||
if existing then
|
||||
existing:Destroy()
|
||||
end
|
||||
|
||||
local map = Instance.new("Folder")
|
||||
map.Name = "Map"
|
||||
|
||||
local roadFolder = Instance.new("Folder")
|
||||
roadFolder.Name = "Expressway"
|
||||
roadFolder.Parent = map
|
||||
|
||||
local totalLength = SEGMENT_LENGTH * NUM_SEGMENTS
|
||||
|
||||
-- Road deck (segmented so you can later bank/curve individual pieces) ------
|
||||
for i = 0, NUM_SEGMENTS - 1 do
|
||||
local z = -(i * SEGMENT_LENGTH) - SEGMENT_LENGTH / 2
|
||||
local road = part({
|
||||
Name = "Road_" .. i,
|
||||
Size = Vector3.new(ROAD_WIDTH, 2, SEGMENT_LENGTH),
|
||||
Position = Vector3.new(0, 0, z),
|
||||
Color = Color3.fromRGB(28, 28, 34),
|
||||
Material = Enum.Material.Asphalt,
|
||||
})
|
||||
road.Parent = roadFolder
|
||||
|
||||
-- Center lane line
|
||||
local line = part({
|
||||
Name = "Lane_" .. i,
|
||||
Size = Vector3.new(1, 2.1, SEGMENT_LENGTH),
|
||||
Position = Vector3.new(0, 0.1, z),
|
||||
Color = Color3.fromRGB(230, 200, 60),
|
||||
Material = Enum.Material.Neon,
|
||||
})
|
||||
line.Parent = roadFolder
|
||||
end
|
||||
|
||||
-- Guardrails + invisible kill walls on both sides -------------------------
|
||||
for _, side in ipairs({ -1, 1 }) do
|
||||
local x = side * (ROAD_WIDTH / 2)
|
||||
local rail = part({
|
||||
Name = "Guardrail_" .. (side == -1 and "L" or "R"),
|
||||
Size = Vector3.new(1.5, 4, totalLength),
|
||||
Position = Vector3.new(x, 2, -totalLength / 2),
|
||||
Color = Color3.fromRGB(180, 180, 190),
|
||||
Material = Enum.Material.Metal,
|
||||
})
|
||||
rail.Parent = map
|
||||
|
||||
-- Invisible wall: solid, tall, non-rendered — the "can't leave" barrier.
|
||||
local wall = part({
|
||||
Name = "KillWall_" .. (side == -1 and "L" or "R"),
|
||||
Size = Vector3.new(2, WALL_HEIGHT, totalLength),
|
||||
Position = Vector3.new(x + side * 2, WALL_HEIGHT / 2, -totalLength / 2),
|
||||
Transparency = 1,
|
||||
CanCollide = true,
|
||||
})
|
||||
wall.Parent = map
|
||||
|
||||
-- Neon pylons for that cyberpunk Tokyo glow (cheap: one every 200 studs)
|
||||
for i = 0, NUM_SEGMENTS * 2 do
|
||||
local pylon = part({
|
||||
Name = "Pylon",
|
||||
Size = Vector3.new(1, 14, 1),
|
||||
Position = Vector3.new(x + side * 3, 7, -(i * 200)),
|
||||
Color = side == -1 and Color3.fromRGB(255, 40, 120) or Color3.fromRGB(40, 160, 255),
|
||||
Material = Enum.Material.Neon,
|
||||
})
|
||||
pylon.Parent = map
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawn pad (where cars appear) at the start ------------------------------
|
||||
local pad = part({
|
||||
Name = "SpawnPad",
|
||||
Size = Vector3.new(ROAD_WIDTH - 6, 1, 40),
|
||||
Position = Vector3.new(0, 1.5, -20),
|
||||
Color = Color3.fromRGB(20, 220, 120),
|
||||
Material = Enum.Material.Neon,
|
||||
Transparency = 0.4,
|
||||
})
|
||||
pad.Parent = map
|
||||
|
||||
-- Finish line at the far end ----------------------------------------------
|
||||
local finish = part({
|
||||
Name = "FinishLine",
|
||||
Size = Vector3.new(ROAD_WIDTH, 1, 6),
|
||||
Position = Vector3.new(0, 1.5, -totalLength + 30),
|
||||
Color = Color3.fromRGB(240, 240, 240),
|
||||
Material = Enum.Material.Neon,
|
||||
Transparency = 0.3,
|
||||
})
|
||||
finish.Parent = map
|
||||
|
||||
-- A floor far below to catch anything that clips through (safety net) ------
|
||||
local floor = part({
|
||||
Name = "SafetyFloor",
|
||||
Size = Vector3.new(ROAD_WIDTH * 4, 2, totalLength + 400),
|
||||
Position = Vector3.new(0, -60, -totalLength / 2),
|
||||
Transparency = 1,
|
||||
CanCollide = true,
|
||||
})
|
||||
floor.Parent = map
|
||||
|
||||
map.Parent = Workspace
|
||||
return map
|
||||
end
|
||||
|
||||
return MapBuilder
|
||||
Reference in New Issue
Block a user