Pygame精灵碰撞盒无法随屏幕滚动/缩放同步问题求助
问题描述
- CameraGroup类参考自YouTube频道Clear Code的实现。
- 行星图标的碰撞盒无法与屏幕滚动(WASD键)或缩放(鼠标滚轮)同步,仅在未滚动/缩放前点击行星时,碰撞盒能正常工作。
- 屏幕缩小时行星会变小,同时相机窗口会向屏幕中间收缩,期望保持父窗口全屏,以此显示更多行星。
- 只需将两个行星图标放在
.py文件同目录即可运行代码。
解决方案代码
import os import pygame from sys import exit class CameraGroup(pygame.sprite.Group): def __init__(self): super().__init__() self.display_surf = pygame.display.get_surface() self.screen_size = pygame.math.Vector2(self.display_surf.get_size()) self.half_screen = self.screen_size / 2 # 相机偏移 self.offset = pygame.math.Vector2() # 相机边界 self.camera_borders = {'left': 200, 'right': 200, 'top': 100, 'bottom': 100} l = self.camera_borders['left'] t = self.camera_borders['top'] w = self.screen_size.x - (self.camera_borders['left'] + self.camera_borders['right']) h = self.screen_size.y - (self.camera_borders['top'] + self.camera_borders['bottom']) self.camera_rect = pygame.Rect(l, t, w, h) # 相机移动速度 self.keyboard_speed = 10 # 缩放参数 self.zoom_scale = 1 def keyboard_control(self): keys = pygame.key.get_pressed() if keys[pygame.K_a]: self.camera_rect.x -= self.keyboard_speed if keys[pygame.K_d]: self.camera_rect.x += self.keyboard_speed if keys[pygame.K_w]: self.camera_rect.y -= self.keyboard_speed if keys[pygame.K_s]: self.camera_rect.y += self.keyboard_speed self.offset.x = self.camera_rect.left - self.camera_borders['left'] self.offset.y = self.camera_rect.top - self.camera_borders['top'] def zoom_control(self): keys = pygame.key.get_pressed() if keys[pygame.K_q]: self.zoom_scale += 0.1 self.zoom_scale = min(self.zoom_scale, 2) if keys[pygame.K_e]: self.zoom_scale -= 0.1 self.zoom_scale = max(self.zoom_scale, 0.5) def world_to_screen(self, world_pos): """将世界坐标转换为屏幕坐标""" offset_pos = world_pos - self.offset scaled_pos = offset_pos * self.zoom_scale screen_pos = scaled_pos + self.half_screen - (self.half_screen * self.zoom_scale) return screen_pos def screen_to_world(self, screen_pos): """将屏幕坐标转换为世界坐标""" scaled_pos = screen_pos - self.half_screen + (self.half_screen * self.zoom_scale) world_pos = scaled_pos / self.zoom_scale + self.offset return world_pos def custom_draw(self, planets): self.keyboard_control() self.zoom_control() self.display_surf.fill((0, 0, 0)) for planet in planets: # 转换世界坐标到屏幕坐标 screen_center = self.world_to_screen(pygame.math.Vector2(planet.coord)) # 缩放行星图像 scaled_image = pygame.transform.scale(planet.image, (planet.image.get_width() * self.zoom_scale, planet.image.get_height() * self.zoom_scale)) # 获取缩放后的屏幕矩形 scaled_rect = scaled_image.get_rect(center=screen_center) planet.screen_rect = scaled_rect # 绘制行星 self.display_surf.blit(scaled_image, scaled_rect) class Game: def __init__(self): self.game_over = False self.game_year = 0 self.game_state = 'splash' @staticmethod def activate_planet(camera_group): active_planet_coord = None mouse_pos = pygame.math.Vector2(pygame.mouse.get_pos()) for planet in camera_group: if planet.screen_rect.collidepoint(mouse_pos): active_planet_coord = camera_group.screen_to_world(mouse_pos) return active_planet_coord return active_planet_coord @staticmethod def heartbeat(screen, camera_group, active_planet_coord): global heartbeat, heartbeat_mod ticks = pygame.time.get_ticks() heartbeat_thump = round(ticks / 1000) % 2 if heartbeat_thump == 0: heartbeat_mod = 0.1 else: heartbeat_mod = -0.1 heartbeat += heartbeat_mod heartbeat = max(1, min(heartbeat, 6)) if active_planet_coord is not None: screen_pos = camera_group.world_to_screen(active_planet_coord) heartbeat_color = (0, 255, 0) pygame.draw.circle(screen, heartbeat_color, screen_pos, 25 + round(heartbeat), round(heartbeat) + 2) class Planet(pygame.sprite.Sprite): def __init__(self, icon, group): super().__init__(group) self.icon = icon self.coord = pygame.math.Vector2(0, 0) self.image = pygame.image.load(self.icon).convert_alpha() # 世界坐标矩形 self.rect = self.image.get_rect(center=self.coord) # 屏幕坐标矩形(用于碰撞检测) self.screen_rect = self.rect.copy() @staticmethod def update_coords(planets, star_coords): for idx, planet in enumerate(planets[:len(star_coords)]): planet.coord = pygame.math.Vector2(star_coords[idx]) planet.rect = planet.image.get_rect(center=planet.coord) del planets[len(star_coords):] return planets def main(): global heartbeat, heartbeat_mod pygame.init() width, height = 400, 400 screen = pygame.display.set_mode((width, height)) pygame.display.set_caption("Planet Camera Demo") active_planet_coord = None heartbeat = 1 heartbeat_mod = 0.1 clock = pygame.time.Clock() game = Game() camera_group = CameraGroup() # 过滤加载图像文件 planet_array = [f for f in os.listdir('./') if f.endswith(('.png', '.jpg', '.jpeg'))] planets = [] for _ in range(min(2, len(planet_array))): icon = planet_array.pop() planet = Planet(icon, camera_group) planets.append(planet) # 设置行星初始位置 star_coords = [[100, 100], [200, 200]] planets = Planet.update_coords(planets, star_coords) while True: for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): pygame.quit() exit() elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: active_planet_coord = game.activate_planet(camera_group) elif event.type == pygame.MOUSEWHEEL: camera_group.zoom_scale += event.y * 0.1 camera_group.zoom_scale = max(0.5, min(camera_group.zoom_scale, 2)) camera_group.update() camera_group.custom_draw(planets) game.heartbeat(screen, camera_group, active_planet_coord) pygame.display.update() clock.tick(60) if __name__ == '__main__': main()
修改说明
碰撞盒同步修复
- 新增
world_to_screen和screen_to_world方法,实现世界坐标与屏幕坐标的双向转换,自动适配相机偏移和缩放比例。 - 在绘制阶段为每个行星计算屏幕坐标下的碰撞矩形
screen_rect,点击检测时直接使用该矩形判断鼠标位置,确保滚动/缩放后碰撞区域准确。
- 新增
缩放窗口收缩问题修复
- 移除了固定大小的中间绘制表面,直接在主屏幕上绘制缩放后的行星图像,保持窗口始终全屏显示。
- 行星图像随缩放比例实时调整大小,缩放时能显示更多区域的行星,符合预期需求。
其他优化
- 改用
pygame.math.Vector2处理坐标,简化向量运算逻辑。 - 加载行星图标时过滤图像格式,避免误加载非图像文件。
- 代码结构更清晰,变量命名更规范,逻辑可读性提升。
- 改用
内容的提问来源于stack exchange,提问作者Ian McCurdy
相关产品推荐
相关产品推荐

