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

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

发布时间:2013-08-13 17:30:58 Tags:,,,,

作者:David Silverman

在之前的教程中,我们已经让游戏能够移动,并可以添加动作到我们的砖块中了。另外,我们也创造了一个基本的难度系统去加深游戏难度,从而让玩家可以更长久地玩游戏。(请点击此处阅读本系列第12、3、4、5、6、8篇

基于游戏中的这些功能,我们准备开始执行能够删除预制匹配的系统。尽管这并不是本系列文章中的最后一篇,但却是我们需要执行的最后一个主要系统。

最后的游戏演示版本

这是我们在这系列文章中创造的游戏演示版本:

demo(from tutsplus)

demo(from tutsplus)

快速修正

在开始这部分教程前,我想花几分钟事件去修正之前所遇到的2个问题。

匹配

你可以在如下场景中发现第一个问题:

scenario(from tutsplus)

scenario(from tutsplus)

在这种情况下,如果你将砖块B拖到绿色的点上,它便会因为下方的空缺而掉落,最后降落在C位置上。在大多数场景中都会出现这种情况,游戏将正常运行,但是在某些场景中,游戏可能会暂时检测到某一匹配,即认为砖块B是邻近组合A,所以将消除这三个砖块。

并不是只有在这种场景中才会出现这一问题,在如下场景中也会出现:

scenario(from tutsplus)

scenario(from tutsplus)

如果我不能解决这一问题,玩家便会因为自己未预料到的匹配而获得多余的点数,并疑惑为何会有这么多砖块平白消失。

幸好这是一个容易解决的问题。为了解决该问题,我们可以创造一个名为MatchesPossible的全局变量,它将指示是否出现了匹配,而一个新事件也将检测砖块是否“掉落”,并修改MatchesPossible进行定时,从而确保在砖块掉落时不会出现莫名的匹配。

首先我们将创造一个全局变量:

Global Variable: MatchesPossible
Type = Number
Initial Value = 1

你的新变量应该如下:

variable(from tutsplus)

variable(from tutsplus)

现在我们将创造一个事件去明确何时砖块掉落:

Event:
Condition: Invert: Block>Is overlapping at offset
Object = Block
Offset X = 0
Offset Y = 8
Condition: Block>Compare Y
Comparison = Less or equal
Y co-ordinate = SPAWNY
Action: System>Set value
Variable = MatchesPossible
Value = 1

基于该事件,当砖块下方存在缺口时,MatchesPossible便会被设置为1,即代表不会出现匹配。你将注意到它也会检查砖块的Y位置。这是为了确保砖块不会在最底行,即下方永远都有缺口。

接下来我们需要一个事件在砖块下方不再有缺口时将MatchesPossible设置为0。第二个事件是基于Else条件:

Event:
Condition: System>Else
Action: System>Set value
Variable = MatchesPossible
Value = 0

确保该事件能够遵循第一个事件,从而Else陈述能够发挥作用。

你的两个事件应该如下:

event(from tutsplus)

event(from tutsplus)

最后,我们将在CheckMatches添加一个新的条件,从而让它能够基于MatchesPossible去决定匹配是否成立。在最初CheckMatches事件的函数调用中添加该条件:

Condition: System>Compare variable
Variable = MatchesPossible
Comparison = Equal to
Vale = 0

在添加了这一条件后,你的CheckMatches事件应该如下:

checkmatches(from tutsplus)

checkmatches(from tutsplus)

与目前为止我们遇到的大多数问题不同的是,这一问题的出现频率并不一致。这便意味着我们不能真正测试是否解决了该问题,而是只能测试是否引起了其他问题。如果你现在玩游戏,你便会发现该条件不会再引起新问题了。

点数

在点数系统中添加任何新内容之前,我还要解决第二个问题。

尽管我一直在努力保护该教程,但是我也发现自己的某些做法导致点数系统出现奇怪的现象,即在每次匹配时多给予玩家4到5倍的点数。尽管我不能决定自己做出的哪些改变引起了这种问题,但是我发现了问题的根源在于GivePoints函数在砖块未被立即摧毁时多次被调用。幸好与之前的问题一样,我也能够轻松地解决该问题。

为了解决这一问题,我们需要创造一个新的变量,即告知系统是否能够给予分数。然后,当我们能够使用GivePoints函数时,我们也能够修改变量去确保事件只启动一次。最后,当玩家获得点数时,我们便会改变相应变量,从而确保下次提供点数时不会再有问题出现。

首先,创造一个名为PointsGiven的全局变量:

Global Variable: PointsGiven
Type = Number
Initial Value = 0

你的变量应该如下:

variable(from tutsplus)

variable(from tutsplus)

接下来我们将修改FindMatches函数的部分内容,即通过添加1个新的条件和2个新的行动去提供分数:

Condition: System>Compare variable
Variable = PointsGiven
Comparison = Equal to
Value = 0

现在在行动列表的开始处添加该行动:

Action: System>Set value
Variable = PointsGiven
Value = 1

最后,在行动列表的末尾添加该行动:

Action: System>Set value
Variable = PointsGiven
Value = 0

现在的事件应该如下:

event(from tutsplus)

event(from tutsplus)

基于这些改变,调用GivePoints的事件将只会在PointsGiven变量为0时运行了。因为当我们启动事件时,我们会立即将值设为1,这将会阻止事件多次发挥作用,并确保玩家只会受到正确的点数。

如果你在这时候运行游戏,你在每次创造出匹配时便能获得最准确的点数了。

消除预制匹配

既然我们已经找到了解决问题的方法,我们便可以继续创造系统,并消除系统在随机分配砖块颜色时所创造的一些匹配。

我们现在所面对的问题在于,既然砖块的颜色是完全随机的,那么当你开始游戏时通常都能马上看到许多匹配。之所以说这是一大问题是因为它会导致新玩家在面对游戏前几秒内容时充满困惑,因为它会提供给玩家他们未曾获取的点数。

为了解决这一问题,我们创造了一个函数,它将着眼于每个砖块并判断该砖块与旁边的砖块是否拥有相同的颜色。如果颜色相同,它便会继续改变砖块颜色,直至它不在与周边的砖块相匹配。为了确保该系统起作用,我们也将创造多个支持函数和事件,并在砖块对象上添加一个新的实例变量。

使其发挥作用

首先,为砖块对象创造一个新的实例变量:

Instance Variable: BlockID
Type = Number
Initial value = 0

你的砖块创造事件应该如下:

event(from tutsplus)

event(from tutsplus)

接下来我们需要创造一个将理解砖块的X和Y位置的函数,并告诉我们砖块是什么颜色:

Event:
Condition: Function>On function
Name = “GetBlockColor”
Sub-Event:
Condition: Block>Compare X
Comparison = Equal to
X co-ordinate = Function.Param(0)
Condition: Block>Compare Y
Comparison = Equal to
Y co-ordinate = Function.Param(0)
Action: Function>Set return value
Value = Block.Color
Sub-Event: System>Else
Action: Function>Set return value
Value = -1

完成后的函数应该如下:

function(from tutsplus)

function(from tutsplus)

既然我们已经拥有一个能够分辨砖块颜色的函数,我们便可以开始创造能够分辨砖块的周边砖块是否拥有相同颜色的函数。

该函数的运行非常简单:

首先,我们将在函数中传达一个BlockID。

如果现在的砖块带有BlockID,那么函数将着眼于周边的4个砖块,并判断这些砖块与自己周边的砖块是否拥有同样的颜色。

如果它发现周边的砖块拥有相同的颜色,它便会开始改变目标砖块的颜色,直到砖块颜色与周边砖块都不同。

在创造这一事件之前,我们必须先创造一个新的全局变量。在函数中,我们将使用While循环去判断砖块的颜色是否需要改变。我们即将创造的变量也是While循环将用于判断是否继续运行的变量:

Global Variable: HasMatchingNeighbor
Type = Number
Initial Value = 0

所以让我们创造该事件:

Event:
Condition: Function>On function
Name = “RemoveSpawnedMatches”
Sub-Event:
Condition: Block>Compare instance variable
Instance variable = BlockID
Comparison = Equal to
Value = Function.param(0)
Action: System> Set value
Variable = HasMatchignNeighbor
Value = 1
Sub-Event:
Condition: System> While
Condition: System>Compare variable
Variable = HasMatchingNeighbor
Comparison = Equal to
Value = 1
Sub-Event: Or:
Condition: Block>Compare instance variable                            Instance variable = Color
Comparison = Equal to
Value = Function.Call(“GetBlockColor”, Block.X – (Block.Width + 2), Block.Y)
Condition: Block>Compare instance variable                            Instance variable = Color
Comparison = Equal to
Value = Function.Call(“GetBlockColor”, Block.X + (Block.Width + 2), Block.Y)
Condition: Block>Compare instance variable                            Instance variable = Color
Comparison = Equal to
Value = Function.Call(“GetBlockColor”, Block.X, Block.Y – (Block.Width + 2))
Condition: Block>Compare instance variable                            Instance variable = Color
Comparison = Equal to
Value = Function.Call(“GetBlockColor”, Block.X, Block.Y + (Block.Width + 2))
Action: Block>Set value
Instance variable = Color
Value = floor(Random(1,7))
Action: System>Set value
Variable = HasMatchignNeighbor
Value = 1
Sub-Event:
Condition: System>Else
Action: System>Set value
Variable = HasMatchignNeighbor
Value = 0

你的事件应该如下:

event(from tutsplus)

event(from tutsplus)

所以该函数是如何运行的?

首先它会检查系统所经过的砖块是否带有BlockID。当它定位于一个砖块时,它便会设置HasMatchingNeighbors的值为1,然后运行While循环。

因为While循环智能在HasMatchingNeighbors值为1时运行,所以这便是它所设置的值。在While循环期间,它测试了目标砖块的上下左右砖块是否拥有相同颜色。如果在任何的这些位置上找到一个匹配砖块,它便会随机分配给砖块一个新颜色,然后通过确保HasMatchingNeighbors值为1再次运行测试。当它最后找到一个不会与周边砖块撞色的砖块颜色时,它便会将HasMatchingNeighbors的值改为0,从而While循环便会结束,而项目将继续前进。

现在我们需要在游戏中执行该函数;为此我们将创造两个新的变量和两个新的函数。让我们先从变量开始:

Global Variable: CheckStartingMatches
Type = Number
Value = 0
?123 Global Variable: CheckNewestRow
Type = Number
Value = 0

你的变量应该如下:

variable(from tutsplus)

variable(from tutsplus)

我们所创造的这两个变量将用于触发我们即将创造的两个事件。事件本身将用于砖块迭代,并发送每个砖块到RemoveSpawnedMatches函数。

我们不再创造砖块后立即调用RemoveSpawnedMatches函数是因为砖块网格需要在函数有效运行时才能完成。所以,比起立即调用函数,我们将触发一个事件去检查砖块,然后在网格生成后再调用函数。

第一个事件是专门迭代于最初的砖块组间:

Event:
Condition: System>Compare variable
Instance variable = CheckStartingMatches
Comparison = Equal to
Value = 1
Condition: System>For
Name = “Blocks”
Start index = 0
End index = NumBlocks-1
Action: Function>Call function
Name = “RemoveSpawnedMatches”
Parameter 0 = loopindex(“Blocks”)
SubEvent:
Condition: System>Compare two values
First value = loopindex(“Blocks”)
Comparison = Equal to
Second value = NumBlocks-1
Action: System>Set variable
Instance variable = CheckStartingMatches
Value = 0

你的事件应该如下:

event(from tutsplus)

event(from tutsplus)

第二个事件将在新砖块被创造出来时进行检查:

Event:
Condition: System>Compare variable
Instance variable = CheckNewestRow
Comparison = Equal to
Value = 1
Condition: System>For
Name = “Blocks”
Start index = NumBlocks-9
End index = NumBlocks-1
Action: Function>Call function
Name = “RemoveSpawnedMatches”
Parameter 0 = loopindex(“Blocks”)
SubEvent:
Condition: System>Compare two values
First value = loopindex(“Blocks”)
Comparison = Equal to
Second value = NumBlocks-1
Action: System>Set variable
Instance variable = CheckNewestRow
Value = 0

第二个事件应该如下:
second event(from tutsplus)
执行

当这些函数已经各就各位时,我们只需要去执行它们即可。来到创造砖块的最初事件,即On Start of Layout事件。我们将添加一个子事件,即告知CheckStartingMatches事件进行激活:

Sub-Event:
Condition: System>Compare two values
First value = loopindex(“X”)
Comparison
Second value: 7
Condition: System>Compare two values
First value = loopindex(“Y”)
Comparison
Second value: 3
Action: System>Set value
Instance variable = CheckStartingMatches
Value = 1

你的事件应该如下:

event(from tutsplus)

event(from tutsplus)

子事件将仔细观察For循环何时结束,然后改变CheckStartingMatches变量的值去激活适当的事件。

我们即将开始创造完全相同的子事件,并将其附加到SpawnNewBlocks函数中。

Sub-Event:
Condition: System>Compare two values
First value = loopindex(“X”)
Comparison
Second value: 7
Action: System>Set value
Instance variable = CheckNewestRow
Value = 1

SpawnNewBlocks应该如下:

spawnnewblocks(from tutsplus)

spawnnewblocks(from tutsplus)

该子事件与之前的内容并不相同,除了它能够激活我们所创造的其它事件外。如果你在这时候运行游戏,你便会看到在游戏开始时将不再自动出现任何匹配了。

结论

在本篇教程中我们并未对游戏做出过多改变,但是我们所做的一切都非常重要。

这时候,我们最好能够暂时停下来并为接下来的教程保留最后的两个游戏元素,即链条/组合以及游戏结束屏幕。这才是本系列文章的最后部分,那时候我也将谈论一些本教程中未设计的游戏机制,并提供一些建议帮助你们独自创造这些系统。

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

Make a Match-3 Game in Construct 2: Eliminating Pre-Made Matches

By David Silverman

In the previous tutorial we finally got our game moving and added motion to our blocks. On top of that, we created a rudimentary difficulty system to make the game harder as the player plays longer.

With both of these features in the game, we are ready to implement the system that will eliminate pre-made matches from the board. Although this isn’t the last article in the series, this is the last major system we need to implement – so get comfortable, because we have our work cut out for us.

Final Game Demo

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

Quick Fixes

Before we get started with the main part of this tutorial, I want to take a minute to fix two issues I discovered while I was writing the previous tutorial.

Matching

The first issue I’m referring to comes up in the scenario you can see below:

In this situation, if you drag Block B onto the green dot, it should fall because of the empty spaces below the dot, and eventually land in the spot labeled C. In most scenarios, this will happen and the game will function normally, but in some scenarios the game will instead detect a match in the brief moment where Block B is next to group A and it will end up destroying all three blocks.

This issue isn’t exclusive to that scenario above and can also present itself when you do the same thing in the scenario I’ve highlighted below.

If we don’t fix this, the player will be receiving credit and points for a number of matches they never intended to make and could also get confused about why so many blocks are disappearing unexpectedly.

Thankfully, though, this is a simple issue to fix. To solve this issue we are going to create a new Global Variable called MatchesPossible which will dictate whether matches can be made, and a new Event which will detect when a block is “falling” and will modify MatchesPossible to make it so that no matches can be made while this is happening.

First we will create the Global Variable:

Your new variable should look like this:

This Event makes it so that when a block is found to have an empty space below it MatchesPossible is set to 1, meaning matches are not possible. You’ll also notice that it checks the Y position of the block. This is to ensure the block is not on the lowest row of blocks which will always have empty space below it.

Next, we need an Event that sets MKatchesPossible back to 0 when no blocks have an empty space below them. This second Event is going to be based on an Else condition:

Make sure that this Event immediately follows the first Event so that the Else statement is used correctly.

Your two new Events should look like this:

Finally, we are going to add a new condition to CheckMatches so that it looks at MatchesPossible to determine if a match can be made. Add this condition to the initial function call of the CheckMatches Event.

With the condition added, your CheckMatches Event should now look like this:

Unlike most of the issues we have encountered up to this point, this issue shows up on a fairly inconsistent basis. This means that we can’t really test to see if we fixed the issue, we can only test to see if we caused any other issues. If you play the game now, you should see that no new issues have been caused by this condition.

Points

The second issue I wanted to fix before we adding anything new relates to the Points system.

While working on the project for this tutorial, I noticed that something I did caused the Points system to behave strangely and give the player four or five times as many points as they should be getting for each match. Although I wasn’t able to determine what change I made caused this to start happening, I found that the root of the problem was that the GivePoints function was actually getting called multiple times since the Blocks were not being destroyed immediately. Thankfully, like our last issue, this can be fixed easily.

To fix this we are going to create a new variable which will tell the system whether it can give points. Then, whenever we are about to use the GivePoints function we will also modify the variable to ensure the Event only fires once. Finally, once the points have been given we will change the variable once more so that there won’t be an issue next time we try to give points.

First, create a Global Variable called PointsGiven:

Your variable should like this:

Next we will modify the part of the FindMatches function which actually gives the points by adding a new Condition and two new Actions.

Now add this Action to the beginning of the Action list:

Finally, add this Action to the end of the Action list:

The Event should now look like this:

With these changes we have made it so that the Event which calls GivePoints will only run when the PointsGiven variable is 0. Since we are immediately setting the value to 1 when we start the Event, this prevents the Event from firing more than once and ensures the player will receive the correct number of points.

If you run the game at this point you should receive the correct number of points for every match you make, even if you were not having this issue to begin with.

Eliminating Pre-Made Matches

Now that we have gotten those fixes out of the way, we can move on to creating the system that will eliminate matches that are spawned by the system when it is randomly assigning colors to the blocks it creates.

The problem we have now is that, since the Block colors are entirely random, it’s not uncommon for you to start the game and see a bunch of matches get made immediately. This is an issue because it could make the first few seconds of the game very confusing for someone who has never played before, and because it is giving the player points they didn’t earn.

To solve the issue, we’ll create a function that will look at each block and then see if that block is the same color as any of its neighbors. If it is the same color as one its neighbors, it will continue to change the color of that block until it no longer matches any of the blocks surrounding it.

To make this system work we will also have to create multiple support functions and Events as well, and add a new instance variable to the Block object.

Making It Work

First, create a new instance variable for the Block object:

This variable is what we will use to easily identify a block so that we can tell some of the functions we are going to make exactly what block we want to look at, regardless of its position.

Before we move on, we also need to start using this variable. Go to the On start of layout Event which creates the blocks and add a new Action before the Action that increases NumBlocks:

Your block-spawning Event should now look like this:

Next we need to make a function which will take in the X and Y position of a block and tell us what color that block is:

The function should look like this when it is finished:

Now that we have a function which can tell us the color of any block, we are going to create the function which will actually look at a block and determine whether it has any neighboring Blocks which are the same color.

The way this function will work is quite simple.

First, we will pass a BlockID into the function.

If there is currently a block with that BlockID, the function will look at the four neighboring blocks, and will determine whether the block it’s looking at is the same color as any of its neighbors.

If it finds that there is a neighbor of the same color, it will begin changing the color of the block it’s looking at, and it will continue to change the color until the Block is a different color from all of its neighbors.

Before we can make this Event, we have to make a new Global Variable. In the function we will use a While loop to determine whether the block’s color needs to be changed. The variable we are about to create is the variable the While Loop will use to determine whether it needs to continue running:

Tip:The Event we are going to make contains an Or-based Event. If you’ve never made an Event block that has an Or attribute all you have to do is make the Event Block like you normally would and then right-click the entire Block and choose Make ‘Or’ Block. Unlike a standard Event Block which requires all conditions to be filled before it will fire, an Or block will fire if any condition is fulfilled.

So, let’s make the Event:

Your Event should look like this:

So how does this function work exactly?

The first thing it does is check for a Block with the BlockID that the system passed in. When it locates that Block it sets the value of HasMatchingNeighbors to 1 and then runs the While loop.

Since the While loop will only run when HasMatchingNeighbors is 1, that is the value it sets it to. During the While loop, it tests to seewhether there is a neighboring Block to the left, the right, above, or below that is the same color as the Block we are looking at. If it finds a matching Block in any of these positions, it randomly assigns a new color to the Block and then runs the test again by ensuring HasMatchingNeighbors is set to 1. When it finally finds a color for the Block that doesn’t match any of its neighbors, it changes the value of HasMatchingNeighbors to 0 so that the While loop ends and the program can move on.

Now we need to implement this function into the game; to do this we are going to have to create two new variables and two new functions. Let’s start with the variables.

Your variables should look like this:

The two variables we just made will be used to trigger the two Events we are about to create. The Events themselves will be used to iterate through the blocks immediately after they are created and send each block into the RemoveSpawnedMatches function.

The reason we are not just calling the RemoveSpawnedMatches function immediately after creating the block is because the block grid needs to be complete for the function to work correctly. So, instead of just calling the function directly when blocks are made, we will instead trigger an Event that can go through the blocks and call the Function on its own after the grid is generated.

The first Event will be specifically for iterating through the initial group of blocks:

This is what your event should look like:

The second Event will be specifically for checking new rows of blocks when they are made:

This is what the second Event should look like:

Implementation

With both of these functions in place we now just need to implement them. Go to the initial Event that makes the blocks, the On Start of Layout Event. We are going to add a Sub-Event to this which will tell the CheckStartingMatches Event to activate.

Your Event should now look like this:

This Sub-Event listens for when the nested For loop has ended and then changes the value of the CheckStartingMatches variable to activate the appropriate Event.

We are now going to make almost the exact same Sub-Event and attach it to the SpawnNewBlocks function.

SpawnNewBlocks should now look like this:

This Sub-Event does the same thing as the previous one except that it activates the other Event we created. If you run the game at this point you should see that when you start the game there are no longer any matches automatically occurring.

Conclusion

In this tutorial, we didn’t make too many changes to the game, but the ones we did make were very important.

At this point, I think it’s best for us to stop for now and save the final two game elements for the next tutorial, where we will be covering chains/combos and the Game Over screen. This will be the final part of the series, so I’ll also talk about some game mechanics we won’t be covering in these tutorials, and give you some advice on how you could make those systems on your own.

If you want to get a head start on next week’s content, start looking at how you could detect when the Game Over screen should pop up, based on the position or height of some of the blocks. Alternatively, start thinking about how you could detect when the player makes a chain reaction that causes more than one group to be formed.

Whatever you do, I hope to see you back here next week for the final installment of the series.(source:tutsplus)


上一篇:

下一篇: