I've tried to add a zoom feature to my Top-Down Game in python, but I can't manage to figure out why the screen is tearing. (Problem in Game-Class)
import sys
import pygame as py # Pygames
from pytmx.util_pygame import load_pygame as load_py
py.init() # Pygame initialisieren
py.display.set_caption("Save The Babe") # Titel des Fensters
#########################################################################
# Beginn der Klassendefinitionen
#########################################################################
TILESIZE = 16 # Grösse der Objekte (Pixel)
UI_font = "res/images/UI/StB.ttf"
UI_font_size = 18
# Waffen einlesen
weapon_type = {
"sword": {"cooldown": 100, "damage": 10, "graphic": "res/images/Player/Weapons/Sword/Sprite.png"},
"axe": {"cooldown": 150, "damage": 18, "graphic": "res/images/Player/Weapons/Axe/Sprite.png"},
"hammer": {"cooldown": 300, "damage": 30, "graphic": "res/images/Player/Weapons/Hammer/Sprite.png"},
"staff": {"cooldown": 10000, "damage": 30, "graphic": "res/images/Player/Weapons/Staff/Sprite.png"}
}
# Magie einlesen
magic_type = {
"x_magic": {"damage": 100, "stam": 10, "graphic": "res/images/Player/Weapons/Sword/Sprite.png"},
"y_magic": {"damage": 150, "stam": 18, "graphic": "res/images/Player/Weapons/Axe/Sprite.png"},
}
##########################################
# Die Klasse des Spiels, welche alle Objekte beinhalten sollte
##########################################
class Game:
def __init__(self):
self.display = None
self.win_size = (1024, 768) # Fenstergrösse
self.screen = py.display.set_mode(self.win_size) # Fenstergrösse setzen
self.my_font = py.font.SysFont('Comic Sans MS', 36)
# pytmx-setup
# layers
self.tmx_data = load_py("data/tsx/MapFinal.tmx")
self.clock = py.time.Clock()
self.game_clock = py.time.get_ticks()
self.FPS = 60
**self.zoom_level = 1.0
self.zoom_amount = 0.1
self.camera = py.Rect(0, 0, self.win_size[0], self.win_size[1])
**
# Gruppen und Objekte je nach Eigenschaft
self.visible_sprites = py.sprite.Group() # Sichtbare Spritegruppe
self.collisions_sprites = py.sprite.Group() # Interagierbare Sprites
self.camera = py.math.Vector2() # Vektor für Kamera
self.current_attack = None
self.UI = UserInterface() # UI aufrufen
def create_map(self):
for layer in self.tmx_data.layers:
if hasattr(layer, "data"):
for x, y, surf in layer.tiles():
pos = (x * 16, y * 16)
Object(pos=pos, groups=[self.visible_sprites], surf=surf)
self.player = Player((1136, 854), [self.visible_sprites], self.create_weapon, self.destroy_weapon,
self.create_magic)
Player.collisions_sprites = self.collisions_sprites
# Methode zur Waffenerstellung
def create_weapon(self):
self.current_attack = Weapon(self.player, [self.visible_sprites])
# Methode zur Waffenzerstörung
def destroy_weapon(self):
if self.current_attack:
self.current_attack.kill()
self.current_attack = None
def create_magic(self, magic_kind, damage, stam):
print(magic_kind, damage, stam)
** def handle_zoom(self, direction):
if direction == "in":
self.zoom_level += self.zoom_amount
elif direction == "out":
self.zoom_level -= self.zoom_amount
self.zoom_level = max(0.1, min(2.0, self.zoom_level))
def update_sprite_positions(self):
for sprite in self.visible_sprites:
# Calculate new position with zoom (using floating-point values)
new_x = (sprite.rect.x + self.camera.x) * self.zoom_level
new_y = (sprite.rect.y + self.camera.y) * self.zoom_level
# Update sprite position (convert to integers for rendering)
sprite.rect.topleft = int(new_x), int(new_y)
**
def run(self):
self.display = py.display.get_surface()
# Game-Loop
while True:
for event in py.event.get():
if event.type == py.QUIT:
py.quit()
sys.exit()
elif event.type == py.KEYDOWN:
if event.key == py.K_UP:
self.handle_zoom("in")
elif event.key == py.K_DOWN:
self.handle_zoom("out")
**self.camera.x = -(self.player.rect.centerx * self.zoom_level - self.win_size[0] / 2)
self.camera.y = -(self.player.rect.centery * self.zoom_level - self.win_size[1] / 2)
print(f"Camera position: ({self.camera.x}, {self.camera.y}), Zoom level: {self.zoom_level}")**
# Update sprite positions
self.update_sprite_positions()
# Game Update
self.visible_sprites.update()
self.visible_sprites.draw(self.display)
self.UI.display(self.player)
py.display.update()
self.clock.tick(self.FPS)
##########################################
# Die Klasse des Spielers
##########################################
class Player(py.sprite.Sprite): # Wie sieht der Player aus?
def __init__(self, pos, groups, create_weapon, destroy_weapon, create_magic): # Hier ist der Bauplan des Players
super().__init__(groups) # Spieler zu beiden Gruppen adden
self.display_surf = py.display.get_surface()
# Dictionaries für alle Animationen
self.idle_frames = {
"left": py.transform.scale(py.image.load("res/images/Player/Idle/Player_Left.png"), (TILESIZE, TILESIZE)),
"right": py.transform.scale(py.image.load("res/images/Player/Idle/Player_Right.png"), (TILESIZE, TILESIZE)),
"up": py.transform.scale(py.image.load("res/images/Player/Idle/Player_Up.png"), (TILESIZE, TILESIZE)),
"down": py.transform.scale(py.image.load("res/images/Player/Idle/Player_Down.png"), (TILESIZE, TILESIZE)),
}
self.attack_frames = {
"left": py.transform.scale(py.image.load("res/images/Player/Attack/Attack_Left.png"), (TILESIZE, TILESIZE)),
"right": py.transform.scale(py.image.load("res/images/Player/Attack/Attack_Right.png"),
(TILESIZE, TILESIZE)),
"up": py.transform.scale(py.image.load("res/images/Player/Attack/Attack_Up.png"), (TILESIZE, TILESIZE)),
"down": py.transform.scale(py.image.load("res/images/Player/Attack/Attack_Down.png"), (TILESIZE, TILESIZE)),
}
# Chat-GPT[
self.walking_frames = {
"left": [py.transform.scale(py.image.load(f"res/images/Player/Walk/Walk_Left/Walk_Left{i}.png"),
(TILESIZE, TILESIZE)) for i in range(4)],
"right": [py.transform.scale(py.image.load(f"res/images/Player/Walk/Walk_Right/Walk_Right{i}.png"),
(TILESIZE, TILESIZE)) for i in range(4)],
"up": [py.transform.scale(py.image.load(f"res/images/Player/Walk/Walk_Up/Walk_Up{i}.png"),
(TILESIZE, TILESIZE)) for i in range(4)],
"down": [py.transform.scale(py.image.load(f"res/images/Player/Walk/Walk_Down/Walk_Down{i}.png"),
(TILESIZE, TILESIZE)) for i in range(4)],
}
# ]
# Richtungen Definieren
self.idle_direction = "down"
self.walk_direction = "down"
self.attack_direction = "down"
self.current_frame = 0
# Auslesen
self.image = self.idle_frames[self.idle_direction]
self.rect = self.image.get_rect(topleft=pos)
# Waffen-Setup
self.attacking = False
self.attack_cooldown = 700
self.attack_time = None
self.create_weapon = create_weapon
self.destroy_weapon = destroy_weapon
self.weapon_index = 0
self.weapon = list(weapon_type.keys())[self.weapon_index] # Chat-GPT # Key für Waffen extrahieren + Liste
# indexieren
# Magie-Setup
self.create_magic = create_magic
self.magic_index = 0
self.magic = list(magic_type.keys())[self.magic_index]
# Deltas der Positionsänderungen
self.x_change = 0
self.y_change = 0
# Player Stats definieren
self.stats = {"health": 30, "stamina": 75, "magic": 30, "speed": 5, "max_health": 100, "max_magic": 40}
self.health = self.stats["health"]
self.stamina = self.stats["stamina"]
self.speed = self.stats["speed"]
def update_animation(self):
# Attacken animation image zuschreiben
if self.attacking:
self.image = self.attack_frames[self.attack_direction]
# Beim Stehen bleiben image zuschreiben
elif self.x_change == 0 and self.y_change == 0:
self.image = self.idle_frames[self.idle_direction]
else: # Chat-GPT [
self.current_frame = (self.current_frame + 1) % (len(self.walking_frames[self.idle_direction]) * 5)
# Increase the multiplier (5) to slow down more
self.image = self.walking_frames[self.idle_direction][self.current_frame // 5] # Divide by the multiplier
# ]
def get_keyboard_input(self):
key = py.key.get_pressed()
# Button-Inputs für die Bewegung überprüfen
if not self.attacking:
if key[py.K_a]:
self.x_change -= self.speed
self.attack_direction = "left"
self.idle_direction = "left"
if key[py.K_w]:
self.y_change -= self.speed
self.attack_direction = "up"
self.idle_direction = "up"
if key[py.K_d]:
self.x_change += self.speed
self.attack_direction = "right"
self.idle_direction = "right"
if key[py.K_s]:
self.y_change += self.speed
self.attack_direction = "down"
self.idle_direction = "down"
# Bei Angriff
if key[py.K_l]:
self.attacking = True
self.attack_time = py.time.get_ticks()
self.create_weapon()
if key[py.K_k]:
self.attacking = True
self.attack_time = py.time.get_ticks()
magic_kind = list(magic_type.keys())[self.magic_index]
damage = list(magic_type.values())[self.magic_index]["damage"] + self.stats["magic"]
stam = list(magic_type.values())[self.magic_index]["stam"]
self.create_magic(magic_kind, damage, stam)
# Magie-Indexierung setzen
if key[py.K_5]:
self.magic_index = 0
elif key[py.K_6]:
self.magic_index = 1
self.magic_index %= len(magic_type)
magic_kind = list(magic_type.keys())[self.magic_index]
# Waffen-Indexierung setzen
if key[py.K_1]:
self.weapon_index = 0
elif key[py.K_2]:
self.weapon_index = 1
elif key[py.K_3]:
self.weapon_index = 2
elif key[py.K_4]:
self.weapon_index = 3
self.weapon_index %= len(weapon_type)
self.weapon = list(weapon_type.keys())[self.weapon_index]
def check_collisions(self, direction):
if direction == "x":
collision = py.sprite.spritecollide(self, Player.collisions_sprites, False)
for collided_object in collision:
if self.x_change > 0: # Moving right
self.rect.right = collided_object.rect.left
elif self.x_change < 0: # Moving left
self.rect.left = collided_object.rect.right
elif direction == "y":
collision = py.sprite.spritecollide(self, Player.collisions_sprites, False)
for collided_object in collision:
if self.y_change > 0: # Moving down
self.rect.bottom = collided_object.rect.top
elif self.y_change < 0: # Moving up
self.rect.top = collided_object.rect.bottom
# Waffen cooldown setzen
def cooldown(self):
current_time = py.time.get_ticks()
if self.attacking:
if current_time - self.attack_time >= self.attack_cooldown:
self.attacking = False
self.destroy_weapon()
def update(self):
self.get_keyboard_input()
self.cooldown()
self.rect.x += self.x_change
self.check_collisions("x")
self.rect.y += self.y_change
self.check_collisions("y")
self.update_animation()
self.x_change = 0
self.y_change = 0
# Object erstellen
class Object(py.sprite.Sprite):
def __init__(self, pos, groups, surf):
super().__init__(groups)
self.image = surf
self.rect = self.image.get_rect(topleft=pos)
game = Game()
game.create_map()
game.run()
Adjusting Camera Position: I attempted to adjust the camera position based on the zoom level to ensure that the player remains centered on the screen even after zooming in or out.
Updating Sprite Positions: I worked on updating the positions of sprites to accommodate the zoom level and camera position changes, aiming to ensure that they are rendered correctly on the screen at the right scale and position.
Handling Zoom Direction: I implemented logic to handle zooming in and out based on user input, ensuring that the zoom level remains within reasonable bounds to prevent any extreme scaling issues.
Refining Sprite Position Calculation: I refined the calculation of sprite positions after zooming to prevent them from being chopped or cut off on the screen, making adjustments to the positioning algorithm as necessary.