A thriving NYC-based roleplay experience with over 1,000,000+ visits and 100+ concurrent users. Features detailed city environments, comprehensive roleplay systems, and persistent player progression.
A PS1-style survival horror game featuring sophisticated AI pathfinding, atmospheric lighting, and environmental storytelling. Includes custom character mechanics, inventory system, and cinematic elements for an immersive horror experience.
A collection of UI-based puzzles implemented using React-Lua, demonstrating modern web development practices in Roblox. Features multiple puzzle types with progressive difficulty scaling.
Roblox version of Mingle from Squid Game 2. Features a unique puzzle game with a focus on teamwork and strategy along with atmospheric lighting and a creepy atmosphere. Includes a custom character controller and inventory system.
Sophisticated enemy AI with state machine-driven behavior, A* pathfinding, and sensory perception systems.
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local EnemyAI = {}
EnemyAI.__index = EnemyAI
local RECALCULATION_DISTANCE = 5
local AWARENESS_RADIUS = 30
local HEARING_RADIUS = 20
local SIGHT_RANGE = 40
local SIGHT_ANGLE = math.rad(90)
local MOVEMENT_SPEED = {
PATROL = 8,
INVESTIGATE = 12,
CHASE = 16
}
function EnemyAI.new(enemyModel, spawnLocation)
local self = setmetatable({}, EnemyAI)
self.model = enemyModel
self.humanoid = enemyModel:WaitForChild("Humanoid")
self.rootPart = enemyModel:WaitForChild("HumanoidRootPart")
self.animator = self.humanoid:WaitForChild("Animator")
self.animations = {
idle = self.animator:LoadAnimation(enemyModel.Animations.Idle),
walk = self.animator:LoadAnimation(enemyModel.Animations.Walk),
run = self.animator:LoadAnimation(enemyModel.Animations.Run),
attack = self.animator:LoadAnimation(enemyModel.Animations.Attack),
investigate = self.animator:LoadAnimation(enemyModel.Animations.Investigate)
}
self.state = "PATROL"
self.patrolPoints = self:GeneratePatrolPoints(spawnLocation)
self.currentPatrolIndex = 1
self.target = nil
self.lastKnownPosition = nil
self.investigationTime = 0
self.sightObscured = false
self.path = nil
self.waypoints = {}
self.currentWaypoint = nil
self.pathRecomputing = false
self.pathParams = {
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentCanClimb = true,
WaypointSpacing = 4
}
self.debugMode = false
self.debugParts = {}
self:SetupBehaviors()
return self
end
function EnemyAI:GeneratePatrolPoints(center)
local points = {}
local numPoints = math.random(4, 8)
local radius = math.random(20, 50)
for i = 1, numPoints do
local angle = (i / numPoints) * math.pi * 2
local offset = Vector3.new(math.cos(angle) * radius, 0, math.sin(angle) * radius)
local point = center + offset
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {self.model}
local ray = workspace:Raycast(point + Vector3.new(0, 50, 0), Vector3.new(0, -100, 0), params)
if ray then
point = ray.Position
end
table.insert(points, point)
end
return points
end
function EnemyAI:SetupBehaviors()
self.behaviors = {
PATROL = function(dt)
if not self.currentWaypoint and #self.waypoints == 0 then
self:MoveTo(self.patrolPoints[self.currentPatrolIndex])
if self.humanoid.MoveToFinished:Wait(0.1) then
self.currentPatrolIndex = (self.currentPatrolIndex % #self.patrolPoints) + 1
task.wait(math.random(3, 6))
end
end
self.humanoid.WalkSpeed = MOVEMENT_SPEED.PATROL
self:PlayAnimation("walk")
if self:DetectPlayer() then
self:ChangeState("CHASE")
end
end,
INVESTIGATE = function(dt)
if self.lastKnownPosition then
if not self.currentWaypoint and #self.waypoints == 0 then
self:MoveTo(self.lastKnownPosition)
if (self.rootPart.Position - self.lastKnownPosition).Magnitude < 5 then
self.investigationTime = self.investigationTime + dt
self:PlayAnimation("investigate")
if self.investigationTime > 10 then
self.investigationTime = 0
self.lastKnownPosition = nil
self:ChangeState("PATROL")
end
end
end
self.humanoid.WalkSpeed = MOVEMENT_SPEED.INVESTIGATE
if not self.animations.investigate.IsPlaying then
self:PlayAnimation("walk")
end
if self:DetectPlayer() then
self:ChangeState("CHASE")
end
else
self:ChangeState("PATROL")
end
end,
CHASE = function(dt)
local player = self:GetNearestPlayer()
if player then
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
self.target = character
self.lastKnownPosition = character.HumanoidRootPart.Position
if not self:CanSeeTarget() then
self.sightObscured = true
task.delay(3, function()
if self.sightObscured and self.state == "CHASE" then
self:ChangeState("INVESTIGATE")
end
end)
else
self.sightObscured = false
self:MoveTo(character.HumanoidRootPart.Position)
end
if (self.rootPart.Position - character.HumanoidRootPart.Position).Magnitude < 4 then
self:PlayAnimation("attack")
local humanoid = character:FindFirstChild("Humanoid")
if humanoid then
humanoid:TakeDamage(20)
end
else
self:PlayAnimation("run")
end
self.humanoid.WalkSpeed = MOVEMENT_SPEED.CHASE
else
self:ChangeState("INVESTIGATE")
end
else
self:ChangeState("INVESTIGATE")
end
end
}
RunService.Heartbeat:Connect(function(dt)
if self.behaviors[self.state] then
self.behaviors[self.state](dt)
end
self:UpdatePath(dt)
end)
end
function EnemyAI:ChangeState(newState)
if newState ~= self.state then
self.state = newState
self.path = nil
self.waypoints = {}
self.currentWaypoint = nil
if self.debugMode then
print("Enemy state changed to: " .. newState)
end
end
end
function EnemyAI:CanSeeTarget()
if not self.target then return false end
local targetRoot = self.target:FindFirstChild("HumanoidRootPart")
if not targetRoot then return false end
local distanceToTarget = (targetRoot.Position - self.rootPart.Position).Magnitude
if distanceToTarget > SIGHT_RANGE then return false end
local lookVector = self.rootPart.CFrame.LookVector
local directionToTarget = (targetRoot.Position - self.rootPart.Position).Unit
local dotProduct = lookVector:Dot(directionToTarget)
if dotProduct < math.cos(SIGHT_ANGLE) then return false end
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Blacklist
rayParams.FilterDescendantsInstances = {self.model, self.target}
local result = workspace:Raycast(
self.rootPart.Position + Vector3.new(0, 1, 0),
directionToTarget * distanceToTarget,
rayParams
)
return result == nil
end
function EnemyAI:DetectPlayer()
local nearestPlayer = self:GetNearestPlayer()
if not nearestPlayer then return false end
local character = nearestPlayer.Character
if not character or not character:FindFirstChild("HumanoidRootPart") then return false end
local playerRoot = character.HumanoidRootPart
local distance = (playerRoot.Position - self.rootPart.Position).Magnitude
if distance <= SIGHT_RANGE then
self.target = character
if self:CanSeeTarget() then
self.lastKnownPosition = playerRoot.Position
return true
end
end
if distance <= HEARING_RADIUS then
local playerHumanoid = character:FindFirstChild("Humanoid")
if playerHumanoid and playerHumanoid.MoveDirection.Magnitude > 0.1 then
self.lastKnownPosition = playerRoot.Position
return true
end
end
return false
end
function EnemyAI:GetNearestPlayer()
local nearestDistance = AWARENESS_RADIUS
local nearestPlayer = nil
for _, player in pairs(Players:GetPlayers()) do
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
local distance = (character.HumanoidRootPart.Position - self.rootPart.Position).Magnitude
if distance < nearestDistance then
nearestDistance = distance
nearestPlayer = player
end
end
end
return nearestPlayer
end
function EnemyAI:MoveTo(position)
if self.pathRecomputing then return end
if self.currentWaypoint then
local distance = (position - self.lastTargetPosition).Magnitude
if distance < RECALCULATION_DISTANCE then return end
end
self.pathRecomputing = true
self.lastTargetPosition = position
local success, errorMessage = pcall(function()
self.path = PathfindingService:CreatePath(self.pathParams)
self.path:ComputeAsync(self.rootPart.Position, position)
if self.path.Status == Enum.PathStatus.Success then
self.waypoints = self.path:GetWaypoints()
self.currentWaypoint = nil
if #self.waypoints > 0 then
self.currentWaypoint = self.waypoints[1]
table.remove(self.waypoints, 1)
end
else
self.humanoid:MoveTo(position)
end
end)
if not success and self.debugMode then
warn("Pathfinding error: " .. tostring(errorMessage))
end
self.pathRecomputing = false
end
function EnemyAI:UpdatePath(dt)
if self.currentWaypoint then
local distance = (self.rootPart.Position - self.currentWaypoint.Position).Magnitude
if self.debugMode then
self:VisualizeWaypoint(self.currentWaypoint.Position)
end
self.humanoid:MoveTo(self.currentWaypoint.Position)
if distance < 4 then
if #self.waypoints > 0 then
self.currentWaypoint = self.waypoints[1]
table.remove(self.waypoints, 1)
else
self.currentWaypoint = nil
end
end
end
end
function EnemyAI:PlayAnimation(animName)
if not self.animations[animName] then return end
for name, anim in pairs(self.animations) do
if name ~= animName and anim.IsPlaying then
anim:Stop()
end
end
if not self.animations[animName].IsPlaying then
self.animations[animName]:Play()
end
end
function EnemyAI:VisualizeWaypoint(position)
local part = self.debugParts[1]
if not part then
part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.Size = Vector3.new(1, 1, 1)
part.Shape = Enum.PartType.Ball
part.Material = Enum.Material.Neon
part.Color = Color3.fromRGB(255, 0, 0)
part.Parent = workspace
table.insert(self.debugParts, part)
end
part.Position = position
end
function EnemyAI:Destroy()
for _, part in ipairs(self.debugParts) do
part:Destroy()
end
for _, anim in pairs(self.animations) do
if anim.IsPlaying then
anim:Stop()
end
end
self.path = nil
self.waypoints = {}
self.currentWaypoint = nil
end
return EnemyAI
A sophisticated dialogue system with branching conversations, state management, and interactive UI.
local NPCDialogue = {}
NPCDialogue.__index = NPCDialogue
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
function NPCDialogue.new(dialogueData)
local self = setmetatable({}, NPCDialogue)
self.dialogueData = dialogueData
self.currentNode = nil
self.isActive = false
self.ui = nil
return self
end
function NPCDialogue:Start(player, startNode)
if self.isActive then return end
self.isActive = true
self.currentNode = startNode or self.dialogueData.startNode
self:ShowDialogue(player)
end
function NPCDialogue:ShowDialogue(player)
self.ui = self:CreateDialogueUI()
self:UpdateDialogueContent()
self:AnimateUI()
end
function NPCDialogue:HandleChoice(choice)
if not self.currentNode.choices then return end
local nextNode = self.dialogueData.nodes[choice.nextNode]
if nextNode then
self.currentNode = nextNode
self:UpdateDialogueContent()
else
self:End()
end
end
function NPCDialogue:End()
self.isActive = false
if self.ui then
self.ui:Destroy()
self.ui = nil
end
end
return NPCDialogue
A React-Lua implementation of a 'Lights Out' style puzzle game with progressive difficulty.
local ChainReaction = {}
ChainReaction.__index = ChainReaction
local React = require(game.ReplicatedStorage.Packages.React)
local e = React.createElement
function ChainReaction.new(props)
local self = setmetatable({}, ChainReaction)
self.size = props.size or 5
self.grid = {}
self.moves = 0
self:InitializeGrid()
return self
end
function ChainReaction:InitializeGrid()
for i = 1, self.size do
self.grid[i] = {}
for j = 1, self.size do
self.grid[i][j] = false
end
end
end
function ChainReaction:ToggleCell(x, y)
-- Toggle clicked cell and adjacent cells
self:SetCell(x, y, not self.grid[x][y])
self:SetCell(x+1, y, not self.grid[x+1][y])
self:SetCell(x-1, y, not self.grid[x-1][y])
self:SetCell(x, y+1, not self.grid[x][y+1])
self:SetCell(x, y-1, not self.grid[x][y-1])
self.moves = self.moves + 1
self:CheckWinCondition()
end
function ChainReaction:SetCell(x, y, value)
if x >= 1 and x <= self.size and y >= 1 and y <= self.size then
self.grid[x][y] = value
end
end
function ChainReaction:CheckWinCondition()
for i = 1, self.size do
for j = 1, self.size do
if self.grid[i][j] then
return false
end
end
end
return true
end
return ChainReaction
A Knit service managing door interactions, animations, and state synchronization.
local DoorService = {}
DoorService.__index = DoorService
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Promise = require(game:GetService("ReplicatedStorage").Packages.Promise)
function DoorService:KnitStart()
self.doors = {}
self:InitializeDoors()
end
function DoorService:InitializeDoors()
for _, door in ipairs(workspace.Doors:GetChildren()) do
if door:IsA("Model") then
self.doors[door.Name] = {
instance = door,
isLocked = door:GetAttribute("Locked") or false,
isOpen = false,
animation = door:FindFirstChild("AnimationController")
}
end
end
end
function DoorService.Client:InteractWithDoor(player, doorName)
return Promise.new(function(resolve, reject)
local door = self.Server.doors[doorName]
if not door then
return reject("Door not found")
end
if door.isLocked then
return reject("Door is locked")
end
door.isOpen = not door.isOpen
self.Server:AnimateDoor(door)
resolve(door.isOpen)
end)
end
function DoorService:AnimateDoor(door)
if door.animation then
local track = door.animation:LoadAnimation(
door.isOpen and self.openAnim or self.closeAnim
)
track:Play()
end
end
return DoorService
Liberty Heights NYC: 1,000,000+ visits, 100+ concurrent users, 60 FPS on mobile, 47% memory reduction
Lead Developer, Systems Architecture, 5-person team leadership, Agile methodology
Available for Roblox development projects. Experienced in rapid adaptation to existing codebases and team workflows. Strong focus on code quality, performance optimization, and scalable architecture.
© 2025 boshy | Roblox Development Portfolio