Editeur de niveau : l’approche WrapPanel

Suite à un article très intéressant de David Rousset sur la création d’un éditeur de niveau d’un jeu WP7, je vous propose dans ce post d’aborder une approche différente.
La technique de David consiste, à partir d’une ListBox, à remplacer l’ItemsPanelTemplate par un Grid. Le Grid est un panel puissant mais difficile à manipuler à l’aide du Binding. J’ai pour ma part utilisé un WrapPanel associé à une classe Screen représentant l’écran de l’éditeur.

La classe Screen est constituée d’un tableau de classe Tile qui représente chacune un block de l’écran.
Pour simplifier notre approche chaque Tile contient une propriété unique « Background ». Cette propriété une fois fixée changera la couleur de fond de celle-ci.
On aurait aussi pu, comme David, ajouter une propriété ImageSource pour afficher des petits diamants, blocks de pierre…

public class Tile : NotificationBase
{
    /// <summary>
    /// Nom de la propriété Background
    /// </summary>

    public const string BackgroundPropertyName = "Background";

    /// <summary>
    /// propriété Background : Fond
    /// </summary>

    public Brush Background
    {
        get
        {
            return this._Background;
        }

        set
        {
            if (this._Background != value)
            {
                this._Background = value;
                this.RaisePropertyChanged(BackgroundPropertyName);
            }
        }
    }

    private Brush _Background = null;
}

Pour constituer notre tableau de Tile dans la classe Screen on a besoin de l’initialiser en précisant le nombre de Tile à l’horizontale et à la Verticale.

public void Initialize(int width, int height)
{
    this.TilesWidth = width;
    this.TilesHeight = height;

    Tile[] tiles = new Tile[width * height];

    for (int index = 0; index < tiles.Length; index++ )
    {
        tiles[index] = new Tile();
    }

    this.Tiles = tiles;
}

Afin d’accéder plus facilement aux tiles depuis Screen, on lui greffe un Indexer :

public Tile this[int x, int y]
{
    get
    {
        int index = y * this.TilesWidth + x;

        if (index > -1 && index < this.Tiles.Length)
        {
            return this.Tiles[index];
        }

        return null;
    }
}

On peut, alors, l’utiliser comme suit :

Tile tile = screen[0,0];
tile.Background = new SolidColor( Colors.Orange );
screen[0,2].Background = new SolidColor( Colors.Purple );

L’astuce consiste maintenant à Binder Screen sur le WrapPanel de la ListBox.
Chaque tile sera représenté graphiquement dans l’itemTemplate du ListBox par un TileControl.

L’implémentation du WrapPanel dans le ListBox s’effectue comme suit :


<ListBox x:Name="Editor" ItemsSource="{Binding Screen.Tiles}">

         ...	

         <ListBox.ItemsPanel>
		<ItemsPanelTemplate>
			<toolkit:WrapPanel ItemWidth="32" ItemHeight="32" Orientation="Horizontal" Width="160" Height="320" Background="Red" />
		</ItemsPanelTemplate>
	</ListBox.ItemsPanel>

	<ListBox.ItemTemplate>
		<DataTemplate>
			<my:TileControl Tile="{Binding}" Width="32" Height="32" />
		</DataTemplate>
	</ListBox.ItemTemplate>

</ListBox>

Le WrapPanel, rappelons le, permet d’aligner des controles, les uns après les autres.
Dès que la taille du WrapPanel est atteinte par les controles, le wrappanel ajoute les controles suivants en retournant à la ligne (car il est orienté Horizontalement).

La taille de notre Wrappanel est donc determinante pour que la représentation de la classe Screen s’effectue correctement.
La formule pour determiner la taille du WrapPanel est la suivante :

longueur du WrapPanel en pixel = longueur du Block en pixel * nombre de block en longueur;
hauteur du WrapPanel en pixel = hauteur du Block en pixel * nombre de block en hauteur;

soit :

this.Width = this.ItemWidth * this.TilesWidth;
this.Height = this.ItemHeight * this.TilesHeight;

Puisque Screen contient maintenant l’intégralité des informations de l’editeur autant binder les tailles de l’editeur sur Screen.

<ListBox x:Name="Editor" ItemsSource="{Binding Screen.Tiles}">

	<ListBox.ItemsPanel>
		<ItemsPanelTemplate>
			<toolkit:WrapPanel ItemWidth="{Binding Screen.ItemWidth}" ItemHeight="{Binding Screen.ItemHeight}" Orientation="Horizontal" Width="{Binding Screen.Width}" Height="{Binding Screen.Height}" Background="Red" />
		</ItemsPanelTemplate>
	</ListBox.ItemsPanel>

	<ListBox.ItemTemplate>
		<DataTemplate>
			<my:TileControl Tile="{Binding}" Width="{Binding DataContext.Screen.ItemWidth, ElementName=Editor}" Height="{Binding DataContext.Screen.ItemHeight, ElementName=Editor}" />
		</DataTemplate>
	</ListBox.ItemTemplate>

</ListBox>

Voila l’éditeur est en place !

Vous trouverez la source du projet TileEditor ici.

Tags: , , , , ,

Leave a Comment