画布上的WPF池游戏式弹弓交互

时间:2018-01-17 09:29:45

标签: c# wpf canvas

大家好日子,

我有一个仅限触摸的应用程序,我想创建一个弹弓交互,例如,你可以在池游戏中找到它。像“愤怒的小鸟”之类的东西,但没有引力的物理特性,只有惯性和减速。

要大致了解一下:

Something like that, although I don't care about balls physics and hitting each other, I just want the slingshot interaction.

目前,我在Canvas中有一个BouncingUserControl。用户应该能够做两件事:

  • 如果他使用一个触摸点:拖放BouncingUserControl,然后以一定的速度抛出它。 BouncingUserControl在释放后,将从主窗口的边界反弹并停留在所述窗口内。

  • 如果他在拖动BouncingUserControl 时使用第二个触摸点,则会出现一种弹性:两个触点越远,弹性就越紧张。释放两个触摸点中的一个后,BouncingUserControl将以相反方向的初始速度(取决于“弹性”有多紧张)抛出。

从编程上讲,我走到这一步:

这是我的BouncingUserControl,一个简单的椭圆:

<UserControl x:Class="Slingshot.BouncingUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             ManipulationCompleted="ParentCanvasOnManipulationCompleted"
             ManipulationInertiaStarting="ParentCanvasOnManipulationInertiaStarting"
             ManipulationDelta="ParentCanvasOnManipulationDelta"
             ManipulationStarting="ParentCanvasOnManipulationStarting"
             >
    <Grid>
        <Ellipse Width="50" Height="50" Fill="Beige"/>
    </Grid>
</UserControl>

背后的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Slingshot
{
    /// <summary>
    /// Logique d'interaction pour BouncingUserControl.xaml
    /// </summary>

    public partial class BouncingUserControl : UserControl
    {
        public enum HorizontalEdgeBounceType
        {
            None,
            Left,
            Right
        }

        public enum VerticalEdgeBounceType
        {
            None,
            Top,
            Bottom
        }

        public BouncingUserControl()
        {
            InitializeComponent();

        }

        private HorizontalEdgeBounceType CurrentHorizontalBounce { get; set; }

        private VerticalEdgeBounceType CurrentVerticalBounce { get; set; }


        public void ParentCanvasOnManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs args)
        {
            ((MainWindow)Window.GetWindow(this)).IsCurrentlyManipulating = false;
            ((MainWindow)Window.GetWindow(this)).currentlyManipulatedControl = null;

            var element = args.OriginalSource as FrameworkElement;
            if (element == null)
            {
                return;
            }

            //args.TranslationBehavior.DesiredDeceleration = 40.0 * 96.0 / (1000.0 * 1000.0);
            args.TranslationBehavior.DesiredDeceleration = 20.0 * 96.0 / (1000.0 * 1000.0);
            //args.TranslationBehavior.DesiredDeceleration = 0;
            args.Handled = true;
        }

        private void ParentCanvasOnManipulationCompleted(object sender, ManipulationCompletedEventArgs manipulationCompletedEventArgs)
        {
            CurrentHorizontalBounce = HorizontalEdgeBounceType.None;
            CurrentVerticalBounce = VerticalEdgeBounceType.None;

        }

        private void ParentCanvasOnManipulationDelta(object sender, ManipulationDeltaEventArgs args)
        {
            var element = args.OriginalSource as FrameworkElement;

            if (element != null)
            {
                if (args.IsInertial)
                {
                    HandleHorizontalBounce(args);
                    HandleVerticalBounce(args);

                    args.Handled = true;
                    return;
                }

                var currentLeft = Canvas.GetLeft(this);
                Canvas.SetLeft(this, currentLeft + args.DeltaManipulation.Translation.X);

                var currentTop = Canvas.GetTop(this);
                Canvas.SetTop(this, currentTop + args.DeltaManipulation.Translation.Y);

                args.Handled = true;
            }
        }

        private void ParentCanvasOnManipulationStarting(object sender, ManipulationStartingEventArgs args)
        {
            args.ManipulationContainer = ParentCanvas;
            args.Handled = true;

            ((MainWindow)Window.GetWindow(this)).IsCurrentlyManipulating = true;
            ((MainWindow)Window.GetWindow(this)).currentlyManipulatedControl = this;

        }

        public static readonly DependencyProperty ParentCanvasProperty = DependencyProperty.Register(
            "ParentCanvas", typeof(Canvas), typeof(BouncingUserControl), new PropertyMetadata(default(Canvas)));

        public Canvas ParentCanvas
        {
            get { return (Canvas)GetValue(ParentCanvasProperty); }
            set { SetValue(ParentCanvasProperty, value); }
        }

        private void HandleVerticalBounce(ManipulationDeltaEventArgs args)
        {
            var currentTop = Canvas.GetTop(this);

            //Update the current Vertical bounce if necessary
            if (currentTop < 0)
            {
                CurrentVerticalBounce = VerticalEdgeBounceType.Top;
            }
            else if (currentTop + ActualHeight >= ParentCanvas.ActualHeight)
            {
                CurrentVerticalBounce = VerticalEdgeBounceType.Bottom;
            }

            if (CurrentVerticalBounce != VerticalEdgeBounceType.None)
            {
                //Apply the new Translation taking into account we have bounced vertically
                switch (CurrentVerticalBounce)
                {
                    case VerticalEdgeBounceType.Top:
                        Canvas.SetTop(this, currentTop + Math.Abs(args.DeltaManipulation.Translation.Y));
                        break;
                    case VerticalEdgeBounceType.Bottom:
                        Canvas.SetTop(this, currentTop - Math.Abs(args.DeltaManipulation.Translation.Y));
                        break;
                }
            }
            else
            {
                //Just apply the Translation as normal
                Canvas.SetTop(this, currentTop + args.DeltaManipulation.Translation.Y);
            }
        }

        private void HandleHorizontalBounce(ManipulationDeltaEventArgs args)
        {
            var currentLeft = Canvas.GetLeft(this);

            //Update the current Horizontal bounce if necessary
            if (currentLeft < 0)
            {
                CurrentHorizontalBounce = HorizontalEdgeBounceType.Left;
            }
            else if (currentLeft + ActualWidth >= ParentCanvas.ActualWidth)
            {
                CurrentHorizontalBounce = HorizontalEdgeBounceType.Right;
            }

            if (CurrentHorizontalBounce != HorizontalEdgeBounceType.None)
            {
                //Apply the new Translation taking into account we have bounced horizontally
                switch (CurrentHorizontalBounce)
                {
                    case HorizontalEdgeBounceType.Left:
                        Canvas.SetLeft(this, currentLeft + Math.Abs(args.DeltaManipulation.Translation.X));
                        break;
                    case HorizontalEdgeBounceType.Right:
                        Canvas.SetLeft(this, currentLeft - Math.Abs(args.DeltaManipulation.Translation.X));
                        break;
                }
            }
            else
            {
                //Just apply the Translation as normal
                Canvas.SetLeft(this, currentLeft + args.DeltaManipulation.Translation.X);
            }
        }
    }
}

这个BouncingUserControl的容器是一个简单的画布:

<Window x:Class="Slingshot.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Slingshot"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Canvas x:Name="canvas" Background="#0a6c03" TouchDown="canvas_TouchDown" TouchMove="canvas_TouchMove" TouchUp="canvas_TouchUp">
            <local:BouncingUserControl  ParentCanvas="{Binding ElementName=canvas}" IsManipulationEnabled="True" Canvas.Top="50" Canvas.Left="50" />
        </Canvas>
        <TextBlock x:Name="indicator" HorizontalAlignment="Left" VerticalAlignment="Top"/>
    </Grid>
</Window>

使用此代码隐藏:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Slingshot
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public BouncingUserControl currentlyManipulatedControl;
        public Boolean IsCurrentlyManipulating = false;
        Line slingshotAim;

        public MainWindow()
        {
            InitializeComponent();

            InitializeSlingShot();
        }

        private void InitializeSlingShot()
        {
            slingshotAim = new Line();
            slingshotAim.Stroke = Brushes.LightSteelBlue;

            slingshotAim.StrokeThickness = 3;
            canvas.Children.Add(slingshotAim);
            slingshotAim.Visibility = Visibility.Hidden;
        }

        private void canvas_TouchDown(object sender, TouchEventArgs e)
        {
            Console.WriteLine(canvas.TouchesOver.Count());

            if (IsCurrentlyManipulating && canvas.TouchesOver.Count() == 2)
            {
                slingshotAim.Visibility = Visibility.Visible;

                slingshotAim.X1 = canvas.TouchesOver.ElementAt(0).GetTouchPoint(canvas).Position.X;
                slingshotAim.Y1 = canvas.TouchesOver.ElementAt(0).GetTouchPoint(canvas).Position.Y;

                slingshotAim.X2 = canvas.TouchesOver.ElementAt(1).GetTouchPoint(canvas).Position.X;
                slingshotAim.Y2 = canvas.TouchesOver.ElementAt(1).GetTouchPoint(canvas).Position.Y;
            }
        }

        private void canvas_TouchUp(object sender, TouchEventArgs e)
        {
            if (IsCurrentlyManipulating && canvas.TouchesOver.Count() == 2)
            {
                double slingshotLength = Math.Sqrt(Math.Pow((slingshotAim.X2-slingshotAim.X1),2)+Math.Pow((slingshotAim.Y2-slingshotAim.Y1),2));
                indicator.Text = slingshotLength.ToString();
                slingshotAim.Visibility = Visibility.Hidden;

            }
        }

        private void canvas_TouchMove(object sender, TouchEventArgs e)
        {
            if (IsCurrentlyManipulating && canvas.TouchesOver.Count() == 2)
            {
                double slingshotLength = Math.Sqrt(Math.Pow((slingshotAim.X2 - slingshotAim.X1), 2) + Math.Pow((slingshotAim.Y2 - slingshotAim.Y1), 2));
                indicator.Text = slingshotLength.ToString();

                slingshotAim.X1 = canvas.TouchesOver.ElementAt(0).GetTouchPoint(canvas).Position.X;
                slingshotAim.Y1 = canvas.TouchesOver.ElementAt(0).GetTouchPoint(canvas).Position.Y;

                slingshotAim.X2 = canvas.TouchesOver.ElementAt(1).GetTouchPoint(canvas).Position.X;
                slingshotAim.Y2 = canvas.TouchesOver.ElementAt(1).GetTouchPoint(canvas).Position.Y;
            }
        }
    }
}

我尝试过一个事件,我会传递一个ManipulationEventArgs,但是没有构造函数。另一种解决问题的方法是在Canvas.TopProperty和Canvas.LeftProperty上使用动画,但是这会阻止我使用水平和垂直反弹,所以我宁愿不这样做。

感谢您提供任何帮助或提示。

0 个答案:

没有答案