Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
Related
I'm trying to create projection of the ship trajectory, but I think it is too resource expensive to be usable since the editor freezes after a couple of seconds.
The trajectory has to take into account a couple of things:
Player's velocity (and input)
Player being attracted by other planets. (I THINK this is included in the velocity vector).
I've tried moving expensive operations outside the function that is called at FixedUpdate, link GetComponent or move to scene, but it still freezes, a little less but freezes.
My best guess is that the physics simulation is too expensive to run on FixedUpdate, but I don't know if there is a better method to call it on.
Here is my current code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
...
[SerializeField] public Rigidbody playerRigidBody;
// Projection
private Scene _simulationScene;
private PhysicsScene _physicsScene;
[SerializeField]
private int _maxPhysicsFrameIterations = 10;
private GameObject _ghostObj;
[SerializeField]
private GameObject _markerPrefab;
private List<GameObject> _markersPool;
// Input Values
...
private Rigidbody _ghostRigidBody;
...
// Start is called before the first frame update
void Start()
{
playerRigidBody = GetComponent<Rigidbody>();
currentBoostAmount = maxBoostAmount;
// Projection
_markersPool = new List<GameObject>();
for (int i = 0; i < _maxPhysicsFrameIterations; i++)
{
GameObject marker = Instantiate(_markerPrefab);
_markersPool.Add(marker);
}
CreatePhysicsScene();
}
private void OnDisable()
{
Attractor.players.Remove(this);
Destroy(_ghostObj);
}
// Update is called once per frame
void FixedUpdate()
{
HandleBoosting();
HandleMovement();
if (thrust1D != 0 || upDown1D != 0 || strafe1D != 0 || roll1D != 0 || pitchYaw != Vector2.zero)
{
SimulateTrajectory();
}
}
void CreatePhysicsScene()
{
var scene = SceneManager.GetSceneByName("SimulationScene");
var isSimulatioSceneValid = scene.IsValid();
if (isSimulatioSceneValid)
{
_simulationScene = scene;
}
else
{
_simulationScene = SceneManager.CreateScene("SimulationScene", new CreateSceneParameters(LocalPhysicsMode.Physics3D));
}
_physicsScene = _simulationScene.GetPhysicsScene();
_ghostObj = Instantiate(this.gameObject, transform.position, transform.rotation);
_ghostObj.GetComponent<PlayerInput>().enabled = false;
_ghostObj.GetComponent<Renderer>().enabled = false;
_ghostRigidBody = _ghostObj.GetComponent<Rigidbody>();
SceneManager.MoveGameObjectToScene(_ghostObj, _simulationScene);
}
public void SimulateTrajectory()
{
Physics.autoSimulation = false;
_ghostObj.transform.position = transform.position;
_ghostRigidBody.velocity = playerRigidBody.velocity;
for (int i = 0; i < _maxPhysicsFrameIterations; i++)
{
_physicsScene.Simulate(i + 1 / _maxPhysicsFrameIterations);
_markersPool[i].transform.position = _ghostObj.transform.position;
}
Physics.autoSimulation = true;
}
}
Similar to most people on here I'm fairly new to the unity scene and I'm attempting to create a specific mechanism in my game: I want to create a bush where I can spawn fruit items from; the fruit on the bush take a specific amount of time to grow and can't be interacted with until they're fully grown. Once grown as soon as you grab the fruit, it spawns another in the position of the original to repeats the cycle.
However, how the code works is it spawns a first fruit, grows and interacts once grown but once I grab the first fruit, the plant stops growing and just shows the fully grown object(however after every time you grab that fruit it spawns another fruit as intended) ,this object doesn't react with gravity unlike the original which applies gravity after interaction.
The object is a prefab and contains the following script:
using System.Collections; using System.Collections.Generic;
using UnityEngine; using UnityEngine.XR.Interaction.Toolkit;
public class GrowthScript : MonoBehaviour { Collider ObjectCol; [SerializeField] private GameObject Item;
public int GrowTime = 6;
public float MinSize = 0.1f;
public float MaxSize = 1f;
public float Timer = 0f;
private XRGrabInteractable grabInteractable = null;
public bool IsMaxSize = false;
public bool CanRegrow = false;
// Start is called before the first frame update
void Start()
{
Debug.Log("Growing");
IsMaxSize = false;
ObjectCol = GetComponent<Collider>();
// if the plant isnt full size, it starts a routine to grow
if (IsMaxSize == false)
{
ObjectCol.enabled = !ObjectCol.enabled;
StartCoroutine(Grow());
}
}
private void Awake()
{
grabInteractable = GetComponent<XRGrabInteractable>();
grabInteractable.onSelectEntered.AddListener(Obtain);
}
private IEnumerator Grow()
{
Vector3 startScale = new Vector3(MinSize, MinSize, MinSize);
Vector3 maxScale = new Vector3(MaxSize, MaxSize, MaxSize);
do
{
transform.localScale = Vector3.Lerp(startScale, maxScale, Timer / GrowTime);
Timer += Time.deltaTime;
yield return null;
}
while (Timer < GrowTime);
IsMaxSize = true;
CanRegrow = true;
Debug.Log("Grown");
ObjectCol.enabled = !ObjectCol.enabled;
}
private void Obtain(XRBaseInteractor interactor)
{
if (CanRegrow == true)
{
GameObject instance = Instantiate(Item, transform.position, transform.rotation) as GameObject;
CanRegrow = false;
}
}
}
It would be Deeply appreciated if I could receive help on why the prefab doesn't run the code on respawn or a way to solve the problem.
Much appreciated (Good Luck)
I am spawning objects on startup,(maxObj = 75) then destroying Obj's on event and disabling spawner Obj. When player wants they can re enable spawner. I need count to start at 0 on enable. Another 75 obj's spawn and are then destroyed. etc. Appreciate any help thanks.
enter code here
private static readonly float _spawnTime = 0.125f;
[SerializeField]
private GameObject _asteroidObject = null;
[SerializeField]
private int _maxObjects = 0;
private int _spawnedObjects = 0;
private float _time = 0;
private void Update()
{
if(_spawnedObjects < _maxObjects)
{
if(_time > _spawnTime)
{
Instantiate(_asteroidObject, transform.position, Quaternion.identity);
++_spawnedObjects;
_time = 0;
}
_time += Time.smoothDeltaTime;
}
}
Touch it is quite unclear how a User shall be able to start the spawn again I would recommend to rather use a Coroutine in general. They are like little temporary Update blocks but easier to control and maintain. It is also more efficient since it doesn't call the Update method every frame also when the maxObjects amount is already reached and nothing is going to happen anyway
[SerializeField]
private GameObject _asteroidPrefab;
[SerializeField]
private float _spawnInterval = 0.125f;
[SerializeField]
private int _maxObjects;
private bool _canStart = true;
// However your player calls this method
public void StartSpawn()
{
// Only start spawning if there is no other spawn routine already running
if(_canStart) StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
// Just in case ignore if another routine is already running
if(!_canStart) yield break;
// block concurrent routines
_canStart = false;
var interval = new WaitForSeconds(_spawnInterval);
for(var i = 0; i < _maxObjects; i++)
{
Instantiate(_asteroidObject, transform.position, Quaternion.identity);
// yield makes Unity pause this routine and render the frame
// and continue from here in the next frame
// Then since we yield return another IEnumerator in thi case WaitForSconds
// it results exactly in this: Holds here for given seconds and then goes to the next iteration
yield return interval;
}
// re-enable the StartSpawn
_canStart = true;
}
Then in case you additionally also want to automatically start spawning in the beginning you can simply call it in
private void Start()
{
StartSpawn();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnObj : MonoBehaviour
{
[SerializeField]
private GameObject _asteroidPrefab;
[SerializeField]
private float _spawnInterval = 0.125f;
[SerializeField]
private int _maxObjects;
private bool _canStart = true;
private void Start()
{
StartSpawn();
}
// However your player calls this method
public void StartSpawn()
{
// Only start spawning if there is no other spawn routine already running
if (_canStart) StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
// Just in case ignore if another routine is already running
if (!_canStart) yield break;
// block concurrent routines
_canStart = false;
var interval = new WaitForSeconds(_spawnInterval);
for (var i = 0; i < _maxObjects; i++)
{
Instantiate(_asteroidPrefab, transform.position, Quaternion.identity);
// yield makes Unity pause this routine and render the frame
// and continue from here in the next frame
// Then since we yield return another IEnumerator in thi case WaitForSconds
// it results exactly in this: Holds here for given seconds and then goes to the next iteration
yield return interval;
}
// re-enable the StartSpawn
_canStart = true;
}
}
The jump button is not working in my unity, as my player is not jumping. I followed a tutorial but it does not seem to work. Must be something wrong with my code but I cannot seem to work it out as there are no errors.
I am not sure but, I created an image in unity and placed it in canvas and named it "Fixed Joybutton", with the script Joybutton.cs, but when I run it and try to click it, it wont make my player jump. I thought the problem was the "joybutton = FindObjectOfType();" and changed it to "joybutton = GetComponent();" but still doesnt work.
This is the code for the jump button itself, Joybutton.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
{
[HideInInspector]
public bool Pressed;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void OnPointerDown(PointerEventData eventData)
{
Pressed = true;
}
public void OnPointerUp(PointerEventData eventData)
{
Pressed = false;
}
}
// and this is the code that is in my player, PlayerController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour
{
protected Joystick joystick;
protected Joybutton joybutton;
protected bool jump;
public float speed;
public Text playerDisplay;
public Text scoreDisplay;
public bool isFlat = true;
private Rigidbody rb;
public static int count = 0;
private void Start()
{
joystick = FindObjectOfType<Joystick>();
joybutton = FindObjectOfType<Joybutton>();//this is to find the object, I changed it to "joybutton = GetComponent<Joybutton>();" but still doesnt work
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
rb.velocity = new Vector3(joystick.Horizontal * 5f, rb.velocity.y, joystick.Vertical * 5f); // joystick to move my player, its working
if (!jump && joybutton.Pressed)// this is for the jump, that is not working
{
jump = true;
rb.velocity += Vector3.up * 10f;
}
if (jump && !joybutton.Pressed)
{
jump = false;
}
}
public void OnTriggerEnter(Collider other)
{
GameObject gameControl = GameObject.Find("Timer");//name of my gameobject
Loading loading = gameControl.GetComponent<Loading>();
if (other.gameObject.CompareTag("Pick Up"))
{
other.gameObject.SetActive(false);
count = count + 1;
Awake();
}
else if (other.gameObject.CompareTag("Time Boost"))
{
other.gameObject.SetActive(false);
loading.sec += 10;
if (loading.sec > 60)
{
int sec1 = loading.sec - 60;
loading.sec = sec1;
loading.minutes ++;
}
Awake();
}
}
private void Awake()
{
if (DBManager.username == null)
{
UnityEngine.SceneManagement.SceneManager.LoadScene(0);
}
playerDisplay.text = "Player: " + DBManager.username;//display username
scoreDisplay.text = "Score: " + count.ToString();//display score
}
}
//Player is moving but cannot jump. Please show me the error in my code and solution. Thank you so much.
There's a lot to unpack here.
UI Events like OnPointerDown occur on the frame cycle. FixedUpdate happens on the fixed cycle. There is a good chance that you have a very high framerate, resulting in 5-6 frames per fixed cycle. Your joybutton will only be set to "pressed" for one frame, so there is a very high chance that the joybutton triggers OnPointerUp before the jump can occur.
The quickest solution to this problem would be to not "reset" the joybutton's pressed state using OnPointerUp, and instead reset it when the jump is processed in your PlayerController. That being said, can you help me understand why these 2 scripts are separated?
Rather than set the .Pressed state to false in the JoyButton script, reset it after you consume it in your PlayerController:
if (!jump && joybutton.Pressed)// this is for the jump, that is not working
{
jump = true;
joybutton.Pressed = false; //Add this line of code
rb.velocity += Vector3.up * 10f;
}
How do yo do for avoiding the player to jump twice in the air?
private void FixedUpdate()
{
rb.velocity = new Vector3(
joystick.Horizontal * 5f, rb.velocity.y,
joystick.Vertical * 5f);
if (!jump && joybutton.Pressed)
{
jump = true;
rb.velocity += Vector3.up * 10f;
}
if (jump && !joybutton.Pressed)
{
jump = false;
}
}
Basically I want to make a player that can transform into demon at will whenever the user press the power-up button however I want the transformation to end after 60 seconds (when the transformation ends I want the player revert back to his original state). I also want the transformation to end if the player gets hit by an enemy. So far I've made this code and it works but I'm having trouble resetting the yield wait for seconds back to 60 seconds when if the player gets hit by an enemy and if the user decided to press the button to transform the player back into a demon. Can anyone help me with this problem?
In my hierarchy I have my player as the parent and my demon player as the child. A playermovement script attached to the player as well as the transformation script below:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
void Start () {
target = transform.position;
}
void Update () {
target.z = transform.position.z;
}
public void DemonCharacter() {
StartCoroutine (PowerUpCoroutine ());
}
private IEnumerator PowerUpCoroutine() {
yield return new WaitForSeconds (0.3f);
par1.Play (); // particle system animation to cover transformation happening
par1.transform.position = target;
yield return new WaitForSeconds (0.2f);
demon.SetActive (true); // activates demon gameobject
rend.enabled = false; // deactivate players spriterenderer
col.enabled = false;
yield return new WaitForSeconds (60f);
demon.SetActive (false); // deactivates demon gameobject
rend.enabled = true; // activate players spriterenderer
col.enabled = true;
par1.Stop ();
}
And on my demon player, I attached this script;
I works but when the user clicks on the button to transform into a demon the yield waitforseconds doesn't stop, so when the player transform into a demon seconds later the demon player transforms back into the player rather than resetting the yield wait for seconds.
public BoxCollider2D Playercol;
public Renderer PlayerRend;
void Start()
{
}
void Update ()
{
}
void OnTriggerEnter2D(Collider2D col) {
if (col.tag == "enemy") {
demon.SetActive (false);
PlayerRend.enabled = true;
Playercol.enabled = true;
}
}
Another way than what #m.rogalski suggested would be to use a simple float variable as timer:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
private float demonTimer;
void Start()
{
target = transform.position;
demonTimer = 0.0f;
}
void Update()
{
target.z = transform.position.z;
if (demonTimer > 0.0f)
{
demonTimer -= Time.deltaTime;
if (demonTimer <= 0.0f)
{
demon.SetActive(false);
rend.enabled = true;
col.enabled = true;
}
}
}
public void DemonCharacter()
{
par1.Play();
par1.transform.position = target;
demon.SetActive(true);
rend.enabled = false;
col.enabled = false;
demonTimer = 60.0f;
}
public void CancelDemon()
{
demonTimer = 0.0f;
}
Hope this helps,
My suggestion would be for you to modify your Coroutine to not use WaitForSeconds but use it's own timing calculations.
// create the flag indicating interruption
bool _interrupt = false;
// create your coroutine
IEnumerator PowerUpCoroutine()
{
// set the time you want to hold transformation
const float TRANSFORMATION_INTERVAL = 60.0f;
// currently elapsed time
float currentlyElapsed = 0.0f;
// add your logic for pre-transformation
while ( currentlyElapsed < TRANSFORMATION_INTERVAL && !_interrupt )
{
yield return null;
currentlyElapsed += Time.deltaTime;
}
// add post-transformation logic
// revert transformation process
_interrupt = false;
}
Now if you run this coroutine calling StartCoroutine(PowerUpCoroutine()); you can interrupt it setting _interrupt flag to true. eg :
public void Interrupt()
{
_interrupt = true;
}
// in some update :
if ( gotHitThisFrame == true )
Interrupt();