599 lines
13 KiB
C++
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;
|
|
}; |