Saltar a contenido

Gestionar colisión Bola-Paleta

Llegó el momento de la magia. Vamos a detectar cuando la bola toca la paleta, cambiar la dirección de la bola para que el desplazamiento sea hacia arriba. Para darle más interés al juego, modificaremos el desplazamiento horizontal tomando uno al azar, de forma que el ángulo de rebote sea aleatorio.

10Colision.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import tkinter
import random

class Juego:
    def __init__(self, ventana):
        self.ventana = ventana

        # Configurar ventana
        self.ventana.title('Bola')
        self.ventana.resizable(False, False)
        self.ventana.wm_attributes('-topmost', True)

        # Crear lienzo
        self.lienzo = tkinter.Canvas(self.ventana,
                                     width=500,
                                     height=400,
                                     bd=0)
        self.lienzo.pack()
        self.ventana.update()

        # Crear bola y paleta
        self.bola = Bola(self)
        self.paleta = Paleta(self)        

        # Indicadores
        self.terminar = False
        self.en_juego = False
        self.reiniciar= False

        self.texto = self.lienzo.create_text(250, 250, text='PULSE ESPACIO PARA INICIAR')

        # Asociar eventos de teclado
        self.ventana.bind_all('<KeyPress>', self.gestionar_tecla)
        self.ventana.bind_all('<KeyRelease>', self.detener_paleta)

    def gestionar_tecla(self,evento):
        if evento.keysym=='Escape':
            self.terminar = True
        elif evento.keysym=='space':            
            self.en_juego = True
            self.lienzo.itemconfig(self.texto, state='hidden')
        elif evento.keysym=='Left':
            self.paleta.desplaz_paleta(-1)
        elif evento.keysym=='Right':
            self.paleta.desplaz_paleta(1)

    def detener_paleta(self,evento):
            self.paleta.desplaz_paleta(0)

    def bucle(self):
        while True:
            if self.terminar:
                break

            if self.reiniciar and self.en_juego:
                self.lienzo.delete(self.id_bola)
                self.lienzo.delete(self.id_paleta)
                self.bola = Bola(self)
                self.paleta = Paleta(self)
                self.reiniciar = False

            if self.en_juego:
                self.ventana.update_idletasks()
                self.bola.mostrar()
                self.paleta.mostrar()

            self.ventana.update_idletasks()
            self.ventana.update()

class Bola:
    def __init__(self, juego):
        self.juego  = juego

        tamaño = 15
        self.lienzo_ancho = self.juego.lienzo.winfo_width()
        self.lienzo_alto = self.juego.lienzo.winfo_height()

        self.juego.id_bola = self.juego.lienzo.create_oval(0, 0, tamaño, tamaño,
                                                           fill='red')
        centro_x = self.lienzo_ancho/2 - tamaño/2
        centro_y = self.lienzo_alto/2 - tamaño/2
        self.juego.lienzo.move(self.juego.id_bola, centro_x, centro_y)

        self.desplaz_y = -(velocidad_bola)
        self.desplaz_x = random.choice([-2, -3, -4, 2, 3, 4])

    def mostrar(self):
        # obtener coordenadas bola (x1, y1, x2, y2)
        coord_bola = self.juego.lienzo.coords(self.juego.id_bola)
        borde_sup_bola = coord_bola[1]
        borde_inf_bola = coord_bola[3]
        borde_izd_bola = coord_bola[0]
        borde_dch_bola = coord_bola[2]

        # si ha tocado el borde superior, empezar a bajar
        if borde_sup_bola <= 1:
            self.desplaz_y = abs(self.desplaz_y)

        ## si toca la paleta, empezar a subir
        if self.toca_paleta():
            direccion = 1 if abs(self.desplaz_x)== self.desplaz_x else -1
            self.desplaz_x = random.choice([2, 3, 4])*direccion
            self.desplaz_y = abs(self.desplaz_y)*(-1)

        # si toca el borde inferior, fin del juego
        if borde_inf_bola >= self.lienzo_alto:
            self.juego.en_juego = False
            self.juego.reiniciar = True
            self.juego.lienzo.itemconfig(self.juego.texto, state='normal')

        # si ha tocado el borde lateral, cambiar de dirección
        if borde_izd_bola <= 1:
            self.desplaz_x = abs(self.desplaz_x)
        elif borde_dch_bola >= self.lienzo_ancho-1:
            self.desplaz_x = abs(self.desplaz_x)*(-1)

        # desplazar
        self.juego.lienzo.move(self.juego.id_bola, self.desplaz_x, self.desplaz_y)

    def toca_paleta(self):
        pos_bola = self.juego.lienzo.coords(self.juego.id_bola)
        pos_paleta = self.juego.lienzo.coords(self.juego.id_paleta)

        bola_sup = pos_bola[1]
        bola_inf = pos_bola[3]
        bola_izd = pos_bola[0]
        bola_dch = pos_bola[2]

        paleta_sup = pos_paleta[1]
        paleta_inf = pos_paleta[3]
        paleta_izd = pos_paleta[0]
        paleta_dch = pos_paleta[2]

        encima = bola_inf < paleta_sup
        debajo = bola_sup > paleta_inf
        izda   = bola_dch < paleta_izd
        dcha   = bola_izd > paleta_dch

        if debajo or encima:
            return False
        if izda or dcha:
            return False
        return True

class Paleta:
    def __init__(self, juego):
        self.juego  = juego

        ancho = 60
        self.lienzo_ancho = self.juego.lienzo.winfo_width()
        self.lienzo_alto = self.juego.lienzo.winfo_height()

        self.juego.id_paleta = self.juego.lienzo.create_rectangle(0, 0, ancho, 10, fill='blue')
        self.desplaz = velocidad_paleta
        self.desplaz_x = 0

        centro_x = self.lienzo_ancho/2 - ancho/2
        centro_y = self.lienzo_alto - 50

        self.juego.lienzo.move(self.juego.id_paleta, centro_x, centro_y)

    def desplaz_paleta(self, sentido):
        if not self.juego.en_juego:
            return

        if sentido==0:
            self.desplaz_x=0

        coord = self.juego.lienzo.coords(self.juego.id_paleta)
        borde_izd_paleta = coord[0]
        borde_dch_paleta = coord[2]

        self.desplaz_x = 0
        if borde_izd_paleta > 0 and sentido == -1:
            self.desplaz_x = -velocidad_paleta

        elif borde_dch_paleta < self.lienzo_ancho and sentido == 1:        
            self.desplaz_x = velocidad_paleta

    def mostrar(self):
        self.juego.lienzo.move(self.juego.id_paleta, self.desplaz_x, 0)          

velocidad_bola=4
velocidad_paleta=5
v = tkinter.Tk()
juego = Juego(v)
juego.bucle()
juego.ventana.update_idletasks()
juego.ventana.destroy()

Creamos un nuevo método Bola.toca_paleta(). Como la bola guarda una referencia al objeto juego, que a su vez mantiene referencias al canvas, y a las formas circulo y rectángulo que representan la bola y paleta, podemos calcular las coordenadas de ambas formas:

pos_bola = self.juego.lienzo.coords(self.juego.id_bola)
pos_paleta = self.juego.lienzo.coords(self.juego.id_paleta)

Con estas coordenadas podemos averiguar si ambas formas están colisionando o la bola está por encima, debajo, a la izquierda o derecha de la paleta.

Nota: realmente lo que hacemos es comprobar que ambas formas no se superpongan. Esto afecta ligeramente a la jugabilidad, porque la bola podría colisionar con la paleta por el borde lateral de esta, rebotando hacia arriba. Una vez más, no vamos a complicar el programa. Lo dejaremos así.

Expresiones condicionales

En la sentencia:

direccion = 1 if abs(self.desplaz_x)== self.desplaz_x else -1

descubrimos una característica del lenguaje Python que no habíamos visto todavía, la posibilidad de evaluar una expresión que tiene dos posibles resultados, dependiendo de una condición:

variable = valor1 if condición else valor2

Se evalúa la condición y el valor resultante es la primera expresión, si es True o la segunda, si False.