How do I place an object with UI buttons in Unity? - c#

I want to place an object in-game. I have a UI button to make the object appear, but as Unity only runs the function triggered by the button one time, the engine doesn't continuously move the object to the position of the mouse, which is what I want it to do. How do I fix it?
This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlaceObjectsWithUI : MonoBehaviour
{
[SerializeField]
private GameObject placeableObjectPrefab;
private GameObject currentPlaceableObject;
public void PlaceHouse()
{
if (currentPlaceableObject == null)
{
currentPlaceableObject = Instantiate(placeableObjectPrefab);
}
else
{
Destroy(currentPlaceableObject);
}
if (currentPlaceableObject != null)
{
MoveCurrentPlaceableObjectToMouse(); //This is the function I want to be repeated
ReleaseIfClicked();
}
}
private void MoveCurrentPlaceableObjectToMouse()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
currentPlaceableObject.transform.position = hitInfo.point;
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
}
}
private void ReleaseIfClicked()
{
if (Input.GetMouseButtonDown(0))
{
currentPlaceableObject = null;
}
}
}
I tried a while-loop to make Unity run the code until the mouse is clicked, but then Unity freezes. I think it gets stuck in the while-loop.

I think what you're after is the Update method? If you place the final if statement found within PlaceHouse() into the Update method, then it will constantly be checked.
Try adding this:
private void Update()
{
if (currentPlaceableObject != null)
{
MoveCurrentPlaceableObjectToMouse();
ReleaseIfClicked();
}
}

Related

W, A,S, D movement in Unity, don't know how to use new funtions

Hello i have a problem with animation controller script used to play animations depending on which key is pressed
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimationController : MonoBehaviour
{
function UpdateAnimations()
{
if (Input.GetKeyDown(KeyCode.W))
{
animation.CrossFade("goup");
}
else if (Input.GetKeyDown(KeyCode.A))
{
animation.CrossFade("goleft");
}
else if (Input.GetKeyDown(KeyCode.D))
{
animation.CrossFade("goright");
}
else if (Input.GetKeyDown(KeyCode.S))
{
animation.CrossFade("godown");
}
}
void Start()
{
}
// Update is called once per frame
void Update()
{
UpdateAnimations();
}
It says that "Component.animation" is too old, and i should use GetComponent but i don't know how to
First of all you most probably mean void instead of function as your code is in c# not unityscript (which is also long deprecated by now)
And then Yes, how old is that code you got there? The direct accesses to things like Component.animation, Component.renderer, Component.camera were deprecated years ago ^^
As the error already tells you rather use e.g. Component.GetComponent like e.g.
public class AnimationController : MonoBehaviour
{
// Reference this via the Inspector in Unity
// via drag and drop. Then you don't need GetComponent at all
[SerializeField] private Animation _animation;
private void Awake()
{
// or as fallback get it on runtime
if(!_animation) _animation = GetCompoenent<Animation>();
}
// Update is called once per frame
private void Update()
{
UpdateAnimations();
}
private void UpdateAnimations()
{
if (Input.GetKeyDown(KeyCode.W))
{
_animation.CrossFade("goup");
}
else if (Input.GetKeyDown(KeyCode.A))
{
_animation.CrossFade("goleft");
}
else if (Input.GetKeyDown(KeyCode.D))
{
_animation.CrossFade("goright");
}
else if (Input.GetKeyDown(KeyCode.S))
{
_animation.CrossFade("godown");
}
}
}
How have you defined "animation" in your code?
you could try adding animation reference in the top of the code:
private Animation animation;
and in your Start() or Awake() method, add:
// will add reference to the animation to be the Animation component
// in this GameObject
animation = GetComponent<Animation>();

Unity (C#) - How do I single out GameObject being detected by Raycast in a destroy / respawn system

I'm trying to make a Destroy gameobject, wait x seconds, respawn gameobject system. I have 2 scripts and I'm destorying then instantiating it again. I want to use multiples of the same prefab called "Breakable" but have only the one I'm aiming at being destroyed. Similar to games like Minecraft, aim and only the aimed at the block is destroyed.
BlockBreakItem script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BlockBreakItem : MonoBehaviour
{
RaycastHit hit;
int layerMask = 1;
public GameObject breakableObject;
public bool isObjectDestoryed = false;
public int score = 0;
// Update is called once per frame
void Update()
{
breakableDetection();
}
void breakableDetection()
{
Ray rayLocation = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(rayLocation, out hit, 1000, layerMask))
{
print("Detected");
if (Input.GetKey(KeyCode.Mouse0))
{
breakableObject = GameObject.Find("Breakable");
Destroy(breakableObject);
isObjectDestoryed = true;
score = score +1 ;
}
}
}
}
RespawnBrokenObject script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RespawnBrokenObject : MonoBehaviour
{
private BlockBreakItem BlockBreakItem;
public GameObject breakablePrefab;
// Start is called before the first frame update
void Start()
{
BlockBreakItem = GameObject.FindObjectOfType<BlockBreakItem>();
}
// Update is called once per frame
void Update()
{
if (BlockBreakItem.isObjectDestoryed == true)
{
Invoke("respawnObject", 5.0f);
BlockBreakItem.isObjectDestoryed = false;
}
}
void respawnObject()
{
GameObject tempObjName = Instantiate(breakablePrefab);
tempObjName.name = breakablePrefab.name;
BlockBreakItem.breakableObject = tempObjName;
}
}
I hope the code isn't too messy! Always worried it won't be understood xD
Fortunately it's easy and basic in Unity!
You don't do this:
breakableObject = GameObject.Find("Breakable");
The good news is the cast will tell you what object you hit! Great, eh?
It's basically:
hit.collider.gameObject
You can use hit.collider.gameObject.name in a Debug.Log for convenience.
It's that easy!
Note you then (if you need it) get your component on that object with something like .GetComponent<YourBreakishBlock>()... easy.
Note that you can CHECK if the object hit, is one of the "YourBreakishBlock" objects, by simply checking if that component is present.
Note simply google something like "unity physics raycast out hit, which object was hit ?" for endless examples,
https://forum.unity.com/threads/getting-object-hit-with-raycast.573982/
https://forum.unity.com/threads/changing-properties-of-object-hit-with-raycast.538819/
etc.
--
Make this simple class:
public class ExamplePutThisOnACube: MonoBehavior
{
public void SAYHELLO() { Debug.Log("hello!"); }
}
Now put that on SOME cubes but NOT on OTHER cubes.
In your ray code:
ExamplePutThisOnACube teste =
hit.collider.gameObject.GetComponent<ExamplePutThisOnACube>();
and then check this out:
if (teste != null) { teste.SAYHELLO(); }
Now run the app and try pointing at the various cubes!

interact with gameobjects in Unity

I want to interact with gameobjects in Unity. These objects might be keys, doors, chests, ...
Let's say there is a key on a table. When the player comes closer it should get outlined by a shader and a text appears "pick up key".
I created an interface for interactable gameobjects.
public interface IInteractable
{
string InteractabilityInfo { get; }
void ShowInteractability();
void Interact();
}
By doing this I can add the interface component to my Key script
public class Key : MonoBehaviour, IInteractable
{
public string InteractabilityInfo { get { return "some text here"; } }
public void ShowInteractability()
{
// Outline Shader maybe?
}
public void Interact()
{
// Do something with the key
}
}
When it comes to a script that checks if something is interactable I created a script that creates a raycast which checks for interactables. (I attach this script to my FPS camera)
public class InteractabilityCheck : MonoBehaviour
{
private const int RANGE = 3;
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
if (Input.GetKeyDown(KeyCode.E))
{
interactable.Interact();
}
}
}
}
}
This script tries to get the interface component and calls the methods from it if it's not null.
This code works fine but what I don't like is that the Raycast fires one per frame. Is there another way achieving interactability?
Yes, raycasting is "expensive" and calling it every single frame is not cool at all and there are many ways to avoid that. In your case you can simple restructure the code like that:
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
interactable.Interact();
}
}
}
}
Also a good idea is to get the gameplay logic in a separed method just so so you can modify it easier later and avoid spaggeti xaxa. Like so:
public void interactWithYourObject()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
interactable.Interact();
}
}
}
and then in your update just call it if your condition is true like that:
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
interactWithYourObject()
}
}
An alternative method is to place a spherical trigger on the interactables, you can then control the range through the radius of the spherical trigger.
This is similar to another question I just answered so I've just reworked the code.
using UnityEngine;
[RequireComponent(typeof(SphereCollider))]
internal abstract class CollisionTrigger : MonoBehaviour
{
private bool _isPlayerInsideTrigger = false;
private void Awake()
{
if(!GetComponent<SphereCollider>().isTrigger)
{
Debug.LogError("Please set the sphere collider to a trigger.");
enabled = false;
return;
}
}
private void Update()
{
if(_isPlayerInsideTrigger)
{
FakeOnTriggerStay();
}
}
private void OnTriggerEnter(Collider collider)
{
if(!collider.CompareTag("Player")) return;
_isPlayerInsideTrigger = true;
}
public abstract void FakeOnTriggerStay();
private void OnTriggerExit(Collider collider)
{
if(!collider.CompareTag("Player")) return;
_isPlayerInsideTrigger = false;
}
}
This is an example to demonstrate what your Key class would look like using the provided class above.
internal class Key : CollisionTrigger
{
public override void FakeOnTriggerStay()
{
// Show the user they can now interact with it.
// Outline Shader maybe?
if(Input.GetKeyDown(KeyCode.E))
{
// Do something with the key.
}
}
}

Unity3D using OnTriggerStay

I'm using the event OnTriggerStay2D to destroy an object, the reason i'm using this instead of OnTriggerEnter2D is because i'm dragging the object using the touchscreen, and i want to destroy it after it is released, the problem i have is that OntriggerStay2D is not always called, so sometimes the object is not destroyed after it is released and it has to be moved again to work, i've read the docummentation from Unity
OnTriggerStay is called almost all the frames for every Collider other that is touching the trigger.
public void OnTriggerStay2D(Collider2D other)
{
if (gameObject.tag == other.tag) {
Destroy (other.gameObject);
}
}
I would like to know if there's any way to call OntriggerStay2D everytime i release the object.
Thanks.
Edit
Dragging code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Drag : MonoBehaviour {
private bool draggingItem = false;
private GameObject draggedObject;
private Vector2 touchOffset;
void Update ()
{
if (HasInput)
{
DragOrPickUp();
}
else
{
if (draggingItem)
DropItem();
}
}
Vector2 CurrentTouchPosition
{
get
{
Vector2 inputPos;
inputPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
return inputPos;
}
}
private void DragOrPickUp()
{
var inputPosition = CurrentTouchPosition;
if (draggingItem)
{
draggedObject.transform.position = inputPosition + touchOffset;
}
else
{
RaycastHit2D[] touches = Physics2D.RaycastAll(inputPosition, inputPosition, 0.5f);
if (touches.Length > 0)
{
var hit = touches[0];
if (hit.transform != null && hit.rigidbody != null)
{
draggingItem = true;
draggedObject = hit.transform.gameObject;
touchOffset = (Vector2)hit.transform.position - inputPosition;
}
}
}
}
private bool HasInput
{
get
{
return Input.GetMouseButton(0);
}
}
public void DropItem()
{
draggingItem = false;
}
}
Avoid using OnTriggerStay2D for this. You can use a boolean variable that you set to true and false in the OnTriggerEnter2D and OnTriggerExit2D function.
bool isTouching = false;
void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("Entered");
if (collision.gameObject.CompareTag("YourOtherObject"))
{
isTouching = true;
}
}
void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("Exited");
if (collision.gameObject.CompareTag("YourOtherObject"))
{
isTouching = false;
}
}
You can now check the isTouching variable when the object is released.
if(isTouching){
....
}
Note that I suggest you abandon your current code that uses Raycast and Input.GetMouseButton(0); since you are using this on mobile devices too. You should be using Unity's new EventSystem for this since it is made to be mobile friendly too.
Since you are using 2D collider, see #7 from this answer.
Here is a complete example of how to drag a Sprite with the new EventSystem. Combine that with the answer above and you get a much more better solution.

Coroutine stops working

I have two scripts with a coroutine in them. It works perfectly fine in the first one, but not in the second one, for no apparent reason.
It works in this one:
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.ImageEffects;
public class GameStartController : MonoBehaviour {
public Button startButton;
public GameObject cubeSpawner;
// Use this for initialization
private void Start() {
startButton = startButton.GetComponent<Button>();
}
public void StartGame() {
EnableCubeSpawner();
SpawnStartingCubes();
HideStartMenu();
StartCoroutine("FocusCamera");
PlayBackgroundMusic();
}
// Enables the cube spawner, so it can start spawning cubes
private void EnableCubeSpawner() {
cubeSpawner.SetActive(true);
}
private void SpawnStartingCubes() {
cubeSpawner.GetComponent<CubeSpawner>().GenerateStartingCubes();
}
private void PlayBackgroundMusic() {
var audio = GameObject.FindWithTag("Audio").GetComponent<AudioController>();
audio.PlayBackgroundMusic();
}
private void HideStartMenu() {
startButton.transform.parent.GetComponent<CanvasGroup>().interactable = false;
startButton.transform.parent.GetComponent<CanvasGroup>().alpha = 0f;
}
private IEnumerator FocusCamera() {
var camera = GameObject.FindWithTag("MainCamera").GetComponent<Camera>();
var velocity = 0f;
while (Mathf.Abs(camera.GetComponent<DepthOfField>().aperture) > 0.001f) {
Debug.Log(Mathf.Abs(camera.GetComponent<DepthOfField>().aperture));
camera.GetComponent<DepthOfField>().aperture = Mathf.SmoothDamp(camera.GetComponent<DepthOfField>().aperture, 0f, ref velocity, 0.3f);
yield return null;
}
camera.GetComponent<DepthOfField>().aperture = 0f;
}
}
The coroutine works just fine and the camera aperture goes smoothly from 0.6 to 0.
However in the second script this doesn't happen:
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityStandardAssets.ImageEffects;
public class GameOverController : MonoBehaviour {
public void EndGame() {
StartCoroutine("UnfocusCamera");
DisableCubeSpawner();
DestroyAllCubes();
StopBackgroundMusic();
ShowStartMenu();
}
// Disables the cube spawner, so it can stop spawning cubes
private void DisableCubeSpawner() {
var cubeSpawner = GameObject.FindWithTag("CubeSpawner");
cubeSpawner.SetActive(false);
}
private void DestroyAllCubes() {
var gameObjects = FindObjectsOfType(typeof(GameObject));
foreach (var gameObject in gameObjects.Where(gameObject => gameObject.name.Contains("Cube"))) {
Destroy(gameObject);
}
}
private void StopBackgroundMusic() {
var audio = GameObject.FindWithTag("Audio").GetComponent<AudioController>();
audio.StopBackgroundMusic();
}
private void ShowStartMenu() {
var startMenu = GameObject.FindWithTag("StartMenu");
startMenu.GetComponent<CanvasGroup>().interactable = true;
startMenu.GetComponent<CanvasGroup>().alpha = 1f;
}
private IEnumerator UnfocusCamera() {
var camera = GameObject.FindWithTag("MainCamera").GetComponent<Camera>();
var velocity = 0f;
while (camera.GetComponent<DepthOfField>().aperture < 0.6f) {
Debug.Log(Mathf.Abs(camera.GetComponent<DepthOfField>().aperture));
camera.GetComponent<DepthOfField>().aperture = Mathf.SmoothDamp(camera.GetComponent<DepthOfField>().aperture, 0.6f, ref velocity, 0.3f);
yield return null;
}
// camera.GetComponent<DepthOfField>().aperture = 0f;
}
}
It only works for one frame (aperture goes from 0 to 0.03), and then just stops.
Why is this happening?
If you destroy (or disable) a game object, coroutines running on components attached to it will stop. I'm unable to find a primary source on this, but here are two other stack overflow questions where this was the problem:
Unity3d coroutine stops after while-loop
Unity - WaitForSeconds() does not work
The coroutine stops because the GameObject your GameOverController is attached to is destroyed. Presumably Unity checks whether an object still exists before resuming its coroutine, and if the object is destroyed, Unity does not continue to execute it.
To fix this problem, you can delay destroying the GameObject until the animation is complete (perhaps putting the destroy code after the while loop in the coroutine) or put the component on a GameObject that won't be destroyed.
In most cases this mean that your object or script became inactive. Best way to check this is to add OnDisable() method to your script and call logging from it

Categories