基于M5STACK PLUS实现掌上游戏机
利用于M5STACK PLUS实现掌上游戏机功能,简单也十分有趣
标签
嵌入式系统
测试
显示
ESP32
M5Stick
sin
更新2022-09-02
华东理工大学
526

  本项目是参加电子森林暑假在家练活动而开始制作,并且在规定时间内按时完成。详细情况为基于M5STACK PLUS实现掌上游戏机,制作时间从7月上旬持续到八月下旬,参与制作的成员只有一位,是来自于华东理工大学的昵称为sin的成员。

   关于设计思路是现在脑中确定自己想要复刻的游戏是接金币这个游戏,然后上网找寻相关图片素材,逐一下载,并且调整为游戏机需要的大小,然后统一保存。接着设计游戏框架,构筑每一个函数的功能并且设定参数进行传递。最后设计循环嵌套,实现游戏失败之后再次重新开始一局游戏的功能。

  简单的硬件介绍如下:本次开发采用的是电子森林提供的M5STACK PLUS,这个板子小巧便捷,并且集成了多个模块,比如姿态传感器,麦克风,LED灯,A键,B键 ……同时还具备8可引脚可以外接模块或者和单片机形成互动。同时这块板卡的芯片采用的是树莓派ESP32,兼具WiFI模块,可以向物联网开发的方向进行开拓。总而言之,这块板卡功能十分强大,有着极大的开拓潜力,值得大家来学习开发。

FjlYQ8a3SzZI8r-EoAZE-4N2am-s

   本次我们小组实现的功能是基于M5STACK PLUS实现掌上游戏机的功能,游戏的名字我取名为接金币。这个游戏的设定背景是天上有个宝箱,但是宝箱出现了漏洞里面的金币会随机下落,但是这些金币当中也会混杂一些炸弹会对我们造成伤害。我们要做的便就是在安全的地方不断收集金币同时还要躲开炸弹,这一个游戏结束的方式只有我们的三个生命值全部被炸弹消耗掉。接着我们可以按下a键从新开始一局游戏。界面布局上,首先由一根黑线划分为两个区域,上方是得分和剩余生命值显示的区域,上面会实时显示我们的得分和剩余生命值。下方是游戏区,在游戏区的最上方我们可以看到漏金币的宝箱不断左右移动,在移动的同时还会有金币下落,其中也混杂着一些炸弹。下方显示的是我们用于接金币的小袋子,在这里我们可以左右摇晃游戏机,通过摇晃的角度控制最下方的袋子左右移动,当金币和炸弹超出屏幕或者被接住时都会消失,相应的如果是接住了我们的生命和得分部分会出现变化。

Fna8IsQJkwf6WvaIJkIrai3uDHls

FvRswerOC-OWK2vsHPS81cSP7FKK

   接着进入代码的讲解部分。最基础的库的调用和基础参数的赋值就简单掠过,需要注意的是这里将金币和炸弹用两个列表嵌套小列表。

from m5stack import *
from m5ui import *
from uiflow import *
import imu
import time
import random

imu0 = imu.IMU()
image0 = M5Img(0, 31, "res/box.jpg", True)
image1 = M5Img(0, 190, "res/bag.jpg", True)
image2 = M5Img(-10, 69, "res/coin.jpg", True)
image3 = M5Img(-10, 108, "res/bomb.jpg", True)
image7 = M5Img(-10, 69, "res/coin.jpg", True)
image8 = M5Img(-10, 69, "res/coin.jpg", True)
image9 = M5Img(-10, 69, "res/coin.jpg", True)
image10 = M5Img(-10, 69, "res/coin.jpg", True)
image11 = M5Img(-10, 108, "res/bomb.jpg", True)

timing = 0
speed_box = 5
speed_coin = 17
box_x = 0
bag_x = 0
value_x = 0
coin_pos = []
bomb_pos = []
bag_y = 226
heart = 3
num = 0
Loop = 1
coin_num = 0
bomb_num = 0

   接着进入函数部分,第一个函数是控制主界面的函数,这个函数确定了屏幕的亮度,底色是白色,以及黑线和分数生命字样的显示

def draw_screen():
    global timing, lcd, num, heart
    axp.setLcdBrightness(50)
    setScreenColor(0xffffff)
    lcd.line(0, 30, 135, 30, 0x000000)
    lcd.font(lcd.FONT_DefaultSmall)

    timing += random.randint(1, 3)
    lcd.print("SCORE:",1, 1, 0x000000,)
    lcd.print(num, 50, 1, 0x000000)
    lcd.print("YOUR LIFE:",1, 16, 0x000000,)
    lcd.print(heart, 72, 16, 0x000000)

    而后下一个函数是defeat函数块,在这里当判定为游戏失败后,进入这里,只有当A键被按下时才可以重新进入游戏

def defeat():
    global num, Loop, heart
    while True:
        setScreenColor(0xffffff)
        lcd.font(lcd.FONT_Default)
        lcd.print('you lose', 20, 50, 0x000000)
        lcd.print('press A', 20, 100, 0x000000)
        lcd.print('to restart', 20, 150, 0x000000)

        wait_ms(100)
        btnA.wasPressed(buttonA_wasPressed)
        if Loop == 1:
            heart = 3
            num = 0
            coin_pos = []
            bomb_pos = []
            coin_num = 0
            bomb_num = 0
            bag_x = 0
            break
def buttonA_wasPressed():
    global Loop
    Loop = 1

   下一个函数利用随机数不断增加timing而后如果碰巧达到标准而且自己金币和炸弹的图片还够用的时候,向炸弹和金币的列表之中添加小列表,小列表的值为[宝箱x轴,宝箱最低点,0/1判断类型是金币还是炸弹]

def birthing():
    global bomb_y, bomb_x, timing, coin_y, coin_x, coin_num, bomb_num, bomb_pos, coin_pos, heart

    if timing % 21 == 1 and bomb_num <= 1:
        bomb_pos.append([box_x, 45, 1])
        bomb_num += 1
    if timing % 7 == 1 and coin_num <= 4:
        coin_pos.append([box_x, 45, 0])
        coin_num += 1

后面是碰撞判定函数,如果金币或者炸弹的x,y位置在判定区域之内,将炸弹金币从列表之中剔除,并且产生相应的影响(金币判定使多分增加,炸弹判定使生命减少),同时当生命值为零时,控制循环退出的参数loop改变

def hit():
    global Loop, VICTORY, num, heart, bag_y, coin_num, bomb_num, bomb_pos, coin_pos
    for i in range(len(coin_pos)):
        coin_x = coin_pos[i][0]
        coin_y = coin_pos[i][1]
        if bag_x - 10 <= coin_x <= bag_x + 10 and bag_y - 5 <= coin_y <= bag_y + 10:
            num += 1
            del coin_pos[i]
            coin_num -= 1
            break
    for i in range(len(bomb_pos)):
        bomb_x = bomb_pos[i][0]
        bomb_y = bomb_pos[i][1]
        if bag_x - 10 <= bomb_x <= bag_x + 10 and bag_y - 5 <= bomb_y <= bag_y + 10:
            heart -= 1
            del bomb_pos[i]
            bomb_num -= 1
            if heart == 0:
                Loop = -Loop
                VICTORY = 2
            break

   下一个函数是运动函数,在这里会不断向金币和炸弹的y值上增加他们设定的运动距离(speed_coin和speed_bomb)接着根据存在的炸弹以及金币个数进行逐一贴图,如果炸弹,金币超出屏幕范围也相应除去

def coin_movement():
    global bomb_pos, coin_pos, coin_num, bomb_num, heart

    for i in range(0, len(coin_pos)):
        coin_pos[i][1] += speed_coin
        if coin_pos[i][1] >= 240:
            del coin_pos[i]
            coin_num -= 1
            break
    if len(coin_pos) == 5:
        image2.setPosition(coin_pos[0][0], coin_pos[0][1])
        image7.setPosition(coin_pos[1][0], coin_pos[1][1])
        image8.setPosition(coin_pos[2][0], coin_pos[2][1])
        image9.setPosition(coin_pos[3][0], coin_pos[3][1])
        image10.setPosition(coin_pos[4][0], coin_pos[4][1])
    if len(coin_pos) == 4:
        image2.setPosition(coin_pos[0][0], coin_pos[0][1])
        image7.setPosition(coin_pos[1][0], coin_pos[1][1])
        image8.setPosition(coin_pos[2][0], coin_pos[2][1])
        image9.setPosition(coin_pos[3][0], coin_pos[3][1])
    if len(coin_pos) == 3:
        image2.setPosition(coin_pos[0][0], coin_pos[0][1])
        image7.setPosition(coin_pos[1][0], coin_pos[1][1])
        image8.setPosition(coin_pos[2][0], coin_pos[2][1])
    if len(coin_pos) == 2:
        image2.setPosition(coin_pos[0][0], coin_pos[0][1])
        image7.setPosition(coin_pos[1][0], coin_pos[1][1])
    if len(coin_pos) == 1:
        image2.setPosition(coin_pos[0][0], coin_pos[0][1])
    for i in range(0, len(bomb_pos)):
        bomb_pos[i][1] += speed_coin
        if bomb_pos[i][1] >= 240:
            del bomb_pos[i]
            bomb_num -= 1
            break
        if len(bomb_pos) == 2:
            image3.setPosition(bomb_pos[0][0], bomb_pos[0][1])
            image11.setPosition(bomb_pos[1][0], bomb_pos[1][1])
        if len(bomb_pos) == 1:
            image3.setPosition(bomb_pos[0][0], bomb_pos[0][1])
    wait_ms(90)

后面的函数负责控制上方盒子的运动,当盒子触碰到两侧墙壁是将速度取负完成转向

def box_movement():
    global speed_box, box_x, value_x
    box_x = box_x + speed_box
    if box_x <= 0:
        speed_box = 3
    if box_x >= 121:
        speed_box = -3
    image0.setPosition(box_x, 31)

这个函数通过姿态传感器获取游戏机摇晃角度并且根据公式转化为贴图的x值进行显示,当过于倾斜时,将bag控制在最左或最右边

def bag_movement():
    global bag_x, value_x
    value_x = str((imu0.ypr[2]))
    value_x = float(value_x)

    if value_x <= -15:
        bag_x = 0
    if value_x >= 20:
        box_x = 130
    if -15 < value_x < 20:
        bag_x = int(4 * (float(value_x))) + 50

    image1.setPosition(bag_x, 228)

最后这里是主函数部分,将多个函数放在一起即可实现相关功能

while True:

    draw_screen()
    birthing()
    bag_movement()
    box_movement()
    coin_movement()
    if Loop == -1:  # -1为失败
        defeat()
    hit()

    接着是遇到的主要困难和解决方式的分享。首先第一个遇到的难题便就是如何开始研究。对于嵌入式还了解不多的我刚开始真的是手足无措。在老师的直播之后自己才算是浅浅入门,我知道自己想要完成的项目还挺不错,同样的想要实现也对个人能力有着不小的要求。这个时候我了解到有一个M5 flow的线上编程平台,在这里我尝试先使用图形化编程,将每个 功能都先使用图形化编程的方式简单实现,然后,将编码转化为代码编程,逐一了解如何编写代码,慢慢研究,知道自己具备了实现项目的全部能力需求。第二个是板卡自己的特殊性带来的误解。因为自己曾经和小伙伴组团利用python的pygame模块实现过游戏功能的实现,在这里我便就理所当然的认为这边会是一样的,一张图片在同一帧中可以重复调用。最开始只觉得是屏幕闪烁太快导致眼睛无法分辨出来。而后在降低闪烁频率后发现问题并没有得到解决,之后也尝试了更改亮度,调整逻辑等方面的尝试。最后再一次偶然和小伙伴的交流中意识到了问题所在,找到了问题的原因,如何解决也是需要思考的问题。最开始我理所当然的认为只要用完即换十分简单,可是编写代码后发现十分困难,如何找到自己要贴图的位置十分困难。最后我决定使用条件判断,并且计数场上金币和炸弹的个数,逐一贴上图片,在反复的尝试之后也算是完美解决了这个问题。

   后续自己将会在专业课程的学习上更加努力,在对嵌入式有着更加深入的了解后,结合电子森林的资源继续研究,做出更好的作品。

附件下载
COIN-CATCHING_ELEC_FOREST.py
源代码,作者是复制到网站编译运行 https://flow.m5stack.com/#video
素材包.zip
游戏使用图片的素材包
团队介绍
由一人组成的小团队,还在不断努力中
团队成员
sin
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号