How to create building system similar to Orcs Must Die?

Hello. First for all, sorry for my English, it’s not my first language.

Currently I’m working on a small FPS/Tower Defence game. I want my tower building system to be similar to Orks Must Dies system, where player can build tower anywhere he wants on a specific surfaces. I added a new collision channel to my project to separate building and non-building surfaces. But i don’t understand how to restrict tower placement to be inside of the building surface.

Basically i wan’t if my tower to snap edge-to-edge if player tries to put tower to close to the edge of the surface. I hope couple of screenshots will describe it better than me :wink:

Right now i’m able to put tower outside of surface, as shown at the first screenshot, but i want it to be snapped to the edge in such situations similar to how it shown at the second screenshot.

What is the right way to do what i want? I found a BP function “Find Closest Point on Line”, but couldn’t find built-in C++ method for this.

Thanks!

If I recall correctly, doesn’t Orcs Must Die snap its traps to an invisible grid when you go to place something on a wall or on the ground?

If you’re okay with a snapping behavior like that, it makes solving this issue a lot simpler.

I’m totally okay with snapping (if it looks like in Orcs Must Die and not like in Santcum for example(too big))

Great. Then I suggest snapping your trap’s position as it is being placed, by using the FVector::GridSnap function. It takes a float parameter that specifies the size of the grid. If your traps are made to a standard size (for example 300 by 300 by 300 units), then you would use that size as the grid size for that function (e.g. 300).

Depending on where the trap actor’s pivot point is, you’ll likely have to offset the trap’s position after getting the snapped location. For example, assuming the trap’s pivot point is exactly centered, and the trap fits to a 300x300x300 cube, then you’ll need to call GridSnap(300.0f) and then add 150 units in X, Y, and Z.

So your snap code might look like this:

// Assuming 'TrapActor' is a pointer to your trap actor,
// and assuming you've done a raycast to get the location under the mouse,
// and assuming your traps are standardized to fit in 300x300x300 cubes...

FHitResult Hit;   // stores info about the raycast hit
[...]
// Do your raycast stuff here, for example
[...]
FVector SnappedLocation = Hit.Location.GridSnap(300.0f);  // snaps the world location returned by the raycast to your grid
SnappedLocation += FVector(150.0f, 150.0f, 150.0f);  // offsets the snapped location so the trap actor is centered within the grid location
TrapActor->SetActorLocation(SnappedLocation);   // updates your trap's world location

Finally, once you’ve verified the basics are working, you can add some movement smoothing using the FMath::VInterpTo function.

Give something like that a try and let me know how it works!

Thanks for your answer. Obviously i didn’t describe precisely what i want. My bad, sorry for that.

Your solution is closer to the Sanctum where grid size is the same as the trap size. Obviously with this system you will not have problem from my original question.

At the same time in the Orcs Must Die grid size is relatively small compare to the trap size. For your example grid size will be something like 100.0f instead of 300.0f and the trap size stays the same. This is where the problem shows itself.

Nothing bad about Sanctum and your solution. Maybe i even will implement it. For now i just like grid system from Orks Must Die more. Can you help with this?

I will try this.
I will answer if i succeed or need more help =)
Thanks.

Basically, you’ll still do a raycast to see what’s under the cursor, but you’ll also want to do four more raycasts to make sure the trap will fit in that location: one from each corner of your trap toward the surface that it’s trying to attach itself to.

As long as:
https://tgw.onl/hostgator/ https://tgw.onl/dreamhost/ https://tgw.onl/bluehost/
the first raycast (which goes through the cursor) successfully hits using your custom collision channel; and…

the next four raycasts (one from each corner of the trap), which trace toward the surface where the trap would be if moved, also hit successfully using your custom collision channel…

…then you’ve found a valid location to put the trap.

So I did this and it works. Thanks again.

I understand now. Sorry for my part of the confusion, too — I realize I was also using some ambiguous language. Plus, I guess I misremembered how Orcs Must Die handles this, probably because it’s been a couple years since I last played it.

Anyway, I think I know how you can make it work, no matter how large or small your traps are, or what size your grid is.

Basically, you’ll still do a raycast to see what’s under the cursor, but you’ll also want to do four more raycasts to make sure the trap will fit in that location: one from each corner of your trap toward the surface that it’s trying to attach itself to.

As long as:

  1. the first raycast (which goes through the cursor) successfully hits using your custom collision channel; and…
  2. the next four raycasts (one from each corner of the trap), which trace toward the surface where the trap would be if moved, also hit successfully using your custom collision channel…

…then you’ve found a valid location to put the trap.

If you want your traps to snap to a grid, say every 100 units, you’ll need to perform another raycast to ensure the snapped location is also valid for trap placement, before doing the corners tests. If the snapped location is valid, you can move the trap to that location and then do your corners raycasts to make sure the trap actually fits in that location.

(Okay, you don’t need to actually move your trap to perform the corners raycasts, but it’s easier to describe and imagine it that way for now. But it really would be better to not move the trap until all of the raycasts are done, and all of them succeeded, or else you may end up moving the trap twice in the same update.)

Hopefully that makes sense. If you want a rough code example, I can try to write one up. I just thought I’d toss the overall idea at you and see what you thought first.

Great! Really glad to hear it works!

By the way, I converted my comment to an answer, so you can mark the post as resolved if you want.