Python, correct way to pass variables between functions - pygame

I'm trying to make a game in python using the pygame module.
def draw_character(self, color, box):
rect = pygame.Rect(box)
pygame.draw.rect(self.game.display, (color), pygame.Rect(rect))
return rect
and in the draw_character function preserves the collision shape of the character
move function:
def move(self, rect):
if rect.bottom >= self.game.DISPLAY_H:
print("work")
and finally I call the function like this
self.move(self.draw_character((221,171,177), (self.f1, self.f2, 60, 60)))
Is there another way I can call the rect variable or am I already doing this correctly

Add a Character class and make the rectangle (rect) and the color an attribute of the class:
class Character:
def __init__(rect, color, game):
self.rect = rect
self.color = color
self.game = game
def move(self):
if self.rect.bottom >= self.game.DISPLAY_H:
print("work")
def draw(self):
pygame.draw.rect(self.game.display, self.color, self.rect)
Create an instance of the class Character. Move and draw the character in the application loop:
character = Character((221,171,177), (self.f1, self.f2, 60, 60))
while True:
# [...]
character.move()
character.draw()

Related

Move a sprite, but cannot delete the precedent image of it [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by 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 either pygame.display.update() or pygame.display.flip()

Questions with creating sprites in pygame

So basically what I know how to do is add a player sprite (make a player class that inherits from pygame.sprite, etc...) this works for me.
What I would like to know how to do is iterate the creation of sprites and add them to the sprite group.
This is because I have a 2 dimensional array and I have a function that reads this and places the "tiles" accordingly in the 2d space, this is to create levels easier.
So what I want this function to do is create these sprites (I guess with a for loop that reads the array ?) and add them to do the group but this doesn't work so I have some questions first:
1)Can you create sprites outside of the init function in a class?
2)What really are sprites, is it a surface coupled to a rect ?
3)And finally do you have an idea of simply how to get this done: If I give you a 2d array, how would you make the function that reads this array and calculates the position (this is okay, I think I have it figured out) and most importantly, make sprites out of the given positions that can then be added to the sprites group.
Thanks in advance
Can you create sprites outside of the init function in a class?
Sure.
What really are sprites, is it a surface coupled to a rect ?
If we talk about pygame's Sprite class: yes.
Such a sprite is basically a Surface (the image attribute) and a Rect (the rect
attribute). They work best together with pygame's Group classes.
And finally do you have an idea of simply how to get this done ....
Just create a nested loop to iterate over the array.
Here's a simple example:
import pygame
pygame.init()
TILESIZE = 64
class Actor(pygame.sprite.Sprite):
def __init__(self, color, pos):
super().__init__()
self.image = pygame.Surface((TILESIZE, TILESIZE))
self.image.fill(pygame.Color(color))
self.rect = self.image.get_rect(topleft = pos)
def main():
data = [
' YUB ',
' G ',
' B ',
' ',
' Y ',
' U ',
]
screen = pygame.display.set_mode((len(data[0]) * TILESIZE, len(data) * TILESIZE))
sprites = pygame.sprite.Group()
colors = {
'G': 'green',
'B': 'black',
'Y': 'yellow',
'U': 'dodgerblue'
}
x, y = 0, 0
for line in data:
for char in line:
if char in colors:
sprites.add(Actor(colors[char], (x * TILESIZE, y * TILESIZE)))
x += 1
x = 0
y += 1
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
sprites.update()
screen.fill((100, 100, 100))
sprites.draw(screen)
pygame.display.flip()
main()
You can find another example here.

Use mask function (for collision detection) with multiple sprites

I am brand new to Pygame and am making a game for my A-level course. I am trying to have multiple bats that I can spawn and collide with. I am using Pygames mask function for 'pixel perfect collision' but I cannot get multiple bats to spawn at the same time with the collision system also working. I tried using groups but I haven't been able to get this to work. Does anyone know how to fix my code/ a better way around this problem? Thanks! The relevant code is below...
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y):
pygame.sprite.Sprite.__init__(self)
self.bat1 = pygame.image.load("Sprites\Bat_enemy\Bat-1.png").convert_alpha() # For hit registration for bat
self.bat1 = pygame.transform.scale(self.bat1, (80, 70))
self.bat_mask = pygame.mask.from_surface(self.bat1)
self.bat_rect = self.bat1.get_rect()
self.bat_x = bat_x
self.bat_y = bat_y
bats = pygame.sprite.Group()
Then in main loop:
num_of_bats = [1]
#Bat#
for i in num_of_bats:
bat_x = (random.randint(0, 600))
bat_y = (random.randint(0, 600))
bat = Bat(bat_x, bat_y, i)
bats.add(bat)
for bat in bats:
offsetP2B = (int(x - batx), int(y - self.baty)) #Player to Bat
resultP2B = bat_mask.overlap(player_mask, offsetP2B)
First get rectangle based collision working, then worry about the bitmask accuracy!
There's a couple of problems with your Sprite. The big one is that PyGame uses sprite.image to draw the bitmap. Your sprite code is using bat1 instead. It also needs to position the sprite.rect to the co-ordinate of the Bat. Furthermore, the collision mask must be called mask, and the sprite's collision/position pygame.Rect must be called rect.
I'm not sure if it's just a paste-o, but the sprite group definition shouldn't be inside the Sprite class.
So ... with a few minor fixups:
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y, bat_image):
pygame.sprite.Sprite.__init__(self)
self.image = bat_image
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface( self.image )
self.rect.centre = ( bat_x, bat_y )
def update( self ):
# TODO: code to make this bat move/flat whatever
pass
There's only minor differences here. It's better to load the image in once, outside the sprite class, than loading it in hundreds(?) of times - once for each bat.
Now it's pretty easy to make a colony of bats:
import os.path
START_BAT_COUNT = 30
BAT_IMAGE_PATH = os.path.join( 'Sprites', 'Bat_enemy', 'Bat-1.png' )
# group to hold all the bat sprites
all_bats = pygame.sprite.Group()
# Going Batty!
bat_image = pygame.image.load( BAT_IMAGE_PATH ).convert_alpha()
for i in range( START_BAT_COUNT ):
bat_x = (random.randint(0, 600))
bat_y = (random.randint(0, 600))
new_bat = Bat( bat_x, bat_y, bat_image )
all_bats.add( new_bat )
The in your main loop:
# move every bat
all_bats.update()
...
# paint every bat
all_bats.draw( screen )

Pygame smooth movement

How can I get a pygame rect to move smoothly? Like if I update the x position by 2 it looks smooth but if I update it by bigger number like 25 it teleports to the position. Also, if possible, can this work for decimals also?
Visual Representation
import pygame
import math
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
class Dot(pygame.sprite.Sprite):
# This class represents a car. It derives from the "Sprite" class in Pygame.
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
# Pass in the color of the car, and its x and y position, width and height.
# Set the background color and set it to be transparent
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.color = color
self.width = width
self.height = height
pygame.draw.rect(self.image, self.color, [0, 0, self.width, self.height])
self.rect = self.image.get_rect()
How can I get a pygame rect to move smoothly?
If your rectangle has to move 25px per frame, then the it makes no sens to draw the rectangles at the positions in between. The display is updated once per frame and it make no sens at all to draw the rectangle at the positions in between.
Possibly you have to less frames per second. In that case you have to increase the framerate and you can decrease the movement. Note, the human eye can just process a certain number of images per second. The trick is that you generate enough frames, that the movement appears smooth for the human eye.
pygame.Rect can store integral values only. If you want to operate with a very high framerate and floating accuracy, then you have to store the position of the object in separate floating point attributes. Synchronize the rounded position to the rectangle attribute. Note, you cannot draw on a "half" pixel of the window (at least in pygame).
e.g.:
class Dot(pygame.sprite.Sprite):
# This class represents a car. It derives from the "Sprite" class in Pygame.
def __init__(self, color, x, y, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
# Pass in the color of the car, and its x and y position, width and height.
# Set the background color and set it to be transparent
self.x = x
self.y = y
self.image = pygame.Surface([width, height])
self.image.fill(self.color)
self.rect = self.image.get_rect(center = (round(x), round(y)))
def update(self):
# update position of object (change `self.x`, ``self.y``)
# [...]
# synchronize position to `.rect`
self.rect.center = (round(x), round(y))

Pygame, my circle turns to a rect after I stored it in a variable, how do I prevent that from happening

I need to store a Circle in a variable but after I've done that it has turned into a rect
circle_1 = pygame.draw.circle(screen, (0, 0, 0), (300, 300), 30)
Print(circle_1)
the print returns
<rect(270, 270, 60, 60)>
but I can't work with that.
My circle is predefined but it won't show it on the canvas, here is an example of the problem
> import pygame, sys
>
>
> pygame.init() screen = pygame.display.set_mode((600, 600))
> predefined_circle = pygame.draw.circle(screen,(0, 0, 0),(300, 300), 30)
>
> def update():
> screen.fill((200, 0, 0))
> while 1:
> for event in pygame.event.get():
> if event.type == pygame.QUIT: sys.exit()
> # It shows my circle if I dirctly tip pygame.draw.circle(screen,(0, 0, 0),(300, 300), 30) into it
> predefined_circle
> pygame.display.update()
>
> update()
So that you can better relate to what I'm trying to achieve here is the code of what I'm doing but it is not necessary to read as I've already tried to explain it as best as I can above.
Please note the comments should explain everything that the block of code below it is doing.
# Creating the canvas which can paint any wanted Object from another class
class Canvas:
# Initialising the screen and setting all needed variables
def __init__(self, painting):
pygame.init()
self.screen_size = (600, 600)
self.background = (25, 255, 255)
self.screen = pygame.display.set_mode(self.screen_size)
self.paint = painting
# Let the user set the name of the canvas
def set_screen_name(self):
return self.screen
# Draw the everything you want to
def update(self):
# Paint the canvas
self.screen.fill(self.background)
# Make the game be quittable
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
# Draw the defined Circle and then update the Canvas
# it only draws a circle if directly tip pygame.draw.circle(surface, color, position, radius)
self.paint
pygame.display.update()
# Draw any circle you like
class Cir:
# Get all the required Information's to Draw a circle
def __init__(self, canvas, what_color, position, radius, line=0):
self.can = canvas
self.color = what_color
self.pos = position
self.r = radius
self.line = line
self.cir = None
# Create the circle with the acquired Information's
def create(self):
self.cir = pygame.draw.circle(self.can, self.color, self.pos, self.r, self.line)
return self.cir
# So far there is no Surface for the Cir class
# And there is no Object that cloud be painted for the Canvas class
# I initialise a canvas instance without anything that needs to be painted
get_surface = Canvas(None)
# Now I can access set_screen_name from the Canvas class and give the surface a name
# Which the Cir class can now use as a surface
screen = get_surface.set_screen_name()
c1 = pygame.draw.circle(screen, (0,0,0), (300, 300), 30)
print(c1)
# I'm initialising a circle
init_circle = Cir(screen, (0, 255, 0), (300, 300), 30)
# Create the initialised circle
circle_1 = init_circle.create()
# Give the Canvas class the created circle
paint = Canvas(circle_1)
# Draw the circle
paint.update()
My circle turns to a rect.
Actually, no, it doesn't. As per the documentation for those drawing functions, the intent of the calls is to draw something immediately, not to give you an object you can draw later:
Draw several simple shapes to a Surface.
From analysis of your question, it sounds like you believe that you are storing the act of drawing the circle so that it can be done later. That is not the case. Instead, what you are doing is actually drawing the circle and saving the result of that drawing action - evaluating the result later on will not actually draw, or redraw, the circle.
So, if the draw function is not returning something for later drawing, what is it returning? That can also be found in the above-mentioned documentation:
The functions return a rectangle representing the bounding area of changed pixels.
In other words, it's telling you the smallest rectangle that was changed by the drawing action - this will be a square with sides the same length as the diameter and centered around the same point.
Obviously, the authors of PyGame thought this information may be handy for some purpose, just not the purpose of redrawing the circle :-)
One way to do what you're trying to achieve would be to simply have a function to draw the "predefined" circle and call that instead of trying to evaluate the rectangle returned from a previous call.