I am trying to create a basic "top-down" game using Pygame and Python. There are a lot of copy/paste codes out there but what I wanted to create was a version that used "left mouse clicks" to move the player rather than the arrow keys or WASD.
import pygame
import sys
import os
# Initialize Pygame
pygame.init()
# Constants
DESIRED_WIDTH, DESIRED_HEIGHT = 800, 600
PLAYER_SIZE = 50
ANIMATION_SPEED = 5
# Colors
RED = (255, 0, 0)
# Create the screen with the desired resolution
screen = pygame.display.set_mode((DESIRED_WIDTH, DESIRED_HEIGHT))
pygame.display.set_caption("Top-Down Mouse Click Movement")
# Create a back buffer surface
back_buffer = pygame.Surface((DESIRED_WIDTH, DESIRED_HEIGHT))
# Clock to control the frame rate
clock = pygame.time.Clock()
# Player properties
player_pos = pygame.Vector2(DESIRED_WIDTH // 2, DESIRED_HEIGHT // 2)
# Load background image
background_image_path = os.path.join("graphics", "ground.png")
background_image = pygame.image.load(background_image_path).convert()
# Variable to store the target position for background movement
background_target_pos = pygame.Vector2(DESIRED_WIDTH // 2, DESIRED_HEIGHT // 2)
# Game loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# Set the target position for background movement to the clicked position
background_target_pos = pygame.Vector2(pygame.mouse.get_pos())
# Calculate the direction from current background position to target position
background_direction = background_target_pos - player_pos
background_distance_to_target = background_direction.length()
if background_distance_to_target > 0:
# Normalize the direction vector
background_direction.normalize_ip()
# Calculate the movement vector
background_movement_vector = background_direction * min(background_distance_to_target, ANIMATION_SPEED)
new_player_pos = player_pos + background_movement_vector
# Update the player position
player_pos = new_player_pos
# Clear the back buffer
back_buffer.fill((0, 0, 0))
# Calculate the position for the camera to keep the player visibly centered
camera_x = max(0, min(player_pos.x - DESIRED_WIDTH // 2, background_image.get_width() - DESIRED_WIDTH))
camera_y = max(0, min(player_pos.y - DESIRED_HEIGHT // 2, background_image.get_height() - DESIRED_HEIGHT))
# Draw background image centered around the player on the back buffer
back_buffer.blit(background_image, (0 - camera_x, 0 - camera_y))
# Draw the player (red rectangle) at the center of the screen
pygame.draw.rect(back_buffer, RED, (DESIRED_WIDTH // 2 - PLAYER_SIZE // 2, DESIRED_HEIGHT // 2 - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE))
# Copy the back buffer to the screen
screen.blit(back_buffer, (0, 0))
# Update the display
pygame.display.flip()
# Cap the frame rate
clock.tick(60)
From what I understand, in order to keep the player centered, when the user clicks a position we don't actually move the player to that position but instead we would move the background from it's current position to the center of the screen?
For reference I am trying to recreate a game like Synthetic Reality Warpath.
The player always remains in the center of the screen, but only as long as the player does not reach the edge of the world.
The main problem in your code is that the mouse position is not considered relative to the camera. Calculate the mouse position relative to the camera to calculate the motion vector:
camera_pos = player_pos - pygame.Vector2(DESIRED_WIDTH // 2, DESIRED_HEIGHT // 2)
while run:
# [...]
for event in pygame.event.get():
# [...]
elif event.type == pygame.MOUSEBUTTONDOWN:
background_target_pos = pygame.Vector2(pygame.mouse.get_pos()) + camera_pos
The second problem is that you have to draw everything relative to the camera, including the player:
back_buffer.fill((0, 0, 0))
back_buffer.blit(background_image, (-camera_pos.x, -camera_pos.y))
rel_player_pos = player_pos - camera_pos
pygame.draw.rect(back_buffer, "red", (rel_player_pos.x - PLAYER_SIZE // 2, rel_player_pos.y - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE))
You should also limit the position of the player so that no part of the player extends beyond the edge of the scene:
player_pos.x = max(PLAYER_SIZE // 2, min(player_pos.x, background_image.get_width() - PLAYER_SIZE // 2))
player_pos.y = max(PLAYER_SIZE // 2, min(player_pos.y, background_image.get_height() - PLAYER_SIZE // 2))
Complete example:
import pygame
import sys
pygame.init()
DESIRED_WIDTH, DESIRED_HEIGHT = 400, 300
PLAYER_SIZE = 50
ANIMATION_SPEED = 5
screen = pygame.display.set_mode((DESIRED_WIDTH, DESIRED_HEIGHT))
pygame.display.set_caption("Top-Down Mouse Click Movement")
back_buffer = pygame.Surface((DESIRED_WIDTH, DESIRED_HEIGHT))
clock = pygame.time.Clock()
background_image = pygame.Surface((DESIRED_WIDTH * 2, DESIRED_HEIGHT * 2))
ts, w, h, c1, c2 = 50, *background_image.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background_image, color, rect) for rect, color in tiles]
pygame.draw.rect(background_image, "red", background_image.get_rect(), 3)
player_pos = pygame.Vector2(DESIRED_WIDTH // 2, DESIRED_HEIGHT // 2)
background_target_pos = player_pos.copy()
camera_pos = player_pos - pygame.Vector2(DESIRED_WIDTH // 2, DESIRED_HEIGHT // 2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEBUTTONDOWN:
background_target_pos = pygame.Vector2(pygame.mouse.get_pos()) + camera_pos
background_direction = background_target_pos - player_pos
background_distance_to_target = background_direction.length()
if background_distance_to_target > 0:
background_direction.normalize_ip()
background_movement_vector = background_direction * min(background_distance_to_target, ANIMATION_SPEED)
player_pos = player_pos + background_movement_vector
player_pos.x = max(PLAYER_SIZE // 2, min(player_pos.x, background_image.get_width() - PLAYER_SIZE // 2))
player_pos.y = max(PLAYER_SIZE // 2, min(player_pos.y, background_image.get_height() - PLAYER_SIZE // 2))
camera_pos.x = max(0, min(player_pos.x - DESIRED_WIDTH // 2, background_image.get_width() - DESIRED_WIDTH))
camera_pos.y = max(0, min(player_pos.y - DESIRED_HEIGHT // 2, background_image.get_height() - DESIRED_HEIGHT))
back_buffer.fill((0, 0, 0))
back_buffer.blit(background_image, (-camera_pos.x, -camera_pos.y))
rel_player_pos = player_pos - camera_pos
pygame.draw.rect(back_buffer, "red", (rel_player_pos.x - PLAYER_SIZE // 2, rel_player_pos.y - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE))
screen.blit(back_buffer, (0, 0))
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()