#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& 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 _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& GetBody() const { return _body; } private: std::vector _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; };