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.
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:
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:
- 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 devuelveHitInfodetallado.- Para balas, prefiere
raycast()sobre proyectiles físicos. - Combina
distance()conintersects()para zonas de interacción y comprobaciones de colisión precisas. - Siempre usa
ignore=(self,)enintersects()para evitar colisiones consigo mismo.
— Editorial Team
Aún no hay comentarios.