Latest Entries

Ads by Google

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

ランダムマップを作ってみる その5

ランダムマップを作ってみる その4の続きです。

さて、前回作成した部屋を通路で結んでいきます。
通常のローグライクでは部屋と部屋を結ぶ道は1本しか作らないようです。
これはこれでいいんですけど、今回はすべてランダムにしようと思います。
これは部屋から部屋を結ぶ道の数を乱数で決めるというのではなく、とある部屋の一点から次の部屋の一点を結ぶことで、通路の重なりを発生させランダムな道を作るということです。
つまり、運しだいですが3路とか4路のように通路に分岐が発生するということです。
部屋の中の一点は乱数により決めることとします。
その一点をリストに登録しておき、1番目の点と2番目の点を結び、2番目の点と3番目の点を結んでいきます。
最後の点の場合は、1番目の点と結ぶようにします。
これで、部屋を結ぶ通路がすべての部屋を循環することができます。
このとき、部屋の区画が狭ければ通路の重なりが発生します。
点と点を結ぶ方法としては、それぞれの点の中間点に向かって上下に進んで行き、中間点になったら横方向に結んでいく方法と、それぞれの点の中間点に向かって左右に進んで行き、中間点になったら縦方向に結んでいく方法を組み合わせていきます。
ただ、これもランダム性に欠けますから、中間点ではなく2点間の任意の点にします。

この処理でできあがる途中経過の通路はマップとは別のものに保存しておきます。
すべての通路ができあがったら、マップに書き写していきます。

では、実際のプログラムを作ってみます。
今回は区画を表示する必要がありませんから、区画表示用の処理についてはコメントにしておきます。
Public Class MapClass
  …(省略)

  Public Sub New( )
    MakeMap( )
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    …(省略)
  End Sub

  Sub MakeMap( ) 'マップ作成
    …(省略)
    DivSplit(dtmp) '区画の分割
    MakeRoom( ) '部屋の作成
    Makeway( ) '通路の作成

    '↓区画領域をマップに出力する繰り返し・・・領域確認時しか不要
    'For i As Integer = 0 To _dList.Count - 1
    '  dtmp = _dList(i)
    '  For y As Integer = dtmp.Top To dtmp.Bottom
    '    _mdata(y, dtmp.Left) = mCategory.Way
    '    _mdata(y, dtmp.Right) = mCategory.Way
    '  Next
    '  For x As Integer = dtmp.Left To dtmp.Right
    '    _mdata(dtmp.Top, x) = mCategory.Way
    '    _mdata(dtmp.Bottom, x) = mCategory.Way
    '  Next
    'Next

  End Sub

  Sub DivSplit(ByVal dVal As DivStruct) '区画作成
    …(省略)
  End Sub

  Sub MakeRoom( ) '部屋の作成
    …(省略)
  End Sub

  Sub Makeway( ) '通路作成メソッド
    Dim rTmp As List(Of Point) = New List(Of Point)

    For i As Integer = 0 To _rList.Count - 1 '各部屋の任意のXY座標を決定する繰り返し
      Dim x, y As Integer
      x = _rnd.Next(_rList(i).X, _rList(i).Width) 'X軸座標の決定
      y = _rnd.Next(_rList(i).Y, _rList(i).Height) 'Y軸座標の決定
      rTmp.Add(New Point(x, y))
    Next

    For i As Integer = 0 To rTmp.Count - 1 '任意のXY座標から部屋同士を結ぶ通路を作成する繰り返し
      If i < rTmp.Count - 1 Then 'カウント未満なら
        WayMap(rTmp(i), rTmp(i + 1)) '1点目と2点目、2点目と3点目・・・を結ぶ
      Else '最終カウントなら
        WayMap(rTmp(i), rTmp(0)) '最後の点と最初の点を結ぶ
      End If
    Next
  End Sub

  Sub WayMap(ByVal p1 As Point, ByVal p2 As Point) '2点間の通路を作成するメソッド
    Dim wMap(,) As Boolean = New Boolean(MHEIGHT, MWIDTH) { } '通路専用マップ
    Dim wx, wy As Integer
    If _rnd.Next(2) = 0 Then 'X軸方向
      If p1.X < p2.X Then '点1のX座標が点2のX座標より左にあるなら
        wx = _rnd.Next(p1.X, p2.X) '折り返し点(交差点)を決める
        For x As Integer = p1.X To wx '折り返し点まで通路にする
          wMap(p1.Y, x) = True
        Next
        For x As Integer = wx To p2.X '折り返し点から点2までを通路にする
          wMap(p2.Y, x) = True
        Next
      Else '点1のX座標が点2のX座標より右にあるなら
        wx = _rnd.Next(p2.X, p1.X)
        For x As Integer = wx To p1.X
          wMap(p1.Y, x) = True
        Next
        For x As Integer = p2.X To wx
          wMap(p2.Y, x) = True
        Next
      End If
      If p1.Y < p2.Y Then '点1のY座標が点2のY座標より上にあるなら
        For y As Integer = p1.Y To p2.Y '点1から点2へ通路を作成
          wMap(y, wx) = True
        Next
      Else '点1のY座標が点2のY座標より下にあるなら
        For y As Integer = p2.Y To p1.Y '点2から点1へ通路を作成
          wMap(y, wx) = True
        Next
      End If
    Else 'Y軸方向
      If p1.Y < p2.Y Then '点1のY座標が点2のY座標より上にあるなら
        wy = _rnd.Next(p1.Y, p2.Y)
        For y As Integer = p1.Y To wy
          wMap(y, p1.X) = True
        Next
        For y As Integer = wy To p2.Y
          wMap(y, p2.X) = True
        Next
      Else '点1のY座標が点2のY座標より下にあるなら
        wy = _rnd.Next(p2.Y, p2.Y)
        For y As Integer = wy To p1.Y
          wMap(y, p1.X) = True
        Next
        For y As Integer = p2.Y To wy
          wMap(y, p2.X) = True
        Next
      End If
      If p1.X < p2.X Then
        For x As Integer = p1.X To p2.X
          wMap(wy, x) = True
        Next
      Else
        For x As Integer = p2.X To p1.X
          wMap(wy, x) = True
        Next
      End If
    End If
    For y As Integer = 0 To wMap.GetUpperBound(0) '通路マップをメインマップに出力
      For x As Integer = 0 To wMap.GetUpperBound(1)
        If wMap(y, x) = True Then
          _mdata(y, x) = mCategory.Way
        End If
      Next
    Next
  End Sub

End Class

以上で、ランダムマップのクラス作成は完成です。
区画サイズを変更すれば大きな部屋もできますし、小さな部屋も作成することができます。
部屋が小さければ通路がより複雑になって、結構いい感じになったりします。
個人的は一般的なローグライクよりこっちのほうがお気に入りだったりします。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

ランダムマップを作ってみる その4

ランダムマップを作ってみる その3の続きです。

前回のマップクラスで区画を分けることができるようになりました。
今回はそれぞれの区画の中に部屋を作成していきます。
部屋の作り方は簡単です。
区画リストの中から1つずつ区画を取り出し、区画の大きさ内での部屋を作成すればよいわけです。
部屋の大きさは、区画の大きさから余白を2マスとった大きさを最大とします。
余白を1マスにしてしまうと通路を作成するスペースがなくなってしまいます。
逆に1マスにして不規則な部屋を作るというのもありですね。
なお、作成した部屋は、通路を作成するときに再度利用しますからリストに保存しておきます。

以下、オレンジ色の箇所が今回の追加箇所となります。
Public Class MapClass
  Enum mCategory 'マスの種類
    Wall = 0 '壁
    Way = 1 '通路
  End Enum

  Structure DivStruct '区画構造体
    Dim Top, Left, Bottom, Right As Integer
  End Structure

  Structure RoomStruct '部屋構造体
    Dim X, Y, Width, Height As Integer
  End Structure


  Const MWIDTH As Integer = 64 '横マス数
  Const MHEIGHT As Integer = 48 '縦マス数
  Const MSIZE As Integer = 10 'マスサイズ
  Const MINROOM As Integer = 10 '部屋の最小サイズ・・・変動したほうが面白い
  Const MARGIN As Integer = 2 '余白
  Const MINDIV As Integer = MINROOM + (MARGIN * 2) '最小区画サイズ

  Dim _mdata(,) As mCategory 'マップデータ配列
  Dim _dList As List(Of DivStruct) '区画リスト
  Dim _rList As List(Of RoomStruct) '部屋リスト
  Dim _rnd As Random '乱数オブジェクト

  Public Sub New( )
    MakeMap( )
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    …(省略)
  End Sub

  Sub MakeMap( ) 'マップ作成
    Dim dtmp As DivStruct = New DivStruct
    Dim rtmp As RoomStruct = New RoomStruct

    _rnd = New Random
    _mdata = New mCategory(MHEIGHT, MWIDTH) { } 'マップ配列作成
    For y As Integer = 0 To _mdata.GetUpperBound(0) '行・・・Y軸
      For x As Integer = 0 To _mdata.GetUpperBound(1) '列・・・X軸
        _mdata(y, x) = mCategory.Wall 'すべて壁
      Next
    Next
    _dList = New List(Of DivStruct) '区画リストオブジェクトの作成
    _rList = New List(Of RoomStruct) '部屋リストオブジェクトの作成
    '↓初期区画領域の設定
    dtmp.Left = 0
    dtmp.Top = 0
    dtmp.Right = MWIDTH - 1
    dtmp.Bottom = MHEIGHT - 1

    DivSplit(dtmp) '区画の分割
    MakeRoom( ) '部屋の作成

    '↓区画領域をマップに出力する繰り返し・・・領域確認時しか不要
    For i As Integer = 0 To _dList.Count - 1
      …(省略)
    Next
  End Sub

  Sub DivSplit(ByVal dVal As DivStruct) '区画作成
    …(省略)
  End Sub

  Sub MakeRoom( ) '部屋の作成
    Dim dtmp As DivStruct '区画構造体変数
    Dim rtmp As RoomStruct '部屋矩形

    For i As Integer = 0 To _dList.Count - 1 '各区画内に部屋領域を作成
      dtmp = _dList(i)
      rtmp.Width = _rnd.Next(MINROOM, dtmp.Right - dtmp.Left - (MARGIN * 2)) '横幅
      rtmp.Height = _rnd.Next(MINROOM, dtmp.Bottom - dtmp.Top - (MARGIN * 2)) '縦幅
      rtmp.X = _rnd.Next(dtmp.Left + MARGIN, dtmp.Right - MARGIN - rtmp.Width) '左端
      rtmp.Y = _rnd.Next(dtmp.Top + MARGIN, dtmp.Bottom - MARGIN - rtmp.Height) '上端
      rtmp.Width += rtmp.X '右端
      rtmp.Height += rtmp.Y '下端
      _rList.Add(rtmp) '部屋リストを追加
    Next

    '↓各部屋の領域内を通路とする繰り返し
    For i As Integer = 0 To _rList.Count - 1
      rtmp = _rList(i)
      For y As Integer = rtmp.Y To rtmp.Height
        For x As Integer = rtmp.X To rtmp.Width
          _mdata(y, x) = mCategory.Way
        Next
      Next
    Next
  End Sub

End Class

これにより、以下のようなマップ画面が表示されます。
ランダムマップ05
それぞれの区画内に部屋ができていることが確認できるはずです。

次回は、通路を作成していきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

ランダムマップを作ってみる その3

ランダムマップを作ってみる その2の続きです。

では、ローグライク(不思議のダンジョン系)のマップ処理を作成していきます。
まず、マップ領域をいくつかの区画に分割していくことから始めます。
ローグライクでは部屋の数はランダムで決まります。
ということは、部屋を配置する区画の数もランダムで決まらなければなりません。
区画の数をランダムで決めるというのは、あらかじめ区画数を乱数で決めておき、分割していくということではありません。
1つの区画の大きさを乱数によって決めるということになります。
イメージとしては、大きな四角形内に縦か横のどちらかで線を引いて四角形の中に四角形を作っていき、それぞれの四角形が最小限の大きさになったら線を引くのをやめるという感じになります。
このやり方で出来上がったそれぞれの四角形を1つの区画領域として保存すれば区画の分割はできます。
なお、この考え方であれば区画領域同士が重なることはなくなります。
また、縦に線を引くか横に線を引くかはランダムで決めてしまえばいいでしょう。

さて、区画の分割のイメージができたところで実際にプログラムとして作成するにはどうするかを考えます。
分割のイメージは四角形の中に四角形を作っていくということです。
何回四角形を作っていくかは不明ですから、For〜NextではなくDo〜Loopで繰り返せばいいように思いがちです。
ですが、今回の考え方は二分木を作るような考え方となります。
二分木のようなものを作るときは、再帰処理を使った方が効率がよいでしょう。
今回の場合は、最低限の区画領域になるまで再帰していき、これ以上分割できないという大きさなったら区画として追加するように処理させます。

実際のプログラムは以下のようになります。
今回はデータを管理しやすくするために列挙型や構造体も使っています。
Public Class MapClass
  Enum mCategory 'マスの種類
    Wall = 0 '壁
    Way = 1 '通路
  End Enum

  Structure DivStruct '区画構造体
    Dim Top, Left, Bottom, Right As Integer
  End Structure

  Const MWIDTH As Integer = 64 '横マス数
  Const MHEIGHT As Integer = 48 '縦マス数
  Const MSIZE As Integer = 10 'マスサイズ
  Const MINROOM As Integer = 10 '部屋の最小サイズ・・・変動したほうが面白い
  Const MARGIN As Integer = 2 '余白
  Const MINDIV As Integer = MINROOM + (MARGIN * 2) '最小区画サイズ

  Dim _mdata(,) As mCategory 'マップデータ配列
  Dim _dList As List(Of DivStruct) '区画リスト
  Dim _rnd As Random '乱数オブジェクト

  Public Sub New( )
    MakeMap( )
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    For y As Integer = 0 To _mdata.GetUpperBound(0) '行・・・Y軸
      For x As Integer = 0 To _mdata.GetUpperBound(1) '列・・・X軸
        Select Case _mdata(y, x)
          Case mCategory.Wall '壁
            g.FillRectangle(Brushes.Black, New Rectangle(x * MSIZE, y * MSIZE, MSIZE, MSIZE))
          Case mCategory.Way '通路
            g.DrawRectangle(Pens.White, New Rectangle(x * MSIZE, y * MSIZE, MSIZE, MSIZE))
        End Select
      Next
    Next
  End Sub

  Sub MakeMap( ) 'マップ作成
    Dim dtmp As DivStruct = New DivStruct

    _rnd = New Random
    _mdata = New mCategory(MHEIGHT, MWIDTH) { } 'マップ配列作成
    For y As Integer = 0 To _mdata.GetUpperBound(0) '行・・・Y軸
      For x As Integer = 0 To _mdata.GetUpperBound(1) '列・・・X軸
        _mdata(y, x) = mCategory.Wall 'すべて壁
      Next
    Next
    _dList = New List(Of DivStruct) '区画リストオブジェクトの作成
    '↓初期区画領域の設定
    dtmp.Left = 0
    dtmp.Top = 0
    dtmp.Right = MWIDTH - 1
    dtmp.Bottom = MHEIGHT - 1

    DivSplit(dtmp) '区画の分割

    '↓区画領域をマップに出力する繰り返し・・・領域確認時しか不要
    For i As Integer = 0 To _dList.Count - 1
      dtmp = _dList(i)
      For y As Integer = dtmp.Top To dtmp.Bottom
        _mdata(y, dtmp.Left) = mCategory.Way
        _mdata(y, dtmp.Right) = mCategory.Way
      Next
      For x As Integer = dtmp.Left To dtmp.Right
        _mdata(dtmp.Top, x) = mCategory.Way
        _mdata(dtmp.Bottom, x) = mCategory.Way
      Next
    Next
  End Sub

  Sub DivSplit(ByVal dVal As DivStruct) '区画作成
    Dim dPrt As DivStruct = New DivStruct '親区画変数の作成
    Dim dCld As DivStruct = New DivStruct '子区画変数の作成
    Dim num As Integer '分割座標

    dPrt = dVal '区画データの受け渡し
    If dPrt.Bottom - dPrt.Top > MINDIV * 2 AndAlso dPrt.Right - dPrt.Left > MINDIV * 2 Then
      dCld = dPrt '子区画にコピー
      If _rnd.Next(2) = 0 Then '乱数が0なら横分割(Y軸)
        num = _rnd.Next(dPrt.Top + MINDIV, dPrt.Bottom - MINDIV)
        dPrt.Bottom = num '親の下端を書き換え
        dCld.Top = num '子の上端を書き換え
      Else '乱数が1なら縦分割(X軸)
        num = _rnd.Next(dPrt.Left + MINDIV, dPrt.Right - MINDIV)
        dPrt.Right = num '親の右端を書き換え
        dCld.Left = num '子の左端を書き換え
      End If
      DivSplit(dPrt) '親区画をさらに分割
      DivSplit(dCld) '子区画をさらに分割
    Else '分割できる領域がなければ
      _dList.Add(dPrt) '区画リストに追加
    End If
  End Sub
End Class

DivSplitメソッドは実際に四角形の中に四角形を描いていけばイメージしやすいかと思います。
このプログラムを実行すれば以下のようなマップが表示されます。
ランダムマップ03
ただ、区画の分割(縦か横)を乱数によって行っていますから、場合によっては以下のような分割もできてしまいます。
ランダムマップ04
まぁ、個人的にはこれはこれでありなんですけどね。

さて、以上で区画領域を作ることができました。
次回はこの区画領域内に部屋を作っていきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

ランダムマップを作ってみる その2

ランダムマップを作ってみる その1の続きです。

まず、ゲームループとなるForm1クラスから作成していきます。
今回はマップを作成して表示するだけですから、アニメーション処理はありません。
よって、Paintメソッドの更新処理は空白となります。
Public Class Form1
  Dim _map As MapClass

  Sub New( )
    ' この呼び出しは、Windows フォーム デザイナで必要です。
    InitializeComponent( )
    ' InitializeComponent( ) 呼び出しの後で初期化を追加します。
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer, True)
    Me.ClientSize = New Size(640, 480)
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle

    _map = New MapClass
  End Sub

  Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    Dim g As Graphics = e.Graphics
    '更新

    '描画
    _map.Draw(g)
    'フォーム再描画
    Me.Invalidate( )
  End Sub
End Class

次回、ランダムマップ作成クラスを作成していきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

ランダムマップを作ってみる その1

RPGゲームを作っていくと、トルネコとかシレンのような不思議のダンジョン系も作ってみたいと思ったりするわけです。
で、今回はローグライクに挑戦してみます。
といっても、まずはランダムマップを自動生成するところまでを目標とします。
このローグライクはC言語系であればプログラムがネット上に紹介されていたり、本で紹介されたりしているわけですが、VBでのプログラムはなかなか見当たりません。
VBでゲームを作るって人が少ないからでしょうかね。

さて、ランダムマップを作っていくにあたって、アルゴリズムを考えないと先には進めません。
アルゴリズムとして参考としたのが、ダンジョンゲームプログラミングという本です。
C言語系の本ですが、考え方だけは応用できるかと思います。

ローグライクのアルゴリズムはシンプルで以下のような概略となります。
  • マップをいくつかの区画に分ける
  • 区画の中に部屋を作る
  • 部屋と部屋を通路で結ぶ

これだけでランダムマップを作成することができます。
以下は実際に作成したマップです。
ランダムマップ
ランダムマップ02
次回、プログラムを作成していきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

4択クイズを作ってみる その9

4択クイズを作ってみる その8の続きです。

では、ゲームが始まったときに実行されるタイトルクラスを作成していきます。
タイトルクラスでは、タイトルの描画とメニューの描画を行います。
さらに、メニューの上にマウスが重なったときの処理、メニューを選択したときの処理を行います。
特に重要となるのがメニュー選択時の処理です。
この選択したメニューによりゲームに出題される問題の数が変化するようにしていきます。
ということは、プレイヤーがどのメニューを選択したのかをGameClassに返し、それをPlayClassに渡す処理をしなければならないわけです。
実際、4択クイズを作ってみる その8でのGameClassではMouseClickメソッドの中でタイトルオブジェクトから選択したレベル(コース)を取得し、PlayClassオブジェクトを作成するときに渡すようにしています。
タイトルオブジェクトから取得する方法にはFunctionを使うようにしています。
タイトルクラスの中にプロパティを作成し、プロパティから取得しても構いませんが、ちょっと無駄な処理となりそうでしたので今回はFunctionで行っています。

また、タイトルの描画処理では画像ではなく文字によるタイトル描画をやってみました。
これは、ただDrawStringメソッドを使った方法による文字の表示ではなく、パスを使った画像としての文字の表示方法となります。
パスを使うことで文字の縁取りを行うことができます。
パスを使うためには、System.Drawing.Drawing2D名前空間を参照設定しておく必要がありますので、Importsをしておきます。
では、プログラムを作っていきます。
Imports System.Drawing.Drawing2D

Public Class TitleClass
  Const mWidth As Integer = 96 'メニュー幅
  Const mHeight As Integer = 32 'メニュー高
  Const px As Integer = 272 'メニュー表示X座標
  Const py As Integer = 240 'メニュー表示Y座標
  Const dy As Integer = 40 'メニュー表示シフトY座標
  Dim mimg As Bitmap 'メニュー画像配列
  Dim mRect( ) As Rectangle 'メニュー矩形配列
  Dim mId As Integer '表示画像番号
  Dim _fm As FontFamily = New FontFamily("HG丸ゴシックM-PRO") 'フォントの種類...HG丸ゴシックM-PRO
  Dim _pen As Pen = New Pen(Color.Orange, 10) 'ペン

  Public Sub New( )
    mimg = New Bitmap(My.Resources.menu) 'メニュー画像の読み込み
    mimg.MakeTransparent( ) '背景を透明化
    mRect = New Rectangle(9) { } 'メニュー画像矩形配列の作成
    mRect(0) = New Rectangle(0, 0, mWidth, mHeight)
    mRect(1) = New Rectangle(0, 32, mWidth, mHeight)
    mRect(2) = New Rectangle(0, 64, mWidth, mHeight)
    mRect(3) = New Rectangle(0, 96, mWidth, mHeight)
    mRect(4) = New Rectangle(0, 128, mWidth, mHeight)
    mRect(5) = New Rectangle(96, 0, mWidth, mHeight)
    mRect(6) = New Rectangle(96, 32, mWidth, mHeight)
    mRect(7) = New Rectangle(96, 64, mWidth, mHeight)
    mRect(8) = New Rectangle(96, 96, mWidth, mHeight)
    mRect(9) = New Rectangle(96, 128, mWidth, mHeight)
    mId = 0
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    Dim gp As GraphicsPath = New GraphicsPath
    Dim sf As StringFormat = New StringFormat

    Dim str As String = "Perfect" & vbCrLf & "Choice"

    g.FillRectangle(Brushes.White, 0, 0, 640, 480) '背景を白で塗りつぶし

    sf.Alignment = StringAlignment.Center '中央寄せ
    sf.LineAlignment = StringAlignment.Center '縦中央寄せ

    gp.Reset( )
    gp.AddString(str, _fm, FontStyle.Regular, 100, New Point(320, 120), sf) 'パスの追加
    gp.CloseFigure( ) 'パスを閉じる
    g.SmoothingMode = SmoothingMode.AntiAlias
    g.DrawPath(_pen, gp) '縁取り
    g.FillPath(Brushes.Gold, gp) ’パスの塗りつぶし


    If mId <> 1 Then
      g.DrawImage(mimg, px, py, mRect(0), GraphicsUnit.Pixel)
    Else
      g.DrawImage(mimg, px, py, mRect(5), GraphicsUnit.Pixel)
    End If
    If mId <> 2 Then
      g.DrawImage(mimg, px, py + dy, mRect(1), GraphicsUnit.Pixel)
    Else
      g.DrawImage(mimg, px, py + dy, mRect(6), GraphicsUnit.Pixel)
    End If
    If mId <> 3 Then
      g.DrawImage(mimg, px, py + dy * 2, mRect(2), GraphicsUnit.Pixel)
    Else
      g.DrawImage(mimg, px, py + dy * 2, mRect(7), GraphicsUnit.Pixel)
    End If
    If mId <> 4 Then
      g.DrawImage(mimg, px, py + dy * 3, mRect(3), GraphicsUnit.Pixel)
    Else
      g.DrawImage(mimg, px, py + dy * 3, mRect(8), GraphicsUnit.Pixel)
    End If
    If mId <> 5 Then
      g.DrawImage(mimg, px, py + dy * 4, mRect(4), GraphicsUnit.Pixel)
    Else
      g.DrawImage(mimg, px, py + dy * 4, mRect(9), GraphicsUnit.Pixel)
    End If
  End Sub

  Public Sub MouseMove(ByVal p As Point)
    If p.X >= 320 - mWidth / 2 And p.X <= 320 + mWidth / 2 Then
      If p.Y >= py AndAlso p.Y <= py + mHeight Then
        mId = 1
      ElseIf p.Y >= py + dy AndAlso p.Y <= py + dy + mHeight Then
        mId = 2
      ElseIf p.Y >= py + dy * 2 AndAlso p.Y <= py + dy * 2 + mHeight Then
        mId = 3
      ElseIf p.Y >= py + dy * 3 AndAlso p.Y <= py + dy * 3 + mHeight Then
        mId = 4
      ElseIf p.Y >= py + dy * 4 AndAlso p.Y <= py + dy * 4 + mHeight Then
        mId = 5
      Else
        mId = 0
      End If
    Else
      mId = 0
    End If
  End Sub

  Public Function MouseClick(ByVal p As Point) As Integer
    Dim lv As Integer
    If p.X >= 320 - mWidth / 2 And p.X <= 320 + mWidth / 2 Then
      If p.Y >= py AndAlso p.Y <= py + mHeight Then
        lv = 1
      ElseIf p.Y >= py + dy AndAlso p.Y <= py + dy + mHeight Then
        lv = 2
      ElseIf p.Y >= py + dy * 2 AndAlso p.Y <= py + dy * 2 + mHeight Then
        lv = 3
      ElseIf p.Y >= py + dy * 3 AndAlso p.Y <= py + dy * 3 + mHeight Then
        lv = 4
      ElseIf p.Y >= py + dy * 4 AndAlso p.Y <= py + dy * 4 + mHeight Then
        lv = 5
      Else
        lv = 0
      End If
    Else
      lv = 0
    End If
    Return lv
  End Function
End Class

Drawメソッドの中でDim sf As StringFormat = New StringFormatという変数を作成しています。
これは、パスのデータを座標を元にどの位置に表示するかを設定することのできるオブジェクトとなります。
今回は、横方向に中央寄せ、縦方向にも中央寄せとしています。
この設定がパスを作成するときのgp.AddString(str, _fm, FontStyle.Regular, 100, New Point(320, 120), sf)で有効となります。
中央寄せと中央寄せにすることで、パスの中心座標が指定した(320,120)となります。

これで以下のようなタイトル画面ができあがりです。
4択クイズ01

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

画面スクロール その2

画面スクロール その1の続きです。

では、画面スクロール処理の作成を行っていきます。
今回のスクロール処理には、マウスクリックで自動スクロールとカーソルキーでの手動スクロールの2パターンを組み込んでいきます。
スクロールの考え方は、表示矩形のY座標を変化させていくことです。
自動スクロールの場合は、一定時間が経過すると変化させます。
手動スクロールの場合は、カーソルキーが押されている間だけ変化させていきます。
ちなみに、KeyDownイベントはキーを押しているときには常に動作していますので、カーソルキーが押されている間という繰り返し処理は不要です。

自動スクロールはクリックで上から下へ、もう一度クリックで下から上へ移動するようにします。
もちろん自動スクロール中は停止するまでクリックを無効にします。

というこで、プログラムの方を作成していきます。
自動スクロール時にはUpdateメソッドとScrollStartメソッドが実行されます。
手動スクロール時にはKeyDownメソッドが実行されます。
それ以外のメソッドは共通となります。
Public Class ScrollClass
  Const SC As Integer = 10 'スクロール量
  Dim _img As Bitmap
  Dim _rect As Rectangle
  Dim _updown As Boolean 'Falseが上へ、Trueが下へ
  Dim _anime As Boolean 'アニメーションフラグ
  Dim _lasttime As Double
  Dim _dy As Integer '表示Y座標

  Public Sub New( )
    _img = New Bitmap(My.Resources.scroll1) 'リソースから画像の読み込み
    _rect = New Rectangle(0, 0, 640, 480) '初期表示矩形の作成
    _anime = False
    _updown = False
  End Sub

  Public Sub Update(ByVal nowTime As Double)
    If _anime Then 'アニメーション中なら
      If nowTime - _lasttime > 0.1 Then '一定時間経過していれば
        If _updown Then '上から下へ
          If _dy + SC < _img.Height - 480 Then
            _dy += SC
          Else
            _anime = False
          End If
        Else '下から上へ
          If _dy - SC > 0 Then
            _dy -= SC
          Else
            _anime = False
          End If
        End If
        _lasttime = nowTime
        _rect = New Rectangle(0, _dy, 640, 480)
      End If
    End If
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    g.DrawImage(_img, New Rectangle(0, 0, 640, 480), _rect, GraphicsUnit.Pixel)
  End Sub

  Public Sub ScrollStart( )
    If _anime = False Then
      _updown = Not _updown '上下反転
      If _updown Then '上から下へ
        _dy = 0
      Else '下から上へ
        _dy = _img.Height - 480
      End If
      _rect = New Rectangle(0, _dy, 640, 480)
      _lasttime = 0.0
      _anime = True 'アニメーション開始
    End If
  End Sub

  Public Sub KeyDown(ByVal k As Keys)
    If _anime = False Then
      Select Case k
        Case Keys.Up '上キーが押されていたら
          If _dy - SC > 0 Then
            _dy -= SC
          End If
        Case Keys.Down '下キーが押されていたら
          If _dy + SC < _img.Height - 480 Then
            _dy += SC
          End If
      End Select
      _rect = New Rectangle(0, _dy, 640, 480)
    End If
  End Sub
End Class


以上で画像の縦スクロール処理のできあがりです。
自動スクロール中に一時停止とかをやってもいいのかもしれませんが、そこらへんは仕様しだいでしょうかね。
あと、横スクロールをしたければX座標を動かせばよいだけですから改造は容易にできるかと思います。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

画像スクロール その1

画面サイズより大きな画像を表示する際、縮小して表示するという方法や画面を切り替えて表示するという方法がありますが、今回は画面スクロールで表示する方法をやってみます。
この考え方は、一定時間ごとに表示する矩形座標を変更していくかカーソルキーが押されたときに表示する矩形座標を変更するように処理させればよいわけです。
たとえば、640×960の画像を縦スクロールさせたければ矩形座標(0,0,640,480)をスタート座標とし、一定時間ごとにY座標を増やしていけば下へ画像をスクロールすることができます。
逆に矩形座標(0,480,640,480)をスタート座標とし、一定時間ごとにY座標を減らしていけば上へ画像をスクロールすることができます。
どちらの場合も特定の矩形座標になった場合にはスクロールを停止させる必要があります。
たとえば、上から下へのスクロールを停止する矩形座標は(0,480,640,480)となり、逆に下から上へのスクロールを停止する矩形座標は(0,0,640,480)となります。
これらの座標で停止させなければ画像の存在しない領域を表示することになりますから気をつけなければなりません。
ちなみに、画像の高さが960ピクセルで固定ではなく、画像によってバラバラの場合は上から下へスクロールするときの停止矩形座標は(0,画像の高さ-480,640,480)とすれば対応することができます。
さらに、下から上へスクロールさせるときのスタート矩形座標は(0,画像の高さ-480,640,480)とすればどんな画像の高さであっても対応することができます。

今回は640×960の画像を利用していきますので、あらかじめ画像を用意しリソースに追加しておきます。
では、Form1クラスから作成していきます。
このクラスはいつものパターンで作成していきます。
Public Class Form1
  Dim swatch As Stopwatch = New Stopwatch( ) 'ストップウォッチ
  Dim _scroll As ScrollClass

  Sub New( )
    ' この呼び出しは、Windows フォーム デザイナで必要です。
    InitializeComponent( )
    ' InitializeComponent( ) 呼び出しの後で初期化を追加します。
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer, True)
    Me.ClientSize = New Size(640, 480)
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle

    swatch.Reset( )
    swatch.Start( )
    _scroll = New ScrollClass
  End Sub

  Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    Dim g As Graphics = e.Graphics
    Dim nowTime As Double = swatch.ElapsedMilliseconds / 1000.0 'Paintメソッドを実行した瞬間の時間
    '更新
    _scroll.Update(nowTime)
    '描画
    _scroll.Draw(e.Graphics)
    'フォーム再描画
    Me.Invalidate( )
  End Sub

  Private Sub Form1_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick
    _scroll.ScrollStart( )
  End Sub

  Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
    _scroll.KeyDown(e.KeyCode)
  End Sub
End Class

Form1クラスではスクロール処理を行う_scrollオブジェクトを作成します。
マウスボタンがクリックされたときにはScrollStartメソッドを実行し、キー入力があった場合はKeyDownメソッドを実行するようにしておきます。

次回はスクロール処理を行うScrollClassを作成していきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

4択クイズを作ってみる その8

4択クイズを作ってみる その7の続きです。

4択クイズゲームに必要なオブジェクト等ゲーム全般を管理するゲームクラスを作成していきます。
まず、4択クイズを作ってみる その2で考えたモデルでよると、ゲームクラスの中にはタイトルクラスと出題クラスが含まれていることがわかります。
2つのクラスはまったく別の処理を行うクラスです。
これはゲームの状態により切り替えて利用することになります。
この状態をモードとしてあらかじめゲームクラスで用意しておく必要があります。
通常、ゲームはタイトルから始まりますので、モードの初期設定はタイトルモードとなります。
さらにタイトルオブジェクトを作成しておかなければエラーになりますから、ゲームクラスを作成したと同時にタイトルオブジェクトも作成するようにしておきます。
モード移行のタイミングは、以下のようになります。
  • タイトルから出題に変化するときはタイトルのコースを選択したとき
  • 出題からタイトルに変化するときはすべての問題の出題が終わったとき
全問出題したかどうかは出題クラスで把握させるようにしますので、その情報を受け取れば判断することができます。

また、コースとしては10問、20問、・・・50問の5種類があります。
このコースの選択はタイトルクラスで行うわけですが、選ばれたコースは出題クラスで利用します。
コース情報の受け渡しポイントはタイトルモード中にクリックされたときとなります。
ただ、クリックされたコース情報はタイトルオブジェクトが把握していますから、ゲームクラスで利用するためにはタイトルオブジェクトへアクセスする必要があります。
このような場合はプロパティを作成して行えばよいでしょう。

プログラムは以下のようになります。
Public Class GameClass
  Enum MODE As Integer 'ゲームモード列挙型
    title 'タイトルモード
    play 'プレイモード
  End Enum


  Dim _gmode As MODE 'ゲームモード
  Dim _title As TitleClass 'タイトルクラス
  Dim _play As PlayClass 'プレイクラス(出題クラス)

  Public Sub New( )
    _gmode = MODE.title 'タイトルモードを設定
    _title = New TitleClass 'タイトルオブジェクトを作成
  End Sub

  Public Sub Update(ByVal nowTime As Double, ByVal elapsedTime As Double)
    Select Case _gmode
      Case MODE.play 'プレイモードなら
        If _play.EndFlag Then 'ゲーム終了なら
          _play = Nothing 'プレイオブジェクトの解放
          _title = New TitleClass
          _gmode = MODE.title
        Else 'ゲームプレイ中なら
          _play.Update(nowTime, elapsedTime)
        End If

    End Select
  End Sub

  Public Sub Draw(ByVal g As Graphics)
    Select Case _gmode
      Case MODE.title 'タイトルモードなら
        _title.Draw(g)
      Case MODE.play 'プレイモードなら
        _play.Draw(g)
    End Select
  End Sub

  Public Sub MouseMove(ByVal p As Point)
    Select Case _gmode
      Case MODE.title 'タイトルモードなら
        _title.MouseMove(p)
      Case MODE.play 'プレイモードなら
        _play.MouseMove(p)
    End Select
  End Sub

  Public Sub MouseClick(ByVal p As Point, ByVal b As MouseButtons)
    Select Case _gmode
      Case MODE.title 'タイトルモードなら
        Dim lv As Integer = _title.MouseClick(p) 'タイトルクラスよりレベルを取得
        If lv > 0 Then '何らかのレベルを選択していたら
          _gmode = MODE.play 'プレイモードを設定
          _play = New PlayClass(lv) 'プレイオブジェクトを作成
          _title = Nothing 'タイトルオブジェクトの解放
        End If

      Case MODE.play 'プレイモードなら
        _play.MouseClick(p, b)
    End Select
  End Sub
End Class

なお、タイトルクラスはTitleClass、出題クラスはPlayClassとしています。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

4択クイズを作ってみる その7

4択クイズを作ってみる その6の続きです。

まず、ゲームの基盤となるForm1クラスから作成していきます。
このクラスはいつもどおりの定番処理ばかりになります。
このクラスが行う処理は、ゲームクラスの作成・管理と各種イベントの管理になります。

プログラムは以下のような感じです。
Public Class Form1
  Dim _Game As GameClass 'ゲームクラス
  Dim swatch As Stopwatch = New Stopwatch( ) 'ストップウォッチ
  Dim lastTime As Double '前回実行時の時間

  Sub New( )
    ' この呼び出しは、Windows フォーム デザイナで必要です。
    InitializeComponent( )
    ' InitializeComponent( ) 呼び出しの後で初期化を追加します。
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer, True)
    Me.ClientSize = New Size(640, 480)
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle

    lastTime = 0.0
    swatch.Reset( )
    swatch.Start( )
    _Game = New GameClass
  End Sub

  Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    Dim g As Graphics = e.Graphics
    Dim nowTime As Double = swatch.ElapsedMilliseconds / 1000.0 'Paintメソッドを実行した瞬間の時間
    Dim elapsedTime As Double = nowTime - lastTime
    lastTime = nowTime
    '更新
    _Game.Update(nowTime, elapsedTime)
    '描画
    _Game.Draw(g)
    'フォーム再描画
    Me.Invalidate( )
  End Sub

  Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
    _Game.MouseMove(e.Location)
  End Sub

  Private Sub Form1_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick
    _Game.MouseClick(e.Location, e.Button)
  End Sub
End Class

イベント処理としてMouseMoveとMouseClickを作成しています。
MouseMoveイベントはタイトル画面でのコース選択、問題の選択肢の選択時にマウスオーバー処理をさせるために利用します。
MouseEnterイベントを利用するのは一般的なコントロール上であって、今回のようなFormしかないプログラムではすべてEnter状態になってしまいますから利用しません。
また、MouseClickイベントはどのコース、どの選択肢を選択したかを判断するために利用します。
もちろん、重要となるのはクリックしたときの座標ですね。
この座標を利用することにより選択肢等の判断をすることができます。

次回はゲームに関するクラスの生成・管理を行うゲームクラスを作成していきます。

くつろぐ 人気ブログランキング にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキング【ブログの殿堂】

Appendix

最新記事

検索フォーム

月別アーカイブ

売れ筋ランキング

お楽しみ用

アニメグッズ

プロフィール

Author:ひなぎ
VB2005&2008でゲーム作成中

FC2カウンター

Powered By FC2ブログ

今すぐブログを作ろう!

Powered By FC2ブログ

FC2ブログ