Pygame - locking the scroll of a surface area to a limited amount - python

I'm playing with pygame and I'm making a memory game.
In my memory game I need to have a small Menu on the middle-bottom of the screen and the player list on top of the screen. The game manager will output the message in the upper left corner of the screen.
The playable board where the cards will be situated should be in the center of the screen with a small margin on the right and the left and about 200px from the top and the bottom.(e.g. if the screen width and height are 1200 x 800, the playable board should be from 2 to 1998 width and 200 to 600 height.
I've made a screenshot of the idea I have:
I've put up the idea of the scrollable board with the cards, although it's hardcoded for now, I mostly need the idea not the code since I just have a hard time figuring out how it should work.
The playable board (game board on the screenshot), should be moved through the keyboard(so up, right, down, left arrows). The thing I want to do is to allow more cards than 4 in each column and 12 in each row, in a way that when there are more cards than the 12 x 4, the gameboard could be moved, but without exiting the red square marked on the screenshot(so it's a scrollable surface). I want to limit the scrollable area to a width of 1200 and height of 400 px's, but in a way that the area is scrollable only inside the red area.
If that can't work or is planned otherwise, is there any way to register clicks differently? Let's say when I move the keys in a way that the Menu area is above the game board and the menu is clicked, I don't want the card under the Menu to be clicked as well. Is there any way to lock the content under the Menu?
Adding the code sample below:
from pygame import *
import sys
init()
screen_width = 1200
screen_height = 800
screen = display.set_mode((screen_width, screen_height))
board_width = 2000
board_height = 1000
playable_board = Surface((board_width, board_height))
playable_board = playable_board.convert()
screen.fill((255, 255, 255))
#draw.rect(playable_board, (125, 125, 125), (2, 200, board_width, board_height))
for x in range(0, 50):
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 200, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 310, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 420, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 530, 90, 90))
# draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 640, 90, 90))
point_x = 0
point_y = 0
point_change = -30
animationTimer = time.Clock()
endProgram = False
while not endProgram:
for e in event.get():
pass
key_pressed = key.get_pressed()
if e.type == QUIT:
endProgram = True
if e.type == MOUSEBUTTONUP:
mouse_x, mouse_y = e.pos
display.set_caption(('Coordinates: ' + str(mouse_x + abs(point_x)) + 'x' + str(mouse_y + abs(point_y))))
draw.rect(playable_board, (50, 200, 50), (mouse_x + abs(point_x), mouse_y + abs(point_y), 10, 10))
if key_pressed[K_LEFT]:
point_x -= point_change
if key_pressed[K_RIGHT]:
point_x += point_change
if key_pressed[K_UP]:
point_y -= point_change
if key_pressed[K_DOWN]:
point_y += point_change
if point_x > 0:
point_x = 0
if point_x < -(board_width - screen_width):
point_x = -(board_width - screen_width)
if point_y > 0:
point_y = 0
if point_y < -(board_height - screen_height):
point_y = -(board_height - screen_height)
screen.blit(playable_board, (point_x, point_y, screen_width, screen_height))
draw.rect(screen, (100, 255, 100), (500, 650, 200, 150))
draw.rect(screen, (100, 255, 100), (100, 0, 1000, 50))
draw.rect(screen, (100, 255, 100), (0, 70, 250, 100))
animationTimer.tick(60)
display.update()

I'd create a subsurface of the playable_board and then blit it at the desired coordinates. You need to define a rect (called area here) with the desired size of the subsurface and scroll it by incrementing the x and y attributes. To keep the area rect inside of the board_rect (to prevent ValueErrors), you can call the pygame.Rect.clamp or clamp_ip methods.
import pygame as pg
pg.init()
screen = pg.display.set_mode((1200, 800))
board = pg.Surface((2000, 1000))
board.fill((20, 70, 110))
board_rect = board.get_rect()
# Append the rects to a list if you want to use
# them for collision detection.
rects = []
for y in range(11):
for x in range(22):
rect = pg.draw.rect(board, (100, 100, 100), (x*95, y*95, 90, 90))
rects.append(rect)
# The blit position of the subsurface.
pos = (20, 200)
point_change = -10
# This rect is needed to create a subsurface with the size (1160, 400).
area = pg.Rect(0, 0, 1160, 400)
# Use the area rect to create a subsurface of the board.
board_subsurface = board.subsurface(area)
clock = pg.time.Clock()
endProgram = False
while not endProgram:
for e in pg.event.get():
if e.type == pg.QUIT:
endProgram = True
key_pressed = pg.key.get_pressed()
# Scroll by changing the x and y coordinates of the area rect.
if key_pressed[pg.K_LEFT]:
area.x += point_change
elif key_pressed[pg.K_RIGHT]:
area.x -= point_change
if key_pressed[pg.K_UP]:
area.y += point_change
elif key_pressed[pg.K_DOWN]:
area.y -= point_change
# Check if one of the movement keys was pressed.
if any(key_pressed[key] for key in (pg.K_LEFT, pg.K_RIGHT, pg.K_UP, pg.K_DOWN)):
# Clamp the area rect to the board_rect, otherwise calling
# `board.subsurface` could raise a ValueError.
area.clamp_ip(board_rect)
# Create a new subsurface with the size of the area rect.
board_subsurface = board.subsurface(area)
screen.fill((30, 30, 30))
screen.blit(board_subsurface, pos)
pg.draw.rect(screen, (100, 255, 100), (500, 650, 200, 150))
pg.draw.rect(screen, (100, 255, 100), (100, 0, 1000, 50))
pg.draw.rect(screen, (100, 255, 100), (0, 70, 250, 100))
clock.tick(60)
pg.display.update()

Related

Sprite touching something else than a white square [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Collision between masks in pygame
(1 answer)
Closed 1 year ago.
So, I'm making an app/game in python using pygame module, and my problem is that, I cannot find a way to check if a sprite is entirely touching another sprite or not.
What I mean is
if (sprite is touching anything else that some white rectangle):
do some code
Here is my messy code :
main.py :
import pygame, Classes, Groups, Images, random, time
pygame.init()
pygame.font.init()
# App Variables
run_bool = False
# Fonts
ARAL_20_ITALIC = pygame.font.SysFont('Arial' , 20, italic=True)
# Colors
BG = (30, 30, 30)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# Window
size = (800, 600)
scr = pygame.display.set_mode(size)
pygame.display.set_caption('Simulation by Cold Fire (0.1)')
# App Loop
while True:
time.sleep(0.01)
# Graphics
scr.fill(BG)
"""TERRAIN"""
terrain_hitbox = pygame.draw.rect(scr, WHITE, (300, 100, size[0] - 350, size[1] - 150))
pygame.draw.rect(scr, BLACK, (300, 100, size[0] - 350, size[1] - 150), width=10)
"""SUBJECTS"""
for subject in Groups.G_Subject:
if run_bool == True:
subject.update__()
scr.blit(subject.img, subject.rect)
"""GUI"""
pygame.draw.line(scr, WHITE, (200, 0), (200, size[1]), width=3)
scr.blit(ARAL_20_ITALIC.render('Subjects' ,False, WHITE), (30, 10))
add_hitbox = scr.blit(Images.add_img.convert_alpha(), (30, 50))
remove_hitbox = scr.blit(Images.remove_img.convert_alpha(), (30, 90))
scr.blit(ARAL_20_ITALIC.render(f'Subjects: {len(Groups.G_Subject)}' , False, WHITE), (30, 130))
if run_bool == False:
run_hitbox = scr.blit(Images.run_img.convert_alpha(), (210, size[1] - 40))
else:
run_hitbox = scr.blit(Images.stop_img.convert_alpha(), (210, size[1] - 40))
# Updating Screen
pygame.display.flip()
# Events
for event in pygame.event.get():
# Quitting App
if event.type == pygame.QUIT:
pygame.quit()
quit()
# Clicking
if event.type == pygame.MOUSEBUTTONDOWN:
mouse = pygame.mouse.get_pos()
if add_hitbox.collidepoint(mouse[0], mouse[1]):
rand_x = random.randint(terrain_hitbox[0], terrain_hitbox[0] + terrain_hitbox[2])
rand_y = random.randint(terrain_hitbox[1], terrain_hitbox[1] + terrain_hitbox[3])
Classes.Subject(rand_x, rand_y, Images.subject0_img.convert_alpha())
if remove_hitbox.collidepoint(mouse[0], mouse[1]) and not 1 > len(Groups.G_Subject):
Groups.G_Subject.remove(random.choice(Groups.G_Subject.sprites()))
if run_hitbox.collidepoint(mouse[0], mouse[1]):
if run_bool == True:
run_bool = False
else:
run_bool = True
Classes.py :
import pygame, Groups, random, math
class Subject(pygame.sprite.Sprite):
def __init__(self, x, y, img=pygame.image.load('assets/img/subject0.png').convert_alpha(pygame.display.set_mode((800, 600))), speed=5):
super().__init__()
self.img = img
self.rect = self.img.get_rect()
self.rect.x = x
self.rect.y = y
self.oldx = self.rect.x
self.oldy = self.rect.y
self.speed = speed
Groups.G_Subject.add(self)
def calculat_new_xy(self, old_xy, speed, angle_in_radians):
new_x = old_xy.x + (speed * math.cos(angle_in_radians))
new_y = old_xy.y + (speed * math.sin(angle_in_radians))
return new_x, new_y
def update__(self):
self.oldx, self.oldy = self.rect.x, self.rect.y
self.rect.x, self.rect.y = self.calculat_new_xy(self.rect, self.speed, random.randint(1, 360))
Images.py :
import pygame
add_img = pygame.transform.scale(pygame.image.load('assets/img/gui/add.png'), (90, 30))
remove_img = pygame.transform.scale(pygame.image.load('assets/img/gui/remove.png'), (90, 30))
subject0_img = pygame.image.load('assets/img/subject0.png')
run_img = pygame.transform.scale(pygame.image.load('assets/img/gui/run.png'), (90, 30))
stop_img = pygame.transform.scale(pygame.image.load('assets/img/gui/stop.png'), (90, 30))
Groups.py :
import pygame
G_Subject = pygame.sprite.Group()
I do know my code is a mess, so if you don't wanna help, it's ok ! Thx in advance :D

Making targets disappear upon collision with a bullet, using lists

I am developing a small target shooter game for school coursework. I have hit an an impasse after about an hour and a half of iteration and testing. If you see the code below, I have used lists to make it so that when bullets hit a target, they are removed from the list and are no longer printed with 'pygame.draw.rect' but it seems to be much more difficult for the targets as I have made these using OOP and not a one off function. I have tried replicating the bullet lists with the targets, putting the 'IF' statement in the same places etc. but I end up having the same outcome which is the target is hit and 'hit' is printed. The bullet disappears but the target doesn't. I have only been using the language for about a month so although I'm getting more used to it, I still am no expert and I really have just hit a dead end here. Any sort of help would be greatly appreciated. It may be a case of me having to completely rethink my approach and change my code drastically but perhaps theres something you can see which I can not. Thanks for any help given. (Sorry for the messiness of the code, it is after a lot of changes and iterations. Also please note that I have currently only coded to test it on target_1 to save time)
import pygame
#Setting window dimensions and caption. (Module 1)
pygame.init()
window = pygame.display.set_mode((800, 575))
pygame.display.set_caption("TARGET PRACTICE")
#Colour variables. (Module 1)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)
#py_clock tracks framerate of program for other 'pygame.time' commands. (Module 8)
py_clock = pygame.time.Clock()
#Target class created. (Module 5)
class Target:
def __init__(self, x, y, h, w, v):
self.x = x
self.y = y
self.h = h
self.w = w
self.v = v
def hit(self):
print('hit')
all_bullets_keep.remove(item)
all_targets.remove(all_targets[0])
pass
#Instantiation of targets. (Module 5)
target_1 = Target(0, 80, 60, 40, 0.05)
target_2 = Target(0, 100, 60, 40, 0.5)
target_3 = Target(0, 50, 60, 40, 0.5)
target_4 = Target(0, 75, 60, 40, 0.5)
target_5 = Target(0, 45, 60, 40, 0.5)
target_6 = Target(0, 85, 60, 40, 0.5)
#Instantiation of hitboxes. (Module 9)
target_hbx1 = Target(-5, 75, 70, 50, 0.05)
target_hbx2 = Target(-5, 95, 70, 50, 0.5)
target_hbx3 = Target(-5, 45, 70, 50, 0.5)
target_hbx4 = Target(-5, 70, 70, 50, 0.5)
target_hbx5 = Target(-5, 40, 70, 50, 0.5)
target_hbx6 = Target(-5, 80, 70, 50, 0.5)
#Declaring variables to be used in the while loop. (Module 5)
clock = 0
target_2_threshold = 500
target_3_threshold = 1000
target_4_threshold = 1500
target_5_threshold = 2000
target_6_threshold = 2500
#Setting player sprite dimension variables. (Module 6)
player_sprite_x = 357.5
player_sprite_y = 450
player_sprite_h = 125
player_sprite_w = 85
#all_bullets list to store bullets made by function inside loop. (Module7)
all_bullets = []
all_targets = []
all_targets.append(target_1)
all_targets.append(target_2)
all_targets.append(target_3)
all_targets.append(target_4)
all_targets.append(target_5)
all_targets.append(target_6)
#Variables to track and limit shooting function. (Module 9.5)
bullet_delay = 1500
next_bullet_time = 0
exec = True
while exec:
#current_time uses a pygame_time command to track ticks. (Module 9.5)
current_time = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
exec = False
#'IF' statement to trigger the shooting function. (Module 7)
if event.type == pygame.MOUSEBUTTONDOWN:
#Condition set to only trigger the below code if the current_time is greater than the next_bullet time. (Module 9.5)
if event.button == 1 and current_time > next_bullet_time:
next_bullet_time = current_time + bullet_delay
dx = event.pos[0] - (player_sprite_x+ player_sprite_w//2)
dy = event.pos[1] - player_sprite_y
direction = pygame.math.Vector2(dx, dy).normalize()
bullet = {'x': player_sprite_x+42, 'y': player_sprite_y, 'direction': direction}
all_bullets.append(bullet)
#Defines movement of targets and sets delay between drawings. (Module 5)
for item in all_targets:
target_1.x += target_1.v
target_hbx1.x += target_hbx1.v
if clock > target_2_threshold:
target_2.x += target_2.v
target_hbx2.x += target_hbx2.v
if clock > target_3_threshold:
target_3.x += target_3.v
target_hbx3.x += target_hbx3.v
if clock > target_4_threshold:
target_4.x += target_4.v
target_hbx4.x += target_hbx4.v
if clock > target_5_threshold:
target_5.x += target_5.v
target_hbx5.x += target_hbx5.v
if clock > target_6_threshold:
target_6.x += target_6.v
target_hbx6.x += target_hbx6.v
#all_bullets_keep list combined with FOR loop retains only bullets in the arena. (Module 7)
all_bullets_keep = []
for item in all_bullets:
item['x'] += item['direction'][0] # item['direction'][0] * 2
item['y'] += item['direction'][1] # item['direction'][1] * 2
if 0 < item['x'] < 800 and 0 < item['y'] < 575:
all_bullets_keep.append(item)
all_bullets = all_bullets_keep
#Fill the background (Module 5)
window.fill(RED)
#Redraw each target in every frame. (Module 5)
all_targets_keep = []
for item in all_targets:
pygame.draw.rect(window, BLUE, (target_1.x, target_1.y, target_1.h, target_1.w))
pygame.draw.rect(window, BLUE, (target_hbx1.x, target_hbx1.y, target_hbx1.h,target_hbx1.w), 2)
if 0 < target_1.x < 800 and 0 < target_1.y < 575:
all_targets_keep.append(target_1)
if clock > target_2_threshold:
pygame.draw.rect(window, BLUE, (target_2.x, target_2.y, target_2.h, target_2.w))
pygame.draw.rect(window, BLUE, (target_hbx2.x, target_hbx2.y, target_hbx2.h,target_hbx2.w), 2)
all_targets_keep.append(target_2)
if clock > target_3_threshold:
pygame.draw.rect(window, BLUE, (target_3.x, target_3.y, target_3.h, target_3.w))
pygame.draw.rect(window, BLUE, (target_hbx3.x, target_hbx3.y, target_hbx3.h,target_hbx3.w), 2)
all_targets_keep.append(target_3)
if clock > target_4_threshold:
pygame.draw.rect(window, BLUE, (target_4.x, target_4.y, target_4.h, target_4.w))
pygame.draw.rect(window, BLUE, (target_hbx4.x, target_hbx4.y, target_hbx4.h,target_hbx4.w), 2)
all_targets_keep.append(target_4)
if clock > target_5_threshold:
pygame.draw.rect(window, BLUE, (target_5.x, target_5.y, target_5.h, target_5.w))
pygame.draw.rect(window, BLUE, (target_hbx5.x, target_hbx5.y, target_hbx5.h,target_hbx5.w), 2)
all_targets_keep.append(target_5)
if clock > target_6_threshold:
pygame.draw.rect(window, BLUE, (target_6.x, target_6.y, target_6.h, target_6.w))
pygame.draw.rect(window, BLUE, (target_hbx6.x, target_hbx6.y, target_hbx6.h,target_hbx6.w), 2)
all_targets_keep.append(target_6)
all_targets = all_targets_keep
#Draw the player sprite. (Module 6)
pygame.draw.rect(window, BLUE, (player_sprite_x, player_sprite_y, player_sprite_w, player_sprite_h))
#Draw each item in all_bullets. (Module 7)
for item in all_bullets:
pygame.draw.rect(window, BLUE, (item['x']-5, item['y']-5, 10, 10))
b_hitbox = (item['x']-10, item['y']-10, 20, 20)
pygame.draw.rect(window, BLUE, b_hitbox, 2)
for item in all_bullets_keep:
if item['y']-30 < (target_hbx1.y) + (target_hbx1.h) and item['y']+30 > target_hbx1.y:
if item['x']+10 > target_hbx1.x and item['x']-30 < (target_hbx1.x) + (target_hbx1.w):
target_1.hit()
pygame.display.update()
#tick_busy_loop limits number of times the game can refresh per second. (Module 8)
py_clock.tick_busy_loop(120)
pygame.quit()
There's a few minor bugs in your code. I think the target is not disappearing because of something going on in with the all_targets_keep list. It looks like the code is re-adding the target to the "keep" list, whether it's been hit or not.
Your approach to using a class to hold all the Target code is a good idea. But all the targeting code is still spread throughout your main loop. This is causing code-clutter and making the task more difficult.
By moving this code into the class, it frees the code from having a "special case" for each of the targets. The target knows everything about itself, and can perform any test inside itself. For example, the drawing of the target to the screen.
class Target:
def __init__(self, x, y, h, w, v, threshold):
self.x = x
self.y = y
self.h = h
self.w = w
self.v = v
self.threshold = threshold
self.hit = False
def draw( self, window ):
# body
pygame.draw.rect( window, BLUE, ( self.x, self.y, self.w, self.h ), 0 )
# hit-box
pygame.draw.rect( window, BLUE, ( self.x-5, self.y-5, self.w+10, self.h+10 ), 1 )
When the target has a member function to draw itself, that whole-whack of drawing code goes away to become a single, simple function. Then the drawing of all targets becomes:
clock += 1
for item in all_targets:
if ( clock > item.threshold and not item.hit ):
item.draw( window )
The target's "hit box" is also just a function of the existing points, there's no need to keep these separate. So, similarly, using a PyGame rect, a target can check whether it has been hit by a bullet:
class Target:
...
def collidesWith( self, bullet_pos ):
# hit-box is 5 pixels offset from target
target_rect = pygame.Rect( self.x-5, self.y-5, self.w+10, self.h+10 )
self.hit = target_rect.collidepoint( bullet_pos )
return self.hit
Anyway, you're making good progress. But what commenter #importrandom says is true - it would really be easier (eventually) for you to use the PyGame built-in sprite classes. They already take care of a lot of the code you're writing yourself. If you don't want to, that's fine, it's your code.

How to move a Pygame object and erase it in its previous position?

I'm trying to build a game that involves a small red player (rectangle) being controlled by the arrow keys. There's a white grid on top of a black background, 2 different coloured objectives (rectangles), and several random red boxes (rectangles).
The part I need help with is moving the small red player. I can move it, but it seems to draw itself in the new position, but the version of the rectangle that was previously drawn stays there, forming a line. I want the entire rectangle to move and not leave any traces/previous versions of itself.
According to some other posts, I've heard that the only way to do this is to fill the screen with the background colour (in this case, black) and redraw the players on top of it; however, this is really hard in my case as I have the red boxes and objectives placed randomly, so every time I draw them again, they draw in a new random position, not in their old positions. I want the red boxes and objectives to stay in the same position, but have the player rectangles move around (while basically deleting the older versions of themselves).
Here's the code I currently have (I've excluded the basics, like defining colours, imports, and setting the screen height/width):
p1_velocity_x = 0
p1_velocity_y = 0
p2_velocity_x = 0
p2_velocity_y = 0
def grid():
for col in range(0, screen_width - 100, 10):
for row in range(0, screen_height, 10):
screen.set_at((row, col), white)
def red_boxes():
for i in range(100):
pygame.draw.rect(screen, red, (randrange(1, 1200), randrange(1, 470), 25, 25))
def blue_score_box():
pygame.draw.rect(screen, blue, (randrange(1, 1200), randrange(1, 470), 25, 25))
def yellow_score_box():
pygame.draw.rect(screen, yellow, (randrange(1, 1200), randrange(1, 470), 25, 25))
class Player:
color = (0, 0, 0)
def __init__(self, color):
self.x = 0
self.y = 0
self.color = color
p1 = Player(red)
p1.x = randrange(1, 1200, 10)
p1.y = randrange(1, 470, 10)
p2 = Player(yellow)
p2.x = randrange(1, 1200, 10)
p2.y = randrange(1, 470, 10)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
p1.x += 10
screen.fill(black)
grid()
red_boxes()
yellow_score_box()
blue_score_box()
pygame.draw.rect(screen, p1.color, (p1.x, p1.y, 10, 10))
pygame.draw.rect(screen, p2.color, (p2.x, p2.y, 10, 10))
pygame.display.update()
The result of the above code is that I'm able to move the red rectangle as I want, but since I call the red_boxes(), yellow_score_box(), and blue_score_box() methods in the while loop, they keep calling indefinitely, drawing random red, blue, and yellow rectangles all over the screen almost every second. I want them to stay in one place while having the functionality of moving the player as I do now.
Create a pygame.Surface with the same size than the red player rectangle:
bkP1 = pygame.Surface((10, 10))
Backup the background (pygame.Surface.blit) and store the position of the player, before the player is drawn:
prevPos = (p1.x, p1.y)
bkP1.blit(screen, (0, 0), (*prevPos, 10, 10))
Draw the background on top of the player when the player has to be erased:
screen.blit(bkP1, prevPos)
The process may work as follows:
prevPos = None
bkP1 = pygame.Surface((10, 10))
while True:
# [...]
if prevPos:
screen.blit(bkP1, prevPos)
prevPos = (p1.x, p1.y)
bkP1.blit(screen, (0, 0), (*prevPos, 10, 10))
pygame.draw.rect(screen, p1.color, (p1.x, p1.y, 10, 10))
Of course it is possible to add the mechanism to the class Player:
class Player:
def __init__(self, color):
self.x = 0
self.y = 0
self.color = color
self. prevPos = None
self.bk = pygame.Surface((10, 10))
def draw(self, screen):
if self.prevPos:
screen.blit(self.bk, self.prevPos)
self.prevPos = (self.x, self.y)
self.bk.blit(screen, (0, 0), (*self.prevPos, 10, 10))
pygame.draw.rect(screen, self.color, (self.x, self.y, 10, 10))
while True:
# [...]
p1.draw(screen)

How do I change animation speed without altering my game's FPS?

I finally figured out how to animate my sprite, but now I have a new problem. When running the game at my desired FPS (60), the character animation is way too quick. The animation looks smooth at around 10FPS, but the game looks choppy at that framerate. It is possible for my game to run at 60FPS, while the animation runs at a seperate FPS (ex. 10)? Any help appreciated!
Images and Sound FX Dowload
My code:
import pygame
import random
import time
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d, %d" %(0, 20)
pygame.init()
SIZE = W, H = 400, 700
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
# colours
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BACKGROUND = (94, 194, 222)
STRIPE = (60, 160, 190)
LANELINE = (255, 255, 255)
x1 = 30
x2 = 330
lane1 = 30
lane2 = 130
lane3 = 230
lane4 = 330
y = 530
width = 40
height = 64
toggle1 = 0
toggle2 = 0
target_x1 = 30
target_x2 = 330
vel_x = 10
def drawScene():
screen.fill(BACKGROUND)
pygame.draw.polygon(screen, STRIPE, ((200, 700), (300, 700), (400, 600), (400, 500)))
pygame.draw.polygon(screen, STRIPE, ((0, 700), (100, 700), (400, 400), (400, 300)))
pygame.draw.polygon(screen, STRIPE, ((0, 500), (0, 600), (400, 200), (400, 100)))
pygame.draw.polygon(screen, STRIPE, ((0, 300), (0, 400), (400, 0), (300, 0)))
pygame.draw.polygon(screen, STRIPE, ((0, 100), (0, 200), (200, 0), (100, 0)))
pygame.draw.line(screen, LANELINE, (100, 0), (100, 700), 2)
pygame.draw.line(screen, LANELINE, (200, 0), (200, 700), 4)
pygame.draw.line(screen, LANELINE, (300, 0), (300, 700), 2)
mainsheet = pygame.image.load("dolphinSheet.png").convert()
sheetSize = mainsheet.get_size()
horiz_cells = 6
vert_cells = 1
cell_width = int(sheetSize[0] / horiz_cells)
cell_height = int(sheetSize[1] / vert_cells)
cellList = []
for vert in range(0, sheetSize[1], cell_height):
for horz in range(0, sheetSize[0], cell_width):
surface = pygame.Surface((cell_width, cell_height))
surface.blit(mainsheet, (0, 0),
(horz, vert, cell_width, cell_height))
colorkey = surface.get_at((0, 0))
surface.set_colorkey(colorkey)
cellList.append(surface)
cellPosition = 0
# main loop
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
pygame.mixer.music.load('percussiveHit.mp3')
pygame.mixer.music.play()
toggle1 += 1
if toggle1 % 2 == 1:
target_x1 += 100
else:
target_x1 -= 100
elif event.key == pygame.K_d:
pygame.mixer.music.load('percussiveHit.mp3')
pygame.mixer.music.play()
toggle2 += 1
if toggle2 % 2 == 1:
target_x2 -= 100
else:
target_x2 += 100
if x1 < target_x1:
x1 = min(x1 + vel_x, target_x1)
else:
x1 = max(x1 - vel_x, target_x1)
if x2 < target_x2:
x2 = min(x2 + vel_x, target_x2)
else:
x2 = max(x2 - vel_x, target_x2)
if cellPosition < len(cellList) - 1:
cellPosition += 1
else:
cellPosition = 0
drawScene()
pygame.draw.rect(screen, RED, (x1, y, width, height))
pygame.draw.rect(screen, RED, (x2, y, width, height))
screen.blit(cellList[cellPosition], (x1 + 4, y - 1))
screen.blit(cellList[cellPosition], (x2 + 4, y - 1))
# players
# screen.blit(playerImg, (x1 + 4, y - 5))
# screen.blit(playerImg, (x2 + 4, y - 5))
pygame.display.update()
pygame.quit()
Update the image based on a real-time millisecond delay, rather than every frame. Use pygame.time.get_ticks() (returns the number of milliseconds since pygame.init()) to update the image based on a specific time.
For example:
MS_FRAME_TIME = 100 # Mow many milliseconds a frame is shown for
...
last_paint_at = 0 # Start condition to ensure first paint
...
while True:
clock.tick(60)
ticks = pygame.time.get_ticks() # millliseconds since start
# If enough milliseconds have elapsed since the last frame, update!
if ( ticks - last_paint_at > MS_FRAME_TIME ):
last_paint_at = ticks
if ( cellPosition < len(cellList) - 1 ):
cellPosition += 1
else:
cellPosition = 0
screen.blit(cellList[cellPosition], (x1 + 4, y - 1))
screen.blit(cellList[cellPosition], (x2 + 4, y - 1))
If it fits your code, it may also be possible to simply look at the modulus of the time to select the animation frame.

How to make ball bounce off triangle in pygame?

Hello I'm a pretty new programmer and I'm trying to make a ball bounce off a 45 degree triangle. Here is my code:
This program makes the ball bounce when it hits the sides of the window, but I don't know how to make it bounce off a triangle.
import pygame # importing the pygame
import sys # importing the system libraries
import time # importing timer
import random
from pygame.locals import * # importing the locals functions from the pygame library set
pygame.init() # the function from pygame that initializes all relevant variable
# setting length and width
width = 500
length = 300
# colour variables
WHITE = (255,255,255)
BLUE = (0,0,255)
# importing ball image
ball = pygame.image.load('ball.png')
ballRect = ball.get_rect()
ballRect.left = 300
ballRect.right = 300
# setting speed
x_speed = 2
y_speed = 2
# setting window size
WINDOW = pygame.display.set_mode((width, length))# setting the size of the window
pygame.display.update()
# loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ballRect = ballRect.move(x_speed,y_speed)
WINDOW.fill(WHITE) # changing screen colour to white
WINDOW.blit(ball,ballRect) # printing the ball to screen
pygame.display.update()
pygame.display.flip()
time.sleep(0.002) # to slow down the speed of bouncing
pygame.display.update()
# if the left side of ballRect is in a position less than 0, or the right side of ballRect is greater than 500
if ballRect.left < 0 or ballRect.right > (width):
x_speed = x_speed * -1
# if the top of ballRect is in a position less than 0, or the bottom of ballRect is greater than the length
elif ballRect.top < 0 or ballRect.bottom > (length):
y_speed = y_speed * -1
pygame.display.update()
I haven't drawn in the triangle because I don't know where to, but I expect the ball to bounce off the triangle like it does when it hits the sides of the window. Any help would be great!
Interesting task. A triangle can be defined by a simple list:
triangle = [(250, 220), (400, 300), (100, 300)]
The triangle can be drawn by pygame.draw.polygon()
pygame.draw.polygon(WINDOW, RED, triangle, 0)
Use pygame.math.Vector2 to define the position and the motion vector of the ball:
ballvec = pygame.math.Vector2(1, 1)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = 64
Create a function, which does the collision detection. The function has to detect if the ball hits a line. If the line is hit, then the motion vector of the ball is reflected on the line.
The line is represented by 2 points (lp0, lp1), which are pygame.math.Vector2 objects.
The position of the ball (pt) and the motion vector (dir) are pygame.math.Vector2 objects, too:
def isect(lp0, lp1, pt, dir, radius):
# direction vector of the line
l_dir = (lp1 - lp0).normalize()
# normal vector to the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0])
# distance to line
d = (lp0-pt).dot(nv)
# intersection point on endless line
ptX = pt + nv * d
# test if the ball hits the line
if abs(d) > radius or dir.dot(ptX-pt) <= 0:
return dir
if (ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0:
return dir
# reflect the direction vector on the line (like a billiard ball)
r_dir = dir.reflect(nv)
return r_dir
Append the window rectangle and the triangle to a list of lines. Ech line is represented by a tuple of 2 pygame.math.Vector2 objects:
# add screen rect
screen_rect = [(0, 0), (0, 300), (500, 300), (500, 0)]
for i in range(len(screen_rect)):
p0, p1 = screen_rect[i], screen_rect[(i+1) % len(screen_rect)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
# add red trianlge
triangle = [(250, 220), (400, 300), (100, 300)]
for i in range(len(triangle)):
p0, p1 = triangle[i], triangle[(i+1) % len(triangle)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
Do the collision detection in a loop, which traverse the lines. If the ball hits a line, then the motion vector is replaced by the reflected motion vector:
for line in line_list:
ballvec = isect(*line, ballpos, ballvec, balldiameter/2)
Finally update the position of the ball an the ball rectangle:
ballpos = ballpos + ballvec
ballRect.x, ballRect.y = ballpos[0]-ballRect.width/2, ballpos[1]-ballRect.height/2
See the example code, where I applied the suggested changes to your original code. My ball image has a size of 64x64. The ball diameter has to be set to this size (balldiameter = 64):
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
try:
ball = pygame.image.load("Ball64.png")
except:
ball = pygame.Surface((64, 64), pygame.SRCALPHA)
pygame.draw.circle(ball, (255, 255, 0), (32, 32), 32)
ballvec = pygame.math.Vector2(1.5, 1.5)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = ball.get_width()
def reflect_circle_on_line(lp0, lp1, pt, dir, radius):
l_dir = (lp1 - lp0).normalize() # direction vector of the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0]) # normal vector to the line
d = (lp0-pt).dot(nv) # distance to line
ptX = pt + nv * d # intersection point on endless line
if (abs(d) > radius or dir.dot(ptX-pt) <= 0 or # test if the ball hits the line
(ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0):
return dir
r_dir = dir.reflect(nv) # reflect the direction vector on the line (like a billiard ball)
return r_dir
triangle1 = [(250, 220), (400, 300), (100, 300)]
triangle2 = [(250, 80), (400, 0), (100, 0)]
screen_rect = [(0, 0), (0, window.get_height()), window.get_size(), (window.get_width(), 0)]
line_list = []
for p0, p1 in zip(triangle1, triangle1[1:] + triangle1[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(triangle2, triangle2[1:] + triangle2[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(screen_rect, screen_rect[1:] + screen_rect[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(250)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for line in line_list:
ballvec = reflect_circle_on_line(*line, ballpos, ballvec, balldiameter/2)
ballpos = ballpos + ballvec
window.fill((64, 64, 64))
pygame.draw.polygon(window, (255, 0, 0), triangle1, 0)
pygame.draw.polygon(window, (0, 0, 255), triangle2, 0)
window.blit(ball, (round(ballpos[0]-balldiameter/2), round(ballpos[1]-balldiameter/2)))
pygame.display.flip()
pygame.quit()

Categories

Resources