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.
Related
Im following a tutorial to make a game working with some pygame tiled and pytmx stuff, but however, we added a new object´s layer with some empty rectangles over the tiles where the player is not being able to enter, then we maked a wall´s list to put inside this "walls" and then we maded a couple of functions to check if the player is over the collision obstacle, and if that´s the case move the player one step back, but when I run the game, it opens the map and I can actually move my player around, but when I test the obstacle surface and collisions, it´s not working, and the player can go over the "walls". could you please help me to solve this, here I leave my wall´s code (inside of my Game Class):
def update(self):
self.group.update()
#verification du collision
for sprite in self.group.sprites():
if sprite.feet.collidelist(self.walls) > - 1:
sprite.move_back()
def run(self):
clock = pygame.time.Clock()
#boucle du jeu
running = True
while running:
self.player.save_location()
self.handle_input()
self.update()
self.group.center(self.player.rect)
self.group.draw(self.screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
clock.tick(60)
pygame.quit()
But... on the tutorial in Tiled software on the Obstacle information, there´s an option to put the "type" of object, at least that says on the tutorial, but in my Tiled, says "Class" instead of " Type" I think is the same, but however, I tried to put Class instead of Type in collision code, but... you know that Class is a reserved word by python, so... I put back " Type", I´m not sure if this sort of information matters, but maybe yes,
in my tmx_map, on the obstacles layer I already set the "class" ("type object") of each object as "Collision"
anyway, here´s the Player´s code
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.sprite_sheet = pygame.image.load('player.png')
self.image = self.get_image(0,0)
self.image.set_colorkey([0,0,0])
self.rect = self.image.get_rect()
self.position = [x,y]
self.images = {
'down':self.get_image(0,0),
'left' :self.get_image(0,32),
'right':self.get_image(0,64),
'up' :self.get_image(0,96),
}
self.feet = pygame.Rect(0,0, self.rect.width * 0.5, 12)
self.old_position = self.position.copy()
self.speed = 2
def save_location(self): self.old_position = self.position.copy()
def update(self):
self.rect.topleft = self.position
self.feet.midbottom = self.rect.midbottom
def move_back(self):
self.position = self.old_position
self.rect.topleft = self.position
self.feet.midbottom = self.rect.midbottom
thanks in advance
Since Tiled 1.9, "type" was renamed to "class", for consistency between all different kinds of objects, which can now all have a custom class.
Likely pytmx has not been updated yet with this change. Fortunately, since this change affected compatibility, Tiled provides an option to use the "Tiled 1.8" format, in which case the class is still written out as "type".
You can change the "Compatibility version" option in the Project Properties (you need to create a Tiled project to set this option).
I am creating a tile-based 2d overworld for a game - heavily influenced by Pokemon - using pygame/python, Tiled for .tmx files, and the tmx library by Richard Jones. The code I'm using is mostly based on this wonderful demo of Pallet Town in python.
The game runs just fine, however, I am having problems with making tiles on the map (e.g. houses, trees) overlap the player sprite when it would make sense for the player sprite to disappear behind them. For example: in the image here, principles of depth perception would tell us that the house in the foreground should occlude the player in the background, but because the map is 2D there is no depth and therefore no occlusion. I would love to add depth, but seeing as I'm very new to pygame (and python in general), I'm at a loss at how to draw the relevant foreground objects over the sprite.
Luckily I'm not alone in this problem and plenty of documentation on possible solutions exist. For example:
this StackExchange question
this LibGDX tutorial
this Unity tutorial
However, this code isn't typically written for python and I'm not sure how to implement it in my situation. Sorting/drawing by z position (or by a 'depth' property) seems like the most sensible thing to do, but looking at the tmx library I can only find x and y values mentioned. Adding the player sprite to an empty object layer in Tiled is also a solution, but once again I'm unsure of how to do this and all my attempts have led to error messages. (Attempts not detailed here because I honestly don't know what I did and it didn't work anyway.)
My current code is as follows:
class Player(pygame.sprite.Sprite):
def __init__(self, location, collStart, orientation, *groups):
super(Player, self).__init__(*groups)
self.image = pygame.image.load('sprites/player.png')
self.imageDefault = self.image.copy()
self.rect = pygame.Rect(location, (26,26))
self.collider = pygame.Rect(collStart, (13,13))
self.orient = orientation
self.holdTime = 0
self.walking = False
self.dx = 0
self.step = 'rightFoot'
# Set default orientation
self.setSprite()
self.speed = pygame.time.get_ticks() + 50 # slows down walking speed
by .5 sec (current time + 50 ms)
def setSprite(self):
# this function contains information about where to find which sprite
in the sprite sheet, probably not relevant here.
def update(self, dt, game):
key = pygame.key.get_pressed()
if pygame.time.get_ticks() >= self.speed:
self.speed = pygame.time.get_ticks() + 50
# Setting orientation and sprite based on key input, removed the
#code here because it wasn't relevant
#[....]
# Walking mode enabled if a button is held for 0.1 seconds
if self.holdTime >= 100:
self.walking = True
lastRect = self.rect.copy()
lastColl = self.collider.copy() # collider covers the bottom section of the sprite
# Code for walking in the direction the player is facing, not relevant here
#[....]
# Collision detection:
# Reset to the previous rectangle if player collides
# with anything in the foreground layer
if len(game.tilemap.layers['triggers'].collide(self.collider,
'solid')) > 0:
self.rect = lastRect
self.collider = lastColl
# Area entry detection, loads dialog screen from the dialog file:
elif len(game.tilemap.layers['triggers'].collide(self.collider,
'entry')) > 0:
entryCell = game.tilemap.layers['triggers'].find('entry')[0]
game.fadeOut()
run()
pygame.quit()
quit()
return
if self.dx == 16:
# Makes the player appear to take steps w/ different feet, not relevant here
#[....]
# After traversing 32 pixels, the walking animation is done
if self.dx == 32:
self.walking = False
self.setSprite()
self.dx = 0
game.tilemap.set_focus(self.rect.x, self.rect.y)
class Game(object):
def __init__(self, screen):
self.screen = screen
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# In case there is no sprite layer for the current map
except KeyError:
pass
else:
self.tilemap.layers.append(self.objects)
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px,
startCell.bottom-4),
startCell['playerStart'], self.players)
self.tilemap.layers.append(self.players)
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
def main(self):
clock = pygame.time.Clock()
self.initArea('test tilemap.tmx')
while 1:
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
self.tilemap.update(dt, self)
screen.fill((0,0,0))
self.tilemap.draw(self.screen)
pygame.display.flip()
Once again, I'm using the tmx library found here. Maybe something needs to be changed there? Hopefully, someone can help me figure this out. It's definitely possible, as shown by this pokemon clone in python (no source code available, sadly).
Also, first-time StackOverflow user so let me know if I'm committing any faux-passes :)
Figured it out! As suggested by Kingsley in the comments, the solution was to change the draw order of the layers. Layers were being drawn in order of a list in the Layers class, with the player sprite having the highest index and thus being drawn on top of everything else. Moving the player between the background and foreground layer in the list made it appear behind the foreground objects.
To do this I added the following code to the initArea function in the Game class:
def initArea(self, mapFile):
"""Load maps and initialize sprite layers for each new area"""
self.tilemap = tmx.load(mapFile, screen.get_size())
self.players = tmx.SpriteLayer()
self.objects = tmx.SpriteLayer()
# Initializing player sprite
startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
self.player = Player((startCell.px, startCell.py), (startCell.px, startCell.bottom-4),
startCell['playerStart'], self.players)
foregroundItem = self.tilemap.layers.__getitem__("foreground") # finds the layer called foreground
foregroundIndex = self.tilemap.layers.index(foregroundItem) # finds the position of the foreground layer in the Layers list (Layers class specified in .tmx file)
self.tilemap.layers.insert(foregroundIndex-1, self.players) # move the Player layer one layer below the foreground layer
self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)
I'll experiment a bit more tonight, but for now this solution seems to work. Thanks for the help!
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.
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()
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.