プログラミング演習Ⅲ(2023)

【WPF練習13】図形描画と定期処理(7/7)

プロジェクトタイプC# WPFアプリケーション※
プロジェクト名T10a
ソリューション名PET10
ターゲットフレームワーク.NET 6.0 (長期的なサポート)

※ 「WPFアプリ(.NET Framework)」ではないので注意せよ!

注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備を参照せよ.

13-7. 簡易アニメーションの生成

前回までのWPF練習では,ボタンやラベルなどを用いた典型的なデスクトップアプリのようなものばかりを作っていたが 今回はアニメーションデモやゲームのようなものを作ることを試みてみよう.

そのまえに本節までにアプリに様々な変更を施してきて,見通しが悪くなっているのでMainWindow.xamlMainWindow.xaml.csから余計な記述をすべて削除しよう.まずアプリのレイアウトを_のように 変更するため,MainWindow.xaml_に示す 5箇所 の変更を加える.

ウィンドウのレイアウト
MainWindow.xamlの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<Window x:Class="T10a.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:T10a"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="600" Background="#F0F0F0">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        
        <!-- ① Grid.ColumnDefinitions は削除する. -->

        <!-- ②           Grid.Column削除する↓   ↓Contentを"実行"に変更する. -->
        <Button x:Name="button1" Grid.Row="0"   Content="実行" 
                Click="button1_Click"></Button> 
        
        <!-- ③ button2 は削除する. -->

        <!-- ④         Grid.ColumnSpan削除する↓ -->
        <Canvas x:Name="canvas1" Grid.Row="1" 
                Background="White" Width="512" Height="512"
                ClipToBounds="True">

            <!-- 楕円(名前: ellipse1) -->
            <Ellipse x:Name="ellipse1" Canvas.Left="300" Canvas.Top="320"
                 Width="100" Height="100" 
                 Stroke="Black" Fill="Red"></Ellipse>
                 
            <!-- ⑤ 楕円以外は削除する. -->

        </Canvas>
    </Grid>

</Window>

次にMainWindow.xaml.cs_に示す 5箇所 の変更を加える.

MainWindow.xaml.csの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public partial class MainWindow : Window
{
    private DispatcherTimer timer;

    public MainWindow()
    {
        InitializeComponent();

        timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 1);
        timer.Tick += Timer_Tick;

        // ① timer.Start() の呼び出しを削除する.       
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // ② Timer_Tick()メソッドの内容を削除する.
    }

    // ③ count フィールドを削除する.

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        // ④ button1_Click() メソッドの内容を削除する.
    }

    // ⑤ button2_Click() メソッドをまるごと削除する.
}

ここまで書けたら起動してみよう._に示すような見た目になり, ボタンをクリックしても何も起きないことを確認しておこう.

作業結果

今回は楕円ellipse1の位置を動的に変更することで,疑似的にアニメーションしているように見せてみよう. アニメーションの開始はボタンをクリックしたタイミングとする. MainWindowクラスを_に示す通り変更しよう.

MainWindow.xaml.csの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public partial class MainWindow : Window
{
    private DispatcherTimer timer;

    private double speedX; // 楕円の移動速度(X)
    private double speedY; // 楕円の移動速度(Y)

    public MainWindow()
    {
        InitializeComponent();

        timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 0, 0, 16); // 実行間隔を16ミリ秒に変更
        timer.Tick += Timer_Tick;

        speedX = -4.0; // 定期処理のたび X 座標および
        speedY = -3.0; // Y 座標にこれらの値を加算する.
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // 現在の ellipse1 の中心位置を得る.
        // (Canvas.GetLeft/Topから得られるのは左上の座標なので
        //  ellipse1の幅と高さの半分を足している).
        double ballX = Canvas.GetLeft(ellipse1) + ellipse1.Width / 2;
        double ballY = Canvas.GetTop(ellipse1) + ellipse1.Height / 2;

        // 次の ellipse1 の位置を計算する.
        double nextBallX = ballX + speedX;
        double nextBallY = ballY + speedY;

        // ↑で計算した位置を ellipse1 に設定する.
        // (中心位置で計算したので左上位置にするために
        // ellipse1の幅と高さの半分を引いている).
        Canvas.SetLeft(ellipse1, nextBallX - ellipse1.Width / 2);
        Canvas.SetTop(ellipse1, nextBallY - ellipse1.Height / 2);
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        // タイマーが開始済みの場合は何もしない.
        if (timer.IsEnabled) return;

        timer.Start(); // 定期処理を開始
    }
}

ここまで書けたら起動してボタンをクリックしてみよう._に示すように, ellipse1が左上へ移動していくことが分かるだろう.

作業結果

ellipse1は放っておくとキャンバスの外に出て見切れてしまう.そこで, ellipse1がキャンバスの端まで到達したら反射する ように動きを変更してみよう. Timer_Tick()メソッドに_に示す内容を追記する.

MainWindow.xaml.csの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private void Timer_Tick(object sender, EventArgs e)
{
    double ballX = Canvas.GetLeft(ellipse1) + ellipse1.Width / 2;
    double ballY = Canvas.GetTop(ellipse1) + ellipse1.Height / 2;

    double nextBallX = ballX + speedX;
    double nextBallY = ballY + speedY;

    if (nextBallX < 0 || canvas1.Width <= nextBallX)  //
    {                                                 //
        speedX *= -1;                                 //
    }// if                                            // 次のボール位置がキャンバスの外に
                                                      // 出てしまった場合に,速度を反転する.
    if (nextBallY < 0 || canvas1.Height <= nextBallY) //
    {                                                 //
        speedY *= -1;                                 //
    }// if                                            //

    Canvas.SetLeft(ellipse1, nextBallX - ellipse1.Width / 2);
    Canvas.SetTop(ellipse1, nextBallY - ellipse1.Height / 2);
}

ここまで書けたら起動してボタンをクリックしてみよう._に示すように, ellipse1がキャンバス内を動き回ることが分かるだろう.

作業結果

ここまでを確認できたら本チュートリアルは完了である.

次のチュートリアルではこの挙動を応用して簡単なゲームを製作することを試みる.

Last updated on 2024-01-09
Published on 2024-01-09

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.