基于SDL的超级简单的飞机大战小游戏
383
src/Game.cpp
Normal file
@ -0,0 +1,383 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#include "Game.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <SDL.h>
|
||||
#include <SDL_image.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Scence/ScenceMain.h"
|
||||
#include "Scence/ScenceTitle.h"
|
||||
|
||||
SDL_Point Game::renderTextCenter(std::string text, float posY, bool isTitle, SDL_Color color)
|
||||
{
|
||||
SDL_Surface *surface;
|
||||
if (isTitle)
|
||||
{
|
||||
surface = TTF_RenderUTF8_Solid(titleFont, text.c_str(), color);
|
||||
}else
|
||||
{
|
||||
surface = TTF_RenderUTF8_Solid(messageFont, text.c_str(), color);
|
||||
}
|
||||
|
||||
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
SDL_Rect rect = {(getWindowWidth() - surface->w)/2, static_cast<int>((getWindowHeight() - surface->h) * posY), surface->w, surface->h};
|
||||
SDL_RenderCopy(renderer, textTexture, nullptr, &rect);
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroyTexture(textTexture);
|
||||
|
||||
return {rect.x + rect.w, rect.y};
|
||||
}
|
||||
|
||||
void Game::renderTextPos(std::string text, SDL_Point p, bool isLeft, SDL_Color color)
|
||||
{
|
||||
SDL_Surface* surface = TTF_RenderUTF8_Solid(messageFont, text.c_str(), color);
|
||||
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
|
||||
SDL_Rect rect;
|
||||
if (isLeft)
|
||||
{
|
||||
rect = {p.x, p.y, surface->w, surface->h};
|
||||
}else
|
||||
{
|
||||
rect = {getWindowWidth() - p.x - surface->w, p.y, surface->w, surface->h};
|
||||
}
|
||||
|
||||
SDL_RenderCopy(renderer, textTexture, nullptr, &rect);
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroyTexture(textTexture);
|
||||
}
|
||||
|
||||
void Game::saveData()
|
||||
{
|
||||
SYSLOG_INFO("saveing data into save.dat...");
|
||||
std::ofstream file("save.dat");
|
||||
if (!file.is_open())
|
||||
{
|
||||
SDL_Log("Failed to open save.dat file!");
|
||||
return;
|
||||
}
|
||||
for (auto& entity : scoreBoard)
|
||||
{
|
||||
file << entity.first << " " << entity.second << std::endl;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void Game::loadData()
|
||||
{
|
||||
SYSLOG_INFO("loading data from save.dat...");
|
||||
std::ifstream file("save.dat");
|
||||
if (!file.is_open())
|
||||
{
|
||||
SYSLOG_WARN("Failed to open save.dat file! Try to Create one...");
|
||||
return;
|
||||
}
|
||||
scoreBoard.clear();
|
||||
int score = 0;
|
||||
std::string name;
|
||||
|
||||
while (file >> score >> name)
|
||||
{
|
||||
scoreBoard.insert({score, name});
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
Game::Game()
|
||||
{
|
||||
SYSLOG_INFO("init game...");
|
||||
}
|
||||
|
||||
void Game::insertIntoScoreBoard(int score, std::string name)
|
||||
{
|
||||
scoreBoard.insert({score, name});
|
||||
if (scoreBoard.size() > 7)
|
||||
{
|
||||
scoreBoard.erase(--scoreBoard.end());
|
||||
}
|
||||
}
|
||||
|
||||
Game& Game::getInstance()
|
||||
{
|
||||
static Game instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Game::~Game()
|
||||
{
|
||||
saveData();
|
||||
clean();
|
||||
}
|
||||
|
||||
void Game::init()
|
||||
{
|
||||
|
||||
this->frameTime = 1000.f/FPS;
|
||||
// Init SDL
|
||||
SYSLOG_INFO("init SDL...");
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
|
||||
{
|
||||
SYSLOG_ERROR("SDL_Init Error: {}", SDL_GetError());
|
||||
// SYSLOG_ERROR("SDL_Init Error: {}", SDL_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
SYSLOG_INFO("SDL_Init OK");
|
||||
|
||||
// Init window
|
||||
SYSLOG_INFO("creating window...");
|
||||
this->window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, this->windowWidth, this->windowHeight, SDL_WINDOW_SHOWN);
|
||||
if (this->window == nullptr)
|
||||
{
|
||||
SYSLOG_ERROR("SDL_CreateWindow error: {}", SDL_GetError());
|
||||
// SYSLOG_ERROR("SDL_CreateWindow error: {}", SDL_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
SYSLOG_INFO("window created");
|
||||
|
||||
|
||||
// Init Renderer
|
||||
SYSLOG_INFO("creating renderer...");
|
||||
renderer = SDL_CreateRenderer(this->window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (renderer == nullptr)
|
||||
{
|
||||
SYSLOG_ERROR("SDL_CreateRenderer error: {}", SDL_GetError());
|
||||
// SYSLOG_ERROR("SDL_CreateRenderer error: {}", SDL_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
SYSLOG_INFO("renderer created");
|
||||
|
||||
|
||||
// setting logical dpi
|
||||
SDL_RenderSetLogicalSize(renderer, this->windowWidth, this->windowHeight);
|
||||
|
||||
SYSLOG_INFO("init IMG_Init...");
|
||||
if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG)
|
||||
{
|
||||
SYSLOG_ERROR("IMG_Init Error: {}", IMG_GetError());
|
||||
// SYSLOG_ERROR("IMG_Init Error: {}", IMG_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
SYSLOG_INFO("SDL imgs init OK");
|
||||
|
||||
// ttf
|
||||
SYSLOG_INFO("init SDL_ttf...");
|
||||
if (TTF_Init() == -1)
|
||||
{
|
||||
SYSLOG_ERROR("TTF_Init: {}", TTF_GetError());
|
||||
// SYSLOG_ERROR("TTF_Init: {}", TTF_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
SYSLOG_INFO("SDL_ttf init OK");
|
||||
|
||||
|
||||
SYSLOG_INFO("init Mix_Init...");
|
||||
// Init Audio
|
||||
if (Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG) != (MIX_INIT_MP3 | MIX_INIT_OGG))
|
||||
{
|
||||
SYSLOG_ERROR("Mix_Init Error: {}", Mix_GetError());
|
||||
// SYSLOG_ERROR("Mix_Init Error: {}", Mix_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
||||
SYSLOG_ERROR("Mix_OpenAudio error: {}", Mix_GetError());
|
||||
// SYSLOG_ERROR("Mix_OpenAudio error: {}", Mix_GetError());
|
||||
}
|
||||
SYSLOG_INFO("Audio Init OK");
|
||||
|
||||
Mix_AllocateChannels(32);
|
||||
|
||||
Mix_VolumeMusic(MIX_MAX_VOLUME / 4);
|
||||
Mix_Volume(-1, MIX_MAX_VOLUME / 8);
|
||||
|
||||
SYSLOG_INFO("loading assets....");
|
||||
nearBackground.texture = IMG_LoadTexture(renderer, "assets/Stars-A.png");
|
||||
SDL_QueryTexture(nearBackground.texture, NULL, NULL, &nearBackground.width, &nearBackground.height);
|
||||
nearBackground.width /= 2;
|
||||
nearBackground.height /= 2;
|
||||
nearBackground.speed = 30;
|
||||
|
||||
farBackground.texture = IMG_LoadTexture(renderer, "assets/Stars-B.png");
|
||||
SDL_QueryTexture(farBackground.texture, NULL, NULL, &farBackground.width, &farBackground.height);
|
||||
farBackground.width /= 2;
|
||||
farBackground.height /= 2;
|
||||
farBackground.speed = 20;
|
||||
|
||||
titleFont = TTF_OpenFont("assets/Minecraft.ttf", 64);
|
||||
messageFont = TTF_OpenFont("assets/Minecraft.ttf", 32);
|
||||
if (titleFont == nullptr | messageFont == nullptr)
|
||||
{
|
||||
SYSLOG_ERROR("TTF_OpenFont: {}", TTF_GetError());
|
||||
this->running = false;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
SYSLOG_INFO("Game Init OK");
|
||||
currentScence = new ScenceTitle();
|
||||
currentScence->init();
|
||||
}
|
||||
|
||||
void Game::clean()
|
||||
{
|
||||
SYSLOG_INFO("Clean resources...");
|
||||
if (currentScence != nullptr)
|
||||
{
|
||||
currentScence->clean();
|
||||
delete currentScence;
|
||||
}
|
||||
|
||||
if (nearBackground.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(nearBackground.texture);
|
||||
}
|
||||
if (farBackground.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(farBackground.texture);
|
||||
}
|
||||
|
||||
// Audio
|
||||
Mix_CloseAudio();
|
||||
Mix_Quit();
|
||||
|
||||
// Img
|
||||
IMG_Quit();
|
||||
|
||||
// TTF
|
||||
if (titleFont != nullptr)
|
||||
{
|
||||
TTF_CloseFont(titleFont);
|
||||
}
|
||||
if (messageFont != nullptr)
|
||||
{
|
||||
TTF_CloseFont(messageFont);
|
||||
}
|
||||
|
||||
TTF_Quit();
|
||||
|
||||
SDL_DestroyRenderer(this->renderer);
|
||||
SDL_DestroyWindow(this->window);
|
||||
SDL_Quit();
|
||||
SYSLOG_INFO("clean resource OK");
|
||||
}
|
||||
|
||||
void Game::run()
|
||||
{
|
||||
while (running)
|
||||
{
|
||||
const auto startTime = SDL_GetTicks();
|
||||
|
||||
SDL_Event event;
|
||||
handleEvents(event);
|
||||
update(deltaTime);
|
||||
render();
|
||||
|
||||
const auto endTime = SDL_GetTicks();
|
||||
deltaTime = endTime - startTime;
|
||||
auto diff = endTime - startTime;
|
||||
if (diff < frameTime)
|
||||
{
|
||||
SDL_Delay(frameTime - diff);
|
||||
deltaTime = frameTime / 1000.f;
|
||||
}else
|
||||
{
|
||||
deltaTime = diff / 1000.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Game::changeScence(Scence* scence)
|
||||
{
|
||||
if (currentScence != nullptr)
|
||||
{
|
||||
currentScence->clean();
|
||||
delete currentScence;
|
||||
}
|
||||
currentScence = scence;
|
||||
scence->init();
|
||||
GAMELOG_TRACE("changing scence: {}", scence->name());
|
||||
}
|
||||
|
||||
void Game::handleEvents(SDL_Event &e)
|
||||
{
|
||||
while (SDL_PollEvent(&e))
|
||||
{
|
||||
if (e.type == SDL_QUIT)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_F4)
|
||||
{
|
||||
isFullScreen = isFullScreen ? SDL_FALSE : SDL_TRUE;
|
||||
SDL_SetWindowFullscreen(window, isFullScreen);
|
||||
}
|
||||
}
|
||||
currentScence->handleEvents(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::update(float deltatime)
|
||||
{
|
||||
bgUpdate(deltatime);
|
||||
currentScence->update(deltatime);
|
||||
}
|
||||
|
||||
void Game::render()
|
||||
{
|
||||
// clear Screen
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
|
||||
// render bg
|
||||
renderBackground();
|
||||
|
||||
|
||||
currentScence->render();
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
void Game::bgUpdate(float deltatime)
|
||||
{
|
||||
nearBackground.offset += nearBackground.speed * deltatime;
|
||||
if (nearBackground.offset >= 0)
|
||||
{
|
||||
nearBackground.offset -= nearBackground.height;
|
||||
}
|
||||
|
||||
farBackground.offset += farBackground.speed * deltatime;
|
||||
if (farBackground.offset >= 1)
|
||||
{
|
||||
farBackground.offset -= farBackground.height;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::renderBackground()
|
||||
{
|
||||
for (int posY = static_cast<int>(farBackground.offset);posY < getWindowHeight(); posY+= farBackground.height)
|
||||
{
|
||||
for (int posX = 0;posX < getWindowWidth(); posX += farBackground.width)
|
||||
{
|
||||
SDL_Rect rect = {posX, posY, farBackground.width, farBackground.height};
|
||||
SDL_RenderCopy(renderer, farBackground.texture, nullptr, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
for (int posY = static_cast<int>(nearBackground.offset);posY < getWindowHeight(); posY+= nearBackground.height)
|
||||
{
|
||||
for (int posX = 0;posX < getWindowWidth(); posX+= nearBackground.width)
|
||||
{
|
||||
SDL_Rect rect = {posX, posY, nearBackground.width, nearBackground.height};
|
||||
SDL_RenderCopy(renderer, nearBackground.texture, nullptr, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
89
src/Game.h
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
#include <map>
|
||||
#include <SDL_render.h>
|
||||
#include <SDL_ttf.h>
|
||||
#include <string>
|
||||
|
||||
#include "Object.h"
|
||||
#include "Scence/Scence.h"
|
||||
|
||||
|
||||
class Game {
|
||||
public:
|
||||
static Game& getInstance();
|
||||
~Game();
|
||||
|
||||
void init();
|
||||
void run();
|
||||
void changeScence(Scence* scence);
|
||||
|
||||
void handleEvents(SDL_Event &e);
|
||||
void update(float deltatime);
|
||||
void render();
|
||||
void bgUpdate(float deltatime);
|
||||
void renderBackground();
|
||||
|
||||
SDL_Window* getWindow() const { return window; }
|
||||
SDL_Renderer* getRenderer() const { return renderer; }
|
||||
int getWindowWidth() const { return windowWidth; }
|
||||
int getWindowHeight() const { return windowHeight; }
|
||||
|
||||
//renderText
|
||||
SDL_Point renderTextCenter(std::string text, float posY, bool isTitle = false, SDL_Color color = {255, 255, 255, 255});
|
||||
void renderTextPos(std::string text, SDL_Point p, bool isLeft = true, SDL_Color color = {255, 255, 255, 255});
|
||||
|
||||
|
||||
|
||||
void insertIntoScoreBoard(int score, std::string name);
|
||||
//getter
|
||||
int getFinalScore() const { return finalScore; }
|
||||
|
||||
std::multimap<int, std::string, std::greater<int>> &getScoreBoard() { return scoreBoard; };
|
||||
//setter
|
||||
void setFinalScore(int finalScore) { this->finalScore = finalScore; }
|
||||
|
||||
void saveData();
|
||||
void loadData();
|
||||
|
||||
private:
|
||||
Game();
|
||||
Game(const Game&) = delete;
|
||||
Game& operator=(const Game&) = delete;
|
||||
|
||||
void clean();
|
||||
|
||||
bool running = true;
|
||||
SDL_bool isFullScreen = SDL_FALSE;
|
||||
Scence* currentScence = nullptr;
|
||||
SDL_Window* window = nullptr;
|
||||
SDL_Renderer* renderer = nullptr;
|
||||
|
||||
int windowWidth = 800;
|
||||
int windowHeight = 600;
|
||||
|
||||
int finalScore = 0;
|
||||
|
||||
TTF_Font *titleFont = nullptr;
|
||||
TTF_Font *messageFont = nullptr;
|
||||
|
||||
|
||||
int FPS = 60;
|
||||
float frameTime;
|
||||
float deltaTime = 0; // s
|
||||
|
||||
Background nearBackground;
|
||||
Background farBackground;
|
||||
|
||||
|
||||
|
||||
std::multimap<int, std::string, std::greater<int>> scoreBoard;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //GAME_H
|
||||
51
src/Log.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/15.
|
||||
//
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
std::shared_ptr<spdlog::logger> Log::sys_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> Log::game_logger = nullptr;
|
||||
|
||||
void Log::init()
|
||||
{
|
||||
|
||||
if (sys_logger == nullptr)
|
||||
{
|
||||
sys_logger = spdlog::stdout_color_mt("sys");
|
||||
sys_logger->set_level(spdlog::level::level_enum::trace);
|
||||
sys_logger->flush_on(spdlog::level::level_enum::trace);
|
||||
|
||||
|
||||
sys_logger->set_pattern("%^[%n][%H:%M:%S]%v%$");
|
||||
}
|
||||
|
||||
if (game_logger == nullptr)
|
||||
{
|
||||
game_logger = spdlog::stdout_color_mt("game");
|
||||
game_logger->set_level(spdlog::level::level_enum::trace);
|
||||
game_logger->flush_on(spdlog::level::level_enum::trace);
|
||||
|
||||
game_logger->set_pattern("%^[%n][%H:%M:%S]%v%$");
|
||||
}
|
||||
SYSLOG_INFO("Log system init complete!");
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>& Log::GetSysLOG()
|
||||
{
|
||||
if (sys_logger == nullptr) {
|
||||
throw std::runtime_error("Logger not initialized. Call GlobalLogger::Init() first.");
|
||||
}
|
||||
return sys_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>& Log::GetGameLOG()
|
||||
{
|
||||
if (game_logger == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Logger not initialized. Call GlobalLogger::Init() first.");
|
||||
}
|
||||
return game_logger;
|
||||
}
|
||||
41
src/Log.h
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/15.
|
||||
//
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
#include <memory>
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
|
||||
class Log {
|
||||
public:
|
||||
static void init();
|
||||
static std::shared_ptr<spdlog::logger> &GetSysLOG();
|
||||
static std::shared_ptr<spdlog::logger> &GetGameLOG();
|
||||
|
||||
private:
|
||||
static std::shared_ptr<spdlog::logger> sys_logger;
|
||||
static std::shared_ptr<spdlog::logger> game_logger;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#define SYSLOG_TRACE(format, ...) Log::GetSysLOG()->trace(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define SYSLOG_DEBUG(format, ...) Log::GetSysLOG()->debug(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define SYSLOG_INFO(format, ...) Log::GetSysLOG()->info(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define SYSLOG_WARN(format, ...) Log::GetSysLOG()->warn(FMT_STRING("[{}:{}]:" format), __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#define SYSLOG_ERROR(format, ...) Log::GetSysLOG()->error(FMT_STRING("[{}:{}]:" format), __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#define SYSLOG_CRITICAL(format, ...) Log::GetSysLOG()->critical(FMT_STRING("[{}:{}]:" format), __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
|
||||
#define GAMELOG_TRACE(format, ...) Log::GetGameLOG()->trace(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define GAMELOG_DEBUG(format, ...) Log::GetGameLOG()->debug(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define GAMELOG_INFO(format, ...) Log::GetGameLOG()->info(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define GAMELOG_WARN(format, ...) Log::GetGameLOG()->warn(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define GAMELOG_ERROR(format, ...) Log::GetGameLOG()->error(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
#define GAMELOG_CRITICAL(format, ...) Log::GetGameLOG()->critical(FMT_STRING("{}" format), ":", ##__VA_ARGS__)
|
||||
|
||||
|
||||
#endif //LOG_H
|
||||
109
src/Object.h
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#ifndef OBJECT_H
|
||||
#define OBJECT_H
|
||||
|
||||
enum class ItemType
|
||||
{
|
||||
Life,
|
||||
Shield,
|
||||
Time
|
||||
};
|
||||
|
||||
struct Item
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = {0, 0};
|
||||
SDL_FPoint direction = { 0, 0};
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 200;
|
||||
int bounce = 3;
|
||||
|
||||
ItemType type = ItemType::Life;
|
||||
};
|
||||
|
||||
struct Player
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 300;
|
||||
|
||||
int health = 3;
|
||||
|
||||
Uint32 coolDown = 200;
|
||||
Uint32 lastShootTime = 0;
|
||||
};
|
||||
|
||||
|
||||
struct Enemy
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 200;
|
||||
|
||||
int health = 2;
|
||||
|
||||
Uint32 coolDown = 3000;
|
||||
Uint32 lastShootTime = 0;
|
||||
};
|
||||
|
||||
|
||||
struct BulletPlayer
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 400;
|
||||
|
||||
int damage = 1;
|
||||
};
|
||||
|
||||
|
||||
struct BulletEnemy
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
SDL_FPoint direction = { 0, 0 };
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 300;
|
||||
|
||||
int damage = 1;
|
||||
};
|
||||
|
||||
|
||||
struct Explosion
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
int currentFrame = 0;
|
||||
int totalFrames = 0;
|
||||
|
||||
Uint32 stratTime = 0;
|
||||
Uint32 FPS = 10;
|
||||
};
|
||||
|
||||
struct Background
|
||||
{
|
||||
SDL_Texture* texture = nullptr;
|
||||
SDL_FPoint position = { 0, 0 };
|
||||
float offset = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int speed = 30;
|
||||
};
|
||||
|
||||
#endif //OBJECT_H
|
||||
|
||||
11
src/Scence/Scence.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#include "Scence.h"
|
||||
|
||||
#include "../Game.h"
|
||||
|
||||
Scence::Scence(): game(Game::getInstance())
|
||||
{
|
||||
}
|
||||
34
src/Scence/Scence.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#ifndef SCENCE_H
|
||||
#define SCENCE_H
|
||||
#include <SDL_events.h>
|
||||
#include <string>
|
||||
|
||||
class Game;
|
||||
|
||||
class Scence {
|
||||
public:
|
||||
Scence();
|
||||
virtual ~Scence() = default;
|
||||
|
||||
virtual void init() = 0;
|
||||
virtual void update(float deltatime) = 0;
|
||||
virtual void render() = 0;
|
||||
virtual void clean() = 0;
|
||||
virtual void handleEvents(SDL_Event &e) = 0;
|
||||
|
||||
public:
|
||||
std::string name() { return this->thisName; }
|
||||
|
||||
protected:
|
||||
Game& game;
|
||||
|
||||
std::string thisName;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //SCENCE_H
|
||||
162
src/Scence/ScenceEnd.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/14.
|
||||
//
|
||||
|
||||
#include "ScenceEnd.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ScenceMain.h"
|
||||
#include "../Game.h"
|
||||
#include "../Log.h"
|
||||
|
||||
void ScenceEnd::init()
|
||||
{
|
||||
thisName = "SenceEnd";
|
||||
|
||||
GAMELOG_TRACE("ScenceEnd Initializing...");
|
||||
if (!SDL_IsTextInputActive())
|
||||
{
|
||||
SDL_StartTextInput();
|
||||
}
|
||||
if (!SDL_IsTextInputActive())
|
||||
{
|
||||
SYSLOG_ERROR("failed to start text input: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceEnd::update(float deltatime)
|
||||
{
|
||||
blinkTimer += deltatime;
|
||||
if (blinkTimer > blinkFrequency)
|
||||
{
|
||||
blinkTimer -= blinkFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceEnd::render()
|
||||
{
|
||||
if (isTyping)
|
||||
{
|
||||
renderPhase1();
|
||||
}else
|
||||
{
|
||||
renderPhase2();
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceEnd::clean()
|
||||
{
|
||||
}
|
||||
|
||||
void ScenceEnd::backspaceString()
|
||||
{
|
||||
if (name.empty())
|
||||
return;
|
||||
|
||||
auto lastchar = name.back();
|
||||
if ((lastchar & 0b10000000) == 0b10000000)
|
||||
{
|
||||
name.pop_back();
|
||||
while ((name.back() & 0b11000000) != 0b11000000)
|
||||
{
|
||||
name.pop_back();
|
||||
}
|
||||
}
|
||||
name.pop_back();
|
||||
|
||||
}
|
||||
|
||||
void ScenceEnd::handleEvents(SDL_Event& e)
|
||||
{
|
||||
if (isTyping)
|
||||
{
|
||||
if (e.type == SDL_TEXTINPUT)
|
||||
{
|
||||
name += e.text.text;
|
||||
}
|
||||
if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_RETURN)
|
||||
{
|
||||
isTyping = false;
|
||||
SDL_StopTextInput();
|
||||
if (name.empty())
|
||||
{
|
||||
name = "匿名";
|
||||
}
|
||||
|
||||
game.insertIntoScoreBoard(game.getFinalScore(), name);
|
||||
|
||||
}
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_BACKSPACE)
|
||||
{
|
||||
if (name.length() > 0)
|
||||
{
|
||||
backspaceString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}else
|
||||
{
|
||||
if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_J)
|
||||
{
|
||||
GAMELOG_TRACE("restart game");
|
||||
auto scenceMain = new ScenceMain();
|
||||
game.changeScence(scenceMain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceEnd::renderPhase1()
|
||||
{
|
||||
auto score = game.getFinalScore();
|
||||
std::string scoretext = "你的得分是: " + std::to_string(score);
|
||||
std::string gameOverText = "Game Over";
|
||||
std::string typeInNameText = "请输入名字, 回车确认:";
|
||||
|
||||
game.renderTextCenter(gameOverText, 0.3f, true);
|
||||
game.renderTextCenter(scoretext, 0.5f );
|
||||
game.renderTextCenter(typeInNameText, 0.7f);
|
||||
|
||||
if (name != "")
|
||||
{
|
||||
SDL_Point p = game.renderTextCenter(name, 0.8f);
|
||||
if (blinkTimer < 0.5f)
|
||||
{
|
||||
game.renderTextPos("_", p);
|
||||
}
|
||||
}else
|
||||
{
|
||||
if (blinkTimer < 0.5f)
|
||||
{
|
||||
game.renderTextCenter("_", 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceEnd::renderPhase2()
|
||||
{
|
||||
game.renderTextCenter("得分榜", 0.1f, true);
|
||||
|
||||
auto posY = 0.25f * game.getWindowHeight();
|
||||
|
||||
int i = 1;
|
||||
for (auto item : game.getScoreBoard())
|
||||
{
|
||||
std::string name =std::to_string(i++) + ". " + item.second;
|
||||
std::string score= std::to_string(item.first);
|
||||
game.renderTextPos(name, {100, static_cast<int>(posY)});
|
||||
game.renderTextPos(score, {100, static_cast<int>(posY)}, false);
|
||||
|
||||
posY += 50 ;
|
||||
}
|
||||
|
||||
if (blinkTimer < 0.5f)
|
||||
{
|
||||
game.renderTextCenter("Press J to Restart Game!", 0.9f);
|
||||
}
|
||||
}
|
||||
36
src/Scence/ScenceEnd.h
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/14.
|
||||
//
|
||||
|
||||
#ifndef SCENCEEND_H
|
||||
#define SCENCEEND_H
|
||||
#include <string>
|
||||
|
||||
#include "Scence.h"
|
||||
|
||||
|
||||
class ScenceEnd : public Scence{
|
||||
public:
|
||||
void init() override;
|
||||
void update(float deltatime) override;
|
||||
void render() override;
|
||||
void clean() override;
|
||||
void backspaceString();
|
||||
void handleEvents(SDL_Event& e) override;
|
||||
|
||||
private:
|
||||
void renderPhase1();
|
||||
void renderPhase2();
|
||||
|
||||
private:
|
||||
bool isTyping = true;
|
||||
|
||||
float blinkFrequency = 1.0f;
|
||||
float blinkTimer = 0;
|
||||
|
||||
std::string name = "";
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //SCENCEEND_H
|
||||
708
src/Scence/ScenceMain.cpp
Normal file
@ -0,0 +1,708 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#include "ScenceMain.h"
|
||||
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include "ScenceEnd.h"
|
||||
#include "ScenceTitle.h"
|
||||
#include "../Game.h"
|
||||
#include "../Log.h"
|
||||
|
||||
|
||||
void ScenceMain::init()
|
||||
{
|
||||
GAMELOG_TRACE("ScenceMain Initializing...");
|
||||
|
||||
thisName = "ScenceMain";
|
||||
|
||||
// loading Music
|
||||
bgm = Mix_LoadMUS("assets/Zedd,Alessia Cara - Stay.ogg");
|
||||
if (bgm == nullptr)
|
||||
{
|
||||
SYSLOG_ERROR("Failed to load Music: {}" ,Mix_GetError());
|
||||
}else
|
||||
{
|
||||
Mix_PlayMusic(bgm, -1);
|
||||
}
|
||||
|
||||
// loading Font
|
||||
scoreFont = TTF_OpenFont("assets/Minecraft.ttf", 24);
|
||||
|
||||
sounds["player_shoot"] = Mix_LoadWAV("assets/laser_shoot4.wav");
|
||||
sounds["enemy_shoot"] = Mix_LoadWAV("assets/xs_laser.wav");
|
||||
sounds["explosion"] = Mix_LoadWAV("assets/explosion11.wav");
|
||||
sounds["hit"] = Mix_LoadWAV("assets/eff1.wav");
|
||||
sounds["get_item"] = Mix_LoadWAV("assets/eff5.wav");
|
||||
|
||||
|
||||
std::random_device rd;
|
||||
gen = std::mt19937(rd());
|
||||
rand = std::uniform_real_distribution<float>(0.f, 1.f);
|
||||
|
||||
// Player
|
||||
GAMELOG_TRACE("ScenceMain loading assets...");
|
||||
player.texture = IMG_LoadTexture(game.getRenderer(), "assets/SpaceShip.png");
|
||||
SDL_QueryTexture(player.texture, nullptr, nullptr, &player.width, &player.height);
|
||||
player.width /= 4;
|
||||
player.height /= 4;
|
||||
player.position.x = game.getWindowWidth() / 2 - player.width / 2;
|
||||
player.position.y = game.getWindowHeight() - player.height - 10;
|
||||
|
||||
// Bullet Player
|
||||
BulletPlayerTemplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/bullet.png");
|
||||
SDL_QueryTexture(BulletPlayerTemplate.texture, nullptr, nullptr, &BulletPlayerTemplate.width, &BulletPlayerTemplate.height);
|
||||
BulletPlayerTemplate.width /= 4;
|
||||
BulletPlayerTemplate.height /= 4;
|
||||
|
||||
// Enemy
|
||||
EnemyTemplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/insect-1.png");
|
||||
SDL_QueryTexture(EnemyTemplate.texture, nullptr, nullptr, &EnemyTemplate.width, &EnemyTemplate.height);
|
||||
EnemyTemplate.width /= 4;
|
||||
EnemyTemplate.height /= 4;
|
||||
|
||||
// Bullet Enemy
|
||||
BulletEnemyTemplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/bullet-1.png");
|
||||
SDL_QueryTexture(BulletEnemyTemplate.texture, nullptr, nullptr, &BulletEnemyTemplate.width, &BulletEnemyTemplate.height);
|
||||
BulletEnemyTemplate.width /= 4;
|
||||
BulletEnemyTemplate.height /= 4;
|
||||
|
||||
ExplosionTexmplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/explosion.png");
|
||||
SDL_QueryTexture(ExplosionTexmplate.texture, nullptr, nullptr, &ExplosionTexmplate.width, &ExplosionTexmplate.height);
|
||||
ExplosionTexmplate.totalFrames = ExplosionTexmplate.width / ExplosionTexmplate.height;
|
||||
ExplosionTexmplate.width = ExplosionTexmplate.height;
|
||||
|
||||
|
||||
ItemLifeTemplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/bonus_life.png");
|
||||
SDL_QueryTexture(ItemLifeTemplate.texture, nullptr, nullptr, &ItemLifeTemplate.width, &ItemLifeTemplate.height);
|
||||
ItemLifeTemplate.width /= 4;
|
||||
ItemLifeTemplate.height /= 4;
|
||||
ItemLifeTemplate.type = ItemType::Life;
|
||||
|
||||
ItemShootSpeedTemplate.texture = IMG_LoadTexture(game.getRenderer(), "assets/bonus_time.png");
|
||||
SDL_QueryTexture(ItemShootSpeedTemplate.texture, nullptr, nullptr, &ItemShootSpeedTemplate.width, &ItemShootSpeedTemplate.height);
|
||||
ItemShootSpeedTemplate.width /= 4;
|
||||
ItemShootSpeedTemplate.height /= 4;
|
||||
ItemShootSpeedTemplate.type = ItemType::Time;
|
||||
|
||||
|
||||
uiHealth = IMG_LoadTexture(game.getRenderer(), "assets/Health UI Gold.png");
|
||||
}
|
||||
|
||||
void ScenceMain::update(float deltatime)
|
||||
{
|
||||
if (!holdGame)
|
||||
{
|
||||
if (playerIsDead)
|
||||
{
|
||||
changeScenceDelay(deltatime, 3);
|
||||
}else
|
||||
{
|
||||
keyboardControl(deltatime);
|
||||
}
|
||||
updatebulletPlayer(deltatime);
|
||||
spawnEnemy();
|
||||
updateEnemy(deltatime);
|
||||
updateEnemyBullet(deltatime);
|
||||
updatePlayer(deltatime);
|
||||
updateExplosion(deltatime);
|
||||
updateItems(deltatime);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::updateExplosion(float deltatime)
|
||||
{
|
||||
auto currentTime = SDL_GetTicks();
|
||||
|
||||
for (auto it = explosions.begin(); it != explosions.end();)
|
||||
{
|
||||
auto explosion = *it;
|
||||
|
||||
explosion->currentFrame = (currentTime - explosion->stratTime) * explosion->FPS / 1000;
|
||||
if (explosion->currentFrame > explosion->totalFrames)
|
||||
{
|
||||
delete explosion;
|
||||
it = explosions.erase(it);
|
||||
}else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::renderbulletEnemy() const
|
||||
{
|
||||
for (auto bullet : bulletEnemies)
|
||||
{
|
||||
SDL_Rect rect = {static_cast<int>(bullet->position.x), static_cast<int>(bullet->position.y), bullet->width, bullet->height};
|
||||
// SDL_RenderCopy(game.getRenderer(), bullet->texture, nullptr, &rect);
|
||||
const float angle = atan2f(bullet->direction.y, bullet->direction.x) * 180.0f / M_PI - 90.0f;
|
||||
SDL_RenderCopyEx(game.getRenderer(), bullet->texture, nullptr, &rect, angle, nullptr, SDL_FLIP_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::render()
|
||||
{
|
||||
|
||||
renderbulletPlayer();
|
||||
renderbulletEnemy();
|
||||
|
||||
if (!playerIsDead)
|
||||
{
|
||||
const SDL_Rect rect = {static_cast<int>(player.position.x), static_cast<int>(player.position.y), player.width, player.height};
|
||||
SDL_RenderCopy(game.getRenderer() ,player.texture, nullptr, &rect);
|
||||
|
||||
}
|
||||
|
||||
renderEnemy();
|
||||
renderItem();
|
||||
renderExplosion();
|
||||
|
||||
renderUI();
|
||||
}
|
||||
|
||||
void ScenceMain::renderExplosion() const
|
||||
{
|
||||
for (auto explosion : explosions)
|
||||
{
|
||||
SDL_Rect src = {explosion->currentFrame * explosion->width, 0 , explosion->width, explosion->height};
|
||||
SDL_Rect dst = {static_cast<int>(explosion->position.x), static_cast<int>(explosion->position.y), explosion->width, explosion->height};
|
||||
SDL_RenderCopy(game.getRenderer(), explosion->texture, &src, &dst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ScenceMain::clean()
|
||||
{
|
||||
// Free Music;
|
||||
if (bgm != nullptr)
|
||||
{
|
||||
Mix_HaltMusic();
|
||||
Mix_FreeMusic(bgm);
|
||||
}
|
||||
|
||||
for (auto& bullet : bulletsPlayer)
|
||||
{
|
||||
if (bullet != nullptr)
|
||||
delete bullet;
|
||||
}
|
||||
bulletsPlayer.clear();
|
||||
|
||||
for (auto& enemy: enemies)
|
||||
{
|
||||
if (enemy != nullptr)
|
||||
{
|
||||
delete enemy;
|
||||
}
|
||||
}
|
||||
enemies.clear();
|
||||
|
||||
for (auto& item: bulletEnemies)
|
||||
{
|
||||
if (item != nullptr)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
bulletEnemies.clear();
|
||||
|
||||
for (auto& item: explosions)
|
||||
{
|
||||
if (item != nullptr)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
explosions.clear();
|
||||
|
||||
for (auto& item: items)
|
||||
{
|
||||
if (item != nullptr)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
items.clear();
|
||||
|
||||
if (player.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(player.texture);
|
||||
player.texture = nullptr;
|
||||
}
|
||||
|
||||
if (BulletEnemyTemplate.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(BulletEnemyTemplate.texture);
|
||||
BulletEnemyTemplate.texture = nullptr;
|
||||
}
|
||||
|
||||
if (BulletPlayerTemplate.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(BulletPlayerTemplate.texture);
|
||||
BulletPlayerTemplate.texture = nullptr;
|
||||
}
|
||||
|
||||
if (EnemyTemplate .texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(EnemyTemplate.texture);
|
||||
EnemyTemplate.texture = nullptr;
|
||||
}
|
||||
|
||||
if (ExplosionTexmplate.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(ExplosionTexmplate.texture);
|
||||
ExplosionTexmplate.texture = nullptr;
|
||||
}
|
||||
if (ItemLifeTemplate.texture != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(ItemLifeTemplate.texture);
|
||||
}
|
||||
|
||||
for (auto sound : sounds)
|
||||
{
|
||||
if (sound.second != nullptr)
|
||||
{
|
||||
Mix_FreeChunk(sound.second);
|
||||
}
|
||||
}
|
||||
sounds.clear();
|
||||
|
||||
if (uiHealth != nullptr)
|
||||
{
|
||||
SDL_DestroyTexture(uiHealth);
|
||||
}
|
||||
if (scoreFont != nullptr)
|
||||
{
|
||||
TTF_CloseFont(scoreFont);
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::handleEvents(SDL_Event& e)
|
||||
{
|
||||
if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
|
||||
{
|
||||
auto scenceTitle = new ScenceTitle();
|
||||
game.changeScence(scenceTitle);
|
||||
}
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_P)
|
||||
{
|
||||
holdGame = !holdGame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::keyboardControl(const float deltatime)
|
||||
{
|
||||
const Uint8* state = SDL_GetKeyboardState(nullptr);
|
||||
if (state[SDL_SCANCODE_W])
|
||||
{
|
||||
player.position.y -= deltatime * player.speed;
|
||||
}
|
||||
if (state[SDL_SCANCODE_S])
|
||||
{
|
||||
player.position.y += deltatime * player.speed;
|
||||
}
|
||||
if (state[SDL_SCANCODE_A])
|
||||
{
|
||||
player.position.x -= deltatime * player.speed;
|
||||
}
|
||||
if (state[SDL_SCANCODE_D])
|
||||
{
|
||||
player.position.x += deltatime * player.speed;
|
||||
}
|
||||
|
||||
|
||||
// Simple Check Collision
|
||||
if (player.position.x < 0) { player.position.x = 0; }
|
||||
if (player.position.y < 0) { player.position.y = 0; }
|
||||
if (player.position.y > game.getWindowHeight() - player.height) { player.position.y = game.getWindowHeight() - player.height; }
|
||||
if (player.position.x > game.getWindowWidth() - player.width) { player.position.x = game.getWindowWidth() - player.width; }
|
||||
|
||||
if (state[SDL_SCANCODE_J])
|
||||
{
|
||||
auto currentTime = SDL_GetTicks();
|
||||
if (currentTime - player.coolDown > player.lastShootTime)
|
||||
{
|
||||
ShootPlayer();
|
||||
player.lastShootTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void ScenceMain::ShootPlayer()
|
||||
{
|
||||
auto bullet = new BulletPlayer(BulletPlayerTemplate);
|
||||
bullet->position.x = player.position.x + player.width / 2 - bullet->width / 2;
|
||||
bullet->position.y = player.position.y;
|
||||
bulletsPlayer.push_back(bullet);
|
||||
Mix_PlayChannel(-1, sounds["player_shoot"], 0);
|
||||
}
|
||||
|
||||
void ScenceMain::updatebulletPlayer(float deltatime)
|
||||
{
|
||||
for (auto it = bulletsPlayer.begin(); it != bulletsPlayer.end(); )
|
||||
{
|
||||
auto bullet = *it;
|
||||
bullet->position.y -= deltatime * bullet->speed;
|
||||
if (bullet->position.y + bullet->height < 0)
|
||||
{
|
||||
delete bullet;
|
||||
it = bulletsPlayer.erase(it);
|
||||
}else
|
||||
{
|
||||
bool hit = false;
|
||||
for (auto& enemy : enemies)
|
||||
{
|
||||
SDL_Rect enemyRect = {static_cast<int>(enemy->position.x), static_cast<int>(enemy->position.y), enemy->width, enemy->height};
|
||||
SDL_Rect bulletRect = {static_cast<int>(bullet->position.x), static_cast<int>(bullet->position.y), bullet->width, bullet->height};
|
||||
if (SDL_HasIntersection(&enemyRect, &bulletRect))
|
||||
{
|
||||
enemy->health -= bullet->damage;
|
||||
|
||||
Mix_PlayChannel(-1, sounds["hit"], 0);
|
||||
|
||||
delete bullet;
|
||||
it = bulletsPlayer.erase(it);
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit)
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::renderbulletPlayer() const
|
||||
{
|
||||
for (auto & bullet : bulletsPlayer)
|
||||
{
|
||||
SDL_Rect rect = {static_cast<int>(bullet->position.x), static_cast<int>(bullet->position.y), bullet->width, bullet->height};
|
||||
SDL_RenderCopy(game.getRenderer(), bullet->texture, nullptr, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::spawnEnemy()
|
||||
{
|
||||
if (rand(gen) > 1 / 60.f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Enemy* enemy = new Enemy(EnemyTemplate);
|
||||
enemy->position.x = rand(gen) * (game.getWindowWidth() - enemy->width);
|
||||
enemy->position.y = - enemy->height;
|
||||
|
||||
enemies.push_back(enemy);
|
||||
|
||||
}
|
||||
|
||||
void ScenceMain::enemyExplode(Enemy* enemy)
|
||||
{
|
||||
score += 5;
|
||||
auto currentTime = SDL_GetTicks();
|
||||
auto explosion = new Explosion(ExplosionTexmplate);
|
||||
explosion->position.x = enemy->position.x + enemy->width / 2 - explosion->width / 2;
|
||||
explosion->position.y = enemy->position.y + enemy->height / 2 - explosion->height / 2;
|
||||
explosion->stratTime = currentTime;
|
||||
|
||||
explosions.push_back(explosion);
|
||||
Mix_PlayChannel(-1, sounds["explosion"], 0);
|
||||
|
||||
if (rand(gen) < 0.5f)
|
||||
{
|
||||
dropItem(enemy);
|
||||
}
|
||||
|
||||
delete enemy;
|
||||
}
|
||||
|
||||
void ScenceMain::updateEnemy(float deltatime)
|
||||
{
|
||||
for (auto it = enemies.begin(); it != enemies.end();)
|
||||
{
|
||||
|
||||
auto currentTime = SDL_GetTicks();
|
||||
auto enemy = *it;
|
||||
enemy->position.y += deltatime * enemy->speed;
|
||||
if (enemy->position.y > game.getWindowHeight())
|
||||
{
|
||||
delete enemy;
|
||||
it = enemies.erase(it);
|
||||
}else
|
||||
{
|
||||
if (enemy->health <= 0)
|
||||
{
|
||||
enemyExplode(enemy);
|
||||
it = enemies.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!playerIsDead)
|
||||
{
|
||||
if (currentTime - enemy->lastShootTime > enemy->coolDown)
|
||||
{
|
||||
shootEnemy(*enemy);
|
||||
enemy->lastShootTime = currentTime;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::renderEnemy() const
|
||||
{
|
||||
for (auto& enemy : enemies)
|
||||
{
|
||||
SDL_Rect rect = {static_cast<int>(enemy->position.x), static_cast<int>(enemy->position.y), enemy->width, enemy->height};
|
||||
SDL_RenderCopy(game.getRenderer(), enemy->texture, nullptr, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ScenceMain::shootEnemy(Enemy& enemy)
|
||||
{
|
||||
auto bullet = new BulletEnemy(BulletEnemyTemplate);
|
||||
bullet->position.x = enemy.position.x + enemy.width / 2 - bullet->width / 2;
|
||||
bullet->position.y = enemy.position.y + enemy.height / 2 - bullet->height / 2;
|
||||
bullet->direction = getDirection(enemy);
|
||||
|
||||
bulletEnemies.push_back(bullet);
|
||||
Mix_PlayChannel(-1, sounds["enemy_shoot"], 0);
|
||||
}
|
||||
|
||||
SDL_FPoint ScenceMain::getDirection(const Enemy& enemy) const
|
||||
{
|
||||
auto x = (player.position.x + player.width /2) - (enemy.position.x + enemy.width / 2);
|
||||
auto y = (player.position.y + player.height /2) - (enemy.position.y + enemy.height / 2);
|
||||
|
||||
const auto length = std::sqrt(x * x + y * y);
|
||||
x /= length;
|
||||
y /= length;
|
||||
|
||||
return SDL_FPoint{x, y};
|
||||
}
|
||||
|
||||
void ScenceMain::updateEnemyBullet(float deltatime)
|
||||
{
|
||||
for (auto it = bulletEnemies.begin(); it != bulletEnemies.end(); )
|
||||
{
|
||||
auto bullet = *it;
|
||||
bullet->position.x += bullet->speed * deltatime * bullet->direction.x;
|
||||
bullet->position.y += bullet->speed * deltatime * bullet->direction.y;
|
||||
|
||||
if (bullet->position.y > game.getWindowHeight() ||
|
||||
bullet->position.y + bullet->height < 0 ||
|
||||
bullet->position.x + bullet->width < 0 ||
|
||||
bullet->position.x > game.getWindowWidth())
|
||||
{
|
||||
delete bullet;
|
||||
it = bulletEnemies.erase(it);
|
||||
}else
|
||||
{
|
||||
SDL_Rect PlayerRect = {static_cast<int>(player.position.x), static_cast<int>(player.position.y), player.width, player.height};
|
||||
SDL_Rect bulletRect = {static_cast<int>(bullet->position.x), static_cast<int>(bullet->position.y), bullet->width, bullet->height};
|
||||
if (SDL_HasIntersection(&PlayerRect, &bulletRect) && !playerIsDead)
|
||||
{
|
||||
player.health -= bullet->damage;
|
||||
|
||||
Mix_PlayChannel(-1, sounds["hit"], 0);
|
||||
|
||||
delete bullet;
|
||||
it = bulletEnemies.erase(it);
|
||||
break;
|
||||
}else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::updatePlayer(float deltatime)
|
||||
{
|
||||
if (playerIsDead)
|
||||
{
|
||||
game.setFinalScore(score);
|
||||
return;
|
||||
}
|
||||
if (player.health <= 0)
|
||||
{
|
||||
this->playerIsDead = true;
|
||||
}
|
||||
|
||||
for (auto enemy : enemies)
|
||||
{
|
||||
SDL_Rect enemyRect = {static_cast<int>(enemy->position.x), static_cast<int>(enemy->position.y), enemy->width, enemy->height};
|
||||
SDL_Rect playerRect = {static_cast<int>(player.position.x), static_cast<int>(player.position.y), player.width, player.height};
|
||||
|
||||
if (SDL_HasIntersection(&enemyRect, &playerRect))
|
||||
{
|
||||
enemy->health = 0;
|
||||
player.health -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::dropItem(Enemy *enemy)
|
||||
{
|
||||
|
||||
auto rands = rand(gen);
|
||||
Item *item;
|
||||
if (rands < 0.3)
|
||||
{
|
||||
item = new Item(ItemLifeTemplate);
|
||||
}
|
||||
else if (rands < 0.7)
|
||||
{
|
||||
item = new Item(ItemShootSpeedTemplate);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = new Item(ItemLifeTemplate);
|
||||
}
|
||||
|
||||
item->position.x = enemy->position.x + enemy->width / 2 - item->width / 2;
|
||||
item->position.y = enemy->position.y + enemy->height / 2 - item->height / 2;
|
||||
|
||||
float angle = rand(gen) * 2 * M_PI;
|
||||
item->direction.x = cos(angle);
|
||||
item->direction.y = sin(angle);
|
||||
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
void ScenceMain::updateItems(float deltatime)
|
||||
{
|
||||
for (auto it = items.begin(); it != items.end();)
|
||||
{
|
||||
auto item = *it;
|
||||
|
||||
item->position.x += item->direction.x * item->speed * deltatime;
|
||||
item->position.y += item->direction.y * item->speed * deltatime;
|
||||
|
||||
if (item->bounce > 0)
|
||||
{
|
||||
if (item->position.x < 0 && item->direction.x < 0)
|
||||
{
|
||||
item->direction.x = -item->direction.x;
|
||||
item->bounce--;
|
||||
}
|
||||
|
||||
if (item->position.y < 0 && item->direction.y < 0)
|
||||
{
|
||||
item->direction.y = -item->direction.y;
|
||||
item->bounce--;
|
||||
}
|
||||
|
||||
if (item->position.x + item->width > game.getWindowWidth() && item->direction.x > 0)
|
||||
{
|
||||
item->direction.x = -item->direction.x;
|
||||
item->bounce--;
|
||||
}
|
||||
|
||||
if (item->position.y + item->height > game.getWindowHeight() && item->direction.y > 0)
|
||||
{
|
||||
item->direction.y = -item->direction.y;
|
||||
item->bounce--;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->direction.x + item->width < 0 ||
|
||||
item->direction.x > game.getWindowWidth() ||
|
||||
item->direction.y + item->height < 0 ||
|
||||
item->direction.y > game.getWindowHeight())
|
||||
{
|
||||
delete item;
|
||||
it = items.erase(it);
|
||||
}else
|
||||
{
|
||||
SDL_Rect itemRect = {static_cast<int>(item->position.x), static_cast<int>(item->position.y), item->width, item->height};
|
||||
SDL_Rect playerRect = {static_cast<int>(player.position.x), static_cast<int>(player.position.y), player.width, player.height};
|
||||
if (SDL_HasIntersection(&itemRect, &playerRect))
|
||||
{
|
||||
playerGetItem(item);
|
||||
delete item;
|
||||
it = items.erase(it);
|
||||
}else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::playerGetItem(Item* item)
|
||||
{
|
||||
score += 10;
|
||||
|
||||
if (item->type == ItemType::Life)
|
||||
{
|
||||
player.health +=2;
|
||||
}else if (item->type == ItemType::Time)
|
||||
{
|
||||
player.coolDown *= 0.9f;
|
||||
}
|
||||
|
||||
Mix_PlayChannel(-1, sounds["get_item"], 0);
|
||||
}
|
||||
|
||||
void ScenceMain::changeScenceDelay(float deltatime, float delay)
|
||||
{
|
||||
timerEnd += deltatime;
|
||||
if (timerEnd >= delay)
|
||||
{
|
||||
auto scenceEnd = new ScenceEnd();
|
||||
game.changeScence(scenceEnd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ScenceMain::renderItem()
|
||||
{
|
||||
for (auto item : items)
|
||||
{
|
||||
SDL_Rect rect = {static_cast<int>(item->position.x), static_cast<int>(item->position.y), item->width, item->height};
|
||||
SDL_RenderCopy(game.getRenderer(), item->texture, nullptr, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceMain::renderUI()
|
||||
{
|
||||
int x = 10;
|
||||
int y = 10;
|
||||
int size = 32;
|
||||
int offset = 40;
|
||||
|
||||
for (int i = 0; i < player.health; i++)
|
||||
{
|
||||
int ty = y;
|
||||
if (i >= 9)
|
||||
{
|
||||
ty += offset * static_cast<int>(i / 9);
|
||||
}
|
||||
SDL_Rect rect = {x + i % 9 * offset, ty, size, size};
|
||||
SDL_RenderCopy(game.getRenderer(), uiHealth, nullptr, &rect);
|
||||
}
|
||||
|
||||
auto text = "SCORE: " + std::to_string(score);
|
||||
|
||||
SDL_Surface* surface = TTF_RenderUTF8_Solid(scoreFont, text.c_str(), SDL_Color{255, 255, 255, 255});
|
||||
SDL_Texture* scoreTexture = SDL_CreateTextureFromSurface(game.getRenderer(), surface);
|
||||
SDL_Rect rect = {game.getWindowWidth() - 180, y, surface->w, surface->h};
|
||||
SDL_RenderCopy(game.getRenderer(), scoreTexture, nullptr, &rect);
|
||||
SDL_DestroyTexture(scoreTexture);
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
91
src/Scence/ScenceMain.h
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/9.
|
||||
//
|
||||
|
||||
#ifndef SCENCEMAIN_H
|
||||
#define SCENCEMAIN_H
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
#include <SDL_mixer.h>
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
#include "../Object.h"
|
||||
#include "Scence.h"
|
||||
|
||||
|
||||
class ScenceMain : public Scence{
|
||||
public:
|
||||
|
||||
void init() override;
|
||||
void update(float deltatime) override;
|
||||
void render() override;
|
||||
void clean() override;
|
||||
void handleEvents(SDL_Event &e) override;
|
||||
|
||||
private:
|
||||
void keyboardControl(float deltatime);
|
||||
|
||||
void updateItems(float deltatime);
|
||||
void updateEnemyBullet(float deltatime);
|
||||
void updatePlayer(float deltatime);
|
||||
void updateEnemy(float deltatime);
|
||||
void updatebulletPlayer(float deltatime);
|
||||
void updateExplosion(float deltatime);
|
||||
|
||||
void renderbulletEnemy() const;
|
||||
void renderExplosion() const;
|
||||
void renderbulletPlayer() const;
|
||||
void renderEnemy() const;
|
||||
void renderItem();
|
||||
void renderUI();
|
||||
|
||||
|
||||
void ShootPlayer();
|
||||
|
||||
void spawnEnemy();
|
||||
void enemyExplode(Enemy* enemy);
|
||||
void shootEnemy(Enemy& enemy);
|
||||
SDL_FPoint getDirection(const Enemy& enemy) const;
|
||||
void dropItem(Enemy* enemy);
|
||||
void playerGetItem(Item* item);
|
||||
|
||||
void changeScenceDelay(float deltatime, float delay);
|
||||
|
||||
|
||||
private:
|
||||
std::mt19937 gen;
|
||||
std::uniform_real_distribution<float> rand;
|
||||
|
||||
Mix_Music* bgm;
|
||||
std::pmr::map<std::string, Mix_Chunk*> sounds;
|
||||
|
||||
SDL_Texture* uiHealth;
|
||||
TTF_Font *scoreFont;
|
||||
int score = 0;
|
||||
|
||||
bool holdGame = false;
|
||||
|
||||
|
||||
Player player;
|
||||
bool playerIsDead = false;
|
||||
|
||||
float timerEnd = 0;
|
||||
|
||||
BulletPlayer BulletPlayerTemplate;
|
||||
Enemy EnemyTemplate;
|
||||
BulletEnemy BulletEnemyTemplate;
|
||||
Explosion ExplosionTexmplate;
|
||||
Item ItemLifeTemplate;
|
||||
Item ItemShootSpeedTemplate;
|
||||
|
||||
std::vector<BulletPlayer*> bulletsPlayer;
|
||||
std::vector<Enemy*> enemies;
|
||||
std::vector<BulletEnemy*> bulletEnemies;
|
||||
std::vector<Explosion*> explosions;
|
||||
std::vector<Item*> items;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //SCENCEMAIN_H
|
||||
61
src/Scence/ScenceTitle.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/13.
|
||||
//
|
||||
|
||||
#include "ScenceTitle.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#include "ScenceMain.h"
|
||||
#include "../Game.h"
|
||||
#include "../Log.h"
|
||||
|
||||
void ScenceTitle::init()
|
||||
{
|
||||
GAMELOG_TRACE("ScenceTitle Initializing");
|
||||
thisName = "ScenceTitle";
|
||||
|
||||
// 载入音乐并且播放
|
||||
bgm = Mix_LoadMUS("assets/Zedd,Alessia Cara - Stay.ogg");
|
||||
Mix_PlayMusic(bgm,-1);
|
||||
GAMELOG_TRACE("ScenceTitle ready");
|
||||
}
|
||||
|
||||
void ScenceTitle::update(float deltatime)
|
||||
{
|
||||
timer += deltatime;
|
||||
if (timer > 1.5f)
|
||||
{
|
||||
timer -= 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
void ScenceTitle::render()
|
||||
{
|
||||
game.renderTextCenter("SPACE WAR", 0.35f, true, {255,0,0});
|
||||
|
||||
if (timer > 0.75f)
|
||||
{
|
||||
game.renderTextCenter("Press J to Start!", 0.65f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ScenceTitle::clean()
|
||||
{
|
||||
GAMELOG_TRACE("ScenceTitle Cleaning");
|
||||
Mix_HaltMusic();
|
||||
Mix_FreeMusic(bgm);
|
||||
}
|
||||
|
||||
void ScenceTitle::handleEvents(SDL_Event& e)
|
||||
{
|
||||
if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_J)
|
||||
{
|
||||
auto scenceMain = new ScenceMain();
|
||||
game.changeScence(scenceMain);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Scence/ScenceTitle.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by sfd on 2025/4/13.
|
||||
//
|
||||
|
||||
#ifndef SCENCETITLE_H
|
||||
#define SCENCETITLE_H
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#include "Scence.h"
|
||||
|
||||
class ScenceTitle : public Scence {
|
||||
public:
|
||||
|
||||
void init() override;
|
||||
void update(float deltatime) override;
|
||||
void render()override;
|
||||
void clean()override;
|
||||
void handleEvents(SDL_Event &e)override;
|
||||
|
||||
private:
|
||||
Mix_Music *bgm;
|
||||
float timer = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //SCENCETITLE_H
|
||||
BIN
src/assets/Health UI Gold.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
src/assets/Minecraft.ttf
Normal file
BIN
src/assets/SpaceShip.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/Stars-A.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/Stars-B.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/Zedd,Alessia Cara - Stay.ogg
Normal file
BIN
src/assets/bonus_life.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/bonus_shield.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/assets/bonus_time.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/assets/bullet.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/eff1.wav
Normal file
BIN
src/assets/eff5.wav
Normal file
BIN
src/assets/explosion.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/explosion11.wav
Normal file
BIN
src/assets/insect-1.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/insect-2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/laser-1.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/laser_shoot4.wav
Normal file
BIN
src/assets/xs_laser.wav
Normal file
16
src/main.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <SDL.h>
|
||||
#include "Game.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
int main(int ,char **)
|
||||
{
|
||||
Log::init();
|
||||
|
||||
Game &game = Game::getInstance();
|
||||
|
||||
game.init();
|
||||
game.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||