Back to Home

Distances and Collisions in Ursina 3D

Guide to distance(), distance_2d(), distance_xz() and intersects() functions in Ursina. Code examples for distance calculation, collision detection, AI and item pickup in 3D games.

Ursina: distance and intersects for games
Advertisement 728x90

Distance and Collision Detection in Ursina for 3D Games

In Ursina, the distance(), distance_2d(), and distance_xz() functions enable precise calculation of distances between objects in 3D space. The intersects() method detects collisions using colliders. These tools are essential for mechanics like detection, item collection, and AI behavior.

Distance Calculation Functions

The distance() function computes Euclidean distance in 3D across all X, Y, and Z axes between the centers of two Entity objects:

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=20, collider='box')
player = FirstPersonController(position=(0, 2, 0))
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue)
Sky()

def update():
    dist = distance(player, sphere)
    print(f"Distance from hero to sphere: {dist:.2f}")

app.run()

Formula: √((x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²). Ideal for full spatial analysis.

Google AdInline article slot

distance_2d() ignores the Z-axis and operates in the X-Y plane:

from ursina import * 
from ursina.prefabs.first_person_controller import FirstPersonController 
app = Ursina() 
ground = Entity( 
    model='plane', 
    texture='grass', 
    scale=20, 
    collider='box' 
) 
player = FirstPersonController(position=(0, 2, 0)) 
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue) 
Sky() 

def update(): 
    dist = distance_2d(player, sphere) 
    print(f"2D distance from hero to sphere: {dist:.2f}") 

app.run()

Useful for UI elements or objects at the same height level.

distance_xz() focuses on the horizontal X-Z plane, ignoring the Y-axis:

Google AdInline article slot
from ursina import * 
from ursina.prefabs.first_person_controller import FirstPersonController 
app = Ursina() 
ground = Entity( 
    model='plane', 
    texture='grass', 
    scale=20, 
    collider='box' 
) 
player = FirstPersonController(position=(0, 2, 0)) 
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue) 
Sky() 

def update(): 
    dist = distance_xz(player, sphere) 
    print(f"Horizontal distance from hero to sphere: {dist:.2f}") 

app.run()

Perfect for ground-based AI where vertical position isn’t critical.

The intersects() Collision System

The intersects() method checks for collisions between colliders (box, sphere, mesh) via Panda3D’s CollisionTraverser. Both objects must have a collider and collision=True set.

Returns a HitInfo object with these fields:

Google AdInline article slot
  • hit: True/False
  • entity: the collided object
  • point: impact coordinates
  • distance: distance to the impact point

Example 1: Wall Detection

from ursina import *
app = Ursina()
player = Entity(model='cube', color=color.orange, collider='box')
wall = Entity(model='cube', position=(5,0,0), scale=(1,5,5), collider='box', color=color.red)

def update():
    player.x += 0.1 * time.dt
    hit_info = player.intersects()
    if hit_info.hit:
        print("Collision!")
        print(f"With: {hit_info.entity}")
        print(f"Impact point: {hit_info.point}")
        print(f"Distance to impact: {hit_info.distance:.2f}")

app.run()

Example 2: Item Pickup

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=10, collider='box')
player = FirstPersonController(model='cube', origin_y=-.5)
pickup = Entity(model='sphere', position=(1,.5,3), collider='box')

def update():
    hit_info = player.intersects()
    if hit_info.hit and hit_info.entity == pickup:
        print('Item picked up!')
        destroy(pickup)

app.run()

More accurate than distance() for complex shapes.

Example 3: Bullet System

import random
from ursina import *
app = Ursina()

class Bullet(Entity):
    def __init__(self, position, direction):
        super().__init__(
            model='sphere',
            scale=.1,
            speed=15,
            collider='sphere',
            position=position,
            color=color.yellow,
            name='bullet'
        )
        self.direction = direction.normalized()

    def update(self):
        self.position += self.direction * time.dt * self.speed
        hit_info = self.intersects(ignore=(self,))
        if hit_info.hit:
            print(f"Bullet hit {hit_info.entity.name}!")
            destroy(self)

class Enemy(Entity):
    def __init__(self):
        super().__init__(
            model='cube',
            color=color.red,
            scale=1,
            collider='box',
            name='enemy'
        )
        self.x = random.choice([-4, 4])
        self.z = random.choice([-4, 4])

for i in range(5):
    Enemy()
player = Entity(model='cube', color=color.blue, position=(0, 0, 0))
shoot_timer = 0

def update():
    global shoot_timer
    shoot_timer += time.dt
    if shoot_timer >= 1:
        shoot_timer = 0
        random_direction = Vec3(
            random.uniform(-1, 1),
            random.uniform(-1, 1),
            random.uniform(-1, 1)
        ).normalized()
        bullet = Bullet(
            position=player.position,
            direction=random_direction
        )

app.run()

Recommended to use raycast() for precise hits instead of physical projectiles.

Practical Use Cases

Pickup by Distance:

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=10, collider='box')
player = FirstPersonController(model='cube', origin_y=-.5, color=color.orange, has_pickup=False)
pickup = Entity(model='sphere', position=(1,.5,3))

def update():
    if not player.has_pickup and distance(player, pickup) < pickup.scale_x / 2:
        print('Item picked up!')
        player.has_pickup = True
        pickup.animate_scale(0, duration=.1)
        destroy(pickup, delay=.1)

app.run()

Enemy AI with Detection Zone:

from ursina import *
app = Ursina()
player = Entity(model='cube', position=(0, 0, 0), color=color.blue)
ground = Entity(model='plane', scale=20, color=color.dark_gray, y=-1)

class Enemy(Entity):
    def __init__(self, **kwargs):
        super().__init__(model='cube', scale_y=2, origin_y=-.5, color=color.light_gray, **kwargs)
        self.original_color = self.color
        self.detection_radius = 10

    def update(self):
        dist = distance_xz(player.position, self.position)
        if dist > self.detection_radius:
            self.color = self.original_color
            return
        self.color = color.red
        self.look_at_2d(player.position, 'y')
        if dist > 2:
            self.position += self.forward * time.dt * 3

enemies = [Enemy(x=x * 5, z=5) for x in range(-2, 3)]

def update():
    player.x += held_keys['d'] * time.dt * 5

app.run()

Key Takeaways

  • Use distance() for full 3D, distance_2d() for X-Y plane, distance_xz() for horizontal X-Z plane.
  • intersects() requires colliders on both objects and returns detailed HitInfo.
  • For bullets, prefer raycast() over physical projectiles.
  • Combine distance() with intersects() for interaction zones and precise collision checks.
  • Always use ignore=(self,) in intersects() to prevent self-collision.

— Editorial Team

Advertisement 728x90

Read Next