First, the CameraGroup class is attributed to the wonderful youtube channel Clear Code.
Second, the hitboxes will not zoom (mousewheel) or scroll (WASD keys) with the planet icons. Try clicking the planets before zooming or scrolling and you will see the hitbox in action.
Third, as the screen is zoomed out the planets get smaller and the camera window shrinks towards the middle of the screen. Ideally, the camera window should stay the full size of the parent window to allow displaying more planets in the zoomed out state.
Finally, I am eager to learn, so please be brutal. Just drop the two planet icons in the folder with the .py file.
import os
import pygame
from pygame import *
from sys import exit
class CameraGroup(pygame.sprite.Group):
def __init__(self):
super().__init__()
self.display_surf_foreground = pygame.display.get_surface()
# camera offset
self.offset = pygame.math.Vector2()
self.half_w = self.display_surf_foreground.get_size()[0] // 2
self.half_h = self.display_surf_foreground.get_size()[1] // 2
self.scroll_offset = (0, 0)
# box setup
self.camera_borders = {'left': 200, 'right': 200, 'top': 100, 'bottom': 100}
l = self.camera_borders['left']
t = self.camera_borders['top']
w = self.display_surf_foreground.get_size()[0] - (self.camera_borders['left'] + self.camera_borders['right'])
h = self.display_surf_foreground.get_size()[1] - (self.camera_borders['top'] + self.camera_borders['bottom'])
self.camera_rect = pygame.Rect(l, t, w, h)
# camera speed
self.keyboard_speed = 10
# zoom
self.zoom_scale = 1
self.foreground_surf_size = (400, 400)
self.foreground_surf = pygame.Surface(self.foreground_surf_size, pygame.SRCALPHA)
self.foreground_rect = self.foreground_surf.get_rect(center=(self.half_w, self.half_h))
self.foreground_surf_size_vector = pygame.math.Vector2(self.foreground_surf_size)
self.foreground_offset = pygame.math.Vector2()
self.foreground_offset.x = self.foreground_surf_size[0] // 2 - self.half_w
self.foreground_offset.y = self.foreground_surf_size[1] // 2 - self.half_h
# planets and labels
self.planet_surf = pygame.Surface
self.planet_rect = pygame.Rect
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_keyboard_control(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_q]:
self.zoom_scale += 0.1
if self.zoom_scale > 2:
self.zoom_scale = 2
if keys[pygame.K_e]:
self.zoom_scale -= 0.1
if self.zoom_scale < .5:
self.zoom_scale = .5
def custom_draw(self, planets):
self.keyboard_control()
self.zoom_keyboard_control()
self.foreground_surf.fill((0, 0, 0, 255))
# active elements
for planet in planets:
self.planet_surf = pygame.image.load(planet.icon).convert_alpha()
offset_coord = planet.coord - self.offset + self.foreground_offset
self.planet_rect = self.planet_surf.get_rect(center=offset_coord)
self.foreground_surf.blit(self.planet_surf, self.planet_rect)
scaled_surf = pygame.transform.scale(self.foreground_surf, self.foreground_surf_size_vector * self.zoom_scale)
scaled_rect = scaled_surf.get_rect(center=(self.half_w, self.half_h))
self.display_surf_foreground.blit(scaled_surf, scaled_rect)
class Game:
def __init__(self):
self.game_over = False
self.game_year = 0
self.game_state = 'splash'
@staticmethod
def activate_planet(screen, planets):
active_planet_coord = None
for planet in planets:
if planet.rect.collidepoint(pygame.mouse.get_pos()):
active_planet_coord = planet.rect.center
return active_planet_coord
return active_planet_coord
@staticmethod
def heartbeat(screen, active_planet_coord):
# global heartbeat_mod
global heartbeat
ticks = pygame.time.get_ticks()
heartbeat_thump = round(ticks / 1000) % 2
if heartbeat_thump == 0:
heartbeat_mod = .1
else:
heartbeat_mod = -.1
heartbeat += heartbeat_mod
if heartbeat < 1:
heartbeat = 1
elif heartbeat > 6:
heartbeat = 6
heartbeat_color = (0, 255, 0)
pygame.draw.circle(screen, heartbeat_color, active_planet_coord, 25 + round(heartbeat), round(heartbeat) + 2)
class Planet(pygame.sprite.Sprite):
def __init__(self, icon, group):
super().__init__(group)
self.icon = icon
self.coord = (0, 0)
self.group = group
self.image = pygame.image.load(self.icon).convert_alpha()
self.rect = self.image.get_rect(center=self.coord)
self.planet_icons = []
self.planet_names = []
@staticmethod
def update_coords(planets, star_coords):
i = 0
for planet in planets:
planet.coord = tuple(star_coords[i])
planet.rect = planet.image.get_rect(center=planet.coord)
i += 1
if i == len(star_coords):
del planets[len(star_coords):]
return planets
def main():
global heartbeat
global heartbeat_mod
pygame.init()
width, height = 400, 400
screen = pygame.display.set_mode((width, height))
pygame.display.init()
pygame.display.update()
active_planet_coord = (-100, -100)
heartbeat = 0
heartbeat_mod = .1
clock = pygame.time.Clock()
game = Game()
camera_group = CameraGroup()
planet_array = os.listdir('./')
planet_array.pop(0) # remove 'camera scroll example.py' file
planet_icons = planet_array
planets = []
for new_planet in range(2):
icon = planet_icons.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:
pygame.quit()
exit()
elif 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(screen, camera_group)
if active_planet_coord is None:
active_planet_coord = (-100, -100)
pygame.display.update()
elif event.type == pygame.MOUSEWHEEL:
camera_group.zoom_scale += event.y * 0.1
if camera_group.zoom_scale > 2:
camera_group.zoom_scale = 2
elif camera_group.zoom_scale < .5:
camera_group.zoom_scale = .5
camera_group.update()
camera_group.custom_draw(planets)
game.heartbeat(screen, active_planet_coord)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
An alternative way to solve this problem is to "inverse zoom" the mouse position when you use it for click detection:
zoom:
p_zoom = (p - zoom_center) * zoom_scale + zoom_center
inverse zoom:
p = (p_zoom - zoom_center) / zoom_scale + zoom_center
Apply this to your code:
class Game:
# [...]
@staticmethod
def activate_planet(screen, planets):
zoom_scale = planets.zoom_scale
zoom_center = pygame.math.Vector2(screen.get_rect().center)
pos = pygame.math.Vector2(pygame.mouse.get_pos())
pos = (pos - zoom_center) / zoom_scale + zoom_center
active_planet_coord = None
for planet in planets:
if planet.rect.collidepoint(pos):
planet_center = (planet.rect.center - zoom_center) * zoom_scale + zoom_center
active_planet_coord = round(planet_center.x), round(planet_center.y)
return active_planet_coord
return active_planet_coord