Visual Studio 2019!!
2026-10-25 16:33:51
发布于:浙江
#include <stdio.h>
#include <graphics.h>
#include <mmsystem.h>
#include <windows.h>
#include <time.h>
#include <vector>
#include <utility>
#include"tools.h"
#pragma comment(lib, "winmm.lib")
#define WIDTH 1000
#define HEIGHT 600
#define KEY_DELAY 70
#define VOLUME_STEP 10
#define TOTAL_ROWS 5 // 总行数
#define PEA_SPEED 8 // 豌豆移动速度(像素/帧)
#define PEA_DAMAGE 1 // 豌豆伤害
#define BASE_ZOMBIE_HP 100 // 基础僵尸血量
#define MIN_SHOOT_INTERVAL 1000 // 最小发射间隔(毫秒)
#define MAX_SHOOT_INTERVAL 2500 // 最大发射间隔(毫秒)
#define ZOMBIE_DEAD_FRAME_DELAY 80 // 僵尸死亡帧间隔
#define ZOMBIE_EAT_FRAME_DELAY 100 // 僵尸吃植物帧间隔
#define ZOMBIE_DETECTION_RANGE 800 // 僵尸检测范围
#define PLANT_HP 5 // 植物血量
// 僵尸相关参数
#define ZOMBIE_EAT_OFFSET 30 // 僵尸碰撞判定右移
#define ZOMBIE_MOVE_SPEED 1 // 僵尸移动速度
#define ZOMBIE_EAT_DISTANCE 10 // 僵尸进入吃植物状态的距离
#define ZOMBIE_HOME_BREACH -100 // 僵尸闯入家中的X坐标
// 食人花相关参数
#define MANEATER_ATTACK_RANGE 90 // 攻击范围
#define MANEATER_COOLDOWN 4000 // 攻击冷却时间
#define MANEATER_EAT_FRAMES 15 // 吃僵尸动画帧数
#define MANEATER_EAT_FRAME_DELAY 80 // 吃僵尸动画帧间隔
// 胜利条件参数
#define WIN_ZOMBIE_COUNT 30 // 消灭多少僵尸获胜
// 难度提升参数
#define ZOMBIES_PER_LEVEL 5 // 每消灭这些僵尸提升一次难度
#define HP_INCREASE_PER_LEVEL 1 // 每级增加的僵尸血量
// 游戏状态枚举
enum GameState {
GAME_PLAYING,
GAME_WIN,
GAME_OVER
};
// 植物类型枚举
enum { PLANT_PEA, PLANT_SUN, PLANT_MANEATER, TOTAL_PLANTS };
// 豌豆结构体
struct Pea {
int x; // X坐标
int y; // Y坐标
int row; // 所在行
bool used; // 是否活跃
};
// 爆炸特效结构体
struct Explosion {
int x; // X坐标
int y; // Y坐标
int frame; // 当前帧
bool used; // 是否活跃
ULONGLONG frameTimer; // 帧计时器
};
// 僵尸结构体
struct Zombie {
int x; // X坐标
int y; // Y坐标
int row; // 所在行
// 行走动画
int walkFrame; // 行走帧(0~21)
// 吃植物动画
bool isEating; // 是否在吃植物
int eatFrame; // 吃植物帧(0~20)
ULONGLONG eatTimer; // 吃植物帧计时器
// 死亡动画
bool isDying; // 是否处于死亡动画中
bool isBeingEaten; // 是否被食人花吃
int deadFrame; // 死亡帧(0~19)
ULONGLONG deadFrameTimer; // 死亡帧计时器
// 基础状态
int hp; // 僵尸血量
bool used; // 是否存活
// 目标植物信息
int targetPlantRow;
int targetPlantCol;
int targetPlantX;
};
// 植物结构体
struct PlantStatus
{
int type;
int frame;
int sunTimer; // 太阳花生成阳光计时器
ULONGLONG bulletTimer; // 豌豆射手发射计时器
ULONGLONG attackCooldown; // 食人花攻击冷却计时器
ULONGLONG eatFrameTimer; // 食人花吃僵尸帧计时器
bool isEatingZombie; // 食人花是否正在吃僵尸
int eatZombieFrame; // 食人花吃僵尸动画帧
int hp; // 植物血量
} map[TOTAL_ROWS][9] = { 0 };
// 阳光结构体
struct SunShine
{
int x;
int y;
int frameindex;
int DSTY;
bool USED;
int TIMER;
};
// 全局资源与变量
IMAGE imgBg, imgBr, imgCards[TOTAL_PLANTS];
IMAGE imgWinBg, imgGameOverBg; // 胜利和失败背景
IMAGE* imgPlants[TOTAL_PLANTS][20];
IMAGE imgSun[29];
IMAGE* imgZombies[22] = { nullptr }; // 僵尸行走图片
IMAGE* imgZombieDead[20] = { nullptr }; // 僵尸死亡图片
IMAGE* imgZombieEat[21] = { nullptr }; // 僵尸吃植物图片
IMAGE imgPea; // 豌豆图片
IMAGE imgExplosion; // 爆炸特效图片
Zombie zombies[20] = { 0 }; // 僵尸数组
Pea peas[30] = { 0 }; // 豌豆数组
Explosion explosions[15] = { 0 }; // 爆炸特效数组
SunShine balls[10]; // 阳光池
char pathBuffer[64], debugText[256];
int curX, curY, Select_Index = 0;
int sunlight = 100;
bool isPlantSoundPlaying = false;
ULONGLONG plantSoundStartTime = 0;
const int PLANT_SOUND_DURATION = 1000;
ULONGLONG lastKeyPressTime = 0;
int currentVolume = 500;
GameState gameState = GAME_PLAYING; // 当前游戏状态
// 难度系统变量
int zombieKillCount = 0; // 已消灭僵尸数量
int difficultyLevel = 1; // 当前难度等级
// 检查文件是否存在
bool FileExists(const char* path) {
FILE* fp;
if (fopen_s(&fp, path, "r") == 0) {
fclose(fp);
return true;
}
return false;
}
// 播放音效
void PlaySoundEffect(const char* soundPath, const char* alias) {
if (!FileExists(soundPath)) return;
char cmd[128];
sprintf_s(cmd, "close %s", alias);
mciSendString(cmd, NULL, 0, NULL);
sprintf_s(cmd, "open "%s" type mpegvideo alias %s", soundPath, alias);
mciSendString(cmd, NULL, 0, NULL);
sprintf_s(cmd, "setaudio %s volume to %d", alias, currentVolume);
mciSendString(cmd, NULL, 0, NULL);
sprintf_s(cmd, "play %s", alias);
mciSendString(cmd, NULL, 0, NULL);
}
// 播放背景音乐
void PlayBGM() {
if (FileExists("res/bg.MP3")) {
mciSendString("open res/bg.MP3 type mpegvideo alias bgm", NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);
}
}
// 获取当前难度下的僵尸血量
int GetZombieHP() {
return BASE_ZOMBIE_HP + (difficultyLevel - 1) * HP_INCREASE_PER_LEVEL;
}
// 检查指定行是否有僵尸
bool hasZombieInRow(int row) {
for (int i = 0; i < 20; i++) {
if (zombies[i].used && !zombies[i].isDying && !zombies[i].isBeingEaten && zombies[i].row == row) {
return zombies[i].x < WIDTH&& zombies[i].x > -ZOMBIE_DETECTION_RANGE;
}
}
return false;
}
// 获取指定行最近僵尸的距离
int getNearestZombieDistance(int row, int plantCol) {
int plantX = 252 + plantCol * 78;
int minDistance = ZOMBIE_DETECTION_RANGE;
bool found = false;
for (int i = 0; i < 20; i++) {
if (zombies[i].used && !zombies[i].isDying && !zombies[i].isBeingEaten && zombies[i].row == row) {
if (zombies[i].x < plantX) {
int distance = plantX - zombies[i].x;
if (distance < minDistance) {
minDistance = distance;
found = true;
}
}
}
}
return found ? minDistance : ZOMBIE_DETECTION_RANGE;
}
// 检查植物与僵尸之间是否有其他僵尸遮挡
bool isZombieBlocked(int row, int zombieX, int plantX) {
for (int i = 0; i < 20; i++) {
if (!zombies[i].used || zombies[i].isDying || zombies[i].isBeingEaten || zombies[i].row != row) continue;
if (zombies[i].x > zombieX && zombies[i].x < plantX) {
return true; // 有遮挡
}
}
return false;
}
// 检测僵尸是否碰到可吃的植物
bool checkZombiePlantCollision(Zombie& zombie, int& targetRow, int& targetCol, int& targetX) {
int zombieEffectiveX = zombie.x + ZOMBIE_EAT_OFFSET;
int zombieW, zombieH;
if (zombie.isEating && imgZombieEat[zombie.eatFrame]) {
zombieW = imgZombieEat[zombie.eatFrame]->getwidth();
zombieH = imgZombieEat[zombie.eatFrame]->getheight();
}
else if (imgZombies[zombie.walkFrame]) {
zombieW = imgZombies[zombie.walkFrame]->getwidth();
zombieH = imgZombies[zombie.walkFrame]->getheight();
}
else return false;
int zombieEffectiveW = zombieW - ZOMBIE_EAT_OFFSET;
for (int col = 8; col >= 0; col--) {
int row = zombie.row;
if (map[row][col].type == 0 || map[row][col].hp <= 0) continue;
int plantX = 252 + col * 78;
int plantY = 87 + row * 97 - 5;
int plantW = imgPlants[map[row][col].type - 1][0]->getwidth();
int plantH = imgPlants[map[row][col].type - 1][0]->getheight();
if (isZombieBlocked(row, zombie.x, plantX)) {
continue;
}
bool collide = (zombieEffectiveX < plantX + plantW) &&
(zombieEffectiveX + zombieEffectiveW > plantX) &&
(zombie.y < plantY + plantH) &&
(zombie.y + zombieH > plantY);
if (collide) {
targetRow = row;
targetCol = col;
targetX = plantX;
return true;
}
}
return false;
}
// 食人花检测范围内的僵尸
int checkManeaterTarget(int row, int col) {
int plantX = 252 + col * 78;
int plantY = 87 + row * 97 - 5;
int plantW = imgPlants[PLANT_MANEATER][0]->getwidth();
// 精确攻击范围,确保僵尸足够近
int attackLeft = plantX;
int attackRight = plantX + plantW + MANEATER_ATTACK_RANGE;
for (int i = 0; i < 20; i++) {
if (!zombies[i].used || zombies[i].isDying ||
zombies[i].isBeingEaten || zombies[i].row != row)
continue;
Zombie& zombie = zombies[i];
int zombieW = imgZombies[zombie.walkFrame]->getwidth();
// 僵尸必须部分进入攻击范围
if (zombie.x + zombieW > attackLeft && zombie.x < attackRight) {
// 确保僵尸在合理位置才被攻击
if (zombie.x < plantX + plantW + 30) {
return i; // 返回目标僵尸索引
}
}
}
return -1; // 没有找到目标
}
// 生成僵尸
void createZombie() {
static int count = 0;
static int spawnInterval = 50;
static int currentRow = 0;
static int offsetTimer = 0;
count++;
if (count >= spawnInterval) {
spawnInterval = 100 + rand() % 300;
count = 0;
// 确定生成行
int targetRow = currentRow;
offsetTimer++;
if (offsetTimer >= 3) {
offsetTimer = 0;
switch (currentRow) {
case 0: targetRow = 1; break;
case TOTAL_ROWS - 1: targetRow = TOTAL_ROWS - 2; break;
default: targetRow = currentRow + (rand() % 2 ? 1 : -1); break;
}
}
currentRow = (currentRow + 1) % TOTAL_ROWS;
// 找空闲僵尸槽位
int freeZombieIdx = -1;
for (int i = 0; i < 20; i++) {
if (!zombies[i].used) {
freeZombieIdx = i;
break;
}
}
if (freeZombieIdx == -1) return;
// 初始化僵尸
Zombie& newZombie = zombies[freeZombieIdx];
newZombie.used = true;
newZombie.isDying = false;
newZombie.isBeingEaten = false;
newZombie.isEating = false;
newZombie.hp = GetZombieHP(); // 使用当前难度的血量
newZombie.walkFrame = 0;
newZombie.eatFrame = 0;
newZombie.deadFrame = 0;
newZombie.eatTimer = GetTickCount();
newZombie.deadFrameTimer = GetTickCount();
newZombie.x = WIDTH;
newZombie.y = 50 + targetRow * 97;
newZombie.row = targetRow;
newZombie.targetPlantRow = -1;
newZombie.targetPlantCol = -1;
newZombie.targetPlantX = -1;
}
}
// 生成豌豆
void createPea(int x, int y, int row) {
for (int i = 0; i < 30; i++) {
if (!peas[i].used) {
peas[i].used = true;
peas[i].x = x;
peas[i].y = y;
peas[i].row = row;
PlaySoundEffect("res/audio/pea_shoot.mp3", "peaShoot");
return;
}
}
}
// 生成爆炸特效
void createExplosion(int zombieX, int zombieY, int zombieWidth, int zombieHeight) {
for (int i = 0; i < 15; i++) {
if (!explosions[i].used) {
explosions[i].used = true;
explosions[i].x = zombieX - 17 + zombieWidth / 2 - imgExplosion.getwidth() / 2;
explosions[i].y = zombieY + zombieHeight / 2 - imgExplosion.getheight() / 2;
explosions[i].frame = 0;
explosions[i].frameTimer = GetTickCount();
PlaySoundEffect("res/audio/explode.mp3", "explode");
return;
}
}
}
// 更新豌豆
void UpdatePeas() {
ULONGLONG currentTime = GetTickCount();
for (int i = 0; i < 30; i++) {
if (!peas[i].used) continue;
Pea& pea = peas[i];
pea.x += PEA_SPEED;
if (pea.x > WIDTH + imgPea.getwidth()) {
pea.used = false;
continue;
}
int peaW = imgPea.getwidth();
int peaH = imgPea.getheight();
for (int j = 0; j < 20; j++) {
if (!zombies[j].used || zombies[j].isDying || zombies[j].isBeingEaten || zombies[j].row != pea.row) continue;
Zombie& zombie = zombies[j];
int zombieW, zombieH;
if (zombie.isEating && imgZombieEat[zombie.eatFrame]) {
zombieW = imgZombieEat[zombie.eatFrame]->getwidth();
zombieH = imgZombieEat[zombie.eatFrame]->getheight();
}
else if (imgZombies[zombie.walkFrame]) {
zombieW = imgZombies[zombie.walkFrame]->getwidth();
zombieH = imgZombies[zombie.walkFrame]->getheight();
}
else continue;
bool collide = (pea.x < zombie.x + zombieW) &&
(pea.x + peaW > zombie.x) &&
(pea.y < zombie.y + zombieH) &&
(pea.y + peaH > zombie.y);
if (collide) {
pea.used = false;
createExplosion(zombie.x, zombie.y, zombieW, zombieH);
zombie.hp -= PEA_DAMAGE;
// 播放僵尸中弹音效
PlaySoundEffect("res/audio/grassstep.mp3", "zombieHit");
if (zombie.hp <= 0) {
zombie.isDying = true;
zombie.isEating = false;
zombie.deadFrame = 0; // 重置死亡帧
zombie.deadFrameTimer = currentTime;
PlaySoundEffect("res/audio/zombie_dead.mp3", "zombieDead");
// 增加僵尸消灭计数,检查是否提升难度
zombieKillCount++;
if (zombieKillCount >= ZOMBIES_PER_LEVEL * difficultyLevel) {
difficultyLevel++;
PlaySoundEffect("res/audio/difficulty_up.mp3", "difficultyUp");
}
// 检查胜利条件
if (zombieKillCount >= WIN_ZOMBIE_COUNT) {
gameState = GAME_WIN;
mciSendString("stop bgm", NULL, 0, NULL);
PlaySoundEffect("res/win.mp3", "winSound");
}
}
break;
}
}
}
}
// 更新爆炸特效
void UpdateExplosions() {
ULONGLONG currentTime = GetTickCount();
for (int i = 0; i < 15; i++) {
if (!explosions[i].used) continue;
Explosion& exp = explosions[i];
if (currentTime - exp.frameTimer > 500) {
exp.used = false;
}
}
}
// 更新僵尸
void UpdateZombies() {
if (gameState != GAME_PLAYING) return;
createZombie();
ULONGLONG currentTime = GetTickCount();
for (int i = 0; i < 20; i++) {
if (!zombies[i].used) continue;
Zombie& currZombie = zombies[i];
// 检查失败条件:僵尸闯入家中
if (currZombie.x < ZOMBIE_HOME_BREACH && !currZombie.isDying && !currZombie.isBeingEaten) {
gameState = GAME_OVER;
mciSendString("stop bgm", NULL, 0, NULL);
PlaySoundEffect("res/lose.mp3", "loseSound");
return;
}
// 被食人花吃的僵尸处理
if (currZombie.isBeingEaten) {
// 确保死亡动画正常播放
if (currentTime - currZombie.deadFrameTimer > ZOMBIE_DEAD_FRAME_DELAY) {
currZombie.deadFrame++;
currZombie.deadFrameTimer = currentTime;
// 死亡动画播放完毕后移除僵尸
if (currZombie.deadFrame >= 20) {
currZombie.used = false;
currZombie.isBeingEaten = false; // 重置状态
currZombie.isDying = false;
}
}
continue;
}
// 死亡中状态处理
if (currZombie.isDying) {
// 确保死亡帧按时更新
if (currentTime - currZombie.deadFrameTimer > ZOMBIE_DEAD_FRAME_DELAY) {
currZombie.deadFrame++;
currZombie.deadFrameTimer = currentTime;
// 死亡动画完成后标记为不活跃
if (currZombie.deadFrame >= 20) {
currZombie.used = false;
currZombie.isDying = false; // 重置状态
}
}
continue;
}
// 检测可吃的植物
int targetRow = -1, targetCol = -1, targetX = -1;
bool hasPlantToEat = checkZombiePlantCollision(currZombie, targetRow, targetCol, targetX);
if (hasPlantToEat) {
currZombie.targetPlantRow = targetRow;
currZombie.targetPlantCol = targetCol;
currZombie.targetPlantX = targetX;
int plantW = imgPlants[map[targetRow][targetCol].type - 1][0]->getwidth();
int zombieEatW = imgZombieEat[0]->getwidth();
int targetPosition = (targetX + plantW) - (zombieEatW - ZOMBIE_EAT_OFFSET);
int distance = currZombie.x - targetPosition;
if (distance > ZOMBIE_EAT_DISTANCE) {
currZombie.isEating = false;
int moveStep = min(ZOMBIE_MOVE_SPEED, distance / 2);
currZombie.x -= moveStep;
currZombie.walkFrame++;
if (currZombie.walkFrame >= 22) {
currZombie.walkFrame = 0;
}
}
else {
currZombie.isEating = true;
if (distance > 0) {
currZombie.x -= min(distance, ZOMBIE_MOVE_SPEED);
}
if (currentTime - currZombie.eatTimer > ZOMBIE_EAT_FRAME_DELAY) {
currZombie.eatFrame++;
currZombie.eatTimer = currentTime;
if (currZombie.eatFrame >= 21) {
currZombie.eatFrame = 0;
if (map[targetRow][targetCol].hp > 0) {
map[targetRow][targetCol].hp--;
PlaySoundEffect("res/audio/zombie_eat.mp3", "zombieEat");
if (map[targetRow][targetCol].hp <= 0) {
map[targetRow][targetCol].type = 0;
currZombie.isEating = false;
currZombie.targetPlantRow = -1;
currZombie.targetPlantCol = -1;
currZombie.targetPlantX = -1;
}
}
}
}
}
}
else {
currZombie.isEating = false;
currZombie.targetPlantRow = -1;
currZombie.targetPlantCol = -1;
currZombie.targetPlantX = -1;
currZombie.walkFrame++;
if (currZombie.walkFrame >= 22) {
currZombie.walkFrame = 0;
}
currZombie.x -= ZOMBIE_MOVE_SPEED;
int zombieW = imgZombies[currZombie.walkFrame]->getwidth();
if (currZombie.x < -zombieW) {
currZombie.used = false;
}
}
}
}
// 处理食人花逻辑
void handleManeaterPlant(int row, int col) {
PlantStatus& plant = map[row][col];
ULONGLONG currentTime = GetTickCount();
// 检查是否在冷却中
if (currentTime - plant.attackCooldown < MANEATER_COOLDOWN && !plant.isEatingZombie) {
// 冷却中,正常动画
plant.frame++;
if (imgPlants[PLANT_MANEATER][plant.frame] == nullptr) {
plant.frame = 0;
}
return;
}
// 正在吃僵尸的动画
if (plant.isEatingZombie) {
if (currentTime - plant.eatFrameTimer > MANEATER_EAT_FRAME_DELAY) {
plant.eatZombieFrame++;
plant.eatFrameTimer = currentTime;
// 吃僵尸动画结束
if (plant.eatZombieFrame >= MANEATER_EAT_FRAMES) {
plant.isEatingZombie = false;
plant.eatZombieFrame = 0;
plant.attackCooldown = currentTime; // 开始冷却
}
}
return;
}
// 检测范围内的僵尸
int targetZombieIdx = checkManeaterTarget(row, col);
if (targetZombieIdx != -1) {
// 开始吃僵尸
plant.isEatingZombie = true;
plant.eatZombieFrame = 0;
plant.eatFrameTimer = currentTime;
Zombie& targetZombie = zombies[targetZombieIdx];
targetZombie.isBeingEaten = true;
targetZombie.isDying = true; // 标记为死亡
targetZombie.deadFrame = 0; // 重置死亡帧
targetZombie.deadFrameTimer = currentTime;
// 播放吃僵尸音效
PlaySoundEffect("res/audio/maneater_eat.mp3", "maneaterEat");
// 增加僵尸消灭计数,检查难度提升
zombieKillCount++;
if (zombieKillCount >= ZOMBIES_PER_LEVEL * difficultyLevel) {
difficultyLevel++;
PlaySoundEffect("res/audio/difficulty_up.mp3", "difficultyUp");
}
// 检查胜利条件
if (zombieKillCount >= WIN_ZOMBIE_COUNT) {
gameState = GAME_WIN;
mciSendString("stop bgm", NULL, 0, NULL);
PlaySoundEffect("res/win.mp3", "winSound");
}
}
// 正常动画
plant.frame++;
if (imgPlants[PLANT_MANEATER][plant.frame] == nullptr) {
plant.frame = 0;
}
}
// 更新植物
void UpdatePlants() {
if (gameState != GAME_PLAYING) return;
ULONGLONG currentTime = GetTickCount();
// 1. 豌豆射手(动态调整发射频率)
for (int row = 0; row < TOTAL_ROWS; row++) {
for (int col = 0; col < 9; col++) {
if (map[row][col].type != PLANT_PEA + 1) continue;
PlantStatus& plant = map[row][col];
if (hasZombieInRow(row)) {
// 根据最近僵尸距离调整发射间隔
int distance = getNearestZombieDistance(row, col);
int interval = MAX_SHOOT_INTERVAL -
((MAX_SHOOT_INTERVAL - MIN_SHOOT_INTERVAL) * (ZOMBIE_DETECTION_RANGE - distance) / ZOMBIE_DETECTION_RANGE);
interval = max(MIN_SHOOT_INTERVAL, min(MAX_SHOOT_INTERVAL, interval));
if (currentTime - plant.bulletTimer > interval) {
int plantX = 252 + col * 78;
int plantY = 76 + row * 97 - 5;
if (imgPlants[PLANT_PEA][0]) {
int plantW = imgPlants[PLANT_PEA][0]->getwidth();
int plantH = imgPlants[PLANT_PEA][0]->getheight();
int peaX = plantX + plantW;
int peaY = plantY + plantH / 2 - imgPea.getheight() / 2;
createPea(peaX, peaY, row);
}
plant.bulletTimer = currentTime;
}
}
// 豌豆射手动画
plant.frame++;
if (imgPlants[PLANT_PEA][plant.frame] == nullptr) {
plant.frame = 0;
}
}
}
// 2. 太阳花
for (int row = 0; row < TOTAL_ROWS; row++) {
for (int col = 0; col < 9; col++) {
if (map[row][col].type != PLANT_SUN + 1) continue;
PlantStatus& plant = map[row][col];
plant.sunTimer++;
int interval = 150 + rand() % 150;
int sunflowerCount = 0;
for (int r = 0; r < TOTAL_ROWS; r++)
for (int c = 0; c < 9; c++)
if (map[r][c].type == PLANT_SUN + 1) sunflowerCount++;
interval = max(75, interval - (interval / 2) * sunflowerCount / 45);
if (plant.sunTimer >= interval) {
plant.sunTimer = 0;
int x = 252 + col * 78 + 30;
int y = 87 + row * 97 - 25;
for (int i = 0; i < 10; i++) {
if (!balls[i].USED) {
balls[i].USED = true;
balls[i].frameindex = 0;
balls[i].x = x;
balls[i].y = y;
balls[i].DSTY = y + 50;
balls[i].TIMER = 0;
break;
}
}
}
plant.frame++;
if (imgPlants[PLANT_SUN][plant.frame] == nullptr) {
plant.frame = 0;
}
}
}
// 3. 食人花
for (int row = 0; row < TOTAL_ROWS; row++) {
for (int col = 0; col < 9; col++) {
if (map[row][col].type == PLANT_MANEATER + 1) {
handleManeaterPlant(row, col);
}
}
}
// 4. 生成随机阳光
static int sunCount = 0;
static int sunFre = 250;
sunCount++;
if (sunCount >= sunFre) {
sunFre = 200 + rand() % 100;
sunCount = 0;
int ballMax = 10;
int i;
for (i = 0; i < ballMax && balls[i].USED; i++);
if (i >= ballMax) return;
balls[i].USED = true;
balls[i].frameindex = 0;
balls[i].x = 260 + rand() % 640;
balls[i].y = 80;
balls[i].DSTY = 550;
balls[i].TIMER = 0;
}
// 5. 阳光动画
for (int i = 0; i < 10; i++) {
if (balls[i].USED) {
balls[i].frameindex = (balls[i].frameindex + 1) % 29;
if (balls[i].TIMER == 0 && balls[i].y < balls[i].DSTY) {
balls[i].y += 2;
}
else {
balls[i].TIMER++;
if (balls[i].TIMER > 100) {
balls[i].USED = false;
}
}
}
}
}
// 初始化游戏资源
void GameInit() {
// 加载背景和工具栏
loadimage(&imgBg, "res/Map/map0.jpg");
loadimage(&imgBr, "res/bar5.png");
// 加载胜利和失败背景
if (FileExists("res/win2.png")) loadimage(&imgWinBg, "res/win2.png");
if (FileExists("res/gameFail.png")) loadimage(&imgGameOverBg, "res/gameFail.png");
// 加载卡片图片
for (int i = 0; i < TOTAL_PLANTS; i++) {
sprintf_s(pathBuffer, "res/Cards/card_%d.png", i + 1);
if (FileExists(pathBuffer)) loadimage(&imgCards[i], pathBuffer);
}
// 加载植物帧图片
for (int i = 0; i < TOTAL_PLANTS; i++) {
for (int j = 0; j < 20; j++) {
sprintf_s(pathBuffer, "res/zhiwu/%d/%d.png", i, j + 1);
if (FileExists(pathBuffer)) {
imgPlants[i][j] = new IMAGE;
loadimage(imgPlants[i][j], pathBuffer);
}
else imgPlants[i][j] = nullptr;
}
}
// 加载阳光图片
for (int i = 0; i < 29; i++) {
sprintf_s(pathBuffer, "res/Screen(sun shine)/sunshine/%d.png", i + 1);
if (FileExists(pathBuffer)) loadimage(&imgSun[i], pathBuffer);
}
// 加载僵尸图片
for (int i = 0; i < 22; i++) {
sprintf_s(pathBuffer, "res/zm/%d.png", i + 1);
if (FileExists(pathBuffer)) {
imgZombies[i] = new IMAGE;
loadimage(imgZombies[i], pathBuffer);
}
else imgZombies[i] = nullptr;
}
for (int i = 0; i < 20; i++) {
sprintf_s(pathBuffer, "res/zm_dead/%d.png", i + 1);
if (FileExists(pathBuffer)) {
imgZombieDead[i] = new IMAGE;
loadimage(imgZombieDead[i], pathBuffer);
}
else imgZombieDead[i] = nullptr;
}
for (int i = 0; i < 21; i++) {
sprintf_s(pathBuffer, "res/zm_eat/%d.png", i + 1);
if (FileExists(pathBuffer)) {
imgZombieEat[i] = new IMAGE;
loadimage(imgZombieEat[i], pathBuffer);
}
else imgZombieEat[i] = nullptr;
}
// 加载豌豆和爆炸图片
if (FileExists("res/bullets/PeaNormal/0.png")) {
loadimage(&imgPea, "res/bullets/PeaNormal/0.png");
}
if (FileExists("res/bullets/PeaNormalExplode/PeaNormalExplode_0.png")) {
loadimage(&imgExplosion, "res/bullets/PeaNormalExplode/PeaNormalExplode_0.png");
}
// 初始化数组
memset(balls, 0, sizeof(balls));
memset(zombies, 0, sizeof(zombies));
memset(peas, 0, sizeof(peas));
memset(explosions, 0, sizeof(explosions));
for (int i = 0; i < TOTAL_ROWS; i++) {
for (int j = 0; j < 9; j++) {
map[i][j].hp = 0;
map[i][j].attackCooldown = 0;
map[i][j].isEatingZombie = false;
map[i][j].eatZombieFrame = 0;
map[i][j].eatFrameTimer = 0;
}
}
for (int i = 0; i < 20; i++) zombies[i].used = false;
for (int i = 0; i < 30; i++) peas[i].used = false;
for (int i = 0; i < 15; i++) explosions[i].used = false;
// 初始化难度系统
zombieKillCount = 0;
difficultyLevel = 1;
gameState = GAME_PLAYING;
initgraph(WIDTH, HEIGHT);
srand((unsigned int)time(NULL));
PlayBGM();
}
// 获取卡片阳光消耗
int GetSunCost(int index) {
switch (index) {
case PLANT_PEA: return 100;
case PLANT_SUN: return 50;
case PLANT_MANEATER: return 150; // 食人花消耗更高
default: return 0;
}
}
// 绘制调试信息
void DrawDebugInfo() {
settextcolor(WHITE);
settextstyle(18, 0, "Consolas");
int yOffset = 10;
// 鼠标坐标
sprintf_s(debugText, "Mouse: (%d, %d)", curX, curY);
outtextxy(10, yOffset, debugText);
yOffset += 20;
// 阳光和难度信息
sprintf_s(debugText, "Sunshine: %d", sunlight);
outtextxy(10, yOffset, debugText);
yOffset += 20;
sprintf_s(debugText, "Difficulty: %d", difficultyLevel);
outtextxy(10, yOffset, debugText);
yOffset += 20;
sprintf_s(debugText, "Zombies Killed: %d/%d",
zombieKillCount, WIN_ZOMBIE_COUNT);
outtextxy(10, yOffset, debugText);
yOffset += 20;
sprintf_s(debugText, "Zombie HP: %d", GetZombieHP());
outtextxy(10, yOffset, debugText);
yOffset += 20;
// 其他统计信息
int plantCount = 0, zombieCount = 0, peaCount = 0, eatingZombieCount = 0;
for (int i = 0; i < TOTAL_ROWS; i++)
for (int j = 0; j < 9; j++)
if (map[i][j].type > 0) plantCount++;
for (int i = 0; i < 20; i++) {
if (zombies[i].used) {
zombieCount++;
if (zombies[i].isEating) eatingZombieCount++;
}
}
for (int i = 0; i < 30; i++) if (peas[i].used) peaCount++;
sprintf_s(debugText, "Plants: %d/45", plantCount);
outtextxy(10, yOffset, debugText); yOffset += 20;
sprintf_s(debugText, "Zombies: %d/20 (Eating: %d)", zombieCount, eatingZombieCount);
outtextxy(10, yOffset, debugText); yOffset += 20;
sprintf_s(debugText, "Peas: %d/30", peaCount);
outtextxy(10, yOffset, debugText); yOffset += 20;
// 选中卡片提示
if (Select_Index > 0) {
int cost = GetSunCost(Select_Index - 1);
sprintf_s(debugText, "Cost: %d Sun", cost);
settextcolor(cost > sunlight ? RED : YELLOW);
outtextxy(10, yOffset, debugText);
settextcolor(WHITE);
yOffset += 20;
}
// 音量
sprintf_s(debugText, "Volume: %d%%", currentVolume / 10);
outtextxy(10, yOffset, debugText); yOffset += 20;
// 游戏状态
const char* stateText[] = { "Playing", "You Win!", "Game Over" };
sprintf_s(debugText, "State: %s", stateText[gameState]);
outtextxy(10, yOffset, debugText); yOffset += 20;
}
// 绘制游戏元素
void DrawScene() {
BeginBatchDraw();
// 根据游戏状态绘制不同背景
if (gameState == GAME_WIN) {
putimage(0, 0, &imgWinBg);
}
else if (gameState == GAME_OVER) {
putimage(0, 0, &imgGameOverBg);
}
else {
// 正常游戏背景+工具栏
putimage(0, 0, &imgBg);
putimagePNG(260, 0, &imgBr);
// 植物卡片
for (int i = 0; i < TOTAL_PLANTS; i++) {
putimagePNG(348 + i * 67, 6, &imgCards[i]);
}
// 地图植物
for (int row = 0; row < TOTAL_ROWS; row++) {
for (int col = 0; col < 9; col++) {
if (map[row][col].type == 0) continue;
int type = map[row][col].type - 1;
int frame = map[row][col].frame;
int x = 252 + col * 78;
int y = 87 + row * 97 - 5;
// 食人花吃僵尸时显示对应的动画帧
if (type == PLANT_MANEATER && map[row][col].isEatingZombie) {
frame = map[row][col].eatZombieFrame;
}
if (imgPlants[type][frame]) {
putimagePNG(x, y, imgPlants[type][frame]);
}
}
}
// 豌豆
for (int i = 0; i < 30; i++) {
if (peas[i].used) {
putimagePNG(peas[i].x, peas[i].y, &imgPea);
}
}
// 僵尸
for (int i = 0; i < 20; i++) {
if (!zombies[i].used) continue;
Zombie& currZombie = zombies[i];
if (currZombie.isDying || currZombie.isBeingEaten) {
if (currZombie.deadFrame < 20 && imgZombieDead[currZombie.deadFrame]) {
putimagePNG(currZombie.x, currZombie.y, imgZombieDead[currZombie.deadFrame]);
}
}
else if (currZombie.isEating) {
if (currZombie.eatFrame < 21 && imgZombieEat[currZombie.eatFrame]) {
putimagePNG(currZombie.x, currZombie.y, imgZombieEat[currZombie.eatFrame]);
}
}
else {
if (imgZombies[currZombie.walkFrame]) {
putimagePNG(currZombie.x, currZombie.y, imgZombies[currZombie.walkFrame]);
}
}
}
// 爆炸特效
for (int i = 0; i < 15; i++) {
if (explosions[i].used) {
putimagePNG(explosions[i].x, explosions[i].y, &imgExplosion);
}
}
// 选中植物预览
if (Select_Index > 0) {
int type = Select_Index - 1;
if (imgPlants[type][0]) {
int w = imgPlants[type][0]->getwidth();
int h = imgPlants[type][0]->getheight();
putimagePNG(curX - w / 2, curY - h / 2, imgPlants[type][0]);
}
}
// 阳光数量
char text[10];
sprintf_s(text, "%d", sunlight);
settextcolor(WHITE);
settextstyle(30, 0, "Arial");
int tw = textwidth(text);
int tx = 273 + (335 - 273 - tw) / 2;
int ty = 71 + 1 + (95 - 71 - textheight(text)) / 2;
outtextxy(tx, ty, text);
// 阳光
for (int i = 0; i < 10; i++) {
if (balls[i].USED) {
putimagePNG(balls[i].x, balls[i].y, &imgSun[balls[i].frameindex]);
}
}
}
// 调试信息
setfillcolor(BLACK);
setlinecolor(RED);
fillrectangle(5, 5, 250, 260 + TOTAL_ROWS * 20);
DrawDebugInfo();
// 胜利/失败提示文字
if (gameState == GAME_WIN) {
settextcolor(YELLOW);
settextstyle(60, 0, "Arial Black");
outtextxy(WIDTH / 2 - textwidth("VICTORY!") / 2, HEIGHT / 2 - 30, "VICTORY!");
settextstyle(20, 0, "Arial");
outtextxy(WIDTH / 2 - textwidth("Press ESC to exit") / 2, HEIGHT / 2 + 50, "Press ESC to exit");
}
else if (gameState == GAME_OVER) {
settextcolor(RED);
settextstyle(60, 0, "Arial Black");
outtextxy(WIDTH / 2 - textwidth("GAME OVER") / 2, HEIGHT / 2 - 30, "GAME OVER");
settextstyle(20, 0, "Arial");
outtextxy(WIDTH / 2 - textwidth("Press ESC to exit") / 2, HEIGHT / 2 + 50, "Press ESC to exit");
}
EndBatchDraw();
}
// 收集阳光
void collectSunshine(ExMessage* msg) {
if (gameState != GAME_PLAYING) return;
for (int i = 0; i < 10; i++) {
if (balls[i].USED) {
int x = balls[i].x;
int y = balls[i].y;
int w = imgSun[0].getwidth();
int h = imgSun[0].getheight();
if (msg->x > x && msg->x < x + w && msg->y > y && msg->y < y + h) {
balls[i].USED = false;
sunlight += 25;
PlaySoundEffect("res/audio/points.mp3", "sunshine");
}
}
}
}
// 处理鼠标操作
void HandleMouse() {
if (gameState != GAME_PLAYING) return;
ExMessage msg;
static bool isPlantSelected = false;
while (peekmessage(&msg, EX_MOUSE)) {
switch (msg.message) {
case WM_LBUTTONDOWN:
if (!isPlantSelected) {
// 选中植物卡片
if (msg.x >= 350 && msg.x <= 350 + TOTAL_PLANTS * 67 && msg.y >= 6 && msg.y <= 96) {
int index = (msg.x - 350) / 65;
int cost = GetSunCost(index);
if (sunlight >= cost) {
Select_Index = index + 1;
isPlantSelected = true;
curX = msg.x;
curY = msg.y;
}
}
}
else {
// 放置植物
if (msg.x >= 250 && msg.x <= 250 + 9 * 78 && msg.y >= 52 && msg.y <= 52 + TOTAL_ROWS * 97) {
int row = (msg.y - 78) / 97;
int col = (msg.x - 250) / 78;
if (row >= 0 && row < TOTAL_ROWS && col >= 0 && col < 9 && map[row][col].type == 0) {
int cost = GetSunCost(Select_Index - 1);
if (sunlight >= cost) {
PlaySoundEffect("res/audio/plant1.mp3", "plant1");
isPlantSoundPlaying = true;
plantSoundStartTime = GetTickCount();
map[row][col].type = Select_Index;
map[row][col].frame = 0;
map[row][col].bulletTimer = GetTickCount();
map[row][col].hp = PLANT_HP;
map[row][col].attackCooldown = 0;
map[row][col].isEatingZombie = false;
map[row][col].eatZombieFrame = 0;
map[row][col].eatFrameTimer = 0;
if (Select_Index == PLANT_SUN + 1) {
map[row][col].sunTimer = rand() % 100;
}
sunlight -= cost;
}
}
}
isPlantSelected = false;
Select_Index = 0;
}
collectSunshine(&msg);
break;
case WM_MOUSEMOVE:
curX = msg.x;
curY = msg.y;
break;
}
}
}
// 检查植物放置声音
void CheckPlantSound() {
if (isPlantSoundPlaying) {
ULONGLONG currentTime = GetTickCount();
if (currentTime - plantSoundStartTime >= PLANT_SOUND_DURATION) {
mciSendString("close plant1", NULL, 0, NULL);
isPlantSoundPlaying = false;
}
}
}
// 处理音量控制
void HandleVolumeKeys() {
ULONGLONG currentTime = GetTickCount();
if ((GetAsyncKeyState(VK_LEFT) & 0x8000) && (currentTime - lastKeyPressTime > KEY_DELAY)) {
currentVolume = max(0, currentVolume - VOLUME_STEP);
char cmd[32];
sprintf_s(cmd, "setaudio bgm volume to %d", currentVolume);
mciSendString(cmd, NULL, 0, NULL);
lastKeyPressTime = currentTime;
}
if ((GetAsyncKeyState(VK_RIGHT) & 0x8000) && (currentTime - lastKeyPressTime > KEY_DELAY)) {
currentVolume = min(1000, currentVolume + VOLUME_STEP);
char cmd[32];
sprintf_s(cmd, "setaudio bgm volume to %d", currentVolume);
mciSendString(cmd, NULL, 0, NULL);
lastKeyPressTime = currentTime;
}
}
// 释放资源
void Cleanup() {
mciSendString("stop bgm", NULL, 0, NULL);
mciSendString("close bgm", NULL, 0, NULL);
mciSendString("close plant1", NULL, 0, NULL);
mciSendString("close peaShoot", NULL, 0, NULL);
mciSendString("close explode", NULL, 0, NULL);
mciSendString("close zombieDead", NULL, 0, NULL);
mciSendString("close zombieEat", NULL, 0, NULL);
mciSendString("close maneaterEat", NULL, 0, NULL);
mciSendString("close difficultyUp", NULL, 0, NULL);
mciSendString("close zombieHit", NULL, 0, NULL);
mciSendString("close winSound", NULL, 0, NULL);
mciSendString("close loseSound", NULL, 0, NULL);
// 释放图片资源
for (int i = 0; i < TOTAL_PLANTS; i++) {
for (int j = 0; j < 20; j++) {
if (imgPlants[i][j]) { delete imgPlants[i][j]; imgPlants[i][j] = nullptr; }
}
}
for (int i = 0; i < 22; i++) {
if (imgZombies[i]) { delete imgZombies[i]; imgZombies[i] = nullptr; }
}
for (int i = 0; i < 20; i++) {
if (imgZombieDead[i]) { delete imgZombieDead[i]; imgZombieDead[i] = nullptr; }
}
for (int i = 0; i < 21; i++) {
if (imgZombieEat[i]) { delete imgZombieEat[i]; imgZombieEat[i] = nullptr; }
}
closegraph();
}
// 处理输入
void HandleInput() {
HandleMouse();
HandleVolumeKeys();
}
int main() {
GameInit();
int timer = 0;
while (true) {
timer += getDelay();
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
Cleanup();
return 0;
}
HandleInput();
CheckPlantSound();
if (timer >= 50) {
timer = 0;
// 只有在游戏进行中才更新游戏状态
if (gameState == GAME_PLAYING) {
UpdatePlants();
UpdatePeas();
UpdateZombies();
UpdateExplosions();
}
// 游戏结束时,等待音效播放完成后自动退出
else if (gameState == GAME_OVER) {
static bool checked = false;
static ULONGLONG endTime = 0;
if (!checked) {
endTime = GetTickCount() + 5000; // 5秒后自动退出
checked = true;
}
if (GetTickCount() > endTime) {
Cleanup();
return 0;
}
}
DrawScene();
}
}
return 0;
}
#pragma once
#include <graphics.h>
void putimagePNG(int picture_x, int picture_y, IMAGE* picture);
int getDelay();
#include "tools.h"
#include <graphics.h>
// 载入PNG图并去透明部分
void _putimagePNG(int picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
DWORD* dst = GetImageBuffer(); // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
DWORD* draw = GetImageBuffer();
DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
int graphWidth = getwidth(); //获取绘图区的宽度,EASYX自带
int graphHeight = getheight(); //获取绘图区的高度,EASYX自带
int dstX = 0; //在显存里像素的角标
// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
for (int iy = 0; iy < picture_height; iy++)
{
for (int ix = 0; ix < picture_width; ix++)
{
int srcX = ix + iy * picture_width; //在显存里像素的角标
int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
int sg = ((src[srcX] & 0xff00) >> 8); //G
int sb = src[srcX] & 0xff; //B
if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
{
dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
int dr = ((dst[dstX] & 0xff0000) >> 16);
int dg = ((dst[dstX] & 0xff00) >> 8);
int db = dst[dstX] & 0xff;
draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)
| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)
| (sb * sa / 255 + db * (255 - sa) / 255);
}
}
}
}
// 适用于 y <0 以及x<0的任何情况
void putimagePNG(int x, int y, IMAGE* picture) {
IMAGE imgTmp, imgTmp2, imgTmp3;
int winWidth = getwidth();
int winHeight = getheight();
if (y < 0) {
SetWorkingImage(picture);
getimage(&imgTmp, 0, -y,
picture->getwidth(), picture->getheight() + y);
SetWorkingImage();
y = 0;
picture = &imgTmp;
}
else if (y >= getheight() || x >= getwidth()) {
return;
}
else if (y + picture->getheight() > winHeight) {
SetWorkingImage(picture);
getimage(&imgTmp, x, y, picture->getwidth(), winHeight - y);
SetWorkingImage();
picture = &imgTmp;
}
if (x < 0) {
SetWorkingImage(picture);
getimage(&imgTmp2, -x, 0, picture->getwidth() + x, picture->getheight());
SetWorkingImage();
x = 0;
picture = &imgTmp2;
}
if (x > winWidth - picture->getwidth()) {
SetWorkingImage(picture);
getimage(&imgTmp3, 0, 0, winWidth - x, picture->getheight());
SetWorkingImage();
picture = &imgTmp3;
}
_putimagePNG(x, y, picture);
}
int getDelay() {
static unsigned long long lastTime = 0;
unsigned long long currentTime = GetTickCount();
if (lastTime == 0) {
lastTime = currentTime;
return 0;
}
else {
int ret = currentTime - lastTime;
lastTime = currentTime;
return ret;
}
}
DWORD* GetImageBuffer(IMAGE* pImg)
{
return GetImageBuffer(pImg);
}
我学废了自己写的!!!!!植物大战僵尸看得懂的自己找素材
这里空空如也














有帮助,赞一个