Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Align a wall to a floor

Discussion in 'Scripting' started by toreau, Apr 9, 2014.

  1. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    Hi!

    I've gone blind on this, but basically I have a first-person player who should be able to build stuff in the game by lying out floor tiles and then "attach" walls etc. to those tiles.

    My problem is the math required to calculate the positioning of wall once they are close to a floor tile, as visualized here (you can see the floor tile, the green line is the wall the player is "holding", and the red line is a Debug.DrawRay):

    $unity3d.png

    Here's the part of my script, and please notice that I make the wall a child of the floor when it comes close enough:

    Code (csharp):
    1. // Update is called once per frame
    2. void Update () {
    3.  
    4.     // Do we have an object?
    5.     if ( itemObject == null ) {
    6.         return;
    7.     }
    8.  
    9.     // Are we holding an object?
    10.     if ( isHoldingObject ) {
    11.  
    12.         // Let the itemObject follow the camera
    13.         itemObject.transform.rotation = new Quaternion( 0.0f, playerCamera.transform.rotation.y, 0.0f, playerCamera.transform.rotation.w );
    14.         itemObject.transform.position = playerCamera.transform.position + playerCamera.transform.forward * distanceFromCamera;
    15.  
    16.         // Are there any other objects close to this itemObject that it can connect to?
    17.         GameObject nearestObject = FindNearestConstructableObject( itemObject );
    18.  
    19.         // Do we have a constructable object close enough?
    20.         if ( nearestObject != null ) {
    21.  
    22.             itemObject.renderer.material.color = legalPositionColor;
    23.  
    24.             // Make itemObject the child of nearestObject
    25.             itemObject.transform.parent = nearestObject.transform;
    26.  
    27.             // Snap itemObject to nearestObject
    28.             if ( currentItem.name == "Wall" ) {
    29.                 Debug.DrawRay( itemObject.transform.position, itemObject.transform.forward, illegalPositionColor );
    30.  
    31.                 //
    32.                 // WHAT SHOULD I DO HERE?!
    33.                 //
    34.             }
    35.  
    36.         }
    37.         else {
    38.  
    39.             itemObject.renderer.material.color = illegalPositionColor;
    40.  
    41.             // Unparent
    42.             itemObject.transform.parent = null;
    43.  
    44.         }
    45.  
    46.         // "Let go" of the object
    47.         if ( Input.GetButtonDown("Fire1") ) {
    48.             inventory.RemoveItem( currentItem );
    49.             isHoldingObject = false;
    50.             itemObject.renderer.material.color = origItemColor;
    51.         }
    52.  
    53.     }
    54.  
    55. }
    56.  
    Any help is much appreciated!
     
  2. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Hi!

    If you parent the wall under the tile,
    and set it's rotation to zero,
    and position to zero,
    and after that move the wall half a tile in either x or z it will align correctly.
     
  3. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    Ok, but how can I then rotate and move the wall towards the player (ie. the side of the floor tile edge closest to the player)?
     
  4. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    What I wrote was for snapping to the tile, having the player no chance to influence its rotation. You could do a raycast and see where your camera hits the tile and then choose a rotation and position out of that.

    If you want to rotate the wall as the camera rotates you need to have it as child under the camera. You cant have both in an easy way :)

    Id go for raycast to hit the tile, look where you hit it and then place the wall as I stated before but different in position and rotation depending on where you hit the tile.

    Edit: Alternativly rotate the wall in it's local-space by 90 if you press a button in combination with my prev. post.
     
  5. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    The itemObject already follows the camera's rotation, without being a child of it, ref. "Let the itemObject follow the camera" in the code above.

    I have thought about using raycasts, but they won't work in the long run; let's say I want to put up a wall on the third floor, where a raycast would go into nothing. Therefore I look for the nearest objects already there, so that if I have two pillars, I can put up a wall in between them (not caring about any floor in that case).

    This is getting frustrating, because in my head I feel I'm pretty close. :)

    On every Update() I check if the itemObject is close to another "constructable" object, in this case the floor tile. If it is, I set the itemObject to be a child of the nearestObject. I also zeroes out itemObject's position and rotation in this case, so that the parent/child are relative to each other.

    Now.

    I know there must be a way to find out the relative x/z angle between these two object as I move around with the itemObject "in hand", and that with that "angle data" (or whatever) can decide a fixed 90-degrees rotation (and position).

    Does it at all make sense? :)
     
  6. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    It makes sense, but I would still do a raycast.

    If your tile has an pivot in the middle then create a vector3 between where your raycast hit and the middle of the tile. Depending on this vectors normalized values (1 to -1) you can calculate how you are supposted to rotate the wall. If you are afraid of having alot of objects ontop of eachother just tag all tiles with "tile" and do a RaycastAll, where you find the topmost "tile".
     
  7. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    But - what about my problem of having a raycast that's not hitting anything when I'm on the third floor and putting up a wall between two pillars? :)
     
  8. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    One thing I'm thinking about now, is that I could probably have a raycast going out of a wall's, floor's, whatever, all sides. And by that I can see if a wall is just between two pillars. Or something. *hmm*
     
  9. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Make some screenshots and explain them, it will make it easier to understand what you mean :)
     
  10. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    Here's a visualization of what I want to do. Even though it's in 2D, it should work in 3D;

    $unity3d_2.png

    I have looked into a solution where I do six raycasts (from each object's direction) to find nearby (and matching) objects that the current object can attach to, but that makes things just harder, as I see it. A Physics.Overlapsphere seems a lot more logical to me, but feel free to prove me wrong. :)
     
  11. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    Alright, here is another approach,

    each tile is in fact a prefab with invisible colliders resembling the walls height, position and rotatons. these can be childs of the tile.

    When you raycast you can always see what wall you hit to place the wall(or use the predefined one and just enable its mesh renderer).
     
  12. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    I think I have thought about the same thing. Are you suggesting that each tile has four "ghost tiles" (as I like to call it) attached to it in all four directions (all but up and down)?

    But doesn't this mean that a scene with lots of building will have a lot of overhead?
     
  13. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,282
    this might do the trick, given that the wall and floor have variables defining their width, height, depth and are centered.
    And I'm not misunderstanding the problem ;)
    Code (csharp):
    1.  
    2. //attach wall to floor, easier to deal with local positions
    3.  
    4. wall.transform.parent = floor.transform;
    5.  
    6. //find possible positions by comparing directions
    7.  
    8. positions = new List<Vector3>();
    9.  
    10. if (Vector3.Dot(wall.localPosition, Vector3.forward) > 0) {
    11.  
    12.     positions.Add(Vector3.forward * floor.depth / 2);
    13. }
    14.  
    15. if (Vector3.Dot(wall.localPosition, Vector3.right) > 0) {
    16.  
    17.     positions.Add(Vector3.right * floor.width / 2);
    18. }
    19.  
    20. if (Vector3.Dot(wall.localPosition, -Vector3.right) > 0) {
    21.  
    22.     positions.Add(-Vector3.right * floor.width / 2);
    23. }
    24.  
    25. if (Vector3.Dot(wall.localPosition, -Vector3.forward) > 0) {
    26.  
    27.     positions.Add(-Vector3.forward * floor.depth / 2);
    28. }
    29.  
    30. //find best position
    31.  
    32. float bestDistSqr = float.PositiveInfinity;
    33. Vector3? bestPosition = null;
    34.  
    35. foreach (Vector3 position in positions) {
    36.  
    37.     float distSqr = (position - wall.transform.localPosition).magnitudeSqr;
    38.    
    39.     if (distSqr < bestDistSqr) {
    40.    
    41.         bestDistSqr = distSqr;
    42.         bestPosition = position;
    43.     }
    44. }
    45.  
    46. //place wall at best position
    47.  
    48. wall.transform.localPosition = bestPosition.Value + Vector3.up * wall.height / 2;
    49.  
    you could also check the walls facing direction if you need walls to be placed with a certain orientation:

    Code (csharp):
    1.  
    2. if (Vector3.Dot(floor.transform.InverseTransformDirection(wall.forward), Vector3.forward) < 0) {
    3. //add the forward position if the wall is facing it
    4. }
    5.  
     
    Last edited: Apr 12, 2014
  14. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    I'll look into this as soon as possible, but I have already two questions:

    Should I assume that floor.depth and floor.width is localPosition.z and localPosition.x respectively?

    Also, what is bestPosition.Value?

    Thanks!
     
  15. mr malee

    mr malee

    Joined:
    Sep 19, 2012
    Posts:
    25
    depth is the size of the object on z axis. Width is size on "x" axis.

    bestPosition.Value is the Vector3 value of the nullable type variable "bestPosition"
     
    Last edited: Apr 12, 2014
  16. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    Here's the code I ended up with, but it still doesn't work. The new position is nowhere close to the nearestObject's position (ie. the floor's position).

    Code (csharp):
    1. // Parenting
    2. itemObject.transform.parent = nearestObject.transform;
    3.    
    4. // Find possible positions by comparing directions
    5. List<Vector3> positions = new List<Vector3>();
    6.  
    7. if ( Vector3.Dot (itemObject.transform.localPosition, Vector3.forward) > 0 ) {
    8.     positions.Add ( Vector3.forward * nearestObject.collider.bounds.size.z / 2 );
    9. }
    10.  
    11. if ( Vector3.Dot (itemObject.transform.localPosition, Vector3.right) > 0 ) {
    12.     positions.Add ( Vector3.right * nearestObject.collider.bounds.size.x / 2 );
    13. }
    14.  
    15. if ( Vector3.Dot (itemObject.transform.localPosition, -Vector3.right) > 0 ) {
    16.     positions.Add ( -Vector3.right * nearestObject.collider.bounds.size.x / 2 );
    17. }
    18.  
    19. if ( Vector3.Dot (itemObject.transform.localPosition, -Vector3.forward) > 0 ) {
    20.     positions.Add ( -Vector3.forward * nearestObject.collider.bounds.size.z / 2 );
    21. }
    22.  
    23. // Find best position
    24. float bestDistSqr = float.PositiveInfinity;
    25. Vector3? bestPosition = null;
    26.  
    27. foreach ( Vector3 position in positions ) {
    28.     float distSqr = ( position - itemObject.transform.localPosition ).sqrMagnitude;
    29.  
    30.     if ( distSqr < bestDistSqr ) {
    31.         bestDistSqr  = distSqr;
    32.         bestPosition = position;
    33.     }
    34. }
    35.  
    36. // Place wall at best position
    37. itemObject.transform.localPosition = bestPosition.Value + Vector3.up * itemObject.collider.bounds.size.y / 2;
    Anything obviously wrong here?

    UPDATE: One obvious thing I see, is that bestDistSqr is never being used for anything.
     
    Last edited: Apr 12, 2014
  17. Gustav_Bok

    Gustav_Bok

    Joined:
    Dec 6, 2012
    Posts:
    35
    How far away is it? Give us some numbers, like the position it gets after it is placed, the tiles position,rotation and size.
     
  18. toreau

    toreau

    Joined:
    Feb 8, 2014
    Posts:
    204
    Here's a screenshot from the scene view, which seems to illustrate that the wall is being snapped into place "one floor tile" away from the floor tile towards the player.

    $Screen Shot 2014-04-16 at 16.36.55.png