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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...