Unity3d script losing AudioClip reference when using AddComponent() - audio

I have script (#2) below with a public AudioClip variable. When I 'addComponent', it loses that reference.
I've tested manually adding it to an object in the editor, and in that case it works OK. Why does my script added during runtime lose the reference?
GameObject 1: has this script (#1) attached
void HitByRay() {
clock = GameObject.FindGameObjectWithTag("Clock_Arrow").GetComponent<Clock>();
clock.gameObject.AddComponent<Tick_Audio_Script>();
}
Which attaches the following script (#2) to the 'Clock' object.
public int safetyCounter;
float gapToNext;
public AudioClip tickAudio;
// Use this for initialization
void Start () {
startTicker(100);
}
void startTicker(int maxTicks)
{
safetyCounter = maxTicks;
gapToNext = 1f;
playTick();
}
void playTick()
{
Debug.Log("Tick");
if (gapToNext < 0.1 || safetyCounter == 0)
{
Debug.Log("We're done...!");
return;
}
// **ERROR HERE CLIP NOT FOUND**
AudioSource.PlayClipAtPoint(tickAudio, gameObject.transform.position, 1f);
gapToNext = gapToNext * 0.97f;
safetyCounter--;
Invoke("playTick",gapToNext);
}
Here's the script in the editor, where I've assigned the audioclip.
But when it's attached via 'AddComponent', the reference to that clip does not come through (after I hit play and 'hit' my trigger object which attaches this script)? This results in a null reference error as there is no clip found to be played.
My AudioListener (located on a different object) is working, as there are other sounds being played correctly in the scene.
Again, I've tested adding this script manually to any object pre-run in the editor it works. Why is this?

This has a simple solution. You can do one of the following:
1) Create a prefab and add it to "clock" with the needed references.
2) do this:
void HitByRay() {
clock = GameObject.FindGameObjectWithTag("Clock_Arrow").GetComponent<Clock>();
clock.gameObject.AddComponent<Tick_Audio_Script>();
//NEW PART
clock.gameObject.GetComponent<Tick_Audio_Script>().TickAudio = (desired Audio);
}
Hope this helps.

Related

Getting object name and randomly placing it to Text UI - Unity

I am a beginner in Unity and I am currently making a simple game. I have a problem managing the flow of my minigame. The minigame is simply finding an object, when I found and tap on the item name below will be shaded or marked or there can be animation just to indicate that the item is found.
What I want to do is to get the name of the objects that need to be found and set them randomly in the three (3) item names below. like every time this minigame opens the names of the items are randomly placed in the 3 texts. And when the item is found the name below will be marked or shaded or anything that will indicate it is found, but for now I will just set it inactive for easier indication. How can I properly do this whole process?
The objects inside the scene are button for them to have onCLick() events
Correction: the term choices are wrong because they just display the name of the items that you need to find, just in case you get confused with the term choice in my minigame. I will fix it.
Here is the visuals for the minigame:
The script I currently have for when the objects was clicked:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
[SerializeField] Button pillowBtn, pcBtn, lampBtn;
// [SerializeField] TextMeshProUGUI choice1, choice2, choice3;
// FROM THE SOLUTION
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] List<TextMeshProUGUI> textBoxes;
// METHOD NAMES IS TEMPORARY
public void pillowClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void desktopClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void lampClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
}
You asked for a lot and I hope I understood your intention.
first, if you want to randomly choose an amount of game objects from your game, I think the best way to do it is by adding the refernece of all the game objects you want to choose from radomly inside a list of game objects and then randomly take a game object of the list and make it a child of another game object I call "fatherGoTranform" on my code like that:
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] float numOfGO = 4;
[SerializeField] Transform fatherGoTranform;
void Start()
{
for(int i=0;i<numOfGO;i++)
{
int index = Random.Range(0, gameObjectWanted.Count-1);
GameObject currentGO = gameObjectWanted[index ];
currentGO.transform.parent = fatherGoTranform;
gameObjectWanted.RemoveAt(index);
}
}
and then to click on a game object and the do with what you want try this:
void Update()
{
//Check for mouse click
if (Input.GetMouseButtonDown(0))
{
RaycastHit raycastHit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out raycastHit, 100f))
{
if (raycastHit.transform != null)
{
//Our custom method.
CurrentClickedGameObject(raycastHit.transform.gameObject);
}
}
}
}
I have not checked the code so if there is an error tell me and I will fix it
This is the solution that worked for my problem. I randomly placed numbers to the list according to childCount and every index indicate the index of the text that I want to put them on which I get as a Transform child.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
// [SerializeField] Button pillowBtn, pcBtn, lampBtn;
[SerializeField] GameObject choices, v1, finishPanel;
// RANDOM NUMBER HOLDER FOR CHECKING
private int randomNumber, foundCount;
// RANDOMIZED NUMBER LIST HOLDER
public List<int> RndmList = new List<int>();
private void Awake()
{
foundCount = 0;
RndmList = new List<int>(new int[v1.transform.childCount]);
for (int i = 0; i < v1.transform.childCount; i++)
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
while (RndmList.Contains(randomNumber))
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
}
RndmList[i] = randomNumber;
// Debug.Log(v1.transform.GetChild(randomNumber-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text = v1.transform.GetChild(randomNumber - 1).name;
}
}
public void objectFound()
{
string objectName = EventSystem.current.currentSelectedGameObject.name;
Debug.Log("you found the " + objectName);
for (int i = 0; i < choices.transform.childCount; i++)
{
if (objectName == choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text)
{
// Debug.Log(i);
// choices.transform.GetChild(i).gameObject.SetActive(false);
// choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
if(RndmList.Contains(i+1))
{
// Debug.Log(i);
// Debug.Log(v1.transform.GetChild(RndmList[i]-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
v1.transform.GetChild(RndmList[i]-1).GetComponent<Button>().enabled = false;
foundCount++;
}
}
}
if(foundCount == v1.transform.childCount)
{
finishPanel.SetActive(true);
}
}
}

Hololens2 Correct Usage of SpatialAnchors

I am trying to implement an Hololens 2 App using SpatialAnchors but it seems that I am not quite understanding the concept right. I am able to create, save and restore SpatialAnchors of the Windows.Perception.Spatial namespace, but whenever I restore them they are placed relative to the start position of the headset.
I use this snippet to create an anchor:
SpatialAnchor thisAnchor = SpatialAnchor.TryCreateRelativeTo(coord);
And this to get the relative SpatialCoordinateSystem (from this post)
public static bool UseSGIPSceneCoordinateSystem(out SpatialCoordinateSystem sGIPSceneCoordinateSystem)
{
// gain access to the scene object
SceneObserverAccessStatus accessStatus = Task.Run(RequestAccess).GetAwaiter().GetResult();
if (accessStatus == SceneObserverAccessStatus.Allowed)
{
Scene scene = Task.Run(GetSceneAsync).GetAwaiter().GetResult();
sGIPSceneCoordinateSystem = SpatialGraphInteropPreview.CreateCoordinateSystemForNode(scene.OriginSpatialGraphNodeId);
return true;
}
else
{
sGIPSceneCoordinateSystem = null;
return false;
}
}
I use this method to restore the SpatialAnchor I need:
public bool GetAnchor(string id, out SpatialAnchor anchor)
{
[...]
_anchors = _anchorStore.GetAllSavedAnchors();
foreach (var kvp in _anchors)
{
if(kvp.Key == id)
{
anchor = kvp.Value;
return true;
}
}
anchor = null;
return false;
}
And this to process the restored Anchor and get a place in the scene for it:
public void SomeOtherMethod(SpatialAnchor anchor)
{
SpatialCoordinateSystem showcaseCoordinateSystem = anchor.CoordinateSystem;
//get the reference SpatialCoordinateSystem
if (!UseSGIPSceneCoordinateSystem(out SpatialCoordinateSystem referenceCoordinateSystem))
return;
_anchorMatrix = showcaseCoordinateSystem.TryGetTransformTo(referenceCoordinateSystem);
System.Numerics.Matrix4x4 _notNullMatrix = _anchorMatrix.Value;
Matrix4x4 unityAnchorMatrix = _notNullMatrix.ToUnity();
anchorGameObject.transform.FromMatrix(unityAnchorMatrix);
}
I think that I am using the wrong SpatialCoordinateSystem but canĀ“t find information on how to get a SpatialCoordinateSystem which the HoloLens2 generates for the physical spatial surrounding of the user that is persistent.
I am using:
Unity 2020.3.13f1
OpenXR Plugin 1.3.1
MRTK 2.7.3
MRTK-OpenXR 1.2.1
MRTK SceneUnderstanding 0.6.0
I am also confused of the amount of different SpatialAnchor-Systems, reference coordinatesystems and the documentation. Every post I find about SpatialAnchors seems to use a different approach. There seems to be SpatialAnchor-Systems for the Unity WLT, UnityEngine.VR.WSA with WorldManager, Azure Spatial Anchors,
UnityEngine.XR.WindowsMR.WindowsMREnvironment, etc.
And unless I havent overseen it, the microsoft documentation is not really clear about which one to use and how to use it right.
I would be really thankful if someone could bring some light into this issue.
I posted this question also on forum.unity.com

Could I Intercept Exit FullScreen event from octane.xam.VideoPlayer plugin (XAMARIN FORMS)?

My application works portait, ma i want fullscreen video playback even in landscape mode using the plugin mentionend above.
For this purpose I create a customrenderer to take access to native AVPlayerViewController Ios Control.
I tried in many many ways, but seems to be impossible to handle exit fullscreen event. In that method i want to force layout portrait. I have the code for reset orientation already implemented but the problem is to put the code in the right place.
Any other that faced the same issue??
I tried to search for something useful in AVPlayerView(not accessible), AVPlayerVideoController, AVPlayerCurrentItem etc
Any ideas?
Thanks you in advance.
I have translated the OC code to C# in this link for you, see the following codes:
using Foundation;
using CoreGraphics;
playerViewController = new AVPlayerViewController();
playerViewController.ContentOverlayView.AddObserver(this, new NSString("bounds"), NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old , IntPtr.Zero);
public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
{
base.ObserveValue(keyPath, ofObject, change, context);
if(ofObject == playerViewController.ContentOverlayView)
{
if(keyPath == "bounds")
{
NSValue oldRect = change.ValueForKey(new NSString("NSKeyValueChangeOldKey")) as NSValue;
NSValue newRect = change.ValueForKey(new NSString("NSKeyValueChangeNewKey")) as NSValue;
CGRect oldBounds = oldRect.CGRectValue;
CGRect newBounds = newRect.CGRectValue;
bool wasFullscreen = CGRect.Equals(oldBounds, UIScreen.MainScreen.Bounds);
bool isFullscreen = CGRect.Equals(newBounds, UIScreen.MainScreen.Bounds);
if(isFullscreen && !wasFullscreen)
{
if(CGRect.Equals(oldBounds,new CGRect(0,0,newBounds.Size.Height, newBounds.Size.Width)))
{
Console.WriteLine("rotated fullscreen");
}
else
{
Console.WriteLine("entered fullscreen");
}
}
else if(!isFullscreen && wasFullscreen)
{
Console.WriteLine("exited fullscreen");
}
}
}
}

Unity Vuforia Google VR - Can't make onPointerEnter to GameObject change material for itself

I have two 3d buttons in my scene and when I gaze into any of the buttons it will invoke OnPointerEnter callback and saving the object the pointer gazed to.
Upon pressing Fire1 on the Gamepad I apply materials taken from Resources folder.
My problem started when I gazed into the second button, and pressing Fire1 button will awkwardly changed both buttons at the same time.
This is the script I attached to both of the buttons
using UnityEngine;
using UnityEngine.EventSystems;
using Vuforia;
using System.Collections;
public class TriggerMethods : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
Material _mat;
GameObject targetObject;
Renderer rend;
int i = 0;
// Update is called once per frame
void Update () {
if (Input.GetButtonDown("Fire1"))
TukarMat();
}
public void OnPointerEnter(PointerEventData eventData)
{
targetObject = ExecuteEvents.GetEventHandler<IPointerEnterHandler>(eventData.pointerEnter);
}
public void OnPointerExit(PointerEventData eventData)
{
targetObject = null;
}
public void TukarMat()
{
Debug.Log("Value i = " + i);
if (i == 0)
{
ApplyTexture(i);
i++;
}
else if (i == 1)
{
ApplyTexture(i);
i++;
}
else if (i == 2)
{
ApplyTexture(i);
i = 0;
}
}
void ApplyTexture(int i)
{
rend = targetObject.GetComponent<Renderer>();
rend.enabled = true;
switch (i)
{
case 0:
_mat = Resources.Load("Balut", typeof(Material)) as Material;
rend.sharedMaterial = _mat;
break;
case 1:
_mat = Resources.Load("Khasiat", typeof(Material)) as Material;
rend.sharedMaterial = _mat;
break;
case 2:
_mat = Resources.Load("Alma", typeof(Material)) as Material;
rend.sharedMaterial = _mat;
break;
default:
break;
}
}
I sensed some logic error and tried making another class to only manage object the pointer gazed to but I was getting more confused.
Hope getting some helps
Thank you
TukarMat() is beeing called on both buttons when you press Fire1. If targetObject is really becoming null this should give an error on first button since it's trying to get component from a null object. Else, it'll change both as you said. Make sure OnPointerExit is beeing called.
Also, it seems you are changing the shared material.
The documentation suggests:
Modifying sharedMaterial will change the appearance of all objects using this material, and change material settings that are stored in the project too.
It is not recommended to modify materials returned by sharedMaterial. If you want to modify the material of a renderer use material instead.
So, try changing the material property instead of sharedMaterial since it'll change the material for that object only.

How do I block access to a method until animations are complete

I have a Silverlight app. that has a basic animation where a rectangle is animated to a new position. The animation consists of two DoubleAnimation() - one transforms the X, the other transforms the Y. It works OK.
I basically want to block any other calls to this animate method until the first two animations have completed. I see that the DoubleAnimation() class has a Completed event it fires but I haven't been successful in constructing any kind of code that successfully blocks until both have completed.
I attempted to use Monitor.Enter on a private member when entering the method, then releasing the lock from one of the animations Completed event, but my attempts at chaining the two events (so the lock isn't released until both have completed) haven't been successful.
Here's what the animation method looks like:
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
var xIsComplete = false;
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 350));
var easing = new ElasticEase() { EasingMode = EasingMode.EaseOut, Oscillations = 1, Springiness = 4 };
var animateX = new DoubleAnimation();
var animateY = new DoubleAnimation();
animateX.EasingFunction = easing;
animateX.Duration = duration;
animateY.EasingFunction = easing;
animateY.Duration = duration;
var sb = new Storyboard();
sb.Duration = duration;
sb.Children.Add(animateX);
sb.Children.Add(animateY);
Storyboard.SetTarget(animateX, rect);
Storyboard.SetTargetProperty(animateX, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTarget(animateY, rect);
Storyboard.SetTargetProperty(animateY, new PropertyPath("(Canvas.Top)"));
animateX.To = newX;
animateY.To = newY;
sb.Begin();
}
EDIT (added more info)
I ran into this initially because I was calling this method from another method (as it processed items it made a call to the animation). I noticed that the items didn't end up where I expected them to. The new X/Y coordinates I pass in are based on the items current location, so if it was called multiple times before it finished, it ended up in the wrong location. As a test I added a button that only ran the animation once. It worked. However, if I click on the button a bunch of times in a row I see the same behavior as before: items end up in the wrong location.
Yes, it appears Silverlight animations are run on the main UI thread. One of the tests I tried I added two properties that flagged whether both animations had completed yet. In the AnimateRectange() method I checked them inside of a while loop (calling Thread.Sleep). This loop never completed (so it's definitely on the same thread).
So I created a queue to process the animations in order:
private void ProcessAnimationQueue()
{
var items = this.m_animationQueue.GetEnumerator();
while (items.MoveNext())
{
while (this.m_isXanimationInProgress || this.m_isYanimationInProgress)
{
System.Threading.Thread.Sleep(100);
}
var item = items.Current;
Dispatcher.BeginInvoke(() => this.AnimateRectangle(item.Rect.Rect, item.X, item.Y));
}
}
Then I call my initial routine (which queues up the animations) and call this method on a new thread. I see the same results.
As far as I am aware all of the animations in Silverlight are happening on the UI thread anyway. I am guessing that only the UI thread is calling this animation function anyway, so I am not sure that using locking will help. Do you really want to be blocking the entire thread or just preventing another animation from starting?
I would suggest something more like this:
private bool isAnimating = false;
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
if (isAnimating)
return;
// rest of animation code
sb.Completed += (sender, e) =>
{
isAnimating = false;
};
isAnimating = true;
sb.Begin();
}
Just keep track of whether or not you are currently animating with a flag and return early if you are. If you don't want to lose potential animations your other option is to keep some kind of a queue for animation which you could check/start when each animation has completed.
This question really peaked my interest. In fact I'm going to include it in my next blog post.
Boiling it down, just to be sure we are talking about the same thing, fundementally you don't want to block the call to AnimateRectangle you just want to "queue" the call so that once any outstanding call has completed its animation this "queued" call gets executed. By extension you may need to queue several calls if a previous call hasn't even started yet.
So we need two things:-
A means to treat what are essentially asynchronous operations (sb.Begin to Completed event) as a sequential operation, one operation only starting when the previous has completed.
A means to queue additional operations when one or more operations are yet to complete.
AsyncOperationService
Item 1 comes up in a zillion different ways in Silverlight due to the asynchronous nature of so many things. I solve this issue with a simple asynchronous operation runner blogged here. Add the AsyncOperationService code to your project.
AsyncOperationQueue
Its item 2 that really took my interest. The variation here is that whilst an existing set of operations are in progress there is demand to add another. For a general case solution we'd need a thread-safe means of including another operation.
Here is the bare-bones of a AsyncOperationQueue:-
public class AsyncOperationQueue
{
readonly Queue<AsyncOperation> myQueue = new Queue<AsyncOperation>();
AsyncOperation myCurrentOp = null;
public void Enqueue(AsyncOperation op)
{
bool start = false;
lock (myQueue)
{
if (myCurrentOp != null)
{
myQueue.Enqueue(op);
}
else
{
myCurrentOp = op;
start = true;
}
}
if (start)
DequeueOps().Run(delegate { });
}
private AsyncOperation GetNextOperation()
{
lock (myQueue)
{
myCurrentOp = (myQueue.Count > 0) ? myQueue.Dequeue() : null;
return myCurrentOp;
}
}
private IEnumerable<AsyncOperation> DequeueOps()
{
AsyncOperation nextOp = myCurrentOp;
while (nextOp != null)
{
yield return nextOp;
nextOp = GetNextOperation();
}
}
}
Putting it to use
First thing to do is convert your existing AnimateRectangle method into a GetAnimateRectangleOp that returns a AsyncOperation. Like this:-
public AsyncOperation GetAnimateRectangleOp(Rectangle rect, double newX, double newY)
{
return (completed) =>
{
// Code identical to the body of your original AnimateRectangle method.
sb.Begin();
sb.Completed += (s, args) => completed(null);
};
}
We need to hold an instance of the AsyncOperationQueue:-
private AsyncOperationQueue myAnimationQueue = new AsyncOperationQueue();
Finally we need to re-create AnimateRectangle that enqueues the operation to the queue:-
public void AnimateRectangle(Rectangle rect, double newX, double newY)
{
myAnimationQueue.Enqueue(GetAnimateRectangleOp(rect, newX, newY)
}

Resources