第一眼往往能起到决定性作用,这不仅是对人来说。优秀的游戏同样需要一个华丽而盛大的开场,以中国式古风古韵之柔情传承,配以卷轴展开壮丽山河之气势磅礴,云中漫步于旅仙境渐入开场,相信如此美好的初体验定能捕获无数玩家的心:华美的开端能不让人雀跃祈盼后续之旷世奇章吗?
实现开场卷轴的方案大致三类:随机移动、往复移动及无线延展。其中随机移动即宽大的背景在游戏视窗中任意移动,碰撞到边缘时再向随机方向弹回;往复移动即背景画卷以横向或纵向来回移动;而本节我要给大家展示的是无限延展的开场画卷效果。
无限延展运动的画卷给人最大的震撼就是恢弘而无穷无尽,我们需要事先准备一副可左右对合拼接且尺寸大于游戏最大可能视口的图片。作为可全屏操作的Silverlight网游来说,图片的宽度至少得大于2000px以满足目前几乎所有客户端分辨率。至于原理很简单,两张一样的画卷图片紧挨着向同一方向连续移动,当第一张超出游戏屏幕时立刻再返回到第二张的末尾继续跟随移动,由此自然而然便形成了无限延展而连贯的动态画卷背景效果:
当然,仅仅是空灵的卷轴似乎还无法体现宏大的游戏开场气势,我们还可以赋予它灿烂的阳光、飘渺的浮石、成群的飞燕等额外动画,外加整体隐约镂空的云层,然后将单独的背景卷轴搭配上这些动画最终封装成LoginBackground控件,由此开场卷轴便大功告成:
/// 背景的顺序
/// </summary>
public enum BackgourndIndex {
First = 0,
Second = 1,
}
/// <summary>
/// 登陆部分的动画背景
/// </summary>
public sealed class LoginBackground : EntityObject {
AnimationBase sunnyAnimation = new AnimationBase() { Loop = true, Z = 2, };
AnimationBase pumiceAnimation = new AnimationBase() { Loop = true, Z = 2, };
AnimationBase flyBirds = new AnimationBase() { Loop = true, Z = 2, };
AnimationBase flowLightAnimation = new AnimationBase() { Loop = true, Z = 2, };
public LoginBackground() {
Source = GlobalMethod.GetImage("UI/ScrollPicture.jpg", UriType.Project);
}
/// <summary>
/// 播放完一副的时间(毫秒)
/// </summary>
public static int Interval = 20000;
Storyboard storyboard;
BackgourndIndex index;
/// <summary>
/// 开始卷轴运动
/// </summary>
public void Run(BackgourndIndex index, double start, double end) {
this.index = index;
storyboard = new Storyboard();
storyboard.Children.Add(GlobalMethod.CreateDoubleAnimation(this, "(Canvas.Left)", start, end, TimeSpan.FromMilliseconds(index == BackgourndIndex.First ? Interval:Interval*2), null));
storyboard.Completed += new EventHandler(storyboard_Completed);
storyboard.Begin();
}
void storyboard_Completed(object sender, EventArgs e) {
Storyboard storyboard = sender as Storyboard;
storyboard.Completed -= storyboard_Completed;
storyboard = new Storyboard();
storyboard.Children.Add(GlobalMethod.CreateDoubleAnimation(this, "(Canvas.Left)", RealWidth - 6, -RealWidth, TimeSpan.FromMilliseconds(Interval * 2), null));
storyboard.Completed += storyboard_Completed;
storyboard.Begin();
}
/// <summary>
/// 显示内部动画
/// </summary>
public void DisplayAnimation() {
CreateAnimation(sunnyAnimation, 14, new Point(680, 0));
CreateAnimation(pumiceAnimation, 67, new Point(810, 25));
CreateAnimation(flyBirds, 69, new Point(570, 410));
CreateAnimation(flowLightAnimation, 68, new Point(1760, 105));
}
void CreateAnimation(AnimationBase animation, int code, Point position) {
animation.Code = code;
animation.Position = position;
EventHandler handler = null;
animation.Disposed += handler = delegate {
animation.Disposed -= handler;
this.Children.Remove(animation);
};
this.Children.Add(animation);
animation.HeartStart();
}
public override void Dispose(object sender, EventArgs e) {
sunnyAnimation.Dispose(sunnyAnimation,null);
pumiceAnimation.Dispose(pumiceAnimation, null);
flyBirds.Dispose(flyBirds, null);
flowLightAnimation.Dispose(flowLightAnimation, null);
storyboard.Stop();
base.Dispose(sender, e);
}
}
画卷展开后,接下来便是进入游戏正题。为了提高用户体验,我将所有窗体都设计为继承自WindowsBase附带透明拖动、渐显切入模式的九宫格控件,每个窗体所需UI素材部分不同(记录在Login.xml配置文件中),因此按照动态加载原则,每此弹出新的窗口前都先分析并下载该窗口所需资源,完成后再以动画形式切入,其中配以伪模态Loading环,最终的效果连贯而不失精致(以语言选择窗体LanguageChoicer为例):
/// 游戏语言选择
/// </summary>
public sealed class LanguageChoicer : WindowBase {
/// <summary>
/// 提交
/// </summary>
public event MouseButtonEventHandler Submit;
public LanguageChoicer() {
this.Loaded += (s, e) => {
QueueParallelDownloader queueDownloader = new QueueParallelDownloader();
queueDownloader.DownloadCompleted += new OpenReadCompletedEventHandler(queueDownloader_OpenReadCompleted);
queueDownloader.OpenReadAsync(GetResourceList("LanguageChoicer"), null, false, 300);
};
}
void queueDownloader_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) {
QueueParallelDownloader queueDownloader = sender as QueueParallelDownloader;
queueDownloader.DownloadCompleted -= queueDownloader_OpenReadCompleted;
Canvas body = new Canvas() {
Width = 254,
Height = 200,
Background = new ImageBrush() { ImageSource = GlobalMethod.GetImage("UI/105.png", UriType.Web) }
};
RadioButton[] languages = new RadioButton[5];
languages[0] = new RadioButton() {
Tag = "zh-cn",
Content = new TextBlock() {
Text = "简体中文",
Style = Application.Current.Resources["TextStyle0"] as Style,
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromArgb(255, 239, 255, 208)),
},
FontSize = 14,
GroupName = "Language",
IsChecked = true,
};
languages[1] = new RadioButton() {
Tag = "zh-tw",
Content = new TextBlock() {
Text = "繁體中文",
Style = Application.Current.Resources["TextStyle0"] as Style,
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromArgb(255, 239, 255, 208)),
},
FontSize = 14,
GroupName = "Language",
};
languages[2] = new RadioButton() {
Tag = "us",
Content = new TextBlock() {
Text = "English",
Style = Application.Current.Resources["TextStyle0"] as Style,
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromArgb(255, 239, 255, 208)),
},
FontSize = 14,
GroupName = "Language"
};
languages[3] = new RadioButton() {
Tag = "jp",
Content = new TextBlock() {
Text = "日本語",
Style = Application.Current.Resources["TextStyle0"] as Style,
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromArgb(255, 239, 255, 208)),
},
FontSize = 14,
GroupName = "Language"
};
languages[4] = new RadioButton() {
Tag = "kr",
Content = new TextBlock() {
Text = "한국의",
Style = Application.Current.Resources["TextStyle0"] as Style,
TextWrapping = TextWrapping.Wrap,
Foreground = new SolidColorBrush(Color.FromArgb(255, 239, 255, 208)),
},
FontSize = 14,
GroupName = "Language"
};
for (int i = 0; i <= languages.GetUpperBound(0); i++) {
body.Children.Add(languages[i]);
Canvas.SetLeft(languages[i], 86); Canvas.SetTop(languages[i], -5 + i * 35);
}
ImageButton closer = new ImageButton() {
TotalWidth = 93,
TotalHeight = 27,
Background = new ImageBrush() { ImageSource = GlobalMethod.GetImage("UI/106.png", UriType.Web), Stretch = Stretch.None },
Text = "提交(OK)",
TextStyle = Application.Current.Resources["TextStyle1"] as Style,
TextSize = 14,
TextHasEffect = true,
TextEffectColor = Colors.Black,
TextBrush = new SolidColorBrush(Colors.LightGray),
TextHoverBrush = new SolidColorBrush(Colors.White),
};
body.Children.Add(closer); Canvas.SetLeft(closer, 73); Canvas.SetTop(closer, 167);
closer.Click += (s1, e1) => {
//LoginManager.loading.Show();
for (int i = 0; i <= languages.GetUpperBound(0); i++) {
if (languages[i].IsChecked.Value) {
ParallelDownloader downloader = new ParallelDownloader();
OpenReadCompletedEventHandler handler = null;
downloader.OpenReadCompleted += handler = (s2, e2) => {
Infos["Language"] = XElement.Load(e2.Result as Stream);
if (Submit != null) { Submit(this, e1); }
//LoginManager.loading.Hide();
};
downloader.OpenReadAsync(string.Format(GlobalMethod.WebPath("Language/{0}.xml"), languages[i].Tag.ToString()), DownloadPriority.Highest, null, false, 0);
break;
}
}
};
StyleBox styleBox = new StyleBox() {
HeadHeight = 60,
BodyHeight = 200,
FootHeight = 15,
NorthCenterWidth = 74,
CenterWidth = 254,
CenterEdgeWidth = 30,
SouthCenterWidth = 228,
NorthEdgeImageSource = GlobalMethod.GetImage("UI/100.png", UriType.Web),
NorthImageSource = GlobalMethod.GetImage("UI/101.png", UriType.Web),
CenterEdgeImageSource = GlobalMethod.GetImage("UI/102.png", UriType.Web),
SouthEdgeImageSource = GlobalMethod.GetImage("UI/103.png", UriType.Web),
SouthImageSource = GlobalMethod.GetImage("UI/104.png", UriType.Web),
CenterContent = body,
};
styleBox.HeadText.Foreground = new SolidColorBrush(Colors.White);
styleBox.HeadText.Text = "选 择 语 言";
styleBox.HeadText.FontSize = 14;
styleBox.HeadText.Margin = new Thickness(0, -20, 0, 0);
Body = styleBox;
base.OnResourceReady(this, e);
}
}
额外需要说明一下,作为Silverlight网页游戏来说,多国语言(本地化)实现起来相当简单。游戏开端当玩家选择一种语言后,按照第十七节的方式首先将该语言包下载到本地并缓存,游戏中一切需要显示文字的地方都将从这里获取并解析。当然,特别要注意一些细节问题比如同一单词不同语言下长度不同,可能导致界面变形或超出范围,配合上一些偏移和字体尺寸参数可轻松解决。
下一节我会为大家继续讲解游戏登陆部分,其中的WCF+数据库操作将是重头大戏。相信《梦隋唐Online》这部强势作品的发布势将掀起新一轮Silverlight网游革命让世界仰望中国页游技术质的飞跃!
本系列源码请到目录中下载
在线演示地址:http://silverfuture.cn