Animacja przesunięcia obrazka między komórkami Grid

0

Jak najprościej zrobić, aby przezroczysty obrazek płynie poruszał się od jednej komórki kontrolki Grid (WPF) do sąsiedniej?

Mój XAML:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="Wumpus.MainWindow"
	x:Name="Window"
	Title="Wumpus World"
	WindowState="Maximized">
    <StackPanel>
        <WrapPanel>
            <!--<Image  Source="gfx/Player.gif" Width="65" Panel.ZIndex="100" Margin="100,0,0,0" />-->
            <Border Panel.ZIndex="0" BorderBrush="Black" BorderThickness="3" Width="503" Height="503">

                <Grid Panel.ZIndex="0" Width="500" Height="500" ShowGridLines="true"  Name="WGrid">

                </Grid>
            </Border>
            <ListBox Name="PredykatyList" Width="Auto" Height="500">
                
            </ListBox>
            
        </WrapPanel>
        <Label Height="Auto">
            <WrapPanel>
                <TextBlock Name="ScoreTextBlock" TextWrapping="Wrap"></TextBlock>
                <TextBlock Name="TimeTextBlock" TextWrapping="Wrap"></TextBlock>
            </WrapPanel>
        </Label>
        <WrapPanel>
            <Button Content="Następny krok" Height="23" Name="StepButton" Width="175" Margin="20" Click="StepButton_Click" />
            <Button Content="Wykonaj 100 rundek" Height="23" Name="RoundButton" Width="175" Click="RoundButton_Click" />
        </WrapPanel>
        
    </StackPanel>
   
</Window>

Code-behind (C#):

(...)
namespace Wumpus
{
	
	public partial class MainWindow : Window
	{
		private uint _size = 7;
		private BitmapImage _wumpus = null;
		private BitmapImage _pit = null;
		private BitmapImage _player = null;
		private BitmapImage _wall = null;
		private BitmapImage _gold = null;
        private BitmapImage EastPlayer, SouthPlayer, WestPlayer, NorthPlayer;
		private game.level _lvl = null;

		private double imageHeight = 0;
		private double imageWidth = 0;
        public Image[,] _imgGrid = null;

        private BazaWiedzy bw = new BazaWiedzy();
        private bool alive = true;
        private bool win = false;
        private int time = 1;
        private int return_timer = 0;
        private int Score = 0;
        public MainWindow()
		{
			this.InitializeComponent();
			
            _imgGrid = new Image[_size,_size];

			imageWidth = WGrid.Width / _size * 0.9;
			imageHeight = WGrid.Height / _size * 0.9;

            for (int i = 0; i < _size; i++)
            {
                ColumnDefinition col = new ColumnDefinition();
                col.Width = new GridLength(WGrid.Width / _size);
                WGrid.ColumnDefinitions.Add(col);
                RowDefinition row = new RowDefinition();
                row.Height = new GridLength(WGrid.Height / _size);
                WGrid.RowDefinitions.Add(row);                
            }

            initRes();
            RedrawWorld();
		}

		private void initRes()
		{
			_wumpus = new BitmapImage(new Uri("gfx/Wumpus.gif", UriKind.RelativeOrAbsolute));
            _gold = new BitmapImage(new Uri("gfx/Gold.gif", UriKind.RelativeOrAbsolute));
            _pit = new BitmapImage(new Uri("gfx/Pit.gif", UriKind.RelativeOrAbsolute));
            _player = new BitmapImage(new Uri("gfx/Player.gif", UriKind.RelativeOrAbsolute));
            _wall = new BitmapImage(new Uri("gfx/Wall.gif", UriKind.RelativeOrAbsolute));
            EastPlayer = new BitmapImage(new Uri("gfx/EastPlayer.gif", UriKind.RelativeOrAbsolute));
            WestPlayer = new BitmapImage(new Uri("gfx/WestPlayer.gif", UriKind.RelativeOrAbsolute));
            SouthPlayer = new BitmapImage(new Uri("gfx/SouthPlayer.gif", UriKind.RelativeOrAbsolute));
            NorthPlayer = new BitmapImage(new Uri("gfx/NorthPlayer.gif", UriKind.RelativeOrAbsolute));
            for (uint x = 0; x < _size; ++x)
                for (uint y = 0; y < _size; ++y)
                {
                    _imgGrid[x,y] = new Image();
                    _imgGrid[x, y].Height = imageHeight;
                    _imgGrid[x, y].Width = imageWidth;
                    WGrid.Children.Add(_imgGrid[x, y]);
                    Grid.SetRow(_imgGrid[x, y], (int)y);
                    Grid.SetColumn(_imgGrid[x, y], (int)x);
                }

			_lvl = new game.level(_size);
			_lvl.clearMap();
_lvl.onNewEnviroment += newEnviroment;
            _lvl.onWallHit += wallHit;

            fillKB();
		}
(...)
private void setBitmap(BitmapImage bi, uint x, uint y)
		{
			if (x >= _size || y >= _size)
				return;
            _imgGrid[x, y].Source = bi;
            _imgGrid[x, y] = new Image();
            _imgGrid[x, y].Height = imageHeight;
            _imgGrid[x, y].Width = imageWidth;
            WGrid.Children.Add(_imgGrid[x, y]);
            Grid.SetRow(_imgGrid[x, y], (int)y);
            Grid.SetColumn(_imgGrid[x, y], (int)x);

		}

        public void RedrawWorld()
        {
            WGrid.Children.Clear();
            for(uint x = 0; x < _size; ++x)
				for (uint y = 0; y < _size; ++y)
				{
                    setBitmap(null, x, y);
                    if (_lvl.map[x, y].wumpus)
						setBitmap(_wumpus, x, y);
                    else if (_lvl.map[x, y].gold)
						setBitmap(_gold, x, y);
					else if (_lvl.map[x, y].pit)
						setBitmap(_pit, x, y);
					else if (_lvl.map[x, y].wall)
						setBitmap(_wall, x, y);
				}

            switch (_lvl.afterWallHit)
            {
                case Wumpus.game.level.AfterWallHit.no:

                    setBitmap(_player, _lvl.player_x, _lvl.player_y);
                    break;
                case Wumpus.game.level.AfterWallHit.EAST:
                    setBitmap(EastPlayer, _lvl.player_x, _lvl.player_y);
                    break;
                case Wumpus.game.level.AfterWallHit.WEST:
                    setBitmap(WestPlayer, _lvl.player_x, _lvl.player_y);
                    break;
                case Wumpus.game.level.AfterWallHit.NORTH:
                    setBitmap(NorthPlayer, _lvl.player_x, _lvl.player_y);
                    break;
                case Wumpus.game.level.AfterWallHit.SOUTH:
                    setBitmap(SouthPlayer, _lvl.player_x, _lvl.player_y);
                    break;
            }
        }

        public void RefreshList()
        {
            PredykatyList.Items.Clear();
            foreach (Formula f in bw.Formuly)
            {
                PredykatyList.Items.Add(f.ToString());
            }
        }
(...)
public bool TakeStep()
        {
            //Ask
            List<Podstawienie> akcja = bw.Ask(new Predykat("Najlepsza", new string[] { "\""+time.ToString()+"\"", "akcja" }, false));
            if(akcja.Count == 0)
                akcja = bw.Ask(new Predykat("Dobra", new string[] { "\"" + time.ToString() + "\"", "akcja" }, false));
            if (akcja.Count == 0)
                akcja = bw.Ask(new Predykat("Ryzykowna", new string[] { "\"" + time.ToString() + "\"", "akcja" }, false));
            if (akcja.Count == 0)
                akcja = bw.Ask(new Predykat("Samobójcza", new string[] { "\"" + time.ToString() + "\"", "akcja" }, false));
            if (akcja.Count == 0)
            {
                alive = false;
                return false;
            }
            
            ++time;
            //zrób
            string do_zrobienia = "";
            foreach (Podstawienie p in akcja)
            {
                do_zrobienia = akcja[0]["akcja"].Substring(1, p["akcja"].Length - 2);
                switch (do_zrobienia)
                {
                    case "PodnieśZłoto":
                        _lvl.pickGold();
                        break;
                    case "IdźWsch":
                        _lvl.moveplayer(Wumpus.game.Direction.EAST);
                        return_timer = -1;
                        break;
                    case "IdźPłd":
                        _lvl.moveplayer(Wumpus.game.Direction.SOUTH);
                        return_timer = -1;
                        break;
                    case "IdźZach":
                        _lvl.moveplayer(Wumpus.game.Direction.WEST);
                        return_timer = -1;
                        break;
                    case "IdźPłn":
                        _lvl.moveplayer(Wumpus.game.Direction.NORTH);
                        return_timer = -1;
                        break;
                    case "Wróć":
                        //zawracanie
                        if (return_timer < 0)
                            return_timer = time;
                        --return_timer;
                        List<Podstawienie> powrot = bw.Ask(new Predykat("Jest", new string[] { "posx", "posy", "\"" + return_timer.ToString() + "\"" }, false));
                        if (powrot.Count == 0)
                            alive = false;
                        else
                        {
                            foreach (Podstawienie pos in powrot)
                            {
                                try
                                {
                                    int posx = int.Parse(pos["posx"].Substring(1, pos["posx"].Length - 2));
                                    int posy = int.Parse(pos["posy"].Substring(1, pos["posy"].Length - 2));
                                    if (posy == _lvl.player_y)
                                    {
                                        if (posx - _lvl.player_x == 1)
                                            _lvl.moveplayer(Wumpus.game.Direction.EAST);
                                        else if (posx - _lvl.player_x == -1)
                                            _lvl.moveplayer(Wumpus.game.Direction.WEST);
                                        else if (posx == _lvl.player_x)
                                            break;
                                        else
                                            continue;
                                        break;
                                    }
                                    if (posx == _lvl.player_x)
                                    {
                                        if (posy - _lvl.player_y == 1)
                                            _lvl.moveplayer(Wumpus.game.Direction.SOUTH);
                                        else if (posy - _lvl.player_y == -1)
                                            _lvl.moveplayer(Wumpus.game.Direction.NORTH);
                                        else
                                            continue;
                                        break;
                                    }
                                }
                                catch
                                {
                                    alive = false;
                                }
                            }
                        }
                        //idź tam gdzie był w return_timer
                        break;
                }
                break;
            }
            Score--;
			ScoreTextBlock.Text = Score + " pkt ";
			RedrawWorld();

			if (_lvl.player_x == 0 && _lvl.player_y == 0 && _lvl.hasGold)
			{
				win = true;
				Score += 1000;
				ScoreTextBlock.Text = Score + " pkt ";
				return false;
			}
			if (!alive)
			{
				Score -= 1000;
				ScoreTextBlock.Text = Score + " pkt ";
				return false;
			}
            
            return true;
        }

        private void StepButton_Click(object sender, RoutedEventArgs e)
        {
            if (!TakeStep())
            {
                if (win)
                {
					MessageBox.Show("Wygrana w " + time.ToString() + " ruchach!");
                }
                else
                {
					MessageBox.Show("Agent nieżyje.");
                }

				bw = new BazaWiedzy();
				fillKB();

				_lvl.clearMap();
				_lvl.generateLevel(1, 2, 6, 1);

				win = false;
				alive = true;
				Score = 0;
				time = 1;

				RefreshList();
				RedrawWorld();
            }
        }

        private void RoundButton_Click(object sender, RoutedEventArgs e)
        {
            DateTime startTime = DateTime.Now;
            for(int i = 0; i < 100; ++i)
            {
                if(!TakeStep())
                {
					if (win)
					{
						MessageBox.Show("Wygrana w " + time.ToString() + " ruchach!");
					}
					else
					{
						MessageBox.Show("Agent nieżyje.");
					}

					bw = new BazaWiedzy();
					fillKB();

					_lvl.clearMap();
					_lvl.generateLevel(1, 2, 6, 1);
					win = false;
					alive = true;
					Score = 0;
					time = 1;

					RefreshList();
					RedrawWorld();

					break;
                }
                if (win)
                {
                    //hurray
                    ScoreTextBlock.Text = ScoreTextBlock.Text + "(zwycięstwo) ";
                }
                else
                {
                    //boo hoo
                    ScoreTextBlock.Text = ScoreTextBlock.Text + "(śmierć) ";
                }
                bw = new BazaWiedzy();
                alive = true;
                win = false;
                time = 1;
                return_timer = 0;
                Score = 0;
                ScoreTextBlock.Text = "0";
                initRes();
                RedrawWorld();
            }
            DateTime stopTime = DateTime.Now;
            TimeSpan roznica = stopTime - startTime;
            TimeTextBlock.Text = roznica.TotalMilliseconds.ToString() + " ms";
        }
	}
}

Kontrolka Image wstawiona do WrapPanel zawsze przesuwa kontrolkę Border i Grid nawet, gdy ustawi się im inne atrybuty "Panel.ZIndex". Ustawienie zaś zbyt dużego marginesu kontrolce Image wstawionej w komórce Grid powoduje, że wszystkie obrazki w Grid znikają.

Jak zrobić animację poruszania się przezroczystego obrazka z jednej komórki Grid do sąsiedniej (np. z komórki pierwszej z góry i z lewej strony do sąsiedniej po prawej stronie)? Przezroczysty w sensie, że piksele przezroczystości w PNG nie zasłaniają tego co pod spodem, ale to pewnie da się to zrobić za pomocą atrybutu Opacity. Obrazek gracza, o którym piszę, miałby być przesuwany po naciśnięciu przycisku i na podstawie odpowiedniej zmiennej string. Teraz jest usuwany i wstawiany w inne miejsce tak jak inne obrazki.

0

04:25 - 17:35 ~= 10h50m.. to nie tak znowu dużo jak na nietrywialny problem i forum internetowe. Twój post nawet z pierwszej storny listy wątków nie zdążył spaść. Powinines się zacząć niepokoić, jak spadnie i minie ze dwa-trzy dni..

W konteście animacji, niestety (i oczywiście) nie jesteś w stanie animować płynnie pozycji att-prop'ów Grid.Column ani Grid.Row. Są całkowite, źródłowy jest N, docelowy jest N+-1, nie ma nic pomiędzy. Jeśli uprzesz się na pozostawanie przy Grid, siłą rzeczy będziesz musiał kombinować ze sztucznymi przesunięciami, aż element "dojedzie" do swojej docelowej pozycji, i wtedy za jedym razem musisz wykonać przeskok na N+-1 oraz usunąć sztuczne przesunięcia.

Przesunięcia możesz próbować realizowac poprzez Margin, ale jest to trochę .. brudne. Raz, że margines nie do tego słyżu "i tyle", dwa, że jest on typu Thickness, który .. nie da się łatwo animować. Dokładnie na takie potrzeby, w WPF, każdy element rysowalny posiada "RenderTransforms" który możesz ustawić na dowolna kombinację i złożenie z:

  • Traslate
  • Rotate
  • Scale
    I z tego co pamiętam, da się je animować nawet StoryBoard'ami.
    Niemniej, nie uchroni Cię to przed wyliczaniem pozycji komórek grida w pikselach (na tym operuje Translate!), ani od czyszczenia przesunięcia z jedoczesnym przeskokiem N+-1 itp.

Generalnie, Grid też "nie do tego służy", acz jak widzisz, z odrobiną roboty da się - nawet prawie Ci się udało.

IMHO, do takich rzeczy przewidziano element 'Canvas', gdzie pozycje wszystkiego podajesz ręcznie w X/Y i animujesz przez to w miarę łatwo. Masz tam totalną kontrolę nad tym co kiedy gdzie się znajduje, i jest to kontrola absolutna - nic nie probuje niczego za Ciebie layoutować. i jest to też drobne utrudnienie, ponieważ nic nie zachowa layoutu Gridowego - musisz go sam wyliczyć. Zakładam jednak że "komórki" i tak miałeś wszystkie sztywnej i dobrze znanej szerokości? Podzielić actualsize na N komórek nie jest zbyt skomplikowane, a że w rozwiązaniu Gridowym, z animacja, i tak musialbys poprzeliczac szerokości komórek - to calkiem prawdopodobne, że kod oparty na Canvas wcale nie byłby dużo bardziej skomplikowany

disclaimer: nie wczytywałem się w Twoj kod. przeczytałem tylko opis problemu i pytania. nie probowałem szukac błędu w kodzie ani spradzać jak on działa.

0

Dziękuję za podpowiedź z Canvas. Użyłem kontrolki Canvas do umieszczenia obrazka gracza nad kontrolką Grid:

 <Window
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       x:Class="Wumpus.MainWindow"
       x:Name="Window"
       Title="Wumpus World"
       WindowState="Maximized">
        <StackPanel>
            <WrapPanel>
                <Canvas Panel.ZIndex="10" Name="PCanvas">
                    <Image  Panel.ZIndex="100" Name="Player" Source="gfx/Player.gif" Width="65" />
                </Canvas>
                    <!--<Image  Source="gfx/Player.gif" Width="65" Panel.ZIndex="100" Margin="100,0,0,0" />-->
                <Border Panel.ZIndex="0" BorderBrush="Black" BorderThickness="3" Width="503" Height="503">

                    <Grid Panel.ZIndex="0" Width="500" Height="500" ShowGridLines="true"  Name="WGrid">

                    </Grid>
                </Border>
                <ListBox Name="PredykatyList" Width="Auto" Height="500">
                   
                </ListBox>
               
            </WrapPanel>
            <Label Height="Auto">
                <WrapPanel>
                    <TextBlock Name="ScoreTextBlock" TextWrapping="Wrap"></TextBlock>
                    <TextBlock Name="TimeTextBlock" TextWrapping="Wrap"></TextBlock>
                </WrapPanel>
            </Label>
            <WrapPanel>
                <Button Content="Następny krok" Height="23" Name="StepButton" Width="175" Margin="20" Click="StepButton_Click" />
                <Button Content="Wykonaj 100 rundek" Height="23" Name="RoundButton" Width="175" Click="RoundButton_Click" />
            </WrapPanel>
           
        </StackPanel>
       
    </Window> 

Zadeklarowałem takie zmienne:

            Duration duration = new Duration(TimeSpan.FromSeconds(1));
            DoubleAnimation myDoubleAnimation = new DoubleAnimation();
            DoubleAnimation VerticalAnimation = new DoubleAnimation();
            double PlayerLeft = 0;
            double PlayerTop = 0;
            Storyboard sb = new Storyboard();
            Storyboard wsb = new Storyboard();
            double PlayerWidth; 

Na początku konstruktora okna:

             this.InitializeComponent();

             myDoubleAnimation.Duration = duration;
                sb.Duration = duration;
                sb.Children.Add(myDoubleAnimation);
                Storyboard.SetTarget(myDoubleAnimation, Player);
                Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath("(Canvas.Left)"));

                VerticalAnimation.Duration = duration;
                wsb.Duration = duration;
                wsb.Children.Add(VerticalAnimation);
                Storyboard.SetTarget(VerticalAnimation, Player);
                Storyboard.SetTargetProperty(VerticalAnimation, new PropertyPath("(Canvas.Top)"));

                _imgGrid = new Image[_size,_size]; 

W ten sposób poruszam obrazek gracza:

                    switch (d)
                    {
                        case Wumpus.game.Direction.NORTH:
                            VerticalAnimation.From = PlayerTop;
                            PlayerTop -= PlayerWidth;
                            VerticalAnimation.To = PlayerTop;
                            wsb.Begin();
                            break;
                        case Wumpus.game.Direction.EAST:
                            myDoubleAnimation.From = PlayerLeft;
                            PlayerLeft += PlayerWidth;
                            myDoubleAnimation.To = PlayerLeft;
                            sb.Begin();
                            break;
                        case Wumpus.game.Direction.SOUTH:
                            VerticalAnimation.From = PlayerTop;
                            PlayerTop += PlayerWidth;
                            VerticalAnimation.To = PlayerTop;
                            wsb.Begin();
                            break;
                        case Wumpus.game.Direction.WEST:
                            myDoubleAnimation.From = PlayerLeft;
                            PlayerLeft -= PlayerWidth;
                            myDoubleAnimation.To = PlayerLeft;
                            sb.Begin();
                            break;
                    } 

A w ten sposób zrobiłem umieszczanie obrazka gracza nad komórką pierwszą od góry i od lewej, gdy gracz wygrywa lub przegrywa:

                    if (win)
                    {
                        MessageBox.Show("Wygrana w " + time.ToString() + " ruchach!");
                    }
                    else
                    {
                   MessageBox.Show("Agent nieżyje.");
                    }
                    PlayerLeft = 0;
                    PlayerTop = 0;
                    PCanvas.SetValue(Canvas.LeftProperty, PlayerLeft);
                    Player.SetValue(Canvas.TopProperty, PlayerTop);
                    VerticalAnimation.From = PlayerTop;
                    VerticalAnimation.To = PlayerTop;
                    wsb.Begin();
                    myDoubleAnimation.From = PlayerLeft;
                    myDoubleAnimation.To = PlayerLeft;
                    sb.Begin();
                bw = new BazaWiedzy();
                fillKB();
                   
                _lvl.clearMap();
                _lvl.generateLevel(1, 2, 6, 1);

                win = false;
                alive = true;
                Score = 0;
                time = 1;

                RefreshList();
                RedrawWorld();
 

Cały projekt: post ( http://forum.elkapw.pl/viewtopic.php?f=77&t=507 ), ZIP ( http://forum.elkapw.pl/download/file.php?id=297 ).

0
szmitek napisał(a)

Cały projekt: post ( http://forum.elkapw.pl/viewtopic.php?f=77&t=507 ), ZIP ( http://forum.elkapw.pl/download/file.php?id=297 ).

Prof. Kasprzak byłby dumny.

1 użytkowników online, w tym zalogowanych: 0, gości: 1