I'm making the program, that blits the 2D array as 2D map of tiles. The self.tile_size variable is used to scale the tile image that will be blit.
I want the Q to make it smaller, and E to make it bigger. The problem is, that output in in pygame window is not what i wanted. The image in link shows what happened, when i ran a program and tried to make image bigger. Image of output
Here's my code:
import pygame
from pygame.locals import *
pygame.init()
import math, sys, os, random, time
from a_star import *
tiles_list = [
pygame.image.load('Floor.png'),
pygame.image.load('Wall.png'),
]
PLAN_W = 20
PLAN_H = 20
PLAN_STATUS = [[True for i in range(PLAN_W)]for j in range(PLAN_H)]
OBSTACLES = [
[0,0],
[0,1],
[0,2]]
obsta(OBSTACLES, PLAN_STATUS) # function imported from a_star that change PLAN_STATUS cells from OBSTACLES to False
class World():
def __init__(self):
self.screen_width = 800
self.screen_height = 600
self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
self.clock = pygame.time.Clock()
self.bg_color = (255,255,255)
self.tile_size = 48
self.map_status = PLAN_STATUS
self.tiles_list = tiles_list
self.scale_change = 0
self.run()
def scale_tiles(self):
print(self.tile_size)
self.new_list = []
for i in self.tiles_list:
i = pygame.transform.scale(i, (self.tile_size, self.tile_size))
self.new_list.append(i)
self.tiles_list = self.new_list
def render_map(self):
print(self.tile_size)
'''
self.screen.fill(self.bg_color)
for x in range(PLAN_H):
for y in range(PLAN_W):
if self.map_status[y][x]:
tile = self.tiles_list[0]
elif not self.map_status[y][x]:
tile = self.tiles_list[1]
self.screen.blit(tile, (x*self.tile_size, y*self.tile_size))
'''
self.screen.blit(self.tiles_list[0], (400, 300))
def run(self):
while True:
self.events = pygame.event.get()
self.keys = pygame.key.get_pressed()
self.mouse_pos = pygame.mouse.get_pos()
self.mouse_but = pygame.mouse.get_pressed()
#UPDATE
self.screen.fill(self.bg_color)
#self.scale_tiles()
self.render_map()
pygame.display.flip()
#EVENTS
if self.keys[ord('e')] == 1:
self.tile_size += 1
self.scale_tiles()
if self.keys[ord('q')] == 1:
self.tile_size -= 1
self.scale_tiles()
'''for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
print('q')
self.scale_change = -1
if event.key == pygame.K_e:
print('e')
self.scale_change = 1
if event.type == pygame.KEYUP:
if event.key == pygame.K_q or event.key == pygame.K_e:
print('stopped key')
self.scale_change = 0'''
self.clock.tick(50)
if __name__=="__main__":
main = World()
Where's the problem in my code?
Thank you for the help
Floor.png
Use the images/surfaces in the original, global tiles_list to create the new_list not the already scaled images in self.tiles_list, otherwise it seems to increase the impreciseness with every scaling.
for i in tiles_list: not for i in self.tiles_list:.
Related
I am trying to a blue window that is mostly made up of rectangles, which can be turned green when clicked by the mouse, but when I run the code all that I get is a black window, and I don't know why it happens. This is my code.
import pygame, sys
clock = pygame.time.Clock()
from pygame.locals import *
pygame.init()
pygame.display.set_caption("Worldbuilder")
global screen
screen = pygame.display.set_mode((600,600))
global click
click = False
class tile:
def __init__(self,x,y,tilesize):
global a
a = pygame.Rect(x,y,tilesize,tilesize)
def draw(self,type):
mx,my = pygame.mouse.get_pos()
if a.collidepoint((mx,my)):
if click:
if type == 0:
type = 1
else:
type = 0
if type == 0:
pygame.draw.rect(screen,(0,0,255),a)
if type == 1:
pygame.draw.rect(screen,(0,255,0),a)
global tilex, tiley
tilex = 0
tiley = 0
list = []
for i in range(1,12):
for i in range(1,12):
list.append(tile(tilex,tiley,50))
tilex += 50
tiley += 50
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
click = True
for i in list:
i.draw(0)
pygame.display.update()
clock.tick(60)
Read about Classes. a needs to be an attribute of the class tile instead of a global variable:
class tile:
def __init__(self,x,y,tilesize):
self.a = pygame.Rect(x,y,tilesize,tilesize)
# [...]
There are multiple problems when you generate the grid of tiles. This can be done much easier:
for i in range(12):
for j in range(12):
list.append(tile(i*50,j*50,50))
Complete example:
import pygame, sys
clock = pygame.time.Clock()
from pygame.locals import *
pygame.init()
pygame.display.set_caption("Worldbuilder")
global screen
screen = pygame.display.set_mode((600,600))
global click
click = False
class tile:
def __init__(self,x,y,tilesize):
self.a = pygame.Rect(x,y,tilesize,tilesize)
def draw(self,type):
mx,my = pygame.mouse.get_pos()
if self.a.collidepoint((mx,my)):
if click:
if type == 0:
type = 1
else:
type = 0
if type == 0:
pygame.draw.rect(screen,(0,0,255),self.a)
if type == 1:
pygame.draw.rect(screen,(0,255,0),self.a)
tilex = 0
tiley = 0
list = []
for i in range(12):
for j in range(12):
list.append(tile(i*50,j*50,50))
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
click = True
for i in list:
i.draw(0)
pygame.display.update()
clock.tick(60)
import pygame
from sys import exit
import random
WIDTH, HEIGHT = 1400,750
FPS = 60
HEADWAY_GAP = 40
vehicleColor = {0:"red",1:"blue",2:"yellow"}
directions = ["Right", "Left"]
classofVehicle = ["Car","Bike","Bus"]
coord = {"Right":(0,300), "Left":(1400,350)}
objects = {"Right":[],"Left":[]}
pygame.init()
myWindow = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
road = pygame.image.load("Required_images/Road/Road_final.png")
class Vehicle(pygame.sprite.Sprite):
def __init__(self, ClassofVehicle, Color_Code, direction, pos):
super().__init__()
self.Class = ClassofVehicle
self.Color_Code = Color_Code
self.direction = direction
self.pos = pos
path = "Required_images/"+ClassofVehicle+"/"+ClassofVehicle+"_"+Color_Code+".png"
self.image = pygame.image.load(path)
self.rect = self.image.get_rect()
if self.direction == "Right":
self.rect.midright = pos
else:
self.rect.midleft = pos
self.index = len(objects[direction])-1
def movingVehicles(self):
if self.direction == "Right":
if self.index == 0 or (self.rect.x + self.rect.width < objects[self.direction][self.index].rect.x - HEADWAY_GAP):
self.rect.x += 1
elif self.direction == "Left":
if self.index == 0 or (self.rect.x - self.rect.width > objects[self.direction][self.index].rect.x + HEADWAY_GAP):
self.rect.x -= 1
def update(self):
self.movingVehicles()
Object_timer = pygame.USEREVENT+1
pygame.time.set_timer(Object_timer, 1000)
objectsGroup = pygame.sprite.Group()
def generatingObjects():
Dir = random.choice(directions)
po = coord[Dir]
color_code = random.choice([0,1,2])
cla = random.choice(classofVehicle)
object = Vehicle(cla,vehicleColor[color_code],Dir,po)
objectsGroup.add(object)
objects[Dir].append(object)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == Object_timer:
generatingObjects()
myWindow.fill((0,0,0))
objectsGroup.update()
objectsGroup.draw(myWindow)
pygame.display.update()
clock.tick(FPS)
I have created a sprite class and a sprite group. I also created an user defined event to generate vehicles for every 1000ms. To display the vehicles on the screen instead of looping through all the elements of the sprite group separately I used group_name.draw() method. But when I run the code, some of the images are getting stuck on the screen for some time and are moving after sometime. I tried to look for any error in the logic but I couldn't find it. If you have any knowledge or able to find the error, please help and also any kind of suggestions are appreciated.
It is a matter of Indentation. You have to draw the scene in the application loop instead of after the application loop:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == Object_timer:
generatingObjects()
# INDENTATION
#->|
myWindow.fill((0,0,0))
objectsGroup.update()
objectsGroup.draw(myWindow)
pygame.display.update()
clock.tick(FPS)
make sure to indent the elements in your while loop and use a pygame.display.flip() at the very end of your loop.
so it looks like
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == Object_timer:
generatingObjects()
myWindow.fill((0,0,0))
objectsGroup.update()
objectsGroup.draw(myWindow)
clock.tick(FPS)
pygame.display.flip()
I was working with some projectiles with pygame and found out that even with just 200 lines of code, the game ran with under 50 fps. (There isn't a big loop, except the running loop, and my PC is fairly new)
So, is this because pygame uses SDL?
If so, would using GPU like OpenGL improve the performance?
Main.py
#Eemport
import pygame as pyg,sys,background, player, wall
from pygame.locals import *
#Screen
screen_size_width = 1280
screen_size_height = 720
screen_size = (screen_size_width,screen_size_height)
#Initialization
pyg.init()
screen = pyg.display.set_mode(screen_size)
pyg.display.set_caption("Collision and better physics")
#Set clock
Clock = pyg.time.Clock()
#Set Background
Background = background.background("beach.jpg",screen_size)
#Set Player
player_size_width = 128
player_size_height = 128
player_location_x = 640
player_location_y = 360
player_size = (player_size_width,player_size_height)
player_location = (player_location_x,player_location_y)
player_speed = 5
Player = player.player("crab.png",player_size,player_location)
#Set input
keys = {'right': False, 'left': False, 'up': False, 'down': False}
nextlocation = [0,0]
#make wall
walls = []
walls.append(wall.wall((600,100),(340,600)))
#Running loop
running = True
while running:
#Read input
for event in pyg.event.get():
if event.type == QUIT:
pyg.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pyg.quit()
sys.exit()
if event.key == K_UP:
keys['up'] = True
if event.key == K_DOWN:
keys['down'] = True
if event.key == K_LEFT:
keys['left'] = True
if event.key == K_RIGHT:
keys['right'] = True
if event.type == KEYUP:
if event.key == K_UP:
keys['up'] = False
if event.key == K_DOWN:
keys['down'] = False
if event.key == K_LEFT:
keys['left'] = False
if event.key == K_RIGHT:
keys['right'] = False
#Update values
#1. Player Value
if keys['right']:
Player.move(player_speed,0,walls)
if keys['left']:
Player.move(-player_speed,0,walls)
if keys['up']:
Player.move(0,player_speed,walls)
if keys['down']:
Player.move(0,-player_speed,walls)
#Draw on screen Be aware of priority!!!
Background.draw_image(screen)
for wall in walls:
pyg.draw.rect(screen, (255,255,255),wall.rect)
Player.draw_image(screen)
Clock.tick()
print(Clock.get_fps(),Player.rect.x,Player.rect.y)
#Update display
pyg.display.update()
Wall.py
import pygame as pyg
from pygame.locals import *
class wall(object):
"""
This class represents walls
No action
"""
def __init__(self,size,location):
self.width = size[0]
self.height = size[1]
self.locationx = location[0]
self.locationy = location[1]
self.rect = pyg.Rect(self.locationx,self.locationy,self.width,self.height)
background.py
import pygame as pyg
from pygame.locals import *
class background(object):
"""
This class represents the background
action - draw
"""
def __init__(self,image_file,screen_size):
self.screen_size = screen_size
self.image = pyg.image.load(image_file)
self.image = pyg.transform.scale(self.image,self.screen_size)
self.rect = pyg.Rect((0,0),self.screen_size)
def draw_image(self,screen):
screen.blit(self.image,self.rect)
def load_new_image(self,image_file):
self.image = pyg.image.load(image_file)
player.py
import pygame as pyg
from pygame.locals import *
class player(object):
"""
This class represents the player
contains actions for the player
"""
def __init__(self,image_file,image_size,location):
#image
self.image = pyg.image.load(image_file)
self.image = pyg.transform.scale(self.image,image_size)
self.image_size = image_size
#Rect
self.rect = pyg.Rect(location,image_size)
def move(self, dx, dy,walls):
self.rect.x += dx
collide_wall = self.rect.collidelist(walls)
if collide_wall != -1:
if dx > 0:
self.rect.right = walls[collide_wall].rect.left
else:
self.rect.left = walls[collide_wall].rect.right
self.rect.y -= dy
collide_wall = self.rect.collidelist(walls)
if collide_wall != -1:
if dy > 0:
self.rect.bottom = walls[collide_wall].rect.top
else:
self.rect.top = walls[collide_wall].rect.bottom
def draw_image(self,screen): #Draw image on screen
screen.blit(self.image,self.rect)
def load_new_image(self,image_file): #loads new image
self.image = pyg.image.load(image_file)
self.image = pyg.transform.scale(self.image, self.image_size)
def current_location(self):
return (self.rect.x , self.rect.y)
Images/pygame.Surfaces should usually be converted with the convert or convert_alpha methods of the pygame.Surface class. This will improve the performance dramatically.
IMAGE = pygame.image.load('an_image.png').convert()
IMAGE2 = pygame.image.load('an_image_with_transparency.png').convert_alpha()
Also, load your images only once when the program starts and reuse them in your program. Loading them from the hard disk with pygame.image.load again is slow and should be avoided.
If you need even more speed, you can use OpenGL in conjunction with pygame, but that means rewriting your rendering code and of course you would have to learn OpenGL first.
Alternatively, you could check out some of the other game frameworks for Python.
My code isn't working and I don't understand it why, I'm working with module PYGAME and I want do a animation (three images) when I push the key "s". My code is this:
import pygame, sys
from pygame.locals import *
from time import *
pygame.init()
ventana = pygame.display.set_mode((400,300))
pygame.display.set_caption("Hi!")
imgA = pygame.image.load("IMG/fan_azul.png")
imgB = pygame.image.load("IMG/fan_naranja.png")
imgC = pygame.image.load("IMG/fan_rojo.png")
listaImg = [imgA,imgB,imgC]
POS,aux,e = 0,1,0
picture = listaImg[POS]
posX,posY,fondo = 200,100,(50,50,50)
while True:
ventana.fill(fondo)
#ventana.blit(picture,(posX,posY))
for evento in pygame.event.get():
if evento.type == QUIT:
pygame.quit()
sys.exit()
elif evento.type == KEYDOWN:
if evento.key == K_s:
for e in range(0,3):
POS = e
picture = listaImg[POS]
ventana.blit(picture,(posX,posY))
print "TIEMPO MEDIDO: "+str(POS)
pygame.time.wait(1000)
pygame.display.update()
It is important to avoid inner loops that are going to hang up processing events and other drawing to the frame. Remember you are usually trying to get 30 to 60 frames per second, and your K_s handler is taking 3 seconds by itself. Keep the handle events, update state, and draw screen parts all separate. To do an animation, I usually do something like this.
import pygame, sys
from pygame.locals import *
import time
pygame.init()
ventana = pygame.display.set_mode((400,300))
pygame.display.set_caption("Hi!")
FONDO = (50,50,50)
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, position, filenames):
pygame.sprite.Sprite.__init__(self)
self.images = [pygame.image.load(filename) for filename in filenames]
self.delay = 0
self.start = 0
self.rect = pygame.Rect(position, (0, 0))
self._set_image(0)
def _set_image(self, image_num):
self.image_num = image_num
self.image = self.images[self.image_num]
self.rect.size = self.image.get_rect().size
def show(self, group, delay=1.0):
group.add(self)
self.delay = delay
self._set_image(0)
self.start = time.time()
def update(self):
if self.alive() and time.time() - self.start > self.delay:
if self.image_num + 1 < len(self.images):
self._set_image(self.image_num + 1)
else:
self.kill()
fan = AnimatedSprite((200, 10), ["IMG/fan_azul.png",
"IMG/fan_naranja.png",
"IMG/fan_rojo.png"])
animation = pygame.sprite.Group()
while True:
# Handle Events
for evento in pygame.event.get():
if evento.type == QUIT:
pygame.quit()
sys.exit()
elif evento.type == KEYDOWN:
if evento.key == K_s:
fan.show(animation)
# Update State
animation.update()
# Draw
ventana.fill(FONDO)
animation.draw(ventana)
pygame.display.update()
Currently, I have small image that I move using the D and A keys on the keyboard. The code works just how I want it to, but it seems to be a bit unnecessarily complicated. Is there a more efficient way of doing this?
In the main loop, my code checks to see what key is pressed through the events, but after that, if a key is no longer pressed, it checks to see if another key is pressed, just in case a user pressed a key while another key was initially pressed.
Here is my code:
import pygame
import sys
from pygame.locals import *
SCREENX = 640
SCREENY = 480
LEFT = 'left'
RIGHT = 'right'
class Character(pygame.sprite.Sprite):
def __init__(self, image_file):
super().__init__()
temp_image = pygame.image.load(image_file)
self.image = pygame.transform.scale(temp_image, (100, 100))
self.rect = self.image.get_rect()
self.moving = False
self.direction = RIGHT
def start_move(self, direction):
self.moving = True
if direction != self.direction:
self.image = pygame.transform.flip(self.image, True, False)
self.direction = direction
def stop_move(self):
if self.moving:
self.moving = False
def move(self):
if self.direction == RIGHT:
self.rect.x += 5
if self.direction == LEFT:
self.rect.x -= 5
def update(self):
if self.moving:
self.move()
def main():
pygame.init()
surface = pygame.display.set_mode((SCREENX, SCREENY))
clock = pygame.time.Clock()
sprites = pygame.sprite.Group()
girl = Character("assets/girl.png")
girl.rect.x = SCREENX/2
girl.rect.y = SCREENY - girl.rect.height
sprites.add(girl)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if not girl.moving:
if event.key == K_d:
girl.start_move(RIGHT)
if event.key == K_a:
girl.start_move(LEFT)
if event.type == KEYUP:
if event.key == K_a or event.key == K_d:
girl.stop_move()
keys_pressed = pygame.key.get_pressed()
if keys_pressed[K_d]:
girl.start_move(RIGHT)
if keys_pressed[K_a]:
girl.start_move(LEFT)
surface.fill((255, 255, 255))
sprites.update()
sprites.draw(surface)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
Yes, you can get rid of most of the event handling, you can generalize your main loop, and you can get rid of some fields of your Character class.
See my explanatory notes in the comments:
import pygame
import sys
from pygame.locals import *
SCREENX = 640
SCREENY = 480
class Character(pygame.sprite.Sprite):
def __init__(self, image_file):
pygame.sprite.Sprite.__init__(self)
temp_image = pygame.image.load(image_file)
# only to the flipping of the image once
self.image_r = pygame.transform.scale(temp_image, (100, 100))
self.image_l = pygame.transform.flip(self.image_r, True, False)
self.image = self.image_l
self.rect = self.image.get_rect()
def update(self, pressed_keys):
move = 0
if pressed_keys[K_d]: move += 1
if pressed_keys[K_a]: move -= 1
self.rect.move_ip(move*5, 0)
# check which direction we're facing and set the image
self.image = self.image_l if move < 0 else self.image_r
def main():
pygame.init()
surface = pygame.display.set_mode((SCREENX, SCREENY))
clock = pygame.time.Clock()
sprites = pygame.sprite.Group()
girl = Character("assets/girl.png")
girl.rect.x = SCREENX / 2
girl.rect.y = SCREENY - girl.rect.height
sprites.add(girl)
while True:
for event in pygame.event.get():
# since we are in a function, we can simply return.
# we don't care here what happens after we quit the main loop
if event.type == QUIT: return pygame.quit()
# get all pressed keys, and just let all sprites
# decide what they want to do with it
keys_pressed = pygame.key.get_pressed()
# you could wrap this information in a more
# general container represeting the game state
# See how the game loop does not care what a
# Sprite does with this information
sprites.update(keys_pressed)
surface.fill((255, 255, 255))
sprites.draw(surface)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()