2D専用グラフィックスライブラリ SkiaSharpデモ
・赤は親、子は赤以外。親は多少大きく、動きは少し遅くしてある。
・親は全ボール数の30%程度に設定。パラメータで可変。
・親と子が接近すると線が引かれる。離れると切れる。
・親と親は線が引かれない。子は複数の親と線が引かれる。
・線の色はランダムまたは固定選択。
・線が引かれる距離、ボール総数等はキー操作で可変。
・A型はこちら https://qiita.com/inf102/items/e4c95ee481d4a232acbd
1.キー操作説明
-
[A].......................通常モード、自動展開モード切り替え
-
[F].......................フルスクリーン フルスクリーンにならない時はリサイズ可能な
表示にした後[F]キーまたは起動後にFキーまたは起動直後にFキー -
[カーソル上]....ボールを増やす
-
[カーソル下]....ボールを減らす
-
[PageUp]..........線が引かれるまでの距離を長く
-
[PageDw].........線が引かれるまでの距離を短く
-
[R].......................線の色をランダム/固定色の切り替え
-
[マウス左]........表示リセット
-
[マウス右]........終了
2.動画(2560x1440) YouTube
3.コードについて
-
タイプAを改修しました。
適当にコメント入れたので仕組みはコード見れば分かると思います。 -
親が300個、子が700個の場合は、一つの親が700個の子の位置を算出。それを300回繰り返します。他にボールの位置計算で三角関数2回x1000個分。かなりの計算量ですがPC性能のおかげで動作してます。
- ボールの位置によっては計算できない事が有ったので直前で補正かけてます。
- SkiaSharp参考URL -> https://qiita.com/inf102/items/e6cc6d8590b929f5e54e
4.WPF Sourcecode
/////////////////////////////////////////////////////////
// BallField TypeB / WPF+SkiaSharp DotnetFramework.
// (c)inf102 S.H. 2024. allrights reserved.
/////////////////////////////////////////////////////////
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=150; // 開始時ボール数
const int WAIT=2 ; // 画面更新間隔(mmSec) 2以上
static double DIST=13.0; // 線を引く距離
///////////////////////////////////////////////////////////////////
static bool AUTO_ENABE=false;
static bool FULL_SCREEN=false;
static bool RND_COLOR=true;
static int T_CNT=0;
static int P_CNT=0;
static int C_CNT=0;
// 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; // 大きさ
public int P_FLG; // 0=child 1=parent
}
static POINT_XY[] XYDATA= new POINT_XY[MAX_BALL];
SKPaint BALL_PaintSkia = new SKPaint{
Style = SKPaintStyle.Fill,
IsAntialias=true,
};
SKPaint LINE_PaintSkia = new SKPaint{
Style = SKPaintStyle.Fill,
IsAntialias=true,
StrokeWidth =2,
Color=new SKColor(0,255,0)
};
// キャンパスサイズ
static int CAX,CAY;
public MainWindow() {
InitializeComponent();
TITLE_OUTPUT();
}
void TITLE_OUTPUT() {
Title ="WPF+SkiaSharp BallDemo2/AllBall="+CRENT_BALL.ToString() +"(MAX="+MAX_BALL.ToString()+")/Parent="+P_CNT.ToString()+"/Child="+C_CNT.ToString()+"/DIST="+DIST.ToString()+"/R=RndColor/L_Button=Restart,R_Button=Exit";
}
// Ballデータ設定
public void BallDataSet(){
P_CNT=0;
C_CNT=0;
Random rnd = new System.Random();
for(int x = 0; x < CRENT_BALL; x++) {
XYDATA[x].RAG = rnd.Next(1,360);
// parent
if (rnd.Next(0,5) == 1) { //0,5は0->4
P_CNT++;
XYDATA[x].P_FLG=1;
XYDATA[x].SPEED = rnd.Next(1,12);
XYDATA[x].SIZE=11;
XYDATA[x].R=255;
XYDATA[x].G=0;
XYDATA[x].B=0;
}
// child
else {
C_CNT++;
XYDATA[x].P_FLG=0;
XYDATA[x].SPEED = rnd.Next(1,16);
XYDATA[x].SIZE=8;
XYDATA[x].R=rnd.Next(0,50);
XYDATA[x].G=rnd.Next(40,255);
XYDATA[x].B=rnd.Next(40,255);
}
XYDATA[x].START_X = CAX/2;
XYDATA[x].START_Y = CAY/2;
XYDATA[x].L = 1;
}
TITLE_OUTPUT();
}
private async void Window_Loaded(object sender, RoutedEventArgse) {
while(true){
await Task.Delay(WAIT);
// 全てのボールの次の表示位置算出
GetXY();
// 自動展開モード
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 (T_CNT>= 130){
T_CNT=0;
BallDataSet();
}
T_CNT++;
}
// 全ボールに対して壁衝突判定、衝突の場合は反射角等を再設定
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;
BallDataSet();
}
if (e.Key == System.Windows.Input.Key.Down) {
CRENT_BALL-=15;
if (CRENT_BALL <=1 ) CRENT_BALL=1;
BallDataSet();
}
if (e.Key == System.Windows.Input.Key.PageUp) {
DIST+=1.0f;
if (DIST >=20.0f ) DIST=20.0f;
TITLE_OUTPUT();
}
if (e.Key == System.Windows.Input.Key.PageDown) {
DIST-=1.0f;
if (DIST <=10.0f ) DIST=10.0f;
TITLE_OUTPUT();
}
if (e.Key == System.Windows.Input.Key.R) {
RND_COLOR=!RND_COLOR;
}
// 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();
}
}
// 表示モード切り替え
if (e.Key == System.Windows.Input.Key.A) {
AUTO_ENABE=!AUTO_ENABE;
BallDataSet();
}
}
// ボール、線描画
void MainPaintSurface(object sender, SKPaintSurfaceEventArgs args){
CAX=args.Info.Width;
CAY=args.Info.Height;
args.Surface.Canvas.Clear(SKColors.Black);
double L;
int x0,y0,x1,y1;
Random rnd = new System.Random();
// 全ボール描画
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);
args.Surface.Canvas.DrawCircle (XYDATA[x].CUR_X,XYDATA[x].CUR_Y,XYDATA[x].SIZE,BALL_PaintSkia);
// 親
if (XYDATA[x].P_FLG == 1) {
// 全ての子と距離算出
for(int f = 0; f < CRENT_BALL; f++) {
// 子
if(XYDATA[f].P_FLG == 0) {
// 距離算出用補正
if (XYDATA[x].CUR_X - XYDATA[f].CUR_X < 0) {
x0 = XYDATA[f].CUR_X;
x1 = XYDATA[x].CUR_X;
}
else {
x0 = XYDATA[x].CUR_X;
x1 = XYDATA[f].CUR_X;
}
if (XYDATA[x].CUR_Y - XYDATA[f].CUR_Y < 0) {
y0 = XYDATA[f].CUR_Y;
y1 = XYDATA[x].CUR_Y;
}
else {
y0= XYDATA[x].CUR_Y;
y1= XYDATA[f].CUR_Y;
}
// 距離算出
L = Math.Sqrt (((x0-x1)^2)+((y0-y1)^2));
// LINE_COLOR
if (RND_COLOR==true) LINE_PaintSkia.Color=new SKColor((byte)rnd.Next(70,255),(byte)rnd.Next(70,255),(byte)rnd.Next(70,255));
else LINE_PaintSkia.Color=new SKColor((byte)0,(byte)255,(byte)0);
// 親と子の線
if (L < DIST ) {
args.Surface.Canvas.DrawLine(XYDATA[x].CUR_X,XYDATA[x].CUR_Y,XYDATA[f].CUR_X,XYDATA[f].CUR_Y,LINE_PaintSkia);
}
}
}
}
}
}
}
}
5.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="640" Width="900" 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>