-
Notifications
You must be signed in to change notification settings - Fork 1
/
platform_object.py
165 lines (145 loc) · 5.91 KB
/
platform_object.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
import math
import pygame.draw
from constants import *
from particle import Land
class PlatformObject:
def __init__(self, frame, x, y, w=TILE_WIDTH, h=TILE_WIDTH, r=None, solid=True):
self.frame = frame
self.x = x
self.y = y
self.w = w
self.h = h
self.r = r if r else w/2
self.solid = solid
self.vx = 0
self.vy = 0
self.vx_des = 0
self.ballistic = True
def update(self, dt, events):
""" Update physics """
if self.ballistic:
self.ballistic_update(dt)
else:
self.grounded_update(dt)
if self.solid:
self.collision_update()
def draw(self, surface, offset):
""" Render placeholder graphics """
return
if self.ballistic:
pygame.draw.circle(surface, (255, 0, 0), (self.x + offset[0], self.y + offset[1]), self.r, 2)
else:
pygame.draw.rect(surface, (255, 0, 0), self.get_rect(offset), 2)
def ballistic_update(self, dt):
""" Update velocity and position via continuous physics """
self.x += self.vx * dt
self.y += self.vy * dt
self.vx += (-DRAG * self.vx) * dt
self.vy += (-DRAG * self.vy + GRAVITY) * dt
def grounded_update(self, dt):
""" Update velocity and position via continuous physics """
self.x += self.vx * dt
self.y += self.vy * dt
if abs(self.vx_des - self.vx) < V_MIN_SLIDE:
self.vx = self.vx_des
self.vx += FRICTION * (self.vx_des - self.vx) * dt
def collision_update(self):
""" Collide with any intersecting tiles and determine if object is grounded """
tiles = self.frame.grid.get_nearby_tile_rects((self.x, self.y), *self.get_tile_range())
ballistic = True
for tile in tiles:
# Check for tile collision
d = self.collide(tile)
if d:
# Correct penetration
self.x -= d[0]
self.y -= d[1]
# Reflect velocity along contact normal
norm = math.sqrt(d[0] ** 2 + d[1] ** 2)
if norm:
dv = d[0]/norm * self.vx + d[1]/norm * self.vy
if dv >= 0:
self.vx -= d[0] / norm * dv
self.vy -= d[1] / norm * dv
if self.ballistic:
self.vx *= TANGENTIAL_RESTITUTION
self.vy *= TANGENTIAL_RESTITUTION
self.vx -= d[0] / norm * dv * HORIZONTAL_RESTITUTION
self.vy -= d[1] / norm * dv * VERTICAL_RESTITUTION
# Check if grounded
#if self.ballistic and d[1] > 0 and (self.vy ** 2 + self.vx ** 2) < V_MIN_BOUNCE ** 2:
if self.ballistic and d[1] > 0 and (self.vy ** 2) < V_MIN_BOUNCE ** 2:
# Collided from above, with resulting velocity below a threshold
ballistic = False
self.vx = 0
self.vy = 0
if not self.ballistic and self.y + self.h/2 <= tile.top:
# Still in contact with a tile directly below
ballistic = False
# Custom behavior depending on tile type
tile_type = self.frame.grid.get_tile_at(tile.center)
self.on_collision(tile_type=tile_type, tile_rect=tile)
self.set_ballistic(ballistic)
def set_ballistic(self, new_val):
if new_val == self.ballistic:
return
self.ballistic = new_val
if new_val == True:
self.on_become_ballistic()
else:
self.on_become_grounded()
def on_become_ballistic(self):
pass
def on_become_grounded(self):
particle = (Land((self.x, self.y + self.r)))
self.frame.particles.append(particle)
particle.scale = self.r/26
pass
def on_collision(self, tile_type, tile_rect):
""" Override for any custom tile collision behavior """
pass
def get_tile_range(self):
""" Minimum x and y distance of tiles to check for possible collisions """
return (self.r, self.r) if self.ballistic else (self.w/2, self.h/2)
def get_rect(self, offset=(0, 0)):
""" Pygame rect for bounding box """
dx, dy = self.get_tile_range()
return pygame.Rect(self.x - dx + offset[0], self.y - dy + offset[1], 2 * dx, 2 * dy)
def collide(self, rect):
""" Automatically choose the correct collision function """
if self.ballistic:
return self.collide_circle(rect)
else:
return self.collide_box(rect)
def collide_circle(self, rect):
""" Return penetration vector from self to rect if collision occurs, else None """
px = max(rect.left, min(rect.right, self.x))
py = max(rect.top, min(rect.bottom, self.y))
d = math.sqrt((px - self.x) ** 2 + (py - self.y) ** 2)
if d <= self.r:
if d == 0:
return 0, self.r
scale = (d - self.r) / d
return (self.x - px) * scale, (self.y - py) * scale
else:
return None
def collide_box(self, rect):
""" Return penetration vector from self to rect if collision occurs, else None """
left, right = self.x - self.w/2, self.x + self.w/2
top, bottom = self.y - self.h/2, self.y + self.h/2
dx1 = right - rect.left
dx2 = rect.right - left
dy1 = bottom - rect.top
dy2 = rect.bottom - top
if min(min(dx1, dx2), min(dy1, dy2)) < 0:
return None
dx = dx1 if dx1 < dx2 else -dx2
dy = dy1 if dy1 < dy2 else -dy2
if min(dx1, dx2) < min(dy1, dy2):
return dx, 0
else:
return 0, dy
def dist(self, pos):
dx = pos.x - self.x
dy = pos.y - self.y
return math.sqrt(dx ** 2 + dy ** 2)