-
Notifications
You must be signed in to change notification settings - Fork 0
/
ball.py
153 lines (143 loc) · 6.24 KB
/
ball.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
import pygame
from math import sin, cos, pi
import os
class Ball:
def __init__(self, position, bearing, speed, radius):
"""
Position is a *LIST* of [x, y] in pixels. So I can mutate them.
Bearing is in radians anticlockwise from right ->
Speed is in pixels per tick
Radius is in pixels
"""
self.position = position
self.bearing = bearing
self.speed = speed
self.radius = radius
self.sprite = pygame.image.load(
os.path.join("images", "ball_{}.png".format(radius)))
if self.sprite.get_size() != (2*radius, 2*radius):
raise RuntimeError("Ball sprite is the wrong size.")
def draw(self, surface, pos=None):
# Blit's position argument is the top-left of the sprite
# whereas self.position is the centre.
if pos is None:
surface.blit(self.sprite, (self.position[0] - self.radius,
self.position[1] - self.radius))
else:
surface.blit(self.sprite, pos)
def move(self, dt):
"""
dt is the time which has passed, in milliseconds, since the last time
ball.move was called. This means even if your computer chugs at a low
fps the ball should still move at the same speed
"""
speed_adj = self.speed * dt / (1000.0 / 50)
self.position[0] += int(speed_adj * cos(self.bearing))
self.position[1] += int(speed_adj * sin(self.bearing))
def condition_bearing(self):
"""
Ensure the bearing is in the range -pi to pi
"""
while self.bearing < -pi:
self.bearing += 2*pi
while self.bearing > pi:
self.bearing -= 2*pi
def collide_rect_internal(self, rect):
"""
rect is a pygame Rect.
Use this method when we are bouncing around inside the rectangle
Returns true if a bounce occurred, else False
"""
bounce = False
if self.position[1] - rect.top < self.radius:
# Bounce top edge
self.bearing *= -1
self.position[1] = rect.top + self.radius
bounce = True
if rect.bottom - self.position[1] < self.radius:
# Bounce bottom edge
self.bearing *= -1
self.position[1] = rect.bottom - self.radius
bounce = True
if self.position[0] - rect.left < self.radius:
# Bounce left edge
self.bearing = self.bearing * -1 + pi
self.position[0] = rect.left + self.radius
bounce = True
if rect.right - self.position[0] < self.radius:
# Bounce right edge
self.bearing = self.bearing * -1 + pi
self.position[0] = rect.right - self.radius
bounce = True
self.condition_bearing()
return bounce
def collide_rect_external(self, rect, invert=False):
"""
rect is a pygame Rect.
Use this method when we are bouncing against the outside of a rectangle
if invert is true, we mirror the bouncing for extra hilarity.
Returns true if a bounce occurred, else False
"""
bounce = False
# If we end up inside the rectangle this collision detection breaks
# down horribly.
if (self.position[0] > rect.left and self.position[0] < rect.right
and self.position[1] > rect.top
and self.position[1] < rect.bottom):
raise RuntimeError("Cannot resolve external collision with a ball "
"inside a rectangle.")
# Collision between a ball and the outside of a rectangle is
# surprisingly complicated. In the simple 2d simulation the incident
# angle equals the reflected angle, both normal to the reflecting
# plane. This breaks down on a corner, because the angle of the
# reflecting plane is undefined. So instead we just simulate the
# centre point of the ball reflecting off a rectangle enlarged by the
# radius of the circle.
# A more interesting simulation would be to give the enlarged rectangle
# radiused corners, with the same radius as the ball. Then you get
# interesting reflection angles if the ball reflects off the corner of
# the rectangle. It's unclear how canonical Breakout handles this.
# Check if we should collide against the top or bottom edges
if (self.position[0] > rect.left - self.radius
and self.position[0] < rect.right + self.radius):
if (rect.top - self.position[1] < self.radius
and rect.top - self.position[1] > 0):
# Bounce top edge
if invert:
self.bearing += pi
else:
self.bearing *= -1
self.position[1] = rect.top - self.radius
bounce = True
elif (self.position[1] - rect.bottom < self.radius
and self.position[1] - rect.bottom > 0):
# Bounce bottom edge
if invert:
self.bearing += pi
else:
self.bearing *= -1
self.position[1] = rect.bottom + self.radius
bounce = True
# Check if we should collide against the left or right edges
if (self.position[1] > rect.top - self.radius
and self.position[1] < rect.bottom):
if (rect.left - self.position[0] < self.radius
and rect.left - self.position[0] > 0):
# Bounce left edge
if invert:
self.bearing += pi
else:
self.bearing = self.bearing * -1 + pi
self.position[0] = rect.left - self.radius
bounce = True
elif (self.position[0] - rect.right < self.radius
and self.position[0] - rect.left > 0):
# Bounce right edge
if invert:
self.bearing += pi
else:
self.bearing = self.bearing * -1 + pi
self.position[0] = rect.right + self.radius
bounce = True
self.condition_bearing()
return bounce