2012年12月4日火曜日

飛び跳ねるボール

ボールは重力の影響をうけ、画面下と左右の壁で跳ね返ります。
跳ね返り係数は100%で、空気抵抗は考えません。
ボール本体のほかに、残像を2つ表示しています。1つは50ミリ秒まえの、もう1つは100ミリ秒前の残像です。

かつて熱中した アルカノイド というゲームを思い出します。

MainWindow.xaml

<Window x:Class="AnimationTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="200" Width="300" SizeChanged="Window_SizeChanged">
    <Canvas Grid.Row="2" Name="canvas1">
        <Canvas.Resources>
            <Style TargetType="Ellipse">
                <Setter Property="Height" Value="10" />
                <Setter Property="Width" Value="10"/>
                <Setter Property="Stroke" Value="Black" />
                <Setter Property="Fill" Value="WhiteSmoke" />
            </Style>
        </Canvas.Resources>
        <Ellipse Name="ellipse1" Canvas.Left="{Binding Path=X}" Canvas.Top="{Binding Path=Y}" />
        <Ellipse Name="ellipse2" Canvas.Left="{Binding Path=X}" Canvas.Top="{Binding Path=Y}" Opacity=".5"/>
        <Ellipse Name="ellipse3" Canvas.Left="{Binding Path=X}" Canvas.Top="{Binding Path=Y}" Opacity=".2"/>
        <Rectangle Name="bar1" Width="10" Height="4" Fill="Black" Canvas.Bottom="0" Canvas.Left="{Binding Path=X}"/>
    </Canvas>
</Window>

MainWindow.xaml.cs

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace AnimationTest
{
    public partial class MainWindow Window
    {
        private Location location1 = new Location(0);
        private Location location2 = new Location(50);
        private Location location3 = new Location(100);
        private Task task1;
        private Task task2;
        private Task task3;
        private int rateY = 1200;
        private int rateX = 1000;

        private Stopwatch sw = new Stopwatch();

        public MainWindow()
        {
            InitializeComponent();

            this.ellipse1.DataContext = this.location1;
            this.ellipse2.DataContext = this.location2;
            this.ellipse3.DataContext = this.location3;
            this.bar1.DataContext = this.location1;
        }

        private void ComputeLocation(Location location)
        {
            double rateY2 = rateY / Math.Sqrt(this.canvas1.ActualHeight) * 0.52;
            int harfRateY = rateY / 2;
            double rateX2 = rateX / this.canvas1.ActualWidth * 0.55;
            int harfRateX = rateX / 2;

            while (!location.IsCanceled)
            {
                Thread.Sleep(15);

                long milliseconds = sw.ElapsedMilliseconds;
                milliseconds = milliseconds - location.Delay;
                if (milliseconds < 0)
                {
                    milliseconds = 0;
                }

                long fractionY = milliseconds % rateY;
                if (fractionY >= harfRateY)
                {
                    fractionY = rateY - fractionY;
                }
                double sqrtY = fractionY / rateY2;
                location.Y = (int)Math.Floor(sqrtY * sqrtY);

                long fractionX = milliseconds % rateX;
                if (fractionX >= harfRateX)
                {
                    fractionX = rateX - fractionX;
                }
                location.X = (int)Math.Floor(fractionX / rateX2);
            }
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.location1.IsCanceled = true;
            this.location2.IsCanceled = true;
            this.location3.IsCanceled = true;

            if (this.task1 != null)
            {
                this.task1.Wait();
            }
            if (this.task2 != null)
            {
                this.task2.Wait();
            }
            if (this.task3 != null)
            {
                this.task3.Wait();
            }

            this.location1.IsCanceled = false;
            this.location2.IsCanceled = false;
            this.location3.IsCanceled = false;

            sw.Restart();

            this.task1 = Task.Factory.StartNew(obj => ComputeLocation((Location)obj), this.location1);
            this.task2 = Task.Factory.StartNew(obj => ComputeLocation((Location)obj), this.location2);
            this.task3 = Task.Factory.StartNew(obj => ComputeLocation((Location)obj), this.location3);
        }
    }
}

Location.cs

using System.ComponentModel;

namespace AnimationTest
{
    public class Location INotifyPropertyChanged
    {
        public bool IsCanceled { setget; }

        private long delay;
        public long Delay
        {
            get return this.delay; }
        }

        private int x;
        public int X
        {
            get return this.x; }
            set
            {
                if (value != this.x)
                {
                    this.x = value;
                    NotifyPropertyChanged("X");
                }
            }
        }

        private int y;
        public int Y
        {
            get return this.y; }
            set
            {
                if (value != this.y)
                {
                    this.y = value;
                    NotifyPropertyChanged("Y");
                }
            }
        }

        public Location(long milliseconds)
        {
            this.delay = milliseconds;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(thisnew PropertyChangedEventArgs(info));
            }
        }
    }
}

0 件のコメント:

コメントを投稿