You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

修改说明

  1. 碰撞盒同步修复

    • 新增world_to_screen和screen_to_world方法,实现世界坐标与屏幕坐标的双向转换,自动适配相机偏移和缩放比例。
    • 在绘制阶段为每个行星计算屏幕坐标下的碰撞矩形screen_rect,点击检测时直接使用该矩形判断鼠标位置,确保滚动/缩放后碰撞区域准确。
  2. 缩放窗口收缩问题修复

    • 移除了固定大小的中间绘制表面,直接在主屏幕上绘制缩放后的行星图像,保持窗口始终全屏显示。
    • 行星图像随缩放比例实时调整大小,缩放时能显示更多区域的行星,符合预期需求。
  3. 其他优化

    • 改用pygame.math.Vector2处理坐标,简化向量运算逻辑。
    • 加载行星图标时过滤图像格式,避免误加载非图像文件。
    • 代码结构更清晰,变量命名更规范,逻辑可读性提升。

内容的提问来源于stack exchange,提问作者Ian McCurdy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 14:55:21