6
\$\begingroup\$

I made this little game to test if my Nokia 5110 screen can handle games. As in handle many frames per second/game loops.

I am using a library made for this screen called Adafruit_Nokia_LCD

These are the imports for some context, if needed:

import Adafruit_Nokia_LCD as LCD
import Adafruit_GPIO.SPI as SPI
import threading
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

This is how I load images. I have some black and white images, use a binary converter to turn them into a multiline string. These are some tamagotchi sprites for example:

tam1 = """00000000000000111100000
00001111000000111110000
00001111100001111110000
00011111100001111110000
00011111111111111111000
00011111111111111111000
00011111111111111111000
00111111111111111111000
00111111111111111111100
01001111000000011111110
01100000011110000000010
10110000111101000000001
10110000111110100000001
10110000111110100000001
10110000111110100000001
01110000011100100000001
01100000001111000000010
00100101000000000000010
00010011000000000000100
00001100000000000011110
00000011111111111100001
00000100000000000000001
00001000000000000011110
00001001000000000010000
00000111000000000010000
00000001000000000011100
00000010000000000100010
00000100000111111000010
00000100011000000111100
00000011100000000000000"""

There are two more sprites, for a walking animation called tam2 and tam3

I split them into lines:

tam1a = tam1.split('\n')
tam2a = tam2.split('\n')
tam3a = tam3.split('\n')

I have a different thread running to capture key input, in which case is spacebar to jump:

key = "lol" #uhh this is the keyword for no input pretty much idk i just left it in
getch = _Getch()
def thread1():
    global key
    lock = threading.Lock()
    while True:
        with lock:
            key = getch() #this is some code that works just like the msvcrt version
threading.Thread(target = thread1).start()

This is the game loop, after setting some variables:

#jump variables 
dist = 0 #distance off the ground
gup = False #isjumping
#obstacle variables
xx = 0 #x position of the obstacle that loops through the screen

#other variables
score = 0;
ind = 0; #current frame of player, there is 3
extraspeed = 0; #makes the pillar go faster as your score rises
while True: #gameloop start
    draw.rectangle((0,0,LCD.LCDWIDTH,LCD.LCDHEIGHT), outline=255, fill=255)
    #clears the screen --^
    draw.text((0,0),str(score),font=font) #draw score
    extraspeed = floor(score / 100) #set extraspeed based on score
    if extraspeed > 10:
        extraspeed = 10
    score += 3
    draw.rectangle((xx,32,xx+3,42),outline=0,fill=0)
    xx = xx + 4 +extraspeed #move the pillar
    if xx >= 84:
        xx = -4;
    if key == ' ':
        if dist == 0:
            gup = True
            key = "lol" #if dist = 0 means its on the ground
            #the "lol" thing is something I left over for some reason, it means no key pressed
        else:
            key = "lol"
    if gup == True:
        if dist != 12: #jumps up to 12 pixels off the ground
            dist += 3
        else:
            gup = False #start falling if reached top
    else:
        if dist > 0:
            dist -= 3
        else:
            dist = 0
    #ind is the current animation sprite to draw
    if ind == 1:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam1a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start
    if ind == 2:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam2a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start
    if ind == 3:
        i = 12-dist #top left corner of drawn sprite y
        j = 60 #top left corner of drawn sprite x
        for line in tam3a:
            for c in line:
                if c == '1':
                    draw.point((j,i),fill=0)
                j+=1
            i+=1
            j=60 #make same as j at start

    ind += 1
    if ind == 4:
        ind = 1 #restart the animation
    draw.line((0,43,83,43),fill=0)
    draw.line((0,44,83,44),fill=0) #draw some ground
    if xx >= float(67) and xx<= float(80) and dist <= 7:
        break #Some simple collision detection

# Display image.
    disp.image(image) #draw everything
    disp.display() #display everything

    time.sleep(0.2) #5 frames per second
draw.text((40,10),'Hit',font=font) #got here if 'break' occurs
disp.image(image) #displays hit, which means game over and loop is broken
disp.display()

My problem:


First of all: This screen is connected through GPIO pins to my Raspberry-Pi.

What do you think of this method to load sprites?

How about drawing the sprites? I iterate through the string, line by line then character by character and draw the pixels respectively (0 means dont draw, 1 means draw black)

Now while this code works, I experience ghosting and image burn in my lcd screen. While I know the nokia 5110 screen wasn't designed to be for a gaming system, can my code be optimized to a point of reducing this ghosting ?

What do you think of my key input getting method, is a seperate thread fine for this job?

Lastly, would a different drawing method allow me to reach 60 frames per second or is it just impossible due to the way this screen is manufactured?


Here's a pic. Notice how there is image burn from the last frame? There is only one pillar, and only one frame of the player, but two appear. Is this fixable, or is it because of the screen only?

Image burn and ghosting

\$\endgroup\$
  • 1
    \$\begingroup\$ Although I don't have too much hardware knowledge at this level, I suspect you may be trying to update the screen too frequently. As I recall from using screens like this long ago, large redraws would definitely not be instantaneous (and may cause some wiping effects). I suspect the issue is that you are clearing the entire screen (which takes time) and then subsequently trying to set certain pixels. It might be more efficient to keep track of the delta of pixels changed between frames and only update those. \$\endgroup\$ – Bailey Parker Oct 29 '18 at 5:36
0
\$\begingroup\$

I noticed that you are updating extraspeed every frame.

An alternative to this could be:

if extraspeed < 10:
    desired_boost = floor(score / 100)
    if desired_boost <= 10:
        extraspeed = desired_boost
    else:
        extraspeed = 10

This way, you aren't updating extraspeed every frame, only when it is less than 10.

Also, you should remove the semicolons on the following lines:

score = 0;
ind = 0; #current frame of player, there is 3
extraspeed = 0;

I'll look further into this for you, but I've never personally drawn to a screen other than my computer screen so I may be unable to assist you further.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.