終于來到了這里,廢話不多說需忿,開搞诅炉!
界面實現(xiàn)
想怎么畫就怎么畫蜡歹,為所欲為之為所欲為。
<Window x:Class="WpfApp.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:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="400" KeyDown="Window_KeyDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="400*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Viewbox Grid.RowSpan="2" >
<Label Content="2048" />
</Viewbox>
<Viewbox Grid.Row="0" Grid.Column="1" VerticalAlignment="Bottom">
<TextBlock Text=" Score "/>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="1" VerticalAlignment="Top">
<TextBlock x:Name="Score" Text=" 0 " />
</Viewbox>
<Viewbox Grid.Row="0" Grid.Column="2" VerticalAlignment="Bottom">
<TextBlock Text=" Best " />
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="2" VerticalAlignment="Top">
<TextBlock x:Name="Best" Text=" 0 " />
</Viewbox>
<Grid x:Name="grid" Grid.Row="2" Grid.ColumnSpan="3">
</Grid>
</Grid>
</Window>
有個小細節(jié)涕烧,因為中間使用了 Viewbox 來顯示分數(shù)月而,Viewbox會根據(jù)文本長度自動調(diào)節(jié)字體的大小,如下议纯。
這里需要將字符串統(tǒng)一設置成固定的長度父款,當小于這個長度時兩邊補充些空格,讓分數(shù)顯示更統(tǒng)一一些瞻凤。
為String增加一個擴展方法憨攒,實現(xiàn)這個功能。
這時為Score賦值時可以直接寫為
Score.Text = g.Score.ToString().FormatForLength(5);
阀参,
public static string FormatForLength(this string _string,int len)
{
string res = _string.ToString();
int count = len - res.Length;
string temp = "";
for (int i = 0; i < count; i++)
{
temp += " ";
}
res = temp + res + temp;
return res;
}
動畫邏輯
2048的游戲里肝集,當玩家無操作的時候,無動畫结笨,玩家操作一次播一次動畫包晰,這種情況可以很簡單的將動畫部分抽離出來。
開始游戲
>>>顯示當前游戲狀態(tài)
>>>玩家操作
>>>生成動畫并播放
>>>顯示當前游戲狀態(tài)
>>>玩家操作
>>>生成動畫并播放
······
那么這里可以簡單的將
當前游戲狀態(tài)
炕吸,和生成動畫
抽象出來伐憾,不封裝太深,整個接口吧赫模。
interface IWpf2048UI
{
//為了將每個方塊和它的動畫關(guān)聯(lián)起來树肃,建立一個NameScope,
//生成方塊時將方塊加進去瀑罗,生成動畫時從中拿出來關(guān)聯(lián)
NameScope NameScope { get; set; }
//動畫的持續(xù)時間
Duration Duration { get; set; }
//生成游戲畫板(當前游戲狀態(tài))
Panel PanelFactory(G2048 g);
//生成游戲動畫(生成動畫并關(guān)聯(lián)到畫板中)
Storyboard StoryboardFactory(Dictionary<BizLogic.Point, BizLogic.Point> moved, Size blockSize);
}
交互邏輯
有了上面的接口胸嘴,交互邏輯如下。
有個小細節(jié)斩祭,當游戲動畫正在播放時劣像,玩家又一次按下了方向鍵可能會導致此次動畫播放有問題。
這里可以加上一把鎖??摧玫,當開始播放動畫時加鎖耳奕,加鎖期間不響應玩家操作,播放完畢后開鎖诬像。具體實現(xiàn)如下bool operationLock
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
init();
}
G2048 g;
IWpf2048UI iWpf2048UI;
bool operationLock = false;
int Row;
int Colum;
/// <summary>
/// 初始化
/// </summary>
private void init()
{
Row = 5;
Colum = 5;
g = new G2048(Row,Colum);
this.SetValue(NameScope.NameScopeProperty, iWpf2048UI.NameScope);
this.grid.Children.Add(iWpf2048UI.PanelFactory(g));
}
/// <summary>
/// 按鍵事件
/// </summary>
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (operationLock)
{
return;
}
G2048.Direction direction;
switch (e.Key)
{
case Key.Up: direction = G2048.Direction.Up; break;
case Key.Down: direction = G2048.Direction.Down; break;
case Key.Left: direction = G2048.Direction.Left; break;
case Key.Right: direction = G2048.Direction.Right; break;
default:
return;
}
if (!g.Operate(direction))
{
return;
}
playAnim();
}
/// <summary>
/// 播放動畫
/// </summary>
private void playAnim()
{
Console.WriteLine(g.ToString());
operationLock = true;
Storyboard storyboard = iWpf2048UI.StoryboardFactory(g.Moved, new Size(grid.ActualWidth / Row, grid.ActualHeight / Colum));
storyboard.Completed += storyboard_Completed;
storyboard.Begin(this);
}
/// <summary>
/// 動畫結(jié)束
/// </summary>
void storyboard_Completed(object sender, EventArgs e)
{
grid.Children.Clear();
grid.Children.Add(iWpf2048UI.PanelFactory(g));
Score.Text = g.Score.ToString().FormatForLength(5);
operationLock = false;
}
接口的實現(xiàn)
接下來實現(xiàn)一下動畫接口
IWpf2048UI
屋群,因為將生成方塊和生成方塊的動畫分割開了,需要一個映射關(guān)系NameScope
坏挠,統(tǒng)一將生成的方塊按坐標命名芍躏,存入NameScope
,生成動畫時按命名附加降狠。
這里統(tǒng)一下方塊的命名方式对竣,按其坐標命名庇楞,即
public static string Sign(this BizLogic.Point point)
{
return "X"+point.X +"Y"+ point.Y;
}
以下是默認的動畫呈現(xiàn)方式
Wpf2048UIDefault
class Wpf2048UIDefault : IWpf2048UI
{
public NameScope NameScope { get; set; } = new NameScope();
public Duration Duration { get; set; } = new Duration(TimeSpan.FromMilliseconds(500));
public Panel PanelFactory(G2048 g)
{
Grid tempGrid = new Grid();
for (int i = 0; i < g.Row; i++)
{
tempGrid.RowDefinitions.Add(new RowDefinition());
}
for (int j = 0; j < g.Colum; j++)
{
tempGrid.ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 0; i < g.Row; i++)
{
for (int j = 0; j < g.Colum; j++)
{
if (g.Map[i, j] == 0)
{
continue;
}
FrameworkElement element = this.FrameworkElementFactory(new BizLogic.Point(i, j), g.Map[i, j]);
element.SetValue(Grid.RowProperty, i);
element.SetValue(Grid.ColumnProperty, j);
tempGrid.Children.Add(element);
}
}
return tempGrid;
}
private FrameworkElement FrameworkElementFactory(BizLogic.Point point,int value)
{
Border border = new Border();
Transform transform = new TranslateTransform();
border.RenderTransform = transform;
NameScope.Add(point.Sign(),transform);
Viewbox viewbox = new Viewbox();
TextBlock block = new TextBlock();
if (value != 0)
{
block.Text = value.ToString().FormatForLength(3);
block.VerticalAlignment = VerticalAlignment.Center;
block.HorizontalAlignment = HorizontalAlignment.Center;
block.TextAlignment = TextAlignment.Center;
}
border.Margin = new Thickness(3);
viewbox.Child = block;
border.Child = viewbox;
border.Background = new SolidColorBrush(getColorByValue(value));
return border;
}
public virtual Storyboard StoryboardFactory(Dictionary<BizLogic.Point, BizLogic.Point> Moved,Size blockSize)
{
if (Moved.Count == 0)
{
return null;
}
Storyboard storyboard = new Storyboard();
DependencyProperty dp;
double len;
//如果是橫向
if (Moved.First().Key.X== Moved.First().Value.X)
{
dp = TranslateTransform.XProperty;
len = blockSize.Width;
}
else
{
dp = TranslateTransform.YProperty;
len= blockSize.Height;
}
foreach (var move in Moved)
{
double lenTemp = (move.Value.X - move.Key.X + move.Value.Y - move.Key.Y) * len;
//獲取一個移動動畫
DoubleAnimation daTemp = DoubleAnimationFactory(lenTemp);
//使指定的動畫的UI載體
Storyboard.SetTargetName(daTemp, move.Key.Sign());
//使動畫與UI載體的屬性相關(guān)聯(lián)
Storyboard.SetTargetProperty(daTemp, new PropertyPath(dp));
//指定場景的時間,并把各個對像的動畫添加到場景里面
storyboard.Children.Add(daTemp);
}
storyboard.Duration = Duration;
storyboard.Completed += storyboard_Completed;
return storyboard;
}
void storyboard_Completed(object sender, EventArgs e)
{
NameScope.Clear();
}
/// <summary>
/// 根據(jù)數(shù)值返回不同的顯示內(nèi)容
/// </summary>
protected virtual string getStringByValue(int value)
{
return value.ToString().FormatForLength(3);
}
/// <summary>
/// 根據(jù)數(shù)值返回不同的顏色
/// </summary>
protected virtual Color getColorByValue(int value)
{
Color color = Colors.BurlyWood;
switch (value)
{
//返回不同的顏色
}
return color;
}
測試下
將接口實現(xiàn)放進去。
接下來就是見證奇跡的時刻柏肪,F(xiàn)5走起
iWpf2048UI = new Wpf2048UIDefault
{
Duration = new Duration(TimeSpan.FromMilliseconds(500))
};