I am new to pygame and have been having some problems with a sprite flickering/dragging it's image when it moves across the screen. I have read other answers on here about getting a certain pix/s, where you should change the framerate and lower pix/frame, but that solution hasn't helped so I was wondering if I'm just not implementing it correctly and that's the only solution or if I am missing something. Below is some the code for the game that runs at a constant framerate. Thank you!
pygame.init()
display=pygame.display.set_mode((screen_width,screen_height), pygame.RESIZABLE)
background=pygame.Surface((screen_width, screen_height))
background.fill(background_color)
class Ship(pygame.sprite.Sprite):
def __init__(self, image_file):
pygame.sprite.Sprite.__init__(self)
self.original_image_unscaled=pygame.image.load(image_file)
self.original_image=pygame.transform.smoothscale(self.original_image_unscaled, (56,70))
self.image=self.original_image
self.rect=self.image.get_rect()
self.xpos=0
self.ypos=0
self.rect.centerx=self.xpos
self.rect.centery=self.ypos
self.vel=0
self.acc=.05
self.brake=.2
self.turn_brake=1.5
self.angle=0
self.max_speed=5
def getShipPos(self):
return (self.xpos, self.ypos)
def getShipVel(self):
return self.vel
def getShipAcc(self):
return self.acc
def getShipRect(self):
return self.rect
def update(self):
self.rect.centerx=self.xpos
self.rect.centery=self.ypos
def rotateShip(self, angle):
#this rotates the ship by a specified angle
self.image, self.rect = pygameRotateImage2(self.original_image, self.rect, angle-self.angle)
self.angle+=angle
def simpleMove(self, x_final, y_final):
self.xpos=x_final
self.ypos=y_final
def moveShip(self):
keys=pygame.key.get_pressed()
xpos_i=self.xpos
ypos_i=self.ypos
if keys[K_w]:
if self.vel>self.max_speed:
self.vel=self.max_speed
else:
self.vel+=self.acc
elif keys[K_s]:
self.vel-=self.brake
if self.vel<0:
self.vel=0
if keys[K_a]:
self.rotateShip(-self.turn_brake)
elif keys[K_d]:
self.rotateShip(self.turn_brake)
self.xpos+=self.vel*math.sin(math.radians(self.angle))
self.ypos+= -self.vel*math.cos(math.radians(self.angle))
display.blit(background, (0,0))
pygame.display.update()
player_ship=Ship('image.png')
player_ship.simpleMove(screen_width/2, screen_height/2)
movement_group=pygame.sprite.Group()
movement_group.add(player_ship)
clock=pygame.time.Clock()
while True:
clock.tick(60)
(mousex,mousey)=pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
sys.exit()
player_ship.moveShip()
movement_group.update()
display.blit(background, (0,0))
movement_group.draw(display)
pygame.display.update()
Double buffering will probably help at least some. It is a technique used in computer graphics to reduce the number of times a pixel changes. It can be implemented as:
pygame.display.set_mode(dimensions, pygame.RESIZEABLE|pygame.DOUBLEBUF)
Another option is to move the clock.tick from the beginning of the loop to the end of the loop. That is where I usually see it, and it could alter the outcome of the graphics.
Try changing the line pygame.display.update() to pygame.display.flip().
I had the same problem in the past.
I now usually create a method in the classes that need to be displayed on screen which blit the picture (of the class itself) to the screen.
I than call that method before the screen update.
For example, this is a method of the class "Tank", which actually build the tank used by the player:
def master_player(self): #calculate and draw the player sprite
self.posix() # call of a method which calculate the position
self.image_tank.set_colorkey(PURPLE)
screen.blit(player.image_tank, player.position_tuple)
As I said before, I call this method at the end of the game loop and before the screen update.
Try moving the player_ship.moveShip() right under the pygame.display.update()
Like this:
player_ship.moveShip()
movement_group.update()
display.blit(background, (0,0))
movement_group.draw(display)
pygame.display.update()
Related
I created a class that has a function to draw a surface in screen now i want to append it in a list and draw it multiple times but also delete it from list after it has crossed screen
import pygame
pygame.init()
screen=pygame.display.set_mode((1000,600))
obs_list=[]
class obstacle():
def __init__(self):
self.surface=pygame.surface.Surface((50,50))
self.rect=self.surface.get_rect(center=(500,300))
def draw(self,screen):
screen.blit(self.surface,self.rect)
self.move()
def move(self):
self.rect.centerx+=2
def remove(self):
if self.rect.centerx>=800:
obs_list.remove(self)
while True:
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
screen.fill('white')
obs_list.append(obstacle())
for obs in obs_list:
obs.draw(screen)
pygame.display.flip()
i did this but it just kinda like drags the square in the screen leaving trail behind i don't know what is happening and if i am doing it wrong way or cant do what i am trying to do without using sprite
Since the ostacle is append to obs_list in the applicaition loop, the list will keep growing. And all obstacles from all past frames in the list are drawn in each frame. You have to append the obstacle to the list before the application loop instead of in the application loop:
obs_list.append(obstacle()) # <--- insert
while True:
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
screen.fill('white')
#obs_list.append(obstacle()) <--- delete
for obs in obs_list:
obs.draw(screen)
pygame.display.flip()
I want to draw a box to my screen, however when I call the function to do so it says I'm missing the argument for "surface"
As seen in the code below, the function is in a class. The function contains two parameters: "self" and "surface", and I'm passing the variable "screen" in place of "surface" so I can draw the box:
import pygame
import time
pygame.init()
(width, height) = (600, 400)
bg_colour = (100, 20, 156)
class infoBox(object):
def __init__(self, surface):
self.box = pygame.draw.rect(surface, (255,255,255),(0,400,400,100),2)
def draw(self, surface):
surface.blit(self.box)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("battle EduGame")
clock = pygame.time.Clock()
pygame.display.flip()
gameRun = True
while gameRun:
event = pygame.event.poll()
if event.type == pygame.QUIT: #if the "x" is pressed
pygame.quit() #quit game
gameRun = False #break the loop.
quit()
screen.fill(bg_colour)
infoBox.draw(screen)
pygame.display.update()
clock.tick(60)
I've done things exactly like this in previous codes however it's choosing not to work here.
Note your calling technique:
class infoBox(object):
def draw(self, surface):
surface.blit(self.box)
...
infoBox.draw(screen)
draw is an instance method, but you've invoked it as a class method. Therefore, you have only one argument, screen. You haven't provided the required self instance ... which will be needed immediately.
You need to create the object and use that to invoke the routine, something like
game_box = InfoBox(screen)
...
game_box.draw(screen)
I'm making my own space invaders game and so far I've been able to move my ship around using the mouse. However, I still can't shoot. Here's my game loop.
def game_loop():
x=0
y=0
xlist=[]
ylist=[]
while True:
mouseclk=pygame.mouse.get_pressed()
game_display.fill(white)
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
quit()
x, y = pygame.mouse.get_pos()
xlist.append(x)
ylist.append(y)
if x>=display_width-40:
x=display_width-40
if y>=display_height-48:
y=display_height-48
if pygame.mouse.get_focused()==0:
game_display.blit(spaceship, (x, y))
elif pygame.mouse.get_focused()==1:
game_display.blit(spaceshipflames, (x, y))
pygame.display.update()
if pygame.mouse.get_focused()==0:
pause()
clock.tick(500)
I've tried using the following code inside my game loop:
if mouseclk[0]==1:
shoot.play()
while True:
pygame.draw.circle(game_display, white, (x+20, y-2), 5)
pygame.draw.circle(game_display, red, (x+20, y-7), 5)
y-=5
if y<=0:
break
pygame.display.update()
clock.tick(400)
But the end result is very glitchy and doesn't allow me to shoot multiple bullets without making the game choppy.
Is there a way to run both loops at the same time, or a completely different way to implement shooting?
I'd recommend making use of classes (especially for games) and splitting up your code into smaller functions.
When making a game, each class should represent some type of object in the game, for example a ship or a bullet here. Using classes should help with this problem of multiple bullets causes glitches.
Breaking into smaller functions will make your code much easier to read and update as it grows. Try to stick to the Single Responsibility Principle as much as possible.
How you might implement shooting with these things in mind:
bullets = []
class Bullet:
def __init__(self, position, speed):
self.position = position
self.speed = speed
def move(self):
self.position[1] += self.speed
class Ship:
def __init__(self, position, bullet_speed):
self.position = position
self.bullet_speed = bullet_speed
def shoot(self):
new_bullet = Bullet(self.position, self.bullet_speed)
bullets.append(new_bullet)
Where the position variables have the form [x,y]. Then to move your bullets forward, put this line somewhere in your main game loop:
for bullet in bullets:
bullet.move()
And loop over all the bullets and draw each to the screen to render them.
This isn't the most detailed example, but hopefully it's enough to get you going in the right direction.
So i have been creating a game with sprites and groups. Everything is going really well aside from that one thing that keeps bugging me. A picture says more than a thousand words:
http://i.imgur.com/S3fsone.png
As you can see, the background image is shown in the crosshair, just like it should. The ship, however, is getting cut off by rectangular white edges. Both the crosshair and ship are sprites, and the images used in them are transparent PNG images.
I have no idea how to remove this. Currently i am experimenting on the crosshair, so with that in mind, i'll post the relevant code:
#Ship class
class Ship(pygame.sprite.Sprite):
def __init__(self, position):
pygame.sprite.Sprite.__init__(self)
self.imageoriginal = pygame.image.load("ship.png")
self.image = self.imageoriginal
self.rect = self.image.get_rect()
self.rect.center = position
#Crosshair class
class Pointer(pygame.sprite.Sprite):
def __init__(self, image):
super(Pointer, self).__init__()
self.image = image.convert_alpha()
self.rect = self.image.get_rect()
def update(self):
self.rect.center = pygame.mouse.get_pos()
def main():
pygame.init()
#Window and background
window = pygame.display.set_mode((1000, 600), SRCALPHA)
background = pygame.image.load("background.png")
window.blit(background, (0, 0))
#Ship and ship group
ship = Ship((490, 500))
theship = pygame.sprite.Group(ship)
#Crosshair image and group
crosshairimage = pygame.image.load("images\\crosshair.png")
pointer = pygame.sprite.Group()
#Game loop
running = True
while running:
clock.tick(60)
#Cut out from my event loop. Creates the crosshair that follows the mouse
for e in pygame.event.get():
elif e.key == K_4:
pointer.add(Pointer(crosshairimage))
#Update and redraw methods for both
theship.clear(window, background)
theship.update(window)
theship.draw(window)
pointer.clear(window, background)
pointer.update()
pointer.draw(window)
pygame.display.flip()
And that should be it. The crosshair follows the mouse perfectly, and the non-red part of it is transparent on the background. It is not transparent over other sprites however, and that is the ghist of the problem. Other sprites moving over other sprites also cause this problem.
I can guess that it is the rect in the sprites that causes this, but i wouldn't know how to fix it. Tried using a mask but i kind of need the rects to be able to move my sprites don't i? Found no equal to the "self.rect.center = position" method in the mask class.
Much appreciated if anyone could tell me what i can do about this, it's been a headache for a long time now.
I think the problem is in your drawing section. Instead of having each group clear and draw separately, clear both groups, then update them, then draw them, so instead of:
#Update and redraw methods for both
theship.clear(window, background)
theship.update(window)
theship.draw(window)
pointer.clear(window, background)
pointer.update()
pointer.draw(window)
pygame.display.flip()
have:
#Update and redraw methods for both
theship.clear(window, background)
pointer.clear(window, background)
theship.update(window)
pointer.update()
theship.draw(window)
pointer.draw(window)
pygame.display.flip()
I hope this helps.
Unimportant Preamble:
Hello, I'm using Python and Pygame to create a game. This is for the purpose of improving my programming skills rather than a serious attempt at game creation. I've taken a break from Python lately for Objective C, but I'm now returning to it. This is a problem that I was having before I took a brief break, and I've returned to a question that was originally puzzling me. I've spent quite a while researching it, and I have a suspicion I've come across the solution and merely failed to understand it. I apologize for some of the bad naming conventions/lack of comments. I'm working on improving that.
Substance of Question:
Anyway, I've attached the four images I'm using. The program uses a simple function to position various Tiles on the screen. The mouse cursor is a sword. It is the entire image, but I'll be changing that later. I've made the program type "blue" in the shell whenever the cursor collides with a Tile. My goal is to have this happen when it collides with "ANY" tile of that color.
Long-term, I want to be able to modify the properties of these tile sprites. Various game-pieces would interact, and I would need to save the state of each sprite. I'd also be setting interactions for the other sprites.
Right now the sprites are all generating images, but my collision rectangle for the Tile is simply moving after each image is generated. I suppose that makes sense given the code, but I need a way to multiple sprites, each with a rectangle for collision.
Thanks
EDIT: I was unable to add images due to a new-user restriction. They are available enter link description here I think I read somewhere that people can (and do) edit posts here. So if anyone who the ability to move the images into this thread is welcome to do so.
import random,math,sys,os
import pygame
from pygame.locals import *
pygame.init() #Initializing Pygame
#Colors
black=(0,0,0)
#Screen
screen=pygame.display.set_mode((1200,800),0,0)
pygame.display.set_caption("Nero's Sandbox")
pygame.mouse.set_visible(False)
clock=pygame.time.Clock()
fps=40
#Game Functions:
def terminate():
pygame.quit()
sys.exit()
def numgen(x,y):
return random.randint(x,y)
#Loop Variables
tri=2
#Groups:
allsprites = pygame.sprite.Group()
alltiles = pygame.sprite.Group()
allmice = pygame.sprite.Group()
#Mouse Classes
class Pointy(pygame.sprite.DirtySprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('redsword.png').convert() #31x32 image
self.image.set_colorkey(black)
self.rect=self.image.get_rect()
self.set=pygame.sprite.Group()
self.add(allmice, allsprites, self.set)
pygame.sprite.RenderPlain((self.set,allmice,allsprites))
def update(self):
screen.fill(black)
alltiles.draw(screen)
if event.type == pygame.MOUSEMOTION:
pos = pygame.mouse.get_pos()
self.rect.topright = pos
self.set.draw(screen)
#Tile Sprites - only one rect is being recognized.
class Tile(pygame.sprite.Sprite):
def __init__(self, graphic):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(graphic).convert()
self.image = pygame.transform.scale((self.image),(50,50))
self.rect=self.image.get_rect()
self.add(alltiles, allsprites)
self.set=pygame.sprite.RenderPlain((self))
def update(self, x, y):
pos = (x,y)
self.rect.topleft = pos
#Micers
pointy1=Pointy()
#Game Loops
while True: #Ensures all loops within program are constantly called when conditions are met.
screen.fill(black)
while tri==2:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
pygame.display.flip()
x = 0
y = 50
w = 0
while x!=600:
x=x+50
w = w+1
if w%2==0:
purpletile1=Tile('purplesquare.png')
purpletile1.set.update(x,y)
purpletile1.set.draw(screen)
else:
c=numgen(1,2)
if c==1:
bluetile1=Tile('lightbluesquare.png')
bluetile1.set.update(x,y)
bluetile1.set.draw(screen)
if c==2:
redtile1=Tile('redsquare.png')
redtile1.set.update(x,y)
redtile1.set.draw(screen)
if x>=600 and y!=450:
if y<450:
x = 0
y = y+50
w=w-1
if y>=450:
tri=3
while tri==3:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
alltiles.draw(screen)
pointy1.set.update()
pointy1.set.draw(screen)
pygame.display.flip()
clock.tick(fps)
if pygame.sprite.collide_rect(pointy1,bluetile1):
print('blue')
I had this same problem myself! I did some debugging, and it appeared that all instances of my class shared the same instance of pygame.Rect()
You may want to change the line:
pygame.sprite.Sprite.__init__(self)
to
super.__init__(self)
This way, pygame.sprite.Sprite's init will set attributes to your tile. I could be wrong, I'm not entirely familiar with python's inheritance syntax, but that is the way I do it.
I could also be the get_rect that is causing the same rectangle to be used for all classes, but that doesn't seem to be likely.
I hope that I was some help, and just remember that pygame.Rect is an object, so somehow you are instantiating only once.