Files
dl/project/dl_test/game_snake.h
T
2026-09-16 14:07:40 +08:00

599 lines
13 KiB
C++

#pragma once
#include "head.h"
#include "ui_main.h"
// trae ai 写的
constexpr Size MAP_SIZE = { 24,24 };
constexpr Size MAP_NODE_SIZE = { 12,12 };
constexpr float MOVE_SPEED = 0.15f; // 移动速度(秒/步)
enum class GameSnakeMapNodeState {
PASS,
FOOD,
BODY,
WALL
};
enum class GameSnakeDirection {
UP,
DOWN,
LEFT,
RIGHT
};
class GameSnakeMapNode {
public:
void SetNodeXy(const PointU& xy)
{
_xy = xy;
}
const PointU& GetNodeXy() const
{
return _xy;
}
void SetCoor(Coor* coor)
{
_coor = coor;
}
void SetNodeState(GameSnakeMapNodeState s)
{
_state = s;
switch (_state)
{
case GameSnakeMapNodeState::PASS:
_spr.CancelRef();
break;
case GameSnakeMapNodeState::FOOD:
{
_spr = g_factory->CreateSprite("snake.apple");
_spr->SetOrder(0.8f);
// 使用共享的Coor
_spr->SetCoor(_coor);
// 设置精灵的大小和位置
Position pos = toPosition(_xy * MAP_NODE_SIZE);
_spr->SetQuad(createQuad(pos, pos + Position{12.0f,12.0f}));
}
break;
case GameSnakeMapNodeState::BODY:
{
_spr = g_factory->CreateSprite("snake.body");
_spr->SetOrder(0.8f);
// 使用共享的Coor
_spr->SetCoor(_coor);
// 设置精灵的大小和位置
Position posBody = toPosition(_xy * MAP_NODE_SIZE);
_spr->SetQuad(createQuad(posBody, posBody + Position{12.0f,12.0f}));
}
break;
case GameSnakeMapNodeState::WALL:
{
_spr = g_factory->CreateSpriteDefault(ColorDef::GRAY);
_spr->SetOrder(0.8f);
// 使用共享的Coor
_spr->SetCoor(_coor);
// 设置精灵的大小和位置
Position posWall = toPosition(_xy * MAP_NODE_SIZE);
_spr->SetQuad(createQuad(posWall, posWall + Position{12.0f,12.0f}));
}
break;
default:
break;
}
}
GameSnakeMapNodeState GetNodeState() const
{
return _state;
}
void RenderNode()
{
if (_spr)
{
_spr->Render();
}
}
private:
GameSnakeMapNodeState _state;
PointU _xy;
Coor* _coor; // 共享的Coor对象
gcSprite _spr;
};
class GameSnakeMap {
public:
GameSnakeMap()
{
// 创建一个Coor对象,所有精灵的Coor都指向它
_coor = Coor::Create();
}
~GameSnakeMap()
{
delete _coor;
}
void Reset()
{
_vec.resize(MAP_SIZE[0] * MAP_SIZE[1]);
for (unsigned j = 0; j< MAP_SIZE[1]; ++j)
{
for (unsigned i = 0; i < MAP_SIZE[0]; ++i)
{
unsigned idx = MAP_SIZE[0] * j + i;
// 先设置节点的坐标
_vec[idx].SetNodeXy({i,j});
// 设置共享的Coor对象
_vec[idx].SetCoor(_coor);
// 边界设置为墙壁
if (i == 0 || i == MAP_SIZE[0] - 1 || j == 0 || j == MAP_SIZE[1] - 1)
{
_vec[idx].SetNodeState(GameSnakeMapNodeState::WALL);
}
else
{
_vec[idx].SetNodeState(GameSnakeMapNodeState::PASS);
}
}
}
}
void SetOffset(const Position& offset)
{
// 只需要更新Coor的位置,所有精灵的位置会自动更新
_coor->SetPosition(offset);
}
void CreateFood(const PointU& xy)
{
unsigned idx = MAP_SIZE[0] * xy[1] + xy[0];
if (idx >= _vec.size())
{
log_err("创建food下标越界:x: {},y: {}", xy[0], xy[1]);
return;
}
_vec[idx].SetNodeState(GameSnakeMapNodeState::FOOD);
}
void SetNodeState(const PointU& xy, GameSnakeMapNodeState state)
{
unsigned idx = MAP_SIZE[0] * xy[1] + xy[0];
if (idx < _vec.size())
{
_vec[idx].SetNodeState(state);
}
}
GameSnakeMapNodeState GetNodeState(const PointU& xy) const
{
unsigned idx = MAP_SIZE[0] * xy[1] + xy[0];
if (idx < _vec.size())
{
return _vec[idx].GetNodeState();
}
return GameSnakeMapNodeState::PASS;
}
void RenderMap(const std::vector<PointU>& snakeBody)
{
for (size_t i = 0; i < _vec.size(); ++i)
{
// 不渲染蛇头的位置,留待Snake::Render()渲染
bool isHead = false;
if (!snakeBody.empty())
{
PointU head = snakeBody.front();
// 检查head坐标是否有效
if (head.x < MAP_SIZE[0] && head.y < MAP_SIZE[1])
{
unsigned idx = MAP_SIZE[0] * head.y + head.x;
if (idx == i)
{
isHead = true;
}
}
}
if (!isHead)
{
_vec[i].RenderNode();
}
}
}
public:
Coor* GetCoor() const
{
return _coor;
}
private:
std::vector<GameSnakeMapNode> _vec;
Coor* _coor; // 所有精灵共享的Coor
};
class GameSnakeSnake {
public:
GameSnakeSnake()
{
// 初始位置
_body.push_back({ 8, 8 });
_body.push_back({ 7, 8 });
_body.push_back({ 6, 8 });
_dir = GameSnakeDirection::RIGHT;
_nextDir = GameSnakeDirection::RIGHT;
_moveTimer = 0.0f;
_grow = false;
}
void SetDirection(GameSnakeDirection dir)
{
// 防止180度反向移动
if ((dir == GameSnakeDirection::UP && _dir == GameSnakeDirection::DOWN) ||
(dir == GameSnakeDirection::DOWN && _dir == GameSnakeDirection::UP) ||
(dir == GameSnakeDirection::LEFT && _dir == GameSnakeDirection::RIGHT) ||
(dir == GameSnakeDirection::RIGHT && _dir == GameSnakeDirection::LEFT))
{
return;
}
_nextDir = dir;
}
bool Update(float deltaTime, GameSnakeMap& map, int& score)
{
_moveTimer += deltaTime;
if (_moveTimer < MOVE_SPEED)
{
return true;
}
_moveTimer = 0.0f;
// 更新方向
_dir = _nextDir;
// 计算新头部位置
PointU head = _body.front();
switch (_dir)
{
case GameSnakeDirection::UP:
head.y--;
break;
case GameSnakeDirection::DOWN:
head.y++;
break;
case GameSnakeDirection::LEFT:
head.x--;
break;
case GameSnakeDirection::RIGHT:
head.x++;
break;
}
// 边界检查
if (head.x >= MAP_SIZE[0] || head.y >= MAP_SIZE[1] || head.x < 0 || head.y < 0)
{
return false; // 碰撞边界,游戏结束
}
// 墙壁碰撞检查
if (map.GetNodeState(head) == GameSnakeMapNodeState::WALL)
{
return false; // 碰撞墙壁,游戏结束
}
// 自身碰撞检查
for (const auto& bodyPart : _body)
{
if (bodyPart == head)
{
return false; // 碰撞自身,游戏结束
}
}
// 检查是否吃到食物
if (map.GetNodeState(head) == GameSnakeMapNodeState::FOOD)
{
_grow = true;
score += 10; // 增加分数
// 播放吃到食物的音效
snd_play("eat");
// 创建新食物
PointU foodPos;
do
{
foodPos = {
Random::Uint(1, MAP_SIZE[0] - 2),
Random::Uint(1, MAP_SIZE[1] - 2)
};
}
while (map.GetNodeState(foodPos) != GameSnakeMapNodeState::PASS);
map.CreateFood(foodPos);
}
else
{
// 移除尾部
if (!_grow)
{
PointU tail = _body.back();
map.SetNodeState(tail, GameSnakeMapNodeState::PASS);
_body.pop_back();
}
else
{
_grow = false;
}
}
// 添加新头部
_body.insert(_body.begin(), head);
map.SetNodeState(head, GameSnakeMapNodeState::BODY);
return true;
}
void Render(Coor* coor)
{
// 渲染蛇头
if (!_body.empty())
{
PointU head = _body.front();
gcSprite head_sprite = g_factory->RenderSprite("snake.right");
head_sprite->SetOrder(0.7f);
// 使用精灵自身的AddCoor方法创建坐标系
head_sprite->SetCoor(coor);
Coor* head_coor = head_sprite->AddCoor();
// 计算蛇头的位置
Position pos = toPosition(head * MAP_NODE_SIZE);
// 根据移动方向旋转蛇头(弧度制)
float rotate = 0.0f;
switch (_dir)
{
case GameSnakeDirection::UP:
rotate = 3.1415926535f / 2.0f; // 90度
// 调整位置,使旋转后蛇头保持在正确位置
head_coor->SetPosition(pos + Position{ 12.0f, 0.0f });
break;
case GameSnakeDirection::DOWN:
rotate = 3.0f * 3.1415926535f / 2.0f; // 270度
// 调整位置,使旋转后蛇头保持在正确位置
head_coor->SetPosition(pos + Position{ 0.0f, 12.0f });
break;
case GameSnakeDirection::LEFT:
rotate = 3.1415926535f; // 180度
// 调整位置,使旋转后蛇头保持在正确位置
head_coor->SetPosition(pos + Position{ 12.0f, 12.0f });
break;
case GameSnakeDirection::RIGHT:
rotate = 0.0f; // 0度
// 不需要调整位置
head_coor->SetPosition(pos);
break;
}
head_sprite->SetQuad(createQuad(Position{ 0.0f, 0.0f }, Position{ 12.0f, 12.0f }));
head_coor->SetRotate(rotate);
}
}
const std::vector<PointU>& GetBody() const
{
return _body;
}
private:
std::vector<PointU> _body;
GameSnakeDirection _dir;
GameSnakeDirection _nextDir;
float _moveTimer;
bool _grow;
};
class GameSnake : public UI::Scene
{
public:
GameSnake()
{
g_system->SetWindowSize({ 800, 600 });
Random::SetSeed(0);
_sprBg = g_factory->CreateSpriteDefault(createColor(0x2c3e50ff)); // 深蓝色背景
_txtFps = g_factory->CreateTextDefault();
_txtScore = g_factory->CreateTextDefault();
_txtScore->SetFontSize(24);
_txtScore->SetString("Score: 0");
_txtControl = g_factory->CreateTextDefault();
_txtControl->SetFontSize(16);
_txtControl->SetString("WASD/方向键控制,R开始游戏,ESC返回主菜单");
_txtStart = g_factory->CreateTextDefault();
_txtStart->SetFontSize(36);
_txtStart->SetString("按R开始游戏");
_map.Reset();
_score = 0;
_gameOver = false;
_gameStarted = false;
}
void ResetGame()
{
_map.Reset();
_snake = GameSnakeSnake();
_score = 0;
_txtScore->SetString("Score: 0");
// 初始化蛇身
for (const auto& bodyPart : _snake.GetBody())
{
_map.SetNodeState(bodyPart, GameSnakeMapNodeState::BODY);
}
// 创建初始食物
PointU foodPos;
do
{
foodPos = {
Random::Uint(1, MAP_SIZE[0] - 2),
Random::Uint(1, MAP_SIZE[1] - 2)
};
}
while (_map.GetNodeState(foodPos) != GameSnakeMapNodeState::PASS);
_map.CreateFood(foodPos);
// 播放游戏开始音效
snd_play("click");
_gameOver = false;
_gameStarted = true;
}
virtual void Update() override
{
if (g_input->KeyDown(KeyCode::ESCAPE))
{
g_scene->Del(this);
g_scene->Add("ui_main", new UIMain);
}
_sprBg->SetQuad(createQuad(g_system->GetWindowSize(), false));
_sprBg->SetOrder(0.9f);
// 计算地图偏移量,使地图居中显示
Size mapSize = MAP_SIZE * MAP_NODE_SIZE;
Size windowSize = g_system->GetWindowSize();
Position offset;
offset[0] = (windowSize[0] - mapSize[0]) / 2.0f;
offset[1] = (windowSize[1] - mapSize[1]) / 2.0f;
_map.SetOffset(offset);
_txtFps->SetString("FPS: " + std::to_string(g_time->GetFPS()));
_txtFps->GetCoor()->SetPosition({ 10, 10 });
_txtFps->SetOrder(0.5f);
// 放置Score到围墙左上角上方
_txtScore->SetString("Score: " + std::to_string(_score));
_txtScore->GetCoor()->SetPosition({ offset[0], offset[1] - 30.0f });
_txtScore->SetOrder(0.5f);
// 放置操作提示文本到围墙左下角
_txtControl->GetCoor()->SetPosition({ offset[0], offset[1] + mapSize[1] + 10.0f });
_txtControl->SetOrder(0.5f);
// 处理输入
HandleInput();
// 检查是否开始游戏
if (!_gameStarted)
{
// 显示开始游戏提示
_txtStart->GetCoor()->SetPosition({ (windowSize[0] - 200.0f) / 2.0f, (windowSize[1] - 40.0f) / 2.0f });
_txtStart->SetOrder(0.4f);
_txtStart->Render();
if (g_input->KeyDown(KeyCode::R))
{
ResetGame();
}
}
else
{
// 更新游戏逻辑
if (!_gameOver)
{
if (!_snake.Update(g_time->GetDelta(), _map, _score))
{
_gameOver = true;
// 播放游戏结束音效
snd_play("end");
// 显示游戏结束信息
_txtGameOver = g_factory->CreateTextDefault();
_txtGameOver->SetFontSize(36);
_txtGameOver->SetString("Game Over!");
_txtGameOver->SetOrder(0.4f);
_txtRestart = g_factory->CreateTextDefault();
_txtRestart->SetFontSize(24);
_txtRestart->SetString("Press R to restart");
_txtRestart->SetOrder(0.4f);
}
}
else
{
if (_txtGameOver)
{
// 每次渲染时重新计算位置,确保在屏幕中间
_txtGameOver->GetCoor()->SetPosition({ (windowSize[0] - 150.0f) / 2.0f, (windowSize[1] - 40.0f) / 2.0f });
_txtGameOver->Render();
}
if (_txtRestart)
{
// 每次渲染时重新计算位置,确保在屏幕中间
_txtRestart->GetCoor()->SetPosition({ (windowSize[0] - 180.0f) / 2.0f, (windowSize[1] + 20.0f) / 2.0f });
_txtRestart->Render();
}
if (g_input->KeyDown(KeyCode::R))
{
ResetGame();
// gcText是智能指针,不需要手动释放
// 只需要重置游戏状态,下次游戏结束时会重新创建
}
}
_map.RenderMap(_snake.GetBody());
_snake.Render(_map.GetCoor());
}
}
virtual void Render() override
{
_sprBg->Render();
_txtFps->Render();
_txtScore->Render();
_txtControl->Render();
}
private:
void HandleInput()
{
if (g_input->KeyDown(KeyCode::W) || g_input->KeyDown(KeyCode::UP))
{
_snake.SetDirection(GameSnakeDirection::UP);
}
else if (g_input->KeyDown(KeyCode::S) || g_input->KeyDown(KeyCode::DOWN))
{
_snake.SetDirection(GameSnakeDirection::DOWN);
}
else if (g_input->KeyDown(KeyCode::A) || g_input->KeyDown(KeyCode::LEFT))
{
_snake.SetDirection(GameSnakeDirection::LEFT);
}
else if (g_input->KeyDown(KeyCode::D) || g_input->KeyDown(KeyCode::RIGHT))
{
_snake.SetDirection(GameSnakeDirection::RIGHT);
}
}
gcSprite _sprBg;
gcText _txtFps;
gcText _txtScore;
gcText _txtGameOver;
gcText _txtRestart;
gcText _txtControl;
gcText _txtStart;
GameSnakeMap _map;
GameSnakeSnake _snake;
int _score;
bool _gameOver;
bool _gameStarted;
};