pygame.draw.rect on image - python

I am writing a python 3.7 pygame pacman clone.
Right now, I hard-coded the level in a 2d array for collision detection, and every frame I do:
screen.fill((0,0,0))
for x in range(GRID_W):
for y in range(GRID_H):
num=tiles[x][y]
if num is WALL or num is GHOST_HOUSE_BORDER:
pygame.draw.rect(screen,(255,0,255),[x*TILE_W,y*TILE_H,TILE_W,TILE_H])
This is really slow for some reason. I think that pygame draws the rects pixel-by-pixel in a 2d for loop, which would be very inneficient.
Is there a way to do this rendering before the main loop, so I just blit an image to the screen? Or is there a better way to do it?
My computer is a Macbook Pro:
Processor 2.9 GHz Intel Core i7
Memory 16 GB 2133 MHz LPDDR3
Graphics Radeon Pro 560 4096 MB, Intel HD Graphics 630 1536 MB
It can run intense OpenGL and OpenCL applications just fine, so pygame should not be a stretch.

The slowing down has nothing to do with the drawing being slow. It's actually because your map gets bigger.
In your file, you have several classes that have attributes speed (for example, line 192, your player has a self.speed). If you increase the size of your map without increasing your sprite's speeds, they will look like they are moving slower. They are actually moving the exact same speed, just not the same speed relative to the map.
If you want your game to be able to scale the size display screen, you also need to scale everything based on that same scaling factor. Otherwise, increasing/decreasing the size of your game will also affect all the interactions in your game (moving, jumping, etc... depending on the game).
I'd recommend putting a SCALE constant at the top of your file and multiplying all of your sizing and moving things by it. That way the game still feels the same no matter what size you want to play on.

Related

Koch snowflake rendering time (and how to draw a snowflake using turtle)

I'm currently working through the online course material for the MIT 6.006 course for fun. I'm on problem set #2 (found here) and had a question about the calculations for the asymptotic rendering time for the koch snowflake problem (problem #1).
According to the solutions, when the CPU is responsible for the rendering and the calculation of coordinates, the asymptotic rendering time is faster than if the process is split up between the CPU and GPU. The math makes sense to me, but does anyone have an intuition about why this is true?
In my mind, the CPU still has to calculate the coordinates to render the snowflake (Theta(4^n) time), and then has to render the image. In my mind, these should be additive, not multiplicative.
However, the solutions state these are multiplicative, so since each triangle/line segment is shorter (for the last two subproblems in problem 1) the runtime is reduced to either Theta((4/3)^n) or Theta(1)!
I'm not a computer scientist--this stuff is just a fun hobby for me. I'd really appreciate an answer from one of you geniuses out there :)
Also, some fun I had while playing with the python turtle module. Heres some very imperfect code to draw a koch snowflake in python:
import turtle
def snowflake(n,size=200):
try: turtle.clear()
except: pass
turtle.tracer(0,0)
snowflake_edge(n,size)
turtle.right(120)
snowflake_edge(n,size)
turtle.right(120)
snowflake_edge(n,size)
turtle.update()
turtle.hideturtle()
def snowflake_edge(n,size=200):
if n==0:
turtle.forward(size)
else:
snowflake_edge(n-1,size/3.0)
turtle.left(60)
snowflake_edge(n-1,size/3.0)
turtle.right(120)
snowflake_edge(n-1,size/3.0)
turtle.left(60)
snowflake_edge(n-1,size/3.0)
As indicated by the comments on P.5 of the problem set, the approach taken by the CPU and the GPU are different.
The CPU-only (without hardware acceleration) appproach is to first compute what needs to be drawn, and then send it to the GPU to draw. Since we are assuming that the cost to rasterize is bigger than the cost to gather the line segments, then the amount of time to draw the image will be dominated by the GPU, whose cost will be proportional to the number of pixels it actually has to draw.
The GPU-CPU (with hardware acceleration) approach computes all the triangles, big and small, and then sends them to the GPU to draw the big triangle, delete the middle pixels, draw the smaller triangles, delete the unneeded pixels, and so on. Since drawing takes a long time, then the time GPU has to spend drawing and erasing will dominate the total amount of time spent; since most (asymptotically, 100%) of the pixels that are drawn will in the end be erased, then the total amount of time taken will be substantially more than simply the number of pixels that actually have to be drawn.
In other words: the hardware-acceleration scenario dumps most of the work on to the GPU, which is much slower than the CPU. This is okay if the CPU has other things to work on while the GPU is doing its processing; however, this is not the case here; so, the CPU is just wasting cycles while the GPU is doing its drawing.

Loading many images in PyGame with limited RAM

I'm using PyGame on a Raspberry Pi, so I only have 512mb of RAM to work with. I have to load and display a lot of images in succession, though. I can't naively load all of these images into RAM as PyGame surfaces - I don't have enough RAM. The images themselves are fairly small, so I assume that PyGame surfaces are fairly big, and this is why I run out of RAM. I've tried loading from the disk every time I want to display an image, but that's obviously slow (noticeably so).
Is there a reasonable way to display lots of images in succession in PyGame with limited RAM - either by keeping the size in memory of the PyGame surface as low as possible, or some other way?
If you change your files to bmp, it should help. If you have really that little ram, then you should lower the resolution of your files using an image editor such as Preview or Paintbrush. Also, space might be saved through more efficient programming, such as putting objects in a list and just calling a list update.

Pygame Large Surfaces

I'm drawing a map of a real world floor with dimensions roughly 100,000mm x 200,000mm.
My initial code contained a function that converted any millimeter based position to screen positioning using the window size of my pygame map, but after digging through some of the pygame functions, I realized that the pygame transformation functions are quite powerful.
Instead, I'd like to create a surface that is 1:1 scale of real world and then scale it right before i blit it to the screen.
Is this the right way to be doing this? I get an error that says Width or Height too large. Is this a limit of pygame?
I dont fully understand your question, but to attempt to answer it here is the following.
No you should not fully draw to the screen then scale it. This is the wrong approach. You should tile very large surfaces and only draw the relevant tiles. If you need a very large view, you should use a scaled down image (pre-scaled). Probably because the amount of memory required to draw an extremely large surface is prohibitive, and scaling it will be slow.
Convert the coordinates to the tiled version using some sort of global matrix that scales everything to the size you expect. So you should also filter out sprites that are not visible by testing their inclusion inside the bounding box of your view port. Keep track of your view port position. You will be able to calculate where in the view port each sprite should be located based on its "world" coordinates.
If your map is not dynamic, I would suggest draw a map outside the game and load it in game.
If you plan on converting the game environment into a map, It might be difficult for a large environment. 100,000mm x 200,000mm is a very large area when converting into a pixels. I would suggest you to scale it down before loading.
As for scaling in-game, you can use pygame.transform.rotozoom or pygame.transform.smoothscale.
Also like the first answer mentions, scaling can take significant memory and time for very large images. Scaling a very large image to a very small image can make the image incomprehensible.

Pygame: Changing game resolution

I have been developing a 2D side-scrolling platformer for a while now. This is my first proper game, and it is almost finished. I haven't given much thought about resolutions until now.
My game is tile-based, most tiles and the player sprite are .PNG-files with dimensions of 40x40 pixels. The default screen height and width are 1280x720. Most games nowadays, to my knowledge, have lots of different resolution choices with different aspect ratios.
I would want to give the player couple choices of resolutions, like 800x600, 1024x768 and 1280x720. This needs to be done so the player can see equal amount of tiles in the game, whichever the resolution is, to not give unfair advantages or disadvantages to using a resolution. How would be the best way to achieve this?
I have thought about scaling the sprites of the game according to a given resolution, using
self.image = pygame.transform.scale(self.image, (30, 30))
self.image = pygame.transform.smoothscale(self.image, (30, 30))
This means that the sprites are scaled down, with 30x30 px. being the target dimensions.
I got them working properly, but the problem with both are the loss of quality. Less with pygame.transform.smoothscale, but still noticeable enough to my eyes.
This makes me wonder, is the best way to approach this really be making new art assets of the same files, but with different dimensions? It seems like a lot of work, more so when you have multiple choices of resolutions. I also thought about making a default screen resolution, sticking with it and not let the player choose any. But I rather not do that.
Most games nowadays, to my knowledge, have lots of different resolution choices with different aspect ratios
But bear in mind that most games nowadays aren't based on 576 tiles! It's a lot easier to rescale things when you aren't working with static art assets.
Think about this; if you've developed your (square) assets for a 16:9 format monitor (1280x720), should the tiles be rectangular (and the characters thinner) on a 4:3 format monitor (e.g. 1024x768)? Simply scaling in this way will give you an odd-looking game. And as you say, creating a range of art assets will be an enormous job, given how many "common" resolutions there are.
I would recommend you concentrate on making the game look good in a base resolution that will fit within most other common resolutions (e.g. 800x600) and occupy any spare screen real estate with black if not running windowed. You won't be using the whole screen, but players will concentrate on the bit you are using if the gameplay's good!
If you really do want to support multiple resolutions, I would pick one aspect ratio, and definitely create separate assets for each size; as you've already noticed, simply scaling from e.g. 40x40 to 30x30 doesn't look very good.

Pygame: Tiled Map or Large Image

I am trying to decide if it is better to use a pre-rendered large image for a scrolling map game or to render the tiles individual on screen each frame. I have tried to program the game both ways and don't see any obvious difference in speed, but that might be due to my lack of experiences.
Besides for memory, is there a speed reasons to not use a pre-rendered map?
The only reason I can think of for picking one over the other on modern hardware (anything as fast and with as much ram as, say, an iPhone), would be technical ones that make the game code itself easier to follow. There's not much performance wise to distinguish them.
One exception I can think of, is if you are using a truly massive background, and doing tile rendering in a GPU, tiles can be textures and you'll get a modest speed bump since you don't need to push much data between cpu and gpu per frame, and it'll use very little video ram.
Memory and speed are closely related. If your tile set fits in video memory, but the pre-rendered map doesn't, speed will suffer.
Maybe it really depends of the map's size but this shouldn't be a problem even with a low-level-computer.
The problem with big images is that it takes a lot of time to redraw all the stuff on it so you will get an unflexible "map".
But a real advantage with an optimized image(use convert()-function and 16 bit) is are the fast blittings.
I work with big images as well on a maybe middle-good-computer and I have around 150 FPS by blitting huge images which require just ~ 100? MB RAM
image = image.convert()#video system has to be initialed
The following code creates an image(5000*5000), draws something on it, (blit this to the screen, fill the screen)*50 times and at the end it tells how long it took to do one blit and one flip.
def draw(dr,image,count,radius,r):
for i in range(0,5000,5000//count):
for j in range(0,5000,5000//count):
dr.circle(image,(r.randint(0,255),r.randint(0,255),r.randint(0,255)),[i,j],radius,0)
def geschw_test(screen,image,p):
t1 = p.time.get_ticks()
screen.blit(image,(-100,-100))
p.display.flip()
return p.time.get_ticks() - t1
import pygame as p
import random as r
p.init()
image = p.Surface([5000,5000])
image.fill((255,255,255))
image.set_colorkey((255,255,255))
screen = p.display.set_mode([1440,900],p.SWSURFACE,16)
image = image.convert()#extremely efficient
screen.fill((70,200,70))
draw(p.draw,image,65,50,r)#draw on surface
zahler = 0
anz = 20
speed_arr = []
while zahler < anz:
zahler += 1
screen.fill((0,0,0))
speed_arr.append(geschw_test(screen,image,p))
p.quit()
speed = 0
for i in speed_arr:
speed += i
print(round(speed/anz,1),"miliseconds per blit with flip")
Depends on the size of the map you want to make, however, with the actual technologies it's very hard to see a tile-map "rendered" to take longer than expected, tiled based games are almost extinguished, however is always a good practice and a starting point to the world of game programming

Categories

Resources