GameObjected "sucked" into another GameObject effect while dragging - unity3d

I am building a chemistry game where the user can drag around different atoms and connect them together to build molecules. I am able to get the atoms to "stick" to one another when they collide. However, the spheres are stuck just on the outside of each other. I want to get them to overlap slightly like the picture below.
Basically, I want the user to click and drag the mouse to drag atom spheres within the radius of other atom spheres. When that collision happens, I want the atom to be "sucked" into the other atom. However, the problem I am facing is that because the user is holding on to the atom with the mouse, any sort of movement is negated. I've tried each of the three lines of code below but none of them work in getting that atom to suck into the other one while the user is holding the mouse.
atom_entering.transform.position = Vector3.MoveTowards(atom_entering.transform.position, this_atom.transform.position, 5*Time.deltaTime);
atom_entering.GetComponent<Rigidbody>().MovePosition(this_atom.transform.position + (atom_entering.transform.position - this_atom.transform.position));
atom_entering.GetComponent<Rigidbody>().AddForce(this_atom.transform.position, ForceMode.Acceleration);
If I try to decrease the radius of each atom, it can work but then the atom starts clipping any other collisions like the table itself.
Edit: Asked to post complete code:
void OnCollisionEnter(Collision collision)
{
GameObject atom_entering = collision.gameObject;
GameObject this_atom = this.gameObject;
if(atom_entering.name == "HydrogenPrefab(Clone)" && num_hydrogens < max_hydrogens && !hydrogen_connected)
{
// Code to make the "suction" effect here
// One of the three lines above went here but none of them worked.
// First, make the hydrogen a child of this oxygen atom.
// Next, create a Fixed Joint component on the hydrogen and stick it to the oxygen
atom_entering.transform.parent = this_atom.transform;
atom_entering.AddComponent<FixedJoint>();
atom_entering.GetComponent<FixedJoint>().connectedBody = this_atom.GetComponent<Rigidbody>();
atom_entering.GetComponent<HydrogenCollider>().setConnectedStatus(true);
// Increment the number of hydrogens connected to the oxygen
num_hydrogens += 1;
}
}
In a separate C# script:
void OnMouseDrag()
{
this.GetComponent<Rigidbody>().isKinematic = true;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, Camera.main.farClipPlane))
{
float oldY = this.transform.position.y;
this.transform.position = new Vector3(hit.point.x, oldY, hit.point.z);
}
this.GetComponent<Rigidbody>().isKinematic = false;
}
Edit: Here is a video of how it is currently behaving. https://vimeo.com/185896832
I want that same behavior expect I just want to get it "sucked into" the parent atom. If I do anything with the radius, the atoms sink into the floor a bit which is not what I want. It doesn't make sense to have an object with a normal radius when it interacts with the floor and a different radius when it interacts with an atom.

Well here's how I would do it :
I would create prefabs of my atoms with empty GameObjects as children serving as references (example : OxygenAtom => HydrogenPos1, HydrogenPos2, HydrogenPos3 and HydrogenPos4)
On atoms I would have a script managing the available position and occupied ones
When the atom you're carrying (let's say Atom2) collides with another atom (Atom1), you check the available positions for one that fits the Atom2 type.
If it fits, you tag the available position as occupied and parent Atom2 as child of Atom1 and finally place him at the position you just checked as occupied.
EDIT : here's a sample of code to bind atoms together :
protected void OnCollisionEnter(Collision collision)
{
if(gameObject.name.Equals("Oxygen") && collision.gameObject.name.Equals("Hydrogen"))
{
Debug.Log("Binding...");
StartCoroutine(BindAtomAfterFixedUpdate(collision.gameObject));
}
}
private IEnumerator BindAtomAfterFixedUpdate(GameObject atomToBind)
{
GetComponent<SphereCollider>().isTrigger = true;
// or GetComponent<SphereCollider>().enabled = false;
yield return new WaitForFixedUpdate();
atomToBind.transform.position = transform.FindChild("HydrogenSpot").position;
gameObject.AddComponent<FixedJoint>().connectedBody = atomToBind.GetComponent<Rigidbody>();
GetComponent<SphereCollider>().isTrigger = false;
// or GetComponent<SphereCollider>().enabled = true;
}

Related

Converting mouse coordinates for ui in world space

Im having problems with position convertions. The way im trying to solve it may be very wrong but thats due to inexperience in that case and im up for any suggestion on how to do it differently.
What im trying to do is a gui with a dot graph envelope that the user can change by draging the dots with the mouse.
This is what i would wan it to look like.
https://imgur.com/FP6f1Cz
First i did the UI like normal in overlay but i couldnt get the line renderer to work so i took the whole ui into world space. This makes the line renderer visible. With the UI in world space ive tried both to put the envelope line renderer in the canvas with the rest of the ui and outside the canvas UI.
Here is the code that renders the lines where the dots are and moves the dots when the mouse drags them :
public class Envelope : MonoBehaviour
{
LineRenderer lineRenderer;
// Start is called before the first frame update
void Start()
{
lineRenderer = GetComponentInChildren<LineRenderer>();
}
// Update is called once per frame
void Update()
{
var points = GetComponentsInChildren<EnvelopePoint>().Select(ep => ep.transform.localPosition).ToArray();
lineRenderer.positionCount = points.Length;
lineRenderer.SetPositions(points);
}
}
public class EnvelopePoint : MonoBehaviour
{
[SerializeField] bool isHeld = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (isHeld)
{
// Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 mousePos = Input.mousePosition;
transform.position = mousePos;
}
}
private void OnMouseDown()
{
isHeld = true;
}
private void OnMouseUp()
{
isHeld = false;
}
}
The best result is to put the envelope outside of the canvas.
The lines render well to where the points are but im not able to convert the mouse position to correct coordinates for the dots. When i click on a dot to drag it the dot snaps to a position a bit lower and a bit to the left of the mouse. Like this:
https://imgur.com/3KK6VD3
But then i can drag the dots and the lines adjust perfectly.
I guess i have two questions:
How should i get the mouse position conversion correctly?
Is this a strange or over complicated way of doing this? Is there a more reasonable way?
Id love some tip as well on what i should read up on to better understand the different screen types and how to convert between them.
RectTransformUtility.ScreenPointToWorldPointInRectangle: Transform a screen space point to a position in world space that is on the plane of the given RectTransform.
There is also ScreenPointToLocalPointInRectangle but since you are modifying Line Renderer's points (which are in world space), I think ScreenPointToWorldPointInRectangle best suits your needs.

Best way to create a 2d top down race track procedurally

I am attempting to create a 2d top-down car racing game. This game will have a random road map each time the player plays the game. I have thought about doing this in two different ways: A tilemap, or just generate the roads by placing different prefabs (straight roads, turns, etc). I have decided to go with the prefab route.
The way I believe it should work is to have prefab square "tiles" which have their own colliders set on the edges so I can tell if a player goes off the track in which case they blow up. I would have a MapGenerator Script which will generate an initial random map by keeping track of the last tile placed (including its location and road type: left turn, straight, right, etc). This script will then keep adding onto the road randomly as the player gets closer and closer to the end which makes it an infinite road.
I just want to know if this is just not efficient or if I am thinking of this completely wrong.
Here are a couple of images showing my road tiles which I made in photoshop and then one prefab for a straight road (take note of the colliders on its edges).
A similar game to one I want to make is Sling Drift which I can provide the link if you want. I don't know the policy on adding links to forum chat.
Also, here is my code for the map generator:
//Type of tyle, types are normal (straight road or horizontal road) and turns
public enum MapTileType
{
NORMAL,
N_E,
N_W,
S_E,
S_W
}
//structure for holding the last tile location and its type.
public struct TypedTileLocation
{
public TypedTileLocation(Vector2 pos, MapTileType tyleType)
{
m_tileType = tyleType;
m_position = pos;
}
public Vector2 m_position;
public MapTileType m_tileType;
}
public class MapGenerator : MonoBehaviour
{
//Map Tiles
public GameObject m_roadTile;
public GameObject m_turnNorthWestTile;
//holds all the tiles made in the game
private List<GameObject> m_allTiles;
//Map Tile Widths and Height
private float m_roadTileWidth, m_roadTileHeight;
//Used for generating next tile
TypedTileLocation m_lastTilePlaced;
private void Awake()
{
//store the initial beginning tile location (0,0)
m_lastTilePlaced = new TypedTileLocation(new Vector2(0,0), MapTileType.NORMAL);
//set height and width of tiles
m_roadTileWidth = m_roadTile.GetComponent<Renderer>().bounds.size.x;
m_roadTileHeight = m_roadTile.GetComponent<Renderer>().bounds.size.y;
m_allTiles = new List<GameObject>();
}
// Start is called before the first frame update
void Start()
{
SetupMap();
}
void SetupMap()
{
//starting at the beginning, just put a few tiles in straight before any turns occur
for (int i = 0; i < 6; ++i)
{
GameObject newTempTile = Instantiate(m_roadTile, new Vector2(0, m_roadTileHeight * i), Quaternion.identity);
m_lastTilePlaced.m_tileType = MapTileType.NORMAL;
m_lastTilePlaced.m_position.x = newTempTile.transform.position.x;
m_lastTilePlaced.m_position.y = newTempTile.transform.position.y;
m_allTiles.Add(newTempTile);
}
//now lets create a starter map of 100 road tiles (including turns and straigt-aways)
for (int i = 0; i < 100; ++i)
{
//first check if its time to create a turn. Maybe I'll randomly choose to either create a turn or not here
//draw either turn or straight road, if the tile was a turn decide which direction we are now going (N, W, E, S).
//this helps us determine which turns we can take next
//repeat this process.
}
}
void GenerateMoreMap()
{
//this will generate more map onto the already existing road and then will delete some of the others
}
// Update is called once per frame
void Update()
{
}
private void OnDrawGizmos()
{
}
}
Thanks!
Have you tried splines? They let you make curvy paths like race tracks easily. If not, here is a video that might help: https://www.youtube.com/watch?v=7j_BNf9s0jM.

How can i turn the camera to left and right in unity for a sphere?

updated CODE .. the problem is that i cant move the ball only if i hold the E button than i use WASD or arrows to move the ball...and when i hold Q the camera 2 activates and i can move the ball with WASD or arrows...but with the right axis for that angle...is like ballance that game... i want to not hold the E or Q just to change the camera and move the ball corecctly for that angle with the right axis for the ball but not holding the cameras button
using UnityEngine;
using System.Collections;
public class controlPlayer : MonoBehaviour {
public float viteza ; // this is speed
;
void Start(){
}
void Update()
{
if(Input.GetKey(KeyCode.Q) ) // if i hold Q the camera 2 activates and with WASD i can move the ball
{
float miscaOrizontal = Input.GetAxis("Horizontal");
float miscaVertical = Input.GetAxis("Vertical");
Vector3 miscare = new Vector3(miscaVertical,0.0f,(-miscaOrizontal)); //also here i chande the axis for the camera 2
rigidbody.AddForce(miscare * viteza * Time.deltaTime);
}
if(Input.GetKey(KeyCode.E) ) // here when i press E the camera 1 activates and alos i can move the ball if i keep pressing E
{ float miscaOrizontal2 = Input.GetAxis("Horizontal");
float miscaVertical2 = Input.GetAxis("Vertical");
Vector3 miscare2 = new Vector3(miscaOrizontal2,0.0f,miscaVertical2); // here are the controls for the ball for the camera ..i had to change the axis here..
rigidbody.AddForce(miscare2 * viteza * Time.deltaTime);
}
}
}
}
Alright, so if I'm understanding you correctly, you want the ball to move in relation to which direction the camera is currently facing. As well as you don't want to have to hold down "Q" or "E" while you move your ball with WASD controls.
So there are a few things that I'd like to mention here, so we'll take them one at a time. First off, you shouldn't have "AddForce" inside of an Update call. Update is called every frame whenever it can. It's great for input, but generally you want to call it in "FixedUpdate" instead. It provides a better response across many devices. If you leave it in update, you can get inaccurate physics results, and missed collisions. There's a lot out there on this subject, so if you want to know more about it, then just google for a little while.
As for the code side, you want to store a reference to what camera you're using to avoid having to hold these buttons down.
What I mean is:
private Camera referencedCamera = null;
private void Start()
{
referencedCamera = camera1;
}
private void Update()
{
if(Input.GetKey(KeyCode.Q)) referencedCamera = camera2;
else if(Input.GetKey(KeyCode.E)) refrencedCamera = camera1;
}
This way you can reference which camera is being used, rather than a key input. Something else to look into is the "State Design Pattern". Below is a video over the "State Design Pattern." That is a great video tutorial series that Derek Banas put up on design patterns, extremely recommend watching them, but the state pattern could potentially solve this problem for you as well. So that should take care of not having to hold the button down in order to make the move. More or less, the buttons will now allow you to just switch which camera is currently being used.
https://www.youtube.com/watch?v=MGEx35FjBuo
So now that that is solved, you want to transform the input that you currently have, and you want to put it in terms of hte camera that is currently being used. So to avoid rewriting a lot of the code over and over again, just know that these two code snippets should be pieced together into one. So "referencedCamera" should be already defined in the code I'm about to write:
private void FixedUpdate()
{
if(referencedCamera != null)
{
float miscaOrizontal = Input.GetAxis("Horizontal");
float miscaVertical = Input.GetAxis("Vertical");
Vector3 miscare = referencedCamera.transform.TransformDirection(
new Vector3(miscaVertical, 0.0f, -miscaOrizontal));
rigidbody.AddForce(miscare * viteza * Time.deltaTime);
}
}
So now with this, is it's the same code, but there is a call to the referenced cameras transform to take the vector 3 from the input and put it in relation to the camera instead, and add the new relational vector to the object.
I also want to recommend you looking into events rather than if checks in the update function, but that is beyond the scope of the question. Hope this helps and any questions, don't hesitate to ask.
EDIT
After further discussion, we came across something that should also be mentioned. If your camera that you are referencing is angled, the force will be applied in that angle, so keep that in mind. If it is pointed towards an object downward, then forward relative to the camera will apply a force downward, and not entirely in what you may consider the forward direction. Thought it was worth mentioning.

Only select the first sprite in a pile of sprites (Unity3D 2D-game)

I have a number of sprites on top of each-other on the game board. When i use the mouse and select the sprite on top, all sprites under the mouse position is selected. My question is how to only select the sprite that I click on and not catch the ones below?
Here is the code i am using for my tests, attached to the sprites:
function Update () {
if(Input.GetMouseButtonDown(0)) {
var theActualMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
// print("ALL MousePosition: " + theActualMousePosition);
if(collider2D.OverlapPoint(theActualMousePosition)) {
// print("HIT theActualMousePosition: " + theActualMousePosition);
print(collider2D.gameObject.tag);
}
}
}
The results you are getting are completely expected as your code is placed on the GameObjects, what you should do is to push your script out of those objects or use another function than OverlapPoint (because overlap point does not check for collision it simply checks if you are in the bounds of an object, which means it is valid for all object)
Some ideas :
Using OnMouseDown should provide you with an event only for the first collider encountered
Using A raycast from the camera : Camera.ScreenPointToRay should be able to be used for a raycast and then to check only the first collider encountered
Using Layers depending on layer collision Order.
EDIT :
Or you could also cast the ray the other way around :
function Update () {
if(Input.GetMouseButtonDown(0)) {
var theActualMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 direction = (transform.Position-theActualMousePosition).normalized;
Ray ray = Ray(transform.Position,direction)
if(Physics.Raycast(ray) == null) {
//Then there is nothing between the object and the camera then the object is the first one
print(collider2D.gameObject.tag);
}
}
}

Unity3D: Two objects with the same script

I would like to associate the same script to different empty objects I just use as placeholders in the game. The aim is to exploit their positions so that when the user touch a point in the screen, close to one of these objects, a dedicate GUI appears. The problem is that though the two objects are different their scripts seem to influence each other so that when the game is running and I touch one of these two objects both the gui appears. What am I doing wrong?
....
private var check: boolean;
var topCamera : Camera;
var customSkin : GUISkin;
function Update () {
if (Input.GetMouseButtonDown(0)){
if(Input.mousePosition.x > this.transform.position.x - Screen.width*0.20 && Input.mousePosition.x < this.transform.position.x + Screen.width*20){
if(Input.mousePosition.y > this.transform.position.y - Screen.height*0.2 && Input.mousePosition.y < this.transform.position.y + Screen.height*0.2){
check = true;
}
}
}
if(check){
//the camera zooms forward
}else{
//the camera zooms backward
}
}
function OnGUI () {
if (this.check){
var w = Screen.width;
var h = Screen.height;
var bw = 0.083;
var bws = 0.001 *w;
GUI.skin = customSkin;
GUI.Box(new Rect(w*0.6,h*0.3,w*0.38,h*0.45), "Stuff");
customSkin.box.fontSize = 0.04*h;
customSkin.textField.fontSize = 0.08*h;
customSkin.button.fontSize = 0.04*h;
textFieldString = GUI.TextField (Rect (w*0.62, h*0.39, w*0.34, h*0.1), textFieldString);
if (GUI.Button (Rect (w*0.62,h*0.50, w*bw, h*0.1), "+")) {
if (this.check){
this.check=false;
}else{
this.check = true;
}
//...
}
//...
}
This is probably not working, because you are comparing apples with oranges in your Update() function. Input.mousePosition returns the the position in 2D pixel coordinates and transform.position returns the GameObject's position in 3D world coordinates.
To check if you clicked on an object, you need to attach a Collider to the game object in question and test for collisions using a Raycast in your script. Here is the relavant example from the documentation in JavaScript:
var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, 100)) {
print ("Hit something");
}
The cool thing about this approach is that we are checking for collisions between the Collider and the ray. If you only want to see if you clicked near the GameObject, just make the Collider larger than the GameObject. No need for messing around with inequalities!
If your objective is to click somewhere close to the object and not only at the object, then you have some configurations (positions of those objects in space) where there are space that are close enough to both objects for their GUI to appear and therefore you need some script to decide which one is closer.
I suggest you to implement a monobehaviour that is a singleton that would track those clicks and measure the distance of all objects, to get the closest.
Reading again your post, I think you want to get the GUI just when you click at the object, and when you do this you get both GUIs. I think that's happening because wrong calculation of the area that makes check to go true.
Can you give more details? Is there some space where there shouldn't have GUI messages when clicked, or is everything filled by those objects?