上一篇:使用Silverlight制作简单的小游戏—Jewellery (Part 1)
从Jewels开始编写代码。
首先是Initialize方法。在Start之后,我们要先填充Jewel数组,并显示在Canvas中。这里有一点要注意的是,初始化的过程,要保证最终的显示中,不会存在可被消除的Jewel。由于Jewel的种类是随机生成的,最简单的做法就是,如果已经在当前位置的行/列中,紧邻位置存在相同种类的Jewel,则随机生成的种类不能是这个种类。代码如下:
private void Initialize() { for (int i = 0; i < this.RowCount; ++i) for (int j = 0; j < this.ColumnCount; ++j) { this.JewelMap[i, j] = this.CreateJewel(i, j); } this.FillMap(this.KindCount); } private T CreateJewel(int x, int y) { T jewel = new T(); jewel.IndexX = x; jewel.IndexY = y; jewel.Width = this.JewelWidth; jewel.Height = this.JewelHeight; jewel.Click += new EventHandler(jewel_Click); return jewel; } private void FillMap(int kindCount) { List<int> unallowed = new List<int>(); for (int i = 0; i < this.RowCount; ++i) for (int j = 0; j < this.ColumnCount; ++j) { T jewel = this.JewelMap[i, j]; if (i > 1 && this.JewelMap[i - 1, j].Kind == this.JewelMap[i - 2, j].Kind) { unallowed.Add(this.JewelMap[i - 1, j].Kind); } if (j > 1 && this.JewelMap[i, j - 1].Kind == this.JewelMap[i, j - 2].Kind) { unallowed.Add(this.JewelMap[i, j - 1].Kind); } int kind = rand.Next(kindCount); while (unallowed.Contains(kind)) kind = rand.Next(kindCount); jewel.Kind = kind; unallowed.Clear(); } }
每个Jewel也需要初始化,为了可以让每个Jewel显示出来,需要为JewelBase增加一个Initialize的虚方法,并传入当前的Canvas。所以要在Jewels初始化之后,要调用每个Jewel的Initialize方法。具体内容,在实现Jewel时,我们再说。
CreateJewel方法中,我们为每个Jewel的Click事件绑定一个统一的处理方法,用于接收用户点击/选择一个Jewel,这个是处理用户交互的关键。有了点击处理,接下来就是Select和Unselect,除了记录下来当前选择的Jewel,只要调用接口IJewelSelector就好,先跳过。这里主要要处理的是Exchange方法,以及在Exchange之后,TryToDestroy方法,这个算是游戏的核心吧。在说这个之前,我想先说明的是,无论是Move还是Destroy,这个过程肯定是一个动画显示的过程。在SL中,动画的显示一般是一个thread的问题。在动画显示结束后,再做接下来的处理,这样才能得到一个好的响应。所以,我们先做Exchange,并等待两个Jewel的Move动画结束,再来TryToDestroy。考虑到,Destroy也是一个动画,这里单独做了一个Class,用于注册Event,并等待所有Jewel响应结束。大致代码如下:
public delegate bool EventCondition(string eventName); public static class JewelEvent { // launch this method when a jewel finish its animation public static void Add(string name, JewelBase source) { } // get all jewels which has finished public static JewelBase[] GetSources(string name) { } public static T[] GetSources<T>(string name) where T : JewelBase { } public static void RegisterAndBegin(string name, int count, Action action) { RegisterAndBegin(name, (e) => GetSources(name).Length == count, action); } public static void RegisterAndBegin(string name, EventCondition condition, Action action) { RegisterAndBegin(name, condition, (Delegate)action); } // register a event, auto launch action depend on condition public static void RegisterAndBegin(string name, EventCondition condition, Delegate action, params object[] parameters) { } public static void Unregister(string name) { } public static bool HasRegistered(string name) { } public static void Begin(string name) { } public static void End(string name) { } public static void Reset(string name) { } }
我们可以这样使用它,来确保动画全部结束后,再来执行接下来的判断。
private void Exchange(T source, T target) { JewelEvent.RegisterAndBegin("Move", 2, this.ExchangeFinished); // launch JewelBase.MoveTo method } private void ExchangeFinished() { if (!this.TryToDestroy()) { // restore T[] jewels = JewelEvent.GetSources<T>(“Move”); this.Exchange(jewels[0], jewels[1]); } }
这便是Exchange方法最主要做的事情,调用两个Jewel中的MoveTo方法,使它们可以移向新的位置,并等待移动结束后,判断是否存在可以被销毁的Jewel,如果不存在,则恢复这两个Jewel的位置。
现在我们来看一下TryToDestroy这个方法。其实没什么好说的,就是先横向,再纵向的查找可以被销毁的Jewel,大于3个便是可以销毁的。这里要提到的,还是关于动画的显示问题。程序中要先遍历所有,找出每一个可以被销毁的Jewel,确保没有重复的,并存在List中,最后再调用Jewel中的Destroy方法。还是要用到刚才说的那个Event的类。大致代码如下:
private bool TryToDestroy() { List<T> jewels = new List<T>(); // check row for (int y = 0; y < this.ColumnCount; ++y) { // ... } // check column for (int x = 0; x < this.RowCount; ++x) { // ... } if (jewels.Count == 0) { return false; } // destroy JewelEvent.RegisterAndBegin("Destroy", jewels.Count, this.DestroyFinished); jewels.ForEach((e) => { e.Destroy(); this.JewelMap[e.IndexX, e.IndexY] = null; }); return true; } private void DestroyFinished() { T[] jewels = JewelEvent.GetSources<T>(JewelEventNames.Destroy); this.Fill(jewels); // fill empty location }
就差最后一个方法了,就是在Destroy之后,要做Jewel的填补工作,这样游戏才能一直玩下去。:) 还是遍历,我在这个方法中,从下向上遍历,遇到null的地方,我就记下来,然后继续往上走,直到找到一个Jewel后,调用这个Jewel的MoveTo方法,使它可以掉到消失的Jewel的位置上。如果没有找到,就把传进来的数组中的Jewel放到顶端,并重新初始化。这样做的好处是,不用再去新建一个Jewel,所有的Jewel实例都是重复应用的。
private void FillEmpty(T[] jewels) { List<T> sources = new List<T>(); List<int> targetXs = new List<int>(); // save new location x List<int> targetYs = new List<int>(); // save new location y for (int x = 0; x < this.RowCount; ++x) { // ... } if (sources.Count == 0) return; JewelEvent.RegisterAndBegin("Move", sources.Count, this.FillFinished); for (int i = 0; i < sources.Count; ++i) { this.MoveTo(sources[i], targetXs[i], targetYs[i]); } sources.Clear(); } private void FillFinished() { this.TryToDestroy(); // try to destroy again }
做完Fill,要记得再去TryToDestroy,因为新的填充,可能还会存在可消除的Jewel。连续消除,这也算是游戏中最炫的地方吧。:) 所以,这里是一个循环,直到没有可消除的Jewel为止。
好了,这就是核心的逻辑部分,基本上不涉及SL的东西。接下来我们要开始处理Jewel中的动画了,使它真的可以显示出来,让这段逻辑发挥作用。
未完待续…