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

 

未完待续…

posted on 2010-09-19 18:40  海毛虫  阅读(617)  评论(0编辑  收藏  举报