Stacking effects & powerups in UI | Unity C# - c#

How could I make it so, for example, if only one thing is on it will move to the first slot, I think you know what I mean... So how could I make this?1
I thought about using arrays but I don't understand how.
This is my code currently, rn it just shows and hides but doesnt move them so there just a gap.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class UI : MonoBehaviour
{
public Text myText;
public GameObject Player;
public GameObject SugarIcon;
public GameObject DashIcon;
public GameObject JumpIcon;
public GameObject ShieldIcon;
private string dashreadystring;
private string doublejumpstring;
private string triplejumpstring;
private string shieldbuffstring;
private int iconamount = 0;
void Start()
{
GameObject Player = GameObject.Find("Player");
}
void Update()
{
PlayerScript PlayerScript = Player.GetComponent<PlayerScript>();
bool dashready = PlayerScript.canDash;
float sugarbuff = PlayerScript.sugarbuffactive;
int airJumpCount = PlayerScript.airJumpCount;
int airJumpCountMax = PlayerScript.airJumpCountMax;
bool canDoubleJump = PlayerScript.canDoubleJump;
bool shieldon = PlayerScript.shieldon;
float score = Player.transform.position.x;
// If Dash is ready, show icon, if not, hide it.
if (dashready == true)
{
DashIcon.GetComponent<Renderer>().enabled = true;
}
else
{
DashIcon.GetComponent<Renderer>().enabled = false;
}
// If Sugarbuff is active, show icon, if not, hide it.
if (sugarbuff == 1.5)
{
SugarIcon.GetComponent<Renderer>().enabled = true;
}
else
{
SugarIcon.GetComponent<Renderer>().enabled = false;
}
// If can jump,
if ((airJumpCount < airJumpCountMax) | ((canDoubleJump) && (sugarbuff == 1.5f)))
{
JumpIcon.GetComponent<Renderer>().enabled = true;
}
else
{
JumpIcon.GetComponent<Renderer>().enabled = false;
}
if (shieldon)
{
ShieldIcon.GetComponent<Renderer>().enabled = true;
}
else
{
ShieldIcon.GetComponent<Renderer>().enabled = false;
}
}
}
How could I make this? Thanks.

If your icons are children of a parent container that uses a HorizontalLayoutGroup component, it will automatically line up your icons horizontally (use VerticalLayoutGroup or GridLayoutGroup to autoalign in different patterns).
Then, if you disable the gameObject of the icons, they will disappear. The HorizontalLayoutGroup will rearrange the remaining icons in the way I think you want. When you reenable the icon, it will be reinserted into the horizontal layout.
Notes:
You are currently disabling the Renderer Component on the gameObject. This does make the object invisible, but it still exists and takes up space. Instead, disable the gameObject for the icon itself (SugarIcon.enabled = false). Hopefully the Icon objects are just images, and have no scripts/logic on them, but if so, you'll need to decouple that.
It looks like you're using GetComponent() calls many times every frame. Don't do this -- GetComponent() calls have a pretty big overhead. Instead, call it once, when you initialize your manager, and then cache the result. This will save a ton of processing.
// Call GetComponent() and cache the reference one time (when you initialize the objects).
Renderer sugarIconRenderer = SugarIcon.GetComponent<Renderer>();
// Call the cached reference every frame when you need it.
sugarIconRenderer.enabled = false;

Related

Unity Question: How Can I Switch From One prefab To Another With GetAxis?

I want to bind the up and down arrow keys to cycle through different sprites upon being pressed. If one end is reached, it would loop back to the first sprite. I've tried using the following code:
public class PhaseChanger : MonoBehaviour
{
// saved for efficiency
[SerializeField]
public GameObject prefabMoon0;
[SerializeField]
public GameObject prefabMoon1;
[SerializeField]
public GameObject prefabMoon2;
[SerializeField]
public GameObject prefabMoon3;
[SerializeField]
public GameObject prefabMoon4;
// needed for new phase
GameObject currentPhase;
bool previousFramePhaseChangeInput = false;
/// <summary>
/// Start is called before the first frame update
/// </summary>
void Start()
{
currentPhase = Instantiate<GameObject>(prefabMoon0);
}
/// <summary>
/// Update is called once per frame
/// </summary>
void Update()
{
// change phase on up arrow or down arrow
if (Input.GetAxis("ChangePhase") > 0)
{
// only change phase on first input frame
if (!previousFramePhaseChangeInput)
{
previousFramePhaseChangeInput = true;
// Save current position and destroy current phase
Destroy(currentPhase);
// instantiate next phase
if (currentPhase = prefabMoon0)
{
currentPhase = Instantiate(prefabMoon1);
}
else if (currentPhase = prefabMoon1)
{
currentPhase = Instantiate(prefabMoon2);
}
else if (currentPhase = prefabMoon2)
{
currentPhase = Instantiate(prefabMoon3);
}
else if (currentPhase = prefabMoon3)
{
currentPhase = Instantiate(prefabMoon4);
else
{
// no phase change input
previousFramePhaseChangeInput = false;
}
}
}
}
When I attach the script to my main camera and run it, I'm able to make a single change with the up arrow, and then nothing else happens on subsequent presses.
I feel like I'm really close to making this work, but I also may being doing the whole thing inefficiently. Help would be much appreciated, thanks!
Also: I know I said sprites in my post and am sharing a script that calls on prefabs. I didn't know how to approach this using just the sprites without making a prefab for each. Is it possible to do this without separate prefabs for each sprite?
Problems
First of all you are using assignments
currentPhase = XY
where you should be using
currentPhase == XY
The reason why it still compiles is the implicit conversion operator for UnityEngine.Object -> bool. Basically your assigning equals writing
currentPhase = XY;
if(currentPhase)
It won't work like this either way because you are using Instantiate to create a new clone of a prefab which will of course have a different reference than the original prefab it was cloned from.
So even if your checks where looking like
if(currentPhase == XY)
they will ever be true.
Solution
Instead of checking for reference equality I would rather store all prefabs/instances in an array
public GameObject[] phases;
and then simply have an int index for this array so you can simply move to the next element from the array by increasing the index.
private int currentPhase;
And you can increase it and make it wrap around using e.g.
currentPhase = (currentPhase + 1) % phases.Length;
so it will always grow from 0 up to phases.Length - 1 and then start over from 0 again.
And then I don't know the exact requirements of your use case but I would suggest to rather not all the time use Instantiate and Destroy but rather have already all the objects as instances under your object and just (de)actívate them!
you could do this like e.g.
public GameObject[] phases;
private int currentPhase;
private void Awake ()
{
Init();
}
private void Update ()
{
if (Input.GetAxis("ChangePhase") > 0)
{
if (!previousFramePhaseChangeInput)
{
previousFramePhaseChangeInput = true;
NextPhase();
}
}
else
{
previousFramePhaseChangeInput = false;
}
}
// Disables all phases except the first one and sets the current index to 0
private void Init()
{
for(var i = 1; i < phases.Length; i++)
{
phases[i].SetActive(false);
}
phases[0].SetActive(true);
currentPhase = 0;
}
// Disables the current phase and enables the next one
// wraps around at the end of the array
public void NextPhase()
{
phases[currentPhase].SetActive(false);
// increase the counter and wrap around at the end of the array
currentPhase = (currentPhase + 1) % phases.Length;
phases[currentPhase].SetActive(true);
}
If you still want to Instantiate the objects because having them already in the scene is no option (for whatever reason) you could do it right before calling Init like e.g.
public GameObject[] phasePrefabs;
private GameObject[] phases;
private void Awake ()
{
var amount = phasePrefabs.Length;
phases = new GameObject [amount];
for(var i = 0; i < amount; i++)
{
phases[i] = Instantiate(phasePrefabs[i]);
}
Init();
}
Though as said I would prefer to already have them right away as this is way less error prone ;)

Can only spawn one object at once in unity

In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}

How to preserve the full state of objects between scenes?

When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}
I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)

How do I properly detect game objects?

My goal is to write one script that I can use on different game objects and it should have specific variables tied to it on that game object only without affecting other scripts in the process.
For example, if I take this script and put it on two game objects each game object should have their own unique variable value in that same script.
If my question is not clear enough, I'm more than happy to elaborate further.
I have a good understanding of the Unity Editor, however, I'm pretty new to C# so I don't think it's unreasonable that I made a rookie mistake somewhere in my code.
The way I've got things setup is that I have two separate scripts:
Fighting controls the values like the Team, Health, Attack Damage, Cool Down, Cooling down and Snap
TrigDetect controls the detection of a trigger being activated as a result of an enemy entering the trigger radius.
The problem I'm currently having lies in the TrigDetect script I guess.
It should also be noted that an empty attached to each game object in question contains both of these scripts and is tagged as "Troop".
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
//public GameObject[] Enemy;
bool once = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Entered");
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Exitted");
}
}
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Troop"))
{
Fighting self = GetComponent<Fighting>();
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
//Enemy = GameObject.FindGameObjectsWithTag("Troop");
//Debug.Log("Staying");
//Debug.Log(Enemy);
//Debug.Log(self.Health);
//Debug.Log(fScript.Health);
if (once == false)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
if (self.Team != fScript.Team)
{
if (self.CoolingDown == false)
{
self.CoolingDown = true;
fScript.Health -= self.AttackDamage;
}
else
{
self.CoolDown -= Time.deltaTime;
if (self.CoolDown <= 0)
{
self.CoolingDown = false;
self.CoolDown = self.original;
}
}
}
}
}
}
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
public bool CoolingDown = false;
public bool Snap = false;
// Update is called once per frame
void Update () {
if (Snap == true || Health <= 0)
{
//Destroy(gameObject, .5f);
Destroy(transform.parent.gameObject);
}
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
}
The expected result when I move one game object into the trigger radius of the other is that they should both start subtracting Health from each other based on the AttackDamage value. They should do this every time the CoolingDown value is false. When an attack is executed, it's flipped to true and a timer starts, when the timer is done it's flipped back to false.
However, upon moving the two objects into each other's radius', the first object has its health taken away as expected and then proceeds to do nothing until it's health reaches 0 then it dies because of the object attacking it. The object attacking is successfully attacking the other object but, is still not being affected by the object it's attacking.
Basically, Find(name) only returns the first instance of anything by that name, thus your g = Find(name) is almost guaranteed to never be the object related to your trigger/collision condition. The OnTriggerStay(Collider other) already gives you the 'other' collider that's in your trigger zone, so use it. :)
Replace this:
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
with this:
Fighting fScript = other.GetComponent<Fighting>();
To your question header:
Every instaced (non-static) value is allways unique to the according component and thereby to the according GameObject it is attached to. You might want to refrase the question because this is actually not your issue.
The problem is that when you do
GameObject.Find("Detection");
it actually finds the same object both times: Namely the first one in the hierarchy. So in one of of the two components you find your own empty object and skip the rest in
if(self.Team != FScript.Team)
.. you could try to use
other.Find("Detection");
instead to only search in the according context .. However, you should not use Find at all!
It is very performance intense
You should allways reuse references and not search them over and over again
You don't need it in your case
Since you say both scripts are attached to the same object you can simply use
GetComponent<Fighting>();
and you can do so already in Awake and reuse the reference instead:
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
Than for the collision you don't have to use Find either because you already have the reference of the object you collide with: other.gameObject. I don't know your entire setup but you can search for the component either downwards in the hierachy
// the flag true is sued to also find inactive gameObjects and components
// leave it without parameters if you don't want this
var otherFighting = other.GetComponentInChildren<Fighting>(true);
or searcg upwards in the hierachy
var otherFighting = other.GetComponentInParent<Fighting>(true);
or if you already know you collide exactly with the correct GameObject anyway simply use
var otherFighting = other.GetComponent<Fighting>();
I will use the latter in my example.
Than cheking the health all the time in Update is a huge perfomance issue. You should rather have a method e.g. TakeDamage and do your check only if your health is actually changed:
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
// you don't need that flag see below
//public bool CoolingDown = false;
public bool Snap = false;
private void Update()
{
// you might also put this in a callback instead of update at some point later
if(Snap == true)
{
Destroy(transform.parent.gameObject);
}
// Note: this also makes not muh sense because if you destroyed
// the parent than you cannot instantiate it again!
// use a prefab instead
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
public void TakeDamge(int DamageAmount)
{
Health -= DamageAmount;
if (Health > 0) return;
Destroy(transform.parent.gameObject);
}
}
Another performance issue in general: Even if Start, Update etc are empty, if they are present in your script Unity will call them. So if you don't use them then completely remove them to avoid that useless overhead.
So I would have
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
bool once = false;
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
void OnTriggerStay(Collider other)
{
// if wrong tag do nothing
if (!other.CompareTag("Troop")) return;
Fighting fScript = other.GetComponent<Fighting>();
// here you should add a check
if(!fScript)
{
// By using the other.gameObject as context you can find with which object
// you collided exactly by clicking on the Log
Debug.LogError("Something went wrong: Could not get the Fighting component of other", other.gameObject);
}
if (!once)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
// if same team do nothing
if (self.Team == fScript.Team) return;
// you don't need the CoolingDown bool at all:
self.CoolDown -= Time.deltaTime;
// if still cooling down do nothing
if(self.CoolDown > 0) return;
fScript.TakeDamage(self.AttackDamage);
self.CoolDown = self.original;
}
}

Unity3D Vive throw objects when let go of

I am attempting to make a physics sandbox-type game for the Vive, but the velocity of an object is completely reset and just begins falling when you let go of an object you were previously holding, making throwing impossible.
The system I am using currently will disable gravity on an object, and disable colliders of on an object when you pick it up. It will also child the object to your controller, making it like holding the object. When you let go of the button to release the object, it will enable gravity, enable colliders, and then set its parent object to null. This works to pick up and release an object, but it does not work at all for throwing objects. I have played around with timing and order of the components of the code, and nothing works.
Is there any way to find the velocity of an object and the directional velocity, without the object using gravity? Velocity doesn't work if gravity is disabled.
Here is my code:
using UnityEngine;
using System.Collections;
public class WandController : MonoBehaviour
{
//Basic Controller tracking stuff
private Valve.VR.EVRButtonId gripButton = Valve.VR.EVRButtonId.k_EButton_Grip;
public bool gripButtonDown = false;
public bool gripButtonUp = false;
public bool gripButtonPressed = false;
private Valve.VR.EVRButtonId triggerButton = Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger;
public bool triggerButtonDown = false;
public bool triggerButtonUp = false;
public bool triggerButtonPressed = false;
private SteamVR_Controller.Device controller { get { return SteamVR_Controller.Input((int)trackedObj.index); } }
private SteamVR_TrackedObject trackedObj;
//Game Variables
public GameObject wouldSelect; //What is in the select zone, has tiny script for the zone that sets the newest triggerenter to this variable
public GameObject isHolding; //When you hold something, it goes from wouldselect to isholding
public bool holding = false;
public GameObject holdingZone; //The holding zone, also where objects go if they are picked up
// Use this for initialization
void Start()
{
trackedObj = GetComponent<SteamVR_TrackedObject>();
}
// Update is called once per frame
void Update()
{
//Basic Controller configuration & button management stuff
if (controller == null)
{
Debug.Log("Controller not initialized");
return;
}
gripButtonDown = controller.GetPressDown(gripButton);
gripButtonUp = controller.GetPressUp(gripButton);
gripButtonPressed = controller.GetPress(gripButton);
triggerButtonDown = controller.GetPressDown(triggerButton);
triggerButtonUp = controller.GetPressUp(triggerButton);
triggerButtonPressed = controller.GetPress(triggerButton);
if (gripButtonDown)
{
Debug.Log("Grip Button was just pressed");
}
if (gripButtonUp)
{
Debug.Log("Grip Button was just unpressed");
}
if (triggerButtonDown)
{
Debug.Log("Trigger Button was just pressed");
}
if (triggerButtonUp)
{
Debug.Log("Trigger Button was just unpressed");
}
//Calling void that allows you to grab
CanGrab();
}
void CanGrab ()
{
if(wouldSelect != null && wouldSelect.tag == "Object" && triggerButtonDown == true && holding == false)
{
wouldSelect.GetComponent<Collider>().enabled = false;
wouldSelect.GetComponent<Rigidbody>().useGravity = false;
isHolding = wouldSelect;
wouldSelect.transform.SetParent(this.transform);
wouldSelect.transform.position = holdingZone.transform.position;
holding = true;
}
if(holding == true && triggerButtonUp == true)
{
wouldSelect.GetComponent<Collider>().enabled = true;
isHolding.GetComponent<Rigidbody>().useGravity = true;
isHolding.transform.SetParent(null);
holding = false;
wouldSelect = null;
isHolding = null;
}
}
}
I had a similar issue and solved it by:
tracking the position of the object at every frame, and storing it as lastPosition
when the object is let go, using (transform.position - lastPosition) to give me a rough estimate of the velocity of that object
From there, you can add an impulse force if you have a rigidbody, etc, I found a thread with some details on the various ways Unity does it -- http://answers.unity3d.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html
I did a number of throwing mechanics in VR using the same parameters you had (Setting the object as a child of your controller, disabling gravity, disabling colliders).
The way I did it was to record the position of the object on the previous and current frame, and take the difference between them as the velocity. However, there are three main factors to consider:
There might be some jitter in the tracking of the controller, and sometimes the object does not fly in the direction of throw.
There might be a lag time between the user pressing/letting go of the throwing button and the button press/release getting recorded, and so the object is thrown only towards the falling arc of the throw. (This happens a lot in my playtests)
There is a peak force during a throw during which an object gains the most velocity for its flight, and sometimes people let go of the object a split second after. This is rather inconsequential, but you should definitely consider if you want very realistic throwing (Take a look at The Lab demo and you will know what I mean)
My implementation:
Record the last x frames (for me the sweet spot is between 10-15 frames) for the object's position.
Take the difference in the first and last frame in the window and use that to calculate the velocity.
transform.velocity = position[n] - position[0];
If I want a slightly more accurate implementation, I'll calculate the force of the throw by taking the differences in velocities for adjacent frames. If there is a time window between the peak force and the release of the object, do not take the velocities in the later half of the window.
For example, if I decide to record the last 10 frames of a throw, and there are 4 frames between the peak force and the release, I will take frame current-12 to current-2, instead of frame current-10 to current for the velocity.

Categories