游戏中有各种各样的拖拽需求,大到窗口,小到图标,在游戏界面操作中,点击和拖拽占据了用户操作的大部分行为,如何做好一个拖拽控件至关重要,做一个可重用的拖拽控件更加重要,我的这些实现方法可能比较另类,但只要有效就行,在这个基础上,你可以扩展很多的做法。
可能有朋友已经写了这方面的文章,但是本篇介绍的方法是一个可以一劳永逸的重用控件,只需要一个基类代码就可以完成所有的需求——图标、窗体、自定义的目标,所以,本片没有放在小技巧里而是游戏开发分类里。
最先,需要了解一下拖拽原理,即当鼠标按下做一个标识,在鼠标移动时实时修改目标坐标信息,鼠标抬起的时候,释放掉鼠标操作,当然了,为了更好的操作坐标,我们一般将父容器改成Canvas。
将围绕这个做后面的工作,请了解之前有关基类和容器的概念,这样后面看起来就容易很多,考虑它的重用性,创建一个基本控件让后面的控件继承,将一些通用逻辑写在这个基类里,让其他的类去继承重用,这也就是游戏引擎的基本做法之一。
那么,在Blend或者Visual Studio里创建一个名为MovableObject的控件,然后将控件xaml修改成下面的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <UserControl 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" mc:Ignorable= "d" x:Class= "DragObject.MovableObject" d:DesignWidth= "640" d:DesignHeight= "480" Width= "Auto" Height= "Auto" > <Grid x:Name= "LayoutRoot" > <Rectangle Stroke= "Black" RadiusX= "5" RadiusY= "5" > <Rectangle.Fill> <LinearGradientBrush EndPoint= "0.5,1" StartPoint= "0.5,0" > <GradientStop Color= "Transparent" /> <GradientStop Color= "Black" Offset= "1" /> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Image x:Name= "ShowImage" Stretch= "Fill" /> <Rectangle x:Name= "Sel_Rectangle" Stroke= "White" StrokeThickness= "2" Visibility= "Collapsed" RadiusX= "5" RadiusY= "5" /> </Grid> </UserControl> |
即便使用代码实现并不难,但是为了方便理解,就直接使用XAML较为简便,浏览它的结构图就可以看到。
上面的表示方法分别是带了一个底,和一个要显示图像的Image,以及一个当鼠标移入时候需要表示选择标识。
那么下面就是Coding阶段,打开MovableObject类,代码设计如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public partial class MovableObject : UserControl { //鼠标点的保存,同时还承担是否点击的判定 Point ? mousePoint = null ; public MovableObject() { InitializeComponent(); //选择框隐蔽掉 Sel_Rectangle.Visibility = System.Windows.Visibility.Collapsed; } //鼠标移动的方法重载 protected override void OnMouseMove(MouseEventArgs e) { //判定是否按下鼠标左键 if (mousePoint!= null ) { //计算新的位置 double newTop = e.GetPosition( null ).Y - mousePoint.Value.Y + Canvas.GetTop( this ); double newLeft = e.GetPosition( null ).X - mousePoint.Value.X + Canvas.GetLeft( this ); Canvas.SetTop( this , newTop); Canvas.SetLeft( this , newLeft); mousePoint = e.GetPosition( null ); } base .OnMouseMove(e); } //鼠标抬起的方法重载 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { mousePoint = null ; //释放鼠标设备 this .ReleaseMouseCapture(); base .OnMouseLeftButtonDown(e); } //鼠标按下的方法重载 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { mousePoint = e.GetPosition( null ); //捕获鼠标设备 this .CaptureMouse(); //下面三行是用来保证当前控件为最顶层的做法 var parent = this .Parent as Panel; parent.Children.Remove( this ); parent.Children.Add( this ); base .OnMouseLeftButtonUp(e); } //鼠标进入的方法重载 protected override void OnMouseEnter(MouseEventArgs e) { Sel_Rectangle.Visibility = System.Windows.Visibility.Visible; base .OnMouseEnter(e); } //鼠标移出的方法重载 protected override void OnMouseLeave(MouseEventArgs e) { Sel_Rectangle.Visibility = System.Windows.Visibility.Collapsed; base .OnMouseLeave(e); } } |
我做了一些注释,可以很明确的得知这个基类的作用,现在可以新建类继承于这个类,来实现扩展的目的。
为此,我准备三种不同的目标效果——图标(MyIcon)、大图片(MyFace)、自定义控件(MyCard)
图标和图片只需要用上原有控件的Image即可,而自定义控件则是通过Blend或其他方式设计制作出来的独立控件,那么如何实现这三个效果呢?请往下看:
我们先创建三个类,他们都继承于MovableObject
public class MyIcon : MovableObject
public class MyFace : MovableObject
public class MyCard : MovableObject
下面在各自的构造函数中填入对应的操作逻辑即可,下面给出了完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class MyIcon : MovableObject { public MyIcon() { IconIndex = 1; } private int _Iconindex = -1; public int IconIndex { get { return _Iconindex; } set { _Iconindex = value; var uri = new Uri( "/DragObject;component/Res/image" + value + ".png" , UriKind.Relative); ShowImage.Source = new System.Windows.Media.Imaging.BitmapImage(uri); } } } public class MyFace : MovableObject { public MyFace() { var uri = new Uri( "/DragObject;component/Res/nowpaper.jpg" , UriKind.Relative); ShowImage.Source = new System.Windows.Media.Imaging.BitmapImage(uri); |
1 2 3 4 5 6 7 8 9 10 | } } public class MyCard : MovableObject { public MyCard() { LayoutRoot.Children.Insert(LayoutRoot.Children.IndexOf(ShowImage), new Card()); LayoutRoot.Children.Remove(ShowImage); } } |
MyFace是直接修改ShowImage的Source,MyIcon里写了一个索引属性,这样可以在外面控制ShowImage,显示需要显示的图片,而MyCard里new出来一个控件,并替换掉了原有的ShowImage,MyCard使用了前面制作的一个控件。
好了,现在在MainPage的构造函数中编写其他的代码,但是LayoutRoot需要变成Canvas,这样才能更好的控制图像位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public MainPage() { InitializeComponent(); //添加自定义的拖拽目标 var icon = new MyIcon(); LayoutRoot.Children.Add(icon); Canvas.SetLeft(icon, 20); Canvas.SetTop(icon, 50); icon = new MyIcon() { IconIndex = 3 }; LayoutRoot.Children.Add(icon); Canvas.SetLeft(icon, 20); Canvas.SetTop(icon, 150); var face = new MyFace(); LayoutRoot.Children.Add(face); Canvas.SetLeft(face, 100); Canvas.SetTop(face, 50); var card = new MyCard(); LayoutRoot.Children.Add(card); Canvas.SetLeft(card, 270); Canvas.SetTop(card, 50); } |
现在运行看看效果吧,在这个基础上,可以制作窗口或需要拖拽的物体只需要继承修改一下,在下一篇,将会制作一个窗口并使用技巧完成拖入等操作,放心吧绝对不复杂。
本篇工程源代码下载地址如下:点击直接下载
本篇文章的作者:Nowpaper
推荐Silverlight游戏开发博客:深蓝色右手
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架