Unity
Game Dev

CircleCast in Unity is a 2D physics method that detects objects within a circular area moving along a specified direction. Use Physics2D.CircleCast(origin, radius, direction, distance, layerMask) for area-based collision detection, enemy detection systems, and predictive movement. Essential for creating sophisticated gameplay mechanics like AoE abilities and AI behavior. Optimize performance using layer masks, appropriate update frequencies, and limited detection ranges for smooth gameplay.

Project Manager Using AI for Workflow

CircleCasting in Unity is a really useful 2D physics feature that lets you check for objects inside a circle as it moves through the game world. You can think of it like casting an invisible bubble in a certain direction to see what it bumps into. While it is mainly designed for 2D projects, getting comfortable with CircleCast can open the door to more advanced collision detection and gameplay mechanics, from detecting nearby enemies to building responsive movement systems. In this guide, we will break down what CircleCast is, how it works, and different ways you can use it, from simple setups to more advanced techniques that can make your games feel smoother and more interactive.

What is CircleCast in Unity?

CircleCast is a physics query method in Unity's 2D physics system that casts a circle through the scene in a specified direction. Think of it as sweeping a circular area through space to detect what objects it encounters along its path. Unlike a simple point-based raycast, CircleCast provides area-based detection, making it ideal for detecting objects within a radius.

Key Characteristics of CircleCast:

  • Shape: Uses a circular detection area
  • Dimension: Primarily for 2D physics (Physics2D)
  • Direction: Can be cast in any direction
  • Distance: Controllable casting distance
  • Filtering: Supports layer masks and collision filtering

When to Use CircleCast

CircleCast is particularly useful for:

  • Character Detection: Finding nearby enemies or allies within a specific range
  • Area of Effect (AoE) Abilities: Detecting targets for spells or special attacks
  • Collision Prediction: Preventing objects from overlapping by checking future positions
  • Environmental Queries: Scanning areas for interactive objects or obstacles
  • AI Behavior: Helping NPCs detect players or objects within their awareness radius

Basic CircleCast Syntax and Parameters

The most common CircleCast method signature is:

Physics2D.CircleCast(origin, radius, direction, distance, layerMask)

Parameters Explained:

  • origin (Vector2): The starting point of the circle
  • radius (float): The radius of the circle being cast
  • direction (Vector2): The direction to cast the circle
  • distance (float): How far to cast the circle
  • layerMask (int): Which layers to include in the detection (optional)

Implementing Basic CircleCast

Here's a simple example of how to implement CircleCast in a Unity script:

using UnityEngine;

public class BasicCircleCast : MonoBehaviour
{
    [Header("CircleCast Settings")]
    public float castRadius = 1.0f;
    public float castDistance = 5.0f;
    public LayerMask detectionLayers = -1;
    
    void Update()
    {
        PerformCircleCast();
    }
    
    void PerformCircleCast()
    {
        Vector2 origin = transform.position;
        Vector2 direction = transform.right; // Cast to the right
        
        RaycastHit2D hit = Physics2D.CircleCast(
            origin, 
            castRadius, 
            direction, 
            castDistance, 
            detectionLayers
        );
        
        if (hit.collider != null)
        {
            Debug.Log($"CircleCast hit: {hit.collider.name}");
            Debug.Log($"Hit point: {hit.point}");
            Debug.Log($"Hit distance: {hit.distance}");
        }
    }
    
    // Visualize the CircleCast in Scene view
    void OnDrawGizmos()
    {
        Vector2 origin = transform.position;
        Vector2 direction = transform.right;
        
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(origin, castRadius);
        
        Gizmos.color = Color.yellow;
        Vector2 endPosition = origin + direction * castDistance;
        Gizmos.DrawWireSphere(endPosition, castRadius);
        
        Gizmos.color = Color.green;
        Gizmos.DrawLine(origin, endPosition);
    }
}

Here's an image of the script in action with some basic 2D objects.

We've added some code to change the SpriteRenderer's color on hit. Notice how the image shows multiple potential hits, but it only updates 1 object. This is the basic behavior of CircleCast. If we want to detect multiple collisions, then we'll need to use CircleCastAll.

Advanced CircleCast Techniques

Multiple Hit Detection with CircleCastAll

Sometimes you need to detect all objects within the CircleCast area, not just the first one. Below is a script that does precisely that.

using UnityEngine;

public class MultipleHitCircleCast : MonoBehaviour
{
    [Header("Multi-Hit Settings")]
    public float castRadius = 2.0f;
    public float castDistance = 10.0f;
    public LayerMask targetLayers = -1;
    public bool showDebug = true;

    void FixedUpdate()
    {
        DetectAllTargets();
    }

    void DetectAllTargets()
    {
        Vector2 origin = transform.position;
        Vector2 direction = transform.right;

        RaycastHit2D[] hits = Physics2D.CircleCastAll(
            origin,
            castRadius,
            direction,
            castDistance,
            targetLayers
        );

        foreach (RaycastHit2D hit in hits)
        {
            Debug.Log($"Detected: {hit.collider.name} at distance {hit.distance}");

            // Process each detected object
            ProcessDetectedObject(hit.collider.gameObject);
        }
    }

    void ProcessDetectedObject(GameObject detectedObject)
    {
        // YOUR DETECT CODE HERE

        // Our image shows this logic
        // detectedObject.GetComponent<SpriteRenderer>().color = Color.green;
    }

    void OnDrawGizmos()
    {
        if (!showDebug) return;

        Vector2 origin = transform.position;
        Vector2 direction = transform.right;
        
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(origin, castRadius);
        
        Gizmos.color = Color.yellow;
        Vector2 endPosition = origin + direction * castDistance;
        Gizmos.DrawWireSphere(endPosition, castRadius);
        
        Gizmos.color = Color.green;
        Gizmos.DrawLine(origin, endPosition);
    }
}

In our image below, you can see that everything between the red and yellow circle now gets detected. Imagine that the circle started at the red position and quickly scanned all the way to the yellow position, marking anything along its path. But this is really all happening inside of a single frame.

Conditional CircleCast with Filtering

You can create more sophisticated detection systems by combining CircleCast with custom filtering:

using UnityEngine;
using System.Collections.Generic;

public class SmartCircleCast : MonoBehaviour
{
    [System.Serializable]
    public class DetectionSettings
    {
        public string targetTag = "Enemy";
        public float maxDistance = 5.0f;
        public bool requireLineOfSight = true;
        public LayerMask obstacleLayers = 1;
    }
    
    [Header("Smart Detection")]
    public float detectionRadius = 3.0f;
    public DetectionSettings settings;
    public bool showDebug = false;
    
    void FixedUpdate()
    {
        GameObject[] validTargets = FindValidTargets();
        foreach (GameObject target in validTargets)
        {
            // YOUR DETECTION CODE HERE
            // tint the object green
            //target.GetComponent<SpriteRenderer>().color = Color.green;
        }
    }

    public GameObject[] FindValidTargets()
    {
        Vector2 origin = transform.position;
        Vector2 direction = Vector2.zero; // 360-degree detection
        
        RaycastHit2D[] hits = Physics2D.CircleCastAll(
            origin,
            detectionRadius,
            direction,
            0f // No movement, just area detection
        );
        
        List<GameObject> validTargets = new List<GameObject>();
        
        foreach (RaycastHit2D hit in hits)
        {
            if (IsValidTarget(hit.collider.gameObject, origin))
            {
                validTargets.Add(hit.collider.gameObject);
            }
        }
        
        return validTargets.ToArray();
    }
    
    bool IsValidTarget(GameObject target, Vector2 origin)
    {
        // Check tag
        if (!target.CompareTag(settings.targetTag))
            return false;
        
        // Check distance
        float distance = Vector2.Distance(origin, target.transform.position);
        if (distance > settings.maxDistance)
            return false;
        
        // Check line of sight if required
        if (settings.requireLineOfSight)
        {
            Vector2 directionToTarget = (target.transform.position - transform.position).normalized;
            RaycastHit2D losCheck = Physics2D.Raycast(
                origin,
                directionToTarget,
                distance,
                settings.obstacleLayers
            );
            
            if (losCheck.collider != null)
                return false; // Obstacle blocking line of sight
        }
        
        return true;
    }

    void OnDrawGizmos()
    {
        if (!showDebug) return;

        Vector2 origin = transform.position;
        
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(origin, detectionRadius);
    }
}

Below is an example of this script. You can see the 2 red circles tagged as "Enemy" were inside the CircleCast, but only the one in line of sight was detected as hit and turned green. It's important to note that the obstacleLayers should be different then Default, or the layer of the enemy. Otherwise, this script's RayCast would have thought either the white circle, or even the red "enemy" circles themselves were obstacles. In this example, our blue boxes are on the Obstacles layer.

Performance Optimization Tips

1. Use Appropriate Update Frequency

Don't perform CircleCast every frame unless necessary, it's good to have a sample rate, which limits the amount of times you cast to a set interval. This will make your games more performant:

public class OptimizedCircleCast : MonoBehaviour
{
    private float lastCastTime;
    private float castInterval = 0.1f; // Cast every 0.1 seconds
    
    void Update()
    {
        if (Time.time - lastCastTime >= castInterval)
        {
            PerformCircleCast();
            lastCastTime = Time.time;
        }
    }
}

2. Limit Detection Range

Always use reasonable detection ranges to avoid unnecessary calculations:

[Range(0.1f, 10.0f)]
public float maxDetectionRange = 5.0f;

3. Use Layer Masks Effectively

Properly configure layer masks to only detect relevant objects:

[Header("Layer Configuration")]
public LayerMask enemyLayers = (1 << 8); // Only layer 8
public LayerMask environmentLayers = (1 << 0) | (1 << 9); // Layers 0 and 9

Common CircleCast Use Cases and Examples

Enemy Detection System

using UnityEngine;

public class EnemyDetector : MonoBehaviour
{
    [Header("Detection Settings")]
    public float detectionRadius = 4.0f;
    public LayerMask enemyLayer = 1 << 8;
    
    private bool enemyDetected = false;
    
    void Update()
    {
        CheckForEnemies();
    }
    
    void CheckForEnemies()
    {
        RaycastHit2D hit = Physics2D.CircleCast(
            transform.position,
            detectionRadius,
            Vector2.zero, // No direction needed for area detection
            0f,
            enemyLayer
        );
        
        if (hit.collider != null && !enemyDetected)
        {
            OnEnemyDetected(hit.collider.gameObject);
            enemyDetected = true;
        }
        else if (hit.collider == null && enemyDetected)
        {
            OnEnemyLost();
            enemyDetected = false;
        }
    }
    
    void OnEnemyDetected(GameObject enemy)
    {
        Debug.Log($"Enemy detected: {enemy.name}");
        // Trigger alert state, change music, etc.
    }
    
    void OnEnemyLost()
    {
        Debug.Log("Enemy lost from detection range");
        // Return to normal state
    }
}

Predictive Collision Detection

using UnityEngine;

public class PredictiveMovement : MonoBehaviour
{
    [Header("Movement Prediction")]
    public float moveSpeed = 5.0f;
    public float collisionCheckRadius = 0.6f;
    public float predictiveDistance = 0.1f;
    // NOTE: Make sure this layer isn't shared with your gameobject, or it won't ever move!
    public LayerMask obstacleLayer;
    
    void FixedUpdate()
    {
        Vector2 inputDirection = GetInputDirection();
        
        if (inputDirection != Vector2.zero && CanMoveInDirection(inputDirection))
        {
            transform.Translate(inputDirection * moveSpeed * Time.deltaTime);
        }
    }
    
    bool CanMoveInDirection(Vector2 direction)
    {
        RaycastHit2D hit = Physics2D.CircleCast(
            transform.position,
            collisionCheckRadius,
            direction,
            predictiveDistance,
            obstacleLayer
        );

        Debug.Log(hit.collider);
        
        // Can move if no collision detected
        return hit.collider == null;
    }
    
    Vector2 GetInputDirection()
    {
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");
        return new Vector2(horizontal, vertical).normalized;
    }
}

Troubleshooting Common Issues

Issue 1: CircleCast Not Detecting Objects

Possible Solutions:

  • Check if objects have 2D colliders attached
  • Verify layer mask settings
  • Ensure objects are on the correct layers
  • Check if colliders are set as triggers when they shouldn't be

Issue 2: Performance Problems

Possible Solutions:

  • Reduce casting frequency
  • Limit detection range
  • Use more specific layer masks
  • Consider using Coroutines for expensive operations

Issue 3: Inconsistent Detection

Possible Solutions:

  • Verify collider sizes and positions
  • Check for floating-point precision issues
  • Ensure consistent FixedUpdate timing for physics-related code
  • Add Debug Drawing to help see what's happening.

Best Practices for CircleCast Implementation

  1. Always visualize your casts using Gizmos during development
  2. Use layer masks to improve performance and accuracy
  3. Cache results when possible to avoid redundant calculations
  4. Consider the physics timestep when using CircleCast in FixedUpdate
  5. Test edge cases like objects entering/exiting detection range
  6. Document your layer configurations for team collaboration

Conclusion

CircleCast is a versatile tool in Unity's physics arsenal that enables sophisticated area-based detection systems. By understanding its parameters, implementing proper optimization techniques, and following best practices, you can create responsive and efficient gameplay mechanics.

Whether you're developing enemy AI, collision prediction systems, or interactive environmental elements, CircleCast provides the foundation for robust spatial queries in your 2D Unity projects. Remember to always profile your code and optimize for your specific use case to ensure smooth gameplay performance.

Start with simple implementations and gradually add complexity as your understanding grows. With practice, CircleCast will become an invaluable tool in your Unity development toolkit.

Looking for a reliable partner for your next project?

At SLIDEFACTORY, we’re dedicated to turning ideas into impactful realities. With our team’s expertise, we can guide you through every step of the process, ensuring your project exceeds expectations. Reach out to us today and let’s explore how we can bring your vision to life!

Contact Us
Posts

More Articles

Vision Pro Headset
Contact Us

Need Help? Let’s Get Started.

Looking for a development partner to help you make something incredible?

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.