WPF:贪吃蛇小游戏

一、项目结构:

  • 主窗口(Window)包含一个Frame,Frame加载一个Page(游戏页面)。
    包括以下文件:
    MainWindow.xaml / MainWindow.xaml.cs
    GamePage.xaml / GamePage.xaml.cs
    App.xaml / App.xaml.cs (默认)


二、游戏逻辑

  • 使用Canvas绘制网格,蛇移动,食物生成,方向控制(键盘),得分显示,游戏结束处理。
    游戏循环使用DispatcherTimer。
    方向控制:键盘事件。
    蛇身体用List<Point>。
    食物随机生成。吃到蛇身自动变长、累计得分;
    碰撞检测:撞墙或自身。
    游戏结束显示消息。

三、数据结构

1、蛇身列表(单链表)

private List<Point> snake = new List<Point>();

2、方向枚举

private enum Direction
{
Up, Down, Left, Right
}

3、键盘控制

方向控制(键盘)

4、游戏整体执行流程

  • StartNewGame():
    窗口配置、界面、绘制地图、初始化3节蛇、生成首个食物;
  • GameTimer_Tick():
    定时器循环;刷新分数、监听按键、修改方向、判断是否吃到食物,更新列表,碰撞检测;
  • DrawGame()
    绘制网格,绘制食物, 绘制蛇(绿色方块,头部亮绿)
  • Page_KeyDown
    键盘事件

四、程序

1、MainWindow.xaml

<Window x:Class="SnakeGame.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="贪吃蛇" Height="480" Width="640"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
    <Grid>
        <Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" />
    </Grid>
</Window>

2、MainWindow.xaml.cs

using System.Windows;

namespace SnakeGame
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // 启动时导航到游戏页面
            MainFrame.Navigate(new GamePage());
        }
    }
}

3、GamePage.xaml

<Page x:Class="SnakeGame.GamePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="GamePage" Loaded="Page_Loaded"
      KeyDown="Page_KeyDown" Focusable="True"
      Background="White">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- 状态栏 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5" Background="LightGray">
            <TextBlock Text="得分:" FontSize="16" FontWeight="Bold" Margin="5" />
            <TextBlock x:Name="ScoreText" Text="0" FontSize="16" FontWeight="Bold" Margin="5,5,20,5" />
            <TextBlock Text="方向:" FontSize="16" Margin="5" />
            <TextBlock x:Name="DirectionText" Text="→" FontSize="16" Margin="5" />
            <Button x:Name="RestartButton" Content="重新开始" Click="RestartButton_Click" Margin="20,0,0,0" Width="80" />
        </StackPanel>

        <!-- 游戏画布 -->
        <Canvas x:Name="GameCanvas" Grid.Row="1" Background="Black" />
    </Grid>
</Page>

4、GamePage.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace SnakeGame
{
    public partial class GamePage : Page
    {
        // 游戏配置
        private const int GridSize = 20;          // 每个格子大小(像素)
        private const int Rows = 20;              // 网格行数
        private const int Cols = 25;              // 网格列数

        // 蛇的数据
        private List<Point> snake = new List<Point>();
        private Point food;
        private Direction currentDirection = Direction.Right;
        private Direction nextDirection = Direction.Right;

        // 游戏状态
        private bool isGameOver = false;
        private int score = 0;

        // 定时器
        private DispatcherTimer gameTimer;

        // 随机数
        private Random rand = new Random();

        public GamePage()
        {
            InitializeComponent();
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            // 初始化游戏
            StartNewGame();
            // 聚焦页面以捕获键盘
            this.Focus();
        }

        private void StartNewGame()
        {
            // 重置数据
            snake.Clear();
            // 初始蛇:长度为3,水平放置
            snake.Add(new Point(5, 10)); // 头
            snake.Add(new Point(4, 10));
            snake.Add(new Point(3, 10));

            currentDirection = Direction.Right;
            nextDirection = Direction.Right;
            score = 0;
            isGameOver = false;

            ScoreText.Text = "0";
            DirectionText.Text = "→";

            GenerateFood();

            // 停止旧定时器
            if (gameTimer != null)
            {
                gameTimer.Stop();
            }
            // 创建新定时器(间隔200ms)
            gameTimer = new DispatcherTimer();
            gameTimer.Interval = TimeSpan.FromMilliseconds(300);
            gameTimer.Tick += GameTimer_Tick;
            gameTimer.Start();

            DrawGame();
            this.Focus(); // 重新聚焦
        }

        private void GenerateFood()
        {
            // 确保食物不在蛇身上
            while (true)
            {
                int x = rand.Next(0, Cols);
                int y = rand.Next(0, Rows);
                Point p = new Point(x, y);
                if (!snake.Contains(p))
                {
                    food = p;
                    break;
                }
            }
        }

        private void GameTimer_Tick(object sender, EventArgs e)
        {
            if (isGameOver) return;

            // 应用有效方向
            currentDirection = nextDirection;

            // 计算新蛇头位置
            Point head = snake[0];
            Point newHead = head;
            switch (currentDirection)
            {
                case Direction.Up: newHead = new Point(head.X, head.Y - 1); break;
                case Direction.Down: newHead = new Point(head.X, head.Y + 1); break;
                case Direction.Left: newHead = new Point(head.X - 1, head.Y); break;
                case Direction.Right: newHead = new Point(head.X + 1, head.Y); break;
            }

            // 判断是否吃到食物
            bool ateFood = (newHead.X == food.X && newHead.Y == food.Y);

            // 移动蛇:先插入新头
            snake.Insert(0, newHead);
            if (!ateFood)
            {
                // 没吃到则移除尾部
                snake.RemoveAt(snake.Count - 1);
            }

            // 碰撞检测
            // 1. 撞墙
            if (newHead.X < 0 || newHead.X >= Cols || newHead.Y < 0 || newHead.Y >= Rows)
            {
                GameOver();
                return;
            }
            // 2. 撞自身(新头不能与除头部以外的部分重合,注意如果没吃到食物,尾部已移除,但头部新位置可能与旧身体重合)
            // 由于我们插入了新头,检查新头是否与身体其他部分重合(索引从1开始)
            for (int i = 1; i < snake.Count; i++)
            {
                if (snake[i].X == newHead.X && snake[i].Y == newHead.Y)
                {
                    GameOver();
                    return;
                }
            }

            // 处理吃到食物
            if (ateFood)
            {
                score++;
                ScoreText.Text = score.ToString();
                GenerateFood(); // 生成新食物
            }

            // 更新方向显示
            UpdateDirectionText();

            // 重绘画布
            DrawGame();
        }

        private void GameOver()
        {
            isGameOver = true;
            gameTimer.Stop();
            MessageBox.Show($"游戏结束!\n得分:{score}", "贪吃蛇", MessageBoxButton.OK, MessageBoxImage.Information);
            // 点击确定后重新开始(可选)
            StartNewGame();
        }

        private void DrawGame()
        {
            GameCanvas.Children.Clear();

            // 绘制网格(可选,灰色细线)
            for (int i = 0; i <= Cols; i++)
            {
                Line line = new Line
                {
                    X1 = i * GridSize,
                    Y1 = 0,
                    X2 = i * GridSize,
                    Y2 = Rows * GridSize,
                    Stroke = Brushes.DarkGray,
                    StrokeThickness = 0.5
                };
                GameCanvas.Children.Add(line);
            }
            for (int i = 0; i <= Rows; i++)
            {
                Line line = new Line
                {
                    X1 = 0,
                    Y1 = i * GridSize,
                    X2 = Cols * GridSize,
                    Y2 = i * GridSize,
                    Stroke = Brushes.DarkGray,
                    StrokeThickness = 0.5
                };
                GameCanvas.Children.Add(line);
            }

            // 绘制食物(红色圆球)
            Ellipse foodEllipse = new Ellipse
            {
                Width = GridSize - 2,
                Height = GridSize - 2,
                Fill = Brushes.Red
            };
            Canvas.SetLeft(foodEllipse, food.X * GridSize + 1);
            Canvas.SetTop(foodEllipse, food.Y * GridSize + 1);
            GameCanvas.Children.Add(foodEllipse);

            // 绘制蛇(绿色方块,头部亮绿)
            for (int i = 0; i < snake.Count; i++)
            {
                Rectangle rect = new Rectangle
                {
                    Width = GridSize - 1,
                    Height = GridSize - 1,
                    Fill = (i == 0) ? Brushes.LimeGreen : Brushes.Green
                };
                Canvas.SetLeft(rect, snake[i].X * GridSize + 0.5);
                Canvas.SetTop(rect, snake[i].Y * GridSize + 0.5);
                GameCanvas.Children.Add(rect);
            }
        }

        private void UpdateDirectionText()
        {
            switch (currentDirection)
            {
                case Direction.Up: DirectionText.Text = "↑"; break;
                case Direction.Down: DirectionText.Text = "↓"; break;
                case Direction.Left: DirectionText.Text = "←"; break;
                case Direction.Right: DirectionText.Text = "→"; break;
            }
        }

        // 键盘事件
        private void Page_KeyDown(object sender, KeyEventArgs e)
        {
            if (isGameOver) return;

            // 防止反向
            switch (e.Key)
            {
                case Key.W:
                case Key.Up:
                    if (currentDirection != Direction.Down)
                        nextDirection = Direction.Up;
                    break;
                case Key.S:
                case Key.Down:
                    if (currentDirection != Direction.Up)
                        nextDirection = Direction.Down;
                    break;
                case Key.A:
                case Key.Left:
                    if (currentDirection != Direction.Right)
                        nextDirection = Direction.Left;
                    break;
                case Key.D:
                case Key.Right:
                    if (currentDirection != Direction.Left)
                        nextDirection = Direction.Right;
                    break;
                case Key.R:
                    StartNewGame();
                    break;
            }
        }

        // 重新开始按钮
        private void RestartButton_Click(object sender, RoutedEventArgs e)
        {
            StartNewGame();
        }

        // 方向枚举
        private enum Direction
        {
            Up, Down, Left, Right
        }
    }
}
运行界面
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容