I'm trying to get this grabbing script working with my game. In The game you play as a fish in water, and fish should be able to grab and carry objects.
Problem:
I get that with raycast you fire The ray to certain direction which doesn't work for me in this game. This code is otherwise working fine, but I can't figure how to fix the code so that objects can be grabbed from any direction as long as in range.
What I've tried:
I've tried Physics2D.CircleCast, but there also you have to set the certain direction for the cast(?) Also tried OnTriggerEnter but couldn't get that working either. Is there way to cast ray to all directions(not z)? Or is there some more simple way to do this?
using UnityEngine;
using System.Collections;
public class grabberscript : MonoBehaviour
{
public bool grabbed;
RaycastHit2D hit;
public float distance = 0f;
public Transform holdpoint;
public LayerMask notgrabbed;
void Update()
{
if (Input.GetButtonDown("Fire3"))
{
if (!grabbed)
{
Physics2D.queriesStartInColliders = false;
hit = Physics2D.Raycast(transform.position, Vector2.right * transform.localScale.x, distance);
if (hit.collider != null && hit.collider.tag == "grabbable")
{
grabbed = true;
}
}
else
{
grabbed = false;
}
}
if (grabbed)
hit.collider.gameObject.transform.position = holdpoint.position;
}
}
What you are looking for is Physics2D.OverlapCircle .. instead of casting the circle into a certain direction this only checks whether a collider lies within a circle at given center and radius. You the should not check via a tag but rather use a dedicated layer for all objects that shall be able to be grabbed
public class grabberscript : MonoBehaviour
{
public float distance = 0f;
public Transform holdpoint;
// Use the layer(s) for all objects that shall be grab able
public LayerMask grabbable;
private Transform currentlyGrabbedObject;
void Update()
{
if (Input.GetButtonDown("Fire3"))
{
if (!currentlyGrabbedObject)
{
Collider2D hit = Physics2D.OverlapCircle(transform.position, distance, grabbable);
if (hit)
{
currentlyGrabbedObject = hit.transform;
}
}
else // release currently grabbed object
{
currentlyGrabbedObject = null;
}
}
if (currentlyGrabbedObject)
{
currentlyGrabbedObject.position = holdpoint.position;
}
}
}
If you don't use a Layer as said before you would rather have to use Physics2D.OverlapCircleAll and use the first hit matching your tag e.g. using Linq FirstOrDefault like
using System.Linq;
...
Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, distance);
Collider2D hit = hits.FirstOrDefault(h => h.CompareTag("grabbale");
if (hit)
{
currentlyGrabbedObject = hit.transform;
}
I think you can use an OverlapCircle to check whether the item to grab is in range or not like this:
if(!grabbing)
{
//This will save the position of all gameObjects in a specific range which are set in a or multiple specific layers
Collider2D[] grabbedItems = Physics2D.OverlapCircleAll(transform.position, grabRange, grabbableLayer);
}
Foreach(Collider2D item in grabbedItems)
{
if(item.ComapreTag("grabbable"))
{
item.isGrabbed = true;
}
}
Then in the gameObject that you intend to make grabbable, you can add a script like this:
public bool isGrabbed = false;
public Transform holdPoint;
void Update()
{
if(isGrabbed)
{
TrackHoldPoint();
}
}
private void TrackHoldPoint()
{
transform.position = holdPoint.position;
}
Then just to be easier to set, you can add this small script to see in edit mode the range of the cirlce:
private void OnDrawGizmosSelected()
{
if (transform == null) return;
Gizmos.DrawWireSphere(transform.position, range);
}
Related
I'm trying to pick up objects on my game. so the object that the user is directly looking at can be picked up on a button click. I have LookObject which is a gameObejct that stores the objects that I'm currently looking at. The issue I'm facing here is that the LookObject is NOT accurately showing the objects that I'm looking at.
[Header("InteractableInfo")]
public float sphereCastRadius = 0.5f;
public int interactableLayerIndex;
private Vector3 raycastPos;
public GameObject lookObject;
private PhysicsObjects physicsObject;
private Camera mainCamera;
public GameObject winUI;
private InteractiveObjects interactiveObjects;
void Update()
{
//Here we check if we're currently looking at an interactable object
raycastPos = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hit;
if (Physics.SphereCast(raycastPos, sphereCastRadius, mainCamera.transform.forward, out hit, maxDistance, 1 << interactableLayerIndex))
{
lookObject = hit.collider.transform.gameObject;
}
//if we press the button of choice
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (currentlyPickedUpObject == null)
{
//and we are looking an interactable object
if (lookObject != null )
{
PickUpObject();
}
}
}
//if we press the pickup button and have something, we drop it
else
{
BreakConnection();
}
}
public void BreakConnection()
{
pickupRB.constraints = RigidbodyConstraints.None;
currentlyPickedUpObject = null;
physicsObject.pickedUp = false;
currentDist = 0;
}
public void PickUpObject()
{
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
[Here is the cube inspector][2]
The cube and all other interactable objects have this script:
public class PhysicsObjects : MonoBehaviour
{
public float waitOnPickup = 0.2f;
public float breakForce = 35f;
[HideInInspector] public bool pickedUp = false;
[HideInInspector] public PlayerInteractions playerInteractions;
private void OnCollisionEnter(Collision collision)
{
if (pickedUp)
{
if (collision.relativeVelocity.magnitude > breakForce)
{
playerInteractions.BreakConnection();
}
}
//this is used to prevent the connection from breaking when you just picked up the object as it sometimes fires a collision with the ground or whatever it is touching
public IEnumerator PickUp()
{
yield return new WaitForSecondsRealtime(waitOnPickup);
pickedUp = true;
}
}
You are doing
if (Physics.SphereCast(raycastPos, sphereCastRadius, mainCamera.transform.forward, out hit, maxDistance, 1 << interactableLayerIndex))
{
lookObject = hit.collider.transform.gameObject;
}
with a radius of 0.5 meters!
Then next you do
Physics.Raycast(mainCamera.transform.position, mainCamera.transform.forward, out hit, maxDistance);
without any layer filter so you hit e.g. the floor.
So the check
if (hit.transform)
{
interactiveObjects = hit.transform.GetComponent<InteractiveObjects>();
}
else
{
lookObject = null;
interactiveObjects = null;
}
enters the if case, even then when the hit.transform of the second cast is not he same as the first one.
I don't really understand why you do two casts in the first place. You probably rather want to simply stick with
if (Physics.SphereCast(mainCamera.ViewportPointToRay(Vector2.one * 0.5f), sphereCastRadius, out var hit, maxDistance, 1 << interactableLayerIndex))
// or if you don't want to use a sphere cast anyway
//if(Physics.Raycast(mainCamera.ViewportPointToRay(Vector2.one * 0.5f), maxDistance, 1 << interactableLayerIndex))
{
var lookObject = hit.collider.gameObject;
if(lookObject.TryGetComponent<InteractiveObjects>(out var interactiveObject))
{
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (!currentlyPickedUpObject)
{
// whatever happens in here
PickUpObject(interactiveObject);
}
}
}
}
and simply use a smaller radius for the sphere cast or as commented use the simple RayCast directly if you don't want to use a radius anyway.
in general I would also recommend to rather use a
public LayerMask interactableLayers;
and then directly use interactableLayers instead of 1 << interactableLayerIndex.
I am currently working on a 2D isometric game where the player will be able to control different units, and interact through them.
I created a Scriptable Object called UnitType. Thanks to this system I can define an action range and a moving range, that is the maximum distance in cells that the player can move to or interact with.
The problem is I don't know how to implement this through code. This is what I want to achieve, using an action range of 2 cells for the demonstration.
This is the goal
With a friend of mine, we thought about calculating the linear equation of those 4 lines to check if the raycast hit was within them, but it's not working right with negative x.
This is the current system
What would be the best practice ?
Thank you very much for your time and attention,
Mousehit is a Unity GameObject that is the child of the character. Its positions is bound to the center of the tilemap cells thanks to mousePosition that is a script attached to the GameManager.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class Interact : MonoBehaviour
{
[SerializeField] private Sprite upperLeft, upperRight, midLeft, midRight, bottomLeft, bottomMid, bottomRight;
[SerializeField] private GameObject mouseHit;
public UnitType unitType;
private GameObject gameManager;
private CheckMousePosition mousePosition;
private bool isInteracting, isAtGoodDistance;
private SpriteRenderer spriteRenderer;
private Tilemap tilemap;
private RaycastHit2D hit;
//private Transform mouseHit;
void Start()
{
tilemap = GameObject.Find("Tilemap").GetComponent<Tilemap>();
gameManager = GameObject.FindGameObjectWithTag("GameManager");
mousePosition = gameManager.GetComponent<CheckMousePosition>();
spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
isInteracting = false;
mouseHit.SetActive(false);
}
// Update is called once per frame
void Update()
{
if (isInteracting)
{
mouseHit.SetActive(true);
mouseHit.transform.position = new Vector3(mousePosition.tilemap.GetCellCenterLocal(mousePosition.cellPosition).x, mousePosition.tilemap.GetCellCenterLocal(mousePosition.cellPosition).y, 1);
if (Input.GetMouseButtonDown(0))
{
Interaction();
}
}
if (isInteracting == false)
{
spriteRenderer.sprite = null;
mouseHit.SetActive(false);
}
}
public void InitInteraction()
{
isInteracting = true;
transform.root.GetComponent<ContextualMenu>().CloseContextualMenu();
}
private void Interaction()
{
//When the player interacts, we cast a ray that will determine what he is interacting with.
hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.collider != null)
{
if (isInteracting)
{
isInteracting = false; //the player is no longer interacting.
//If the player is interacting with a farmable cell using the worker.
if (transform.root.name == "Worker"
&& hit.collider.GetComponent<FarmableCell>() != null)
{
CheckInteractionDistance();
//hit.collider.GetComponent<FarmableCell>().CheckIfFarmed();
}
}
}
}
private void CheckInteractionDistance()
{
if (mouseHit.transform.localPosition.y <= (-0.5 * (mouseHit.transform.localPosition.x) + unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x >= (-0.5 * (mouseHit.transform.localPosition.x) - unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x >= (0.5 * (mouseHit.transform.localPosition.x) - unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x <= (0.5 * (mouseHit.transform.localPosition.x) + unitType.actionDistance / 2))
{
Debug.Log("ok");
}
else
{
Debug.Log("not ok");
}
}
}
Im working on a first person shooter. I have an aim function, which puts the pistol right in front of the camera, to make it look like your holding it in front of you. Im trying to make it so the pistol will also rotate with the camera on the Z axis, so that way the pistol wont stay still, because that looks odd and gets in the way. To do this, I tried this:
GPR.gun.transform.rotation = Quaternion.Euler(0, 0, plrCam.transform.rotation.z);, however this ends up rotating the gun very slightly around the z axis, and mainly rotating it around the y axis whenever I move my camera. I am a beginner programmer in Unity so please try to make answers more digestible to beginners so I can understand it. Here is my full script:
using System.Collections.Generic;
using UnityEngine;
public class PistolFire : MonoBehaviour
{
//Gun Properties
public float range = 50f;
public float damage = 10f;
//Sensitivity decrease for looking down the sights
public float downSights = 5f;
//Other vars
private playerGunControls playerGun;
private GameObject plrCam;
private Camera fpsCam;
private ParticleSystem muzzleFlash;
private GameObject impactEffect;
private bool aimed = false;
private GameObject aimPos;
private GunPickupRaycast GPR;
private GameObject handPos;
private GameObject Player;
// Start is called before the first frame update
void Start()
{
//Getting objects because gun is instantiated, so this is necessary
plrCam = GameObject.Find("Player Camera");
playerGun = plrCam.GetComponent<playerGunControls>();
fpsCam = plrCam.GetComponent<Camera>();
muzzleFlash = GetComponentInChildren<ParticleSystem>();
impactEffect = GameObject.Find("Impact Effect");
aimPos = GameObject.Find("aimPos");
GPR = plrCam.GetComponent<GunPickupRaycast>();
handPos = GameObject.Find("Hand Pos");
Player = GameObject.Find("Player");
}
// Update is called once per frame
void Update()
{
//Check for shoot button down
if (Input.GetButtonDown("Fire1"))
{
if (playerGun.holding == "Pistol")
{
Shoot();
}
}
//Check if aim button down
if (Input.GetButton("Fire2"))
{
if (playerGun.holding == "Pistol")
{
Aim();
}
}
//Check if no longer aiming to reset to normal
if (aimed == true && !(Input.GetButton("Fire2")))
{
Unaim();
}
}
void Shoot()
{
muzzleFlash.Play();
RaycastHit hit;
if(Physics.Raycast(plrCam.transform.position, plrCam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Health health = hit.transform.GetComponent<Health>();
if (health != null)
{
health.TakeDamage(damage);
}
//Instantiate the Impact Effect
GameObject IE = Instantiate(impactEffect, hit.point, Quaternion.identity);
Destroy(IE, 1.5f);
}
}
void Aim()
{
aimed = true;
Debug.Log("Aiming");
GPR.gun.transform.position = aimPos.transform.position;
GPR.gun.transform.rotation = Quaternion.Euler(0, 0, plrCam.transform.rotation.z);
}
void Unaim()
{
GPR.gun.transform.position = handPos.transform.position;
Debug.Log("No longer aiming");
aimed = false;
}
}
I fixed my problem by making the gun a child of my camera instead of a child of my player.
I have a problem. I'm doing a project in Unity 3D (c#), a 3D worlds editor. My problem is that I want to move multiple objects by selecting them. I managed to move one with my mouse cursor, but for multiple I failed :D
This is my code to move one :
public class ClickAndDrag : MonoBehaviour {
private RaycastHit raycastHit;
private GameObject Gobj;
private float distance;
private Vector3 ObjPosition;
private bool Bobj;
// Use this for initialization
void Start() {
}
// Update is called once per frame
void Update() {
if (Input.GetMouseButton (0)) {
var ray = GetComponent<Camera> ().ScreenPointToRay (Input.mousePosition);
var hit = Physics.Raycast (ray.origin, ray.direction, out raycastHit);
if (hit && !Bobj) {
Gobj = raycastHit.collider.gameObject;
distance = raycastHit.distance;
Debug.Log (Gobj.name);
}
Bobj = true;
ObjPosition = ray.origin + distance * ray.direction;
Gobj.transform.position = new Vector3 (ObjPosition.x, ObjPosition.y, ObjPosition.z);
} else {
Bobj = false;
Gobj = null;
}
}
}
Thanks for your help!
private GameObject Gobj; is a variable for a single GameObject. Reformat it to private List<GameObject> objects; and instead of Gobj.transform.position = new Vector3 (ObjPosition.x, ObjPosition.y, ObjPosition.z) do this:
foreach (GameObject item in objects)
{
item.transform.position = new Vector3 (ObjPosition.x, ObjPosition.y, ObjPosition.z)
}
EDIT: In case you aren't sure about how to manipulate a List, List<T> has a set of built in functions to make it really easy. You can now just call objects.Add(newObject); to add an object, and objects.Remove(oldObject); to remove an object.
So I have several objects, when I click on one object, the camera zooms in and moves to that object. Then I click on that object and the camera moves to another coordinate where another object is located. However, I would like the user to trace back to the original object and then back to the initial position of the camera. So, I was thinking about implementing a double click to have the camera move back to the original position.
I tried looking for a double click reference and I tried to attach this script to my object. However, nothing happens and I'm not sure why. I even set the camera to main camera.
using UnityEngine;
using System.Collections;
public class DoubleClickBack : MonoBehaviour {
public Camera mainCam;
float doubleClickStart = 0;
void CheckDoubleClick() {
void OnMouseUp() {
if ((Time.time - doubleClickStart) < 0.3f) {
this.OnDoubleClick();
doubleClickStart = -1;
}
else {
doubleClickStart = Time.time;
}
}
}
void OnDoubleClick() {
Debug.Log("Double Clicked!");
mainCam.transform.position = new Vector3(0, 1, -7);
Camera.main.orthographicSize = 0.4f;
}
}
I have even tried to implement a space bar approach instead below and nothing happens as well. I wonder what I am doing wrong. Should there be attached to the object in the inspector window besides the script, animator and box collider?
using UnityEngine;
using System.Collections;
public class SpaceMove : MonoBehaviour {
public Camera mainCam;
void CheckSpace() {
if (Input.GetKeyDown("space"))
print("space key was pressed");
mainCam.transform.position = new Vector3(0, 1, -8);
Camera.main.orthographicSize = 0.4f;
}
}
It's not calling OnMouseUp(), which means these might be happening:
OnMouseUp() is called on the script attached to the object that received the click, so you may have forgot to add the script to that particular object;
The object that was clicked doesn't have a collider.
Since this is a functionality that you may want to make it work with many objects, maybe you want to make it work to any click, even when it doesn't hit anything. This is an example of how you could do it if you want it to work always, with the advantage that you don't need to attach to every object, only one:
void Update()
{
if (Input.GetMouseButtonUp(0))
{
Debug.Log("Pressed left click.");
CheckDoubleClick();
}
if (Input.GetMouseButtonUp(1))
{
Debug.Log("Pressed right click.");
CheckDoubleClick();
}
if (Input.GetMouseButtonUp(2))
{
Debug.Log("Pressed middle click.");
CheckDoubleClick();
}
}
private void CheckDoubleClick()
{
if ((Time.time - doubleClickStart) < 0.3f)
{
this.OnDoubleClick();
doubleClickStart = -1;
}
else
{
doubleClickStart = Time.time;
}
}
I figured out my problem. I forgot to call the function inside Update(). Here's the solution in case anyone is interested:
This is what I currently have running:
using UnityEngine;
using System.Collections;
public class DoubleClickBack : MonoBehaviour {
public Camera mainCam;
float doubleClickStart = 0;
void CheckDoubleClick() {
void OnMouseUp() {
if ((Time.time - doubleClickStart) < 0.3f) {
this.OnDoubleClick();
doubleClickStart = -1;
}
else {
doubleClickStart = Time.time;
}
}
}
void OnDoubleClick() {
Debug.Log("Double Clicked!");
mainCam.transform.position = new Vector3(0, 1, -7);
Camera.main.orthographicSize = 0.4f;
}
}
Also, I changed Input.GetMouseButtonDown(0) to Input.GetMouseButtonUp(0).