【STM32单片机】飞机大战游戏


一、功能简介

本项目使用STM32F103C8T6单片机控制器,使用IIC OLED模块、按键模块等。

主要功能:
系统运行后,OLED液晶显示游戏画面,K1-K4按键分别控制方向上下左右,S1和S5键为游戏AB键。


二、软件设计

/*
作者:嗨小易(QQ技术交流群:570487280)

*/


void initGame(void)
{
    initGameParams();
    counter = 0;
    ledLevel = 0;
    isMenu = false;
    state = STATE_START;
    isInvalid = true;
}

MODE_T updateGame(void)
{
    handleDPad();
    counter++;
    if (ledLevel > 0)
        ledLevel--;
    callHandlerFunc(state);

    return (state == STATE_LEAVE) ? MODE_TITLE : MODE_GAME;
}

void drawGame(void)
{
    if (state == STATE_LEAVE)
    {
        ledLevel = 0;
    }
    else if (isInvalid)
    {
        OLED_Clear();
        drawPlayer();
        drawShots();
        drawBullets();
        drawEnemies();
        drawExplosion();
        drawBackground();
        drawLetters();
    }
    isInvalid = false;
}

/*---------------------------------------------------------------------------*/
/*                             Control Functions                             */
/*---------------------------------------------------------------------------*/

static void initGameParams(void)
{
    gameRank = 0;
    gameFrames = 0;
    playerX = WIDTH / 8 * PLAYER_SCALE;
    playerY = HEIGHT / 2 * PLAYER_SCALE;
    playerFire = 0;
    playerSlow = 0;
    isDefeated = false;
    initShots();
    initBullets();
    initEnemies();
    explo.a = IMG_EXPLO_ID_MAX;
}

static void initShots(void)
{
    for (SHOT_T *pS = shots; pS < &shots[SHOT_MAX]; pS++)
        pS->x = WIDTH;
}

static void initBullets(void)
{
    for (BULLET_T *pB = bullets; pB < &bullets[BULLET_MAX]; pB++)
        pB->spd = 0;
    bulletsNum = 0;
}

static void initEnemies(void)
{
    for (ENEMY_T *pE = enemies; pE < &enemies[ENEMY_MAX]; pE++)
        pE->life = 0;
}

/*-----------------------------------------------------------------------------------------------*/

static void handleStart()
{
    if (counter >= START_OMIT)
    {
        updateShots();
        updatePlayer();
    }
    if (counter == START_DURATION)
    {

        isRecordDirty = true;
        state = STATE_PLAYING;
    }
    isInvalid = true;
}

static void handlePlaying()
{
    updateShots();
    updatePlayer();
    updateExplo();
    isDefeated = updateBullets();
    bool isCleared = updateEnemies();
    gameFrames++;

    isRecordDirty = true;

    if (isDefeated || isCleared)
    {
        if (isDefeated)
        {
            dprintln(F("Game over..."));
        }
        else
        {
            dprintln(F("Clear!!"));
        }
        counter = 0;
        state = STATE_OVER;
    }
    isInvalid = true;
}

static void handleOver()
{
    if (!isDefeated || counter >= OVER_OMIT)
    {
        updateShots();
        updateExplo();
        updateBullets();
        updateEnemies();
        if (gameFrames < GAME_DURATION + ENEMY_ACTIVE)
            gameFrames++;
    }
    if (counter >= OVER_OMIT)
    {
        if (counter >= 128)
            counter -= 8;
        if (is_B_button_pressed())
        {
            (isDefeated) ? onRetry() : onQuit();
        }
    }
    isInvalid = true;
}

/*-----------------------------------------------------------------------------------------------*/

static int constrain(int a, int left, int right)
{
    if (a < left)
        a = left;
    if (a > right)
        a = right;
    return a;
}

static void updatePlayer(void)
{
    int8_t vx = is_right_button_pressed() - is_left_button_pressed();
    int8_t vy = is_down_button_pressed() - is_up_button_pressed();
    int8_t vr = (playerSlow == PLAYER_SLOW_MAX) ? 2 : ((vx != 0 && vy != 0) ? 3 : 4);
    playerX += vx * vr;
    playerX = constrain(playerX, 3 * PLAYER_SCALE, (WIDTH / 2 - 3) * PLAYER_SCALE - 1);
    playerY += vy * vr;
    playerY = constrain(playerY, 3 * PLAYER_SCALE, (HEIGHT - 3) * PLAYER_SCALE - 1);

    if (is_B_button_pressed())
    {
        if (playerFire == 0)
            playerFire = PLAYER_FIRE_MAX;
        if (playerSlow < PLAYER_SLOW_MAX)
            playerSlow++;
    }
    else
    {
        if (playerSlow > 0)
            playerSlow--;
    }
    if (playerFire > 0 && playerFire-- % PLAYER_FIRE_INT == 0)
    {
        newShot((PLAYER_FIRE_MAX / PLAYER_FIRE_INT) - playerFire / PLAYER_FIRE_INT);
    }
}

static void newShot(uint8_t g)
{
    static SHOT_T *pS = shots;
    uint8_t x = playerX / PLAYER_SCALE + SHOT_SPD, y = playerY / PLAYER_SCALE;
    pS->x = x;
    pS->y = y - g;
    pS++;
    pS->x = x;
    pS->y = y + g;
    if (++pS >= &shots[SHOT_MAX])
        pS = shots;
}

static void updateShots(void)
{
    for (SHOT_T *pS = shots; pS < &shots[SHOT_MAX]; pS++)
    {
        if (pS->x < WIDTH)
            pS->x += SHOT_SPD;
    }
}

static void updateExplo(void)
{
    if (explo.a < IMG_EXPLO_ID_MAX)
        explo.a++;
}

/*-----------------------------------------------------------------------------------------------*/

static void newGroup(uint8_t groupIdx)
{
    int idx = groupIdx % GROUP_MAX;
    GROUP_T *pG = &groups[idx];

    /*  Moving path design  */
    pG->xRad = random(4, 37);
    pG->yRad = random(3, 28);
    pG->yFlip = random(2);
    pG->xCoef = random(4, 64);
    pG->yCoef = random(4, 64);
    pG->entryInt = random(8);

    /*  Bullets design  */
    pG->type = random(ENEMY_TYPE_MAX);
    pG->fireInt = random(FPS, FPS * 3 + 1);
    pG->fireCycle = random(8);
    pG->fireExSpd = random(4);
    uint8_t fires = gameRank * pG->fireInt / FPS / 2;
    if (fires == 0)
        fires = 1;
    uint8_t fireDiv = (random(4)) ? 1 : random(1, sqrt(fires) + 1);
    uint8_t fireThick = fires / fireDiv;
    if (pG->type <= ENEMY_TYPE_FUNWISE)
    {
        pG->fireTimes = fireDiv;
        pG->fireNum = fireThick;
    }
    else
    {
        pG->fireTimes = fireThick;
        pG->fireNum = fireDiv;
    }
    uint16_t fireCycleMax = pG->fireInt / pG->fireTimes - BULLET_CYC_BASE;
    if (pG->fireCycle > fireCycleMax)
        pG->fireCycle = fireCycleMax;
    if ((pG->type == ENEMY_TYPE_FUNWISE || pG->type == ENEMY_TYPE_MOWING) && fireThick > 1)
    {
        uint8_t mowGap = random(32, 64) / (fireThick - 1);
        pG->fireGap = constrain(mowGap, BULLET_GAP_BASE, 23) - BULLET_GAP_BASE;
    }
    else
    {
        pG->fireGap = random(1, 5);
    }

    /*  Position of each enemy  */
    ENEMY_T *pE = &enemies[idx * ENEMY_UNITY];
    int8_t x = playerX / PLAYER_SCALE, y = playerY / PLAYER_SCALE;
    for (int i = 0; i < ENEMY_UNITY; i++, pE++)
    {
        pE->bx = random(pG->xRad / 2, WIDTH / 2 - 3 - pG->xRad);
        pE->by = random(pG->yRad + 3, HEIGHT - 3 - pG->yRad);
        pE->life = ENEMY_LIFE_INIT;
        pE->framesGap = random(256);
        Point enemyPoint = getEnemyCoords(pG, pE, 0);
        pE->fireDeg = myAtan2f(y - enemyPoint.y, x - enemyPoint.x);
    }
}

static bool updateEnemies(void)
{
    /*  New group  */
    if (gameFrames < GAME_DURATION && gameFrames % GROUP_INT == 0)
    {
        newGroup(gameFrames / GROUP_INT);
    }

    GROUP_T *pG = groups - 1;
    ENEMY_T *pE = enemies;
    uint8_t enemyLives = 0;
    int16_t enemyFrames;
    bool isDamaged = false;
    for (int i = 0; i < ENEMY_MAX; i++, pE++)
    {
        if (i % ENEMY_UNITY == 0)
        {
            pG++;
            enemyFrames = (gameFrames - (pG - groups) * GROUP_INT) % (GROUP_INT * GROUP_MAX);
        }
        else
        {
            enemyFrames -= (pG->entryInt + 1) * 4;
        }

        /*  Is it active?  */
        if (pE->life == 0)
            continue;
        if (enemyFrames > ENEMY_ACTIVE)
            pE->life = 0;
        if (state != STATE_PLAYING)
            continue;
        if (enemyFrames < ENEMY_FADE || enemyFrames > ENEMY_ACTIVE - ENEMY_FADE)
        {
            continue;
        }

        /*  Hit judgement  */
        Point enemyPoint = getEnemyCoords(pG, pE, enemyFrames);
        Rect enemyRect = Rect(enemyPoint.x - 3, enemyPoint.y - 3, 7, 7);
        for (SHOT_T *pS = shots; pE->life > 0 && pS < &shots[SHOT_MAX]; pS++)
        {
            if (pS->x < WIDTH && collide(Point(pS->x, pS->y), enemyRect))
            {
                pS->x = WIDTH;
                pE->life--;
                isDamaged = true;
            }
        }
        if (pE->life == 0)
        { // Defeat!
            explo.x = enemyPoint.x;
            explo.y = enemyPoint.y;
            explo.a = 0;
            dprintln(F("Defeat!"));
            continue;
        }
        enemyLives++;

        /*  Should it fire?  */
        uint16_t firePhase = (enemyFrames + pE->framesGap) % pG->fireInt;
        uint8_t fireCycle = pG->fireCycle + BULLET_CYC_BASE;
        if (firePhase % fireCycle > 0)
            continue;
        uint8_t fireCnt = firePhase / fireCycle;
        if (fireCnt >= pG->fireTimes)
            continue;

        /*  Fire enemy's bullets  */
        int8_t spd = gameRank + pG->fireExSpd + BULLET_BASE_SPD;
        int16_t x = enemyPoint.x, y = enemyPoint.y;
        int8_t deg = myAtan2f(playerY / PLAYER_SCALE - y, playerX / PLAYER_SCALE - x);
        int8_t mowGap = (pG->fireGap + BULLET_GAP_BASE) * ((pG->fireCycle & 1) * 2 - 1);
        if (fireCnt == 0)
        {
            pE->fireDeg = deg;
            if (pG->type == ENEMY_TYPE_MOWING && pG->fireTimes > 1)
            {
                pE->fireDeg -= mowGap * (pG->fireTimes - 1) / 2;
            }
        }
        switch (pG->type)
        {
        case ENEMY_TYPE_NEEDLE:
            spd = spd * 3 / 2 - pG->fireNum / 2;
            newNeedleBullets(x, y, deg, spd, pG->fireNum);
            break;
        case ENEMY_TYPE_FUNWISE:
            newFanwiseBullets(x, y, deg, spd, pG->fireNum - (fireCnt & 1), mowGap);
            break;
        case ENEMY_TYPE_RAPIDFIRE:
            spd = spd * 3 / 2;
            newFanwiseBullets(x, y, pE->fireDeg, spd, pG->fireNum, pG->fireGap);
            break;
        case ENEMY_TYPE_WHIP:
            spd += fireCnt * 2 - pG->fireNum;
            newFanwiseBullets(x, y, deg, spd, pG->fireNum, pG->fireGap);
            break;
        case ENEMY_TYPE_MOWING:
            newNeedleBullets(x, y, pE->fireDeg, spd, pG->fireNum);
            pE->fireDeg += mowGap;
            break;
        default:
            break;
        }
    }
    return (enemyLives == 0 && gameFrames >= GAME_DURATION);
}





三、实验现象

B站演示视频:https://space.bilibili.com/444388619
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

联系作者

视频地址:https://space.bilibili.com/444388619/video
专注于51单片机、STM32、国产32、DSP、Proteus、arduino、ESP32、物联网软件开发,PCB设计,视频分享,技术交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值