I need to rotate a 3D scene or a character using the mouse position: for example, if I move the mouse to the right, I want to turn to the right / the character should turn right.
I've seen lots of games place the mouse in the center of the screen, then get its position relatively to the previous frame, update the game accordingly and move the mouse back in the center.
I tried to replicate this but I ran into the issue that pygame does not rotate reliably: with higher framerates, I tend to get a slower rotation. This is not a problem when rotating a character in a 2D game, but this is concerning when a 3D scene rotates differently if there are more objects to draw.
I used this simple code to make sure the problem actually came from this:
import pygame
from pygame.locals import *
pygame.init()
WIDTH, HEIGHT = (640, 480)
screen = pygame.display.set_mode((WIDTH, HEIGHT))
font = pygame.font.SysFont('consolas', 20)
clock = pygame.time.Clock()
angle = 0
time_passed = 0
pygame.mouse.set_pos((WIDTH//2, HEIGHT//2))
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
# I did not use the MOUSEMOTION event because it used to glitch a bit
x, y = pygame.mouse.get_pos()
angle += (WIDTH//2 - x) * 360 / WIDTH # 360° every WIDTH pixels
pygame.mouse.set_pos((WIDTH//2, HEIGHT//2))
screen.fill(0)
text = font.render('Angle: %d°' %angle, True, (255, 255, 255))
w, h = text.get_size()
screen.blit(text, (WIDTH//2 - w//2, HEIGHT//2 - h//2))
pygame.display.flip()
time_passed = clock.tick(60)/1000
I think either the mouse or the OS tweak the travelled distance according to the mouse speed, so I did the following tests at similar and relatively constant speed:
Going say 5 centimeters with the framerate limited to 60 FPS did not result in the same final angle as with 120 FPS for example, even when moving the mouse at the same speed, with the same mouse with high DPI.
Do I need to implement a tick system for example, where I only update the position every few frames? I think the problem wouldn't be solved with slower framerates then, and maybe the movement would not be as responsive as I want.
Or perhaps there is an alternative to pygame.mouse.get_pos() / event.pos, getting the raw, more precise position from the mouse?
I would appreciate any help.
You get the position of the mouse with pygame.mouse.get_pos() and you reset the position with pygame.mouse.set_pos(). What if there was an event between pygame.mouse.get_pos() and pygame.mouse.set_pos()? Also pygame.mouse.get_pos() only tells you the current position of the mouse curosr, but it does not tell you how the mouse got there.
I suggest to use the MOUSEMOTION event. The event queue gives you all movements of the mouse. The relative movement of the mouse pointer since the last movement can be queried with the rel attribute:
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
angle += event.rel[0] * 360 / screen.get_width()
Minimal example
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
font = pygame.font.SysFont('consolas', 20)
clock = pygame.time.Clock()
angle = 0
text = font.render(f'Angle: {angle}°', True, (255, 255, 255))
pygame.mouse.set_pos(screen.get_rect().center)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
run = False
if event.type == pygame.MOUSEMOTION:
angle += event.rel[0] * 360 / screen.get_width()
text = font.render(f'Angle: {angle}°', True, (255, 255, 255))
pygame.mouse.set_pos(screen.get_rect().center)
screen.fill(0)
screen.blit(text, text.get_rect(center = screen.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Related
This question already has answers here:
Setting up an invisible boundary for my sprite
(1 answer)
Use vector2 in pygame. Collide with the window frame and restrict the ball to the rectangular area
(1 answer)
How to make ball bounce off wall with PyGame?
(1 answer)
Not letting the character move out of the window
(2 answers)
Closed 10 months ago.
I just started trying out pygame, so I watched a tutorial. When the guy showed, how to create edges left and right from the screen, it just didn't work when I did it, even though I've written it 100% equal as the tutorial guy. The tutorial is already two years old, so maybe pygame just has changed. Can somebody help me?
That's my code:
import pygame
import sys
pygame.init()
background = pygame.image.load("C:\Programmieren\Python\Grafiken\pygame test1 - background.png")
screen = pygame.display.set_mode([1200,595])
clock = pygame.time.Clock()
pygame.display.set_caption("pygame test1")
def draw():
screen.blit(background, (0, 0))
pygame.draw.rect(screen, (0, 0, 255), (x, y, width, height))
pygame.display.update()
x = 300
y = 300
speed = 3
width = 40
height = 80
left_wall = pygame.draw.rect(screen, (0,0,0), (-2,0,2,600), 0)
right_wall = pygame.draw.rect(screen, (0,0,0), (1201,0,2,600), 0)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
character = pygame.Rect(x,y,width, height)
pressed = pygame.key.get_pressed()
if presses[pygame.K_UP] or pressed[pygame.K_w] or pressed[pygame.K_SPACE]:
y -= speed
if pressed[pygame.K_RIGHT] or pressed[pygame.K_d] and not character.colliderect(right_wall):
x += speed
if pressed[pygame.K_DOWN] or pressed[pygame.K_s]:
y += speed
if pressed[pygame.K_LEFT] or pressed[pygame.K_a] and not character.colliderect(left_wall):
x -= speed
draw()
clock.tick(60)
The following code works for me after replacing the background.png path with a picture that exists on my computer. I changed left_wall and right_wall to call pygame.Rect instead of pygame.draw.rect and copied the draw.rect calls that were previously assigned to left_wall and right_wall to draw() function, and added some needed parentheses in two of the if statements. Also fixed a typo where pressed was spelled presses.
Without the parentheses, the character would always move right when right key is pressed even past the right edge of the window. When "d" is pressed, it would check against colliderect. Same for left arrow and "a" keys. or short-circuits, so if K_RIGHT or K_LEFT is pressed, it doesn't check the right hand side of the or in the if statements. With the parentheses added, the "move right" if statement always checks colliderect when K_RIGHT or K_d is pressed, instead of only when K_d is pressed.
import pygame
import sys
pygame.init()
background = pygame.image.load("C:\Programmieren\Python\Grafiken\pygame test1 - background.png")
screen = pygame.display.set_mode([1200,595])
clock = pygame.time.Clock()
pygame.display.set_caption("pygame test1")
def draw():
screen.blit(background, (0, 0))
pygame.draw.rect(screen, (0, 0, 255), (x, y, width, height))
pygame.draw.rect(screen, (0,0,0), left_wall, 0)
pygame.draw.rect(screen, (0,0,0), right_wall, 0)
pygame.display.update()
x = 300
y = 300
speed = 3
width = 40
height = 80
left_wall = pygame.Rect(-2,0,2,600)
right_wall = pygame.Rect(1201,0,2,600)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
character = pygame.Rect(x,y,width, height)
pressed = pygame.key.get_pressed()
if pressed[pygame.K_UP] or pressed[pygame.K_w] or pressed[pygame.K_SPACE]:
y -= speed
if (pressed[pygame.K_RIGHT] or pressed[pygame.K_d]) and not character.colliderect(right_wall):
x += speed
if pressed[pygame.K_DOWN] or pressed[pygame.K_s]:
y += speed
if (pressed[pygame.K_LEFT] or pressed[pygame.K_a]) and not character.colliderect(left_wall):
x -= speed
draw()
clock.tick(60)
I was making a pygame project and i made a square move and It would summon a square every frame and there would just be a line of squares.
I tried to update the screen every frame (Because i hadn't yet), but that did not work. Here is my code:
#Import Pygame
import pygame
#screen object(width, height)
screen_x = 750
screen_y = 600
screen = pygame.display.set_mode((screen_x, screen_y))
#Set the caption of the screen
pygame.display.set_caption('Game')
#Define a velocity, x, and y variable
velocity = 2.5x = 0.0y = 0.0
#Variable to keep our game loop running
running = True
#Game loop
while running:
# Initialing Color
color = (255,0,0)
if pygame.key.get_pressed()[pygame.K_w]:
y += 2.5
if pygame.key.get_pressed()[pygame.K_s]:
y -= 2.5
if pygame.key.get_pressed()[pygame.K_a]:
x -= 2.5
if pygame.key.get_pressed()[pygame.K_d]:
x += 2.5
# Drawing Rectangle
pygame.draw.rect(screen, color, pygame.Rect(x, y, 50, 50))
pygame.display.flip()
pygame.display.update()
# for loop through the event queue
for event in pygame.event.get():
# Check for QUIT event
if event.type == pygame.QUIT:
running = False
The entire scene is redrawn in every frame, therefore you have to clear the display in every frame:
import pygame
#screen object(width, height)
screen_x = 750
screen_y = 600
screen = pygame.display.set_mode((screen_x, screen_y))
#Set the caption of the screen
pygame.display.set_caption('Game')
#Define a velocity, x, and y variable
velocity = 2.5
x, y = 0, 0
color = (255,0,0)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(100)
# for loop through the event queue
for event in pygame.event.get():
# Check for QUIT event
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
x += (keys[pygame.K_d] - keys[pygame.K_a]) * velocity
y += (keys[pygame.K_s] - keys[pygame.K_w]) * velocity
# clear display
screen.fill((0, 0, 0))
# Drawing Rectangle
pygame.draw.rect(screen, color, pygame.Rect(x, y, 50, 50))
# update display
pygame.display.flip()
pygame.quit()
exit()
The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by calling either pygame.display.update() or pygame.display.flip()
The solution is very simple. You forgot to fill the screen with a color. Because your code just draw's a square to the screen surface every frame, and that's why it's drawing a line of squares. You have to fill the screen before drawing the things, because otherwise you will see an empty screen with that color, that you filled the surface with. Hope it helped.
I'm working on this pygame game and i'm just getting started but got a bit confused because i want the image to move in the x-axis along with the mouse but when i run the program i want the image to show up at the center or the 'floor' but appears at the left side instead. This is my code and a screenshot of what's happening.
import pygame
import sys
pygame.init()
pygame.mixer.init()
WIDTH, HEIGHT = 400, 500
FPS = 60
TITLE = 'FOOD DROP'
SIZE = 190
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE_SKY = (152, 166, 255)
# Display
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
# Surfaces
floor_surface = pygame.Surface((WIDTH, 100))
floor_surface.fill(BLUE_SKY)
floor_rect = floor_surface.get_rect(midbottom=(200, 500))
# Images
LOAD_DITTO = pygame.image.load('Graphics/ditto.png')
DITTO = pygame.transform.scale(LOAD_DITTO, (SIZE, SIZE))
# Time
CLOCK = pygame.time.Clock()
class Figure:
def draw_figure(self, mouse_x):
SCREEN.blit(DITTO, (mouse_x - 90, 330))
# Game loop
SCREEN_UPDATE = pygame.USEREVENT
# main_game = Main()
figure = Figure()
running = True
while running:
CLOCK.tick(FPS)
mx, my = pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
SCREEN.fill(WHITE)
SCREEN.blit(floor_surface, floor_rect)
figure.draw_figure(mx)
pygame.display.update()
When i run the program, this happens:
And i want the image to appear right at the center or the x-axis, not the border, i don't know why is this happening. Just to state, that screenshot was taken when the mouse hadn't been placed over the display.
If the mouse pointer is not in the window (out of focus), the initial position of the mouse pointer is (0, 0). Therefore pygame.mouse.get_pos returns (0, 0). It is also not possible to set the mouse position with pygame.mouse.set_pos if it is not in the window.
Initialize the variables mx and mx with the center of the window. Change the mouse position only when the mouse pointer is in the window (in focus). pygame.mouse.get_focused can be used to test whether the mouse is in the window.
mx, my = SCREEN.get_rect().center
running = True
while running:
CLOCK.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if pygame.mouse.get_focused():
mx, my = pygame.mouse.get_pos()
SCREEN.fill(WHITE)
SCREEN.blit(floor_surface, floor_rect)
figure.draw_figure(mx)
pygame.display.update()
pygame.quit()
sys.exit()
So I'm trying to make the game asteroids but are having trouble with working out the angle of which the spacecraft should look in. I know the basics of trigonometry but with the y-axis flipped I'm getting a bit confused. I've made a mockup of the problem so you don't have to look through the whole code.
import pygame
import sys
import math
DIMENSIONS = (600, 600)
window = pygame.display.set_mode(DIMENSIONS, pygame.RESIZABLE)
window.fill((20, 20, 20))
pygame.display.set_caption("Pygame template")
clock = pygame.time.Clock()
player_pos = pygame.Vector2(275,275)
player = pygame.Rect(player_pos.xy, (50,50))
looking_vector = pygame.Vector2(1,1)
while True:
window.fill((20,20,20))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
player = pygame.Rect(player_pos.xy, (50,50))
# Relative position of mouse
mouse_pos = pygame.mouse.get_pos()
delta = mouse_pos - player_pos
# Calculate the angle
angle_to_mouse = math.atan2(delta.y, delta.x)
looking_vector.xy = (100*math.cos(angle_to_mouse), 100*math.sin(angle_to_mouse))
# Player rect
pygame.draw.rect(window, (50, 50, 255), player)
# Line to mouse
pygame.draw.line(window, (255,50,50), player_pos + (25,25), pygame.mouse.get_pos())
# Line in direction to looking_vector
pygame.draw.line(window, (50,255,50), player_pos + (25,25), player_pos + looking_vector)
pygame.display.update()
clock.tick(60)
As you can see in the image above the red line - which is only a line between the center of the rectangle and the mouse position - is completely fine but when it comes to the green line it is a bit off.
This is pretty much the effect I get in my asteroid game only that it's pretty much fine if the player stays close to the axes. However if the player moves further away from either one of them the effect amplifies and the looking vector is completely in the wrong direction.
Your algorithm works fine. However, you need to use the center of the player to calculate the angle instead of the top left corner of the player:
player = pygame.Rect(player_pos.xy, (50,50))
player_center = pygame.Vector2(player.center)
# Relative position of mouse
mouse_pos = pygame.mouse.get_pos()
delta = mouse_pos - player_center
# Calculate the angle
angle_to_mouse = math.atan2(delta.y, delta.x)
looking_vector.xy = (100*math.cos(angle_to_mouse), 100*math.sin(angle_to_mouse))
# Player rect
pygame.draw.rect(window, (50, 50, 255), player)
# Line to mouse
pygame.draw.line(window, (255,50,50), player_center, pygame.mouse.get_pos())
# Line in direction to looking_vector
pygame.draw.line(window, (50,255,50), player_center, player_center + looking_vector)
I'm new to python/pygame and I'm trying to make a game. Basically it is a space ship floating around in space(it will be a scrolling background) pointing towards the mouse/crosshair. on clicking it will shoot bullets. there will be flying enemies which need AI and clinging enemies which need AI. Also liquids need to "flow." Collision detection for running into walls/bullets,enemies/floor. And lastly I will need a menu GUI (if i used gui in the right context.)
But right now, i would like to move the background (or for now the sprite) with keyboard controls. also how do I do a stretched image for the background?
This is what i've got:
import pygame, random, sys, time
pygame.init()
SIZE = [1000, 1000]
screen = pygame.display.set_mode(SIZE)
crosshair = pygame.image.load('crosshair.jpg')
player = pygame.image.load('spaceship.jpg')
pygame.display.set_caption("Solar Warfare")
WHITE = [255, 255, 255]
mouse_pos = []
spaceshipX = 0
spaceshipY = 0
done = False
pygame.mouse.set_visible(False)
while not done:
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mouse_pos = pygame.mouse.get_pos([])
screen.blit(crosshair, (mouse_pos[0], mouse_pos[1]))
pygame.display.flip()#What is the other way to
#update the screen?
if event.type == pygame.QUIT:
done = True
if event.type == pygame.K_RIGHT:
spaceshipX += 1
screen.fill(WHITE)
screen.blit(player, (spaceshipX, spaceshipY))
screen.blit(crosshair, (mouse_pos))
pygame.display.flip()
pygame.quit()
I would like to add a scrolling background.
I also need AI for the enemies, but thats later. Same with the Main Menu and levels and bosses/features etc...
I talk alot XD