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

【WPF練習14】壁崩し(7/9)

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

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

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

14-7. 終了処理の実装

次にゲームの終了処理を実装しよう.ゲームを終了するのは前述したように以下のようなケースである.

  • ケースA) ボールが画面の下に落下してしまった場合
  • ケースB) すべてのブロックを破壊した場合

これらのケースではゲームの更新処理,つまりタイマー処理を停止すればよいが,ゲームが終了したことを 示す表示がないと不親切である.このためにcanvas1にラベル(名前:label1)を追加しよう. MainWindow.xaml.cs_に示す内容を追記する.

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
<Window x:Class="T10b.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:T10b"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="300" Background="#F0F0F0">
    <Grid>
        <Viewbox>
            <Canvas x:Name="canvas1" Width="600" Height="800"
                    Background="White" ClipToBounds="True"
                    MouseMove="canvas1_MouseMove">
                
                <Rectangle x:Name="player" Width="100" Height="20"
                           Canvas.Left="250" Canvas.Top="700"
                           Stroke="Black" Fill="Orange"
                           Tag="Player"></Rectangle>

                <Ellipse x:Name="ball" Width="10" Height="10"
                         Canvas.Left="295" Canvas.Top="685"
                         Stroke="Black" Fill="Red"></Ellipse>

                <!-- メッセージ表示用のラベル -->
                <Label x:Name="label1" Width="600"
                       Canvas.Left="0" Canvas.Top="120"
                       HorizontalContentAlignment="Center"
                       VerticalContentAlignment="Center"
                       FontSize="72" Background="#7FFFFFFF"
                       Panel.ZIndex="255"
                       Content="TEST!"></Label>
                
            </Canvas>
        </Viewbox>
    </Grid>
</Window>

ここまで追記したらデザイナで表示を確認しておこう._のように 画面上に「TEST!」と表示されているはずである.

作業結果

このラベルはゲーム中は非表示にする必要があるので, .Visibility(う゛ぃじびりてぃ)プロパティを設定して 非表示にしておこう.このプロパティはWPFのほとんどの部品がもつプロパティで, 表示/非表示を設定することができるプロパティである.MainWindow.xamllabel1_に示すプロパティを追記する.

MainWindow.xamlの追記内容
1
2
3
4
5
6
7
8
<Label x:Name="label1" Width="600"
        Canvas.Left="0" Canvas.Top="120"
        HorizontalContentAlignment="Center"
        VerticalContentAlignment="Center"
        FontSize="72" Background="#7FFFFFFF"
        Panel.ZIndex="255"
        Content="TEST!" 
        Visibility="Hidden"></Label>

追記したらデザイナで表示を確認しておこう._のように label1が非表示になったはずである.

作業結果

それでは終了処理のうちケースAから実装してみよう.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
22
23
24
25
26
27
28
29
30
31
32
private void Timer_Tick(object sender, EventArgs e)
{
    const double EPSILON = 1 / 256.0;

    double ballX = Canvas.GetLeft(ball) + ball.Width / 2;
    double ballY = Canvas.GetTop(ball) + ball.Height / 2;

    if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
    {
        // (..中略..)
    }// if

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

    // ボールが画面の下に落下した場合
    if (canvas1.Height <= nextBallY) 
    {
        label1.Content = "GAME OVER";           // label1 の文言を "GAME OVER"にして,
        label1.Visibility = Visibility.Visible; // 表示状態にする.

        timer.Stop(); // タイマーを停止する.
    }// if
    
    if (nextBallX < 0 || canvas1.Width <= nextBallX) 
    {
        speedX *= -1;
        ballX = Math.Clamp(nextBallX, 0, canvas1.Width) + EPSILON * speedX;
        nextBallX = ballX + speedX;
    }// if

    // (..以下略..)

ここまで書けたら起動してみよう.ボールを画面下に落下させると_に示すように, 「GAME OVER」の表示が現れてそれ以上のゲームの更新処理が行われなくなるはずである.

作業結果

次にケースBの終了処理を実装しよう.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
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private void Timer_Tick(object sender, EventArgs e)
{
    const double EPSILON = 1 / 256.0;

    double ballX = Canvas.GetLeft(ball) + ball.Width / 2;
    double ballY = Canvas.GetTop(ball) + ball.Height / 2;

    if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
    {
        // (..中略..)
    }//if

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

    if (canvas1.Height <= nextBallY) 
    {
        label1.Content = "GAME OVER";
        label1.Visibility = Visibility.Visible;

        timer.Stop();
    }// if

    // canavs1 に含まれている四角形のうちブロック役の
    // 四角形の数を数える( .Count() メソッド).
    var numBricks = (from r in GetRectangles()
                     where (string)r.Tag == "Bricks"
                     select r).Count();
                      
    if (numBricks == 0) // もうブロックがない場合
    {
        label1.Content = "CLEAR!";              // label1 の文言を "CLEAR!"にして,
        label1.Visibility = Visibility.Visible; // 表示状態にする.

        timer.Stop(); // タイマーを停止する.
    }// if

    if (nextBallX < 0 || canvas1.Width <= nextBallX) 
    {
        speedX *= -1;
        ballX = Math.Clamp(nextBallX, 0, canvas1.Width) + EPSILON * speedX;
        nextBallX = ballX + speedX;
    }// if
    
    // (..以下略..)

ここまで書けたら起動してみよう.チートコマンドなどを用いてすべてのブロックを破壊すると_に示すように, 「CLEAR!」の表示が現れてそれ以上のゲームの更新処理が行われなくなるはずである. これでおおむねゲームとしての体裁は整ったが,現在の実装方法だとゲームが終了してしまうとアプリケーションを 起動しなおすことでしかリセットができないので不便である.そこで次節ではゲームのリセット機能を実装してみよう.

作業結果
Last updated on 2024-01-09
Published on 2024-01-09

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