連なるタコさん(前編)

こんにちは、きうちです。

だんだん暖かく、というか暑くなってきましたがいかがお過ごしでしょうか。
もう5月に入れば夏日連続ですかね。熱中症等にならないように気を付けて頑張っていきたいですね。

★ ★ ★

今回は「連なるタコさん」というお題でいってみたいと思います。

これは何かというと、

  • 矢印キー(←→↑↓)もしくはマウスでキャラクターが動く
  • キャラクターが動くと、それに引っ張られるように複数のキャラクターが動く

というものです。

言語は例によってC#にします。Windowsフォームアプリとして作成します。

フォームのサイズは 800×600 にします。今回はフォームに直接描画しようと思いますので、PictureBoxは無し。

背景は黒くします。

フォームとは別に Octopus というクラスをプロジェクトに追加し、内容は以下のようにします。

internal class Octopus
{
    public Point P { get; set; }

    public Point V { get; set; }

    public Octopus(Point p)
    {
        P = p;
        V = new Point(0, 0);
    }
}

フォームクラスにはメンバーとしてこのクラスの配列を持たせ、コンストラクタの InitializeComponent の後で初期化処理を実行します。要素数はとりあえず10にしておきます。Octopusクラスインスタンス化時、コンストラクタには適当な座標(とりあえず画面の真ん中あたり)を与えます。

public partial class Form1 : Form
{
    private Octopus[] member;

    public Form1()
    {
        InitializeComponent();

        member = new Octopus[10];
        for (int i = 0; i < member.Length; i++)
        {
            member[i] = new Octopus(new Point(400, 300));
        }
    }
}

フォームに ImageListを1つ追加して、タコの画像をセットします。
円を描くでも使用した、タコさんです。
これ。→


▲ ImageList の設定。サイズは32x32、深さは24ビット。「イメージの選択」をクリックして画像選択画面へ。


▲ 画像選択画面。「追加」ボタンをクリック。


▲ 追加したい画像を選択。


▲ 追加されました。「OK」ボタンをクリック。

フォームにPaintイベントメソッドを作ります。

イベントメソッドの内容はこちら。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    if (member != null)
    {
        e.Graphics.DrawImage(imageList1.Images[0], member[0].P);
    }
}

ここでとりあえず実行してみます。

画面の真ん中のあたり(ちょっと右下にずれていますが)に、タコが表示されました。

続いて、矢印キーを押すとタコが移動する仕組みを入れてみます。

  1. 矢印キーの種別ごとの座標増分の定義
  2. 座標増分を座標値に足しこむ処理
  3. キー押下(KeyDown)イベント処理

1つ目は、例えば矢印の右(→)についてはX座標がプラス、Y座標は変化させない、という具合です。
2つ目は、座標をPointクラスで表現していますが、Pointクラスは座標プロパティを直接操作できないので、新しいPointオブジェクトを作成する処理。
3つ目は、キーが押下されたらそれに応じて上記2つを利用してタコさんの座標を変化させ、画面を再描画させるというもの。
以下のような形。

private readonly Dictionary Velocity = new Dictionary()
{
    { Keys.Right, new Point(20, 0) },
    { Keys.Left, new Point(-20, 0) },
    { Keys.Up, new Point(0, -20) },
    { Keys.Down, new Point(0, 20) },
};

private Point MoveObject(Point p, Point v)
{
    int x = p.X + v.X;
    int y = p.Y + v.Y;
    if (x < 0) x = 0;
    if (x > this.ClientSize.Width - 16) x = this.ClientSize.Width - 16;
    if (y < 0) y = 0;
    if (y > this.ClientSize.Height - 16) y = this.ClientSize.Height - 16;
    return new Point(x, y);
}

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (Velocity.ContainsKey(e.KeyCode))
    {
        Point v = Velocity[e.KeyCode];
        member[0].P = MoveObject(member[0].P, v);
        Refresh();
    }
}

座標増分Dictionaryで定義してみました。1押しで20ピクセルぐらい動くようにしてあります。
座標を変化させる処理の中では座標値が画面をはみ出そうな場合ははみ出ないようにする処理を施してあります。
キー押下イベントは、押下キーコードで座標増分Dictionaryを参照して座標増分を決定し、MoveObjectメソッドで座標を変化させた後画面再描画処理(FormのRefrechメソッド)を呼んでいます。

ここでまた実行して、矢印キーを押して動くか試してみます。

動きましたね!

★ ★ ★

今度は、「1匹目のタコを2匹目のタコが追いかける」「3匹目のタコが2匹目のタコを…」というのをやってみます。
これは、「1つ前のタコの座標増分をマネする」という仕組みをいれればできます。
あと、ここまでのソースだと1匹目のタコしか描画していないので、Paintイベントにfor文を入れて全部描かせます。
以下のような感じ。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    if (member != null)
    {
        for (int i = 0; i < member.Length; i++)
        {
            e.Graphics.DrawImage(imageList1.Images[0], member[i].P);
            e.Graphics.DrawString(i.ToString(), new Font("MS ゴシック", 8), Brushes.AliceBlue, member[i].P.X + 4, member[i].P.Y - 10);
        }
    }
}

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (Velocity.ContainsKey(e.KeyCode))
    {
        Point v = Velocity[e.KeyCode];
        for (int i = member.Length - 1; i > 0; i--)
        {
            member[i].V = member[i - 1].V;
            member[i].P = MoveObject(member[i].P, member[i].V);
        }
        member[0].V = v;
        member[0].P = MoveObject(member[0].P, member[0].V);

        Refresh();
    }
}

Paintの方、わかりやすいようにタコの頭の上に番号が表示されるようにしてみました。

キー押下イベントの方ですが、for文がお尻の方から始まるようになっています。これは現在の座標増分を保持するため、座標増分が「1つ前のタコのもの」で上書きされる関係上、先頭から処理するとうまくいかないからです。
1匹目のタコだけは何かを模倣するわけではなくてキー押下によって決定した座標増分なので、for文から外れています。

実行してみましょう。

2匹目以降が着いていくようになりましたね!

★ ★ ★

1匹目が矢印キーで動く以外にも、マウスポインタを追いかけて動くようにしてみましょう。

MouseMove イベントを追加します。

内容は次のようにします。

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    Point v = new Point(e.X - member[0].P.X, e.Y - member[0].P.Y);
    for (int i = member.Length - 1; i > 0; i--)
    {
        member[i].V = member[i - 1].V;
        member[i].P = MoveObject(member[i].P, member[i].V);
    }
    member[0].V = v;
    member[0].P = MoveObject(member[0].P, member[0].V);
    Refresh();
}

実行すると…

マウスポインタの動きに追従するようになりましたね!

★ ★ ★

というわけで、今日のところはこのあたりで。

次回は「フォームを透明にしてデスクトップマスコットのようにする」というのをやってみたいと思います。

それでは!

タグ: ,