Möbel Finder: Mastering 3D Drag-and-Drop in Unity (Part 1 / 2)

Table of contents
- I. Introduction and Project Overview
- 1.1 Technical Specifications
- 1.2 Game Definition and Mechanics
- II. Research Focus and Objectives
- III. Technical Implementation and Analysis
- IV. Performance Analysis and Optimization
- V. Visual Progress
- VI. Challenges & Solutions Summary
- VII. Knowledge Synthesis and Lessons Learned
- VIII. Next Steps : Improving the experience
- IX. Technical Specifications
- X. Accessibility and Community Engagement
- XI. Development Stats
- Tags

I. Introduction and Project Overview
1.1 Technical Specifications
Engine: Unity
Language: C#
Platform: PC
Team Size: Solo Play
Development Time: 3 days
Status: Published
1.2 Game Definition and Mechanics
Möbel Finder (German for "Furniture Finder") combines the fun of a casual puzzle with the stress of time-management. It's a light but strategic game that tests players' accuracy and speed as they drag and drop furniture into the right places in a race against the clock.
Each level starts with the furniture in the same spot. As a player you get a time limit and a list of the furnitures to move. When you drag a piece of furniture from the list to its corresponding spot, it disappears from the list. If you put something in the wrong spot, it just goes back to its initial place.
II. Research Focus and Objectives
2.1 Primary Developments Goals
Focus of Development: Fundamental Drag-and-Drop System
Main Goals
Make dragging and dropping items easy for gameplay.
Ensure accurate placement with clear visual feedback.
Add a timer to create some time pressure for success or failure.
2.2 Achievement Summary
What I Accomplished
Incorporated reliable 3D object selection and dragging
Created a plane-constrained mobility system
Solved cursor-speed drag interruption issues
Added visual highlighting for interactive objects
Drop zone validation (basic implementation complete)
III. Technical Implementation and Analysis
3.1 Core Challenge Definition
I had to try different methods to overcome some technical obstacles to create a responsive, smooth drag-and-drop mechanic in 3D space.
3.2 Object Selection & Hover Feedback System
3.2.1 Raycast Detection Implementation
Raycast Detection:
// Detect furniture objects under cursor
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, _rayDistance, _furnitureMask))
{
_selectedGO = hit.collider.gameObject;
_renderer = _selectedGO.GetComponent<MeshRenderer>();
}
- Purpose: It checks for furniture items under the cursor with a layer mask called
_furnitureMask
.
3.2.2 Visual Highlight System
It changes the object (and its child objects) to red when you hover over it.
// Highlight selected object and children
_renderer.material.color = Color.red;
foreach (Transform child in selectedObject.transform)
{
var childRenderer = child.GetComponent<MeshRenderer>();
if (childRenderer != null)
childRenderer.material.color = Color.red;
}
- Note: It uses a list (
_listRenderer
) to cache child renderers for better performance.
3.3 Drag Mechanics: Itereative Development Process
3.3.1 Approach 1: Dual-Raycast Drag-system
In this implementation, two separate raycasts team up:
Furniture Selection Raycast
Layer Mask:
_furnitureMask
Purpose: It identifies which furniture object is being interacted with
if (Physics.Raycast(ray, out hit, _rayDistance, _furnitureMask)) { // Highlight and select furniture object _selectedGO = hit.collider.gameObject; }
Ground Positioning Raycast
Layer Mask:
_groundMask
Purpose: It determines where to place the selected furniture and updates position during drag
_dragOffset = _selectedGO.transform.position - groundHit.point; _selectedGO.transform.position = hit.point + _dragOffset;
Why Two Separate Raycasts?
The furniture raycast handles selection (what you're dragging)
The ground raycast handles placement (where you're dragging it to)
The offset helps keep the object at the right height above the ground
3.3.2 Problem Identification: Drag Interruption
When you are smoothly guiding your mouse across the screen, everything is great until you speed up a bit. The object that you were moving stops suddenly, even though you're still holding the mouse button down. Any idea why?
It's the raycast. You see, when you drag slowly, the raycast keeps hitting the object, which is good. However, if you move too fast, the cursor flies off the object so fast that the system can’t keep up. The raycast loses track of it, and your drag stops, even though you're still holding down the mouse button.
[MOUSE MOVEMENT]the selection depended on constantly detecting the hover, even when you were actively dragging
↓
[Furniture Raycast] → Selects object (what to move)
↓
[Ground Raycast] → Finds placement point (where to move)
↓
[Offset Calculation] → Maintains consistent height
3.3.3 Approach 2: Screen-to-World Conversion
Here i try to fix the dragging issue by keeping the object's depth while tracking the mouse mouvement.
The problem:
To drag a 3D object, the mouse position must be changed from the 2D back into 3D space. The catch is that the mouse only shows X and Y coordinates, which means that there's nothing for depth.
The solution:
a. Get the Depth First: When selecting an object, it is checked how far the object is from the camera, mainly the Z-position.
b. Save the new position: A new position is created using the mouse's current X and Y coordinates along with the previous saved depth.
c. Conversion to 3D: The new position is converted back into actual 3D world coordinates. Finally the object can be moved.
Vector3 screenPosition = new Vector3(
Input.mousePosition.x, // Mouse X
Input.mousePosition.y, // Mouse Y
Camera.main.WorldToScreenPoint(_selectedGO.transform.position).z // Object's current depth
);
// Convert back to world space
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(screenPosition);
_selectedGO.transform.position = worldPosition; // Move the object
Limitation: Keeping the object's original depth on the screen helps it stay in the same place compared to the camera as it moves with your mouse.
!!! This method works well, but it doesn't fit this game because players need to move the object along the ground, not through it.
Right now, the code keeps the object at a set distance from the camera, making it move on an unseen plane that's parallel to the camera. In this game, though, the objects should slide along the ground surface and stay in contact with it instead of floating at a fixed distance.
3.3.4 Final Solution: Plane-Constrained Dragging
The Breakthrough: Using Plane.Raycast
for reliable, consistent movement
Unity has a Plane class that defines a virtual, infinite plane used for calculations, as it exists only in code. This virtual plane allows for consistent object height without needing a physical ground, keeping the height consistent while preventing objects from dropping or moving unexpectedly. It ensures smooth and precise movement and also consistent control of objects, independent of cursor speed.
Being virtual, the plane can be defined directly in code. Since the ground has a given position, we can save this position and use it to position or align the plane in calculations. The plane's normal (Vector3.up
) helps us determine if we're looking at a flat surface. The ray direction is calculated from the camera to the mouse, and a hit occurs only if these directions intersect properly.
public class FurnitureDragger : MonoBehaviour
{
[SerializeField] private Transform _plane; // Reference to the plane transform
[SerializeField] private LayerMask _furnitureMask = 1;
private Plane _dragPlane; // Virtual plane's reference for dragging calculations
private GameObject _selectedGO; // Reference to the selected GameObject
private bool _select = false; // Boolean to track selection state
private Camera _camera; // Reference to the camera
void Start()
{
_camera = Camera.main;
// Null checks
if (_camera == null || _plane == null)
{
Debug.LogError("Missing required references!");
enabled = false;
return;
}
// Initialize the infinite virtual plane at the position of _plane
_dragPlane = new Plane(Vector3.up, _plane.transform.position);
}
void Update()
{
HandleDragAndDrop();
}
private void HandleDragAndDrop()
{
// Select object under cursor
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, _furnitureMask))
{
_selectedGO = hit.collider.gameObject;
_select = true;
Cursor.visible = false; // Hide cursor during drag
}
if (Input.GetMouseButton(0) && _selectedGO != null)
{
Ray rayPlane = Camera.main.ScreenPointToRay(Input.mousePosition);
// Check if the ray intersects with the virtual plane
if (_dragPlane.Raycast(rayPlane, out float enter))
{
_selectedGO.transform.position = rayPlane.GetPoint(enter);
}
}
if (Input.GetMouseButtonUp(0))
{
_select = false;
_selectedGO = null;
Cursor.visible = true; // Clear selection
}
}
}
Note: The normal determines which side of the plane is detectable by the raycast.
Vector3.up
makes the normal face up, so only rays coming from above will hit the plane. If a ray comes from below, it won't cross the plane. The plane's normal changes with rotation.
Rotation (0, 0, 0) – Horizontal Plane - Nomal:
Vector3.up
_dragPlane = new Plane(Vector3.up, _plane.transform.position);
Rotation (90, 0, 0) – Vertical Plane (Wall) - Nomal:
Vector3.forward
_dragPlane = new Plane(Vector3.forward, _plane.transform.position);
IV. Performance Analysis and Optimization
4.1 Optimization Results
Raycasting: I only check the furniture layer, which cuts down on extra calculations.
Material Handling: I keep renderer references handy to skip repeated
GetComponent
checks.Update Frequency: I check for hover only when you’re not dragging anything.
4.2 Build Statitics and Metrics
Target FPS: 60fps (consistently achieved)
Build Size: Around 45MB
Memory Usage: Less than 200MB peak during gameplay
V. Visual Progress
Core Mechanics Demonstration
Easy dragging of furniture with flat limit rule
Highlighting System
When you hover over it, the furniture turns red.
UI Integration
Shows time and score when you match furniture.
VI. Challenges & Solutions Summary
6.1 Challenge 1: Drag Interruption
Problem: Quick mouse moves mess with drag tracking
Solution: A system where selections don't rely on hovering
Result: Drag works well no matter how fast the mouse is moved
6.2 Challenge 2: Inconsistent Object Depth
Problem: Items moved at any Z-depths without rules
Solution: System that keeps movement on a set plane
Result: Things stay where they should on tables or floors
6.3 Challenge 3: Performance with Multiple Objects
Problem: Non-stop raycasting for every item
Solution: Use of layers and chosen detection
Result: Smooth work even with more than 20 items to interact with
VII. Knowledge Synthesis and Lessons Learned
7.1 Technical Insights
Plane.Raycast is good for flat areas and can be use this to note where you touch or click on large flat zones like ground or walls. It’s fast, good, quick, right on point and doesn't make the system slow.
Tracking the Drag State is the Key for good drag-and-drop as it helps to watch closely if you are hovering, holding, or let go for a sharp drag-and-drop feel.
Using a Layermask boost the performance as it helps by cutting down on the things Unity has to check, making it run better.
Visual feedback (highlighting) makes it better for players. When items light up as you interact, it makes the feel of the game much better.
7.2 Development Process
Iterative problem-solving led to better answers than the first try.
Player feedback provided valuable information for improvements.
Early performance testing helped prevent problems that might have required optimization later
VIII. Next Steps : Improving the experience
Gameplay Polish
Set up a system to check drop zone.
Add some visual cues to show when placements are good.
Create a way for the game to get harder as players progress.
Adjust the difficulty based on what player’s feedback.
Progression & Replayability
- Adjusting difficulty based on what players say
IX. Technical Specifications
9.1 Development Environment
Unity Version: 2022.3.62f1 LTS
Target Platform: Windows PC
Physics: Unity 3D Physics
9.2 Architecture
Design Pattern: Component-based architecture
Code Organization: Single-responsibility classes
Asset Management: Prefab-based furniture system
X. Accessibility and Community Engagement
10.1 Distribution Channels
Try It Yourself
Source Code: [Not available at the moment]
Development Log: [Full Documentation]
10.2 Feedback Welcome
I'd love to hear your thoughts on the drag-and-drop mechanics! You can:
Comment below with your experience
Share suggestions for improvements
Report any bugs or issues encountered
XI. Development Stats
Total Development Time: 7 days
Lines of Code: ~500
Major Features: 3 core systems
Bugs Fixed: 12 during development
Performance Target: 60fps (achieved)
Tags
#gamedev
#unity
#indiedev
#dragdrop
#puzzle
#timemanagement
#c#
#gameplay
Next week: Implementing advanced drop zone validation and visual feedback systems
Subscribe to my newsletter
Read articles from Yann K. directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
