Lesson 4: Touch Controls & Input
Welcome back! Now that you have your art pipeline set up, it's time to make your puzzle game actually playable. In this lesson, you'll learn how to implement responsive touch controls that feel natural and intuitive on mobile devices. Great touch controls are the foundation of any successful mobile puzzle game.
Ramen Arcade🍜 🕹️ by Dribbble Artist
What You'll Learn
- Implement responsive touch input handling in Unity
- Create gesture recognition for common puzzle game interactions
- Handle multi-touch scenarios and input conflicts
- Optimize touch responsiveness for different devices
- Build reusable input systems for your puzzle mechanics
Prerequisites
- Unity 2022.3 LTS or newer
- Completed Lesson 3: Mobile Art Pipeline
- Basic C# programming knowledge
- Your mobile puzzle game project from previous lessons
Why Touch Controls Matter
Touch controls are the primary way players interact with mobile games. Poor touch handling leads to:
- Frustrated players - Unresponsive controls kill engagement
- Lower retention - Players quit if controls feel broken
- Negative reviews - Bad controls are the #1 complaint in app stores
- Missed opportunities - Great gameplay is wasted with poor input
Great touch controls feel:
- Instant - No noticeable delay between touch and response
- Accurate - Players hit what they're aiming for
- Forgiving - Small mistakes don't break the experience
- Consistent - Same behavior across all devices
Understanding Mobile Input in Unity
Unity provides several ways to handle touch input:
Input System Options
- Legacy Input System (
Input.touches) - Simple but limited - New Input System (
UnityEngine.InputSystem) - Modern, flexible, recommended - UI Event System - For UI interactions (buttons, panels)
For puzzle games, you'll typically use a combination of the New Input System and UI Event System.
Step 1: Set Up the New Input System
The New Input System provides better performance and more features than the legacy system.
Install Input System Package
- Open Unity Package Manager (Window → Package Manager)
- Click the "+" button → "Add package by name"
- Enter:
com.unity.inputsystem - Click "Add"
Create Input Actions Asset
- Right-click in Project window → Create → Input Actions
- Name it "PuzzleGameInput"
- Double-click to open the Input Actions editor
Configure Input Actions
Create the following action maps and actions:
PuzzleGame (Action Map)
TouchPosition(Value, Vector2) - Current touch positionTouchPress(Button) - Touch startedTouchRelease(Button) - Touch endedDrag(Value, Vector2) - Drag deltaTap(Button) - Quick tap gestureLongPress(Button) - Long press gesture
UI (Action Map)
Navigate(Value, Vector2) - UI navigationSubmit(Button) - UI submit actionCancel(Button) - UI cancel action
Step 2: Implement Basic Touch Detection
Create a new C# script called TouchInputHandler.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class TouchInputHandler : MonoBehaviour
{
private PuzzleGameInput inputActions;
private Camera mainCamera;
private void Awake()
{
inputActions = new PuzzleGameInput();
mainCamera = Camera.main;
}
private void OnEnable()
{
inputActions.Enable();
inputActions.PuzzleGame.TouchPress.performed += OnTouchPress;
inputActions.PuzzleGame.TouchRelease.performed += OnTouchRelease;
inputActions.PuzzleGame.TouchPosition.performed += OnTouchPosition;
}
private void OnDisable()
{
inputActions.PuzzleGame.TouchPress.performed -= OnTouchPress;
inputActions.PuzzleGame.TouchRelease.performed -= OnTouchRelease;
inputActions.PuzzleGame.TouchPosition.performed -= OnTouchPosition;
inputActions.Disable();
}
private void OnTouchPress(InputAction.CallbackContext context)
{
Vector2 touchPosition = inputActions.PuzzleGame.TouchPosition.ReadValue<Vector2>();
Vector3 worldPosition = mainCamera.ScreenToWorldPoint(touchPosition);
worldPosition.z = 0;
// Detect what was touched
RaycastHit2D hit = Physics2D.Raycast(worldPosition, Vector2.zero);
if (hit.collider != null)
{
Debug.Log($"Touched: {hit.collider.name}");
// Handle puzzle piece selection
}
}
private void OnTouchRelease(InputAction.CallbackContext context)
{
// Handle touch release logic
}
private void OnTouchPosition(InputAction.CallbackContext context)
{
// Handle continuous touch position updates (for dragging)
}
}
Step 3: Implement Drag and Drop
For puzzle games, drag and drop is essential. Here's how to implement it:
public class PuzzlePieceController : MonoBehaviour
{
private bool isDragging = false;
private Vector3 offset;
private Camera mainCamera;
private TouchInputHandler touchHandler;
private void Start()
{
mainCamera = Camera.main;
touchHandler = FindObjectOfType<TouchInputHandler>();
}
public void StartDrag(Vector2 screenPosition)
{
isDragging = true;
Vector3 worldPosition = mainCamera.ScreenToWorldPoint(screenPosition);
worldPosition.z = 0;
offset = transform.position - worldPosition;
}
public void UpdateDrag(Vector2 screenPosition)
{
if (!isDragging) return;
Vector3 worldPosition = mainCamera.ScreenToWorldPoint(screenPosition);
worldPosition.z = 0;
transform.position = worldPosition + offset;
}
public void EndDrag()
{
isDragging = false;
// Snap to grid or check for valid drop location
SnapToGrid();
}
private void SnapToGrid()
{
// Implement grid snapping logic
Vector3 snappedPosition = new Vector3(
Mathf.Round(transform.position.x),
Mathf.Round(transform.position.y),
0
);
transform.position = snappedPosition;
}
}
Step 4: Implement Gesture Recognition
Puzzle games often need gestures like taps, long presses, and swipes:
public class GestureRecognizer : MonoBehaviour
{
private float tapTimeThreshold = 0.2f;
private float longPressTimeThreshold = 0.5f;
private float swipeDistanceThreshold = 50f;
private Vector2 touchStartPosition;
private float touchStartTime;
private bool isTouching = false;
public void OnTouchStart(Vector2 position)
{
touchStartPosition = position;
touchStartTime = Time.time;
isTouching = true;
}
public void OnTouchEnd(Vector2 position)
{
if (!isTouching) return;
float touchDuration = Time.time - touchStartTime;
float touchDistance = Vector2.Distance(touchStartPosition, position);
if (touchDuration < tapTimeThreshold && touchDistance < swipeDistanceThreshold)
{
OnTap(position);
}
else if (touchDuration >= longPressTimeThreshold)
{
OnLongPress(position);
}
else if (touchDistance >= swipeDistanceThreshold)
{
OnSwipe(touchStartPosition, position);
}
isTouching = false;
}
private void OnTap(Vector2 position)
{
Debug.Log($"Tap detected at {position}");
// Handle tap action (select piece, activate power-up, etc.)
}
private void OnLongPress(Vector2 position)
{
Debug.Log($"Long press detected at {position}");
// Handle long press (show hint, delete piece, etc.)
}
private void OnSwipe(Vector2 start, Vector2 end)
{
Vector2 direction = (end - start).normalized;
Debug.Log($"Swipe detected: {direction}");
// Determine swipe direction
if (Mathf.Abs(direction.x) > Mathf.Abs(direction.y))
{
if (direction.x > 0) Debug.Log("Swipe Right");
else Debug.Log("Swipe Left");
}
else
{
if (direction.y > 0) Debug.Log("Swipe Up");
else Debug.Log("Swipe Down");
}
}
}
Step 5: Handle Multi-Touch Scenarios
Some puzzle games benefit from multi-touch support:
public class MultiTouchHandler : MonoBehaviour
{
private Dictionary<int, GameObject> activeTouches = new Dictionary<int, GameObject>();
public void HandleTouch(int touchId, Vector2 position, TouchPhase phase)
{
switch (phase)
{
case TouchPhase.Began:
OnTouchBegan(touchId, position);
break;
case TouchPhase.Moved:
OnTouchMoved(touchId, position);
break;
case TouchPhase.Ended:
case TouchPhase.Canceled:
OnTouchEnded(touchId, position);
break;
}
}
private void OnTouchBegan(int touchId, Vector2 position)
{
// Find object at touch position
RaycastHit2D hit = Physics2D.Raycast(
Camera.main.ScreenToWorldPoint(position),
Vector2.zero
);
if (hit.collider != null)
{
activeTouches[touchId] = hit.collider.gameObject;
// Start interaction
}
}
private void OnTouchMoved(int touchId, Vector2 position)
{
if (activeTouches.ContainsKey(touchId))
{
// Update drag position
GameObject obj = activeTouches[touchId];
// Update object position
}
}
private void OnTouchEnded(int touchId, Vector2 position)
{
if (activeTouches.ContainsKey(touchId))
{
// Complete interaction
activeTouches.Remove(touchId);
}
}
}
Step 6: Optimize Touch Responsiveness
Mobile devices vary in performance. Here are optimization tips:
Reduce Input Lag
// Use FixedUpdate for physics-based interactions
private void FixedUpdate()
{
if (isDragging)
{
// Update drag position in FixedUpdate for smoother physics
}
}
// Use LateUpdate for visual updates
private void LateUpdate()
{
// Update visual feedback after all logic is complete
}
Implement Touch Dead Zones
private float deadZone = 10f; // pixels
private bool IsValidTouch(Vector2 touchPosition)
{
// Ignore touches too close to screen edges (accidental touches)
if (touchPosition.x < deadZone ||
touchPosition.x > Screen.width - deadZone ||
touchPosition.y < deadZone ||
touchPosition.y > Screen.height - deadZone)
{
return false;
}
return true;
}
Use Object Pooling for Touch Feedback
public class TouchFeedbackPool : MonoBehaviour
{
public GameObject feedbackPrefab;
private Queue<GameObject> pool = new Queue<GameObject>();
public void ShowFeedback(Vector2 position)
{
GameObject feedback = GetFromPool();
feedback.transform.position = position;
feedback.SetActive(true);
// Animate and return to pool after animation
StartCoroutine(ReturnToPoolAfterDelay(feedback, 0.5f));
}
private GameObject GetFromPool()
{
if (pool.Count > 0)
{
return pool.Dequeue();
}
return Instantiate(feedbackPrefab);
}
private IEnumerator ReturnToPoolAfterDelay(GameObject obj, float delay)
{
yield return new WaitForSeconds(delay);
obj.SetActive(false);
pool.Enqueue(obj);
}
}
Step 7: Test on Multiple Devices
Different devices have different touch characteristics:
Device-Specific Considerations
- Screen size - Larger screens need larger touch targets
- Touch sensitivity - Some devices are more/less sensitive
- Multi-touch support - Not all devices support the same number of simultaneous touches
- Performance - Lower-end devices may lag with complex touch handling
Testing Checklist
- [ ] Test on at least 3 different screen sizes
- [ ] Verify touch targets are at least 44x44 pixels (Apple HIG recommendation)
- [ ] Test with different touch pressures
- [ ] Verify multi-touch works correctly
- [ ] Check for accidental edge touches
- [ ] Test gesture recognition accuracy
- [ ] Verify no input lag on lower-end devices
Mini Challenge: Build a Simple Touch Demo
Create a simple scene to test your touch controls:
- Create a scene with a few puzzle pieces (sprites)
- Implement tap detection - Highlight piece when tapped
- Implement drag and drop - Move pieces around
- Add visual feedback - Show touch position with a cursor
- Test on device - Build and test on your phone
Success Criteria:
- Pieces respond instantly to touch
- Dragging feels smooth and responsive
- No accidental touches at screen edges
- Visual feedback is clear and immediate
Pro Tips
Tip 1: Use Touch Targets Larger Than Visual Elements
Make touch targets 20-30% larger than visual elements. Players will thank you for the easier interaction.
Tip 2: Implement Touch Feedback Immediately
Show visual feedback (highlight, scale, color change) immediately on touch, even before processing the action. This makes controls feel instant.
Tip 3: Handle Edge Cases
- Rapid taps - Debounce rapid taps to prevent accidental double-actions
- Touch cancellation - Handle cases where touch is interrupted (incoming call, notification)
- Screen rotation - Recalculate touch positions when screen rotates
Tip 4: Use Haptic Feedback
#if UNITY_IOS || UNITY_ANDROID
// iOS haptic feedback
#if UNITY_IOS
Handheld.Vibrate();
#endif
// Android haptic feedback
#if UNITY_ANDROID
Handheld.Vibrate();
#endif
#endif
Tip 5: Profile Touch Performance
Use Unity Profiler to identify touch-related performance issues:
- Monitor input processing time
- Check for garbage collection spikes
- Verify no frame drops during touch interactions
Common Mistakes to Avoid
Mistake 1: Ignoring Touch Dead Zones
Problem: Accidental touches at screen edges frustrate players.
Solution: Implement dead zones and ignore touches too close to edges.
Mistake 2: Not Handling Multi-Touch Conflicts
Problem: Multiple touches interfere with each other.
Solution: Track each touch separately and handle conflicts appropriately.
Mistake 3: Touch Targets Too Small
Problem: Players miss touches, especially on smaller screens.
Solution: Use touch targets at least 44x44 pixels (Apple recommendation).
Mistake 4: No Visual Feedback
Problem: Players don't know if their touch registered.
Solution: Always provide immediate visual feedback for every touch.
Mistake 5: Ignoring Device Differences
Problem: Controls work on one device but not others.
Solution: Test on multiple devices and screen sizes.
Troubleshooting
Issue: Touches Not Detected
Possible Causes:
- Input System not enabled
- Camera not set correctly
- Colliders missing on objects
- Touch targets outside camera view
Solutions:
- Verify Input System package is installed
- Check camera settings and viewport
- Add colliders to interactive objects
- Ensure objects are within camera bounds
Issue: Drag Feels Laggy
Possible Causes:
- Updating in wrong Update method
- Too many calculations per frame
- Physics updates interfering
Solutions:
- Use FixedUpdate for physics-based dragging
- Optimize calculations
- Adjust physics timestep if needed
Issue: Gestures Not Recognized
Possible Causes:
- Thresholds too strict
- Timing calculations incorrect
- Distance calculations wrong
Solutions:
- Adjust gesture thresholds
- Verify time and distance calculations
- Test with different touch speeds
What's Next?
Congratulations! You've implemented responsive touch controls for your mobile puzzle game. In the next lesson, you'll learn how to design engaging puzzle mechanics and create level progression systems that keep players coming back.
Next Lesson: Lesson 5: Puzzle Mechanics & Level Design
In Lesson 5, you'll discover:
- How to design puzzle mechanics that are easy to learn but hard to master
- Creating level progression that maintains player engagement
- Implementing difficulty scaling and adaptive challenges
- Building puzzle systems that feel rewarding and satisfying
Key Takeaways
- Touch controls are critical - They're the primary way players interact with your game
- Use the New Input System - It provides better performance and features
- Implement gestures - Taps, long presses, and swipes enhance gameplay
- Optimize for responsiveness - Instant feedback makes controls feel great
- Test on multiple devices - Different devices have different characteristics
- Provide visual feedback - Players need to know their touches registered
Additional Resources
- Unity Input System Documentation
- Mobile Input Best Practices
- Apple Human Interface Guidelines - Touch Targets
- Android Material Design - Touch Targets
Practice Exercise
Before moving to the next lesson, try these exercises:
- Create a touch demo scene with 5 interactive puzzle pieces
- Implement all three gestures - tap, long press, and swipe
- Add visual feedback for each touch type
- Test on a real device and adjust thresholds based on feel
- Optimize performance to ensure 60 FPS during touch interactions
Share your touch control implementation in the community and get feedback from other developers!
Ready to level up your puzzle game? Bookmark this lesson and move on to designing puzzle mechanics that will keep players engaged for hours!