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.
Related
Hi!
i am making something like 8 ball pool in 3d with unity 3d C#.
A is que ball and I know Dir1. I want to calculate Dir2.I am using Raycast to i can get point of contact.
If you really want to go manual
Instead of a Raycast you would rather use a Physics.SphereCast (according to your balls shape) or alternatively you could also use Rigidbody.SweepTest in order to get hit information for if you object would move.
Both provide you with the information
whether something is hit or not (e.g. check via tag if the first thing hit is a wall or another ball)
both give you a RaycastHit which contains detailed information such as
point the exact contact point
distance how far the ball traveled before hitting something (e.g. for calculating the already applied damping/table friction)
normal of the surface you hit so you can calculate your two balls new direction from that
and of course most importantly which object was hit so you can start a new calculation for that ball as well
The rest is The Math and Physics of Billard or Physics of Billard etc and you will find that a complete answer is way to complex for this page ;)
There are way to many things to consider when calculating physics manually and first of all you will need to decide how realistic you want to go actually. There are spins, frictions, jumps and you know in the real world there doesn't exist any fully elastic collision at all ... So boy if you are going to pre-calculate all this by hand you can as well just write your own physics engine ;)
Use the existing physics without calculating yourself at all
Now as a complete alternative approach to all that, which doesn't require you to calculate anything at all:
You could simulate the whole physics!
You could
Store all balls current positions (in order to restore them later)
Use Physics.Simulate in a loop, as condition checking if any sphere has moved between the two calls -> If not (or after certain preview time) then break.
Every step track the positions of all balls
After breaking of the loop reset all positions and velocities
=> You already get all the tracked points for each balls LineRenderer ;)
You could even use the positions in order to move the balls yourself instead of use the physics again to show the actual movement ;)
For large systems this would of course cause immense lag (depends also on your target preview time range). But I'd say for only a limited amount of balls this should be fine.
Here a little code I cluched together in a few minutes (way not perfect of course)
// little helper component for storing some references and identify clearly as a ball
// (rather then allowing to reference just any GameObject)
public class Ball : MonoBehaviour
{
[SerializeField]
private Rigidbody _rigidbody;
public Rigidbody Rigidbody => _rigidbody;
[SerializeField]
private LineRenderer _line;
public LineRenderer Line => _line;
private void Awake()
{
if (!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
if (_line) _line = GetComponentInChildren<LineRenderer>(true);
}
}
and
public class Example : MonoBehaviour
{
// A simple data container for transform values
private class TransformData
{
public Vector3 Position;
public Quaternion Rotation;
private readonly Rigidbody _rigidbody;
public TransformData(Rigidbody rigidbody)
{
_rigidbody = rigidbody;
Update();
}
public void Update()
{
Position = _rigidbody.position;
Rotation = _rigidbody.rotation;
}
}
// all balls
public Ball[] balls;
// the white ball in particular
public Ball whiteBall;
// direction to shoot in
public Vector3 direction;
// force to apply
public float force;
// How far to predict into the future
public float maxPreviewTime = 10;
// shall this be done every frame (careful with performance!)
public bool continousUpdates;
// stores all initial transform values before starting the prediction in order to restore the state later
private readonly Dictionary<Ball, TransformData> initialPositions = new Dictionary<Ball, TransformData>();
// stores all the predicted positions in order - but for now without the information about the exact frame
// for simplicity and performance we only store WHERE the balls move, not exactly WHEN (could change that though if you wish)
private readonly Dictionary<Ball, List<Vector3>> simulatedPositions = new Dictionary<Ball, List<Vector3>>();
private void Awake()
{
// Initialize empty data sets for the existing balls
foreach (var ball in balls)
{
initialPositions.Add(ball, new TransformData(ball.Rigidbody));
simulatedPositions.Add(ball, new List<Vector3>());
}
}
private void Update()
{
// you could call this every frame e.g. in order to see prediction lines while the player changes force and direction
// have performance in mind though!
if (continousUpdates)
{
UpdateLines();
}
// for demo we shoot on space key
if (Input.GetKeyDown(KeyCode.Space))
{
ShootWhiteBall();
}
}
private void ShootWhiteBall()
{
whiteBall.Rigidbody.AddForce(direction.normalized * force, ForceMode.Impulse);
}
[ContextMenu("Update Preview Lines")]
public void UpdateLines()
{
// update all balls initial transform values to the current ones
foreach (var transformData in initialPositions.Values)
{
transformData.Update();
}
// clear all prediction values
foreach (var list in this.simulatedPositions.Values)
{
list.Clear();
}
// disable autosimulation - the API is a bit unclear whether this is required when manually calling "Physics.Simulate"
// so just to be sure
Physics.autoSimulation = false;
// we will track if any transform values changed during the prediction
// if not we break out of the loop immediately to avoid unnecessary overhead
var somethingChanged = true;
// as second break condition we use time - we don't want o get stuck in prediction forever
var simulatedTime = 0f;
// Do the same thing as you would later for shooting the white ball
// preferably even actually use the exact same method to avoid double maintenance
ShootWhiteBall();
while (somethingChanged && simulatedTime < maxPreviewTime)
{
// Simulate a physics step
Physics.Simulate(Time.fixedDeltaTime);
// always assume there was no change (-> would break out of prediction loop)
somethingChanged = false;
foreach (var kvp in simulatedPositions)
{
var ball = kvp.Key;
var positions = kvp.Value;
var currentPosition = ball.Rigidbody.position;
// either this is the first frame or the current position is different from the previous one
var hasChanged = positions.Count == 0 || currentPosition != positions[positions.Count - 1];
if (hasChanged)
{
positions.Add(currentPosition);
}
// it is enough for only one ball to be moving to keep running the prediction loop
somethingChanged = somethingChanged || hasChanged;
}
// increase the counter by one physics step
simulatedTime += Time.fixedDeltaTime;
}
// Reset all balls to the initial state
foreach (var kvp in initialPositions)
{
kvp.Key.Rigidbody.velocity = Vector3.zero;
kvp.Key.Rigidbody.angularVelocity = Vector3.zero;
kvp.Key.Rigidbody.position = kvp.Value.Position;
kvp.Key.Rigidbody.rotation = kvp.Value.Rotation;
}
// apply the line renderers
foreach (var kvp in simulatedPositions)
{
var ball = kvp.Key;
var positions = kvp.Value;
ball.Line.positionCount = positions.Count;
ball.Line.SetPositions(positions.ToArray());
}
// re-enable the physics
Physics.autoSimulation = true;
}
}
as you can see it is not exactly 100% accurate, tbh no sure why but it is probably something that can be tweaked out.
I am making an ability system in my game and am running into a few issues. I have set up a script that tells me which "ability" is closest to my cursor and changes the animation state if it is. With one of my abilities, the debug screen says "The closest ability is A" (which works) if selected, but if my cursor is closer to another, it says "The closest ability is A" followed by "The closest ability is B". This is screwing up my animations by triggering selected on both objects when only one should be selected. Where i my code is making this problem occur and how can I fix it so that only 1 ability can be selected at a time.
Thanks in advance!
Closest to cursor script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AtMouseCursor : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
ClosestAbility();
Vector2 cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = cursorPos;
}
void ClosestAbility()
{
float distanceToClosestAbility = Mathf.Infinity;
Ability closestAbility = null;
Ability[] allAbilities = GameObject.FindObjectsOfType<Ability>();
foreach (Ability currentAbility in allAbilities)
{
float distanceToAbility = (currentAbility.transform.position - this.transform.position).sqrMagnitude;
if (distanceToAbility < distanceToClosestAbility)
{
distanceToClosestAbility = distanceToAbility;
closestAbility = currentAbility;
UnityEngine.Debug.Log("The Closest Ability is " + closestAbility.name);
}
}
foreach (Ability currentAbility in allAbilities)
{
if (currentAbility != closestAbility)
{
currentAbility.GetComponent<Ability>().AbilityNotSelected();
}
else if (currentAbility == closestAbility)
{
currentAbility.GetComponent<Ability>().AbilitySelected();
}
}
}
}
Ability Script (Goes on ability prefabs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ability : MonoBehaviour
{
public Animator anim;
void Start()
{
anim = GetComponent<Animator>();
}
public void AbilitySelected()
{
UnityEngine.Debug.Log("Bruh im selscted");
anim.SetBool("isSelected", true);
}
public void AbilityNotSelected()
{
anim.SetBool("isSelected", false);
}
}
You just need to move your Log statement outside [after] the first loop. You won't know that it's actually the closest ability until you've checked all of them.
(And maybe add a check that allAbilities isn't null or empty.)
Update 1:
ClosestAbility() uses this.transform.position but Update() calls ClosestAbility() before updating said transform position. This looks like it would probably cause you to animate the ability closest to the old mouse position rather than the current mouse position. But it may not be noticeable if it's called every frame.
Update 2:
Code I had in mind:
void ClosestAbility()
{
float distanceToClosestAbility = Mathf.Infinity;
Ability closestAbility = null;
Ability[] allAbilities = GameObject.FindObjectsOfType<Ability>();
if ((allAbilities == null) || (allAbilities.Length == 0))
{
return;
}
foreach (Ability currentAbility in allAbilities)
{
float distanceToAbility = (currentAbility.transform.position - this.transform.position).sqrMagnitude;
if (distanceToAbility < distanceToClosestAbility)
{
distanceToClosestAbility = distanceToAbility;
closestAbility = currentAbility;
UnityEngine.Debug.Log(closestAbility.name + " is close, but there might be an ability that's closer still");
}
}
UnityEngine.Debug.Log("I checked all the abilities and " + closestAbility.name + " is the closest");
foreach (Ability currentAbility in allAbilities)
{
if (currentAbility != closestAbility)
{
currentAbility.GetComponent<Ability>().AbilityNotSelected();
}
else if (currentAbility == closestAbility)
{
currentAbility.GetComponent<Ability>().AbilitySelected();
}
}
UnityEngine.Debug.Log("Finished updating all the animations without any exceptions");
}
Update 3: I'm completely unfamiliar with Unity, but since currentAbility is an Ability, I wonder if the call to currentAbility.GetComponent<Ability>() is unnecessary at best and you could/should just have currentAbility.AbilityNotSelected()? Though if your animations ever turn on it must work at least some of the time, maybe. Still, I'd check if the GetComponent call is returning what you think it's returning.
UnityEngine.Debug.Log("The Closest Ability is " + closestAbility.name);
You are calling this for each ability in the list consecutively. So,
if (distanceToAbility < distanceToClosestAbility)
can be true two or more times in your initial foreach loop. The loop itself is fine. Just use
UnityEngine.Debug.Log("I checked all the abilities and " + closestAbility.name + " is the closest");
this code from IceGlasses code after your first foreach loop to log the closest ability.
The first main problem seems to be the calling
ClosestAbility();
before updating cursorPos and transform.position. This causes ClosestAbility function to use the transform.position value of the previous frame.
The second problem seems to be calling ClosestAbility function at every frame. So if one of the ability animations take longer than a frame and you move your cursor closer to another ability the second ability will also be triggered.
Unless you stop the first animation when the second animation starts playing OR unless you call ClosestAbility function if none of the animations are playing 2 or more animations can play at the same time.
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;
}
}
Essentially, I'm trying to move an object between two "hands" that are currently fully functional. I can pick objects up and manipulate them both within the room space and rotationally.
However, the issue I'm having is transferring the object between the controllers.
I can hand over the object to the other hand, however if I let go of the trigger on the original hand, the object then becomes Non-Kinematic and drops back to the floor.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class HandGrab : MonoBehaviour
{
// Variable for Controller button register
public string buttonName;
// Allows objects to be transferred between hands
public HandGrab secondaryHandRef;
// Determines the referenced object type
public XRNode nodeType;
// This affects the distance of object grab from controller
public Vector3 objectGrabOffset;
// Default grab distance relative to the controller, (Where the collider sphere is) This can be changed to affect the visuals and where the items get manipulated from
public float grabDistance = 0.1f;
// Force at which an object is thrown
public float throwingMultiplier = 1.5f;
// To determine if object has been grabbed or cant be grabbed
public string grabName = "Grabbing";
// Stores the values of the currently grabbed object
public Transform CurrentGrabObject
{
get { return _currentlyHeldObject; }
set { _currentlyHeldObject = value; }
}
// Does as it says and stores the last frame and is used to work out velocity calculations
private Vector3 _lastFramePosition;
// Determines the transform values for the currently selected objects
private Transform _currentlyHeldObject;
// bolean to determine if an object is currently being held
private bool _isHeld;
// *Hopefully gets the roation of the controllers and matches that to the roation of the item*
// *private Transform _objectRotation;*
// Use this for initialization
void Start()
{
// Clearing all variables back to zero
_currentlyHeldObject = null;
_lastFramePosition = transform.position;
_isHeld = false;
// Determining the tracking space available
XRDevice.SetTrackingSpaceType(TrackingSpaceType.RoomScale);
}
// Update is called once per frame
void Update()
{
// rotation and position tracking. Updates every frame, should be 90fps
transform.localPosition = InputTracking.GetLocalPosition(nodeType);
transform.localRotation = InputTracking.GetLocalRotation(nodeType);
// Check the object in hand variable. If hand is empty, check to see if we can pick anything up (Object highlight posibilites)
if (_currentlyHeldObject == null)
{
// Check to see if there are any colliders in the proximity of hands
Collider[] colliders = Physics.OverlapSphere(transform.position, grabDistance);
if (colliders.Length > 0)
{
// If there is a collision, then pickup the object if we press the grab button and give it the tag "Grabbing"
if (Input.GetAxis(buttonName) >= 0.5F && colliders[0].transform.CompareTag(grabName))
{
// Now we set to grabbing to true
if (_isHeld)
{
return;
}
_isHeld = true;
// Sets the current object to the object we have just picked up and binds it as a child object (this should allow all rotation to mimic the hands)
colliders[0].transform.SetParent(transform);
// If there is no RigidBody attached to the current object, then assign it a RigidBody variable
if (colliders[0].GetComponent<Rigidbody>() == null)
{
colliders[0].gameObject.AddComponent<Rigidbody>();
}
// Disable all physics on the grabbed object until it is released (May need to change if we want to enable objects to be swung and hit other "live objects")
colliders[0].GetComponent<Rigidbody>().isKinematic = true;
// Save the current object for a reference later
_currentlyHeldObject = colliders[0].transform;
// If other hand grabs the object, then release the object to be able to be re-parented above
if (secondaryHandRef.CurrentGrabObject != null)
{
secondaryHandRef.CurrentGrabObject = null;
}
}
}
}
else
// We must now have an object in our hand at this point. We now update its current position to the current hand poistion + the offset definded above
{
//If we release the grab button, we drop the object here
if (Input.GetAxis(buttonName) < 0.1f)
{
// Return the value of the object back to Non-Kinematic and re-enable physics
Rigidbody _objectRB = _currentlyHeldObject.GetComponent<Rigidbody>();
_objectRB.isKinematic = false;
_objectRB.collisionDetectionMode = CollisionDetectionMode.Continuous;
// Fun stuff starts here, we now calculate the hands current velocity
Vector3 velocity = (transform.position - _lastFramePosition) / Time.deltaTime;
// We now set that object velocity to the current velocity of the hand (I may adjust this to improve physics simulation)
_objectRB.velocity = velocity * throwingMultiplier;
// Kill the reference and revert the hands back to being empty and remove its parent
_currentlyHeldObject.SetParent(null);
_currentlyHeldObject = null;
}
}
//Release the grab function
if (Input.GetAxis(buttonName) < 0.5f && _isHeld)
{
_isHeld = false;
}
// Store the current position of the hand for velocity calculation in the upcoming frame
_lastFramePosition = transform.position;
//Tying off all loose ends here
}
}
In your update function:
if (_currentlyHeldObject != null && !_currentlyHeldObject.GetComponent<Rigidbody>().isKinematic) {
_currentlyHeldObject.GetComponent<Rigidbody>().isKinematic = true;
}
Currently I'm simply trying to change the sprites candle from unlit to lit when the player has 'picked up' both the candle and the matches and the candle will 'go out' after a certain amount of time. However, when the space bar is pressed the transition from unlit to lit isn't occurring, even though the debug log is returning true when it should. I'm posting here to get some guidance as I have spent most of the day looking online and literally have no idea how to proceed.
Basically the images I am trying to transition between are two different images which are in the sprites folder under assets.
This is what I've got so far.
//the two sprites transition
public Sprite unlitCandle;
public Sprite litCandle;
private SpriteRenderer spriteRenderer;
bool pickUpMatches = false;
bool pickUpCandle = false;
float timeRemaining =5;
bool candleLit = false;
// Use this for initialization
void Start () {
spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer.sprite == null)
spriteRenderer.sprite = unlitCandle;
}
// Update is called once per frame
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.CompareTag("Matches"))
{
collision.gameObject.SetActive(false);
pickUpMatches = true;
}
if (collision.gameObject.CompareTag("UnlitCandle"))
{
collision.gameObject.SetActive(true);
pickUpCandle = true;
}
}
public void CandleTimer()
{
if (candleLit == true)
{
timeRemaining = 5;
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0)
{
candleLit = false;
spriteRenderer.sprite = unlitCandle;
}
}
}
public void ChangeSprite()
{
if (spriteRenderer.sprite == unlitCandle)
{
spriteRenderer.sprite = litCandle;
}
}
void Update () {
if (pickUpCandle == true && pickUpMatches == true)
{
//Debug.Log(candleLit);
if (Input.GetKey(KeyCode.Space) && !candleLit)
{
CandleTimer();
ChangeSprite();
Debug.Log(timeRemaining);
candleLit = true;
//Debug.Log(candleLit);
}
}
}
}
Try comparing with a method like equals() instead of == in
spriteRenderer.sprite == unlitCandle
Because right now you are just comparing references and not the objects.
At least I think thats the problem.
There are a few possible issues with your code. First, you are calling changeSprite at the top of Update, which means that it is unconditionally being called every frame. Therefore, after a single frame of your candle being unlit, it will immediately change its sprite to litCandle.
I assume that the reason you are calling changeSprite every frame is in order to process the timer if you have a lit candle already. Really, you should move the code to process the timer (your whole second if statement in changeSprite) to a separate function and name it something like processCandleTimer. Call that at the top of Update and save the changeSprite method to only be called on the keypress.
Lastly, the issue that I suspect is giving you the most trouble is that you aren't resetting your timer, timeRemaining. The first time you light the candle the timer will go down to 0 after the 5 seconds pass. Every time changeSprite is run after that, you will change the sprite to litCandle in the first if statement and then immediately change it back to unlitCandle because the timer is 0 in the second. To remedy this, you need to add a line like timeRemaining = 5.0f; when the key is hit.