一、功能简介
本项目使用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设计,视频分享,技术交流。