Pushbocks Tutorial - Ambientiger - 05-03-2008
Pushable Blocks (originally requested of me by Gameboy)
**********************************************************
Difficulty (1/5 for copy and paste) - (3/5 to understand it!)
Client side code changes
First of all we'll set up the variables we are going to use to handle our pushblocks.
It's based the door tile system already in mirage, so you may notice a few similarities.
modGlobals - Add these new variable declarations.
Code: ' Used for map PushBlock editor
Public PushDir1 As Byte
Public PushDir2 As Byte
Public PushDir3 As Byte
Code: Public PushTile(0 To MAX_MAPX, 0 To MAX_MAPY) As PushTileRec
modConstants - Just an extra TILE_TYPE constant here.
Code: Public Const TILE_TYPE_PUSHBLOCK = 7
modTypes - Add the TypeRec that holds the info about our pushblocks.
Code: Type PushTileRec
Pushed As Byte
Dir As Byte
Moving As Byte
XOffset As Integer
YOffset As Integer
End Type
Next We'll need to add the new editor sections to help us define how each pushblock will move.
frmMirage:
Add an option button called 'optPushBlock' to the Attributes frame.
Then add the following code to the form near the other Attribute orientated Subs.
Code: Private Sub optPushBlock_Click()
frmPushBlock.Show vbModal
End Sub
frmPushBlock: A new form
Make frmPushBlock.
Create three frames - fraDir1 / fraDir2 / fraDir3
With the captions set to - Direction 1 / Direction 2 / Direction 3
Add five option buttons to each frame named - optDir#None / optDir#Up / optDir#Down / optDir#Left / optDir#Right
- where # is the number of frame.
Set the Value of - optDir1None / optDir2None / optDir3None to True.
And of course, create the OK and Cancel buttons - cmdPushBlockOK / cmdPushBlockCancel
Code: Option Explicit
Private Sub cmdPushBlockOK_Click()
If optDir1None.Value = True Then
PushDir1 = 0
ElseIf optDir1Up.Value = True Then
PushDir1 = 1
ElseIf optDir1Down.Value = True Then
PushDir1 = 2
ElseIf optDir1Left.Value = True Then
PushDir1 = 3
ElseIf optDir1Right.Value = True Then
PushDir1 = 4
Else
PushDir1 = 0
End If
If optDir2None.Value = True Then
PushDir2 = 0
ElseIf optDir2Up.Value = True Then
PushDir2 = 1
ElseIf optDir2Down.Value = True Then
PushDir2 = 2
ElseIf optDir2Left.Value = True Then
PushDir2 = 3
ElseIf optDir2Right.Value = True Then
PushDir2 = 4
Else
PushDir2 = 0
End If
If optDir3None.Value = True Then
PushDir3 = 0
ElseIf optDir3Up.Value = True Then
PushDir3 = 1
ElseIf optDir3Down.Value = True Then
PushDir3 = 2
ElseIf optDir3Left.Value = True Then
PushDir3 = 3
ElseIf optDir3Right.Value = True Then
PushDir3 = 4
Else
PushDir3 = 0
End If
Unload Me
End Sub
Private Sub cmdPushBlockCancel_Click()
Unload Me
End Sub
The extra code and new subs in modGameLogic prepare the map, blit the pushblocks in the game loop and deal with how a pushblock should move when pushed.
modGameLogic
*Sub Main - Add along with the ClearTempTile call.
*Sub GameLoop - Add underneath the For loop that Calls BltTile
Code: ' Blit out pushblock movement
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
If PushTile(x, y).Moving > 0 Then
Call BltPushBlock(x, y)
End If
Next x
Next y
The next one is quite self explanatory.
If your not sure, check a little further down in the game loop for a block of code commented
' Blit out attribs if in editor.
Code: If .Type = TILE_TYPE_PUSHBLOCK Then Call DrawText(TexthDC, x * PIC_X + 8, y * PIC_Y + 8, "P", QBColor(White))
Add this next piece of code underneath the code block commented
' Process player movements (actually move them)
Code: ' Process pushblock movements (actually move them)
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
If PushTile(x, y).Moving > 0 Then
Call ProcessPushBlock(x, y)
End If
Next x
Next y
*Sub BltTile - You'll need to change this Sub slightly to add in the pushblocks,
directly after the ' Is there an animation tile to plot? comment change the line of code to this.
Code: If Anim1 > 0 And TempTile(x, y).DoorOpen = NO And PushTile(x, y).Pushed = NO And PushTile(x, y).Moving = 0 Then
These next two Subs are new, I put the first one in after Sub BltTile,
and the second after Sub ProcessMovement.
Code: Public Sub BltPushBlock(ByVal x As Integer, ByVal y As Integer)
Dim x1 As Long, y1 As Long
' Only used if ever want to switch to blt rather then bltfast
'With rec_pos
'.top = GetPlayerY(Index) * PIC_Y + Player(Index).YOffset
'.Bottom = .top + PIC_Y
'.Left = GetPlayerX(Index) * PIC_X + Player(Index).XOffset
'.Right = .Left + PIC_X
'End With
With rec
.top = Int(Map.Tile(x, y).Mask / 7) * PIC_Y
.Bottom = .top + PIC_Y
.Left = (Map.Tile(x, y).Mask - Int(Map.Tile(x, y).Mask / 7) * 7) * PIC_X
.Right = .Left + PIC_X
End With
x1 = x * PIC_X + PushTile(x, y).XOffset
y1 = y * PIC_Y + PushTile(x, y).YOffset
' Check if its out of bounds because of the offset
If y1 < 0 Then
y1 = 0
With rec
.top = .top + (y1 * -1)
End With
End If
Call DD_BackBuffer.BltFast(x1, y1, DD_TileSurf, rec, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
End Sub
Code: Sub ProcessPushBlock(ByVal x As Integer, ByVal y As Integer)
' Check if player is walking, and if so process moving the pushblock over
If PushTile(x, y).Moving = MOVING_WALKING Then
If PushTile(x, y).Pushed = YES Then
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = -32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = -32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
Else
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
End If
End If
' Check if player is running, and if so process moving the pushblock over
If PushTile(x, y).Moving = MOVING_RUNNING Then
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset - RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = -32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset + RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = -32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
End If
End Sub
*Sub EditorMouseDown - It should be fairly easy to see where this code slots into the Sub.
Code: If frmMirage.optPushBlock.Value = True Then
.Type = TILE_TYPE_PUSHBLOCK
.Data1 = PushDir1
.Data2 = PushDir2
.Data3 = PushDir3
Map.Tile(x1, y1 - 1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1, y1 + 1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1 - 1, y1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1 + 1, y1).Type = TILE_TYPE_NPCAVOID
End If
*Sub CanMove - Pay attention here, or you'll get it wrong.
This piece of code needs to go into each of the directions mentioned in Sub CanMove.
I've put mine in directly after the block of code commented ' Check to see if the key door is open or not.
Remember, once for each direction, eg, If DirUp then, etc.
Code: ' If there's a pushblock there, see if we can push it
If Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Type = TILE_TYPE_PUSHBLOCK Then
If PushTile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Pushed = NO Then
If (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data1 DIR_UP + 1) And (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data2 DIR_UP + 1) And (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data3 DIR_UP + 1) Then
CanMove = False
' Set the new direction if they weren't facing that direction
If d DIR_UP Then
Call SendPlayerDir
End If
Exit Function
End If
End If
End If
The Sub to clear the pushblocks can be added in with the other clearing subs.
Code: Sub ClearPushTile()
Dim x As Long, y As Long
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
PushTile(x, y).Pushed = 0
Next x
Next y
End Sub
The last addition deals with the packet the client receives to tell it to move a pushblock.
A Blocked attribute needs to be added if it is moving or an Npc_Avoid if not.
modHandledata - Add a new variable declaration at the top of the sub.
Then add this in the sub along with the other packet descriptions.
I've added mine in just after the Map Key Packet.
Code: ' ::::::::::::::::::::::
' :: PushBlock packet ::
' ::::::::::::::::::::::
If (LCase(Parse(0)) = "pushblock") Then
x = Val(Parse(1)) 'x co-ordinate
y = Val(Parse(2)) 'y co-ordinate
n = Val(Parse(3)) 'Pushed Value
i = Val(Parse(4)) 'Player Direction
f = Val(Parse(5)) 'Movement
PushTile(x, y).Pushed = n
PushTile(x, y).Moving = f
If PushTile(x, y).Pushed = NO Then
Select Case PushTile(x, y).Dir
Case DIR_UP
Map.Tile(x, y - 1).Mask = 0
Map.Tile(x, y - 1).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = -32
Case DIR_DOWN
Map.Tile(x, y + 1).Mask = 0
Map.Tile(x, y + 1).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = 32
Case DIR_LEFT
Map.Tile(x - 1, y).Mask = 0
Map.Tile(x - 1, y).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = -32
PushTile(x, y).YOffset = 0
Case DIR_RIGHT
Map.Tile(x + 1, y).Mask = 0
Map.Tile(x + 1, y).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 32
PushTile(x, y).YOffset = 0
End Select
Exit Sub
End If
PushTile(x, y).Dir = i
If PushTile(x, y).Pushed = YES Then
Select Case PushTile(x, y).Dir
Case DIR_UP
Map.Tile(x, y - 1).Type = TILE_TYPE_BLOCKED
Case DIR_DOWN
Map.Tile(x, y + 1).Type = TILE_TYPE_BLOCKED
Case DIR_LEFT
Map.Tile(x - 1, y).Type = TILE_TYPE_BLOCKED
Case DIR_RIGHT
Map.Tile(x + 1, y).Type = TILE_TYPE_BLOCKED
End Select
End If
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = 0
Exit Sub
End If
Pushable Blocks - Server side changes
*******************************************
Once again we need to set up the variables for our new pushblocks.
modGlobals
Code: Public PushTile(1 To MAX_MAPS) As PushTileRec
modConstants
Code: Public Const TILE_TYPE_PUSHBLOCK = 7
modTypes
Code: Type PushTileRec
Pushed(0 To MAX_MAPX, 0 To MAX_MAPY) As Byte
PushedTimer As Long
End Type
modGeneral - As the comment in the code says, this closes any open pushblocks from a timer.
*Sub GameAI - Add underneath section of code used for closing doors
Code: ' /////////////////////////////////////////
' // This is used for closing pushblocks //
' /////////////////////////////////////////
If TickCount > PushTile(y).PushedTimer + 5000 Then
For y1 = 0 To MAX_MAPY
For x1 = 0 To MAX_MAPX
If Map(y).Tile(x1, y1).Type = TILE_TYPE_PUSHBLOCK And PushTile(y).Pushed(x1, y1) = YES Then
If PushBlockBlocked(y, x1, y1) = False Then
PushTile(y).Pushed(x1, y1) = NO
Call SendDataToMap(y, "PUSHBLOCK" & SEP_CHAR & x1 & SEP_CHAR & y1 & SEP_CHAR & 0 & SEP_CHAR & 0 & SEP_CHAR & 1 & SEP_CHAR & END_CHAR)
Else
PushTile(y).PushedTimer = GetTickCount
End If
End If
Next x1
Next y1
End If
modGameLogic
*Sub PlayerMove - Add after the block of code commented ' Check for key trigger open
Code: ' Check for pushblock trigger
If Map(GetPlayerMap(Index)).Tile(GetPlayerX(Index), GetPlayerY(Index)).Type = TILE_TYPE_PUSHBLOCK Then
x = GetPlayerX(Index)
y = GetPlayerY(Index)
If Map(GetPlayerMap(Index)).Tile(x, y).Type = TILE_TYPE_PUSHBLOCK And PushTile(GetPlayerMap(Index)).Pushed(x, y) = NO Then
PushTile(GetPlayerMap(Index)).Pushed(x, y) = YES
PushTile(GetPlayerMap(Index)).PushedTimer = GetTickCount
Call SendDataToMap(GetPlayerMap(Index), "PUSHBLOCK" & SEP_CHAR & x & SEP_CHAR & y & SEP_CHAR & 1 & SEP_CHAR & GetPlayerDir(Index) & SEP_CHAR & Movement & SEP_CHAR & END_CHAR)
Call MapMsg(GetPlayerMap(Index), "A block is being pushed.", White)
End If
End If
Lastly we need to add a function to check if there is someone on the other side of the pushblock the player wants to push stopping them doing so, I added this with the other Functions at the top of modGameLogic.
Code: Function PushBlockBlocked(ByVal MapNum As Long, ByVal x As Integer, ByVal y As Integer) As Boolean
Dim i As Long
PushBlockBlocked = False
For i = 1 To MAX_PLAYERS
If Player(i).Char(Player(i).CharNum).Map = MapNum Then
If Player(i).Char(Player(i).CharNum).x = x And Player(i).Char(Player(i).CharNum).y = y Then
PushBlockBlocked = True
End If
End If
Next i
End Function
To use the pushblocks in your game, enter the editor, place a MASK layer tile where you want the block.
Select the Pushblock option from the attributes section of the map editor and select the directions it can move.
Click on the map where you placed the MASK tile, et voila, a working pushblock.
There is a slight issue I noticed while running the server and client of a much larger game than Mirage on the same PC.
At times of lag a player could push a block and move over it but the client didn't move the block over.
Chances are it won't happen too often with a dedicated server, or a little twiddling with the code should sort it out.
Enjoy.
Re: Pushbocks Tutorial - genusis - 05-03-2008
hmm this idea is very good =] we can use this to make mazes and stuff where thye have to move blocks to get through. haha
Re: Pushbocks Tutorial - genusis - 05-03-2008
hmm this idea is very good =] we can use this to make mazes and stuff where thye have to move blocks to get through. haha
Re: Pushbocks Tutorial - Joost - 05-03-2008
Unfair. I was just about to code this. (NoRlly)
Re: Pushbocks Tutorial - Forte - 06-03-2008
ooh nice, I have this in my engine, it's made basically the same way, Well, Nice job on the tut. I'll read through it when I get the chance
Re: Pushbocks Tutorial - Joost - 06-03-2008
After looking trough your tutorial, I've decided Im not gonna use it ;p. Instead of using a mask tile, I'll use an actual block tile, that animates when you push it. It looks cooler, and otherwise the tutorial won't work with optimized surfaces. (Unless you 'drefresh the map every time you push)
Re: Pushbocks Tutorial - Ambientiger - 06-03-2008
I had to modify my original code so that this tutorial works with MSE1, I haven't added optimized surfaces to any of my projects yet so didn't know about the possible problem, thanks for the warning Joost.
Re: Pushbocks Tutorial - Rian - 07-03-2008
I added this to a fresh MSE. I don't really understand how the actual pushblock editor is supposed to work. also, im not sure if it's the tutorial, or if I did it wrong (I probably did it wrong, I did this tutorial with much haste) but I noticed that the block would sometimes move when I walked next to it, not necessarily in front of it.
Re: Pushbocks Tutorial - Ambientiger - 07-03-2008
I stripped the code from cerberus, made up the tutorial and then dropped it into a fresh MSE1 to test it.
PushBlocks Tutorial Package
The way I recommend you add pushblocks to the game is:
go to the map editor
place a Mask Layer tile where you want the block
click the Attributes option
click the PushBlock option
then click ontop of the brick that you placed
select the directions the block can move in from the popup (none/left/right/up/down)
and click okay
[spoiler] [/spoiler] shows the three pushblocks setup from the tutorial package
the left one moves left and down
the middle one moves left and right to give access to the blocked in area
- there is a key attribute in the blocked area that opens the darker middle brick
the middle brick can also move up incase a player gets stuck in the blocked area
[spoiler] [/spoiler]
a player can also stand in the way of a pushblock to stop them closing
if they move away after the 5 or so second reset time the block moves back instantly.
these blocks are the only ones i've tested with this tutorial, Sonire, were the blocks you tested near the edge of the map maybe? might be a bug I hadn't spotted.
|