Python Forum
[PyGame] I found a way to generate sprites without .blit. Is it effecient?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] I found a way to generate sprites without .blit. Is it effecient?
#1
I lost interest in coding with pygame for a while after I realized that I would need some type of pixel art program for future projects. Planning it out, I quickly realized that such a program would be far more complex than anything I had made before. I would literally have to figure out how to do dozens of things just to make this one program, which was excessive.

However, while watching a video by the 8-bit guy (a youtuber that talks about 80s computers), I was inspired and realized that I could write an algorithm that could generate sprites WITHOUT having to use image files!

Here is the code I came up with:

import pygame
pygame.init()

mainWindow=pygame.display.set_mode((500,500))

#position of sprite
x=200
y=250
#size of pixels in screen pixels
pixel=10

#binary data used to generate sprite
row1=[0,0,0,1,1,0,0,0]
row2=[0,0,1,1,1,1,0,0]
row3=[0,1,1,1,1,1,1,0]
row4=[1,1,0,1,1,0,1,1]
row5=[1,1,1,1,1,1,1,1]
row6=[0,0,1,0,0,1,0,0]
row7=[0,1,0,1,1,0,1,0]
row8=[1,0,1,0,0,1,0,1]

sprite=row1+row2+row3+row4+row5+row6+row7+row8

#function that renders sprite based on binary sequence
def makeSprite():
    pixelx=x
    pixely=y

    for a in sprite:
        if a==1:
            pygame.draw.rect(mainWindow, (white),(pixelx,pixely,pixel,pixel))
        pixelx=pixelx+pixel
        if (pixelx-x)%80==0:
            pixelx=x
            pixely=pixely+pixel

#main loop so that sprite can be moved using arrow keys
def main():
    global x
    global y
    while True:
        pygame.time.delay(100)

        for event in pygame.event.get():
            if event.type==pygame.QUIT:
                pygame.quit()
                quit()
        key=pygame.key.get_pressed()
        if key[pygame.K_UP]:
            y=y-pixel
        if key[pygame.K_DOWN]:
            y=y+pixel
        if key[pygame.K_RIGHT]:
            x=x+pixel
        if key[pygame.K_LEFT]:
            x=x-pixel

        mainWindow.fill((0,0,0))
        makeSprite()
        pygame.display.update()

main()
What this program is supposed to do is generate a sprite that looks like one of the aliens from space invaders. You can move the sprite around the screen using the arrow keys.

Besides not needing a way to actually make proper image files, I see a number of advantages to this. For one, it would be really easy to make other algorithms that can flip or rotate the sprite, thus giving me more control over them. I also don't need to have a bunch of loose image files laying around (note that I have yet to figure out how to compile my programs). Also, it makes achieving transparency easier. Instead of telling the program to just ignore certain colors, with my algorithm it just skips over to the next pixel as it draws the sprite pixel by pixel, completely removing the need to have code in there to create transparency.

The only downside I can see to making sprites this way is that its kinda hard to figure out the binary sequence you need to type in to make a sprite. You'll notice that I implemented each row of the sprite as a separate list and had the program just combine them into a single list. I tried to type it out as one long list, but I quickly got lost and decided to do it this way hoping it would give me an idea of what the resulting sprite would look like, which it didn't sadly, but it still made it easier for me to keep track of which row I was typing in. Obviously, doing a larger sprite (such as a 16x16) would require a lot of typing, but for that I could just map out the thing on graph paper to make it easier for me to figure out the binary sequence I need to type in. Another small issue is that the color palette would have to be limited if I wanted to keep each entry in the list one digit (if I use both numbers and letters, I could in theory have 35 colors). Honestly, I don't mind this much. I find the number of colors available to me overwhelming and unnecessary. Also, I want to make retro-style games, so the limited color palette doesn't exactly bother me at all.

Of course, I don't know what effect this would have on how much processing power my games would require. I've noticed that my snake clone takes up an insane amount of my RAM, but that may just be because its having to interpret my code on the fly since I haven't figured out a way to actually compile anything.

Is there any other fundamental difference to generating sprites this way rather than through the .blit command? Either way, I'll probably be relying on it for a while until I get advanced enough where I can make my own ideal pixel art program.
Reply
#2
That very expensive. Use surface to store created image. With surface use set_at. It be more efficient then pygame draw rects for per pixel. This way you do the math once. Then blit surface is very fast.

The old ways won't work the same. Program languages have separated core programming and graphics. This allow programs language to be more flexible. So you can choose between sdl, opengl, or etc.

Here an example I made a while back.
99 percent of computer problems exists between chair and keyboard.
Reply
#3
Honestly, I'm now wondering if I really should be worried about this when I'm making such primitive games mainly for practice. The only reason I'm considering the system requirements of my games at all is because my snake game takes up a third of my RAM, on a $1,000 gaming computer that I purchased only a couple of years ago. Before I saw how much of my processing power my snake clone took up (and how earlier builds of it lagged really badly), the thought that I would have to worry about whether or not my computer could handle any of my games simply never crossed my mind. And as I mentioned, the problem may just be that my program isn't compiled, so my computer is having to interpret the code while the program is running. Wish I had a way to compile my programs so I could see what difference it makes. Ironnically, I have Code Blocks, which is SUPPOSED to be able to compile programs you type into it, but I could never get that function to work for me, and even my professor couldn't figure out why. Either way, it doesn't appear code blocks supports python code. And the default editor for python doesn't seem to have this function anywhere. And from what I can see, neither does notepad++. At least my computer realizes that I can open .pyw files with notepad++ (even if that doesn't really do me any good).
Reply
#4
There shouldn't be any difference in memory usage between compiled and interpreted languages. fwiw, python is a compiled language, and is recompiled each time you run your program (the compiled files are stored in the __pycache__ folder, or as .pyc files for older versions of python).

There also shouldn't be any difference in lag between compiled and interpreted languages.

If you're willing to share the snake code, I'm sure a couple of us will look through it to help find why it's performing so poorly.
Reply
#5
Your code above, as written, does not run.
Error:
Traceback (most recent call last): File "spam.py", line 62, in <module> main() File "spam.py", line 59, in main makeSprite() File "spam.py", line 31, in makeSprite pygame.draw.rect(mainWindow, (white),(pixelx,pixely,pixel,pixel)) NameError: name 'white' is not defined
I've taken your code, and made a few modifications for performance and clarity reasons.
1) Instead of 0/1, I've defined the sprite as _/1, that way it's more clear what the image will look like when rendered (it's easier to edit right in the source if you can see it better). This could be improved further by using strings, so there's no space between the pixels. ie: instead of _,_,_,1,1,_,_,_,, you could have " 11 "
2) The makeSprite() function now generates a pygame Surface instead of drawing to the screen. Now, it should only be called once, when the sprite is spawned.
3) The x/y values are no longer globals, since they're not relevant to sprite spawning, so they've been moved to local variables of the main() function.
4) Sprite generation has been removed from the hot loop (what happens every frame). Instead, all we do is keep track of where it should be drawn, and then draw it.
5) I replaced pygame.time.delay() with a pygame.time.Clock(). delay() will delay your game by that much time, but Clock() will only delay by the number given less how long it took to render the frame. So if your computer starts slowing down (due to other things running, or if you did a lot of processing that frame), it'll delay less. That way there's a more consistent experience. ie: instead of always waiting 100ms between frames, it'll delay by however much it needs to in order to keep the framerate whatever you set it to.
6) With this layout, it should be fairly easily to pass different sprites to makeSprite(), to generate different kinds of aliens, in case you wanted to build more of invaders.
import math
import pygame
pygame.init()
 
mainWindow=pygame.display.set_mode((500,500))
black = (0, 0, 0)
white = (255,255,255)

#size of pixels in screen pixels
pixel = 10
# width of sprites, in pixels
width = 8
 
_ = None  # use underscore to try to make the image more visible
#binary data used to generate sprite
sprite = [
    _,_,_,1,1,_,_,_,
    _,_,1,1,1,1,_,_,
    _,1,1,1,1,1,1,_,
    1,1,_,1,1,_,1,1,
    1,1,1,1,1,1,1,1,
    _,_,1,_,_,1,_,_,
    _,1,_,1,1,_,1,_,
    1,_,1,_,_,1,_,1,
]
 
#function that renders sprite based on binary sequence
def makeSprite(sprite):
    surface = pygame.Surface((width * pixel, math.floor(len(sprite) / width) * pixel))
    surface.fill(black)
    for pos, value in enumerate(sprite):
        if value:
            pos_x = pos % width
            pos_y = math.floor(pos / width)
            surface.fill(white, (pos_x * pixel, pos_y * pixel, pixel, pixel))
    return surface

#main loop so that sprite can be moved using arrow keys
def main():
    #position of sprite
    x=20
    y=25
    surf = makeSprite(sprite)
    clock = pygame.time.Clock()

    while True: 
        for event in pygame.event.get():
            if event.type==pygame.QUIT:
                pygame.quit()
                return
        key=pygame.key.get_pressed()
        if key[pygame.K_UP]:
            y -= 1
        if key[pygame.K_DOWN]:
            y += 1
        if key[pygame.K_RIGHT]:
            x += 1
        if key[pygame.K_LEFT]:
            x -= 1
 
        mainWindow.fill((0,0,0))
        mainWindow.blit(surf, (x * pixel, y * pixel))
        pygame.display.flip()
        clock.tick(10) # target fps

if __name__ == "__main__":
    main()
Reply
#6
(Dec-05-2019, 06:11 PM)nilamo Wrote: Your code above, as written, does not run.

What? How? It runs fine on my computer. Are we running different versions of python? I'm running python 3.6.2 and pygame 1.9.6. Just so we know, my professor taught some programming languages with outdated versions (most notably Java, my entire Java course I was afraid it was going to force an update on me and make me unable to do my assignments). I downloaded pygame myself though, so I'm certain I have the latest version of that (unless version 2 finally released).
Reply
#7
I'm on python 3.7.0, pygame 1.9.4.
But that shouldn't matter, white wasn't defined anywhere (just doing a ctrl-f on this page verifies that).
Reply
#8
(Dec-05-2019, 07:41 PM)nilamo Wrote: I'm on python 3.7.0, pygame 1.9.4.
But that shouldn't matter, white wasn't defined anywhere (just doing a ctrl-f on this page verifies that).

Oh, whoops. I was also using the program to mess around with implementing colors in a way that didn't require me to enter a 9-digit number every time. I decided to remove that segment where I defined several colors since it wasn't really important to the program. It would appear I forgot to remove it from the sprite function though. My mistake.

Here's an earlier version using the original way I implemented color:

import pygame
pygame.init()

mainWindow=pygame.display.set_mode((500,500))

x=200
y=250
pixel=10

row1=[0,0,0,1,1,0,0,0]
row2=[0,0,1,1,1,1,0,0]
row3=[0,1,1,1,1,1,1,0]
row4=[1,1,0,1,1,0,1,1]
row5=[1,1,1,1,1,1,1,1]
row6=[0,0,1,0,0,1,0,0]
row7=[0,1,0,1,1,0,1,0]
row8=[1,0,1,0,0,1,0,1]

sprite=row1+row2+row3+row4+row5+row6+row7+row8

def makeSprite():
    pixelx=x
    pixely=y
    r=0
    g=0
    b=0

    for a in sprite:
        if a==1:
            r=255
            g=255
            b=255
            pygame.draw.rect(mainWindow, (r,g,b),(pixelx,pixely,pixel,pixel))
        pixelx=pixelx+pixel
        if (pixelx-x)%(8*pixel)==0:
            pixelx=x
            pixely=pixely+pixel

def main():
    global x
    global y
    while True:
        pygame.time.delay(100)

        for event in pygame.event.get():
            if event.type==pygame.QUIT:
                pygame.quit()
                quit()
        key=pygame.key.get_pressed()
        if key[pygame.K_UP]:
            y=y-pixel
        if key[pygame.K_DOWN]:
            y=y+pixel
        if key[pygame.K_RIGHT]:
            x=x+pixel
        if key[pygame.K_LEFT]:
            x=x-pixel

        mainWindow.fill((0,0,0))
        makeSprite()
        pygame.display.update()

main()

Also, I didn't know you could use underscore characters in variables? I thought the system wouldn't allow that since operating system variables tend to begin with an underscore.
Reply
#9
You can also use strings.
import pygame

def create_sprite(color, pixel_size, data):
    size = len(data[0]) * pixel_size, len(data) * pixel_size
    surface = pygame.Surface(size, pygame.SRCALPHA)
    surface.fill((0, 0, 0, 0))
    for y, row in enumerate(data):
        for x, value in enumerate(row):
            if value == "x":
                surface.fill(color, (x * pixel_size, y * pixel_size, pixel_size, pixel_size))

    return surface

def main():
    pygame.init()
    # basic pygame setup
    pygame.display.set_caption("Sprite Creation Example")
    surface = pygame.display.set_mode((500, 500))
    rect = surface.get_rect()
    clock = pygame.time.Clock()
    delta = 0
    fps = 60

    squid = create_sprite(pygame.Color("white"), 10,
        ("   xx   ",
         "  xxxx  ",
         " xxxxxx ",
         "xx xx xx",
         "xxxxxxxx",
         "  x  x  ",
         " x xx x ",
         "x x  x x"))

    squid_rect = squid.get_rect(center=rect.center)
    squid_vector = pygame.Vector2(squid_rect.topleft)
    speed = 0.05

    # main loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        key = pygame.key.get_pressed()
        if key[pygame.K_UP]:
            squid_vector.y -= speed * delta
        if key[pygame.K_DOWN]:
            squid_vector.y += speed * delta
        if key[pygame.K_RIGHT]:
            squid_vector.x += speed * delta
        if key[pygame.K_LEFT]:
            squid_vector.x -= speed * delta

        squid_rect.topleft = squid_vector

        # Draw
        surface.fill(pygame.Color('black'))
        surface.blit(squid, squid_rect)
        pygame.display.flip()
        delta = clock.tick(fps)

if __name__ == "__main__":
    main()
99 percent of computer problems exists between chair and keyboard.
Reply
#10
(Dec-05-2019, 04:16 PM)nilamo Wrote: If you're willing to share the snake code, I'm sure a couple of us will look through it to help find why it's performing so poorly.
It is in this thread

To sum it up......From what i remember it appears to him as laggy because he set the frames per second to 7 (yes seven) in attempt to slow the animation down. As he is using FPS to adjust the snake speed instead of using logic. Even a $1,000,000 computer would appear to be laggy when you have restricted the frame rate so slow. And all his logic and animation is based off of the FPS being low. So when he simply raises it, his program goes haywire. This is one i let go because its a run around in trying to explain.
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  pygame, sprites, and rects menator01 12 1,890 Dec-07-2023, 02:37 AM
Last Post: Benixon
  [PyGame] Sprites just randomly appear and dissapear in my pygame.sprite.GoupeSingle trueShadoWrr 2 1,984 Feb-13-2023, 09:34 AM
Last Post: Vadanane
  [PyGame] pygame blit() method syafiq14 1 4,902 Oct-30-2020, 04:46 PM
Last Post: Russ_CW
  index error when using lists to blit rotated images codefun45 7 3,550 Sep-03-2020, 11:11 PM
Last Post: codefun45
  moving image with blit rwahdan 2 3,028 Jul-10-2019, 06:24 PM
Last Post: nilamo
  [PyGame] Having 4 players(Sprites) all being able to jump ElijahCastle 5 4,007 May-07-2019, 05:04 PM
Last Post: SheeppOSU
  Sprites and Actor error ajlconsulting 6 9,334 Jan-30-2019, 12:50 AM
Last Post: metulburr
  draw not showing all sprites ethanstrominger 0 2,578 Jan-25-2019, 10:10 PM
Last Post: ethanstrominger
  error with survace.blit joemcsk1 3 4,461 Aug-06-2018, 12:23 AM
Last Post: metulburr
  [PyGame] move randomly sprites reutB 4 8,234 Mar-29-2017, 01:12 PM
Last Post: metulburr

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020