Need Some Help Deciding/Fixing Visual Feedback In My Game

Hello, thanks for taking the time to read my post.

  1. 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.

  1. 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.

  1. 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,

1 Like