Hello, thanks for taking the time to read my post.
- What do I want to achieve?
I am working on creating a turn-based combat game. While the player is attacking an enemy, they will have the option to target a specific limb for every attack the player has. I have constructed a way to make this happen, for PC at least, but I am struggling to find the best way to make the process visually appealing (or at least, the way I wanted to do it did not work, and I now cannot think of a better way to implement the feedback).
Here is a video that demonstrates the current system:
So here is a basic rundown of the ideal system in my mind.
- Player gets into a battle, presses attack, and then the camera tweens like the video above.
- While the player moves their mouse over any one of the limbs, there is visual feedback to the player which denotes what limb is being hovered
- Upon clicking a limb, it will be inserted into a table, and then upon pressing space, the actual attack happens
Before posting the video, I only had the GUI that popped up when hovering over limbs appear once it was clicked, that is why it looks a bit dicey when I click and the GUI disappears. I currently have it only appearing when a raycast detects any of the enemy parts/meshes for testing purposes.
- What is the issue?
The main issue is that I realized this system will scale up very poorly when actually incorporating models/meshes into the game other than the default R15 rig. Here are some examples detailing what I mean:
Simple Raycasting Module:
local RaycastModule = {}
-- New RaycastParam object to be edited
local raycastParams = RaycastParams.new()
function RaycastModule.AttackCamRaycast(camera, filterInstances)
raycastParams.FilterType = Enum.RaycastFilterType.Include
raycastParams.FilterDescendantsInstances = {filterInstances}
local mouseLocation = UserInputService:GetMouseLocation()
local unitRay = camera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y, 0)
local raycastResult : RaycastResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * RANGE, raycastParams)
if raycastResult then
return raycastResult.Instance, raycastResult.Instance.Color
else
return nil
end
end
return RaycastModule
I am only returning two things. The instance that was found at the player’s mouse position and the original color of whatever instance was found, as I hoped to use this to transition back and forth between a color denoting a selected limb, and the default limb color when nothing was returned from the raycast function.
The Client-Side Module that uses the above function:
-- Table containing the limb highlighted when the mouse is atop it
local highlightedLimb = {}
-- Table that contains the limb that is clicked during the BindToRenderStep function
local clickedLimb = {}
-- Table that confirms which limb to attack
local confirmedLimb = {}
-- Global function that changes the camera while attacking
function CameraModule.AttackCam(callback)
task.wait(.1)
local camera = workspace.CurrentCamera
TweenService:Create(camera, TweenInfo.new(.5), {CFrame = CFrame.lookAt(workspace.FightAreas.Area1.Battleground1.AttackCameraPart.Position, enemyList[1].HumanoidRootPart.Position)}):Play()
task.wait(.5)
local highlightFx = highlightEmitter:Clone()
-- TODO: END GOAL IS TO CREATE AN EMITTER THAT ONLY PRODUCES ONE PARTICLE AT EITHER THE MOUSE HOVER OR POSITION OF PART WHERE THE EMITTER IS ADDED
-- TODO: ALSO FOR WEIRD BEHAVIOR WHERE THE PARTICLES ARE INSTANTLY MOVED, THAT IS BECAUSE OF THE LOCKTOPART PROPERTY
RunService:BindToRenderStep("LimbFinder", Enum.RenderPriority.Camera.Value - 1, function()
local instanceReturned, instanceOriginalColor = RaycastModule.AttackCamRaycast(camera, paramsTable)
if instanceReturned then
if instanceReturned.Name == "HumanoidRootPart" then
instanceReturned = instanceReturned.Parent:FindFirstChild("UpperTorso")
end
end
if (not highlightedLimb[1]) and instanceReturned then
highlightedLimb[1] = instanceReturned
highlightedLimb[1].Color = Color3.fromRGB(255, 0, 0)
CombatTweenModule.LimbName(LimbMappingModule.LimbMappings[highlightedLimb[1].Name], false)
highlightFx.Enabled = true
highlightFx.Parent = highlightedLimb[1]
elseif not instanceReturned then
end
if highlightedLimb[1] ~= instanceReturned then
if not instanceReturned then
highlightedLimb[1].Color = Color3.fromRGB(127, 127, 127)
CombatTweenModule.LimbName(LimbMappingModule.LimbMappings[highlightedLimb[1].Name], true)
highlightedLimb[1] = nil
highlightFx.Enabled = false
elseif instanceReturned then
highlightedLimb[1].Color = Color3.fromRGB(127, 127, 127)
highlightedLimb[1] = instanceReturned
highlightedLimb[1].Color = Color3.fromRGB(255, 0, 0)
CombatTweenModule.LimbName(LimbMappingModule.LimbMappings[highlightedLimb[1].Name], false)
highlightFx.Enabled = true
highlightFx.Parent = highlightedLimb[1]
end
end
end)
-- Click a limb to attack
UserInputService.InputBegan:Connect(function(input : InputObject, gameP : boolean)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
if highlightedLimb[1] then
clickedLimb[1] = highlightedLimb[1]
TweenService:Create(camera, TweenInfo.new(.25), {CFrame = CFrame.lookAt(workspace.FightAreas.Area1.Battleground1.AttackCameraPart.Position, clickedLimb[1].Position)}):Play()
if LimbMappingModule.LimbMappings[clickedLimb[1].Name] then
CombatTweenModule.LimbName(LimbMappingModule.LimbMappings[clickedLimb[1].Name], false)
else
CombatTweenModule.LimbName(clickedLimb[1].Name, false)
end
else
end
elseif input.KeyCode == Enum.KeyCode.Space then
if not CameraModule.spaceDebug then
CameraModule.spaceDebug = not CameraModule.spaceDebug
if not clickedLimb[1] then
elseif clickedLimb[1] then
confirmedLimb[1] = clickedLimb[1]
RunService:UnbindFromRenderStep("LimbFinder")
task.wait(.5)
TweenService:Create(camera, TweenInfo.new(1.25), {CFrame = CFrame.lookAt(workspace.FightAreas.Area1.Battleground1.CameraPart.Position, enemyList[1].HumanoidRootPart.Position)}):Play()
callback(confirmedLimb[1], confirmedLimb[1].Parent)
end
task.wait(.5)
CameraModule.spaceDebug = not CameraModule.spaceDebug
end
end
end)
end
Really all it does is insert the Raycast result into three separate tables based on whether the mouse is highlighting a returned instance, clicked on a returned instance, or pressed space when a returned instance is in the clickedPart table.
However, I am losing hope that this system will ever be efficient because my previous idea to use color changes lacked a lot of foresight, as future models in this game will definitely incorporate clothing and accessories. Though I filtered them out, they will still cover a majority of limbs, so the color is useless.
In addition to that, considering that the meshes utilize Color3.fromRGB() values and the raycastResult returns a Color3 value, trying to set the original color to “instanceOriginalColor” produces a different color than the mesh originally was, so I doubt this is the best way to move forward.
That is why I tried to switch to a particleEmitter to get visual feedback, but the end result just looks quite sketchy. Granted, I have not put a ton of effort into the visual appearance, but I am unsure if this will suffice.
- What solutions have I tried thus far?
Originally, I tried various different things to make this color idea work. I tried converting the Color3 into a Color3.fromRGB value, this did not work. I have tried changing materials instead of color. I have tried spawning new translucent parts atop of the existing limbs, this did not work. Now I am just trying to think of the best way to proceed before settling on one thing.
Really, I just made this post to get some ideas from anyone willing to share about what they feel the best method would be for providing players appropriate visual feedback while retaining the core elements I wish to have in my game, a limb targeting system. Which, by the way, s/o if you know the game it is from.
Thanks again,