Python, confused by dot notation - python

I'm currently reading Python Crash Course, and there is that code:
import pygame
class Ship():
    def __init__(self, screen):
        """Initialize the ship, and set its starting position."""
        self.screen = screen
        # Load the ship image, and get its rect.
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()
        # Start each new ship at the bottom center of the screen.
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
I'm pretty sure i'm asking basic stuff, but nor my brain, nor my google skills aren't that good.
This thing: self.rect = self.image.get_rect(). Aren't get_rect() part of pygame.Screen module? If so, shouldn't it be used like pygame.Screen.get_rect()?
self.rect.centerx = self.screen_rect.centerx Too.many.dots. Is rect class instance, that is being created by get_rect() method, and centerx is attribute of that instance? Also, screen variable (or class instance?) is defined like that: screen = pygame.display.set_mode((1200, 800))
Thanks in advance!
Edit: Thanks for answers!

In PyGame, the get_rect() function is defined in a class that represents some sort of surface. In this case, both the screen and the image have that method because both subclass surfaces and so they both have the get_rect() method.
This code: self.rect.centerx = self.screen_rect.centerx can be translated as setting the centerx of the rect of myself to the centerx of the screen of myself. In other words, it moves the object to the center of the screen. The self keyword represents the current object and each dot represents a property of that object.

get_rect() is a method of Surface. Both Screen and Image are a Surface (they implement the Surface interface). Screen is just special Surface that refers to the Surface that's actually mapped to a location on-screen, while other Surfaces like Image are off-screen object in the memory that can be drawn or drawn into without affecting what's on the screen.
self.rect is a Rect instance, which is used by pygame to track the position and dimension of Surface/s.

Related

How to Display Sprites in Pygame?

This is just a quick question regarding sprites in PyGame, I have my image loaded as in the code below, and I'm just wondering how to display the sprite in PyGame like drawing a rectangle or circle. I don't want to have it behave in anyway. I think I use a blit command, but I'm not sure and I'm not finding much online.
Here's my image code for loading it.
Star = pygame.image.load('WhiteStar.png').convert_alpha()
You could just provide an outline for loading a sprite. I simply want to display it.
Use blit to draw an image. Actually blit draws one Surface onto another. Hence you need to blit the image onto the Surface associated to the display.
You need to specify the position where the image is blit on the target. The position can be specified by a pair of coordinates that define the top left position. Or it can be specified by a rectangle, only taking into account the top left point of the rectangle:
screen = pygame.dispaly.set_mode((width, height))
star = pygame.image.load('WhiteStar.png').convert_alpha()
# [...]
while run:
# [...]
screen.blit(star, (x, y))
# [...]
Use a pygame.Rect when you want to place the center of a surface at a specific point. pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the center of the rectangle can be specified with the keyword argument center. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments):
screen.blit(star, star.get_rect(center = (x, y)))

How to rotate an image in the same position?

I'm currently trying to code a game which includes a wee robot landing on a pad. In trying to work out the physics of the way it would fall, I have come across an issue with the rotation. It rotates on a left or right key press, in the respective direction.
I've tried using blit with the rect.center but it still doesn't seem to work. Any help would be much appreciated!!
def rotate_right(self):
self.new_angle = self.angle + 30
self.rotate_lander()
def rotate_lander(self):
self.image = pygame.transform.rotozoom(self.image, self.new_angle, 1)
self.rect = self.image.get_rect(center=self.image.get_rect().center)
screen.blit(self.image, self.rect.center)
I've managed to get it to rotate, but it moves with every rotation, and I need it to stay in the same position. I think the centre is off, but I'm not sure where it could have gone wrong.
First, in rotate_lander(), you should always rotate the original picture, otherwise it will get distorted (And in your case, run away). So, create another copy of self.image, which you wont change.
original = pygame.Surface(...)
...
def rotate_lander(self):
self.image = pygame.transform.rotozoom(self.original, self.new_angle, 1)
But now, the image still won't rotate on exact same position.
The problem is, that the bounding box of image is changing. And with that, the position of every point of image changes. You shouldn't set the position of self.rect to the center, because it is moving.
Instead, you must update the position according to the change of bounding box. You should compare the bounding box before and after the rotation.
You have a full tutorial on this topic already answered here:
Rotate an image around its center
*If you just wish to keep an image on place (not rotating it around its center), you can just get rid of center.
def rotate_lander(self):
self.image = pygame.transform.rotozoom(self.image, self.new_angle, 1)
self.rect = self.image.get_rect()
screen.blit(self.image, self.rect)

Pygame collision detect with object and rect

Yep, I'm asking another question about this program :D
Anyway, I currently a program that creates two lines on the screen with a gap in between them that can scroll. From here, I obviously need to see if the two objects are colliding. Since I only have one sprite and one rectangle I thought it was slightly pointless and overkill to make two classes for them. However, I can only find tutorials relating to classes which I obviously don't need. So, my question really is:
Is it possible to test for collision between a standard image and a Pygame rect? If it is not, how can I convert either the image, rectangle or both the sprites to do this. (All preferably without using classes.)
Note: the image and rectangle are created in the following ways (if it makes a difference)
bird = pygame.image.load("bird.png").convert_alpha()
pipeTop = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,0),(30,height)))
pipeBottom = pygame.draw.rect(screen, (0,200,30), Rect((scrollx,900),(30,-bheight)))
An image by itself does not have a position. You cannot test collision between a rect and something that is not placed in the world. I would recommend to create a class Bird along with a class Pipe that will both subclass pygame.Sprite.
Pygame already has collision detection built in.
A short example
bird = Bird()
pipes = pygame.Group()
pipes.add(pipeTop)
pipes.add(pipeBottom)
while True:
if pygame.sprite.spritecollide(bird,pipes):
print "Game Over"
EDIT:
Don't be afraid of classes, you will have to use them anyways sooner or later.
If you really don't want to use sprites, you can use the birds rect and the pipe and call collide_rect to check if they overlap.
EDIT2:
an example Bird class modified from pygame docs
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("bird.png").convert_alpha()
# Fetch the rectangle object that has the dimensions of the image
# Update the position of this object by setting the values of rect.x and rect.y
self.rect = self.image.get_rect()
You could then add methods such as move, which will move the bird down with the force of gravity.
The same would apply for the Pipe but instead of loading an image, you can create an empty Surface, and fill it with a color.
image = pygame.Surface(width,height)
image.fill((0,200,30)
You can just get the x and y values and compare them:
if pipe.x < bird.x < pipe.x+pipe.width:
#collision code
pass

Loading and blitting images per frame?

I have a simple top down vertical shooter a la Galaga that I'm messing around with. However, after looking through the documentation I've become a bit confused on how to efficiently load images, and whether to blit them every frame or not. All of the images are loaded through a few classes which inherit the pygame sprite class. At the moment, I'm loading the image as a class level attribute as such:
class Laser(pygame.sprite.Sprite):
image = None
def __init__(self, start_x, start_y):
pygame.sprite.Sprite.__init__(self)
self.pos_x = start_x
self.pos_y = start_y
if Laser.image is None:
Laser.image = pygame.image.load('img/laser_single.png')
self.image = Laser.image
self.rect = self.image.get_rect()
self.rect.topleft = [self.pos_x, self.pos_y]
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
The second issue stems from blitting all of the active sprites onto the pygame surface. At the moment I loop through a list of Laser(), Enemy(), and whatnot objects and blit each one individually before calling pygame.display.update(). It seems redundant to have to blit each object individually, so I'm asking whether or not this is the most efficient method pygame implements. Or, is there a way to blit every object at once and see some sort of performance improvement?
I'm hoping this prevents Python from loading a new instance of the image into memory every time I create a new Laser(). But will this work as I anticipate?
Yes. Not only does this save memory, since every instance will just have a reference to the class variable, it will also increase performance because the image is only loaded once.
The second issue ssems from blitting all of the active sprites onto the pygame surface.
This depends. If your framerate is OK, then stick with it. If your framerate starts dropping, have a look at DirtySprite, and maybe this article.
Another python class that comes in handy is the LayeredDirty sprite group, which will automatically handle dirty sprites and updating only the relevant screen parts instead of the entire screen for you.
It seems redundant to have to blit each object individually
With sprite groups, as e.g. the LayeredDirty or the simple Group, you add your sprites to the group once and just call the draw method of the sprite group.
sprites = pygame.sprite.LayeredDirty((sprite1, sprite2, sprite3))
sprites.add(sprite4)
...
sprites.draw(pygame_screen)
Using sprite groups will also enable you to do simple collision detection between groups using pygame.sprite.groupcollide and pygame.sprite.spritecollideany

What is the correct sequence for bliting surfaces to the screen in pygame?

I am creating a simple mp3 player and my first task was to create a simple button that a user could press. I created a class called Button which handled this behavior and detects if a user has clicked it and then changes color. I am now trying to have a default text that the button displays and another string (pres_string) which will be displayed if the button is being pressed.
The only problem is my background surface seems to be in the wrong place and is drawing over any changes I have made.
Here is my code:
http://pastebin.com/Nh3yy01X
As you can see I've commented out the lines I described and tried it with basic variables in the main function just to test what was going wrong.
Thanks for any help.
(Feel free to change the title of the question, I wasn't sure what most accuratelydescribed my problem)
Clear the surface every loop
def draw(self):
# clear screen."
self.screen.fill( self.color_bg )
# first draw background
# Then buttons
# then any extra top level text
# update
pygame.display.flip()
tip: For colors, you can call pygame.Color() with human-names like red ( gray20 and gray80 have a nice contrast, to use for bg and text. )
from pygame import Color
text = Color('gray20')
Your button, psuedocode. Fix: moved color as an instance member.
class Button(object):
def __init__(self, text, rect=None):
self.color_bg = Color("gray20")
self.color_text = color("gray80")
if rect is None: rect = Rect(0,0,1,1)
self.rect = rect
self._render()
def _render(self):
# draw button and .render() font, cache to surface for later.
self.surface_cached = self.surface.copy()
# render text
#if size changes, save rect.size of cached surface , mantaining location
self.rect.size = cache.get_rect.size
def draw(self):
# draw cached surface
screen.blit( self.surface_cached, self.rect)
For testClick use Rect.collidepoint http://www.pygame.org/docs/ref/rect.html#Rect.collidepoint
2D bitmap-based computer graphics are like drawing or painting - you put the new ink on top of whatever was there already. So your background must be the first thing you draw each time.

Categories

Resources