效果预览
MazeAnimation.gif技术需求
要做一个可以高度自定义的迷宫游戏
技术框架
基于.net 4.6 的技术框架
基于wpf的客户端开发框架
基于HandyControl的UI框架
主要功能技术实现
1.创建迷宫主界面
在主窗体中加入小球、迷宫地图容器、迷宫出口、遮罩阴影等元素
<Window x:Class="Maze.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:Maze"
mc:Ignorable="d"
Title="MainWindow" Height="1000" Width="1000" Background="Transparent" AllowsTransparency="True" WindowStyle="None" WindowStartupLocation="CenterScreen" SizeChanged="Window_SizeChanged" PreviewKeyDown="Window_PreviewKeyDown" Loaded="Window_Loaded">
<Window.Resources>
<!--迷宫出口-->
<PathGeometry x:Key="pg" Figures="M 150,100 L 100,100 L 100,900 L 800,900 L 800,920 M 850,920 L 850,900 L 900,900 L 900,100 L 100,100" />
<ContextMenu x:Key="ContextMenu" MenuItem.Click="ContextMenu_Click">
<MenuItem Header="删除"/>
<MenuItem Header="复制"/>
</ContextMenu>
</Window.Resources>
<Grid>
<Border Background="White" CornerRadius="8">
<Border.ToolTip>
<Grid></Grid>
</Border.ToolTip>
<Border.Effect>
<DropShadowEffect ShadowDepth="0" BlurRadius="5" />
</Border.Effect>
</Border>
<Grid>
<!--网格背景-->
<Canvas x:Name="myCanvas" />
<!--#region 遮罩阴影-->
<Border x:Name="bor" Background="Gray" Width="800" Height="800" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="100,100,0,0" Opacity="0.3">
<Border.Effect>
<BlurEffect Radius="10" KernelType="Box" />
</Border.Effect>
</Border>
<!--#endregion-->
<Canvas x:Name="box" Background="Transparent" MouseLeftButtonDown="Canvas_MouseLeftButtonDown" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" MouseMove="Canvas_MouseMove">
<!--#region 地图-->
<Path Stroke="Orange" StrokeThickness="2" x:Name="rect" ClipToBounds="True">
<Path.Effect>
<DropShadowEffect ShadowDepth="0" BlurRadius="5" Opacity="0.7" Color="#FFFF8A16" />
</Path.Effect>
<Path.Data>
<GeometryGroup x:Name="geo" FillRule="EvenOdd">
<!--<RectangleGeometry Rect="100,100,800,800" />-->
<!--<PathGeometry Figures="M 150,100 L 100,100 L 100,900 L 800,900 L 800,920 M 850,920 L 850,900 L 900,900 L 900,100 L 100,100" />-->
</GeometryGroup>
</Path.Data>
</Path>
<!--#endregion-->
<!--#region 小球-->
<Ellipse x:Name="elp" Width="20" Height="20" Fill="Orange" Canvas.Left="110" Canvas.Top="110">
<Ellipse.Effect>
<DropShadowEffect ShadowDepth="0" BlurRadius="6" Color="#FFD88D04" />
</Ellipse.Effect>
</Ellipse>
<!--#endregion-->
</Canvas>
</Grid>
<!--#region 控制按钮组-->
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="100,30" Orientation="Horizontal">
<StackPanel Orientation="Horizontal" x:Name="sp">
<CheckBox Content="编辑模式" x:Name="rcb" FontSize="12" Margin="0,0,10,0" Checked="CheckBox_Edit_Checked" />
<CheckBox Content="Play模式" x:Name="pcb" FontSize="12" Margin="0,0,10,0" Checked="CheckBox_Play_Checked" />
</StackPanel>
<Button Height="30" Width="80" Content="保存地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="Save_Click" />
<Button Height="30" Width="80" Content="另存为" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="SaveAs_Click" />
<Button Height="30" Width="80" Content="加载地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="LoadMap_Click" />
<Button Height="30" Width="80" Content="创建地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="Create_Click" />
<Button Height="30" Width="80" Content="撤销" Margin="10,0" Style="{StaticResource ButtonDanger}" Click="Undo_Click" />
<Button Height="30" Width="80" Content="关闭" Margin="10,0" Style="{StaticResource ButtonDanger}" Click="CloseWin_Click" />
</StackPanel>
<!--#endregion-->
</Grid>
</Window>
2.定义好主要的属性及枚举
/// <summary>
/// 小球可视范围区域
/// </summary>
EllipseGeometry egy = null;
/// <summary>
/// 当前模式(play&Redit)
/// </summary>
private static Pattern currentPattern;
/// <summary>
/// 键盘每一步操作移动的距离,单位px
/// </summary>
private double distance = 3;
/// <summary>
/// 绘制的直线
/// </summary>
private Line line;
/// <summary>
/// 绘制的辅助线
/// </summary>
private Line guide_Line;
/// <summary>
/// 迷宫地图实线的集合
/// </summary>
private List<LineGeometry> geometries = new List<LineGeometry>();
/// <summary>
/// 点击的初始位置
/// </summary>
Point orginPoint;
/// <summary>
/// 是否按下鼠标
/// </summary>
bool isPressed = false;
/// <summary>
/// 焦点处的小圆
/// </summary>
Ellipse ellipse;
/// <summary>
/// 地图存储文件夹路径
/// </summary>
private string MapFolderPath = UniTools.AppRootPath + "map";
/// <summary>
/// 当前打开的迷宫文件地址
/// </summary>
private string currentMazeFilePath = "";
/// <summary>
/// 模式
/// </summary>
public enum Pattern
{
/// <summary>
/// 编辑模式
/// </summary>
Redit,
/// <summary>
/// 游戏模式
/// </summary>
Play
}
3.绘制网格背景
#region 网格背景
/// <summary>
/// 绘制网格背景
/// </summary>
/// <param name="canvas"></param>
public static void Draw(Canvas canvas)
{
var gridBrush = new SolidColorBrush { Color = Colors.Gray };
double scaleX = 20;
double currentPosY = 0;
currentPosY += scaleX;
while (currentPosY < canvas.ActualHeight)
{
Line line = new Line
{
X1 = 0,
Y1 = currentPosY,
X2 = canvas.ActualWidth,
Y2 = currentPosY,
Stroke = gridBrush,
StrokeThickness = 0.1
};
canvas.Children.Add(line);
currentPosY += scaleX;
}
double scaleY = 20;
double currentPosX = 0;
currentPosX += scaleY;
while (currentPosX < canvas.ActualWidth)
{
Line line = new Line
{
X1 = currentPosX,
Y1 = 0,
X2 = currentPosX,
Y2 = canvas.ActualHeight,
Stroke = gridBrush,
StrokeThickness = 0.1
};
canvas.Children.Add(line);
currentPosX += scaleY;
}
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
Draw(myCanvas);
}
#endregion
4.迷宫地图的创建
这里只要在相关事件里去初始化地图就行,清除掉绘制的线,还原小球的位置,隐藏掉遮罩层
/// <summary>
/// 初始化地图
/// </summary>
private void InitailzeBox()
{
bor.Visibility = Visibility.Collapsed;
egy = null;
box.Clip = null;
geo.Children.Clear();
box.Background = new SolidColorBrush(Colors.Transparent);
var pathgeo = (PathGeometry)this.Resources["pg"];
geo.Children.Add(pathgeo);
geometries.Clear();
box.Children.RemoveRange(2, box.Children.Count - 1);
rcb.IsChecked = true;
BackOrginLocation();
}
/// <summary>
/// 初始化小球及圆形可视区域中心位置
/// </summary>
private void BackOrginLocation()
{
Canvas.SetLeft(elp, 110);
Canvas.SetTop(elp, 110);
if (egy != null)
{
egy.Center = new Point(110 + elp.Width / 2, 110 + elp.Height / 2);
}
}
5.编辑模式下的迷宫地图绘制
编辑模式下可以用鼠标在矩形区域内进行地图绘制,当鼠标处于某个焦点时按下会显示一个圆形的标志,然后拖拽鼠标,此时会有一条虚线(辅助线)跟随,到达指定位置抬起鼠标后即可完成一条线段的绘制,如此反复即可完成地图的绘制
#region 鼠标操作相关事件处理
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (currentPattern == Pattern.Play) return;
if (e.LeftButton == MouseButtonState.Pressed)
{
isPressed = true;
orginPoint = e.GetPosition(box);
var result = VisualTreeHelper.HitTest(rect, orginPoint);
if (result != null)
{
CreateFocusPoint(orginPoint.X, orginPoint.Y);
}
}
e.Handled = true;
}
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (currentPattern == Pattern.Play) return;
if (isPressed)
{
isPressed = false;
//移除焦点区域的圆
RemoveFoucusPoint();
//创建迷宫实线
CreateMazeLiens();
//释放鼠标捕获
box.ReleaseMouseCapture();
}
e.Handled = true;
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (currentPattern == Pattern.Play) return;
if (isPressed)
{
//终点坐标
var EndPoint = e.GetPosition(rect);
//创建一条实线
if (line == null)
{
line = new Line();
line.Stroke = Brushes.Orange;
line.StrokeThickness = 2;
//line.StrokeDashArray = new DoubleCollection() { 2, 3 };
line.StrokeDashCap = PenLineCap.Triangle;
line.StrokeEndLineCap = PenLineCap.Square;
line.StrokeStartLineCap = PenLineCap.Round;
}
if (!box.Children.Contains(line))
{
box.Children.Add(line);
}
//创建辅助线
CreateGuidesByLineAndEndPoint(EndPoint);
}
else
{
//移除实线和辅助线
RemoveMazeLineAndGuides();
}
e.Handled = true;
}
#endregion
6.编辑模式和play模式的切换
play模式下需要显示遮罩层,并创建一个圆形的可视范围,可视范围会跟随小球运动,从而达到隐匿大部分地图的目的,增强迷宫的可玩性
#region 编辑模式和play模式切换处理
/// <summary>
/// 切换操作模式
/// </summary>
private void SwitchPattern()
{
//Play模式
if (currentPattern == Pattern.Play)
{
bool isSuccess = false;
//自动保存当前已编辑内容
Save(ref isSuccess);
//重新加载地图
LoadMapFromFolder();
//1.显示遮罩层
bor.Visibility = Visibility.Visible;
//2.创建圆形可视范围
if (egy == null)
{
egy = new EllipseGeometry()
{
Center = new Point(130, 130),
RadiusX = 60,
RadiusY = 60
};
}
//3.区域裁剪(通过裁剪显示圆形可视范围内的元素)
box.Clip = egy;
box.Background = new SolidColorBrush(Colors.White);
}
else
{
//编辑模式
//1.隐藏遮罩层
bor.Visibility = Visibility.Collapsed;
//2.圆形可视范围清空掉
egy = null;
//3.裁剪区域清空
box.Clip = null;
box.Background = new SolidColorBrush(Colors.Transparent);
}
//初始化可视范围位置
BackOrginLocation();
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (!this.IsLoaded) return;
CheckBox _ckb = sender as CheckBox;
var name = _ckb.Name;
foreach (CheckBox ckb in sp.Children)
{
if (ckb.IsChecked == true)
{
ckb.IsChecked = false;
}
}
if (name == "rcb")
{
//编辑模式
currentPattern = Pattern.Redit;
pcb.IsChecked = false;
}
else
{
//Play模式
currentPattern = Pattern.Play;
rcb.IsChecked = false;
}
//模式切换
SwitchPattern();
}
#endregion
7.play模式下的操作及闯关成功判定
支持“WASD”键及小键盘的方向键控制小球的移动
#region 键盘操作
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
var left = Canvas.GetLeft(elp);
var top = Canvas.GetTop(elp);
var val = 0d;
var EndPosition = new Point();
var hitPoint = new Point();
switch (e.Key)
{
case Key.Up:
case Key.W:
val = top - distance;
EndPosition.X = left;
EndPosition.Y = val;
hitPoint.X = EndPosition.X + elp.Width / 2;
hitPoint.Y = EndPosition.Y;
break;
case Key.Down:
case Key.S:
val = top + distance;
EndPosition.X = left;
EndPosition.Y = val;
hitPoint.X = EndPosition.X + elp.Width / 2;
hitPoint.Y = EndPosition.Y + elp.Height;
break;
case Key.Left:
case Key.A:
val = left - distance;
EndPosition.X = val;
EndPosition.Y = top;
hitPoint.X = EndPosition.X;
hitPoint.Y = EndPosition.Y + elp.Height / 2;
break;
case Key.Right:
case Key.D:
val = left + distance;
EndPosition.X = val;
EndPosition.Y = top;
hitPoint.X = EndPosition.X + elp.Width;
hitPoint.Y = EndPosition.Y + elp.Height / 2;
break;
default:
return;
}
var _center_x = EndPosition.X + elp.Width / 2;
var _center_y = EndPosition.Y + elp.Height / 2;
//修改小球可视化范围位置
if (egy != null)
{
egy.Center = new Point(_center_x, _center_y);
}
//通过命中测试判断是否碰到了实线
bool isContinue = true;
VisualTreeHelper.HitTest(rect, null, f =>
{
isContinue = false;
return HitTestResultBehavior.Continue;
}, new GeometryHitTestParameters(new EllipseGeometry(new Point(_center_x, _center_y), 10, 10)));
//如果没有碰到实线则进行移动处理
if (isContinue)
{
Canvas.SetLeft(elp, EndPosition.X);
Canvas.SetTop(elp, EndPosition.Y);
}
//判断是否闯关成功
if (EndPosition.Y > 900)
{
//成功
MessageBox.Show("恭喜你闯关成功!");
//初始化位置
BackOrginLocation();
}
}
#endregion
8.迷宫地图的保存
将绘制的线段集合对象序列化到xml文件中
/// <summary>
/// 保存地图
/// </summary>
/// <param name="isSuccess"></param>
private void Save(ref bool isSuccess)
{
if (geometries != null && geometries.Count > 0)
{
//将绘制的线段填充到地图线段集合
MazePath mp = new MazePath();
mp.LineList = new List<MazePath.MazeLine>();
foreach (var g in geometries)
{
MazePath.MazeLine mline = new MazePath.MazeLine
{
StartPoint = g.StartPoint,
EndPoint = g.EndPoint
};
mp.LineList.Add(mline);
}
//序列化成xml文件
UniTools.Serialize_to_xml(currentMazeFilePath, mp);
if (System.IO.File.Exists(currentMazeFilePath))
{
isSuccess = true;
}
else
{
isSuccess = false;
}
}
}
9.迷宫地图的加载
反序列化xml文件,将序列化后的地图线段(LineGeometry)填充到画板中并赋予相关的事件
/// <summary>
/// 根据文件路径加载地图
/// </summary>
/// <param name="path"></param>
private void LoadMap(string path)
{
//初始化UI
InitailzeBox();
var _mp = (MazePath)UniTools.Deserialize_from_xml(path, typeof(MazePath));
currentMazeFilePath = path;
if (_mp != null)
{
if (_mp.LineList != null && _mp.LineList.Count > 0)
{
foreach (var item in _mp.LineList)
{
LineGeometry lineGeometry = new LineGeometry
{
StartPoint = item.StartPoint,
EndPoint = item.EndPoint
};
geometries.Add(lineGeometry);
Path p = new Path()
{
Stroke = new SolidColorBrush(Colors.Orange),
StrokeThickness = 2,
Data = lineGeometry
};
p.MouseEnter += (s, m) =>
{
p.Stroke = new SolidColorBrush(Colors.Red);
p.StrokeThickness = 4;
};
p.MouseLeave += (s, l) =>
{
p.Stroke = new SolidColorBrush(Colors.Orange);
p.StrokeThickness = 2;
};
p.MouseLeftButtonDown += (s, b) =>
{
if (b.ClickCount == 2)
{
line = null;
box.Children.Remove(p);
geometries.Remove(lineGeometry);
}
else
{
isPressed = true;
orginPoint = b.GetPosition(box);
var result = VisualTreeHelper.HitTest(p, orginPoint);
if (result != null)
{
CreateFocusPoint(orginPoint.X, orginPoint.Y);
}
}
};
//需要将绘制的线段加入到GeometryGroup中去才能进行整体的命中测试
geo.Children.Add(lineGeometry);
box.Children.Add(p);
}
}
}
}
/// <summary>
/// 从目录中读取地图数据
/// </summary>
private void LoadMapFromFolder()
{
UniTools.CreateFolder(MapFolderPath);
var fileInfo = System.IO.Directory.GetFiles(MapFolderPath);
if (fileInfo.Length > 0)
{
var loadPath = fileInfo[0];
LoadMap(loadPath);
}
}
网友评论