Volver al inicio

Distancias y Colisiones en Ursina 3D

Guía de distance(), distance_2d(), distance_xz() e intersects() en Ursina. Ejemplos de código para cálculo de distancia, detección de colisiones, IA y recogida de ítems en juegos 3D.

Ursina: distance y intersects para juegos
Advertisement 728x90

Distancia y detección de colisiones en Ursina para juegos 3D

En Ursina, las funciones distance(), distance_2d() y distance_xz() permiten calcular con precisión las distancias entre objetos en el espacio 3D. El método intersects() detecta colisiones utilizando colisionadores. Estas herramientas son esenciales para mecánicas como la detección, recogida de objetos y comportamiento de IA.

Funciones de cálculo de distancia

La función distance() calcula la distancia euclidiana en 3D a través de los ejes X, Y y Z entre los centros de dos objetos Entity:

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"Distancia desde el héroe hasta la esfera: {dist:.2f}")

app.run()

Fórmula: √((x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²). Ideal para análisis espacial completo.

Google AdInline article slot

distance_2d() ignora el eje Z y opera en el plano X-Y:

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"Distancia 2D desde el héroe hasta la esfera: {dist:.2f}") 

app.run()

Útil para elementos de interfaz o objetos al mismo nivel de altura.

distance_xz() se enfoca en el plano horizontal X-Z, ignorando el eje Y:

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"Distancia horizontal desde el héroe hasta la esfera: {dist:.2f}") 

app.run()

Perfecto para IA basada en el suelo donde la posición vertical no es crítica.

Sistema de colisiones intersects()

El método intersects() comprueba colisiones entre colisionadores (caja, esfera, malla) mediante el CollisionTraverser de Panda3D. Ambos objetos deben tener un collider y collision=True activado.

Devuelve un objeto HitInfo con estos campos:

Google AdInline article slot
  • hit: True/False
  • entity: el objeto colisionado
  • point: coordenadas del impacto
  • distance: distancia al punto de impacto

Ejemplo 1: Detección de paredes

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("Colisión!")
        print(f"Con: {hit_info.entity}")
        print(f"Punto de impacto: {hit_info.point}")
        print(f"Distancia al impacto: {hit_info.distance:.2f}")

app.run()

Ejemplo 2: Recogida de objetos

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('Objeto recogido!')
        destroy(pickup)

app.run()

Más preciso que distance() para formas complejas.

Ejemplo 3: Sistema de balas

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"Bala impactó a {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()

Se recomienda usar raycast() para impactos precisos en lugar de proyectiles físicos.

Casos de uso prácticos

Recogida por distancia:

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('Objeto recogido!')
        player.has_pickup = True
        pickup.animate_scale(0, duration=.1)
        destroy(pickup, delay=.1)

app.run()

IA de enemigos con zona de detección:

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()

Conclusiones clave

  • Usa distance() para 3D completo, distance_2d() para plano X-Y, distance_xz() para plano horizontal X-Z.
  • intersects() requiere colisionadores en ambos objetos y devuelve HitInfo detallado.
  • Para balas, prefiere raycast() sobre proyectiles físicos.
  • Combina distance() con intersects() para zonas de interacción y comprobaciones de colisión precisas.
  • Siempre usa ignore=(self,) en intersects() para evitar colisiones consigo mismo.

— Editorial Team

Advertisement 728x90

Leer después