Files
XCEngine/参考/Fermion/Boson/projects/Assets/scripts/SnakeGame.cs

304 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using Fermion;
namespace Photon
{
public class SimpleRandom
{
private uint m_Seed;
public SimpleRandom(uint seed) { m_Seed = seed; }
public int Next(int maxValue)
{
m_Seed = (1103515245u * m_Seed + 12345u) & 0x7fffffffu;
return (int)(m_Seed % (uint)maxValue);
}
}
public class SnakeGame : Entity
{
// ========== 可配置参数 ==========
public int GridWidth = 20;
public int GridHeight = 20;
public float CellSize = 1.0f;
public float MoveInterval = 0.15f;
// ========== 颜色配置 ==========
private readonly Vector4 HeadColor = new Vector4(0.2f, 0.8f, 0.2f, 1.0f);
private readonly Vector4 BodyColor = new Vector4(0.1f, 0.6f, 0.1f, 1.0f);
private readonly Vector4 FoodColor = new Vector4(0.9f, 0.2f, 0.2f, 1.0f);
private readonly Vector4 BorderColor = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
private readonly Vector4 GameOverColor = new Vector4(0.8f, 0.1f, 0.1f, 1.0f);
// ========== 游戏状态 ==========
private enum Direction { Up, Down, Left, Right }
private List<Vector2> m_SnakeBody = new List<Vector2>();
private List<Entity> m_SnakeEntities = new List<Entity>();
private Vector2 m_FoodPosition;
private Entity m_FoodEntity;
private Direction m_CurrentDirection = Direction.Right;
private Queue<Direction> m_InputQueue = new Queue<Direction>();
private const int MAX_INPUT_BUFFER = 2;
private float m_MoveTimer = 0.0f;
private float m_CurrentMoveInterval;
private bool m_GameOver = false;
private int m_Score = 0;
private SimpleRandom m_Random;
private Entity m_ScoreEntity;
private TextComponent m_ScoreText;
// 按键状态记录
private bool m_LastUpDown, m_LastDownDown, m_LastLeftDown, m_LastRightDown, m_LastRDown;
// ========== 生命周期 ==========
public void OnCreate()
{
float halfW = GridWidth * CellSize * 0.5f;
float halfH = GridHeight * CellSize * 0.5f;
m_ScoreEntity = Scene.CreateEntity("ScoreText");
m_ScoreEntity.GetComponent<TransformComponent>().Translation =
new Vector3(-halfW, halfH + CellSize * 0.5f, 0.0f);
m_ScoreText = m_ScoreEntity.AddComponent<TextComponent>();
CreateBorder();
StartGame();
}
public void OnUpdate(float ts)
{
HandleInput();
if (m_GameOver) return;
m_MoveTimer += ts;
if (m_MoveTimer >= m_CurrentMoveInterval)
{
m_MoveTimer -= m_CurrentMoveInterval; // 使用减法保持计时精度
MoveSnake();
}
}
// ========== 游戏初始化 ==========
private void StartGame()
{
if (m_Random == null) m_Random = new SimpleRandom((uint)GetHashCode());
ClearOldEntities();
InitializeSnake();
// 重置状态
m_InputQueue.Clear();
m_CurrentDirection = Direction.Right;
m_MoveTimer = 0.0f;
m_CurrentMoveInterval = MoveInterval;
m_GameOver = false;
m_Score = 0;
UpdateScoreText();
SpawnFood();
}
// ========== 输入处理逻辑 (优化核心) ==========
private void HandleInput()
{
bool upDown = Input.IsKeyDown(KeyCode.Up) || Input.IsKeyDown(KeyCode.W);
bool downDown = Input.IsKeyDown(KeyCode.Down) || Input.IsKeyDown(KeyCode.S);
bool leftDown = Input.IsKeyDown(KeyCode.Left) || Input.IsKeyDown(KeyCode.A);
bool rightDown = Input.IsKeyDown(KeyCode.Right) || Input.IsKeyDown(KeyCode.D);
bool rDown = Input.IsKeyDown(KeyCode.R);
// 转向输入检测
if (upDown && !m_LastUpDown) TryQueueDirection(Direction.Up);
else if (downDown && !m_LastDownDown) TryQueueDirection(Direction.Down);
else if (leftDown && !m_LastLeftDown) TryQueueDirection(Direction.Left);
else if (rightDown && !m_LastRightDown) TryQueueDirection(Direction.Right);
if (rDown && !m_LastRDown) StartGame();
m_LastUpDown = upDown; m_LastDownDown = downDown; m_LastLeftDown = leftDown;
m_LastRightDown = rightDown; m_LastRDown = rDown;
}
private void TryQueueDirection(Direction dir)
{
if (m_InputQueue.Count >= MAX_INPUT_BUFFER) return;
// 获取当前逻辑链条中最后一个预定方向
Direction lastPending = m_InputQueue.Count > 0 ?
m_InputQueue.ToArray()[m_InputQueue.Count - 1] : m_CurrentDirection;
// 过滤重复按键和相反方向
if (dir != lastPending && !IsOpposite(dir, lastPending))
{
m_InputQueue.Enqueue(dir);
}
}
private bool IsOpposite(Direction a, Direction b)
{
return (a == Direction.Up && b == Direction.Down) ||
(a == Direction.Down && b == Direction.Up) ||
(a == Direction.Left && b == Direction.Right) ||
(a == Direction.Right && b == Direction.Left);
}
// ========== 蛇移动逻辑 ==========
private void MoveSnake()
{
if (m_SnakeEntities.Count == 0) return;
// 从缓冲队列中取出下一个待执行方向
if (m_InputQueue.Count > 0)
{
m_CurrentDirection = m_InputQueue.Dequeue();
}
Vector2 head = m_SnakeBody[m_SnakeBody.Count - 1];
Vector2 newHead = head;
switch (m_CurrentDirection)
{
case Direction.Up: newHead.Y += 1; break;
case Direction.Down: newHead.Y -= 1; break;
case Direction.Left: newHead.X -= 1; break;
case Direction.Right: newHead.X += 1; break;
}
// 碰撞检测逻辑... (保持你原来的代码逻辑)
if (newHead.X < 0 || newHead.X >= GridWidth || newHead.Y < 0 || newHead.Y >= GridHeight)
{
OnGameOver(); return;
}
for (int i = 1; i < m_SnakeBody.Count; i++)
{
if ((int)m_SnakeBody[i].X == (int)newHead.X && (int)m_SnakeBody[i].Y == (int)newHead.Y)
{
OnGameOver(); return;
}
}
bool ateFood = ((int)newHead.X == (int)m_FoodPosition.X && (int)newHead.Y == (int)m_FoodPosition.Y);
// 更新表现层
m_SnakeEntities[m_SnakeEntities.Count - 1].GetComponent<SpriteRendererComponent>().Color = BodyColor;
m_SnakeBody.Add(newHead);
m_SnakeEntities.Add(CreateSegmentEntity(newHead, HeadColor));
if (ateFood)
{
m_Score++;
UpdateScoreText();
m_CurrentMoveInterval = Math.Max(0.05f, m_CurrentMoveInterval - 0.003f);
SpawnFood();
}
else
{
Scene.DestroyEntity(m_SnakeEntities[0]);
m_SnakeEntities.RemoveAt(0);
m_SnakeBody.RemoveAt(0);
}
}
// ========== 辅助方法 (保持不变) ==========
// ... 此处省略 SpawnFood, CreateBorder, GridToWorld 等未变动的辅助方法 ...
// [请保留你原始代码中剩下的部分]
private void ClearOldEntities()
{
for (int i = 0; i < m_SnakeEntities.Count; i++) Scene.DestroyEntity(m_SnakeEntities[i]);
m_SnakeEntities.Clear();
m_SnakeBody.Clear();
if (m_FoodEntity != null) { Scene.DestroyEntity(m_FoodEntity); m_FoodEntity = null; }
}
private void InitializeSnake()
{
int startX = GridWidth / 2; int startY = GridHeight / 2;
for (int i = 2; i >= 0; i--)
{
Vector2 pos = new Vector2(startX - i, startY);
m_SnakeBody.Add(pos);
m_SnakeEntities.Add(CreateSegmentEntity(pos, i == 0 ? HeadColor : BodyColor));
}
}
private void OnGameOver()
{
m_GameOver = true;
foreach (var entity in m_SnakeEntities)
if (entity.HasComponent<SpriteRendererComponent>())
entity.GetComponent<SpriteRendererComponent>().Color = GameOverColor;
m_ScoreText.Text = $"Game Over! Score: {m_Score} Press R to Restart";
}
private Entity CreateSegmentEntity(Vector2 gridPos, Vector4 color)
{
Entity segment = Scene.CreateEntity("SnakeSegment");
var transform = segment.GetComponent<TransformComponent>();
transform.Translation = GridToWorld(gridPos);
transform.Scale = new Vector3(CellSize * 0.9f, CellSize * 0.9f, 1.0f);
segment.AddComponent<SpriteRendererComponent>().Color = color;
return segment;
}
private Vector3 GridToWorld(Vector2 gridPos)
{
float offsetX = -GridWidth * CellSize * 0.5f + CellSize * 0.5f;
float offsetY = -GridHeight * CellSize * 0.5f + CellSize * 0.5f;
return new Vector3(gridPos.X * CellSize + offsetX, gridPos.Y * CellSize + offsetY, 0.0f);
}
private void UpdateScoreText() => m_ScoreText.Text = $"Score: {m_Score}";
private void CreateBorder()
{
float halfW = GridWidth * CellSize * 0.5f;
float halfH = GridHeight * CellSize * 0.5f;
float thickness = CellSize * 0.15f;
CreateBorderSegment("BB", new Vector3(0, -halfH - thickness * 0.5f, 0), new Vector3(GridWidth * CellSize + thickness * 2, thickness, 1));
CreateBorderSegment("BT", new Vector3(0, halfH + thickness * 0.5f, 0), new Vector3(GridWidth * CellSize + thickness * 2, thickness, 1));
CreateBorderSegment("BL", new Vector3(-halfW - thickness * 0.5f, 0, 0), new Vector3(thickness, GridHeight * CellSize + thickness * 2, 1));
CreateBorderSegment("BR", new Vector3(halfW + thickness * 0.5f, 0, 0), new Vector3(thickness, GridHeight * CellSize + thickness * 2, 1));
}
private void CreateBorderSegment(string name, Vector3 position, Vector3 scale)
{
Entity border = Scene.CreateEntity(name);
border.GetComponent<TransformComponent>().Translation = position;
border.GetComponent<TransformComponent>().Scale = scale;
border.AddComponent<SpriteRendererComponent>().Color = BorderColor;
}
private void SpawnFood()
{
List<Vector2> emptyPositions = new List<Vector2>();
for (int x = 0; x < GridWidth; x++)
for (int y = 0; y < GridHeight; y++)
if (!IsPositionOccupied(x, y)) emptyPositions.Add(new Vector2(x, y));
if (emptyPositions.Count == 0) { OnGameOver(); return; }
m_FoodPosition = emptyPositions[m_Random.Next(emptyPositions.Count)];
if (m_FoodEntity != null) Scene.DestroyEntity(m_FoodEntity);
m_FoodEntity = Scene.CreateEntity("Food");
var transform = m_FoodEntity.GetComponent<TransformComponent>();
transform.Translation = GridToWorld(m_FoodPosition);
transform.Scale = new Vector3(CellSize * 0.8f, CellSize * 0.8f, 1.0f);
m_FoodEntity.AddComponent<SpriteRendererComponent>().Color = FoodColor;
}
private bool IsPositionOccupied(int x, int y)
{
foreach (var pos in m_SnakeBody) if ((int)pos.X == x && (int)pos.Y == y) return true;
return false;
}
}
}