使用Silverlight制作简单的小游戏—Jewellery (Part 2)

上一篇:使用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中的动画了,使它真的可以显示出来,让这段逻辑发挥作用。

 

未完待续…

你可能感兴趣的:(silverlight)