Windowsストア アプリ 作り方解説 Line Attack編 第1回 ~画面のレイアウトと表示~
マイクロソフトの田中達彦です。
本連載では、Windowsストアアプリとして作成したパズルゲームである、Line Attackのプログラムを解説します。
Line Attack : https://apps.microsoft.com/webpdp/app/f11e327c-6228-4c8f-8245-ea57d65e0f09
[注意事項]
- この連載で提供するプロジェクトファイルは、サンプルとして提供しています。
- 毎回の記事で提供するプロジェクトファイルは、その時点でのソースコードです。最終バージョンのソースコードと異なる場合があります。
[今回のプロジェクトファイル]
今回公開するプロジェクトは、画面の描画を行う部分までを実装しています。
[画面レイアウトの考え方]
Line Attackでは、宝石をゆっくり右側にドラッグしていくと、ライン全体が以下のように動きます。
このとき、ラインの左端は空白にならずに、そのラインの右端にあった宝石または十字の画像が徐々に見えてきます。
この動きを簡単に実装するために、Line Attackは以下のような画面構成をしています。
青い枠が、ディスプレイ全体です。
赤い枠が、ゲームのメイン画面である6x6のマスです。
実は、ゲームの画面は見えている6x6のマス目だけで構成されているのではなく、上記のようなディスプレイの大きさをはみ出す18x18のマス目で構成されています。
そして、その中央の6x6の部分のみをゲームのメイン部分の画面として見せています。
なぜこのような画面構成になっているのでしょうか。
赤枠の中のaという文字が入ったマスを、右に動かすこととして説明します。
aのマスを少し右に動かすと、aのマスは赤い枠内、すなわちゲームのメイン画面から消えてしまいます。
そして、以下のように赤い枠の左端からaのマスが現れます。
もしメイン画面を6x6のマスのみで実装し、この部分の画像しか用意していなかったときは、aのマスを半分だけ右に動かすとその列のいちばん左側、すなわち赤い線の左端から半マス分画像が以下のように表示されなくなってしまいます。
このときに、表示されない部分にaの右半分の画像を描画すれば、赤い枠の左端と右端がつながっているように見せることができます。
例えばaの部分を5マス分左に動かしたときは、6x6のマスで実装した時はaより右側の5マス分が黒くなりますので、その部分を補完して描画しないといけません。
補完して描画してもよいですが、それより簡単な方法として18x18のマスをつくり、赤枠の中のaの部分の画像を上下左右にそれぞれ6マス離れたところに常に描画しておく方法があります。
今回は、この方法を採用しています。
この方法の場合は、水平方向の移動のときは横に連なっている18個のマスの画像を同時に動かすことで、黒い部分が出てこなくなります。
[XAMLでの構成]
XAMLでは、この18x18のマスをMainPanelという名称でMainPage.xamlに定義しています。
MainPanelはGridコントロールとして実装し、その中を18x18に均等に分割しています。
ここでは、仮にサイズを600の3倍の縦横1800としています。
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="MainPanel" Height="1800" Width="1800" Background="Black" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<Image Name="TopImage" Source="Assets/back1.png" Stretch="UniformToFill" />
<Image Name="BottomImage" Source="Assets/back1.png" Stretch="UniformToFill"/>
<Image Name="LeftImage" Source="Assets/back1.png" Stretch="UniformToFill"/>
<Image Name="RightImage" Source="Assets/back1.png" Stretch="UniformToFill"/>
</Grid>
</Page>
MainPanelの定義のあとに、Imageタグが4つ並んでいます。
これらは、赤い枠の外側の部分を見えなくするように、隠すためのイメージです。
Imageに濃い青色の画像ファイルを設定し、それを見せたくない場所の前面に置いています。
[画面サイズに合わせた調整]
Windows 8は、さまざまな解像度のモニターを使用できます。
アプリも、そのような解像度に対応させる必要があります。
さらに、モニターを横長の状態だけでなく、縦長の状態で表示させるときもあります。
スナップへの対応も忘れてはいけません。
これらのことにすべて対応させるのは、結構大変です。
Line Attackでは、必ず画面の中央にゲーム画面を配置することで、どのような解像度にも対応できるようにしています。
以下のPage_SizeChanged_1というイベントハンドラーは、画面のサイズが変更されたときに呼ばれるイベントハンドラーです。
アプリの起動直後にも呼ばれます。
private void Page_SizeChanged_1(object sender, SizeChangedEventArgs e)
{
double pageWidth, pageHeight;
int panelX, panelY, panelWidth, panelHeight;
pageWidth = GamePage.ActualWidth;
pageHeight = GamePage.ActualHeight;
if (pageWidth > pageHeight)
{
// landscape
panelHeight = (int)(pageHeight * 600 / 768);
panelWidth = panelHeight;
}
else
{
// portrait
panelWidth = (int)(pageWidth * 600 / 768);
panelHeight = panelWidth;
}
panelX = ((int)pageWidth - panelWidth) / 2;
panelY = ((int)pageHeight - panelHeight) / 2;
MainPanel.Width = panelWidth * 3;
MainPanel.Height = panelHeight * 3;
Thickness margin = new Thickness();
margin.Top = 0;
margin.Left = 0;
margin.Bottom = pageHeight - panelY;
margin.Right = 0;
TopImage.Margin = margin;
margin.Top = pageHeight - panelY;
margin.Left = 0;
margin.Bottom = 0;
margin.Right = 0;
BottomImage.Margin = margin;
margin.Top = panelY;
margin.Left = 0;
margin.Bottom = panelY;
margin.Right = pageWidth - panelX;
LeftImage.Margin = margin;
margin.Top = panelY;
margin.Left = pageWidth - panelX;
margin.Bottom = panelY;
margin.Right = 0;
RightImage.Margin = margin;
}
このイベントハンドラーでは、大きく次の2つのことと実装しています。
- ゲームのメイン部分の画面の大きさを決める
- メイン画面以外のところを隠すためのImageの位置と大きさを決める
メイン部分の画面の大きさは、解像度が1366x768のときに600x600になるように計算しています。
panelWidthとpanelHeightにメイン画面のサイズが入ります。
それ以外の解像度のときには、縦と横の短いほうの600/768のサイズになるようにしています。
横長画面の場合は、ちょうど上と下に少しずつ余白を持たせるような感じです。
XAMLで定義していたMainPanelというGridコントロールは、縦、横それぞれメイン画面の3倍に設定し、メイン画面が9つ入る大きさになっています。
marginというローカルフィールドを設定しているところから下は、上下左右のImageコントロールの座標と大きさを設定しています。
これらのImageによって、MainPanel上のメイン部分以外の場所を隠しています。
[MainPanelへの画像の設定]
XAMLで定義しているMainPanelは、縦横をそれぞれ18分割しているだけでその中には何も定義されていません。
宝石などの画像を表示させるために、縦横18分割している部分にImageコントロールを配置し、画像を表示できる準備を以下のInitilizeMainPanelメソッドで行っています。
private void InitializeMainPanel()
{
int x, y;
double width;
//Thickness margin;
width = MainPanel.Width / MaxColumn;
for (y = 0; y < MaxColumn * 3; y++)
{
for (x = 0; x < MaxColumn * 3; x++)
{
Piece[x, y] = new Image();
Piece[x, y].Stretch = Stretch.Fill;
//margin = new Thickness(x * width, y * width, 0, 0);
//Piece[x, y].Margin = margin;
MainPanel.Children.Add(Piece[x, y]);
Grid.SetColumn(Piece[x, y], x);
Grid.SetRow(Piece[x, y], y);
}
}
}
この中で使用しているPieceというImageコントロールは、以下のように定義されています。
public Image[,] Piece = new Image[30, 30];
結局LineAttackでは6x6のマスしか使用していませんが、作り始めたときは途中のステージから10x10のマスを使用することを考えていたため、この段階では30x30の2次元配列として定義しています。
コメントアウトしている部分は無視してください。
Imageコントロールには、まだ画像を定義していません。
画像は、別のメソッドで定義しています。
このメソッドでは、18x18に分けたひとつひとつのグリッドに、それぞれ1つのPieceというImageコントロールを貼り付けています。
[ステージのデータと画面への描画]
1つのステージのデータの初期値は、StageDataというint型の3次元配列に入れています。
3次元配列に直接データを入れてしまってもいいのですが、パッと見にわかりやすいようにデータを以下のように文字列で表現しています。
// Stage 1
stage = 1;
lineData[0] = "++++++";
lineData[1] = "++++++";
lineData[2] = "++11++";
lineData[3] = "++22++";
lineData[4] = "++++++";
lineData[5] = "++++++";
LineDataToStageData(stage, lineData);
lineDataというフィールドに代入している + は、宝石がなく十字線のみが表示されている場所です。
1、2という数字は、1が水色の宝石、2がオレンジの宝石というように、宝石の色に対応しています。
このように文字列でステージのデータを定義して、LineDataToStageDataというメソッドでStageDataという3次元配列に変換しています。
このStageDataをもとに、画面の表示を行うのがSetPanelメソッドです。
private void SetPanel(int stage)
{
int x, y;
for (y = 0; y < MaxColumn; y++)
{
for (x = 0; x < MaxColumn; x++)
{
Piece[x + MaxColumn * 1, y + MaxColumn * 0].Source = PCBitmapImage[StageData[stage, x, y]];
Piece[x + MaxColumn * 0, y + MaxColumn * 1].Source = PCBitmapImage[StageData[stage, x, y]];
Piece[x + MaxColumn * 1, y + MaxColumn * 1].Source = PCBitmapImage[StageData[stage, x, y]];
Piece[x + MaxColumn * 2, y + MaxColumn * 1].Source = PCBitmapImage[StageData[stage, x, y]];
Piece[x + MaxColumn * 1, y + MaxColumn * 2].Source = PCBitmapImage[StageData[stage, x, y]];
}
}
}
ここではStageDataの値にしたがって、MainPanel上に配置したPieceというImageコントロールに画像データをセットしています。
画像データはメイン部分のみではなく、その上下左右に6つ離れた位置にも同じ画像データを配置しています。
しかし、それらの画像データは別のImageコントロールによって隠されているため、見えません。
ここまで実装している今回のプロジェクトを実行すると、このようになります。
[前後の記事]
第0回 開発秘話と画像の用意
第2回 宝石を動かす
マイクロソフト
田中達彦