游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享用Construct 2制作连线消除游戏的教程(3)

发布时间:2013-06-14 15:39:50 Tags:,,,,

作者:David Silverman

在之前的系列中我们介绍了设置一款连线消除游戏的基本原理,并执行了最初游戏元素,如砖块交换。而在本篇教程中我们将开始检查玩家何时能够创造匹配砖块。(请点击此处阅读本系列第124篇

1.检查匹配

现在,我们只能执行基本的匹配系统版本,并致力于寻找何时匹配存在并消除相匹配的砖块。在之后的文章中我们将继续开发并完善该系统。

注:你需要先掌握递归函数的运行。这是一个独自调用的函数。递归函数也能够进行循环,但因为它们只能吸取并返回变量,所以它们的作用比循环还多。

与之前的教程一样,我想要讨论系统该如何运行,然后尝试着去创建它。

匹配系统将经由每个砖块对象例子而迭代。

对于每个砖块,它将根据递归函数而通过每个砖块的颜色和位置,着眼于水平或垂直邻接砖块,并决定它们是否属于相同颜色。

如果发现相匹配的颜色,它将基于新砖块(而不是最初的那一个)的位置和颜色再次调用函数。

这种过程将持续下去,直到它发现不再有匹配对象。这时候,它将检查共发现了多少匹配对象。

如果它发现了3个以上的匹配对象,它将把所有瞄准的砖块标记为Matched,就像我们在之前教程中将实例变量所标记的IsMatched;如果不这么做的话,它们便失去了意义。

最后,当检查了所有砖块后,函数将消除所有被当成匹配对象而做上标记的砖块。

首先,我们需要一个能够在每个砖块上进行迭代的事件。在我创建系统的方法中,它需要在砖块中迭代两次:一次是为了检查垂直匹配,另一次则是为了检查水平匹配。根据所执行的检查,它将使用不同函数去寻找匹配。

首先我们需要做的便是创造全局变量去追踪我们在任何迭代中所发现的匹配砖块数:

Global Variable        Name: “NumMatchesFound”         Type = Number         Value = 0

现在,让我们创造将在砖块中迭代的事件:

Event: Function > On function         Name: “FindMatches”         Sub-Event: System > For Each             Object: Block             Action: System > Set value                 NumMatchesFound = 1             Action: Function > Call function                 Name: “CheckMatchesX”                 Parameter 0: Block.X                 Parameter 1: Block.Y                 Parameter 2: Block.Color             Sub-Event: System > Compare Variable                 NumMatchesFound  >= 3                 Action: Block > Set Boolean                     IsMatched = True         Sub-Event: System > For Each             Object: Block             Action: System > Set value                 NumMatchesFound = 1             Action: Function > Call function                 Name: “CheckMatchesY”                 Parameter 0: Block.X                 Parameter 1: Block.Y                 Parameter 2: Block.Color             Sub-Event: System > Compare Variable                 NumMatchesFound  >= 3                 Action: Block > Set Boolean                     IsMatched = True         Sub-Event: Block > Is Boolean instance variable set             System > Wait                 Second = 0.1             Block > Destroy

你的代码看起来如下:

code(from tutsplus)

code(from tutsplus)

在这个事件中,我们将循环访问每个砖块,并将其传送到CheckMatchesX或CheckMatchesY中,该函数将检查邻接砖块是否相匹配。

为了将砖块传送到函数中,我们传递给函数3种不同的参数:

参数0是砖块X的位置

参数1是砖块Y的位置

参数2是颜色

当每个砖块被传送到其中一个函数中,而函数也完成了运行,它将检查NumMatchesFound去明确是否发现3个以上匹配转块,然后将这些砖块标记为Matched。

最后,每个被标记为Matched的砖块将在1秒钟后被消除。这种“等待”声明将能让游戏转变砖块的图像去传达它们是相互匹配的,并给予玩家注意到这种改变的时间。

(游戏邦注:尽管删除等待声明并不会影响游戏玩法,但是它却能够帮助玩家更轻松地理解匹配,并放慢游戏节奏让玩家可以更加清楚游戏的进行。)

2.两个检查函数

接下来我们需要创造CheckMatchesX和CheckMatchesY函数。这两个函数的作用与上述的迭代函数类似,即CheckMatchesX是用于检查水平匹配,而CheckMatchesY是用于检查垂直匹配。

水平检查

首先,让我们创建水平检查函数:

Event: Function > On function         Name: “CheckMatchesX”         Sub-Event:              Condition: Block > Compare X                 X = Function.Param(0) + (Block.Width+2)             Condition: Block > Compare Y                 Y = Function.Param(1)             Condition: Block > Compare instance variable                 Color = Function.Param(2)             Action: System > Add to                 Variable = NumBlocks                 Value = 1             Action: Function > Call function                 Name: “CheckMatchesX”                 Parameter 0: Function.Param(0) + (Block.Width+2)                 Parameter 1: Function.Param(1)                 Parameter 2: Function.Param(2)             Sub-Event: System > Compare Variable                 NumMatchesFound  >= 3                 Action: Block > Set Boolean                     IsMatched = True

你的代码如下:

code(from tutsplus)

code(from tutsplus)

所以,这一函数将做些什么?

首先,它将测试邻接砖块是否存在于我们通过的砖块左边。

当函数证实在邻接位置上存在一个砖块时,它便会检查它是否与我们通过的砖块颜色相同。

如果颜色相同的话,它便会增加一个NumMatchesFound,并将最新发现的砖块传送到函数中,就像它面向最初砖块所做的那样。

这种情况将持续着,直到它发现一个砖块与最初砖块颜色不相配时。那时候,它将检查是否发现足够多的匹配砖块能够创造一个群组,并标记那些相配的砖块。

垂直检查

现在让我们创造另外一个版本的函数去执行相同的垂直匹配。这便是CheckMatchesY函数。你可以复制最初的函数并做出适当的改变,或者从头开始创建,而不管你采取何种方法,你最后看到的函数应该如下:

Event: Function > On function         Name: “CheckMatchesY”         Sub-Event:              Condition: Block > Compare X                 X = Function.Param(0)             Condition: Block > Compare Y                 Y = Function.Param(1) + (Block.Width+2)             Condition: Block > Compare instance variable                 Color = Function.Param(2)             Action: System > Add to                 Variable = NumBlocks                 Value = 1             Action: Function > Call function                 Name: “CheckMatchesY”                 Parameter 0: Function.Param(0)                 Parameter 1: Function.Param(1) + (Block.Width+2)                 Parameter 2: Function.Param(2)             Sub-Event: System > Compare Variable                 NumMatchesFound  >= 3                 Action: Block > Set Boolean                     IsMatched = True

你的代码如下:

code(from tutsplus)

code(from tutsplus)

3.寻找其它检查

最后,我们需要调用FindMatches函数。转向SwapBlocks函数并在函数最后添加附事件:

Event: Function > Sub-Event:         Action: Function > Call function             Name: “FindMatches”

你将注意到这个附事件不带有任何条件。如果你之前从未创造过这样的附事件,你可以先创造一个带有任何条件的附事件,因为它要求你在创造附事件时给出一个条件,然后删除该条件,而留下附事件。基于这种方法,你便能够确保附事件能够始终运行着了。

现在你的SwapBlocks事件将如下:

SwapBlocks events(from tutsplus)

SwapBlocks events(from tutsplus)

这时候当你去运行游戏时将会发现,当出现匹配砖块时,它们便会被消除掉。你还会注意到,那些游戏开始时所出现的匹配只会在你进行交换时才会消失。因为我们是在创造了砖块网格后才调用FindMatches函数。

我们之所以未添加这一代码是因为在最后版本中,将出现另一个版本去阻止自动生成的匹配,所以我们并不需要去担心这一问题的出现。(如果你喜欢的话也可以尽早调用FindMatches函数)。

4.巩固检查

这时候,我们已经拥有一个很强大的匹配系统了,但还有一个问题是,我们的代码太过冗余。现在,我们拥有2个不同的函数能够检查邻接匹配砖块,而它们间唯一的不同便是一个是垂直检查,另一个则是水平检查。

构想2的自由版本限制了我们能够拥有的事件,这绝对是一种浪费。所以为了解决这一问题,我们将创造一个全新的函数版本去同时执行这两种检查。

如果你着眼于函数,你将发现这两个版本唯一的不同便在于,一个函数在砖块x位置上添加了Block.Width + 2,而另一个函数则将其添加在砖块的y位置上。所以我们必须通过的障碍便是将其变成一个函数,让函数只能添加Block.Width + 2到X或Y上,而不使用If声明或多个函数,因为这将要求执行更多的事件。

我的解决方法并不是很复杂,即如果我们可以将其整合在一起便能够更好理解,我将在此解释它是如何运转的。

1.删除CheckMatchesY事件。

2.将CheckMatchX事件重新命名为CheckMatches。

3.在FindMatches事件下调用CheckMatchesX。

将CheckMatchesX修改为CheckMatches。

添加参数3。

值=1。

添加参数4。

值=0。

4.在FindMatches事件下调用CheckMatchesY。

将CheckMatchesY修改为CheckMatches。

添加参数3。

值=0。

添加参数4。

值=1。

这些新添加的参数将告诉CheckMatches它是在执行水平检查还是垂直检查。当我们设置参数3的值为1而参数4的值为0时,它便是在执行水平检查,而当我们设置参数3的值为0,参数4的值为1时,它则是在执行垂直检查。

现在让我们回到CheckMatches函数中,修改条件的行动如下:

Event: Function > On function         Name: “CheckMatches”         Sub-Event:              Condition: Block > Compare X                 X = Function.Param(0) + ((Block.Width+2)*Function.Param(3))             Condition: Block > Compare Y                 Y = Function.Param(1) + ((Block.Width+2)*Function.Param(4))             Condition: Block > Compare instance variable                 Color = Function.Param(2)             Action: Block > Set Boolean                 IsMatched = True             Action: Function > Call function                 Name: “CheckMatches”                 Parameter 0: Function.Param(0) + ((Block.Width+2)*Function.Param(3))                 Parameter 1: Function.Param(1) + ((Block.Width+2)*Function.Param(4))                 Parameter 2: Function.Param(2)                 Parameter 3: Function.Param(3)                 Parameter 4: Function.Param(4)             Sub-Event: System > Compare Variable                 NumMatchesFound  >= 3                 Action: Block > Set Boolean                     IsMatched = True

现在你的FindMatches和CheckMatches代码如下:

code(from tutsplus)

code(from tutsplus)

如何运作?

所以,这一新函数版本是如何运作的?

不管你是何时调用CheckMatches,你现在正在传送两个以上的参数,不是只添加Block.Width + 2到x或y位置上,而是添加(Block.Width + 2) * Function.Param(3)到x位置上并添加(Block.Width + 2) * Function.Param(4)到y位置上。

因为这两个参数中的一个的值将总是1,而另一个将总是0,所以这便意味着其中一个位置将得到修改,而不会是两个同时被修改!

举个例子来说吧,如果我们设置参数3的值为1,而参数4的值为0,然后它将添加(Block.Width + 2) * 1(也就是Block.Width + 2)到x位置上,而添加(Block.Width + 2) * 0到y位置上。

让我们假设在这个例子中,最初砖块是在(200,200)的位置上,并且砖块的宽度为40。所以如果我们想要获得邻接垂直砖块的位置,那么公式将如下:

X = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200

Y = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242

如果我们想要获得邻接水平砖块的位置,那么公式如下:

X = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242

Y = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200

当你现在运行游戏时,你将看到匹配系统仍按照最初的方式运行着,但是从我们的角度看来,它已经变成一个更棒的系统了。

结论

这时候,我们的匹配检查函数还未真正完善,但是我们已经在本篇教程中阐述许多内容了,我也觉得有必要留给读者一些时间去深入理解这些内容。

在下一部分教程中我们将添加点系统,完善匹配系统,并添加“重力”去确保下方的砖块被删除时,砖块会出现自然掉落。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Make a Match-3 Puzzle Game in Construct 2: Match Detection

By David Silverman

So far, this series has covered the basics of setting up a Match-3 game, and implementing the initial gameplay elements such as block-swapping. In this tutorial, we are going to build on all of that, and start detecting when the player has made a match.

Final Game Demo

Here is a demo of the game we’re working towards throughout this series:

1. Detecting a Match

For now, we are only going to implement a basic version of the matching system, focusing on finding when matches exist and destroying matched blocks. In later articles we will continue developing and advancing the system.

As with the previous tutorial, I first want to discuss how the system will work, and then attempt to build it.

The match system will iterate through each instance of the Block object.

For each Block, it will pass the color and position of the block it’s looking at into a recursive function, which will look at the horizontal or vertical neighbor, and determine whether they are the same color.

If a match is found, it will call the function again with the position and color of the new block, rather than the original one.
This will continue until it finds no match. At that point, it will check how many matches it found.

If it found three or more matches, it marks all the blocks it just looked at as Matched with the IsMatched instance variable we made in one of the previous tutorials; otherwise, it does nothing.
Finally, once all of the blocks have been checked, the function will destroy every block that’s marked as a match.

First, we need an event which can iterate through each Block. The way I built the system, it actually iterates through the blocks twice: once to check for vertical matches, and once to check for horizontal matches. Depending on which check it is doing, it will use a different function to actually look for the match.

The very first thing we need to do is make a Global Variable to keep track of how many matching blocks we’ve found in any given iteration:

Now, let’s make the Event that will iterate through the blocks

Your code should look like this:

In this event, we iterate through every block and sending them into CheckMatchesX or CheckMatchesY, the functions which will check to see if the neighboring Block is a match.

To send the block into the function, we pass the functions three different parameters:

Parameter 0 is the Block’s X position

Parameter 1 is the Block’s Y position

Parameter 2 is the color.

After each Block is sent into one of the functions and the function finishes running, it checks NumMatchesFound to see if it found three or more matching Blocks, and then labels the Blocks as Matched if it did.

Finally, every Block that is marked as being Matched gets destroyed after .1 seconds passes. This wait statement is there to allow the game to switch the images for the Blocks to the image that indicates they are matched, and to give the player a moment to notice this change.

(While you could remove the wait statement without negatively impacting the gameplay, it makes the matching easier for the player to understand, and slows down the game just enough so that the player can easily keep track of what is going on.)

2. The Two Check Functions

Next we need to make the CheckMatchesX and CheckMatchesY functions. These functions will work similarly to the iterators above, in that there will be one version for checking horizontal matches, CheckMatchesX, and one for vertical matches, CheckMatchesY.

Horizontal Checks

First, let’s build the horizontal check function:

Your code should look like this:

So, what is this function doing?

First, it tests to see whether a neighboring block even exists to the left of the block we passed in.

Once the function confirms there is a Block in the neighboring location, it checks whether it is the same color as the Block we passed in.

If it is, it increases NumMatchesFound by one, and passes the newly found Block into the function just like it did for the original.
This continues until it finds a Block which isn’t the same color as the original. At that point it checks to see if it found enough matching blocks to create a group, and labels the blocks as matches if it did.

Vertical Checks

Now, let’s make another version of this function which will do the same thing for vertical matches. This is going to be our CheckMatchesY function. You can either copy the original function and make all the appropriate changes, or just build it again from scratch; in either case, here is how your function should look when it is finished:

Your code should look like this:

3. Actually Looking for Checks

Finally, we need to actually call the FindMatches function. Go to the SwapBlocks function and add a new sub-event to the end of the function:

You’ll notice that this sub-event doesn’t actually have any conditions. If you’ve never made a sub-event like this before, just make a sub-event with any condition at all, since it requires you to give a condition when making a sub-event, and then delete the condition, but leave the sub-event. This way, you make sure the sub-event always run.

Your SwapBlocks event should now look like this:

If you run the game at this point, you will see that the blocks get destroyed when matches occur. You’ll also notice though that any matches that are there when the game begins don’t disappear until you make a swap of some kind. This is because we never call the FindMatches function after we create the grid of blocks.

The reason we haven’t added this code is because in the final version there will be another function which prevents matches from being auto-generated like this, so there is really no reason to worry about this problem at all. (But feel free to call the FindMatches function earlier, if you like.)

4. Consolidating the Checks
At this point, we have a pretty strong matching system, but the problem is that our code is redundant. Currently, we have two different functions that check to see if there is a matching neighbor, and the only difference between them is that one checks vertically, and the other checks horizontally.

Since the free version of Construct 2 limits how many Events we can have, this is definitely a waste. To solve this, we are going to make a new version of the function that can do both checks.

If you look at the function, you will see the only difference between the two versions is that one adds Block.Width + 2 to the x-position of the Block, and the other adds it to the y-position of the Bock. So, the obstacle we have to get past to make this a single function, is giving the function a way to add Block.Width + 2 to only X, or only Y, without using an If statement or multiple functions, since those require more Events to be executed.

My solution to this is not very complex, but it will be easier to understand if we can see it come together, so we will implement it, and I will explain how it works once we can see it all in action.

1.Delete the CheckMatchesY event.

2.Rename the CheckMatchesX event to, simply, CheckMatches.

3.In the function call for CheckMatchesX under the FindMatches event:

Modify the function call to be for CheckMatches instead of CheckMatchesX.

Add Parameter 3.

Value = 1.

Add Parameter 4.

Value = 0.

4.In the function call for CheckMatchesY under the FindMatches event:

Modify the function call to be for CheckMatches instead of CheckMatchesY.

Add Parameter 3.

Value = 0.

Add Parameter 4.

Value = 1.

As I will explain soon, these added parameters will tell CheckMatches whether it is doing a horizontal check or a vertical check. When we send in 1 for Parameter 3, and 0 for Parameter 4, it is a horizontal check, and when we send in 0 for Parameter 3, and 1 for Parameter 4, it is a vertical check.

Now, go back to the CheckMatches function, and modify the conditions and actions to look like this:

This is what your FindMatches and CheckMatches code should now look like:

How does this work?

So, what is this new version of the function actually doing?

Well, whenever you call CheckMatches you are now sending two more parameters, and rather than adding Block.Width + 2 to either the x- or the y-position, it is adding (Block.Width + 2) * Function.Param(3) to the x-position, and (Block.Width + 2) * Function.Param(4) to the y-position.

Since one of those two parameters will always be 1, and the other will always be 0, this means that either the x- or the y-position will be modified – never both!

For instance, if we pass in 1 for Parameter 3, and 0 for Parameter 4, then it adds (Block.Width + 2) * 1, which is simply Block.Width + 2, to the x-position, and (Block.Width + 2) * 0, which is 0, to the y-position.

Here is a quick example to show what I mean and how it calculates the position of the block where it will check for the match. Let’s say that in this example the original Block is at (200, 200), and the Blocks have a width of 40. So, if we want to get the position of the neighboring vertical Block, the formulas would work out like this:

X = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200

Y = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242

If we wanted to get the position of the neighboring horizontal Block, the formulas would work out like this:

X = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242

Y = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200

If you run the game now, you should see the match system still works the way it originally did, but from our perspective, it is actually a better system.

Conclusion

At this point our match detection function is still incomplete, but we’ve done a lot in this tutorial already and I think it’s important to let all of this sink in before we add anything else. With that in mind, I am going to end this article here. Check out the demo in its current form.

In the next article we will be adding a points system,we will improve the matching system, and we will add “gravity” so that the Blocks will fall when Blocks below them are eliminated.

If you want to get a head start on the next article, take some time to consider how you would detect when there is an empty space below a Block. Try looking at the Block > Is Overlapping at Offset function for inspiration!(source:tutsplus)


上一篇:

下一篇: