De esta forma podemos llamar la función move dentro del ciclo principal del programa, antes de llamar la función que dibuja los elementos
Crear partículas con movimiento, y que tengan un limite es bastante interesante, pero no es suficiente. En el hockey de mesa un punto importante son las colisiones entre el disco y el mazo, por lo cual es imprescindible agregar ese tipo de interacción al programa. Entonces lo que necesitábamos hacer era:
Las cosas se empiezan a poner difíciles cuando tenemos múltiples partículas interactuando unas con otras. De hecho, es matemáticamente imposible resolver las ecuaciones que describen tres o más objetos que interactuan. En nuestra simulación se hicieron simplificaciones, que aunque son un tanto imprecisas, representan una razonable aproximación a la realidad.
Primero para probar si dos partículas se sobreponen unas a otras, necesitamos comparar cada partícula con las demás. Podemos hacer éso con un simple for anidado, justo después de moverlos y checar si han tocado un límite, pero antes de dibujarlos en la pantalla:
El segundo for anidado no es un completo, es decir no iteramos todas las partículas. Si tuviéramos dos ciclos completos, compararíamos cada partícula con las demás dos veces cada una(además de compararla consigo mismo). En cambio, comparamos cada partícula con cada partícula con un índice superior que ella en el arreglo. Entonces necesitamos saber el índice de la partícula actual, el cual podemos conseguir usando enumerate. Usamos el valor de i para cortar el arreglo desde i+1 hasta el final para construir el segundo for.
Como mencionamos en la teoría, cuando dos partículas circulares chocan, hacen contacto en un punto infinitamente pequeño. El ángulo de este punto es la tangente de la partícula en este punto. Entonces el ángulo de salida es perpendicular a la linea que se une el centro de las dos partículas.
Entonces podemos tratar la colisión como si las partículas estuvieran rebotando con una superficie plana con el ángulo de la tangente. Podemos encontrar dicho ángulo con la siguiente fórmula:
Para reflejar el ángulo de las partículas en la superficie, restamos el ángulo actual menos dos veces la tangente, para lo cual necesitamos conocer el ángulo en el que nuestra partícula viajaba, el mismo que usamos en el movimiento.
p1.angle = 2*tangent - p1.angle
p2.angle = 2*tangent - p2.angle
Entonces necesitamos cambiar las velocidades de las dos partículas, debido a que transfieren las energías uno a otro. Podemos hacer esto con una simple tupla.
(p1.speed, p2.speed) = (p2.speed, p1.speed)
Entonces podemos agregar una elasticidad para reducir la energía de ambas partículas después de la colisión.
p1.speed *= elasticity
p2.speed *= elasticity
Código
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Referencias:
# Imagen de fondo obtenida de https://engineering.purdue.edu/477grp6/images/lrprice/air_hockey_concept.png
#
# Código basado en el tutorial http://razonartificial.com/tutoriales-pygame/
# Código basado en el tutorial http://www.petercollingridge.co.uk/book/export/html/6
# Código basado en el tutorial http://www.petercollingridge.co.uk/pygame-physics-simulation/mouse-interactions
#modulos
import sys, pygame, random, math
from pygame.locals import *
#Constantes
width = 800
height = 600
handles = []
pucks = []
nHandles = 2
npocks = 1
puntuacion = [0,0]#jugador A, jugador B
#Clases
class Puck(pygame.sprite.Sprite):
def __init__(self, x, y, id_):
self.inicial = [x,y]
self.anterior = [0, 0]
self.cambio = [0.0, 0.0, 0.0]#X, Y y Contador
self.id_ = id_
pygame.sprite.Sprite.__init__(self)
self.image = load_image("images/ball.png", True)
self.diametro = width*0.07
self.image = pygame.transform.scale(self.image, (int(self.diametro) ,int(self.diametro) ))
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.centery = y
self.speed = [0.8, 0.8]
self.coeficiente_arrastre = 0.001
self.acceleration = [0, 0]
self.velocidad_juego = 4
def actualizar(self, time, bandera):
if bandera == self.id_:
if self.cambio[2] == 15:
self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
pos = pygame.mouse.get_pos()
self.rect.centerx = pos[0]
self.rect.centery = pos[1]
self.cambio[2] = self.cambio[2]+1
self.speed[0] = (self.cambio[0] + (float(pos[0] - self.anterior[0]))/(time*16) )
self.speed[0]/self.cambio[2]
self.speed[1] = (self.cambio[1] + (float(pos[1] - self.anterior[1]))/(time*16) )
self.speed[1]/self.cambio[2]
self.anterior[0] += self.speed[0] * time
self.anterior[1] += self.speed[1] * time
else:
self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
self.anterior[0] = self.rect.centerx
self.anterior[1] = self.rect.centery
self.speed[0] = self.speed[0]*(1-self.coeficiente_arrastre)
self.speed[1] = self.speed[1]*(1-self.coeficiente_arrastre)
self.rect.centerx += self.speed[0] * time
self.rect.centery += self.speed[1] * time
if (self.rect.left <= 0 or self.rect.right >= width) and (self.rect.bottom <= height*(1.0/3) or self.rect.top > height*(2.0/3) ):
self.speed[0] = - self.speed[0]
self.rect.centerx += self.speed[0] * time
elif (self.rect.right < 0 or self.rect.left > width) and not (self.rect.bottom <= height*(1.0/3) or self.rect.top > height*(2.0/3) ):
if self.rect.centerx <= 0:
puntuacion[0] = puntuacion[0] + 1
if self.rect.centerx > width:
puntuacion[1] = puntuacion[1] + 1
self.rect.centerx = self.inicial[0]
self.rect.centery = self.inicial[1]
self.speed[0] = self.speed[1] = 0
if self.rect.top <= 0 or self.rect.bottom >= height:
self.speed[1] = -self.speed[1]
self.rect.centery += self.speed[1] * time
for i in range(len(handles)):
if math.hypot((handles[i].rect.centerx - self.rect.centerx), (handles[i].rect.centery - self.rect.centery) ) <= (self.diametro):
angulo = math.atan2(self.rect.centery - handles[i].rect.centery, self.rect.centerx-handles[i].rect.centerx )
angulo_invertido = math.atan2(-self.speed[1], -self.speed[0])
angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
self.speed[1] = math.sin(angulo_reflexion)
self.speed[0] = math.cos(angulo_reflexion)
for i in range(len(pucks)):
if math.hypot((pucks[i].rect.centerx - self.rect.centerx), (pucks[i].rect.centery - self.rect.centery) ) <= (self.diametro) and pucks[i].id_ != self.id_ :
angulo = math.atan2(self.rect.centery - pucks[i].rect.centery, self.rect.centerx-pucks[i].rect.centerx )
angulo_invertido = math.atan2(-self.speed[1],-self.speed[0])
angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
self.speed[1] = math.sin(angulo_reflexion)
self.speed[0] = math.cos(angulo_reflexion)
class Handle(pygame.sprite.Sprite):
def __init__(self, x, y, id_):
self.inicial = [float(x), float(y)]
self.anterior = [0.0, 0.0]
self.cambio = [0.0, 0.0, 0.0]#X, Y y Contador
self.id_ = id_
pygame.sprite.Sprite.__init__(self)
self.image = load_image("images/handle.png", True)
self.diametro = width*0.15
self.image = pygame.transform.scale(self.image, (int(self.diametro) ,int(self.diametro) ))
self.rect = self.image.get_rect()
self.rect.centerx = float(x)
self.rect.centery = float(y)
self.speed = [0.2, 0.2]
self.coeficiente_arrastre = 0.012
self.acceleration = [0, 0]
def inteligencia(self, time):
if pucks[0].rect.centerx <= width:
if pucks[0].rect.centerx < self.rect.centerx :
self.rect.centerx += 5
else:
self.rect.centerx -= 5
if pucks[0].rect.centery < self.rect.centery :
self.rect.centery += 5
else:
self.rect.centery -= 5
else:
if pucks[0].rect.centerx < self.rect.centerx :
self.rect.centerx += 5
else:
self.rect.centerx -= 5
if pucks[0].rect.centery < self.rect.centery :
self.rect.centery += 5
else:
self.rect.centery -= 5
if self.rect.left <= 0:
self.rect.centerx = width/2
if self.rect.right >= width/2:
self.rect.centerx = width/2
if self.rect.top <= 0:
self.rect.centery = 0
if self.rect.bottom >= height:
self.rect.centery = height
def actualizar(self, time, bandera):
if bandera == self.id_:
if self.cambio[2] == 15:
self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
pos = pygame.mouse.get_pos()
self.rect.centerx = pos[0]
self.rect.centery = pos[1]
self.cambio[2] = self.cambio[2]+1
self.speed[0] = (self.cambio[0] + (float(pos[0] - self.anterior[0]))/(time*16) )
self.speed[0]/self.cambio[2]
self.speed[1] = (self.cambio[1] + (float(pos[1] - self.anterior[1]))/(time*16) )
self.speed[1]/self.cambio[2]
self.anterior[0] += self.speed[0] * time
self.anterior[1] += self.speed[1] * time
else:
self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
self.anterior[0] = self.rect.centerx
self.anterior[1] = self.rect.centery
self.speed[0] = self.speed[0]*(1-self.coeficiente_arrastre)
self.speed[1] = self.speed[1]*(1-self.coeficiente_arrastre)
self.rect.centerx += self.speed[0] * time
self.rect.centery += self.speed[1] * time
if self.rect.left <= 0 or self.rect.right >= width:
self.speed[0] = - self.speed[0]
self.rect.centerx += self.speed[0] * time
if self.rect.top <= 0 or self.rect.bottom >= height:
self.speed[1] = -self.speed[1]
self.rect.centery += self.speed[1] * time
if self.rect.left <= width/2:
self.rect.centerx = width/2
for i in range(len(handles)):
if math.hypot((handles[i].rect.centerx - self.rect.centerx), (handles[i].rect.centery - self.rect.centery) ) <= (self.diametro/2) and handles[i].id_ != self.id_ :
angulo = math.atan2(self.rect.centery - handles[i].rect.centery, self.rect.centerx-handles[i].rect.centerx )
angulo_invertido = math.atan2(-self.speed[1], -self.speed[0])
angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
self.speed[1] = math.sin(angulo_reflexion)
self.speed[0] = math.cos(angulo_reflexion)
for i in range(len(pucks)):
if math.hypot((pucks[i].rect.centerx - self.rect.centerx), (pucks[i].rect.centery - self.rect.centery) ) <= (self.diametro/2) :
angulo = math.atan2(self.rect.centery - pucks[i].rect.centery, self.rect.centerx-pucks[i].rect.centerx )
angulo_invertido = math.atan2(-self.speed[1],-self.speed[0])
angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
self.speed[1] = math.sin(angulo_reflexion)
self.speed[0] = math.cos(angulo_reflexion)
#Funciones
def select_handle(vector):
pos = pygame.mouse.get_pos()
for i in range(len(vector)):
if math.hypot(int(vector[i].rect.centerx-pos[0]), int(vector[i].rect.centery-pos[1]) ) <= (vector[i].diametro/2):
return vector[i].id_
return -1
##########################################
# Carga la imagen y la convierte al formato
# optimizado de pygame
##########################################
def load_image(filename, transparent=False):
try: image = pygame.image.load(filename)
except pygame.error, message:
raise SystemExit, message
image = image.convert()
if transparent:
color = image.get_at((0,0))
image.set_colorkey(color, RLEACCEL)
return image
def main():
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Clase de simulación")
background_image = load_image("images/background.png")
fuente = pygame.font.Font(None, 40)
fuente_a = pygame.font.Font(None, 80)
fuente_b = pygame.font.Font(None, 80)
puntos_a = fuente_a.render('0', True, (255, 255, 255), (159, 182, 205))
puntos_a_r = puntos_a.get_rect()
puntos_b = fuente_b.render('0', True, (255, 255, 255), (159, 182, 205))
puntos_b_r = puntos_b.get_rect()
principal = fuente.render('', True, (255, 255, 255), (159, 182, 205))
principal_r = principal.get_rect()
principal_r.centerx = 400
principal_r.centery = 160
puntos_a_r.centerx = 100
puntos_a_r.centery = 90
puntos_b_r.centerx = 700
puntos_b_r.centery = 90
clock = pygame.time.Clock()
screen = pygame.display.set_mode((width, height))
bandera = -1
# for i in range(nHandles):
handles.append( Handle(random.randrange(0,width), random.randrange(0,height), 0))
handles.append( Handle(random.randrange(0,width), random.randrange(0,height), 1))
for i in range(nHandles, nHandles+npocks):
pucks.append( Puck(random.randrange(0,width), random.randrange(0,height), i) )
while True:
time = clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
sys.exit(0)
if event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
bandera = select_handle(handles)
if bandera < 0:
bandera = select_handle(pucks)
elif event.type == pygame.MOUSEBUTTONUP:
bandera = -1
# En esta parte se ponen las cosas que se redibujan
# for i in handles:
# i.actualizar(time, bandera)
# screen.blit(i.image, i.rect)
screen.blit(background_image, (0, 0))
handles[0].inteligencia(time)
handles[1].actualizar(time, bandera)
for i in pucks:
i.actualizar(time, bandera)
screen.blit(i.image, i.rect)
puntos_a = fuente_a.render(str(puntuacion[0]), True, (255, 255, 255), (159, 182, 205))
puntos_b = fuente_a.render(str(puntuacion[1]), True, (255, 255, 255), (159, 182, 205))
screen.blit(handles[1].image, handles[1].rect)
screen.blit(handles[0].image, handles[0].rect)
screen.blit(puntos_a, puntos_a_r)
screen.blit(puntos_b, puntos_b_r)
screen.blit(principal, principal_r)
#-------------------> Aqui se termian de redibujar los componentes
pygame.display.flip()
#-------------------> Aqui termina el while
return 0
if __name__ == '__main__':
pygame.init()
main()
Referencias: