SkiaSharp 之 WPF 自繪 投籃小游戲(案例版)

来源:https://www.cnblogs.com/kesshei/archive/2022/08/05/16552463.html
-Advertisement-
Play Games

規則,點擊投籃目標點,就會有一個球沿著相關拋物線,然後,判斷是否進入籃子里,其實就是一個矩形,直接是按照碰撞檢測來的,碰到就算進去了,對其增加了一個分數統計等功能。 ...


此案例主要是針對光線投影法碰撞檢測功能的示例,順便做成了一個小游戲,很簡單,但是,效果卻很不錯。

投籃小游戲

規則,點擊投籃目標點,就會有一個球沿著相關拋物線,然後,判斷是否進入籃子里,其實就是一個矩形,直接是按照碰撞檢測來的,碰到就算進去了,對其增加了一個分數統計等功能。

Wpf 和 SkiaSharp

新建一個 WPF 項目,然後,Nuget 包即可
要添加 Nuget 包

Install-Package SkiaSharp.Views.WPF -Version 2.88.0

其中核心邏輯是這部分,會以我設置的 60FPS 來刷新當前的畫板。

skContainer.PaintSurface += SkContainer_PaintSurface;
_ = Task.Run(() =>
{
    while (true)
    {
        try
        {
            Dispatcher.Invoke(() =>
            {
                skContainer.InvalidateVisual();
            });
            _ = SpinWait.SpinUntil(() => false, 1000 / 60);//每秒60幀
        }
        catch
        {
            break;
        }
    }
});

彈球實體代碼 (Ball.cs)

public class Ball
{
    public double X { get; set; }
    public double Y { get; set; }
    public double VX { get; set; }
    public double VY { get; set; }
    public int Radius { get; set; }
}

粒子花園核心類 (ParticleGarden.cs)

/// <summary>
/// 光線投影法碰撞檢測
/// 投籃小游戲
/// </summary>
public class RayProjection
{
    public SKPoint centerPoint;
    public double G = 0.3;
    public double F = 0.98;
    public double Easing = 0.03;
    public bool IsMoving = false;
    public SKPoint CurrentMousePoint = SKPoint.Empty;
    public SKPoint lastPoint = SKPoint.Empty;
    public Rect Box;
    public Ball Ball;
    public SKCanvas canvas;
    public int ALLCount = 10;
    public List<bool> bools = new List<bool>();
    public bool IsOver = false;
    /// <summary>
    /// 渲染
    /// </summary>
    public void Render(SKCanvas canvas, SKTypeface Font, int Width, int Height)
    {
        canvas.Clear(SKColors.White);
        this.canvas = canvas;
        centerPoint = new SKPoint(Width / 2, Height / 2);
        //球
        if (Ball == null)
        {
            Ball = new Ball()
            {
                X = 50,
                Y = Height - 50,
                Radius = 30
            };
        }
        //箱子
        var boxX = Width - 170;
        var boxY = Height - 80;
        if (Box.X == 0)
        {
            Box = new Rect(boxX, boxY, 120, 70);
        }
        else
        {
            if (Box.X != boxX && Box.Y != boxY)
            {
                Box.X = boxX;
                Box.Y = boxY;
            }
        }

        if (bools.Count >= ALLCount)
        {
            IsOver = true;
        }

        if (!IsOver)
        {
            if (IsMoving)
            {
                BallMove(Width, Height);
            }
            else
            {
                DrawLine();
            }

            //彈球
            DrawCircle(canvas, Ball);
            //矩形
            DrawRect(canvas, Box);

            //計分
            using var paint1 = new SKPaint
            {
                Color = SKColors.Blue,
                IsAntialias = true,
                Typeface = Font,
                TextSize = 24
            };
            string count = $"總次數:{ALLCount} 剩餘次數:{ALLCount - bools.Count} 投中次數:{bools.Count(t => t)}";
            canvas.DrawText(count, 100, 20, paint1);
        }
        else
        {
            SKColor sKColor = SKColors.Blue;
            //計分
            var SuccessCount = bools.Count(t => t);
            string count = "";
            switch (SuccessCount)
            {
                case 0:
                    {
                        count = $"太糗了吧,一個都沒投中!";
                        sKColor = SKColors.Black;
                    }
                    break;
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                    {
                        count = $"你才投中:{SuccessCount}次,繼續努力!";
                        sKColor = SKColors.Blue;
                    }
                    break;
                case 6:
                case 7:
                case 8:
                case 9:
                    {
                        count = $"恭喜 投中:{SuccessCount}次!!!";
                        sKColor = SKColors.YellowGreen;
                    }
                    break;
                case 10: { count = $"全部投中,你太厲害了!";
                        sKColor = SKColors.Red;
                    } break;
            }
            using var paint1 = new SKPaint
            {
                Color = sKColor,
                IsAntialias = true,
                Typeface = Font,
                TextSize = 48
            };
            var fontCenter = paint1.MeasureText(count);
            canvas.DrawText(count, centerPoint.X - fontCenter / 2, centerPoint.Y, paint1);
        }
        using var paint = new SKPaint
        {
            Color = SKColors.Blue,
            IsAntialias = true,
            Typeface = Font,
            TextSize = 24
        };
        string by = $"by 藍創精英團隊";
        canvas.DrawText(by, 600, 20, paint);
    }
    /// <summary>
    /// 畫一個圓
    /// </summary>
    public void DrawCircle(SKCanvas canvas, Ball ball)
    {
        using var paint = new SKPaint
        {
            Color = SKColors.Blue,
            Style = SKPaintStyle.Fill,
            IsAntialias = true,
            StrokeWidth = 2
        };
        canvas.DrawCircle((float)ball.X, (float)ball.Y, ball.Radius, paint);
    }
    /// <summary>
    /// 畫一個矩形
    /// </summary>
    public void DrawRect(SKCanvas canvas, Rect box)
    {
        using var paint = new SKPaint
        {
            Color = SKColors.Green,
            Style = SKPaintStyle.Fill,
            IsAntialias = true,
            StrokeWidth = 2
        };
        canvas.DrawRect((float)box.X, (float)box.Y, (float)box.Width, (float)box.Height, paint);
    }
    /// <summary>
    /// 劃線
    /// </summary>
    public void DrawLine()
    {
        //劃線
        using var LinePaint = new SKPaint
        {
            Color = SKColors.Red,
            Style = SKPaintStyle.Fill,
            StrokeWidth = 2,
            IsStroke = true,
            StrokeCap = SKStrokeCap.Round,
            IsAntialias = true
        };
        var path = new SKPath();
        path.MoveTo((float)CurrentMousePoint.X, (float)CurrentMousePoint.Y);
        path.LineTo((float)Ball.X, (float)Ball.Y);
        path.Close();
        canvas.DrawPath(path, LinePaint);
    }
    public void BallMove(int Width, int Height)
    {
        Ball.VX *= F;
        Ball.VY *= F;
        Ball.VY += G;

        Ball.X += Ball.VX;
        Ball.Y += Ball.VY;

        var hit = CheckHit();
        // 邊界處理和碰撞檢測
        if (hit || Ball.X - Ball.Radius > Width || Ball.X + Ball.Radius < 0 || Ball.Y - Ball.Radius > Height || Ball.Y + Ball.Radius < 0)
        {
            bools.Add(hit);
            IsMoving = false;
            Ball.X = 50;
            Ball.Y = Height - 50;
        }

        lastPoint.X = (float)Ball.X;
        lastPoint.Y = (float)Ball.Y;
    }
    public bool CheckHit()
    {
        var k1 = (Ball.Y - lastPoint.Y) / (Ball.X - lastPoint.X);
        var b1 = lastPoint.Y - k1 * lastPoint.X;
        var k2 = 0;
        var b2 = Ball.Y;
        var cx = (b2 - b1) / (k1 - k2);
        var cy = k1 * cx + b1;
        if (cx - Ball.Radius / 2 > Box.X && cx + Ball.Radius / 2 < Box.X + Box.Width && Ball.Y - Ball.Radius > Box.Y)
        {
            return true;
        }
        return false;
    }
    public void MouseMove(SKPoint sKPoint)
    {
        CurrentMousePoint = sKPoint;
    }
    public void MouseDown(SKPoint sKPoint)
    {
        CurrentMousePoint = sKPoint;
    }
    public void MouseUp(SKPoint sKPoint)
    {
        if (bools.Count < ALLCount)
        {
            IsMoving = true;
            Ball.VX = (sKPoint.X - Ball.X) * Easing;
            Ball.VY = (sKPoint.Y - Ball.Y) * Easing;
            lastPoint.X = (float)Ball.X;
            lastPoint.Y = (float)Ball.Y;
        }
    }
}

效果如下:

還不錯,得了7分,當然,我也可以得10分的,不過,還好了。

總結

這個特效的案例重點是光線投影法碰撞檢測,同時又對其增加了游戲的屬性,雖然東西都很簡單,但是作為一個雛形來講也是不錯的。

SkiaSharp 基礎系列算是告一段落了,基礎知識相關暫時都已經有了一個深度的瞭解,對於它的基礎應用已經有一個不錯的認識了,那麼,基於它的應用應該也會多起來,我這邊主要參考Avalonia的內部SkiaSharp使用原理,當然,用法肯定不局限的。

代碼地址

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

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

一鍵三連呦!,感謝大佬的支持,您的支持就是我的動力!

版權

藍創精英團隊(公眾號同名,CSDN 同名,CNBlogs 同名)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 我們在實際開發中肯定會遇到後端的時間傳到前端是這個樣子的:2022-08-02T15:43:50 這個時候前後端就開始踢皮球了,!! 後端說:前端來做就可! 前端說:後端來做就可! 作為一名有責任感的後端,這種事情怎麼能讓前端來搞呢! 還有就是Long類型的返回到前端可能會損失精度,這個情 ...
  • 在上一遍文章中已經介紹了PixelShaderEffect 用hlsl(著色器) 可以實現各種自定義濾鏡效果了,本文將用 "ThresholdEffect" 來講解如何編寫,編譯hlsl,然後使用PixelShaderEffect製作自定義濾鏡。 效果圖: 一.hlsl幫助程式介紹 在寫hlsl 代 ...
  • 簡介 FTP是FileTransferProtocol(文件傳輸協議)的英文簡稱,而中文簡稱為“文傳協議”。用於Internet上的控制文件的雙向傳輸。同時,它也是一個應用程式(Application)。基於不同的操作系統有不同的FTP應用程式,而所有這些應用程式都遵守同一種協議以傳輸文件。 FTP ...
  • 在繼承中,派生類可以拿到基類的方法,若是派生類很多,且有時某部分派生類的部分實現邏輯是一樣的,但其他的派生類又用不到,這個時候這些邏輯若是全部寫到派生類中,就會導致產生很多的重覆邏輯,但是若是寫到基類中就會導致其他用不到當前邏輯的派生類也能調用,這樣就會導致代碼維護出現了問題。由此產生了介面。 在C ...
  • WPF 截圖控制項之移除控制項(九)「仿微信」 WPF 截圖控制項之移除控制項(九)「仿微信」 作者:WPFDevelopersOrg 原文鏈接: https://github.com/WPFDevelopersOrg/WPFDevelopers 框架使用大於等於.NET40; Visual Studio ...
  • 之前寫的DBHelper,名稱確實太Low,就改了個名,叫LiteSql,本來想叫SqlShuttle(SQL一把梭),奈何單詞太長。 有兩個版本,一個是LiteSql,一個是Dapper.LiteSql,LiteSql底層用的是ADO.NET,Dapper.LiteSql底層用的是Dapper,提 ...
  • 從零開始搭建基於 ABP Framework 分層架構解決方案,快速集成框架內置應用模塊。探索基於 ABP Framework 極速開發的最佳路徑,構建一個模塊完備、可開發、可調試、可發佈和部署的分層架構解決方案。 ...
  • 一、前言 之前分享過一期關於DrawingVisual來繪製高性能曲線的博客,今天再分享一篇通過另一種方式來繪製高性能曲線的方法,也就是通過WriteableBitmap的方式;具體的一些細節這裡就不啰嗦了,同樣是局部繪製的思想,滾動條拖動到哪裡,就只繪製那一部分的曲線,直接貼代碼;(該程式在英特爾 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...