# from __future__ import annotations import math import pygame from pgzero.actor import Actor, POS_TOPLEFT, ANCHOR_CENTER, transform_anchor from pgzero import game, loaders import sys import time from typing import Sequence, Tuple, Union from pygame import Vector2 _Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] _fullscreen = False def set_fullscreen(): global _fullscreen mod = sys.modules['__main__'] mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT), pygame.FULLSCREEN) _fullscreen = True def set_windowed(): global _fullscreen mod = sys.modules['__main__'] mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT)) _fullscreen = False def toggle_fullscreen(): if _fullscreen: set_windowed() else: set_fullscreen() def hide_mouse(): pygame.mouse.set_visible(False) def show_mouse(): pygame.mouse.set_visible(True) def distance_to(from_x, from_y, to_x, to_y): dx = to_x - from_x dy = to_y - from_y return math.sqrt(dx**2 + dy**2) def distance_to_squared(from_x, from_y, to_x, to_y): dx = to_x - from_x dy = to_y - from_y return dx**2 + dy**2 def direction_to(from_x, from_y, to_x, to_y): dx = to_x - from_x dy = from_y - to_y angle = math.degrees(math.atan2(dy, dx)) if angle > 0: return angle return 360 + angle def get_move(direction, distance): angle = math.radians(direction) dx = distance * math.cos(angle) dy = -distance * math.sin(angle) return (dx, dy) def move(x, y, direction, distance): dx, dy = get_move(direction, distance) return (x + dx, y + dy) class Collide(): @staticmethod def line_line(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): l1x2_l1x1 = l1x2-l1x1 l1y2_l1y1 = l1y2-l1y1 determinant = (l2y2-l2y1)*l1x2_l1x1 - (l2x2-l2x1)*l1y2_l1y1 # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: return False uA = ((l2x2-l2x1)*(l1y1-l2y1) - (l2y2-l2y1)*(l1x1-l2x1)) / determinant if uA < 0 or uA > 1: return False uB = (l1x2_l1x1*(l1y1-l2y1) - l1y2_l1y1*(l1x1-l2x1)) / determinant if uB < 0 or uB > 1: return False return True @staticmethod def line_lines(l1x1, l1y1, l1x2, l1y2, l2): l1x2_l1x1 = l1x2-l1x1 l1y2_l1y1 = l1y2-l1y1 i = 0 for l in l2: determinant = (l[3]-l[1])*l1x2_l1x1 - (l[2]-l[0])*l1y2_l1y1 # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: i += 1 continue uA = ((l[2]-l[0])*(l1y1-l[1]) - (l[3]-l[1])*(l1x1-l[0])) / determinant uB = (l1x2_l1x1*(l1y1-l[1]) - l1y2_l1y1*(l1x1-l[0])) / determinant if 0 <= uA <= 1 and 0 <= uB <= 1: return i i += 1 return -1 @staticmethod def line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): determinant = (l2y2-l2y1)*(l1x2-l1x1) - (l2x2-l2x1)*(l1y2-l1y1) # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: return (None, None) uA = ((l2x2-l2x1)*(l1y1-l2y1) - (l2y2-l2y1)*(l1x1-l2x1)) / determinant uB = ((l1x2-l1x1)*(l1y1-l2y1) - (l1y2-l1y1)*(l1x1-l2x1)) / determinant if 0 <= uA <= 1 and 0 <= uB <= 1: ix = l1x1 + uA * (l1x2 - l1x1) iy = l1y1 + uA * (l1y2 - l1y1) return (ix, iy) return (None, None) @staticmethod def line_line_dist(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) if ix is not None: return distance_to(l1x1, l1y1, ix, iy) return None @staticmethod def line_line_dist_squared(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) if ix is not None: return distance_to_squared(l1x1, l1y1, ix, iy) return None @staticmethod def line_circle(x1, y1, x2, y2, cx, cy, radius): r_sq = radius ** 2 dist_sq = (x1 - cx) ** 2 + (y1 - cy) ** 2 if dist_sq <= r_sq: return True dist_sq = (x2 - cx) ** 2 + (y2 - cy) ** 2 if dist_sq <= r_sq: return True dx = x2 - x1 dy = y2 - y1 l_sq = dx ** 2 + dy ** 2 dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq ix = x1 + dot * dx if (dx!=0) and (ix < x1) == (ix < x2): return False iy = y1 + dot * dy if (dy!=0) and (iy < y1) == (iy < y2): return False dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 if dist_sq <= r_sq: return True return False @staticmethod def line_circle_XY(x1, y1, x2, y2, cx, cy, radius): if Collide.circle_point(cx, cy, radius, x1, y1): return (x1, y1) x1 -= cx y1 -= cy x2 -= cx y2 -= cy if x2 < x1: x_min, x_max = x2, x1 else: x_min, x_max = x1, x2 if y2 < y1: y_min, y_max = y2, y1 else: y_min, y_max = y1, y2 # Coefficients of circle c_r2 = radius ** 2 # Simplify if dx == 0: Vertical line dx = x2 - x1 if dx == 0: d = c_r2 - x1**2 if d < 0: return (None, None) elif d == 0: i = 0 else: i = math.sqrt(d) iy = None if y_min <= i <= y_max: iy = i if y_min <= -i <= y_max: if iy is None or abs(i - y1) > abs(-i - y1): iy = -i if iy: return (x1 + cx, iy + cy) return (None, None) # Gradient of line l_m = (y2 - y1) / dx # Simplify if l_m == 0: Horizontal line if l_m == 0: d = c_r2 - y1**2 if d < 0: return (None, None) elif d == 0: i = 0 else: i = math.sqrt(d) ix = None if x_min <= i <= x_max: ix = i if x_min <= -i <= x_max: if ix is None or abs(i - x1) > abs(-i - x1): ix = -i if ix: return (ix + cx, y1 + cy) return (None, None) # y intercept l_c = y1 - l_m * x1 # Coefficients of quadratic a = 1 + l_m**2 b = 2 * l_c * l_m c = l_c**2 - c_r2 # Calculate discriminant and solve quadratic discriminant = b**2 - 4 * a * c if discriminant < 0: return (None, None) if discriminant == 0: d_root = 0 else: d_root = math.sqrt(discriminant) ix = None i1 = (-b + d_root) / (2 * a) if x_min <= i1 <= x_max: ix = i1 i2 = (-b - d_root) / (2 * a) if x_min <= i2 <= x_max: if ix is None or abs(i1 - x1) > abs(i2 - x1): ix = i2 if ix: return (ix + cx, l_m * ix + l_c + cy) return (None, None) @staticmethod def line_circle_dist(x1, y1, x2, y2, cx, cy, radius): ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) if ix is not None: return distance_to(x1, y1, ix, iy) return None @staticmethod def line_circle_dist_squared(x1, y1, x2, y2, cx, cy, radius): ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) if ix is not None: return distance_to_squared(x1, y1, ix, iy) return None @staticmethod def line_rect(x1, y1, x2, y2, rx, ry, w, h): if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: return True half_w = w / 2 half_h = h / 2 rect_lines = [ [rx - half_w, ry - half_h, rx - half_w, ry + half_h], [rx - half_w, ry - half_h, rx + half_w, ry - half_h], [rx + half_w, ry + half_h, rx - half_w, ry + half_h], [rx + half_w, ry + half_h, rx + half_w, ry - half_h], ] if Collide.line_lines(x1, y1, x2, y2, rect_lines) != -1: return True return False @staticmethod def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): if Collide.rect_point(rx, ry, w, h, x1, y1): return (x1, y1) half_w = w / 2 half_h = h / 2 rect_lines = [ [rx - half_w, ry - half_h, rx - half_w, ry + half_h], [rx - half_w, ry - half_h, rx + half_w, ry - half_h], [rx + half_w, ry + half_h, rx - half_w, ry + half_h], [rx + half_w, ry + half_h, rx + half_w, ry - half_h], ] XYs = [] for l in rect_lines: ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) if ix is not None: XYs.append((ix, iy)) length = len(XYs) if length == 0: return (None, None) elif length == 1: return XYs[0] ix, iy = XYs[0] shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 for XY in XYs: dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 if dist < shortest_dist: ix, iy = XY shortest_dist = dist return (ix, iy) @staticmethod def line_rect_dist(x1, y1, x2, y2, rx, ry, w, h): ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) if ix is not None: return distance_to(x1, y1, ix, iy) return None @staticmethod def line_rect_dist_squared(x1, y1, x2, y2, rx, ry, w, h): ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) if ix is not None: return distance_to_squared(x1, y1, ix, iy) return None @staticmethod def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) tx = x1 - ox ty = y1 - oy rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return (x1, y1) wc = half_width * costheta hs = half_height * sintheta hc = half_height * costheta ws = half_width * sintheta p = [ [ox + wc + hs, oy + hc - ws], [ox - wc + hs, oy + hc + ws], [ox + wc - hs, oy - hc - ws], [ox - wc - hs, oy - hc + ws], ] obb_lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[2][0], p[2][1]], [p[2][0], p[2][1], p[0][0], p[0][1]] ] XYs = [] for l in obb_lines: ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) if ix is not None: XYs.append((ix, iy)) length = len(XYs) if length == 0: return (None, None) elif length == 1: return XYs[0] ix, iy = XYs[0] shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 for XY in XYs: dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 if dist < shortest_dist: ix, iy = XY shortest_dist = dist return (ix, iy) @staticmethod def line_obb_dist(x1, y1, x2, y2, ox, oy, w, h, angle): ix, iy = Collide.line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle) if ix is not None: return distance_to(x1, y1, ix, iy) return None @staticmethod def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): ix, iy = Collide.obb_line_XY(x1, y1, x2, y2, ox, oy, w, h, angle) if ix is not None: return distance_to_squared(x1, y1, ix, iy) return None @staticmethod def circle_point(x1, y1, radius, x2, y2): rSquare = radius ** 2 dSquare = (x2 - x1)**2 + (y2 - y1)**2 if dSquare < rSquare: return True return False @staticmethod def circle_points(x, y, radius, points): rSquare = radius ** 2 i = 0 for point in points: try: px = point[0] py = point[1] except (KeyError, TypeError): px = point.x py = point.y dSquare = (px - x)**2 + (py - y)**2 if dSquare < rSquare: return i i += 1 return -1 @staticmethod def circle_line(cx, cy, radius, x1, y1, x2, y2): return Collide.line_circle(x1, y1, x2, y2, cx, cy, radius) @staticmethod def circle_circle(x1, y1, r1, x2, y2, r2): rSquare = (r1 + r2) ** 2 dSquare = (x2 - x1)**2 + (y2 - y1)**2 if dSquare < rSquare: return True return False @staticmethod def circle_rect(cx, cy, cr, rx, ry, rw, rh): h_w = rw / 2 h_h = rh / 2 rect_l = rx - h_w rect_t = ry - h_h if cx < rect_l: dx2 = (cx - rect_l) ** 2 elif cx > (rect_l + rw): dx2 = (cx - rect_l - rw) ** 2 else: dx2 = 0 if cy < rect_t: dy2 = (cy - rect_t) ** 2 elif cy > (rect_t + rh): dy2 = (cy - rect_t - rh) ** 2 else: dy2 = 0 dist2 = dx2 + dy2 if dist2 < (cr ** 2): return True return False @staticmethod def rect_point(x, y, w, h, px, py): half_w = w / 2 half_h = h / 2 if ( px < x - half_w or px > x + half_w or py < y - half_h or py > y + half_h ): return False return True @staticmethod def rect_points(x, y, w, h, points): half_w = w / 2 half_h = h / 2 min_x = x - half_w max_x = x + half_w min_y = y - half_h max_y = y + half_h i = 0 for point in points: try: px = point[0] py = point[1] except (KeyError, TypeError): px = point.x py = point.y if ( px >= min_x and px <= max_x and py >= min_y and py <= max_y ): return i i += 1 return -1 @staticmethod def rect_line(x, y, w, h, lx1, ly1, lx2, ly2): return Collide.line_rect(lx1, ly1, lx2, ly2, x, y, w, h) @staticmethod def rect_circle(rx, ry, rw, rh, cx, cy, cr): return Collide.circle_rect(cx, cy, cr, rx, ry, rw, rh) @staticmethod def rect_rect(x1, y1, w1, h1, x2, y2, w2, h2): h_w1 = w1 / 2 h_h1 = h1 / 2 h_w2 = w2 / 2 h_h2 = h2 / 2 if ( x2 - h_w2 > x1 + h_w1 or x2 + h_w2 < x1 - h_w1 or y2 - h_h2 > y1 + h_h1 or y2 + h_h2 < y1 - h_h1 ): return False return True @staticmethod def obb_point(x, y, w, h, angle, px, py): half_width = w / 2 half_height = h / 2 b_radius_sq = half_width ** 2 + half_height ** 2 tx = px - x ty = py - y if tx ** 2 + ty ** 2 > b_radius_sq: return False r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return True return False @staticmethod def obb_points(x, y, w, h, angle, points): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) i = 0 for point in points: try: px = point[0] py = point[1] except (KeyError, TypeError): px = point.x py = point.y tx = px - x ty = py - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return i i += 1 return -1 @staticmethod def obb_line(x, y, w, h, angle, lx1, ly1, lx2, ly2): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) tx = lx1 - x ty = ly1 - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return True tx = lx2 - x ty = ly2 - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return True wc = half_width * costheta hs = half_height * sintheta hc = half_height * costheta ws = half_width * sintheta p = [ [x + wc + hs, y + hc - ws], [x - wc + hs, y + hc + ws], [x + wc - hs, y - hc - ws], [x - wc - hs, y - hc + ws], ] obb_lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[2][0], p[2][1]], [p[2][0], p[2][1], p[0][0], p[0][1]] ] if Collide.line_lines(lx1, ly1, lx2, ly2, obb_lines) != -1: return True return False @staticmethod def obb_lines(x, y, w, h, angle, lines): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) wc = half_width * costheta hs = half_height * sintheta hc = half_height * costheta ws = half_width * sintheta p = [ [x + wc + hs, y + hc - ws], [x - wc + hs, y + hc + ws], [x + wc - hs, y - hc - ws], [x - wc - hs, y - hc + ws], ] obb_lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[2][0], p[2][1]], [p[2][0], p[2][1], p[0][0], p[0][1]] ] i = 0 for l in lines: tx = l[0] - x ty = l[1] - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return i tx = l[2] - x ty = l[3] - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return i if Collide.line_lines(l[0], l[1], l[2], l[3], obb_lines) != -1: return i i += 1 return -1 @staticmethod def obb_circle(x, y, w, h, angle, cx, cy, radius): half_width = w / 2 half_height = h / 2 tx = cx - x ty = cy - y if tx ** 2 + ty ** 2 > (half_height + half_width + radius) ** 2: return False r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if (rx < -half_width - radius or rx > half_width + radius or ry < -half_height - radius or ry > half_height + radius ): return False if (rx <= half_width and rx >= -half_width) or (ry <= half_height and ry >= -half_height): return True dx = abs(rx) - half_width dy = abs(ry) - half_height dist_squared = dx ** 2 + dy ** 2 if dist_squared > radius ** 2: return False return True @staticmethod def obb_circles(x, y, w, h, angle, circles): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) i = 0 for circle in circles: tx = circle[0] - x ty = circle[1] - y rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta if (rx < -half_width - circle[2] or rx > half_width + circle[2] or ry < -half_height - circle[2] or ry > half_height + circle[2] ): i += 1 continue if (rx <= half_width and rx >= -half_width) or (ry <= half_height and ry >= -half_height): return i dx = abs(rx) - half_width dy = abs(ry) - half_height dist_squared = dx ** 2 + dy ** 2 if dist_squared > circle[2] ** 2: i += 1 continue return i return -1 @staticmethod def obb_rect(x, y, w, h, angle, rx, ry, rw, rh): half_width = w / 2 half_height = h / 2 tx = rx - x ty = ry - y if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: return False r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) tx2 = tx * costheta - ty * sintheta ty2 = ty * costheta + tx * sintheta if tx2 > -half_width and tx2 < half_width and ty2 > -half_height and ty2 < half_height: return True wc = half_width * costheta hs = half_height * sintheta hc = half_height * costheta ws = half_width * sintheta p = [ [wc + hs, hc - ws], [-wc + hs, hc + ws], [wc - hs, -hc - ws], [-wc - hs, -hc + ws], ] obb_lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[2][0], p[2][1]], [p[2][0], p[2][1], p[0][0], p[0][1]] ] h_rw = rw / 2 h_rh = rh / 2 rect_lines = [ [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] ] for obb_p in p: if obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh: return True for obb_line in obb_lines: l1x1 = obb_line[0] l1y1 = obb_line[1] l1x2 = obb_line[2] l1y2 = obb_line[3] l1x2_l1x1 = l1x2-l1x1 l1y2_l1y1 = l1y2-l1y1 for rect_line in rect_lines: l2x1 = rect_line[0] l2y1 = rect_line[1] l2x2 = rect_line[2] l2y2 = rect_line[3] determinant = (l2y2-l2y1)*l1x2_l1x1 - (l2x2-l2x1)*l1y2_l1y1 # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: continue uA = ((l2x2-l2x1)*(l1y1-l2y1) - (l2y2-l2y1)*(l1x1-l2x1)) / determinant if uA < 0 or uA > 1: continue uB = (l1x2_l1x1*(l1y1-l2y1) - l1y2_l1y1*(l1x1-l2x1)) / determinant if uB < 0 or uB > 1: continue return True return False @staticmethod def obb_rects(x, y, w, h, angle, rects): half_width = w / 2 half_height = h / 2 r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) i = 0 for rect in rects: rx = rect[0] ry = rect[1] rw = rect[2] rh = rect[3] tx = rx - x ty = ry - y if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: i += 1 continue tx2 = tx * costheta - ty * sintheta ty2 = ty * costheta + tx * sintheta if tx2 > -half_width and tx2 < half_width and ty2 > -half_height and ty2 < half_height: return i wc = half_width * costheta hs = half_height * sintheta hc = half_height * costheta ws = half_width * sintheta p = [ [wc + hs, hc - ws], [-wc + hs, hc + ws], [wc - hs, -hc - ws], [-wc - hs, -hc + ws], ] obb_lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[2][0], p[2][1]], [p[2][0], p[2][1], p[0][0], p[0][1]] ] h_rw = rw / 2 h_rh = rh / 2 rect_lines = [ [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] ] for obb_p in p: if obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh: return i for obb_line in obb_lines: l1x1 = obb_line[0] l1y1 = obb_line[1] l1x2 = obb_line[2] l1y2 = obb_line[3] l1x2_l1x1 = l1x2-l1x1 l1y2_l1y1 = l1y2-l1y1 for rect_line in rect_lines: l2x1 = rect_line[0] l2y1 = rect_line[1] l2x2 = rect_line[2] l2y2 = rect_line[3] determinant = (l2y2-l2y1)*l1x2_l1x1 - (l2x2-l2x1)*l1y2_l1y1 # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: continue uA = ((l2x2-l2x1)*(l1y1-l2y1) - (l2y2-l2y1)*(l1x1-l2x1)) / determinant if uA < 0 or uA > 1: continue uB = (l1x2_l1x1*(l1y1-l2y1) - l1y2_l1y1*(l1x1-l2x1)) / determinant if uB < 0 or uB > 1: continue return i i += 1 return -1 def obb_obb(x, y, w, h, angle, x2, y2, w2, h2, angle2): r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) tx2 = x2 - x ty2 = y2 - y rx2 = tx2 * costheta - ty2 * sintheta ry2 = ty2 * costheta + tx2 * sintheta return Collide.obb_rect(rx2, ry2, w2, h2, angle2-angle, 0, 0, w, h) def obb_obbs(x, y, w, h, angle, obbs): r_angle = math.radians(angle) costheta = math.cos(r_angle) sintheta = math.sin(r_angle) for obb in obbs: x2, y2, w2, h2, angle2 = obb tx2 = x2 - x ty2 = y2 - y rx2 = tx2 * costheta - ty2 * sintheta ry2 = ty2 * costheta + tx2 * sintheta return Collide.obb_rect(rx2, ry2, w2, h2, angle2-angle, 0, 0, w, h) class Actor(Actor): def __init__(self, image:Union[str, pygame.Surface], pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs): self._flip_x = False self._flip_y = False self._scale = 1 self._mask = None self._images = None self._image_idx = 0 self._subrects = None self._transform_cnt = 0 self._orig_surfs = {} self._surfs = {} self._animate_counter = 0 self._animate_run = False self._radius = None self._collision_width = None self._collision_height = None self.fps = 5 self.direction = 0 subrect=kwargs.pop('subrect',None) image_str = None if isinstance(image,str): image_str = image super().__init__(image_str, pos, anchor, **kwargs) if isinstance(image,pygame.Surface): self._orig_surf = image self._update_pos() self._subrect=None if subrect is not None: self.subrect=subrect def distance_to(self, target): if isinstance(target, Actor): x, y = target.pos else: x, y = target return distance_to(self.x, self.y, x, y) def distance_toXY(self, x, y): return distance_to(self.x, self.y, x, y) def direction_to(self, target): if isinstance(target, Actor): x, y = target.pos else: x, y = target return direction_to(self.x, self.y, x, y) def direction_toXY(self, x, y): return direction_to(self.x, self.y, x, y) def move_towards(self, target:Union[int, float, Actor, _Coordinate], dist, stop_on_target=True): if isinstance(target, (int,float)): direction = target else: direction = self.direction_to(target) if stop_on_target: target_distance = self.distance_to(target) if (target_distance < dist) and dist>0: dist = target_distance self.x, self.y = move(self.x, self.y, direction, dist) def move_towardsXY(self, x, y, dist): direction = self.direction_toXY(x, y) self.x, self.y = move(self.x, self.y, direction, dist) def point_towards(self, actor, y=None): self.angle = self.direction_to(actor) def point_towardsXY(self, x, y): self.angle = direction_to(self.x, self.y, x, y) def move_in_direction(self, dist): self.x, self.y = move(self.x, self.y, self.direction, dist) def move_forward(self, dist): self.x, self.y = move(self.x, self.y, self.angle, dist) def move_left(self, dist): self.x, self.y = move(self.x, self.y, self.angle + 90, dist) def move_right(self, dist): self.x, self.y = move(self.x, self.y, self.angle - 90, dist) def move_back(self, dist): self.x, self.y = move(self.x, self.y, self.angle, -dist) @property def images(self): return self._images @images.setter def images(self, images): self._subrects = None self._images = images if len(self._images) != 0: self.image = self._images[0] def load_images(self, sheet_name:str, cols:int, rows:int, cnt:int=0, subrect:pygame.Rect=None): self._subrects=[None]*cols*rows self._image_idx=0 sheet:pygame.Surface = loaders.images.load(sheet_name) if subrect is not None: sheet = sheet.subsurface(subrect) for col in range(0,cols): for row in range(0,rows): width=sheet.get_width()/cols height=sheet.get_height()/rows self._subrects[col+row*cols]=(int(col*width),int(row*height),int(width),int(height)) if len(self._subrects) != 0: self.image = sheet_name self.subrect = self._subrects[0] def sel_image(self, newimage:Union[str, int])-> bool: try: if isinstance(newimage, int): if self._subrects is None and self._images is None: return False if self._subrects is not None: self.subrect = self._subrects[newimage] else: self.image = self._images[newimage] self._image_idx = newimage return True else: self._image_idx = self._images.index(newimage) self.image = newimage except: return False def next_image(self)-> int: if self._subrects is not None: next_image_idx = (self._image_idx+1) % len(self._subrects) self._image_idx = next_image_idx self.subrect = self._subrects[self._image_idx] elif (self._images is not None) : if (self.image in self._images): next_image_idx = (self._images.index(self.image)+1) % len(self._images) self._image_idx = next_image_idx self.image = self._images[self._image_idx] else: self._image_idx = 0 self.image = self._images[0] else: self._image_idx = 0 return self._image_idx def animate(self)-> int: now = int(time.time() * self.fps) if self._animate_counter == 0: self._animate_counter=now frames_elapsed = now-self._animate_counter if frames_elapsed!=0: self._animate_counter = now idx=self.next_image() return idx else: return -1 @property def angle(self): return self._angle @angle.setter def angle(self, angle): self._angle = angle self._transform_surf() self._transform_cnt+=1 @property def scale(self): return self._scale @scale.setter def scale(self, scale): self._scale = scale self._transform_surf() self._transform_cnt+=1 @property def flip_x(self): return self._flip_x @flip_x.setter def flip_x(self, flip_x): self._flip_x = flip_x self._transform_surf() self._transform_cnt+=1 @property def flip_y(self): return self._flip_y @flip_y.setter def flip_y(self, flip_y): self._flip_y = flip_y self._transform_surf() self._transform_cnt+=1 @property def image(self): return self._image_name @image.setter def image(self, image): if image is not None: self._orig_surf = self._surf = loaders.images.load(image) self._image_name = image self._orig_surfs[image]=self._orig_surf else: self._orig_surf = self._surf = pygame.Surface((1,1),pygame.SRCALPHA) self._image_name = '' self._update_pos() if image is not None: if (image not in self._surfs) or (self._surfs[image][1]!=self._transform_cnt): self._transform_surf() self._surfs[image]=(self._surf,self._transform_cnt) @property def subrect(self): return self._subrect @subrect.setter def subrect(self, subrect:pygame.Rect): subr = subrect if subrect is not None: subr=pygame.Rect(subrect) if subr != self._subrect: self._subrect = subr if self._subrect is not None: hashv=hash((subr.x, subr.y,subr.width,subr.height)) surf_name=self._image_name+str(hashv) if surf_name not in self._orig_surfs: self._orig_surfs[surf_name] = loaders.images.load(self.image).subsurface(subr) self._orig_surf=self._orig_surfs[surf_name] self._update_pos() if (surf_name not in self._surfs) or (self._surfs[surf_name][1]!=self._transform_cnt): self._transform_surf() self._surfs[surf_name]=(self._surf,self._transform_cnt) self._surf=self._surfs[surf_name][0] else: self._orig_surf = self._surf = loaders.images.load(self.image) self._update_pos() self._transform_surf() @property def orig_surf(self): return self._orig_surf @orig_surf.setter def orig_surf(self, surf:pygame.Surface): self._orig_surf = self._surf =surf self._update_pos() self._transform_surf() def recalc(self): self._surf = self._orig_surf self._update_pos() self._transform_surf() def _transform_surf(self): self._surf = self._orig_surf p = self.pos if self._scale != 1: size = self._orig_surf.get_size() self._surf = pygame.transform.scale(self._surf, (int(size[0] * self.scale), int(size[1] * self.scale))) if self._flip_x: self._surf = pygame.transform.flip(self._surf, True, False) if self._flip_y: self._surf = pygame.transform.flip(self._surf, False, True) self._surf = pygame.transform.rotate(self._surf, self._angle) self.width, self.height = self._surf.get_size() w, h = self._orig_surf.get_size() ax, ay = self._untransformed_anchor anchor = transform_anchor(ax, ay, w, h, self._angle) self._anchor = (anchor[0] * self.scale, anchor[1] * self.scale) self.pos = p self._mask = None def collidepoint_pixel(self, x, y=0): if isinstance(x, tuple): y = x[1] x = x[0] if self._mask == None: self._mask = pygame.mask.from_surface(self._surf) xoffset = int(x - self.left) yoffset = int(y - self.top) if xoffset < 0 or yoffset < 0: return 0 width, height = self._mask.get_size() if xoffset >= width or yoffset >= height: return 0 return self._mask.get_at((xoffset, yoffset)) def collide_pixel(self, actor): for a in [self, actor]: if a._mask == None: a._mask = pygame.mask.from_surface(a._surf) xoffset = int(actor.left - self.left) yoffset = int(actor.top - self.top) return self._mask.overlap(actor._mask, (xoffset, yoffset)) def collidelist_pixel(self, actors): for i in range(len(actors)): if self.collide_pixel(actors[i]): return i return -1 def collidelistall_pixel(self, actors): collided = [] for i in range(len(actors)): if self.collide_pixel(actors[i]): collided.append(i) return collided def _unrotated_size(self): w = self._orig_surf.get_width()*self.scale h = self._orig_surf.get_height()*self.scale return w, h @property def collision_width(self): if self._collision_width is None: w,_ = self._unrotated_size() return w return self._collision_width @collision_width.setter def collision_width(self, collision_width): self._collision_width = collision_width @property def collision_height(self): if self._collision_height is None: _,h = self._unrotated_size() return h return self._collision_height @collision_height.setter def collision_height(self, collision_height): self._collision_height = collision_height def obb_collidepoint(self, x, y): w,h = self._unrotated_size() return Collide.obb_point(self.centerx, self.centery, w, h, self._angle, x, y) def obb_collidepoints(self, points): w,h = self._unrotated_size() return Collide.obb_points(self.centerx, self.centery, w, h, self._angle, points) def obb_collideobb(self, actor): if self._collision_width is None and self._collision_height is None: x,y = self.centerx, self.centery else: x,y = self.x, self.y if actor._collision_width is None and actor._collision_height is None: x2,y2 = actor.centerx, actor.centery else: x2,y2 = actor.x, actor.y return Collide.obb_obb(x, y, self.collision_width, self.collision_height, self._angle, x2, y2, actor.collision_width, actor.collision_height, actor._angle) @property def radius(self): if self._radius is None: w,h = self._unrotated_size() self._radius = min(w, h) * .5 return self._radius @radius.setter def radius(self, radius): self._radius = radius def circle_collidepoints(self, points): return Collide.circle_points(self.centerx, self.centery, self._radius, points) def circle_collidepoint(self, x, y): return Collide.circle_point(self.centerx, self.centery, self._radius, x, y) def circle_collidecircle(self, actor): return Collide.circle_circle(self.centerx, self.centery, self._radius, actor.centerx, actor.centery, actor._radius) def circle_colliderect(self, actor): return Collide.circle_rect(self.centerx, self.centery, self._radius, actor.centerx, actor.centery, actor.width, actor.height) def circle_collideobb(self, actor): w2, h2 = actor._unrotated_size() return Collide.obb_circle(actor.centerx, actor.centery, w2, h2, actor.angle, self.centerx, self.centery, self._radius) def draw(self): game.screen.blit(self._surf, self.topleft) def get_rect(self): return self._rect