c# - 如何修复网格行和列在 wpf 中不更新?

标签 c# wpf xaml wpf-grid

我正在尝试在 WPF 中制作贪吃蛇游戏,因此我决定使用网格来显示棋盘。 蛇应该移动它的 x 和 y 位置来改变网格列和网格行属性。为此,我创建了一个 SnakePlayer 类,一个 Food 类。 在 MainWindow 中,我每 200 毫秒调用一次游戏循环,然后听键盘设置蛇的方向。 问题是,即使蛇的 x, y 位置在代码中正确更改(我对此进行了测试), 蛇的位置变化没有可视化,因为它一直保持在初始位置。

贪吃蛇类:

namespace Snake
{
    internal class SnakePlayer
    {
        // keeps track of the current direction and makes the snake keep moving
        public (int x, int y) Acceleration = (x: 0, y: 1);
        //rappresents the coordinate of each snake part
        private readonly List<(int x, int y)> Body = new();
        public (int x, int y) Head;
        public SnakePlayer(int NUMBER_OF_ROWS, int NUMBER_OF_COLUMNS)
        {
            int x = Convert.ToInt32((NUMBER_OF_ROWS - 1) / 2);
            int y = Convert.ToInt32((NUMBER_OF_COLUMNS - 1) / 2);
            Body.Add((x, y));
            Head = Body.ElementAt(0);
        }
        public void UpdatePosition()
        {
            for (int i = Body.Count - 2; i >= 0; i--)
            {
                (int x, int y) = Body.ElementAt(i);
                Body[i + 1] = (x, y);
            }
            MoveHead();
        }
        private void MoveHead()
        {
            // for example if acceleration is (1,0) the head keeps going to the right each time the method is called 
            Head.x += Acceleration.x;
            Head.y += Acceleration.y;
        }
        public void Show(Grid gameGrid)
        {
            /* 
             * i basically erase all the previous snake parts and
             * then draw new elements at the new positions 
            */
            gameGrid.Children.Clear();
            Body.ForEach(tail =>
            {
                Border element = GenerateBodyPart(tail.x, tail.y);
                gameGrid.Children.Add(element);
            });
        }
        private static Border GenerateBodyPart(int x, int y)
        {
            static void AddStyles(Border elem)
            {
                elem.HorizontalAlignment = HorizontalAlignment.Stretch;
                elem.VerticalAlignment = VerticalAlignment.Stretch;
                elem.CornerRadius = new CornerRadius(5);
                elem.Background = Brushes.Green;
            }
            Border elem = new();
            AddStyles(elem);
            Grid.SetColumn(elem, x);
            Grid.SetRow(elem, y);
            return elem;
        }
        public void Grow()
        {
            var prevHead = (Head.x,Head.y);
            AddFromBottomOfList(Body,prevHead);
        }
        public bool Eats((int x, int y) position)
        {
            return Head.x == position.x && Head.y == position.y;
        }
        public void SetAcceleration(int x, int y)
        {
            Acceleration.x = x;
            Acceleration.y = y;
            UpdatePosition();
        }
        public bool Dies(Grid gameGrid)
        {
            bool IsOutOfBounds(List<(int x, int y)> Body)
            {
                int mapWidth = gameGrid.ColumnDefinitions.Count;
                int mapHeight = gameGrid.RowDefinitions.Count;
                return Body.Any(tail => tail.x > mapWidth || tail.y > mapHeight || tail.x < 0 || tail.y < 0);
            }
            bool HitsItsSelf(List<(int x, int y)> Body)
            {
                return Body.Any((tail) =>
                {
                    bool isHead = Body.IndexOf(tail) == 0;
                    if (isHead) return false;
                    return Head.x == tail.x && Head.y == tail.y;
                });
            }
            return IsOutOfBounds(Body) || HitsItsSelf(Body);
        }
        public bool HasElementAt(int x, int y)
        {
            return Body.Any(tail => tail.x == x && tail.y == y);
        }
        private static void AddFromBottomOfList<T>(List<T> List,T Element)
        {
            List<T> ListCopy = new();
            ListCopy.Add(Element);
            ListCopy.AddRange(List);
            List.Clear();
            List.AddRange(ListCopy);
        }
    }
}

食品类:

namespace Snake
{
    internal class Food
    {
        public readonly SnakePlayer snake;
        public (int x, int y) Position { get; private set; }
        public Food(SnakePlayer snake, Grid gameGrid)
        {
            this.snake = snake;
            Position = GetInitialPosition(gameGrid);
            Show(gameGrid);
        }
        private (int x, int y) GetInitialPosition(Grid gameGrid)
        {
            (int x, int y) getRandomPosition()
            {
                static int RandomPositionBetween(int min, int max)
                {
                    Random random = new();
                    return random.Next(min, max);
                }
                int cols = gameGrid.ColumnDefinitions.Count;
                int rows = gameGrid.RowDefinitions.Count;
                int x = RandomPositionBetween(0, cols);
                int y = RandomPositionBetween(0, rows);
                return (x, y);
            }
            var position = getRandomPosition();
            if (snake.HasElementAt(position.x, position.y)) return GetInitialPosition(gameGrid);
            return position;
        }
        public void Show(Grid gameGrid)
        {
            static void AddStyles(Border elem)
            {
                elem.HorizontalAlignment = HorizontalAlignment.Stretch;
                elem.VerticalAlignment = VerticalAlignment.Stretch;
                elem.CornerRadius = new CornerRadius(500);
                elem.Background = Brushes.Red;
            }
            Border elem = new();
            AddStyles(elem);
            Grid.SetColumn(elem, Position.x);
            Grid.SetRow(elem, Position.y);
            gameGrid.Children.Add(elem);
        }
    }
}

主窗口:

namespace Snake
{
    public partial class MainWindow : Window
    {
        const int NUMBER_OF_ROWS = 15, NUMBER_OF_COLUMNS = 15;
        private readonly SnakePlayer snake;
        private Food food;
        private readonly DispatcherTimer Loop;
        public MainWindow()
        {
            InitializeComponent();
            CreateBoard();
            snake = new SnakePlayer(NUMBER_OF_ROWS, NUMBER_OF_COLUMNS);
            food = new Food(snake, GameGrid);
            GameGrid.Focus();
            GameGrid.KeyDown += (sender, e) => OnKeySelection(e);
            Loop = SetInterval(GameLoop, 200);
        }
        private void GameLoop()
        {
            snake.UpdatePosition();
            snake.Show(GameGrid);
            food.Show(GameGrid);
            if (snake.Eats(food.Position))
            {
                food = new Food(snake, GameGrid);
                snake.Grow();
            }
            else if (snake.Dies(GameGrid))
            {
                Loop.Stop();
                snake.UpdatePosition();
                ResetMap();
                ShowEndGameMessage("You Died");
            }
        }
        private void OnKeySelection(KeyEventArgs e)
        {
            if(e.Key == Key.Escape)
            {
                Close();
                return;
            }
            var DIRECTIONS = new
            {
                UP = (0, 1),
                LEFT = (-1, 0),
                DOWN = (0, -1),
                RIGHT = (1, 0),
            };
            Dictionary<string, (int x, int y)> acceptableKeys = new()
            {
                { "W", DIRECTIONS.UP },
                { "UP", DIRECTIONS.UP },
                { "A", DIRECTIONS.LEFT },
                { "LEFT", DIRECTIONS.LEFT },
                { "S", DIRECTIONS.DOWN },
                { "DOWN", DIRECTIONS.DOWN },
                { "D", DIRECTIONS.RIGHT },
                { "RIGHT", DIRECTIONS.RIGHT }
            };
            string key = e.Key.ToString().ToUpper().Trim();
            if (!acceptableKeys.ContainsKey(key)) return;
            (int x, int y) = acceptableKeys[key];
            snake.SetAcceleration(x, y);
        }
        private void CreateBoard() 
        {
            for (int i = 0; i < NUMBER_OF_ROWS; i++)
                GameGrid.RowDefinitions.Add(new RowDefinition());
            for (int i = 0; i < NUMBER_OF_COLUMNS; i++)
                GameGrid.ColumnDefinitions.Add(new ColumnDefinition());
        }
        private void ResetMap()
        {
            GameGrid.Children.Clear();
            GameGrid.RowDefinitions.Clear();
            GameGrid.ColumnDefinitions.Clear();
        }
        private void ShowEndGameMessage(string message)
        {
            TextBlock endGameMessage = new();
            endGameMessage.Text = message;
            endGameMessage.HorizontalAlignment = HorizontalAlignment.Center;
            endGameMessage.VerticalAlignment = VerticalAlignment.Center;
            endGameMessage.Foreground = Brushes.White;
            GameGrid.Children.Clear();
            GameGrid.Children.Add(endGameMessage);
        }
        private static DispatcherTimer SetInterval(Action cb, int ms)
        {
            DispatcherTimer dispatcherTimer = new();
            dispatcherTimer.Interval = TimeSpan.FromMilliseconds(ms);
            dispatcherTimer.Tick += (sender, e) => cb();
            dispatcherTimer.Start();
            return dispatcherTimer;
        }
    }
}

主窗口.xaml:

<Window x:Class="Snake.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Snake"
        mc:Ignorable="d"
        WindowStyle="None"
        Background="Transparent"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="600" Width="600" ResizeMode="NoResize" AllowsTransparency="True">
    <Border CornerRadius="20" Height="600" Width="600" Background="#FF0D1922">
        <Grid x:Name="GameGrid" Focusable="True" ShowGridLines="False"/>
    </Border>
</Window>

最佳答案

虽然我不完全理解您希望这款游戏的用户体验,但我进行了一些更改以产生更有意义的结果。

您的 UI 未更新的主要原因是您从未更改 GenerateBodyPart 的位置。它始终等于其初始值。您不应传递永远不变的“尾部”,而应传递具有新位置的“头部”。

改变这个:

private static Border GenerateBodyPart(int x, int y)
{
    ....
    Grid.SetColumn(elem, tail.x);
    Grid.SetRow(elem, tail.y);
    return elem;
}

要这样(注意我删除了 static 关键字以到达 Head):

private Border GenerateBodyPart(int x, int y)
{
    ....
    Grid.SetColumn(elem, Head.x);
    Grid.SetRow(elem, Head.y);
    return elem;
}

此外,您的“方向”对于向上和向下都不正确。应该是这样的:

var DIRECTIONS = new
{
    UP = (0, -1),
    LEFT = (-1, 0),
    DOWN = (0, 1),
    RIGHT = (1, 0),
};

进行这些更改后,UI 至少更新了蛇的位置。 Watch video .我不确切知道你希望蛇如何显示,但那是你以后要弄清楚的。 :)

祝您编码愉快!

这是我的完整源代码以供引用:Download here

关于c# - 如何修复网格行和列在 wpf 中不更新?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71690908/

相关文章:

.net - XAML 中是否有公认的缩进属性样式?

c# - 如何在具有父子关系的 2 个 wpf 控件之间使用 SharedSizeScope?

c# - 如何在 DLL 中包含文档以在 Unity3D 中显示方法摘要?

c# - Swagger/NSwag : Redefine Parameter Type

c# - 不可变字符串误解或文档中的错误?

c# - WPF – 集合内容属性

c# - 如何根据特定条件对DataGrid 中的数据进行分组?

WPF:根据项目的大小和数量使用不同的模板

c# - 如何从 Azure APIM 策略中的 <send-request> 策略的响应中获取 header ?

.net - 在 WPF 中将像素转换为 CM