Recommended approach for board game-like movement

Hi,

I would kindly like to ask for some general orientation for a small game we’re trying to build with my son as a learning project. Our knowledge of Unity is non-zero but pretty limited.

The game has pieces which can be thought as rigid blocks in a game board. Think like Domino pieces or cubes. These pieces can be moved by the player in different directions, using keys, but always in fixed units or cells. E.g.: move left, all pieces shift left by one position.

Now if there is something blocking the movement, the piece shouldn’t move. E.g. if there is a wall, then free pieces that can move will move, but pieces adjacent to the wall will stay in the same place. If the player keeps pressing the left key, all the blocks will end up adjacent to the wall.

We were able to get the basic movement working with a simple script by simply adding the fixed amount of distance to the transform.position of the gameObject on every key press. Where we are stuck now is: how to implement the movement from not happening if the path is blocked?

We tried initially by using Raycast to decide if there will be a collision before doing the movement, but that prevents two consecutive blocks which are free to move otherwise from moving: the block to the right will think that the block to the left is going to collide so it doesn’t move, and only the left one moves.

This makes me think that doing these calculations by hand will be complicated enough that it may be easier to use the Unity physics engine to check and enforce collisions instead. But how can we set-up the physics system in such a way that blocks won’t “bounce” when colliding, ending in different positions than the fixed cells where we want them? We’ve randomly tried different things such as very high mass or Drag but it always results in pretty strange behaviors.

I am almost sure we must be approaching the design in the wrong way. We would greatly appreciate some directional guidance or known tutorials to help implement this type of mechanics with Unity - we’ve searched quite a bit before posting but clearly there’s not thing easy to find or we suck at searching, most likely the latter.

Thanks for any suggestions,
Julian and Luqui

Physics.Raycast lets you check for colliders in a given ‘ray’ direction.

This should work the only way that it might not is if you are moving a group of blocks and only doing one raycast for the move.
If your moving a group of blocks together then you need:

  • Each block to raycast and ignore other blocks in the group (raycast layermask).
  • Only the moving side of the group to raycast using some kind or row column system.

Option 1 is simpler.

You could just use the physics but you would need to apply forces to all the cubes and then stop them after they have moved which could cause major collision issues and take what is a simple turn based system and make it a full physics sandbox.

I wouldn’t use Physics. It tends to bump and smush things (and bounce) in messy “realistic” ways. Blocks will become slightly tilted, out-of-line and sometimes roll over. But if you want to try, which might be fun, set the blocks to not rotate at all and lock their y-axis motion (on the rigidbody). Give them a physics material and set bounce to 0 and “use minimum” (since it also checks how bouncy the wall is). Then you’ll have to figure out a good smack to give them so 1 block can push several. Even then, the blocks will take a little time to move, so it will be possible to quickly move again, catching them partway between squares.

A hack I’ve used is to snap every block into the center of the square with perfect rotation, after the move (which requires a certain amount of math to find the correct square, and the center).

Moving multiple blocks in code is just plain difficult. One thing would be to simplify it. If you move a block, check that direction for a block or wall. If it’s another block, keep looking. If you eventually find a block with a free space to move into, move only that block. It would take you 4 presses to move a line of 4, 1-at-a-time, but the end result will be the same. It may be a pain doing that for all 4 directions (could use 1 loop for each, or 1 complicated general-purpose loop

If you can make a loop, maybe you can use another to move a whole line. Do the same check to find the next block in that direction with a free space. Now you have blockLineStart and blockLineEnd. They may be the same (if the block was free to move), but no problem. Add another loop to move each of them, back-to-front so they don’t overlap. This feels like intermediate-level programming to me.

2 Likes

Write a MasterBlockController. Keep a List of allBlocks available and another List of the currentlyActiveBlocks. Not sure how you select your blocks per move but when you have that group rounded up then send them to a function along with the key press which will indicate either neg or pos X and neg or pos Z. You then iterate through the blocks to be moved to find the block/s that are the furthest along that axis and send those to a temporary List. Now set a local bool hitCheck. As you iterate thru the temporary List fire a raycast the length of one cell to the next’s distance in the direction of the axis movement and if it get’s a hit then set the hitCheck to true. If the hitCheck bool remains false you can then move the currently active block to the next cell in the direction of the move. Look into Vector3.Lerp(startPos, endPos, moveSpeed * timer/duration); for smooth and controllable movement.

Have fun! I am releasing a game this week that I recruited my son to be the gameplay designer and beta tester and he kicked royal butt is writing most of the code he wants added and I go in and brush it up, find bugs and do the heavy lifting stuff…

4 Likes

If all of your pieces are in a grid anyway, I’d have a representation of that grid in my code. That removes the need for raycasts* and allows you to trivially easily check things like “what is in cell (X, Y)?” However, it requires that you’re comfortable managing that data structure.

  • Which is good because these come with all of the considerations of a physics system. Does this thing have a collider? Is it on the right layer? Is it in the correct spot?
5 Likes

I wouldn’t use physics at all for this. I would suggest separating your game logic from the visual display.

In my typical approach I would set up an array for all possible piece positions. More complex collections are available if your board isn’t square. Then when a piece moves, you simply check if the next space is empty.

For moving multiple pieces at once, start from the end which will be the ultimate constraint. So if you are moving left, start from the left most piece. Then move to the right one piece at a time until you have checked all pieces. This will ensure pieces “get out of the way” before the next piece checks for open space.

Once you have determined the new board state, animate the actual piece movement however you like. For board game likes, it may be handy to create a new array with new state every time the board changes. This lets you do cool things like rewind or replay the game relatively simply. Its also great for asynchronous turn play, which many board game likes have as an option.

3 Likes

To put it another way, an actual board game should have a 10x10 (or whatever size) grid in memory. Like GameObject[ ][ ] TheBoard;. If you’re in space [4][4] and want to move right, check whether [5][4] is free. No raycasts needed. But making and using it requires a certain skill with arrays and indexes.

Without that you’ve got to do all sorts of work to keep pieces directly on squares. You can add big square colliders to small round pieces, so they “Fill” their square. But surpose you want to add diagonal movement. Your piece will shove adjacent ones partly into new squares. Or, physics lets object sink into each other a tiny bit. A shoved line of six 1x1 cubes won’t be exactly six units long. But the physics system is fun to mess with, as long as you don’t mind having a vaguely board-game-like play area.

1 Like

Mark the piece red, render it as a “ghost” and forbid placement when the player is trying to place it into a wrong position.

Pretty much how games usually deal with building placement in strategy games. In strategy games, when the player is deciding where to place a structure, it is being rendered as a ghost, and if placement is valid, the ghost is green, but if it is invalid, it is red and you can’t place it. In case of board game no point in rendering it as a ghost, but you simply need to indicate invalid placement and refuse to place it at that point.

That’s if you’re moving pieces with mouse.
If it pieces are moved with arrow keys or keypresses, simply refuse to move the piece and beep or something like that.

Definitely do not use physics.

Also, valid and invalid placements should be determined by internal “grid” (class that stores game grid) and not by physics.

1 Like

But that’s for “pick up and move anywhere”, and usually for a multi-square piece. The OP, so far, is just sliding a piece sideways by 1 square.

I’m blown away by the pouring enthusiasm, creativity and helpfulness here. Thanks a million, you all rock!

You gave me a lot of ideas here and I have a lot to play with. I will particularly take the point of avoiding physics, which was where I was about to head into, so you saved me a trip to a rabbit hole, thanks!

I’m trying to keep my code as simple as possible so that my son has at least a chance to understand it and do tweaks to it. For now I have a move script that is attached to all the pieces, which in turn may be movable or fixed depending on what the player is doing. Not sure how to go about the approach where there is a master script that moves all the pieces by iterating on an array :-/ But I think I can work with what I have.

I like the idea of doing passes checking always the next block until there is a space (idea from my GF, another aspiring developer) or raycasting to see if there is a space available between the moving block and the first non-moving block (my son’s idea, but not sure the raycasting functions will support that).

Just a small plot twist to defend against the ideas of using data structures: pieces are not square, they are hexagonal and fit in an hexagonal grid, can move in any of 6 directions, so I prefer to stay away from that idea for now.

Thanks a ton! I’ll try to post an update if I can figure out a simple-to-explain solution to this fun challenge.

It worked!

So what I ended up doing was the second option. Not using physics.

First, I do a ray cast in the direction of movement using a layer mask to only choose the blocking non-moving objects. If there is no hit I can move. If there is a hit, I calculate the distance to that object and do a Physics.RaycastAll, this time without the layer mask, to a distance up to just before the obstacle. I count the number of blocks that are in between and if there are less objects than distance to the blocking object, I can move! Otherwise the entire row is blocked.

Thanks once more, I hope this is useful to someone else.

To clarify, if you’re using raycasts then you’re still using a part of the physics system. If it’s working then don’t sweat it, carry on. Everyone can tell you how to make it “better”, and some of them will even be right, but when you’re starting out just going one step at a time and making things work and then moving on is fine.

Don’t concern yourself with doing it, unless you particularly want to, but since you mentioned it I’ll have a go at explaining this:

You have two classes:

  • GameGrid: This represents the grid your game is played on. The main things of note in here are an array, something like GridObject[,] gridCells, a method like GetObjectInCell(int x, int y), and a method like SetObjectInCell(int x, int y, GridObject obj).

  • GridObject: This represents an object which belongs to a grid. The main things in here of note are a reference to the GameGrid it belongs to*, ints cellX and cellY to track what cell it is in, and a method like MoveToCell(int x, int y). The MoveToCell(…) method does a few important things:

  • Check if the target cell is valid and empty. If not, abort.

  • Move the object to the new cell.

  • Update the cellX and cellY variables to represent its new location in the grid.

  • Call grid.SetObjectInCell(…) to empty the old cell, and fill the new one.

One neat thing here is that you don’t need to do any raycasting. You can just call grid.GetObjectInCell(desiredX, desiredY). If it’s “null” then the cell is empty. You can have decorative elements in cells, or colliders for other things such as event triggers, and they won’t interfere with your game logic. You can decoratively place pieces off-center in your cells, or they can have holes, or fly, or be animated, or whatever because they don’t have to be hit by raycasts. It frees up the layer system to be used for other things. It’ll make some of your math easier or algorithms simpler (ie: you don’t have to do two raycasts and compare their lengths).

You may also be told that the above is faster than doing raycasts. That’s true, but also practically irrelevant since you’re not doing lots of casts all the time for a system like this.

  • Or, optionally, use GetComponentInParent() to look it up when needed if the GridObjects are children of the GameGrid in the scene.
4 Likes

In that case one way to do it is to spawn clickable arrows for valid directions and for valid directions only. And also filter possible keypresses in the same way.

Some reasons in-memory-grids are preferred over raycasts:

o Adding extra stuff over the board (markers, explosions) can mess up the raycasts
o Resizing the board is probably more of a pain. Likewise moving it to a canvas.
o More difficult to have a piece slide over instead of snap
o I believe there’s free pre-written hex-map code, for Unity
o You’ll probably have to use grid-math anyway. Like if you want something to start in the middle (3 up and 7 diagonal) then just to the left (4 up and 6 diagonal. Ugg).
o Easier to resize the dimensions
o Easier to make an extra copy of the board if you want an AI to use X-move look-ahead

In games when people say to split data from representation, that’s usually a bad idea. But a gameboard is a great example where it works.

Of course in a simple sense they’re the same. isOccupied(directionFromMe0to5) could be an array-check, or a raycast. Likewise isOccupied(upCoord, acrossCoord) could work either way.

I think this and the fact it could make it easier to save the state of the board is the only major benefit to an array based board representation.

At least on modern hardware where processing power is cheap.

Processing power hasn’t been a true limitation in this genre for a very long time. Major disadvantages to physics in my opinion are the ways you have to baby it to make it work properly. Pieces falling through the board, flying into the air, not stacking properly, etc are common culprits with a physics-based solution.

If you want to see some of these in action just go play Tabletop Simulator. It’s a physics-based board game engine.

2 Likes

I’m talking raycasting not physics.:roll_eyes:

What if the objects don’t have colliders (which they shouldn’t, they have no reason to)? What if they’re entities? What if the board has differently sized objects, such that a raycast from a queen wouldn’t properly hit a pawn?

Oooooooooooooor, you could just build a data structure based representation (supporting hex grids) and call it a day.

3 Likes

Unity’s raycasts go through the physics engine. :stuck_out_tongue:

1 Like

If your project involves other people, or is moderately complex, this simply isn’t true.

Not a big deal for people learning, but I wouldn’t hire anyone who couldn’t see at least some of the many other benefits.

1 Like