来自:https://github.com/zelacerda/micropython
代码改造了一下,让它可以跑起来。
简单分析一下代码。外层是一个死循环,有一个状态机来对应不同的场景。
def loop():
while True:
if state == 0: splash_screen()
elif state == 1: game_waiting()
elif state == 2: game_running()
elif state == 3: game_over()
0是最开始的场景,通过检查按键进行切换。
button = Pin(13, Pin.IN)
按键之后切换到running。
def game_running():
global state
if clicked(): flappy_bird.flap()
flappy_bird.move()
if flappy_bird.crashed():
flappy_bird.y = HEIGHT - flappy_bird.height
state = 3
obstacle_1.scroll()
obstacle_2.scroll()
if obstacle_1.collided(flappy_bird.y) or obstacle_2.collided(flappy_bird.y):
state = 3
draw()
看了running其实就明白,所有的操作,以帧为单位。
进入这一帧时,首先根据按键来更新小鸟的位置,然后判断小鸟是否飞出去了。
然后更新障碍物的位置,然后判断障碍物和小鸟是否碰撞。
更新位置之后,在Framebuffer中进行更新。
显示的部分,还有挺有趣。小鸟,障碍物是这样表示的,是三个字符串:
# Bitmap images
BIRD = '07e018f021f871ecf9ecfcfcbe7e4c81717e4082307c0f80'
COL1 = '201c'*26+'ffff'+'800f'*4+'ffff'
COL2 = 'ffff'+'800f'*4+'ffff'+'201c'*26
然后基于这三个字符串创建的三个Framebuffer对象。
显示的时候一次调用blit方法加进去,有点类似memcpy。Framebuffer 的 blit 方法是用于将一个区域的图像数据从一个帧缓冲区复制到另一个帧缓冲区的方法。在图形编程中,blit 是 "block transfer"(块传输)的简称,通常用于在内存中进行图像数据的复制或传输操作。
在上面一旦检测到飞出画面或者碰到障碍物,就进到最后一个场景,结束画面。如果没问题就让屏幕正常显示。这个倒是没啥好说的。
可以看出,常规游戏的实现思路,就是基于2D图形的,以帧为单位,所有的逻辑操作都是基于2D的坐标体系。显示方面封装后就直接看做一块内存,逻辑处理完之后控制内存中数据的变化,最后把改动提交到显示器。
改造后完整代码如下:
game.py
'''
Flappy Bird for ESP8266 modules
github.com/zelacerda/micropython
Version 1.0
2017 - by zelacerda
'''
import ssd1306
from framebuf import FrameBuffer as FB
from machine import I2C, Pin
from utime import sleep
import time
#from urequests import post
# Screen dimensions
WIDTH = 128
HEIGHT = 64
# Initialize pins
i2c = I2C(1, scl=Pin(7), sda=Pin(6))
oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)
button = Pin(13, Pin.IN)
# Some helper functions
def random(a,b):
seed = int(time.time() * 1000) # 将当前时间转换为毫秒级别的整数
# 利用种子生成伪随机数
random_num = (seed * 1103515245 + 12345) % (2**31)
# 将随机数映射到指定范围[a, b]
return random_num % (b - a + 1) + a
def to_bytearray(s):
return bytearray([int('0x'+s[i:i+2]) for i in range(0,len(s),2)])
def write_high_score(n):
f = open('fb_high_score', 'w')
f.write(str(n))
f.close()
def read_high_score():
return 0
def send_score(n):
url = "http://things.ubidots.com/api/v1.6/devices/NodeMCU?token="
token = "A1E-5ZY9vbCGtRiqVinrnhrQxgA4FDSBaA"
url += token
headers = {"Content-Type": "application/json"}
data = '{"flappy-bird-score": ' + str(n) + '}'
#post(url, data=data, headers=headers)
# Bitmap images
BIRD = '07e018f021f871ecf9ecfcfcbe7e4c81717e4082307c0f80'
COL1 = '201c'*26+'ffff'+'800f'*4+'ffff'
COL2 = 'ffff'+'800f'*4+'ffff'+'201c'*26
bird_size = (16,12)
colu_size = (16,32)
# Generate sprites
bird = FB(to_bytearray(BIRD),bird_size[0],bird_size[1],3)
col1 = FB(to_bytearray(COL1),colu_size[0],colu_size[1],3)
col2 = FB(to_bytearray(COL2),colu_size[0],colu_size[1],3)
class FlappyBird:
def __init__(self):
self.height = bird_size[1]
self.y = HEIGHT // 2 - self.height // 2
self.vel = -wing_power
def move(self):
self.vel += gravity
self.y = int(self.y + self.vel)
def flap(self):
self.vel = -wing_power
def crashed(self):
y_limit = HEIGHT - self.height
return self.y > y_limit
class Obstacle:
def __init__(self, x):
self.gap = random(6+gap_size, HEIGHT-6-gap_size)
self.x = x
self.score = 0
def scroll(self):
self.x -= velocity
if self.x < -colu_size[0]:
self.score += 1
self.x = WIDTH
self.gap = random(6+gap_size, HEIGHT-6-gap_size)
def collided(self, y):
if self.x < bird_size[0] and \
self.x > -colu_size[0] and \
(self.gap - y > gap_size or y + bird_size[1] - self.gap > gap_size):
return True
else:
return False
def clicked():
global pressed
if button.value() == 1 and not pressed:
pressed = True
return True
elif button.value() == 0 and pressed:
pressed = False
return False
def draw():
oled.fill(0)
oled.blit(bird, 0, flappy_bird.y)
oled.blit(col1,obstacle_1.x,obstacle_1.gap-gap_size-colu_size[1])
oled.blit(col2,obstacle_1.x,obstacle_1.gap+gap_size)
oled.blit(col1,obstacle_2.x,obstacle_2.gap-gap_size-colu_size[1])
oled.blit(col2,obstacle_2.x,obstacle_2.gap+gap_size)
oled.fill_rect(WIDTH//2 - 13, 0, 26, 9, 0)
oled.text('%03d' % (obstacle_1.score + obstacle_2.score), WIDTH//2 - 12, 0)
oled.show()
# Game parameters
high_score = read_high_score()
gap_size = 13
velocity = 3
gravity = .8
wing_power = 4
state = 0
pressed = False
# Game state functions
def splash_screen():
global state
oled.fill(0)
oled.blit(col2, (WIDTH-colu_size[0])//2, HEIGHT-12)
oled.blit(bird, (WIDTH-bird_size[0])//2, HEIGHT-12-bird_size[1])
oled.rect(0, 0, WIDTH, HEIGHT, 1)
oled.text('F L A P P Y', WIDTH//2-44, 3)
oled.text('B I R D', WIDTH//2-28, 13)
oled.text('Record: ' + '%03d' % high_score, WIDTH//2-44, HEIGHT//2-6)
oled.show()
state = 1
def game_waiting():
global state,score,flappy_bird,obstacle_1,obstacle_2, pressed
if clicked():
flappy_bird = FlappyBird()
obstacle_1 = Obstacle(WIDTH)
obstacle_2 = Obstacle(WIDTH + (WIDTH + colu_size[0]) // 2)
state = 2
def game_running():
global state
if clicked(): flappy_bird.flap()
flappy_bird.move()
if flappy_bird.crashed():
flappy_bird.y = HEIGHT - flappy_bird.height
state = 3
obstacle_1.scroll()
obstacle_2.scroll()
if obstacle_1.collided(flappy_bird.y) or obstacle_2.collided(flappy_bird.y):
state = 3
draw()
def game_over():
global state, high_score
oled.fill_rect(WIDTH//2-32, 10, 64, 23, 0)
oled.rect(WIDTH//2-32, 10, 64, 23, 1)
oled.text('G A M E', WIDTH//2-28, 13)
oled.text('O V E R', WIDTH//2-28, 23)
score = obstacle_1.score + obstacle_2.score
if score > high_score:
high_score = score
oled.fill_rect(WIDTH//2-48, 37, 96, 14, 0)
oled.rect(WIDTH//2-48, 37, 96, 14, 1)
oled.text('New record!',WIDTH//2-44, 40)
write_high_score(high_score)
oled.show()
try:
send_score(score)
except:
pass
state = 1
def loop():
while True:
if state == 0: splash_screen()
elif state == 1: game_waiting()
elif state == 2: game_running()
elif state == 3: game_over()
loop()
oled驱动,ssd1306.py
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)