522 lines
15 KiB
Lua
522 lines
15 KiB
Lua
--BackpackScript3D: VR port of backpack interface using a 3D panel
|
|
--written by 0xBAADF00D
|
|
local ICON_SIZE = 48
|
|
local ICON_SPACING = 52
|
|
local PIXELS_PER_STUD = 64
|
|
|
|
local SLOT_BORDER_SIZE = 0
|
|
local SLOT_BORDER_SELECTED_SIZE = 4
|
|
local SLOT_BORDER_COLOR = Color3.new(90/255, 142/255, 233/255)
|
|
local SLOT_BACKGROUND_COLOR = Color3.new(0.2, 0.2, 0.2)
|
|
local SLOT_HOVER_BACKGROUND_COLOR = Color3.new(90/255, 90/255, 90/255)
|
|
|
|
local HOPPERBIN_ANGLE = math.rad(-45)
|
|
local HOPPERBIN_ROTATION = CFrame.Angles(HOPPERBIN_ANGLE, 0, 0)
|
|
local HOPPERBIN_OFFSET = Vector3.new(0, 0, -5)
|
|
|
|
local HEALTHBAR_SPACE = 12
|
|
local HEALTHBAR_WIDTH = 82
|
|
local HEALTHBAR_HEIGHT = 5
|
|
|
|
local NAME_SPACE = 14
|
|
|
|
local Tools = {}
|
|
local ToolsList = {}
|
|
local slotIcons = {}
|
|
|
|
local BackpackScript = {}
|
|
local topbarEnabled = false
|
|
|
|
local player = game:GetService("Players").LocalPlayer
|
|
local currentHumanoid = nil
|
|
local CoreGui = game:GetService('CoreGui')
|
|
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
|
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
|
|
local Util = require(RobloxGui.Modules.Settings.Utility)
|
|
|
|
local ContextActionService = game:GetService("ContextActionService")
|
|
|
|
local BackpackPanel = Panel3D.Get("Backpack")
|
|
BackpackPanel:ResizeStuds(5, 2)
|
|
BackpackPanel:SetType(Panel3D.Type.Fixed, { CFrame = CFrame.new(0, 0, -5) })
|
|
BackpackPanel:SetVisible(true)
|
|
|
|
local toolsFrame = Instance.new("TextButton", BackpackPanel:GetGUI()) --prevent clicks falling through in case you have a rocket launcher and blow yourself up
|
|
toolsFrame.Text = ""
|
|
toolsFrame.Size = UDim2.new(1, 0, 0, ICON_SIZE)
|
|
toolsFrame.BackgroundTransparency = 1
|
|
toolsFrame.Selectable = false
|
|
local insetAdjustY = toolsFrame.AbsolutePosition.Y
|
|
toolsFrame.Position = UDim2.new(0, 0, 0, HEALTHBAR_SPACE + NAME_SPACE)
|
|
|
|
--Healthbar color function stolen from Topbar.lua
|
|
local HEALTH_BACKGROUND_COLOR = Color3.new(228/255, 236/255, 246/255)
|
|
local HEALTH_RED_COLOR = Color3.new(255/255, 28/255, 0/255)
|
|
local HEALTH_YELLOW_COLOR = Color3.new(250/255, 235/255, 0)
|
|
local HEALTH_GREEN_COLOR = Color3.new(27/255, 252/255, 107/255)
|
|
|
|
local healthbarBack = Instance.new("ImageLabel", BackpackPanel:GetGUI())
|
|
healthbarBack.ImageColor3 = HEALTH_BACKGROUND_COLOR
|
|
healthbarBack.BackgroundTransparency = 1
|
|
healthbarBack.ScaleType = Enum.ScaleType.Slice
|
|
healthbarBack.SliceCenter = Rect.new(10, 10, 10, 10)
|
|
healthbarBack.Name = "HealthbarContainer"
|
|
healthbarBack.Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png"
|
|
local healthbarFront = Instance.new("ImageLabel", healthbarBack)
|
|
healthbarFront.ImageColor3 = HEALTH_GREEN_COLOR
|
|
healthbarFront.BackgroundTransparency = 1
|
|
healthbarFront.ScaleType = Enum.ScaleType.Slice
|
|
healthbarFront.SliceCenter = Rect.new(10, 10, 10, 10)
|
|
healthbarFront.Size = UDim2.new(1, 0, 1, 0)
|
|
healthbarFront.Position = UDim2.new(0, 0, 0, 0)
|
|
healthbarFront.Name = "HealthbarFill"
|
|
healthbarFront.Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png"
|
|
|
|
local playerName = Instance.new("TextLabel", BackpackPanel:GetGUI())
|
|
playerName.Name = "PlayerName"
|
|
playerName.BackgroundTransparency = 1
|
|
playerName.TextColor3 = Color3.new(1, 1, 1)
|
|
playerName.Text = player.Name
|
|
playerName.Font = Enum.Font.SourceSansBold
|
|
playerName.FontSize = Enum.FontSize.Size12
|
|
playerName.TextXAlignment = Enum.TextXAlignment.Left
|
|
playerName.Size = UDim2.new(1, 0, 0, NAME_SPACE)
|
|
|
|
|
|
BackpackScript.ToolAddedEvent = Instance.new("BindableEvent")
|
|
|
|
|
|
local healthColorToPosition = {
|
|
[Vector3.new(HEALTH_RED_COLOR.r, HEALTH_RED_COLOR.g, HEALTH_RED_COLOR.b)] = 0.1;
|
|
[Vector3.new(HEALTH_YELLOW_COLOR.r, HEALTH_YELLOW_COLOR.g, HEALTH_YELLOW_COLOR.b)] = 0.5;
|
|
[Vector3.new(HEALTH_GREEN_COLOR.r, HEALTH_GREEN_COLOR.g, HEALTH_GREEN_COLOR.b)] = 0.8;
|
|
}
|
|
local min = 0.1
|
|
local minColor = HEALTH_RED_COLOR
|
|
local max = 0.8
|
|
local maxColor = HEALTH_GREEN_COLOR
|
|
|
|
local function HealthbarColorTransferFunction(healthPercent)
|
|
if healthPercent < min then
|
|
return minColor
|
|
elseif healthPercent > max then
|
|
return maxColor
|
|
end
|
|
|
|
-- Shepard's Interpolation
|
|
local numeratorSum = Vector3.new(0,0,0)
|
|
local denominatorSum = 0
|
|
for colorSampleValue, samplePoint in pairs(healthColorToPosition) do
|
|
local distance = healthPercent - samplePoint
|
|
if distance == 0 then
|
|
-- If we are exactly on an existing sample value then we don't need to interpolate
|
|
return Color3.new(colorSampleValue.x, colorSampleValue.y, colorSampleValue.z)
|
|
else
|
|
local wi = 1 / (distance*distance)
|
|
numeratorSum = numeratorSum + wi * colorSampleValue
|
|
denominatorSum = denominatorSum + wi
|
|
end
|
|
end
|
|
local result = numeratorSum / denominatorSum
|
|
return Color3.new(result.x, result.y, result.z)
|
|
end
|
|
---
|
|
|
|
local backpackEnabled = true
|
|
local healthbarEnabled = true
|
|
|
|
local function UpdateLayout()
|
|
local width, height = 100, 100
|
|
local borderSize = (ICON_SPACING - ICON_SIZE) / 2
|
|
|
|
local x = borderSize
|
|
local y = 0
|
|
for _, tool in ipairs(ToolsList) do
|
|
local slot = Tools[tool]
|
|
if slot then
|
|
slot.icon.Position = UDim2.new(0, x, 0, y)
|
|
x = x + ICON_SPACING
|
|
end
|
|
end
|
|
|
|
if #ToolsList == 0 then
|
|
width = HEALTHBAR_WIDTH
|
|
height = HEALTHBAR_SPACE + NAME_SPACE
|
|
BackpackPanel.showCursor = false
|
|
else
|
|
width = #ToolsList * ICON_SPACING
|
|
height = ICON_SIZE + HEALTHBAR_SPACE + NAME_SPACE
|
|
BackpackPanel.showCursor = true
|
|
end
|
|
|
|
BackpackPanel:ResizePixels(width, height)
|
|
|
|
playerName.Position = UDim2.new(0, borderSize, 0, 0)
|
|
|
|
healthbarBack.Position = UDim2.new(0, borderSize, 0, NAME_SPACE + (HEALTHBAR_SPACE - HEALTHBAR_HEIGHT) / 2)
|
|
healthbarBack.Size = UDim2.new(0, HEALTHBAR_WIDTH, 0, HEALTHBAR_HEIGHT)
|
|
end
|
|
|
|
local function UpdateHealth(humanoid)
|
|
local percentHealth = humanoid.Health / humanoid.MaxHealth
|
|
if percentHealth ~= percentHealth then
|
|
percentHealth = 1
|
|
end
|
|
healthbarFront.BackgroundColor3 = HealthbarColorTransferFunction(percentHealth)
|
|
healthbarFront.Size = UDim2.new(percentHealth, 0, 1, 0)
|
|
end
|
|
|
|
local function SetTransparency(transparency)
|
|
for i, v in pairs(Tools) do
|
|
v.bg.ImageTransparency = transparency
|
|
v.image.ImageTransparency = transparency
|
|
v.text.TextTransparency = transparency
|
|
end
|
|
|
|
playerName.TextTransparency = transparency
|
|
healthbarBack.ImageTransparency = transparency
|
|
healthbarFront.ImageTransparency = transparency
|
|
end
|
|
|
|
local function OnHotbarEquipPrimary(actionName, state, obj)
|
|
if state ~= Enum.UserInputState.Begin then
|
|
return
|
|
end
|
|
for tool, slot in pairs(Tools) do
|
|
if slot.hovered then
|
|
slot.OnClick()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local function EnableHotbarInput(enable)
|
|
if not backpackEnabled then
|
|
enable = false
|
|
end
|
|
if not currentHumanoid then
|
|
return
|
|
end
|
|
if enable then
|
|
ContextActionService:BindCoreAction("HotbarEquipPrimary", OnHotbarEquipPrimary, false, Enum.KeyCode.ButtonA, Enum.KeyCode.ButtonR2, Enum.UserInputType.MouseButton1)
|
|
else
|
|
ContextActionService:UnbindCoreAction("HotbarEquipPrimary")
|
|
end
|
|
end
|
|
|
|
local function AddTool(tool)
|
|
if Tools[tool] then
|
|
return
|
|
end
|
|
|
|
local slot = {}
|
|
Tools[tool] = slot
|
|
table.insert(ToolsList, tool)
|
|
|
|
slot.hovered = false
|
|
slot.tool = tool
|
|
|
|
slot.icon = Instance.new("TextButton", toolsFrame)
|
|
slot.icon.Text = ""
|
|
slot.icon.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE)
|
|
slot.icon.BackgroundColor3 = Color3.new(0, 0, 0)
|
|
slot.icon.Selectable = true
|
|
slot.icon.BackgroundTransparency = 1
|
|
slotIcons[tool] = slot.icon
|
|
|
|
slot.bg = Instance.new("ImageLabel", slot.icon)
|
|
slot.bg.Position = UDim2.new(0, -1, 0, -1)
|
|
slot.bg.Size = UDim2.new(1, 2, 1, 2)
|
|
slot.bg.Image = "rbxasset://textures/ui/VR/rectBackground.png"
|
|
slot.bg.ScaleType = Enum.ScaleType.Slice
|
|
slot.bg.SliceCenter = Rect.new(10, 10, 10, 10)
|
|
slot.bg.BackgroundTransparency = 1
|
|
|
|
slot.image = Instance.new("ImageLabel", slot.icon)
|
|
slot.image.Position = UDim2.new(0, 1, 0, 1)
|
|
slot.image.Size = UDim2.new(1, -2, 1, -2)
|
|
slot.image.BackgroundTransparency = 1
|
|
slot.image.Selectable = false
|
|
|
|
slot.text = Instance.new("TextLabel", slot.icon)
|
|
slot.text.Position = UDim2.new(0, 1, 0, 1)
|
|
slot.text.Size = UDim2.new(1, -2, 1, -2)
|
|
slot.text.BackgroundTransparency = 1
|
|
slot.text.TextColor3 = Color3.new(1, 1, 1)
|
|
slot.text.Font = Enum.Font.SourceSans
|
|
slot.text.FontSize = Enum.FontSize.Size12
|
|
slot.text.ClipsDescendants = true
|
|
slot.text.Selectable = false
|
|
|
|
local selectionObject = Util:Create'ImageLabel'
|
|
{
|
|
Name = 'SelectionObject';
|
|
Size = UDim2.new(1,0,1,0);
|
|
BackgroundTransparency = 1;
|
|
Image = "rbxasset://textures/ui/Keyboard/key_selection_9slice.png";
|
|
ImageTransparency = 0;
|
|
ScaleType = Enum.ScaleType.Slice;
|
|
SliceCenter = Rect.new(12,12,52,52);
|
|
BorderSizePixel = 0;
|
|
}
|
|
slot.icon.SelectionImageObject = selectionObject
|
|
|
|
local function updateToolData()
|
|
slot.image.Image = tool.TextureId
|
|
slot.text.Text = tool.TextureId == "" and tool.Name or ""
|
|
end
|
|
updateToolData()
|
|
|
|
slot.OnClick = function()
|
|
if not player.Character then return end
|
|
local humanoid = player.Character:FindFirstChild("Humanoid")
|
|
if not humanoid then return end
|
|
|
|
local inBackpack = tool.Parent == player.Backpack
|
|
humanoid:UnequipTools()
|
|
if inBackpack then
|
|
humanoid:EquipTool(tool)
|
|
end
|
|
end
|
|
|
|
slot.icon.MouseButton1Click:connect(slot.OnClick)
|
|
slot.OnEnter = function()
|
|
slot.hovered = true
|
|
end
|
|
slot.OnLeave = function()
|
|
slot.hovered = false
|
|
end
|
|
-- slot.icon.MouseEnter:connect(slot.OnEnter)
|
|
-- slot.icon.MouseLeave:connect(slot.OnLeave)
|
|
|
|
tool.Changed:connect(function(prop)
|
|
if prop == "Parent" then
|
|
if tool.Parent == player:FindFirstChild("Backpack") then
|
|
slot.bg.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE) --temporary hold-over until new backpack design comes along (can't use border with this antialiased frame stand-in)
|
|
slot.bg.Position = UDim2.new(0, 0, 0, 0)
|
|
elseif tool.Parent == player.Character then
|
|
slot.bg.Size = UDim2.new(0, ICON_SIZE + 8, 0, ICON_SIZE + 8)
|
|
slot.bg.Position = UDim2.new(0, -4, 0, -4)
|
|
end
|
|
elseif prop == "TextureId" or prop == "Name" then
|
|
updateToolData()
|
|
end
|
|
end)
|
|
|
|
UpdateLayout()
|
|
|
|
BackpackScript.ToolAddedEvent:Fire(tool)
|
|
end
|
|
|
|
local humanoidChangedEvent = nil
|
|
local humanoidAncestryChangedEvent = nil
|
|
local function RegisterHumanoid(humanoid)
|
|
currentHumanoid = humanoid
|
|
if humanoidChangedEvent then
|
|
humanoidChangedEvent:disconnect()
|
|
humanoidChangedEvent = nil
|
|
end
|
|
if humanoidAncestryChangedEvent then
|
|
humanoidAncestryChangedEvent:disconnect()
|
|
humanoidAncestryChangedEvent = nil
|
|
end
|
|
if humanoid then
|
|
humanoidChangedEvent = humanoid.HealthChanged:connect(function() UpdateHealth(humanoid) end)
|
|
humanoidAncestryChangedEvent = humanoid.AncestryChanged:connect(function(child, parent)
|
|
if child == humanoid and parent ~= player.Character then
|
|
RegisterHumanoid(nil)
|
|
end
|
|
end)
|
|
UpdateHealth(humanoid)
|
|
end
|
|
end
|
|
|
|
local function OnChildAdded(child)
|
|
if child:IsA("Tool") or child:IsA("HopperBin") then
|
|
AddTool(child)
|
|
end
|
|
if child:IsA("Humanoid") and child.Parent == player.Character then
|
|
RegisterHumanoid(child)
|
|
end
|
|
end
|
|
|
|
local function RemoveTool(tool)
|
|
if not Tools[tool] then
|
|
return
|
|
end
|
|
Tools[tool].icon:Destroy()
|
|
for i, v in ipairs(ToolsList) do
|
|
if v == tool then
|
|
table.remove(ToolsList, i)
|
|
break
|
|
end
|
|
end
|
|
Tools[tool] = nil
|
|
slotIcons[tool] = nil
|
|
UpdateLayout()
|
|
end
|
|
|
|
local function OnChildRemoved(child)
|
|
if child:IsA("Tool") or child:IsA("HopperBin") then
|
|
if Tools[child] then
|
|
if child.Parent ~= player:FindFirstChild("Backpack") and child.Parent ~= player.Character then
|
|
RemoveTool(child)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function OnCharacterAdded(character)
|
|
local backpack = player:WaitForChild("Backpack")
|
|
|
|
for i, v in ipairs(character:GetChildren()) do
|
|
if v:IsA("Humanoid") then
|
|
RegisterHumanoid(v)
|
|
break
|
|
end
|
|
end
|
|
|
|
for tool, v in pairs(Tools) do
|
|
RemoveTool(tool)
|
|
end
|
|
Tools = {}
|
|
ToolsList = {}
|
|
|
|
character.ChildAdded:connect(OnChildAdded)
|
|
character.ChildRemoved:connect(OnChildRemoved)
|
|
|
|
for i, v in ipairs(backpack:GetChildren()) do
|
|
OnChildAdded(v)
|
|
end
|
|
|
|
backpack.ChildAdded:connect(OnChildAdded)
|
|
backpack.ChildRemoved:connect(OnChildRemoved)
|
|
end
|
|
|
|
player.CharacterAdded:connect(OnCharacterAdded)
|
|
if player.Character then
|
|
spawn(function() OnCharacterAdded(player.Character) end)
|
|
end
|
|
|
|
local function OnHotbarEquip(actionName, state, obj)
|
|
if not backpackEnabled then
|
|
return
|
|
end
|
|
local character = player.Character
|
|
if not character then
|
|
return
|
|
end
|
|
if not currentHumanoid then
|
|
return
|
|
end
|
|
if state ~= Enum.UserInputState.Begin then
|
|
return
|
|
end
|
|
if #ToolsList == 0 then
|
|
return
|
|
end
|
|
local current = 0
|
|
for i, v in pairs(ToolsList) do
|
|
if v.Parent == character then
|
|
current = i
|
|
end
|
|
end
|
|
currentHumanoid:UnequipTools()
|
|
if obj.KeyCode == Enum.KeyCode.ButtonR1 then
|
|
current = current + 1
|
|
if current > #ToolsList then
|
|
current = 1
|
|
end
|
|
else
|
|
current = current - 1
|
|
if current < 1 then
|
|
current = #ToolsList
|
|
end
|
|
end
|
|
currentHumanoid:EquipTool(ToolsList[current])
|
|
end
|
|
|
|
local function OnCoreGuiChanged(coreGuiType, enabled)
|
|
-- Check for enabling/disabling the whole thing
|
|
if coreGuiType == Enum.CoreGuiType.Backpack or coreGuiType == Enum.CoreGuiType.All then
|
|
backpackEnabled = enabled
|
|
UpdateLayout()
|
|
if enabled then
|
|
ContextActionService:BindCoreAction("HotbarEquip2", OnHotbarEquip, false, Enum.KeyCode.ButtonL1, Enum.KeyCode.ButtonR1)
|
|
toolsFrame.Parent = BackpackPanel:GetGUI()
|
|
else
|
|
ContextActionService:UnbindCoreAction("HotbarEquip2")
|
|
toolsFrame.Parent = nil
|
|
end
|
|
end
|
|
|
|
if coreGuiType == Enum.CoreGuiType.Health or coreGuiType == Enum.CoreGuiType.All then
|
|
healthbarEnabled = enabled
|
|
UpdateLayout()
|
|
if enabled then
|
|
healthbarBack.Parent = BackpackPanel:GetGUI()
|
|
else
|
|
healthbarBack.Parent = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local StarterGui = game:GetService("StarterGui")
|
|
StarterGui.CoreGuiChangedSignal:connect(OnCoreGuiChanged)
|
|
OnCoreGuiChanged(Enum.CoreGuiType.Backpack, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Backpack))
|
|
OnCoreGuiChanged(Enum.CoreGuiType.Backpack, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.All))
|
|
|
|
OnCoreGuiChanged(Enum.CoreGuiType.Health, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Health))
|
|
OnCoreGuiChanged(Enum.CoreGuiType.Health, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.All))
|
|
|
|
local panelLocalCF = CFrame.Angles(math.rad(-5), 0, 0) * CFrame.new(0, 1.75, 0) * CFrame.Angles(math.rad(-5), 0, 0)
|
|
|
|
function BackpackPanel:PreUpdate(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
|
--the backpack panel needs to go in front of the user when they look at it.
|
|
--if they aren't looking, we should be updating self.localCF
|
|
|
|
local topbarPanel = Panel3D.Get("Topbar3D")
|
|
local panelOriginCF = topbarPanel.localCF or CFrame.new()
|
|
self.localCF = panelOriginCF * panelLocalCF
|
|
end
|
|
|
|
function BackpackPanel:OnUpdate()
|
|
SetTransparency(self.transparency)
|
|
|
|
local hovered, tool = BackpackPanel:FindHoveredGuiElement(slotIcons)
|
|
if hovered and tool then
|
|
local slot = Tools[tool]
|
|
if not slot.hovered then
|
|
slot.OnEnter()
|
|
end
|
|
for i, v in pairs(Tools) do
|
|
if v.hovered and v ~= slot then
|
|
v.OnLeave()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function BackpackPanel:OnMouseEnter(x, y)
|
|
EnableHotbarInput(true)
|
|
end
|
|
function BackpackPanel:OnMouseLeave(x, y)
|
|
EnableHotbarInput(false)
|
|
end
|
|
|
|
local VRHub = require(RobloxGui.Modules.VR.VRHub)
|
|
VRHub.ModuleOpened.Event:connect(function(moduleName)
|
|
local module = VRHub:GetModule(moduleName)
|
|
if module.VRIsExclusive then
|
|
BackpackPanel:SetVisible(false)
|
|
end
|
|
end)
|
|
VRHub.ModuleClosed.Event:connect(function(moduleName)
|
|
BackpackPanel:SetVisible(true)
|
|
end)
|
|
|
|
|
|
BackpackPanel:LinkTo("Topbar3D")
|
|
|
|
return BackpackScript
|