def test_clip(self): r1 = Rect(1, 2, 3, 4) self.assertEqual(Rect(1, 2, 2, 2), r1.clip(Rect(0, 0, 3, 4))) self.assertEqual(Rect(2, 2, 2, 4), r1.clip(Rect(2, 2, 10, 20))) self.assertEqual(Rect(2, 3, 1, 2), r1.clip(Rect(2, 3, 1, 2))) self.assertEqual((0, 0), r1.clip(20, 30, 5, 6).size) self.assertEqual(r1, r1.clip(Rect(r1)), "r1 does not clip an identical rect to itself")
def is_collided(self): # Check if the bird touch ground if self.bird_height + self.bird_y + 1 >= self.base_y: self.distance_reward = 0 return True bird_bbox = Rect(self.bird_x, self.bird_y, self.bird_width, self.bird_height) pipe_boxes = [] for pipe in self.pipes: pipe_boxes.append( Rect(pipe["x_upper"], pipe["y_upper"], self.pipe_width, self.pipe_height)) pipe_boxes.append( Rect(pipe["x_lower"], pipe["y_lower"], self.pipe_width, self.pipe_height)) # Check if the bird's bounding box overlaps to the bounding box of any pipe if bird_bbox.collidelist(pipe_boxes) == -1: return False for i in range(2): cropped_bbox = bird_bbox.clip(pipe_boxes[i]) min_x1 = cropped_bbox.x - bird_bbox.x min_y1 = cropped_bbox.y - bird_bbox.y min_x2 = cropped_bbox.x - pipe_boxes[i].x min_y2 = cropped_bbox.y - pipe_boxes[i].y if np.any(self.bird_hitmask[ self.bird_index][min_x1:min_x1 + cropped_bbox.width, min_y1:min_y1 + cropped_bbox.height] * self.pipe_hitmask[i][min_x2:min_x2 + cropped_bbox.width, min_y2:min_y2 + cropped_bbox.height]): self.distance_reward = 0.9 / (self.diff_distance() + 1) return True return False
def _update (self, col, row, tile_type_id, tile_rect=None): if self._cache_graphic: if tile_type_id in self._cache: g = self._cache[tile_type_id] else: g = self._type_to_graphic(tile_type_id) self._cache[tile_type_id] = g else: g = self._type_to_graphic(tile_type_id) dest = self._orig_sfc if tile_rect is None: tile_rect = self.grid.tile_rect(col, row) if isinstance(g, (Graphic, pg.Surface, basestring)): g = (g,) if (g is not None and isinstance(g[0], (Graphic, pg.Surface, basestring))): sfc = g[0] if isinstance(sfc, basestring): sfc = self._load_img(sfc) elif isinstance(sfc, Graphic): sfc = sfc.surface if len(g) == 1: alignment = rect = None else: if isinstance(g[1], int) or len(g[1]) == 2: alignment = g[1] rect = None else: alignment = None rect = g[1] if len(g) == 3: if rect is None: rect = g[2] else: alignment = g[2] if alignment is None: alignment = 0 if rect is None: rect = sfc.get_rect() # clip rect to fit in tile_rect dest_rect = Rect(rect) dest_rect.center = tile_rect.center fit = dest_rect.clip(tile_rect) rect = Rect(rect) rect.move_ip(fit.x - dest_rect.x, fit.y - dest_rect.y) rect.size = dest_rect.size # copy rect to tile_rect with alignment pos = gameutil.align_rect(rect, tile_rect, alignment) dest.blit(sfc, pos, rect) else: if g is None: g = (0, 0, 0, 0) # now we have a colour dest.fill(gameutil.normalise_colour(g), tile_rect) return tile_rect
def push_ip(larger_rect: Rect, smaller_rect: Rect): '''Larger rect pushes out smaller rect via the smallest possible vector.''' clip = larger_rect.clip(smaller_rect) if not clip: return if clip.height <= clip.width: if smaller_rect.centery <= clip.centery: smaller_rect.bottom = larger_rect.top else: smaller_rect.top = larger_rect.bottom else: if smaller_rect.centerx <= clip.centerx: smaller_rect.right = larger_rect.left else: smaller_rect.left = larger_rect.right
def is_passable(self, rect: pg.Rect) -> bool: # TODO: rewrite the algorithm or use JIT/AOT # compilation to speed it up in case optimization # is required clipped = rect.clip(self.image.get_rect()) if rect != clipped: return False self.image.lock() try: for y in range(rect.top, rect.bottom): for x in range(rect.left, rect.right): red, *_ = self.image.get_at((x, y)) if red == 0: return False finally: self.image.unlock() return True
class BoxCollider(Behaviour): def __init__(self): super().__init__() self.name = "BoxCollider" self.is_trigger = False self.center = Vector2(0, 0) self.offset = Vector2(0, 0) self.extent = Vector2(0, 0) self.box = Rect(0, 0, 0, 0) self.is_debug = False def update(self): super().update() t = self.game_object.get_behaviour("Transform") self.center = Vector2(t.position) self.center.x += self.offset.x self.center.y += self.offset.y self.box.center = self.center self.box.width = int(self.extent.x) self.box.height = int(self.extent.y) def render(self): super().render() if self.is_debug: surf = pygame.display.get_surface() pygame.draw.rect(surf, (0, 255, 0), self.box, 1) #collider-specific methods #overlaps() is designed for AABB only def overlaps(self, other): if isinstance(other, BoxCollider): return self.box.colliderect(other.box) #WIP #prevent_overlap forces the current box away from the other def prevent_overlap(self, other): if (isintance(other, BoxCollider) and self.box.colliderect(other.box)): r = self.box.clip(other.box) t = self.game_object.get_behaviour("Transform")
def main(): pygame.init() screen = pygame.display.set_mode((1024, 768)) pygame.display.set_caption("Rect Test") #Create a Rectangle #Obs.: will be mouse-controlled #optional: create a tuple for the rectangle's colour player_rect = Rect(0, 0, 100, 100) player_colour = (0, 255, 0) #Create a list of rectangles rect_list = [Rect(550, 100, 200, 200), Rect(150, 150, 350, 350), Rect(800, 600, 150, 150)] #optional: create a list of colours (should match rect #list length) colours = [(255, 255, 0), (255, 0, 255), ( 0, 255, 255)] #Create an extra rectangle for overlap detection result = Rect(0, 0, 0, 0) is_running = True #Game loop while is_running: for evt in pygame.event.get(): if evt.type == pygame.QUIT: is_running = False #Controlling the player rectangle with the mouse #(uncomment code below) player_rect.center = pygame.mouse.get_pos() #clip result rectangle: #result = player_rect.clip(rect_list[0]) #advanced: clip against a list result = player_rect.clip(rect_list[player_rect.collidelist(rect_list)]) #Render part screen.fill((0, 0, 127)) #Render rectangles: #1: Render player rectangle pygame.draw.rect(screen, player_colour, player_rect) #2: Render list of rectangles #optional: use a for loop to render them with the #same or different colours. If using a for loop, the #example code below should be adjusted accordingly for i in range(len(rect_list)): pygame.draw.rect(screen, colours[i], rect_list[i]) #If there is a collision, render it here #Alternative if statements: #if player_rect.collidelist(rect_list) > -1: #if result.width > 0 and result.height > 0: if result.width > 0 < result.height: pygame.draw.rect(screen, (255, 0, 0), result, 5) #Render loop end pygame.display.flip() #cleanup pygame.quit() sys.exit()
class BaseAnimation: """ Base class for animations, this should perhaps be changed to use sprites in the future (if one decides to go with a RenderGroup model) @cvar background: Surface Background (screen) @cvar surface : The Surface obj. to work with @cvar active : Should it be updated in the poll @cvar delete : Delete from list on next poll @cvar updates : list of updates from screen @cvar next_updat: timestamp for next update """ background = None # Surface Background (screen) surface = None # The Surface obj. to work with active = False # Should it be updated in the poll delete = False # Delete from list on next poll updates = [] # list of updates from screen next_update = 0 # timestamp for next update def __init__(self, rectstyle, fps=20, bg_update=True, bg_wait=False, bg_redraw=False): """ Initialise an instance of BaseAnimation @ivar rectstyle : the rectangle defining the position on the screen (pygame) @ivar fps : Desired fps @ivar bg_update : update the animation with background from screen @ivar bg_wait : initially wait for updated background before activating @ivar bg_redraw : set background to original screen bg when finished """ logger.log( 9, '__init__(rectstyle=%r, fps=%r, bg_update=%r, bg_wait=%r, bg_redraw=%r)', rectstyle, fps, bg_update, bg_wait, bg_redraw) self.rect = Rect(rectstyle) self.bg_update = bg_update self.bg_wait = bg_wait self.bg_redraw = bg_redraw self.surface = Surface((self.rect.width, self.rect.height)).convert() self.set_fps(fps) def get_surface(self, width, height): """ Helper for creating surfaces """ logger.log( 9, 'get_surface(width=%r, height=%r)', width, height) return Surface( (width, height), 0, 32) def get_osd(self): """ Helper for getting osd singleton """ logger.log( 9, 'get_osd()') return osd.get_singleton() def set_fps(self, fps): """ Sets the desired fps """ logger.log( 9, 'set_fps(fps=%r)', fps) self.interval = int(1000.0/float(fps)) def set_screen_background(self): """ Update the background """ logger.log( 9, 'set_screen_background()') if not self.background: self.background = osd.get_singleton().getsurface(rect=self.rect) self.updates = [] elif len(self.updates) > 0: # find the topleft corner x = self.rect.right y = self.rect.bottom for i in self.updates: x = min(x, i.left) y = min(y, i.top) # find the total rect of the collisions upd = Rect(x, y, 0, 0) upd.unionall_ip(self.updates) self.updates = [] x = upd[0] - self.rect.left y = upd[1] - self.rect.top bg_tmp = osd.get_singleton().getsurface(rect=upd) self.background.blit(bg_tmp, (x, y)) self.surface.blit(self.background, (0,0)) def get_rect(self): """ Get the rectangle of the current object @returns: the rectangle tuple """ logger.log( 9, 'get_rect()') return self.rect def start(self): """ Starts the animation """ logger.log( 9, 'start()') render.get_singleton().add_animation(self) if not self.bg_wait: self.active = True def stop(self): """ Stops the animation from being polled """ logger.log( 9, 'stop()') self.active = False def remove(self): """ Flags the animation to be removed from the animation list """ logger.log( 9, 'remove()') self.active = False # set the org. bg if we use this if self.bg_update: osd.get_singleton().putsurface(self.background, self.rect.left, self.rect.top) osd.get_singleton().update([self.rect]) self.delete = True def damage(self, rectstyles=[]): """ Checks if the screen background has been damaged @note: If the rect passed damages our rect, but no actual blit is done on osd.screen, we'll end up with a copy of our animation in our bg. This is BAD. """ logger.log( 9, 'damage(rectstyles=%r)', rectstyles) if not (self.bg_redraw or self.bg_update) or rectstyles == None: return for rect in rectstyles: if rect == None: continue if self.rect.colliderect(rect): if self.bg_wait: self.active = True self.updates.append(self.rect.clip(rect)) logger.debug('Damaged, updating background') def poll(self, current_time): """ Poll the animations """ logger.log( 9, 'poll(current_time=%r)', current_time) if self.next_update < current_time: self.next_update = current_time + self.interval if self.bg_update: self.set_screen_background() self.draw() return self.rect, self.surface def draw(self): """ Overload to do stuff with the surface """ logger.log( 9, 'draw()') pass
class BoxCollider(Behaviour): def __init__(self): super().__init__() self.name = "BoxCollider" self.is_trigger = False self.center = Vector2(0, 0) self.offset = Vector2(0, 0) self.extent = Vector2(0, 0) self.box = Rect(0, 0, 0, 0) self.is_debug = False def start(self): super().start() t = self.game_object.get_behaviour("Transform") self.center = Vector2(t.position) self.center.x += self.offset.x self.center.y += self.offset.y self.box.center = self.center self.box.width = int(self.extent.x) self.box.height = int(self.extent.y) def update(self): super().update() #Code repeated to account for runtime collider changes t = self.game_object.get_behaviour("Transform") self.center = Vector2(t.position) self.center.x += self.offset.x self.center.y += self.offset.y self.box.center = self.center self.box.width = int(self.extent.x) self.box.height = int(self.extent.y) def render(self): super().render() if self.is_debug: surf = pygame.display.get_surface() pygame.draw.rect(surf, (0, 255, 0), self.box, 1) #collider-specific methods #overlaps() is designed for AABB only def overlaps(self, other): if isinstance(other, BoxCollider): return self.box.colliderect(other.box) #WIP #prevent_overlap forces the current box away from the other def prevent_overlap(self, other): if (isinstance(other, BoxCollider) and self.box.colliderect(other.box)): o = other.game_object.get_behaviour("Transform") r = self.box.clip(other.box) t = self.game_object.get_behaviour("Transform") ################################################### #Solution: Check for the smallest axis overlap and# #displace the invoking object's collider box back # #along it. # #Obs.: This prevents overlap, but still causes a # #small penetration depth due to the ship's speed. # #More elaborate calculations will probably be # #needed, but the current approach should suffice # #for the intended objective. # ################################################### if (r.width < r.height): #if the other is to the right, push back to the left if (o.position.x >= t.position.x): t.position.x -= (r.width + 1) else: t.position.x += (r.width + 1) else: #if the other is below, push it up if (o.position.x >= t.position.y): t.position.y -= (r.height + 1) else: t.position.y += (r.height + 1)
class BoxCollider(Collider): def __init__(self, debug=False, size_=(0, 0)): super().__init__("BoxCollider", debug) self.center = None self.rotation = None self.size = size_ self.rect = Rect((0, 0), self.size) def start(self): super().start() def update(self, delta): super().update(delta) self.rect.center = self.center def render(self): super().render() if self._is_debug and self.size and self.center: import pygame.draw from engine.game_env import Game pygame.draw.rect(Game.instance.get_screen(), (0, 255, 0), self.rect, 1) def collidepoint(self, other): super().collidepoint(other) return self.rect.collidepoint(other.center) def colliderect(self, other): super().colliderect(other) return self.rect.colliderect(other.rect) def get_clip_area(self, other): return self.rect.clip(other.rect) def collideline(self, other): super().collideline(other) #REDO!!! This only works in pygame 2.0 return False def collidecircle(self, other): super().collidecircle(other) #BUGGY!!! VERIFY test_x = other.center[0] test_y = other.center[1] if other.center[0] < self.rect.left: test_x = self.rect.left elif other.center[0] > self.rect.right: test_x = self.rect.right if other.center[1] < self.rect.top: test_y = self.rect.top elif other.center[1] > self.rect.bottom: test_y = self.rect.bottom dist_x = other.center[0] - test_x dist_y = other.center[1] - test_y dist = ((dist_x * dist_x) + (dist_y * dist_y))**0.5 return dist <= other.radius
def draw(self, surface, bgd=None): """ Draws all sprites on the surface you pass in. You can pass the background too. If a background is already set, then the bgd argument has no effect. """ # speedups _orig_clip = surface.get_clip() _clip = self._clip if _clip is None: _clip = _orig_clip _surf = surface _sprites = self._spritelist _old_rect = self.spritedict _update = self.lostsprites _update_append = _update.append _ret = None _surf_blit = _surf.blit _rect = pygame.Rect if bgd is not None: self._bgd = bgd _bgd = self._bgd _surf.set_clip(_clip) # ------- # 0. deside if normal render of flip start_time = get_ticks() if self._use_update: # dirty rects mode # 1. find dirty area on screen and put the rects into _update # still not happy with that part for spr in _sprites: if 0 < spr.dirty: if spr.source_rect is not None: _union_rect = Rect(spr.rect.topleft, spr.source_rect.size) else: _union_rect = _rect(spr.rect) _union_rect_collidelist = _union_rect.collidelist _union_rect_union_ip = _union_rect.union_ip i = _union_rect_collidelist(_update) while -1 < i: _union_rect_union_ip(_update[i]) del _update[i] i = _union_rect_collidelist(_update) _update_append(_union_rect.clip(_clip)) _union_rect = _rect(_old_rect[spr]) _union_rect_collidelist = _union_rect.collidelist _union_rect_union_ip = _union_rect.union_ip i = _union_rect_collidelist(_update) while -1 < i: _union_rect_union_ip(_update[i]) del _update[i] i = _union_rect_collidelist(_update) _update_append(_union_rect.clip(_clip)) # can it be done better? because that is an O(n**2) algorithm in # worst case # clear using background if _bgd is not None: for rec in _update: _surf_blit(_bgd, rec, rec) # 2. draw for spr in _sprites: if 1 > spr.dirty: if spr._visible: # sprite not dirty, blit only the intersecting part if spr.source_rect is not None: _spr_rect = Rect(spr.rect.topleft, spr.source_rect.size) else: _spr_rect = spr.rect _spr_rect_clip = _spr_rect.clip for idx in _spr_rect.collidelistall(_update): # clip clip = _spr_rect_clip(_update[idx]) _surf_blit(spr.image, clip, \ (clip[0]-_spr_rect[0], \ clip[1]-_spr_rect[1], \ clip[2], \ clip[3]))#, spr.blendmode) else: # dirty sprite if spr._visible: if spr.source_rect is not None: _old_rect[spr] = _surf_blit(spr.image, spr.rect, \ spr.source_rect)#, spr.blendmode) else: _old_rect[spr] = _surf_blit(spr.image, spr.rect) if spr.dirty == 1: spr.dirty = 0 _ret = list(_update) else: # flip, full screen mode if _bgd is not None: _surf_blit(_bgd, (0, 0)) for spr in _sprites: if spr.visible: if spr.source_rect is not None: _old_rect[spr] = _surf_blit( spr.image, spr.rect, spr.source_rect) #,spr.blendmode) else: _old_rect[spr] = _surf_blit( spr.image, spr.rect) #, spr.source_rect)#,spr.blendmode) _ret = [_rect(_clip)] # return only the part of the screen changed # timing for switching modes # how to find a good treshold? it depends on the hardware it runs on end_time = get_ticks() if end_time - start_time > self._time_threshold: self._use_update = False else: self._use_update = True ## # debug ## print " check: using dirty rects:", self._use_update # emtpy dirty reas list _update[:] = [] # ------- # restore original clip _surf.set_clip(_orig_clip) return _ret