Model placement issue

Hey!
I’m finishing up my placement system, and I ran intro the issue of using a real rig, not just a block, now, It’s just halfway into the ground for no reason. Why? I think it may be to do with Humanoid.HipHeight, But I have no idea.
Script:

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PhysicsService = game:GetService("PhysicsService")

local getMap = ReplicatedStorage.SelectedMap
if not getMap.Value then
	getMap:GetPropertyChangedSignal("Value"):Wait()
end

local getReplicatedMap = ReplicatedStorage.Maps:FindFirstChild(tostring(getMap.Value))
if not getReplicatedMap then
	error("REPLICATED MAP: "..getMap.Name.." DOES NOT EXIST")
end

local PlacementEvents = ReplicatedStorage:WaitForChild("PlacementEvents")
local PlacementEventsMain = PlacementEvents:WaitForChild("Main")
local spawnTowerEvent = PlacementEventsMain:WaitForChild("SpawnTower")


local Camera = workspace.Camera
local gui = script.Parent
local TowerToSpawn = nil
local CanPlace = false
local Rotation = 0
local Placing = false

local function MouseRaycast(blacklist)
	local mousePosition = UserInputService:GetMouseLocation()
	
	local mouseRay = Camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = blacklist
	
	local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
	
	return raycastResult
end

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if gameProcessed then
		return --gameProcessed means inputed in different object e.g chat
	end
	if TowerToSpawn then 
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			if CanPlace then
				spawnTowerEvent:FireServer(TowerToSpawn.Name, TowerToSpawn.PrimaryPart.CFrame)
				RemovePlaceholderTower()
			end
		elseif input.KeyCode == Enum.KeyCode.R then
			Rotation += 90
		end
	end
end)

function RemovePlaceholderTower()
	if TowerToSpawn then
		TowerToSpawn:Destroy()
		TowerToSpawn = nil
		Rotation = 0
		Placing = false
		for i, tower in ipairs(workspace.Towers.ActiveTowers:GetChildren()) do
			tower.RangeBox.Transparency = 1
		end
	end
end


local function AddPlaceHolderTower(name : string)
	local towerExists = getReplicatedMap.Towers:FindFirstChild(tostring(name))
	if towerExists and Placing == false then
		TowerToSpawn = towerExists:Clone()
		TowerToSpawn.Parent = workspace.Towers.PlacingTowers
		
		for i, object in ipairs(TowerToSpawn:GetDescendants()) do
			if object:IsA("BasePart") then
				PhysicsService:SetPartCollisionGroup(object, "Tower")
			end
		end
		
		for i, tower in ipairs(workspace.Towers.ActiveTowers:GetChildren()) do
			tower.RangeBox.Transparency = 0
		end
		Placing = true
	end
end

local function ColorPlaceholderTower(color)
	for i, object in ipairs(TowerToSpawn:GetDescendants()) do
		if object:IsA("BasePart") and object.Name ~= "RangeBox" then
			object.Color = color
		end
	end
	if not CanPlace then
		TowerToSpawn.Range.Transparency = 1
	else
		TowerToSpawn.Range.Transparency = 0
	end
end

gui["1"].MainButton.Activated:Connect(function()
	AddPlaceHolderTower("Tower")
end)

gui["2"].MainButton.Activated:Connect(function()
	AddPlaceHolderTower("Dummy")
end)

RunService.RenderStepped:Connect(function()
	if TowerToSpawn then
		local result = MouseRaycast({TowerToSpawn})
		if result and result.Instance then
			local GetRangeBoxTouchingParts = TowerToSpawn.RangeBox:GetTouchingParts()

			if result.Instance.Parent.Name == "TowerArea" then
				CanPlace = true
				ColorPlaceholderTower(Color3.new(0,1,0))
			else
				CanPlace = false
				ColorPlaceholderTower(Color3.new(1,0,0))
			end
			
						for index, part in ipairs(GetRangeBoxTouchingParts) do
				if part.Name == "RangeBox" and part.Parent ~= TowerToSpawn then
					CanPlace = false
					ColorPlaceholderTower(Color3.new(1,0,0))
				end
			end
			
			local x = result.Position.X
			local y = result.Position.Y + TowerToSpawn.Humanoid.HipHeight + (TowerToSpawn.PrimaryPart.Size.Y / 2)
			local z = result.Position.Z


			local cframe = CFrame.new(x,y,z) * CFrame.Angles(0, math.rad(Rotation), 0)
			TowerToSpawn:SetPrimaryPartCFrame(cframe)
			PhysicsService:SetPartCollisionGroup(TowerToSpawn.Range,"Tower")
			PhysicsService:SetPartCollisionGroup(TowerToSpawn.RangeBox,"TowerBox")
			TowerToSpawn.Range.Size = Vector3.new(0.1, TowerToSpawn.Levels[TowerToSpawn.CurrentLevel.Value].Range.Value * 2, TowerToSpawn.Levels[TowerToSpawn.CurrentLevel.Value].Range.Value * 2)
		end
	end
end)

image

It looks like your setting a models cframe to the point your mouse contacts the ground. This sets the origin of the model (the 3d middle) to the mouse position (the ground) which is why half of the character appears under ground.

To easily achieve desired results, I would just use model:MoveTo() to move the model to a position and place it on top of whatever the mouse is hovering. MoveTo

If youre looking to avoid moveto, perhaps to be able to set the orientation as well through CFrame, you can still use primary part cframe, but you would have to add half of the models height to the desired position’s Y value. To do this, you could use model:GetExtentsSize().Y, and then translate your mouse hit position by half of the Y component of the model. Extents Size

The reason hip height didnt work, is because its a relative value in R6 (In R15, its absolute). This means that by default, your hip height in R6 is 0! To make the hip height solution work (wouldn’t suggest this because then your function would only work for character rigs), you could use the following code: Height = LeftLeg.Size.Y + (0.5 * RootPart.Size.Y) + HipHeight Hip Height

1 Like

Are you able to give a example? I have never used it before, sorry.

Essentially getextentssize is giving you the entire Y size that the model takes up.

For example if you imagine your character had a red hitbox covering as far as every basepart goes in each direction GetExtentsSize is the equivalent of getting a models bounding box Y size
image

1 Like

So something like this?

y = TowerToSpawn:GetExtentsSize().Y / 2
1 Like

Exactly. Try this.

local function getFinalPositionFromHitPosition(mouseHit,model)
     local offset = model:GetExtentsSize().Y/2
     
     return mouseHit:GetPrimaryPartCFrame():ToWorldSpace(CFrame.new(0,offset,0))
end
3 Likes