SkiaSharp 之 WPF 自绘 投篮小游戏(案例版)

打印 上一主题 下一主题

主题 916|帖子 916|积分 2748

此案例主要是针对光线投影法碰撞检测功能的示例,顺便做成了一个小游戏,很简单,但是,效果却很不错。
投篮小游戏

规则,点击投篮目标点,就会有一个球沿着相关抛物线,然后,判断是否进入篮子里,其实就是一个矩形,直接是按照碰撞检测来的,碰到就算进去了,对其增加了一个分数统计等功能。
Wpf 和 SkiaSharp

新建一个 WPF 项目,然后,Nuget 包即可
要添加 Nuget 包
  1. Install-Package SkiaSharp.Views.WPF -Version 2.88.0
复制代码
其中核心逻辑是这部分,会以我设置的 60FPS 来刷新当前的画板。
  1. skContainer.PaintSurface += SkContainer_PaintSurface;
  2. _ = Task.Run(() =>
  3. {
  4.     while (true)
  5.     {
  6.         try
  7.         {
  8.             Dispatcher.Invoke(() =>
  9.             {
  10.                 skContainer.InvalidateVisual();
  11.             });
  12.             _ = SpinWait.SpinUntil(() => false, 1000 / 60);//每秒60帧
  13.         }
  14.         catch
  15.         {
  16.             break;
  17.         }
  18.     }
  19. });
复制代码
弹球实体代码 (Ball.cs)
  1. public class Ball
  2. {
  3.     public double X { get; set; }
  4.     public double Y { get; set; }
  5.     public double VX { get; set; }
  6.     public double VY { get; set; }
  7.     public int Radius { get; set; }
  8. }
复制代码
粒子花园核心类 (ParticleGarden.cs)
  1. /// <summary>
  2. /// 光线投影法碰撞检测
  3. /// 投篮小游戏
  4. /// </summary>
  5. public class RayProjection
  6. {
  7.     public SKPoint centerPoint;
  8.     public double G = 0.3;
  9.     public double F = 0.98;
  10.     public double Easing = 0.03;
  11.     public bool IsMoving = false;
  12.     public SKPoint CurrentMousePoint = SKPoint.Empty;
  13.     public SKPoint lastPoint = SKPoint.Empty;
  14.     public Rect Box;
  15.     public Ball Ball;
  16.     public SKCanvas canvas;
  17.     public int ALLCount = 10;
  18.     public List<bool> bools = new List<bool>();
  19.     public bool IsOver = false;
  20.     /// <summary>
  21.     /// 渲染
  22.     /// </summary>
  23.     public void Render(SKCanvas canvas, SKTypeface Font, int Width, int Height)
  24.     {
  25.         canvas.Clear(SKColors.White);
  26.         this.canvas = canvas;
  27.         centerPoint = new SKPoint(Width / 2, Height / 2);
  28.         //球
  29.         if (Ball == null)
  30.         {
  31.             Ball = new Ball()
  32.             {
  33.                 X = 50,
  34.                 Y = Height - 50,
  35.                 Radius = 30
  36.             };
  37.         }
  38.         //箱子
  39.         var boxX = Width - 170;
  40.         var boxY = Height - 80;
  41.         if (Box.X == 0)
  42.         {
  43.             Box = new Rect(boxX, boxY, 120, 70);
  44.         }
  45.         else
  46.         {
  47.             if (Box.X != boxX && Box.Y != boxY)
  48.             {
  49.                 Box.X = boxX;
  50.                 Box.Y = boxY;
  51.             }
  52.         }
  53.         if (bools.Count >= ALLCount)
  54.         {
  55.             IsOver = true;
  56.         }
  57.         if (!IsOver)
  58.         {
  59.             if (IsMoving)
  60.             {
  61.                 BallMove(Width, Height);
  62.             }
  63.             else
  64.             {
  65.                 DrawLine();
  66.             }
  67.             //弹球
  68.             DrawCircle(canvas, Ball);
  69.             //矩形
  70.             DrawRect(canvas, Box);
  71.             //计分
  72.             using var paint1 = new SKPaint
  73.             {
  74.                 Color = SKColors.Blue,
  75.                 IsAntialias = true,
  76.                 Typeface = Font,
  77.                 TextSize = 24
  78.             };
  79.             string count = $"总次数:{ALLCount} 剩余次数:{ALLCount - bools.Count} 投中次数:{bools.Count(t => t)}";
  80.             canvas.DrawText(count, 100, 20, paint1);
  81.         }
  82.         else
  83.         {
  84.             SKColor sKColor = SKColors.Blue;
  85.             //计分
  86.             var SuccessCount = bools.Count(t => t);
  87.             string count = "";
  88.             switch (SuccessCount)
  89.             {
  90.                 case 0:
  91.                     {
  92.                         count = $"太糗了吧,一个都没投中!";
  93.                         sKColor = SKColors.Black;
  94.                     }
  95.                     break;
  96.                 case 1:
  97.                 case 2:
  98.                 case 3:
  99.                 case 4:
  100.                 case 5:
  101.                     {
  102.                         count = $"你才投中:{SuccessCount}次,继续努力!";
  103.                         sKColor = SKColors.Blue;
  104.                     }
  105.                     break;
  106.                 case 6:
  107.                 case 7:
  108.                 case 8:
  109.                 case 9:
  110.                     {
  111.                         count = $"恭喜 投中:{SuccessCount}次!!!";
  112.                         sKColor = SKColors.YellowGreen;
  113.                     }
  114.                     break;
  115.                 case 10: { count = $"全部投中,你太厉害了!";
  116.                         sKColor = SKColors.Red;
  117.                     } break;
  118.             }
  119.             using var paint1 = new SKPaint
  120.             {
  121.                 Color = sKColor,
  122.                 IsAntialias = true,
  123.                 Typeface = Font,
  124.                 TextSize = 48
  125.             };
  126.             var fontCenter = paint1.MeasureText(count);
  127.             canvas.DrawText(count, centerPoint.X - fontCenter / 2, centerPoint.Y, paint1);
  128.         }
  129.         using var paint = new SKPaint
  130.         {
  131.             Color = SKColors.Blue,
  132.             IsAntialias = true,
  133.             Typeface = Font,
  134.             TextSize = 24
  135.         };
  136.         string by = $"by 蓝创精英团队";
  137.         canvas.DrawText(by, 600, 20, paint);
  138.     }
  139.     /// <summary>
  140.     /// 画一个圆
  141.     /// </summary>
  142.     public void DrawCircle(SKCanvas canvas, Ball ball)
  143.     {
  144.         using var paint = new SKPaint
  145.         {
  146.             Color = SKColors.Blue,
  147.             Style = SKPaintStyle.Fill,
  148.             IsAntialias = true,
  149.             StrokeWidth = 2
  150.         };
  151.         canvas.DrawCircle((float)ball.X, (float)ball.Y, ball.Radius, paint);
  152.     }
  153.     /// <summary>
  154.     /// 画一个矩形
  155.     /// </summary>
  156.     public void DrawRect(SKCanvas canvas, Rect box)
  157.     {
  158.         using var paint = new SKPaint
  159.         {
  160.             Color = SKColors.Green,
  161.             Style = SKPaintStyle.Fill,
  162.             IsAntialias = true,
  163.             StrokeWidth = 2
  164.         };
  165.         canvas.DrawRect((float)box.X, (float)box.Y, (float)box.Width, (float)box.Height, paint);
  166.     }
  167.     /// <summary>
  168.     /// 划线
  169.     /// </summary>
  170.     public void DrawLine()
  171.     {
  172.         //划线
  173.         using var LinePaint = new SKPaint
  174.         {
  175.             Color = SKColors.Red,
  176.             Style = SKPaintStyle.Fill,
  177.             StrokeWidth = 2,
  178.             IsStroke = true,
  179.             StrokeCap = SKStrokeCap.Round,
  180.             IsAntialias = true
  181.         };
  182.         var path = new SKPath();
  183.         path.MoveTo((float)CurrentMousePoint.X, (float)CurrentMousePoint.Y);
  184.         path.LineTo((float)Ball.X, (float)Ball.Y);
  185.         path.Close();
  186.         canvas.DrawPath(path, LinePaint);
  187.     }
  188.     public void BallMove(int Width, int Height)
  189.     {
  190.         Ball.VX *= F;
  191.         Ball.VY *= F;
  192.         Ball.VY += G;
  193.         Ball.X += Ball.VX;
  194.         Ball.Y += Ball.VY;
  195.         var hit = CheckHit();
  196.         // 边界处理和碰撞检测
  197.         if (hit || Ball.X - Ball.Radius > Width || Ball.X + Ball.Radius < 0 || Ball.Y - Ball.Radius > Height || Ball.Y + Ball.Radius < 0)
  198.         {
  199.             bools.Add(hit);
  200.             IsMoving = false;
  201.             Ball.X = 50;
  202.             Ball.Y = Height - 50;
  203.         }
  204.         lastPoint.X = (float)Ball.X;
  205.         lastPoint.Y = (float)Ball.Y;
  206.     }
  207.     public bool CheckHit()
  208.     {
  209.         var k1 = (Ball.Y - lastPoint.Y) / (Ball.X - lastPoint.X);
  210.         var b1 = lastPoint.Y - k1 * lastPoint.X;
  211.         var k2 = 0;
  212.         var b2 = Ball.Y;
  213.         var cx = (b2 - b1) / (k1 - k2);
  214.         var cy = k1 * cx + b1;
  215.         if (cx - Ball.Radius / 2 > Box.X && cx + Ball.Radius / 2 < Box.X + Box.Width && Ball.Y - Ball.Radius > Box.Y)
  216.         {
  217.             return true;
  218.         }
  219.         return false;
  220.     }
  221.     public void MouseMove(SKPoint sKPoint)
  222.     {
  223.         CurrentMousePoint = sKPoint;
  224.     }
  225.     public void MouseDown(SKPoint sKPoint)
  226.     {
  227.         CurrentMousePoint = sKPoint;
  228.     }
  229.     public void MouseUp(SKPoint sKPoint)
  230.     {
  231.         if (bools.Count < ALLCount)
  232.         {
  233.             IsMoving = true;
  234.             Ball.VX = (sKPoint.X - Ball.X) * Easing;
  235.             Ball.VY = (sKPoint.Y - Ball.Y) * Easing;
  236.             lastPoint.X = (float)Ball.X;
  237.             lastPoint.Y = (float)Ball.Y;
  238.         }
  239.     }
  240. }
复制代码
效果如下:


还不错,得了7分,当然,我也可以得10分的,不过,还好了。
总结

这个特效的案例重点是光线投影法碰撞检测,同时又对其增加了游戏的属性,虽然东西都很简单,但是作为一个雏形来讲也是不错的。
SkiaSharp 基础系列算是告一段落了,基础知识相关暂时都已经有了一个深度的了解,对于它的基础应用已经有一个不错的认识了,那么,基于它的应用应该也会多起来,我这边主要参考Avalonia的内部SkiaSharp使用原理,当然,用法肯定不局限的。
代码地址

https://github.com/kesshei/WPFSkiaRayProjectionDemo.git
https://gitee.com/kesshei/WPFSkiaRayProjectionDemo.git


一键三连呦!,感谢大佬的支持,您的支持就是我的动力!
版权

蓝创精英团队(公众号同名,CSDN 同名,CNBlogs 同名)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

麻花痒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表