0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2D CGライブラリSkiaSharp「ボールフィールド A型」

Last updated at Posted at 2024-09-17

・定番のボールが反射するアプリ。
・中央から自動展開する「花火モード」を搭載。
・部屋を暗くして大画面で眺めると花火を楽しめます(?)

・WQHD(2560x1440) 自作機(MPU Core I5 7600K,VGA RTX3060) WAIT2 (0.002Sec)

1.キー操作説明

  • [A]....花火モード(アルファ値を可変させて花火風にしている)

  • [F]....フルスクリーン フルスクリーンにならない時はリサイズ可能な表示にした後[F]キー

  • [カーソル上]....ボールを増やす

  • [カーソル下]....ボールを減らす

  • [マウス左]....表示リセット

  • [マウス右]....終了

2.パラメータ説明

[WAIT値]

  • 2を指定すると0.002秒に一回画面更新します
  • 大きすぎるとカクカク動作になります.通常は2-100程度です.

[MAX_BALL値]

  • カーソルUpキーで表示数を可変させるのでその最大値です

[CRENT_BALL値]

  • 起動時に表示されるボール数
  • MAX_BALL値より少ない値にする

3.備考

  • タイトルバーには現在の表示ボール数、最大表示可能数等を表示
  • 出し過ぎると閉じるボタンを押しても中々閉じない場合あり.ボールは動いているが操作不能になる
  • WPFはDirect3Dで描画しますのでVGAカード(又はMPU内蔵)の負荷テストとしても利用可
  • 安価なノートPCでも1000個は出ましたので高価なVGAカードを載せている場合はもっと可能だと思います。少しづつ増やしてみて下さい
  • 終了しない場合はタスクマネージャで止めます
  • SkiaSharp参考URL -> https://qiita.com/inf102/items/e6cc6d8590b929f5e54e

4.WPF (C#) Source code

/////////////////////////////////////////////////////////////////////////////////
// BallField WPF+SkiaSharp DotnetFramework.  (c)inf102 S.H. 2024.
// 1.Cur[Up][Down]Key [A]Key=AutoMode [F]Key=FullScreen 
// 2.Mouse LButton=Restart RButton=Exit
/////////////////////////////////////////////////////////////////////////////////

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading.Tasks;
using System.Windows;

namespace SkiaSharpSample {

    public partial class MainWindow : Window    {

///////////////////////////////////////////////////////////////////
        const  int MAX_BALL=1000;       // 最大ボール数
        static int CRENT_BALL=200;      // 開始時ボール数
        const  int WAIT=2 ;             // 画面更新間隔(mmSec) 2以上
///////////////////////////////////////////////////////////////////

        static int cnt=0;
        static bool AUTO_ENABE=false;
        static bool FULL_SCREEN=false;

        // BALL DATA
        struct POINT_XY{
            public int START_X,START_Y; // 開始位置
            public int L;               // 開始位置からの移動距離
            public int RAG;             // 角度
            public int CUR_X,CUR_Y;     // 現在位置
            public int R,G,B,A;         // 色
            public int SPEED;           // 速度
            public int SIZE;            // 大きさ
        }
        static POINT_XY[] XYDATA= new POINT_XY[MAX_BALL];

        SKPaint BALL_PaintSkia = new SKPaint{
            Style = SKPaintStyle.Fill,
            IsAntialias=true,
        };

        // キャンパスサイズ
        static int CAX,CAY;

        public MainWindow()        {
            InitializeComponent();
            TITLE_OUTPUT();
        }

        void TITLE_OUTPUT() {
            Title ="WPF+SkiaSharp BALL Demo/BALL=" +CRENT_BALL.ToString()+" (MAX="+MAX_BALL.ToString()+")/Using[Up],[Down]Key/[A]Key=AUTOMode/[F}Key=FullScreen/LButton=Restart,RButton=Exit";
        }

        // Ballデータ設定
        public void BallDataSet(){

            Random rnd = new System.Random();
            for(int x = 0; x < CRENT_BALL; x++) {

                XYDATA[x].RAG = rnd.Next(1,360);
                if (XYDATA[x].RAG == 180 || XYDATA[x].RAG == 270 || XYDATA[x].RAG == 90 ) XYDATA[x].RAG = rnd.Next(1,360);

                XYDATA[x].START_X = CAX/2;
                XYDATA[x].START_Y = CAY/2;
                XYDATA[x].L = 1;

                XYDATA[x].R=rnd.Next(0,255);
                XYDATA[x].G=rnd.Next(0,255);
                XYDATA[x].B=rnd.Next(0,255);
                XYDATA[x].A=rnd.Next(130,255); // 始めの方の数字を減らすと透明が増える

                XYDATA[x].SPEED = rnd.Next(1,15);
                XYDATA[x].SIZE=rnd.Next (2,70);
            }
        }

        private async  void Window_Loaded(object sender, RoutedEventArgs e) {

            while(true){
            
                await Task.Delay(WAIT);
                // 自動モード
                if (AUTO_ENABE == true) {
                    for(int ff = 0; ff < CRENT_BALL; ff++) {
                        XYDATA[ff].A-=2;
                        if (XYDATA[ff].A<=0) XYDATA[ff].A=0;
                    }
                    
                    if (cnt >= 100){
                        cnt=0;
                        BallDataSet();
                    }
                    cnt++;
                }

                GetXY();

                // 全ボールに対して壁に衝突したか 衝突の場合は反射角等を再設定
                for (int x=0;x<CRENT_BALL;x++){

                    // 上
                    if (AUTO_ENABE == false){ // 自動モードは反射させない
                        if (XYDATA[x].CUR_Y < XYDATA[x].SIZE){
                            XYDATA[x].L=1;
                            XYDATA[x].RAG=180-XYDATA[x].RAG;
                            XYDATA[x].START_Y=XYDATA[x].CUR_Y+3;
                            XYDATA[x].START_X=XYDATA[x].CUR_X;
                        }
                    }

                    // 下
                    if (AUTO_ENABE == false){ // 自動モードは反射させない
                        if (XYDATA[x].CUR_Y> CAY-XYDATA[x].SIZE){
                            XYDATA[x].L=1;
                            XYDATA[x].RAG=180-XYDATA[x].RAG;
                            XYDATA[x].START_Y=XYDATA[x].CUR_Y;
                            XYDATA[x].START_X=XYDATA[x].CUR_X;
                        }
                    }

                    // 右
                    if (AUTO_ENABE == false){ // 自動モードは反射させない
                        if (XYDATA[x].CUR_X>CAX-XYDATA[x].SIZE){
                            XYDATA[x].L=1;
                            XYDATA[x].RAG=360-XYDATA[x].RAG;
                            XYDATA[x].START_Y=XYDATA[x].CUR_Y;
                            XYDATA[x].START_X=XYDATA[x].CUR_X;
                        }
                    }

                    // 左
                    if (AUTO_ENABE == false){ // 自動モードは反射させない
                        if (XYDATA[x].CUR_X<XYDATA[x].SIZE){
                            XYDATA[x].L=1;
                            XYDATA[x].RAG=360-XYDATA[x].RAG;
                            XYDATA[x].START_Y=XYDATA[x].CUR_Y;
                            XYDATA[x].START_X=XYDATA[x].CUR_X+3;
                        }
                    }

                    // 移動速度
                    XYDATA[x].L+=XYDATA[x].SPEED;
                }

                // 画面更新
                MainCanvas.InvalidateVisual();
            }
        }

        // 全てのボールの次の表示位置算出
        void GetXY (){
            for (int x=0;x<CRENT_BALL;x++){
                XYDATA[x].CUR_X= (int)(XYDATA[x].START_X+XYDATA[x].L*Math.Sin(XYDATA[x].RAG*Math.PI/180));
                XYDATA[x].CUR_Y= (int)(XYDATA[x].START_Y-XYDATA[x].L*Math.Cos(XYDATA[x].RAG*Math.PI/180));
            }
        }

        private void skiaCanvas_SizeChanged(object sender,SizeChangedEventArgs e) {
            BallDataSet();
        }

        private void MainCanvas_PreviewMouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e) {
            BallDataSet();
        }

        private void MainCanvas_PreviewMouseRightButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e) {
            Application.Current.Shutdown();
        }

        private void Window_KeyDown(object sender,System.Windows.Input.KeyEventArgs e) {

            if (e.Key == System.Windows.Input.Key.Up) {
                CRENT_BALL+=15;
                if (CRENT_BALL >=MAX_BALL ) CRENT_BALL=MAX_BALL;
                TITLE_OUTPUT();
                BallDataSet();
            }

            if (e.Key == System.Windows.Input.Key.Down) {
                CRENT_BALL-=15;
                if (CRENT_BALL <=1 ) CRENT_BALL=1;
                TITLE_OUTPUT();
                BallDataSet();
            }

            // 表示モード切り替え
            if (e.Key == System.Windows.Input.Key.A) {
                AUTO_ENABE=!AUTO_ENABE;
                BallDataSet();
            }

            // FULL SCREEN
            if (e.Key == System.Windows.Input.Key.F) {
                if (FULL_SCREEN == false){
                    FULL_SCREEN=!FULL_SCREEN;
                    this.WindowStyle = WindowStyle.None; //タイトル消す場合
                    this.WindowState = WindowState.Maximized;
                    BallDataSet();
                    return;
                }
                else {
                    FULL_SCREEN=!FULL_SCREEN;
                    this.WindowStyle = WindowStyle.SingleBorderWindow;
                    BallDataSet();
                }
            }
        }

        // ボール描画
        void MainPaintSurface(object sender, SKPaintSurfaceEventArgs args){
            CAX=args.Info.Width;
            CAY=args.Info.Height;

            args.Surface.Canvas.Clear(SKColors.Black);
            for (int x=0;x<CRENT_BALL;x++) {
                BALL_PaintSkia.Color=new SKColor((byte)XYDATA[x].R,(byte)XYDATA[x].G,(byte)XYDATA[x].B,(Byte)XYDATA[x].A);
                args.Surface.Canvas.DrawCircle (XYDATA[x].CUR_X,XYDATA[x].CUR_Y,XYDATA[x].SIZE,BALL_PaintSkia);
            }
        }
    }
}

5.WPF (XAML)

<Window x:Class="SkiaSharpSample.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:SkiaSharpSample" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" 
        xmlns:wpf="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" mc:Ignorable="d"
        Title="WPF+SkiaSharp BALLDemo" Loaded="Window_Loaded" Height="1200" Width="1800"  WindowState="Maximized" KeyDown="Window_KeyDown" >
    <Grid  >
        <wpf:SKElement x:Name="MainCanvas" PaintSurface="MainPaintSurface" Grid.Column="0" Grid.Row="0" SizeChanged="skiaCanvas_SizeChanged" PreviewMouseLeftButtonDown="MainCanvas_PreviewMouseLeftButtonDown" PreviewMouseRightButtonDown="MainCanvas_PreviewMouseRightButtonDown" />
    </Grid>
</Window>

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?