I have this script, but doesn't work, when i kill one enemy the door opens, i need to know how active the door when i kill all enemies. I need to destroy all enemies to open the door, but doesn't work good, if i kill one enemy the door opens for no reason.
public class DoorScript : MonoBehaviour
{
private GameObject[] enemyToKill;
public Transform pos1, pos2;
public float speed;
public Transform startPos;
Vector3 nextPos;
public bool EnemyDead = false;
// Start is called before the first frame update
void Start()
{
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
// Update is called once per frame
void Update()
{
foreach (GameObject enemy in enemyToKill)
{
if (enemy == null)
{
EnemyDead = true;
if (transform.position == pos1.position && EnemyDead)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
}
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(pos1.position, pos2.position);
}
}
Well you are setting your bool to true for the first enemy that is killed.
You should rather check if all enemies are non existent. In general do never compare anything derived from UnityEngine.Object for null! Rather use the implicit bool operator.
Then you could use Linq Any (or All) like e.g.
using System.Linq;
...
void Update()
{
// In general use a bool check!
// A destroyed object reference is not necessarily null
// This returns true if there is still an existing enemy
if(!EnemyDead && enemyToKill.Anyy(enemy => enemy)) return;
// Still keep the flag so the check can be skipped
// As soon as the flag is already set
EnemyDead = true;
if (transform.position == pos1.position)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
If I assume correctly (it is not clear from above code), You have set to all enemies some tag "EnemyR1" and now You want to check, if those enemies exists or not. If there are no enemies, you want to open the doors (like exit point for player).
It also depends, if you kill the enemy immediately (and destroy the GameObject), or you only mark enemy as something (set some property/set is non-active/set different tag).
In the code you want to:
void Start()
{
// There you will get ALL objects that have the tag (that was ok before)
// This function always returns Array. Sometimes empty.
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
void Update()
{
// Now it depends how your logic is implemented:
// If you destroy automatically all objects, array will be empty, check length:
if (enemyToKill.Length == 0)
{
//There are no enemies to be killed, You can open the door
}
// Else if objects still exist, you must check if all have the condition.
// They can be disabled, have some property, etc.. In that case You need to:
bool allKilled = true;
foreach (GameObject enemy in enemyToKill)
{
if (enemy.active) //update this condition as you need
{
allKilled = false; // Set to false, when enemy is not killed
break; // and exit the loop
}
}
if (allKilled)
{
//There are no enemies to be killed, You can open the door
}
}
Related
I want to add doors to my Unity project and so far I have gotten the code to work with my door but I can open it from any distance in the scene. I want to be able to only open it from a small distance away but I cannot figure out how.
public class Door : MonoBehaviour
{
public bool doorIsOpen;
public Transform closedPos;
public Transform openPos;
public float openSpeed;
public float openRadius;
void Update()
{
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
{
if(Input.GetKeyDown(KeyCode.E))
{
doorIsOpen = !doorIsOpen;
}
}
if(doorIsOpen == true)
{
OpenDoor();
}
if(doorIsOpen == false)
{
CloseDoor();
}
}
void OpenDoor()
{
doorIsOpen = true;
gameObject.transform.position = openPos.position;
gameObject.GetComponent<Animator>().SetTrigger("OpenDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", true);
}
void CloseDoor()
{
doorIsOpen = false;
gameObject.transform.position = closedPos.position;
gameObject.GetComponent<Animator>().SetTrigger("CloseDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", true);
}
}
Prerequisites
Add a sphere with a collision body around the door instance in Unity. This sphere functions as the radius which will trigger the ChangeDoorState method.
Change the update method
The update will look at any collisions happening in the specified sphere. If there is at least one object in range (the collision sphere) of the door, then it opens or closes the door instance. Source: https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html
void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(center, radius);
if (hitColliders.Length > 0)
{
ChangeDoorState();
}
}
Merged the OpenDoor and CloseDoor methods
void ChangeDoorState()
{
doorClosureState = doorClosure ? true : false;
gameObject.transform.position = doorClosureState ? closedPos.position : openPos.postition;
gameObject.GetComponent<Animator>().SetTrigger("DoorClosure");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", doorClosureState);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", !doorClosureState);
}
You can increase the value of 'openRadius', the game is creating a sphere at gameObject.transform.position with a radius of 'openRadius' and checking if there is any colliders overlapping the sphere.
//...
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
//...
One issue is that you permanently set the triggers in every frame. You would only want to do so when you hit the key.
Then also Physics.CheckSphere checks whether there is any collider within the range. This could be any collider, not only the player. To make sure it only works if the player is the one in range you should definitely use Layers and give the player its own layer e.g. "Player". Then you can pass the layerMask parameter to make sure the doors only check for the player's layer.
so I would simply use
// if possible already reference this in the Inspector
[SerializeField] private Animator _animator;
[Tooltip("Here select only the Layer(s) which you assigned to your Player object(s)")]
[SerializeField] LayerMask _playerLayer;
private void Awake()
{
// as fallback get it ONCE on runtime
if(!_animator) _animator = GetComponent<Animator>();
}
private void Update()
{
// Afaik the Key is way cheaper to check so do it first
// and use physics only if actually necessary
if(Input.GetKeyDown(KeyCode.E))
{
// Only check for the Player's Layer and ignore other colliders
if(Physics.CheckSphere(transform.position, openRadius, _playerLayer.value))
{
ToggleDoorState();
}
}
}
// Since manually changing the flag in the Inspector will not have
// any effect anymore in order to be able to test it you can use
// the context menu of this component in the Inspector
[ContextMenu(nameof(ToggleDoorState))]
private void ToggleDoorState()
{
// invert the flag
doorIsOpen = !doorIsOpen;
// use the flag in ternary expressions
transform.position = doorIsOpen ? openPos.position : closedPos.position;
_animator.SetTrigger(doorIsOpen ? "OpenDoor" : "CloseDoor");
// use the value of the flag diectly
_animator.SetBool("DoorIsClosed", !doorIsOpen);
_animator.SetBool("DoorIsOpen", doorIsOpen);
}
It is seems a bit though as if you have a bit of redundancy in your animator. I would either use the Bools in order to trigger a transition or use the Triggers, to have both seems odd.
I'm making a 3D roulette game, where when the player presses the 'bet' button the ball will be positioned in a given location and also a force will be added to the ball. I've added 37 individual box colliders to know where the ball lands.
My problem is that from my understanding the bet function gets executed in a single frame. This means that the script checks for the fallen number before the ball has finished moving and landed inside a box collider. So the for the first bet the number fallen will be 0 even if it lands on another number, and then on the 2nd bet it will have the value of the first fallen number, etc...
public void BetSpinWheel()
{
uiController.repeatButton.interactable = true;
Spin();
int earnings = CalculateEarnings(currentBet.ToArray(), lastNumbers);
UpdateBalance(earnings);
lastBet.Clear();
foreach(Bet bet in currentBet)
{
lastBet.Add(bet);
}
currentBet.Clear();
updateUI();
uiController.ChangeLastNumberText();
}
private void Spin()
{
audioSource.Stop();
audioSource.Play();
ballController.SpinBall();
while (ballController.isMoving)
{ random++; }
if (!ballController.isMoving && ballController.hasBallEnteredCollider)
{
lastNumbers = ballController.numberFallen;
print(lastNumbers);
}
}
and here is the ballController.SpinBall() function:
public void SpinBall()
{
rb.constraints = RigidbodyConstraints.None;
transform.position = ballStartPosition;
rb.velocity = Vector3.zero;
transform.rotation = Quaternion.Euler(0f, 0f, 0f);
rb.AddForce(forceToAdd, ForceMode.Impulse);
print(isMoving);
}
If you would like to view the whole project, you can find it here:
https://github.com/hamzaOud/Assignment02
Use a Coroutine:
// The button should call this
public void BetSpinWheel() {
StartCoroutine(BetSpinWhellCoroutine());
}
private IEnumerator BetSpinWheelCoroutine() {
// Your bet stuff here
}
You can also 'store' a coroutine, so that you can stop it if you need to:
private Coroutine betSpinWheelCoroutine
// The button should call this
public void BetSpinWheel() {
// Stop the coroutine first if it was running.
if (betSpinWheelCoroutine != null){
StopCoroutine(betSpinWheelCoroutine);
}
betSpinWheelCoroutine = StartCoroutine(BetSpinWhellCoroutine());
}
private IEnumerator BetSpinWheelCoroutine() {
// Your bet stuff here
}
So, I'm still not the best at this but I'm trying to use this script for 2D movement but the jumping isn't working for some reason. It keeps saying that the OnCollisionEnter function "is declared but never used". Can someone tell me what im doing wrong? Thanks
If I remove the (Collision col) part it says that "void cannot be used in this context".
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RonyWalking : MonoBehaviour
{
Rigidbody2D rigid;
SpriteRenderer sprite;
public bool isJumping;
public float spd = 2.0f;
// Start is called before the first frame update
void Start()
{
rigid = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKey("d")){rigid.velocity = new Vector2(spd, 0);}
else if(Input.GetKey("a")){rigid.velocity = new Vector2(-spd, 0);}
if(Input.GetKeyDown("w") && isJumping == false)
{
rigid.velocity = new Vector2(0, 5);
isJumping = true;
}
void OnCollisionStay(Collision col)
{
isJumping = false;
}
}
}
When using 2D physics, you need to use the 2D lifecycle methods;
void OnCollisionStay2D(Collision2D col)
{
isJumping = false;
}
And you shouldn't put this method inside your Update method... It should be on class level:
public class RonyWalking
{
void Update()
{
// ...
}
void OnCollisionStay2D(Collision2D col)
{
// ...
}
}
Don't worry about "Is declared but never used", this may be because you don't have specific code referencing the method, but Unity will raise events that calls it, "automagically"
Another thing that I can see while reading your code, that may be unintentional behaviour for you, is that when clicking left/right, you set velocity UP to 0, and when clicking up you set LEFT/RIGHT velocity to 0; this will result in freezing the movement mid-air if you jump, then move while in air:
Click D; velocity = 2, 0
Click W; velocity = 0, 5
Character will now move upwards until another input is given
Click D; velocity = 2, 0 and the character will continue moving while in air because when moving sideways the up/down velocity is set to 0
To solve this, either set the other to existing velocity or make the inputs manipulate a Vector that you then apply at the end of the movement code:
Vector2 existingMovement = rigid.velocity;
if (Input.GetKey(KeyCode.D))
existningMovement.x = spd;
else if (Input.GetKey(KeyCode.A))
existningMovement.x = -spd;
if (Input.GeyKeyDown(KeyCode.W) && !isJumping)
{
existningMovement.y = 5f;
isJumping = true;
}
Furthermore, I think you may have some unexpected behaviour with OnCollisionStay; it will fire every frame that you're colliding with the ground, I assume. But I think it may also fire a frame or two AFTER you've jumped since the physics of your character will not INSTANTLY leave the collision, so isJumping will be set to false even after your jump, letting you jump while in the air one more time.
I would recommend that you use OnCollisionExit2D(Collision2D col) to set isJumping = true instead, or OnCollisionEnter2D(Collision2D col) and set it to isJumping = false, depending on the functionality you desire (if you want the ability to jump after walking out of a cliff)
I'm trying to add sound to my pushable object and just have a simple if statement for checking if the pushable object is moving. The concept is quite simple, if the object is moving the sound should play and when it's not moving it shouldn't. The problem however is, that when I debug the value there is a 0 every 5 frames or so. This causes the sound to work inconsistently. The script I have is really simple, and I have tried changing to fixedupdate, but it didn't work. I had read somewhere that physics calculations are done in fixedUpdate.
public class PushableObject : MonoBehaviour
{
Rigidbody rb;
AudioSource audioS;
bool rbIsMoving = false;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
audioS = GetComponent<AudioSource>();
}
// Update is called once per frame
void FixedUpdate()
{
if (rb.velocity == Vector3.zero)
{
Debug.Log("Not moving");
audioS.volume = 0f;
}
else
{
Debug.Log("Moving");
audioS.volume = 0.1f;
}
}
}
Edit: I have just figured out that if the player pushes the pushable object into the wall the sound is still playing, for this reason I think I have to change the way too determine if the object is moving or not.
This happens due to single precision floating point.
You never (should) compare two float values directly since they might logically be equal (e.g. 1 = 5*0.2) but for the computer they might be different by a small "epsilon"
So Unity decides that Vector3 simply uses only a precision of 0.00001 for equality for ==
Rather use Mathf.Approximately which uses a very small Epsilon instead.
Than easier than comparing each component of the rb.velocity against 0 what you actually want is the rb.velocity.magnitude which is actually the overall speed.
if (Mathf.Approximately(rb.velocity.magnitude, 0))
Update
Alternatively store the last position and compare it to the current one using Vector3.Distance either again with Mathf.Approximately
private Vector3 lastPosition;
private void LateUpdate()
{
if(Mathf.Approximately(Vector3.Distance(lastPosition, transform.position), 0))
{
//...
}
else
{
//...
lastPosition = transform.positiom;
}
}
Or with a custom threshold
if(Vector3.Distance(lastPosition, transform.position) <= someThreshold)
or this time you actually can use == if a threshold of 0.00001 is what you want
if(lastPosition == transform.position)
You can check if the RigidBody is sleeping:
if (!rb.IsSleeping()
{
//it's moving
}
else
{
//it's not
}
Or check if the transform position has moved since last frame:
Vector3 lastposition;
Gameobject go = somegameobject;
function Update()
{
if (lastposition == go.transform.position)
{
//not moving
}
else
{
//moving
}
lastposition = go.transform.position;
}
You can use Transform.hasChanged to check if the player position has changed on the last update
if (!this.transform.hasChanged)
{
print("Player is not moving");
}
transform.hasChanged = false;
I am very new to unity and am building a VR app for Oculus Go. I want to pick and move the object by pointing the ray from the controller on the object and then picking or releasing it by pressing the trigger button. I want the object to stay fixed at the end of the ray's position rather than coming suddenly onto the controller. I have used this script to create a ray and basically allow the controller to pick it up but this script shits the object to the controller's position and as a result I can only move object in a circle(in 360 degrees). It also does not drop the object correctly, as the objects continue to float.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerPointer : MonoBehaviour {
//Returns whatever object is infrount of the controller
private GameObject pointerOver;
[SerializeField]
//Is the object that is currently intractable
private PropBase selectedObject;
//Is the object currently stored in hand, ready to throw.
[SerializeField]
private PickUp inHand;
//This is a refrance to the object we want the pointer to be cast from.
[SerializeField]
public Transform controllerRef;
//This is where we want object we are holding to appear
[SerializeField]
private Transform holdingRef;
//The amount of force we want to throw objects from our hand with.
[SerializeField]
[Range(2,12)]
private float throwForce = 10;
//The script that handles the visuals to show what object is selected
[SerializeField]
private HighlightObject selectVisual;
private LineRenderer line;
void Start () {
line = GetComponent<LineRenderer> ();
}
void Update () {
//If a object is currently being held I don't want to select another
object until it is thrown.
if (inHand == null) {
WorldPointer ();
} else {
line.SetPosition (0, controllerRef.position);
line.SetPosition (1, controllerRef.position);
pointerOver = null;
}
//This function handles how you intract with selected objects
Intract ();
}
//This function handles shooting a raycast into the world from the
controller to see what can be intracted with.
void WorldPointer(){
//We set the line visual to start from the controller.
line.SetPosition (0, controllerRef.position);
RaycastHit hit;
//We reset the pointer so things don't stay selected when we are
pointing at nothing.
pointerOver = null;
//This sends a line from the controller directly ahead of it, it returns
true if it hits something. Using the RaycastHit we can then get information
back.
if (Physics.Raycast (controllerRef.position, controllerRef.forward, out
hit)) {
//Beacuse raycast is true only when it hits anything, we don't need
to check if hit is null
//We set pointerOver to whatever object the raycast hit.
pointerOver = hit.collider.gameObject;
//We set the line visual to stop and the point the raycast hit the
object.
line.SetPosition (1, hit.point);
//Here we check if the object we hit has the PropBase component, or
a child class of its.
if (pointerOver.GetComponent<PropBase> ()) {
//We set the object to be highlighted
selectVisual.NewObject (pointerOver);
} else {
selectVisual.ClearObject ();
}
} else {
//If the raycast hits nothing we set the line visual to stop a
little bit infrount of the controller.
line.SetPosition (1, controllerRef.position + controllerRef.forward
* 10);
selectVisual.ClearObject ();
}
Debug.DrawRay(controllerRef.position , controllerRef.forward *
10,Color.grey);
}
void Intract(){
//We set up the input "OculusTouchpad" in the Input manager
if (Input.GetButtonDown ("Jump") || OVRInput.GetDown
(OVRInput.Button.PrimaryTouchpad)) {
selectVisual.ClearObject ();
//Check if you are holding something you can throw first
if (inHand != null) {
inHand.Release (controllerRef.forward, throwForce);
inHand = null;
//We do this check here to prevent Errors if you have nothing
selected
} else if (selectedObject != null) {
//Check if you can pick up the selected object second
if (selectedObject.GetComponent<PickUp> ()) {
//Beacuse PickUp is a child of PropBase, we can ask InHand
to store selectedObject as PickUp, rather than use GetComponent
inHand = selectedObject as PickUp;
inHand.Store (holdingRef);
//If non of the above were valid then simple call the
trigger function of the selected object
} else {
selectedObject.Trigger ();
}
}
//If you have a object that you need to hold down a button to
intract with
} else if (Input.GetButton ("Jump") && selectedObject != null ||
OVRInput.Get (OVRInput.Button.PrimaryTouchpad) && selectedObject != null) {
selectedObject.Pulse ();
//When you are not pressing down the touchpad button, the selected
object can be updated
} else if (pointerOver != null) {
if (pointerOver.GetComponent<PropBase> ()) {
selectedObject = pointerOver.GetComponent<PropBase> ();
} else {
selectedObject = null;
}
} else {
selectedObject = null;
}
}
}
And i have attached this script to the objects I want to pick:
public class PickUp : PropBase
{
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
public virtual void Store(Transform NewParent)
{
//The following stops the object being effected by physics while it's in
the players hand
rb.isKinematic = true;
//And fixes it to the new parent it is given by the player script to
follow.
transform.parent = NewParent;
//It then resets it's position and rotation to match it's new parent
object
transform.localRotation = Quaternion.identity;
transform.localPosition = Vector3.zero;
}
public virtual void Release(Vector3 ThrowDir, float ThrowForce)
{
//On Release the object is made to be effected by physics again.
rb.isKinematic = false;
//Free itself from following it's parent object
transform.parent = null;
//And applies a burst of force for one frame to propel itself away from
the player.
rb.AddForce(ThrowDir * ThrowForce, ForceMode.Impulse);
}
}
What i'd like to see is have the position of the sphere change according to wherever the end of the ray is cast.
I have also attached this script to the player contoller, which allows it to move to a point by pointing to it and pressing the touchpad button.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClickToMove : MonoBehaviour
{
private Vector3 targetPos; //This Vector3 will store the position where we
click to move.
private bool Moving = false; /*This bool keeps track of whether we are in
the process of moving or not.*/
private GameObject targetInstance;
/*The variables we want to customize. Added info headers to these for the
Unity Editor.*/
[Header("Our Go controller object")]
public GameObject goController;
[Header("Movement Speed")]
public float speed = 1;
[Header("Stop When This Far Away From Target")]
public float haltDistance = 0;
[Header("Optional Target Object")]
public GameObject targetObj;
void Update()
{
MoveToTarget(); /*Here we simply run our MoveToTarget method in the
Update method.*/
//That way we don't clutter up the Update method with too much code.
}
void MoveToTarget() //Here we do the cluttering instead.
{
var ray = new Ray(goController.transform.position,
goController.transform.forward); /*Create a ray going from the goController
position and in the Forward direction of the goController.*/
RaycastHit hitInfo; //Store info about what the ray hits.
Physics.Raycast(ray, out hitInfo, 100);
if (OVRInput.GetUp(OVRInput.Button.PrimaryTouchpad)) /*If we release the
trigger..*/
{
targetPos = hitInfo.point; /*Make our targetPos assume the
positional value of the hit point.*/
if (targetObj) /*If we have specified a Target Object to mark where
we click*/
//If we didn't, then we don't want to try to instantiate it.
{
if (targetInstance) /*If there is already a Target Object in the
scene.*/
{
Destroy(targetInstance); //Destroy it.
}
targetInstance = Instantiate(targetObj, targetPos,
transform.rotation); //Create our Target object at the position we clicked.
}
Moving = true; //And finally we set Moving to True.
}
if (Moving == true) //Since Moving is now true
{
transform.position = Vector3.MoveTowards(transform.position, new
Vector3(targetPos.x, transform.position.y, targetPos.z), speed *
Time.deltaTime); /*Transform our x and z position to move towards the
targetPos.*/
/*Note that our y position is kept at default transform position
since we only want to move along the ground plane.*/
}
if (Vector3.Distance(transform.position, targetPos) <= haltDistance + 1)
/*Check proximity to targetPos. Mainly useful to keep your player from
setting a target position right next to say a building and then end up
clipping through half of it.*/
{
if (targetInstance) //If we created a Target Object..
{
Destroy(targetInstance); //Then we want to destroy it when we
reach it.
}
Moving = false; //Since we have now arrived at our target
//destination.
}
}
}
If anyone could point me in a right direction or help me with this, I would greatly appreciate it!
Thanks in advance.
Okay, with your updated question its now possible to try and answer.
First off - have you tried not resetting your BaseProp localPosition to the controller's?
Try commenting the line that says
transform.localPosition = Vector3.zero;
This wil still orient the object and parent it to the controller but will lock it in a position relative to the moment of parenting.
You currently use "holdingRef" object as a place where the object appears. You may want to use "controllerRef" instead.
To vary distance at which the object appears you can set the object position to:
controllerRef.position+ distance*controllerRef.forward
As this is the direction in which you fire your raycasts. You can get the hit distance by querying hit.distance.
If for any reason that doesn't work out for you, the very point of the raycast hitting the collider is available within HitInfo, so with hit.point you can extract the hit position and position the object relative to that point. Another very useful attribute of hitinfo is .normal, which enables you to get direction at which the hit happened.
You can pass that info along with your Store method.