2
\$\begingroup\$

I would like to place object in a 3d world "like minecraft player can do".

What I'm trying to do is to allow the user to place objects, without overlapping them (like in minecraft you can do).

So if the player try to place an object partially over another, my game automatically "move and place" the object in the allowed position.

What can be the right approach ?

\$\endgroup\$
  • 1
    \$\begingroup\$ Are you just asking how to round the float to an int? If so, use Mathf.RoundToInt() \$\endgroup\$ – Griggs Jan 31 '18 at 20:21
  • 1
    \$\begingroup\$ Please keep in mind that Unity is not the best choice for a Minecraft clone. Minecraft uses a custom game engine for a reason. \$\endgroup\$ – Philipp Jan 31 '18 at 20:40
  • \$\begingroup\$ No, i'm not creating a minecraft clone.. i'm just asking how to solve the problem to put object, at runtime, at fixed positions... it's not only "roundToInt" ... it's something (i can't explain well) to put object at fixed positions, based on others object already on screen and my object (based on his size)... yes... i'm not explaining well i know.. \$\endgroup\$ – stighy Jan 31 '18 at 21:10
  • \$\begingroup\$ Will each gameObject have a predefined location (x, y, z), or will each gameObject have a relative position on a certain point/other gameObject? \$\endgroup\$ – Griggs Jan 31 '18 at 22:47
  • 1
    \$\begingroup\$ When you have trouble explaining something, try listing some examples. If it's not always about rounding to an integer, try to describe or draw or model in-engine a situation where the result is not an integer, and show us that example too. From enough clear examples, it becomes easier to identify the rule or pattern that unites them. Here Minecraft might be a red herring, since it is snapped to an integer grid, so try to find examples that break that mold. \$\endgroup\$ – DMGregory Feb 1 '18 at 4:38
4
+150
\$\begingroup\$

Checking collision

OK, let's backtrack here for a second. Let's stop talking about colliders, raycasting, GameObjects, Transforms etc. It's overkill. VOXELS != UNITY. Voxels existed long before Unity, and will long after.

What is a voxel space (as in Minecraft)? Fundamentally, it is this:

Voxel[,,] voxels = new Voxel[VOLUME_WIDTH, VOLUME_HEIGHT, VOLUME_DEPTH];

...a 3D array. THAT array is the basis on which you perform logical checks, NOT colliders.

Yes, you will need to raycast where the user is looking. There are algorithms like 3D DDA that can do this incredibly cheaply without the need for fancy, costly Unity physics-based raycasting.

Alternative placement

Having gotten that out of the way, let's look at alternative placement. You can set whatever criteria you like. I would suggest limiting it, step by step. For example, the first constraint I would put on the location the user is trying to place at is

We can move back, forward, left and right - but we MUST place at the same height.

Already there we've simplified the problem greatly. Now deciding whether we can place for example a 2x2x2 cube into a space looks at the next step: Given a space where the user is pointing at a given height Y at position XZ, does X+1 (or X-1) and Z+1 (or Z-1) accommodate a 2x2 shape. We are now working with planar logic, which is to say working on a flat plane. If there is no 2x2 flat plane in the immediate region (and by plane, I mean area with all the same Y) then we need to look elsewhere nearby, or give the user no alternatives at all.

Another crucial thing is where the ray strikes a surface. If you hit a ground surface (i.e. planar in XZ, such as what you may stand on) cool, let the above apply. However if there is a wall or cliff right in front of you, clearly you cannot place there. You now need to determine if the space directly below that is available for placement. So,

If we are looking at a vertical wall, we must check the placement area directly below it, that is, the first horizontal section below that vertical face, for potential placement.

Gradually, by introducing these rules one by one (and in particular order) you will end up with the behaviour for placement that you desire.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

There are two parts to this question.

  1. Determine if an object can be placed in a given position.
  2. Find the nearest valid location for an object to be placed.

1. Validating Location

This one is fairly simple and ultimitely I feel it's the best.

When the user wants to place an object spawn it in their hands. So they can move it around to determine a position to place it.

In a script on the object respond to the OnCollisionEnter and OnCollisionExit calls. In these methods keep a list of what the object is colliding with. When OnCollisionEnter is hit, store the colliding object in a list and remove it from the list when you see it in the OnCollisionExit call.

Note: while the object is being placed you probably don't want a solid collider for it. So set it's collider to trigger. If you do this then you need to use the OnTriggerEnter and OnTriggerExit methods instead.

While there is an item in the list then the object is colliding with something and can't be placed. Consider swapping the objects material in this state to indicate it.

Then you can simple let the user move it around to a valid location. Letting a user place the object anywhere then "correcting" it for them could cause a disconnect between what the user is trying to do and what the game is doing.

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class ObjectPlacer : MonoBehaviour
{
    private List<GameObject> _collidingObjects = new List<GameObject>();
    private Material _material = null;
    private Color _startingColor = Color.black;

    public Color invalidPlacementColor = Color.red;

    void Start()
    {
        _material = GetComponent<Renderer>().material;
        _startingColor = _material.color;
    }

    void Update()
    {
        if (IsPositionValid())
            _material.color = _startingColor;
        else
            _material.color = invalidPlacementColor;
    }

    void OnTriggerEnter(Collider other)
    {
        if (!_collidingObjects.Contains(other.gameObject))
        {
            _collidingObjects.Add(other.gameObject);
            print("Colliding");
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (_collidingObjects.Contains(other.gameObject))
        {
            _collidingObjects.Remove(other.gameObject);
        }
    }

    bool IsPositionValid()
    {
        return _collidingObjects.Count == 0;
    }
}

Please note that for this sample code to work I made the colliding object a kinematic rigid body with a trigger collider. The OnTriggerEnter is only triggered if one of the two objects touching is a rigid body and the object the behaviour is on has a collider with "isTrigger" set to true.

2. Finding the best location

This part of the problem is fairly hard. The simplest method I can suggest is to use the method in part 1 to determine if you need to do anything. Then iteratively call a method that moves the object away from the collision and check that position.

To check if the object is in a good position you can use a call to physics.spherecast/physics.boxcast/physics.capsulecase and provide parameters that encompass the object being placed. This will give you a RaycastHit if there is a collision. Move the object along the normal and repeat.

Here's some pseudo code

        RaycastHit hit;
        // while the object is colliding move the object by x distance along the normal of collision.
        while (Physics.CapsuleCast(sphereTopPosition, sphereBottomPosition, radius, transform.up, out hit))
        {
            rigidBody.MovePosition(rigidBody.position + hit.normal * someDistance);
        }

The problem with this solution is that you can definitely end up in a situation where the object is just caught in the loop bouncing back and forth between two objects crashing the game.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Try

RaycastHit hit;
if(Physics.Raycast(transform.position, transform.fowards, out hit)){
GameObject item = Instantiate(prefabOfObject, hit.point);
bounds b = item.getComponent<typeofcolliderobjecthas>.bounds;
item.transform.position -= bounds.extents*transform.fowards;
}

I didn’t test this, and it was written in an iPad, but it could work. Note that it should be placed on the camera, and some fields must be replaced.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Spherecast, Capsulecast, or Boxcast (or the Overlap___ versions thereof) would be better than Raycast, due to the volumetric nature of those functions. \$\endgroup\$ – Draco18s no longer trusts SE Feb 10 '18 at 7:13
-1
\$\begingroup\$

In Unity Only Child objects have relative Position to Parent. Otherwise All Global Objects have Position Relative to Origin (0,0,0). Each Child despite having Relative Position have Absolute world Position too, you can assign and use that too. Hope it helps.

| improve this answer | |
\$\endgroup\$
-1
\$\begingroup\$

You could check if it is touching the ground with colliders and if it's not you can make some kind of roll function. Just an idea!

What about raycast themselfs? If you have a 2x2x2 and in this case just to simplify. 5 raycast for each possible direction but -z since we are on a plane and want to check above. Lets say that all your raycast have a length of 1 on all faces. if they come in contact with something you know that this direction isnt an option and you could suggest valid path that have at least 1 unit available for your cube. Just another idea. Of course this is all very linear and i dont have your vision of the project.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This answer would benefit from some elaboration. Could you give an example of what some kind of roll function might look like? \$\endgroup\$ – DMGregory Feb 9 '18 at 1:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.