Created
March 21, 2025 12:26
-
-
Save gaymeowing/7e48397ebc8175a552c40c3227fb5470 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!native | |
--[[ | |
setup dragdetector | |
originally wriitten by PrinceTybalt on 10/6/2023, this is a refactored version | |
that is a function that can be applied to any drag detector, and doesnt require | |
cloning any scripts | |
original: https://create.roblox.com/store/asset/14988529693 | |
]] | |
local Workspace = game:GetService("Workspace") | |
local Players = game:GetService("Players") | |
export type DragDetectorInfo = { | |
read exclude_descendants_instances: { Instance }?, | |
read x_plane_offset_min_max: NumberRange?, | |
read y_plane_offset_min_max: NumberRange?, | |
read carry_distance: number?, | |
} | |
local VECTOR_Y_AXIS = vector.create(0, 1, 0) | |
local PLAYER = Players.LocalPlayer | |
local CHARACTER = PLAYER.Character | |
--[[ | |
controls how far the object can be dragged left/right or up/down, | |
relative to the player whilst the player is dragging it | |
]] | |
local MIN_X_PLANE_OFFSET = -10 | |
local MIN_Y_PLANE_OFFSET = -5 | |
local MAX_X_PLANE_OFFSET = 10 | |
local MAX_Y_PLANE_OFFSET = 15 | |
-- specifies how far in front of the player the object will be carried. | |
local CARRY_DISTANCE = 8 | |
local EPSILON = 0.00001 | |
local EMPTY = {} | |
local function GET_AXIS_DISTANCE( | |
offset_from_orgin: vector, axis_direction: vector, | |
min: number, max: number | |
): number | |
return math.clamp(vector.dot(offset_from_orgin, axis_direction), min, max) | |
end | |
local function get_cframe_from_drag_ray( | |
orgin: vector, direction: vector, part: BasePart, | |
detector: DragDetector, carry_distance: number, | |
params: RaycastParams | |
): CFrame | |
local normalized_direction = vector.normalize(direction) | |
local part_rotation = part.CFrame.Rotation | |
local result = Workspace:Blockcast( | |
part_rotation + orgin, part.Size, normalized_direction * carry_distance, params | |
) | |
local distance = carry_distance | |
if result then | |
distance = result.Distance | |
-- checking if its a physical drag detector | |
if | |
detector.ResponseStyle == Enum.DragDetectorResponseStyle.Physical and | |
not part.Anchored | |
then | |
--[[ | |
this will attempt to place it slightly beyond the surface. in the physical case, | |
this helps push/slide along walls. (We don't to this in the geometric case or we'd penetrate the other objects) | |
]] | |
distance += 0.2 | |
end | |
end | |
return part_rotation + (orgin + normalized_direction * distance) | |
end | |
--[[ | |
returns (orgin, direction) | |
]] | |
local function get_ray_eminating_from_player( | |
orgin: vector, direction: vector, last_view_cframe: CFrame, | |
carry_distance: number, min_x_plane_offset: number, | |
max_x_plane_offset: number, min_y_plane_offset: number, | |
max_y_plane_offset: number | |
): (vector, vector) | |
local character = PLAYER.Character | |
local head: BasePart | |
local root: BasePart | |
if character then | |
head = (character :: any):FindFirstChild("Head") | |
root = character.PrimaryPart :: any | |
end | |
--[[ | |
1: cannot get any ray based on the player | |
2: when we are in first person, the cursorRay has an origin in the center of the view and a | |
direction forward in the view direction. So we are shooting a direction straight ahead from our POV | |
]] | |
if | |
(not (head and root)) or | |
(head.LocalTransparencyModifier > 0.6) | |
then | |
-- any casting here because vector type still brokey :( | |
return orgin, direction | |
end | |
--[[ | |
when we are not in first person, we intersect with a plane facing us, | |
but in front of the player's root, part to find the desired location. | |
then we construct a ray from the player's character directed toward the intersection with that plane. | |
]] | |
local normal_view_frame_look_at = vector.normalize(last_view_cframe.LookVector) | |
local root_position = root.CFrame.Position | |
local player_plane_orgin = root_position + normal_view_frame_look_at * carry_distance | |
--[[ | |
the playerPlane is a plane in front of the player a distance of carry_instance | |
it should be parallel to the ground; remove the Y component just in case | |
]] | |
local level_player_forward = vector.create(normal_view_frame_look_at.x, 0, normal_view_frame_look_at.z) | |
local normal_player_forward = vector.normalize(level_player_forward) | |
local direction_normal = vector.normalize(direction) | |
local rate = vector.dot(direction_normal, normal_player_forward) | |
if math.abs(rate) < EPSILON then | |
-- ray is parellel to plane surface; cannot intersect | |
return orgin, direction | |
else | |
local plane_x_dir = vector.normalize(vector.cross(level_player_forward, VECTOR_Y_AXIS)) | |
local plane_distance = -1 * vector.dot(normal_player_forward, player_plane_orgin) | |
local t = -(plane_distance + vector.dot(orgin, normal_player_forward)) / rate | |
local offset_from_orgin = (orgin + direction_normal * t) - player_plane_orgin | |
local y_distance = GET_AXIS_DISTANCE(offset_from_orgin, VECTOR_Y_AXIS, min_y_plane_offset, max_y_plane_offset) | |
local x_distance = GET_AXIS_DISTANCE(offset_from_orgin, plane_x_dir, min_x_plane_offset, max_x_plane_offset) | |
local intersection = player_plane_orgin + x_distance * plane_x_dir + y_distance * VECTOR_Y_AXIS | |
return root_position, intersection - root_position | |
end | |
end | |
local function drag_style_getter_exclude_instances( | |
cursor_ray: Ray, last_view_cframe: CFrame, | |
detector: DragDetector, carry_distance: number, | |
min_x_plane_offset: number, max_x_plane_offset: number, | |
min_y_plane_offset: number, max_y_plane_offset: number, | |
params: RaycastParams, exclude_descendants_instances: { Instance } | |
): CFrame | |
local part = detector.Parent :: BasePart | |
local new_filter = { part, unpack(exclude_descendants_instances) } | |
local direction = cursor_ray.Direction | |
local orgin = cursor_ray.Origin | |
if CHARACTER then | |
table.insert(new_filter, CHARACTER) | |
orgin, direction = get_ray_eminating_from_player( | |
orgin, direction, last_view_cframe, carry_distance, | |
min_x_plane_offset, max_x_plane_offset, | |
min_y_plane_offset, max_y_plane_offset | |
) | |
end | |
params.FilterDescendantsInstances = new_filter | |
return get_cframe_from_drag_ray(orgin, direction, part, detector, carry_distance, params) | |
end | |
local function drag_style_getter( | |
cursor_ray: Ray, last_view_cframe: CFrame, | |
detector: DragDetector, carry_distance: number, | |
min_x_plane_offset: number, max_x_plane_offset: number, | |
min_y_plane_offset: number, max_y_plane_offset: number, | |
params: RaycastParams | |
): CFrame | |
local part = detector.Parent :: BasePart | |
local new_filter = { part } | |
local direction = cursor_ray.Direction | |
local orgin = cursor_ray.Origin | |
if CHARACTER then | |
table.insert(new_filter, CHARACTER) | |
orgin, direction = get_ray_eminating_from_player( | |
orgin, direction, last_view_cframe, carry_distance, | |
min_x_plane_offset, max_x_plane_offset, | |
min_y_plane_offset, max_y_plane_offset | |
) | |
end | |
params.FilterDescendantsInstances = new_filter | |
return get_cframe_from_drag_ray(orgin, direction, part, detector, carry_distance, params) | |
end | |
--[[ | |
Sets up a DragDetector to have a custom drag style, similar to games like LumberTycoon2. | |
Returns the DragDetector, and a cleanup function to stop applying the drag style. | |
]] | |
local function setup_dragdetector(detector: DragDetector, info: DragDetectorInfo?): (DragDetector, () -> ()) | |
local exclude_descendants_instances: { Instance }? | |
local min_x_plane_offset = MIN_X_PLANE_OFFSET | |
local max_x_plane_offset = MAX_X_PLANE_OFFSET | |
local min_y_plane_offset = MIN_Y_PLANE_OFFSET | |
local max_y_plane_offset = MAX_Y_PLANE_OFFSET | |
local old_drag_style = detector.DragStyle | |
local last_view_cframe = CFrame.identity | |
local style_getter = drag_style_getter | |
local carry_distance = CARRY_DISTANCE | |
local params = RaycastParams.new() | |
if info then | |
local info_x_plane_offset_min_max = info.x_plane_offset_min_max | |
local info_y_plane_offset_min_max = info.y_plane_offset_min_max | |
if info_x_plane_offset_min_max then | |
min_x_plane_offset = info_x_plane_offset_min_max.Min | |
max_x_plane_offset = info_x_plane_offset_min_max.Max | |
end | |
if info_x_plane_offset_min_max then | |
min_x_plane_offset = info_x_plane_offset_min_max.Min | |
max_x_plane_offset = info_x_plane_offset_min_max.Max | |
end | |
exclude_descendants_instances = info.exclude_descendants_instances | |
carry_distance = info.carry_distance or carry_distance | |
if exclude_descendants_instances then | |
style_getter = drag_style_getter_exclude_instances | |
end | |
end | |
local continue = detector.DragContinue:Connect(function(_, _, view_frame) | |
last_view_cframe = view_frame | |
end) | |
detector.DragStyle = Enum.DragDetectorDragStyle.Scriptable | |
params.FilterType = Enum.RaycastFilterType.Exclude | |
detector:SetDragStyleFunction(function(cursor_ray: Ray) | |
return style_getter( | |
cursor_ray, last_view_cframe, detector, carry_distance, | |
min_x_plane_offset, max_x_plane_offset, | |
min_y_plane_offset, max_y_plane_offset, | |
params, exclude_descendants_instances | |
) | |
end) | |
return detector, function() | |
detector:SetDragStyleFunction(function() end) | |
detector.DragStyle = old_drag_style | |
continue:Disconnect() | |
end | |
end | |
do | |
PLAYER.CharacterAdded:Connect(function(character) | |
CHARACTER = character | |
end) | |
PLAYER.CharacterRemoving:Connect(function() | |
CHARACTER = nil | |
end) | |
end | |
return setup_dragdetector |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment