Grab objects in Unity like half life 2 - c#

I am doing a little, but I do not really understand the scripts. I want grab objects ingame like portal or half life 2 and I was trying to code a little script for that action. I made this:
using System.Collections.Generic;
using UnityEngine;
using System;
public class PIckUp : MonoBehaviour {
public Transform theDest;
void OnKeyDown()
{
GetComponent<Rigidbody>().useGravity = false;
this.transform.position = theDest.position;
this.transform.parent = GameObject.Find("Destination").transform;
}
void OnKeyUp()
{
this.transform.parent = null;
GetComponent<Rigidbody>().useGravity = true;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
OnKeyDown();
}
if (Input.GetKeyUp(KeyCode.F))
{
OnKeyUp();
}
}
}
I attach this script to empty object called Destination, when I add a component in grabbable object in "theDest" I target Destination. If i press F all objects are flying i no need aim to them and deform all boxes too i leave here a picture.
I dont find a easy solution, i follow this scripts but are to complex for me and i dont understand how use them or how attach them to a player or object.
https://answers.unity.com/questions/1459773/picking-upholding-objects-portal-style.html

Well the thing is each and every object in the entire scene with this script attached will react to the key press.
I would rather
put all your objects you want to be able to pick on a certain Layer e.g. "Pickable"
make sure they all have colliders
Never parent Rigidbody, rather use Joints e.g. ConfigurableJoint could be useful here, you can completely customize it and use e.g. it's connectedAnchor in order to force the object to move towards you
have a kinematic rigidbody on "theDest" so we can use it as anchor for the joint
have a collider on "theDest" so that we can check distances between colliders
Additional to the layer I would also use a dedicated script like
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(SpringJoint))]
public class Pickable : MonoBehaviour
{
[SerializeField] private Rigidbody rigidbody;
public Rigidbody Rigidbody => rigidbody;
private void Awake()
{
if(!rigidbody && !TryGetComponnet<Rigidbody>(out rigidbody))
{
Debug.LogError("This component requires a Rigidbody!", this);
}
}
public void MarkActive(bool active)
{
// Your customerhod for marking an object the currently active one
// e.g. change its color, add outline etc
}
}
This fulfills three purposes
allows to check if hit object even is pickable
precached components so we don't need GetComponent over and over again
can implement additional behavior such as the mark active visualization
And then have only one single script on your player and use e.g.
public class PIckUpController : MonoBehaviour
{
// Reference this via the Inspector
[SerializeField] private Rigidbody theDest;
// Adjust this in the Inspector, select your Layer(s) that should be Pickable
[SerializeField] private LayerMask pickableLayer;
// Maximum range for picking objects in units
// only Pickable objects within this range can be grabbed
[SerializeField] private float range = 1;
// How fast to attract picked objects
[SerializeField] private float dragSpeed = 1;
private Pickable currentHit;
private Pickable currentPicked;
private ConfigurableJoint currentJoint;
private void Awake ()
{
if(!theDest) theDest = GameObject.Find("Destination"). GetComponent<Rigidbody>();
}
void PickUp()
{
currentJoint = currentHit.AddComponent<ConfigurableJoint>();
// Todo Completely customizable behavior
currentPicked = currentHit;
}
void Release()
{
Destroy(currentJoint);
// TODO optional give it some impulse to throw it away here e.g.
// (Have to tweak the values of course)
currentPicked.Rigidbody.AddExplosionForce(10, theDest.position, 1f, 0.5f, ForceMode.Impulse));
currentPicked = null;
}
private void Update()
{
// Have we already picked up something?
if(!currentPicked)
{
// No -> we chekc if we are pointing on any object
// within the range
var ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out var hit, range, pickableLayer)
{
// yes -> get the Pickable component
if(hit.gameObject.TryGetComponent<Pickable>(out var hitPickable))
{
// Is it a different one than the one we already have?
if(hitPickable != currentHit)
{
// yes -> deselect the current one
if(currentHit)
{
currentHit.MarkActive(false);
}
// And store and select the new one
currentHit = hitPickable;
currentHit.MarkActive(true);
}
}
else
{
// If there is no Pickable component we hit something else
// -> deselect and forget the current hit
if(currentHit)
{
currentHit.MarkActive(false);
currentHit = null;
}
}
}
else
{
// We don't hit anything
// -> deselect and forget the current hit
if(currentHit)
{
currentHit.MarkActive(false);
currentHit = null;
}
}
// Do we have a current hit?
if(currentHit)
{
// yes and pressing F -> pick it up
if (Input.GetKeyDown(KeyCode.F))
{
PickUp();
}
}
}
else
{
// We already have picked up something
// of release F -> release object
if (Input.GetKeyUp(KeyCode.F))
{
Release();
}
else
{
// Todo e.g. make the object move towards the "theDest"
}
}
}
}
This one is the main responsible and does
shoot a Raycast to check which object you are pointing at
can additionally (optional) store and mark the currently hit object (as far as I remember HL2 marks them with an outline)
controls the pickup and release of objects

Related

change game objects color and check if it was clicked

the scenario is, i've 15 objects in my scene, each game objects has it own mesh, so i want to change the color of the game object when the player click on it with the mouse, if the player click on the same game objects several times the game objects should change the color randomly, and if the player does not click in the scene within 20 seconds a button should be activated to ask the player to load a new scene, and all my game object should be spawn in the new scene, in the new scene the player has to click on all 15 objects within 15 seconds, if he click on all objects within the 15 seconds the game will over else it the game will reload the first scene,
Just to clarify what you are trying to achieve, as I understand you want:
You have some objects in "sceneA"
whenever one object is clicked it changes its color randomly (let's say it becomes "activated"
after ??? seconds an activated objects turns inactivated again going back to the original color
if the user manages to get them colored (active) all at the same time within 15 seconds a new scene "sceneB" is loaded
So I would rather have a quite simple class on your clickable objects like e.g.
using System.Collections.Generic;
using UnityEngine;
public class ClickableObject : MonoBehaviour
{
// Will hold the references to all currently existing ClickableObject instances
private static readonly List<ClickableObject> _instances = new List<ClickableObject>();
[Header("References")]
[SerializeField] private Renderer _renderer;
[Header("debugging")]
[SerializeField] private bool isActivated;
[SerializeField] private Color _originalColor;
// public read-only accesses
public bool IsActivated => isActivated;
public static IReadOnlyList<ClickableObject> Instances => _instances;
private void Awake()
{
_instances.Add(this);
if (!_renderer)
{
_renderer = GetComponent<Renderer>();
}
_originalColor = _renderer.material.color;
}
private void OnDestroy()
{
_instances.Remove(this);
}
public void SetActivated(bool activated)
{
isActivated = activated;
_renderer.material.color = activated ? new Color(Random.value, Random.value, Random.value, 1.0f) : _originalColor;
}
}
and then handle the clicks and timeout rather in one central controller like e.g.
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ClickController : MonoBehaviour
{
[Header("References")]
// If possible already reference this via the Inspector
[SerializeField] private Camera _mainCamera;
[Header("Settings")]
// Configure this via the Inspector
[SerializeField] private float timeout = 15f;
// Configure this via the Inspector and select the layer you have assigned to the clickable objects
[SerializeField] private LayerMask clickableObjectsLayer = LayerMask.NameToLayer("Default");
// configure this via the Inspector and enter the name or path of your target scene to load
[SerializeField] private string targetScene = "sceneB";
// will store a reference to the currently running reset routine
private Coroutine currenResetRoutine;
private void Awake()
{
if (_mainCamera) _mainCamera = Camera.main;
}
private void Start()
{
// Initially reset all objects to be not "activated"
ResetClickableObjects();
}
private void Update()
{
// wait for a mouse click
if (Input.GetMouseButtonDown(0))
{
// shoot out a raycast and check if you hit something on the clickableObjectsLayer
var ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out var hit, float.PositiveInfinity, clickableObjectsLayer))
{
// Just to be sure check if it is really a ClickableObject
if (hit.transform.TryGetComponent<ClickableObject>(out var clickableObject))
{
// Check if this is not active yet, otherwise ignore the click
if (!clickableObject.IsActivated)
{
// set this object to active -> colored
clickableObject.SetActivated(true);
// if there is no reset routine running start one with the first clicked object
// => from now on have "timout" seconds to click also the rest of the objects
if (currenResetRoutine != null)
{
currenResetRoutine = StartCoroutine(ResetAfterFifteenSecondsRoutine());
}
// Check if all objects are active at the same time
if (ClickableObject.Instances.All(co => co.IsActivated))
{
// cancel the reset routine
StopCoroutine(currenResetRoutine);
// load the targetScene after 2 seconds
// just to have time for e.g. a "YEAH YOU DID IT YOU AWESOME USER!"
// we use "Invoke" since it also works on a disabled component
Invoke(nameof(LoadScene), 2f);
// disable this component so no clicks are tracked anymore
enabled = false;
}
}
}
}
}
}
private void LoadScene()
{
// Go to the target scene configured in the Inspector
SceneManager.LoadScene(targetScene);
}
private static void ResetClickableObjects()
{
// Reset all ClickableObject to be not activated
foreach (var clickableObject in ClickableObject.Instances)
{
clickableObject.SetActivated(false);
}
}
private IEnumerator ResetAfterFifteenSecondsRoutine()
{
// wait for the timeout configured in the Inspector
yield return new WaitForSeconds(timeout);
// Then reset all ClickableObject to be not activated
ResetClickableObjects();
// reset to allow the next timeout to begin
currenResetRoutine = null;
}
}

get transform component via string to track player

im trying to grab the transform component via t.find and then via the transform component, grab the game object component, and then t.LookAt can work, and look at the player.
this is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class gunner : MonoBehaviour
{
//legacy statements: isShot
//these statements initialize all the main variables used in the project. most of these are for the "ai".
#region variables
[Header("gun stats")]
[Space(10)]
public float damage = 10f;
public float range = 100f;
[Space(10)]
[Header("Camera")]
[Space(10)]
public Camera cam;
[Space(10)]
[Header("AI Variables")]
[Space(10)]
public float shootlength;
public bool aiactive;
public float Speed;
public target self;
public Transform t;
public Rigidbody rb;
public string playername = "Player";
public GameObject PlayerObject;
public Transform PlayerTrans;
[Header("Decor")]
public ParticleSystem ps;
AudioSource audioData;
#endregion
#region gunmanager
private void Start()
{
GameObject PlayerObject = GameObject.Find(playername);
Transform PlayerTrans = PlayerObject.GetComponent<Transform>();
}
void Update()
{
if(aiactive && PlayerTrans != null)
{
Debug.Log("player trans found!");
}
if(aiactive && PlayerObject != null)
{
Debug.Log("player gameObject found!");
}
if(aiactive && PlayerTrans != null && PlayerObject != null)
{
Debug.Log("test passed!");
}
audioData = GetComponent<AudioSource>();
if (Input.GetButtonDown("Fire1") && !aiactive)
{
audioData.Play(0);
Shoot();
}
if(aiactive)
{
StartCoroutine(aiman());
}
}
public void Shoot()
{
ps.Play();
RaycastHit hit;
if(Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, range))
{
Debug.Log("Hit:" + hit.transform.name);
target targ = hit.transform.GetComponent<target>();
if (targ != null)
{
if(!aiactive)
{
self.hp += 50;
}
targ.TakeDamage(damage);
}
}
}
public void aiActivity()
{
if(self.hp >= 0)
{
t.LookAt(PlayerTrans);
rb.AddRelativeForce(Vector3.forward * Speed * Time.deltaTime);
RaycastHit hitplayer;
if(Physics.Raycast(cam.transform.position, cam.transform.forward, out hitplayer, shootlength))
{
if(hitplayer.transform == PlayerObject)
{
Shoot();
}
}
}
else
{
rb.useGravity = true;
aiactive = false;
}
}
IEnumerator aiman()
{
yield return new WaitForSeconds(1f);
aiActivity();
}
#endregion
}
so no compiler errors, but it cannot find the player and float towards it, and therefore, I cannot get this to work.
Here are a few issues with the updated snippet, not sure if they will fix all of the errors you are getting.
You are re-using a variable name t. I would also refrain from naming any global variables very short random letters as it can get confusing. What is this a Transform of? If it is the transform of a player, possibly name it PlayerTransfom.
As I mentioned you are re-using a variable name which is not allowed especially with different types as well as re-initilization. It is fine to re-assign a variable such that it is updated, manipulating, changing, etc. the value, but re-declaring the value is not allowed in c# and I do not believe allowed in any other language (at least that I know of).
To be specific, the line target t = hit.transform.GetComponent<target>(); is delcaring t as the script component type target, but you have already declared a Transform named t above it as public Transform t;.
Another issue is you are attempting to assign a variable using a non-static method in a global setting. The line specifically is public Transform player = Transform.Find(playername);. You are not able to set the transform like this outside of a method as the Find method is not static. Change it to be.
public Transform player;
private void Start()
{
player = Transform.Find(playername);
}
The second error is because of the same reason, but due to this line public GameObject PlayerObject = player.GetComponent<GameObject>();. Again, GetComponent is a nonstatic method so can not be called in a global setting. The other issue is GameObject is not a component, it is a type. GameObjects have components so you can not get the component of itself as it does not exist. You are already using Transform.Find which finds a transform, I would recommend just finding the GameObject, storing that reference then get the Transform from the gameObject. You do not really need to store the transform as its own reference if you have the GameObject reference though.
public Transform player;
public GameObject PlayerObject;
private void Start()
{
PlayerObject = GameObject.Find(playername);
player = PlayerObject.transform;
}
The final error being outputted is specifying that you are using LookAt incorrectly. You need to pass in a type of Transform, but you are passing in a type of GameObject. Change it to...
t.LookAt(player);
Most of these issues are rather trivial and straightforward. I would follow along with a tutorial or read through the docs to get a better understanding of C#/Unity/Programming. Being a beginner is fine, but StackOverflow should not just be used as debugging help when the errors are right there. I do not know if the 4 recommendations I mentioned will fully fix your program as I did not run it. There could be more issues than what I had mentioned, but following the errors should tell you what is wrong and why it is wrong. It is printing the line number, file, and reason for why it is breaking.

reference from various scripts causing problems [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
For some reason when I'm in the normal view in-game I am able to link the scripts that I need to call in order to make an animation like so
however, whenever I start the game it automatically removes them from the slots, and it doesn't work
I am completely clueless as to why this may be happening and unity keep giving me errors that say that I'm not setting an instance to my script I really have no clue why this may be happening.
I have 3 scripts that I'm working with that is giving me the problem
one is the main script for the enemy vision (I am referencing the other scripts in this one)
the second is the enemy animation script which makes him go from idle to attack and the third is an animation for the gun model since I had to make it follow the hands of the enemy
scripts attached bellow
1st script(enemyAI):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAi : MonoBehaviour
{
public bool detected;
GameObject target;
public Transform enemy;
public GameObject Bullet;
public Transform shootPoint;
public float shootSpeed = 10f;
public float timeToShoot = 1f;
public EnemyAni Animation;
public GunAni GunAnimation;
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
public void Update()
{
//makes the enemy rotate on one axis
Vector3 lookDir = target.transform.position - transform.position;
lookDir.y = 0f;
//makes enemy look at the players position
if (detected)
{
enemy.LookAt(target.transform.position, Vector3.up);
enemy.rotation = Quaternion.LookRotation(lookDir, Vector3.up);
}
if (detected == true)
{
Animation.LookPlayer = true;
GunAnimation.ShootPlayer = true;
}
if (detected == false)
{
Animation.LookPlayer = false;
GunAnimation.ShootPlayer = false;
}
}
//detects the player
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
detected = true;
target = other.gameObject;
}
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
detected = false;
}
}
}
2nd Script (EnemyAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAni : MonoBehaviour
{
public Animator animator;
public bool LookPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (LookPlayer == true)
{
animator.SetBool("IsShootingStill", true);
}
else
{
animator.SetBool("IsShootingStill", false);
}
}
}
3rd script (GunAnimation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunAni : MonoBehaviour
{
public Animator animator;
public bool ShootPlayer;
public void Start()
{
animator = GetComponent<Animator>();
}
public void Update()
{
if (ShootPlayer == true)
{
animator.SetBool("IsShooting", true);
}
else
{
animator.SetBool("IsShooting", false);
}
}
}
Your code:
void Start()
{
Animation = GetComponent<EnemyAni>();
GunAnimation = GetComponent<GunAni>();
}
Searches the GameObject that holds the EnemyAI script for EnemyAni and GunAni. If the GameObject doesn't have those it will return null.
Sounds like you want to remove that code.
Note that if the scripts exist on a child of that GameObject you will need to use GetComponentInChildren
There are some things to review, good practices:
GameObject target is private for C#, but it is better to put private before.
variable names like Bullet Animation GunAnimation should always begin with lowercase, because they might be confused with a Class Name (search in internet for CamelCase and PascalCase).
Name should be clear. EnemyAni Animation is not clear enough, because Animation maybe any animation (player, enemy, cube, car, etc.).
It is better enemyAnimation to be clear, as you did with GunAnimation (only just with lower case at beginning)
As slaw said, the Animation must be in the GO attached.
about last item
Let's say you have an empty GO (GameObject) called GameObject1 and you attach EnemyAi script to it.
In Runtime (when game mode begins), it will try to find Animation = GetComponent<EnemyAni>();, but it won't find it
Why?
Because GetComponent<> searches for the Component(Class) that is in the <> (in this case EnemyAni) in its GO (in this case, GameObject1), but the unique script attached to GameObject1 is EnemyAI.
So, you have 3 options:
Attach EnemyAni to GameObject1
Create another GO (for example, GameObjectEnemyAni), attach the script EnemyAni and drag and drop GameObjectEnemyAni to GameObject1 and delete Animation = GetComponent<EnemyAni>(); in Start
Keep in mind: if you leave that line of code, instead of getting the script EnemyAni from GameObjectEnemyAni, it will run the code Animation = GetComponent<EnemyAni>(); in Start, and obviously, it won't find it
Create events. It's a really good practice for avoiding code tight coupling, but that is more advanced stuff.

Unity reset Oculus position when key is pressed

I am trying to create a script that will reset (to a specific location) the HMD and controller locations whenever a key is pressed for calibration reasons. I am very new to unity so all I have been able to figure out is how to get key input.
public class resetPosition : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
Debug.Log("pressed");
}
}
You shouldn't directly change the position of the VRCamera.
Rather add a parent GameObject to the camera and change the position of that one instead via e.g. (assuming your script is attahced to the camera)
public class ResetPosition : MonoBehaviour
{
public Vector3 resetPosition;
private void Awake()
{
// create a new object and make it parent of this object
var parent = new GameObject("CameraParent").transform;
transform.SetParent(parent);
}
// You should use LateUpdate
// because afaik the oculus position is updated in the normal
// Update so you are sure it is already done for this frame
private void LateUpdate()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("pressed");
// reset parent objects position
transform.parent.position = resetPosition - transform.position;
}
}
}

How to Destroy multiple gameObject at runtime?

In unity3D I am creating and destroying capsule dynamically at run-time. I used space to create capsule and C for destroying.
I want create multiple object and destroy multiple object at time. When I pressed Space multiple times object is creating multiple time it fine.
But the problem is when I pressed C multiple times only one object is destroying. How I can achieve destroying multiple object? One by one.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DynamicCreate : MonoBehaviour
{
public GameObject caps;
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.Space))
{
createObject();
}
if (Input.GetKeyDown(KeyCode.C))
{
destroyObject();
}
}
private void createObject()
{
caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
}
public void destroyObject()
{
Destroy(caps);
}
}
You can simply create a list of capsules.
Declare a list of capsule like under:
List<GameObject> Capsules;
void Start()
{
Capsules=new List<GameObject>();
}
Add any created capsule to the list:
private void createObject()
{
caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Add(caps);
}
You can then destroy the capsules in the list:
public void destroyObject()
{
foreach(GameObject capsule in Capsules)
{
Destroy(capsule);
}
}
See if that helps.
I would like to add more to bolkay answer. If you want to add and remove one by one best datastructure to use is a queue.
Queue<GameObject> Capsules;
void Start()
{
Capsules = new Queue<GameObject>();
}
private void createObject()
{
GameObject caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Enqueue(caps);
}
public void destroyObject()
{
if(Capsules.Count > 0)
Destroy(Capsules.Dequeue());
}
Queue data structure will give a first in first out. So it will give out first gameobject which you added to the queue at runtime and you wont need a variable to manage the index as in the state of List.
Note: This solution is still very basic but I would recommend you to use object pooling instead of creating and destroying objects at runtime as creating and destorying objects is quite intensive at runtime. So you should unless abosolutely necessary avoid creating and deleting.
Queue<GameObject> pooledObjects;
Queue<GameObject> Capsules;
void Start()
{
pooledObjects = new Queue<GameObject>();
Capsules = new Queue<GameObject>();
}
private void createObject()
{
if(pooledObjects.Count > 0)
{
GameObject fetchedObject = pooledObjects.Dequeue();
fetchedObject.SetActive(true);
//Do translations,scaling or other stuff on this reference
//Add it back to main list of things present in scene.
Capsules.Enqueue(fetchedObject);
} else
{
//Only create objects if necessary
GameObject caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Enqueue(caps);
}
}
public void destroyObject()
{
//here instead of destorying the object we will just disable them and keep them out if scene so we dont have to recreate them everytime
if(Capsules.Count > 0)
{
GameObject fetchedObject = Capsules.Dequeue();
fetchedObject.SetActive(false);//Turn the object off from scene
pooledObjects.Equals(fetchedObject);
}
}
Here is list of supported datastructures in C# you might want to use depending on your required case.

Categories

Resources