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