Play sound while button is held down - c#

I have a 1.5 seconds audio file - a single gunshot sound. I want to be able to play the sound while the mouse is pressed (like an automatic weapon), and I used InvokeRepeating to call the shoot method, with a very low repeatRate:
if (Input.GetButtonDown("Fire1"))
{
InvokeRepeating("Shoot", 0f, 1f/currentWeapon.fireRate);
} else if (Input.GetButtonUp("Fire1"))
{
CancelInvoke("Shoot");
}
And this is the Shoot method:
void Shoot()
{
shootSound.PlayOneShot(shoot);
}
The problem is the sound cuts off and the shot can't be heard, it's playing for a fraction of a second instead of the whole audio clip. I tried play() and playOneShot().
Is there an option to play each clip to its fullest separately, like creating clones of it?
Thanks!

Most things in your code are just unnecessary. You don't need InvokeRepeating for this. Since you want to continue to player sound(shooting effect) while the button is held down, Input.GetButton should be used instead of Input.GetButtonDown because Input.GetButton is true every frame the button is held down and is made for things like auto fire.
A simple timer with Time.time should also be used to determine the rate to play the sound then play the sound with the PlayOneShot function.
This is what that should look like:
public float playRate = 1;
private float nextPlayTime = 0;
public AudioSource shootSound;
public AudioClip shoot;
void Update()
{
if (Input.GetButton("Fire1") && (Time.time > nextPlayTime))
{
Debug.Log("Played");
nextPlayTime = Time.time + playRate;
shootSound.PlayOneShot(shoot);
}
}
The playRate variable is set to 1 which means 1 sound per-sec. You can use this variable to control the play rate. Lower it to play many sounds. The value of 0.1f seems to be fine but it depends on the sound.

I solved it, I used an empty GameObject with an AudioSource, and instantiated a copy of it in each Shoot method:
GameObject gunObj = Instantiate(gunObject);
Destroy(gunObj, 1f);
Works perfectly now!

Related

Player unable to move after being teleported/transported Unity3d

I've been trying to fix this one bug in my code for over 7 hours now, upon being teleported, the movement controls cease to function, the mouse works fine, you can look around, but you can't move around.
I wanted to set up some simple code that would teleport the player to a "checkpoint" upon achieving a negative or null y level. I was doing this for a parkour based game, if the player fell off the platform they would have to start over, but after teleporting, it becomes impossible to move as I'm sure I have already said. My code is pretty simple:
public class Main : MonoBehaviour
{
float Fall;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 Checkpoint = new Vector3 (0,3,0);
GameObject Player = GameObject.FindGameObjectWithTag("Player");
Fall = GameObject.FindGameObjectWithTag("Player").transform.position.y;
if (Fall<-4)
{
Player.transform.position = Checkpoint;
}
}
}
You would think that this would just simply change the coordinates of the player, but I think this might be screwing with the FPSController script.
I am using Unity3d, with Standard Assets imported, All of the code is in C#.
Instead of checking the Y value of your character, I would instead place a death collider under the map. Make this a trigger and if the player touches this trigger, then teleport them back. Nothing with your code should screw with the FPS controller so it might be something else. I would also highly recommend not using a FindGameObjectWithTag in the Update() method as it is extremely expensive to use this every frame, especially twice. If you would rather keep the Update() Y component of the position check, please rewrite the code to something like this:
public class Main : MonoBehaviour
{
// assign this object of your player in the inspector - it stores the reference to reuse
// instead of grabbing it every frame
[SerializeField] private Transform playerTransform = null;
// make this a variable as it is not changing - might as well make this const too
private Vector3 checkpoint = new Vector3(0, 3, 0);
// constant value of what to check for in the Y
private const int FALL_Y_MARKER = -4;
// Update is called once per frame
void Update()
{
if (playerTransform.position.y < FALL_Y_MARKER)
{
playerTransform.position = checkpoint;
}
}
}
With your current code, there should be nothing breaking your input/movement, but with that said, we can not see your input/movement code. All the above snippet does is check if the Y component of the player objects position is below a certain value, and if it is, it sets the position to a new vector. Can you post a bit more movement code or somewhere else it can go wrong that you think is the issue?

How to make a non-UI countdown in Unity?

I'm trying to make a countdown attached to a sprite. The countdown will count down from 10 to zero, and won't be attached to the Canvas, so it won't be static on the screen. All of the tutorials for something like this have been for UI and don't allow 3D text. Anyone have any ideas on how to do this?
There are two ways to do this:
Use UI elements on a separate canvas set to World Space
Use the TextMesh element to put a 3D mesh of text in the world
I don't really know the pros and cons of each approach so go with whichever is easier for you to implement and keep the other in mind if you run into problems.
If you're looking for a detailed tutorial, anything explaining how to do popup damage numbers will be the same method, just with a different script telling it what text to display. There are a few good tutorials on the top of the YouTube results.
As for the countdown script, it's fairly simple.
//put this script on a GameObject prefab with a TextMesh component, or a canvas element
public class Countdown : MonoBehaviour
{
public TextMesh textComponent; //set this in inspector
public float time; //This can be set in inspector for this prefab
float timeLeft;
public void OnEnable()
{
textComponent = GetComponent<TextMesh>(); //in case you forget to set the inspector
timeLeft = time;
}
private void Update()
{
timeLeft -= Time.deltaTime; //subtract how much time passed last frame
textComponent.text = Mathf.CeilToInt(timeLeft).ToString(); //only show full seconds
if(timeLeft < 0) { gameObject.SetActive(false); } //disable this entire gameobject
}
}

Slow collision detection at low frame rates

I'm experiencing an odd issue with my collision detection. I'm using the Update method to move the player (I don't want to use FixedUpdate because that creates an undesired weird movement). The fixed timestep is set at the default 0.02 (I tried playing with time setting but that didn't work either) . I set the collision detection of the rigidbodies of both objects to "continuous dynamic". Also, I set the target frame rate to 300 and that didn't change anything...
When the framerate is low or the device itself is slow, the collision detection doesn't always work. The player can easily fall through the object it's supposed to collide with, though sometimes it doesn't.
Please tell me what I can do to fix this because I've published a game and many users are reporting this (serious) bug. Thank you for your support.
This is what is supposed to happen:
This is what actually happens:
(as you can see, the cube gets out of the wall and to the other side)
I move the player when the user releases the mouse button:
Script 1:
public Script2 Jumper;
public float TimeToJump;
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (Delay (1f/50f)); //Don't mind the time.
}
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds (waitTime);
if (Jumper != null)
{
Jumper.SetVelocityToJump (gameObject, TimeToJump);
}
}
Script 2 attached to player (cube):
public class Script2 : MonoBehaviour {
GameObject target;
private float timeToJump;
public bool isJumping = false;
public void SetVelocityToJump(GameObject goToJumpTo, float timeToJump)
{
StartCoroutine(jumpAndFollow(goToJumpTo, timeToJump));
this.timeToJump = timeToJump;
this.target = goToJumpTo;
}
private IEnumerator jumpAndFollow(GameObject goToJumpTo, float timeToJump)
{
var startPosition = transform.position;
var targetTransform = goToJumpTo.transform;
var lastTargetPosition = targetTransform.position;
var initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
var progress = 0f;
while (progress < timeToJump)
{
progress += Time.deltaTime;
if (targetTransform.position != lastTargetPosition)
{
lastTargetPosition = targetTransform.position;
initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
}
float percentage = progress * 100 / timeToJump;
GetComponent<Rigidbody>().isKinematic = percentage < 100.0f;
transform.position = startPosition + (progress * initialVelocity) + (0.5f * Mathf.Pow(progress, 2) * _gravity);
yield return null;
}
OnFinishJump (goToJumpTo, timeToJump);
}
private void OnFinishJump(GameObject target, float timeToJump)
{
if (stillJumping)
{
this.isJumping = false;
}
}
private Vector3 getInitialVelocity(Vector3 toTarget, float timeToJump)
{
return (toTarget - (0.5f * Mathf.Pow(timeToJump, 2) * _gravity)) / timeToJump;
}
}
The target of the cube is a child of the bigger cube (the wall).
If you require clarification, please leave a comment below. I might give the link to my game if you need more details.
Quote from here (found thanks to #Logman): "The problem exists even if you use continuous dynamic collision detection because fast moving objects can move so fast that they are too far apart from itself from one frame to the next immediate frame. It's like they teleported and no collision detection would ever be triggered because no collision existed, from each frame perspective, and thus from all calculations processed."
In my case, the cube is not going fast, but you get the concept.
There are several issues with your code.
You are asking a Coroutine to yield for 1/50th of a second. The minimum time a yield must occur for is one frame. If Time.deltaTime > 0.02f this is already one of the problems.
You are using Coroutines and yield return null to compute physics calculations. Essentially, you're computing physics in Update(), which is only called once per frame (null is equivalent to new WaitForEndOfFrame(): as mentioned in (1), a running Coroutine cannot be yielding between frames). Under low frame-rate, the amount of motion an object undertook between two frames might exceed the collision range of the target trigger. Assuming linear, non-accelerating motion: ∆S = v∆t where v = velocity, ∆S is movement to cover in the current frame, ∆t is Time.deltaTime. As you can see, ∆S scales proportionally with ∆t.
You have GetComponent<T>() calls inside loops. Always avoid doing this: store a reference as a member variable instead (initialise it in Start()).
My suggestion for the quickest working hack would be to not worry too much about "being clean", and instead create subroutines that you call from FixedUpdate(), and (create and) use member bools to conditionally test which subroutine to "execute" and which to "skip". You can also use member bools or enums as triggers to switch between various "states".
A better solution would be to let Unity handle the kinematics and you instead work with rigidbody mutators (and not transform.positions), but that may be totally unnecessary for an arcade situation, which yours might be. In that case stick to the hack above.
If you really want to control kinematics by hand, use an engine like SFML. A Particle System tutorial would be a good place to start.
It's your float percentage, among other things.
"If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore."
That's from the isKinematic page of Unity's documentation. You're setting it to true when progress hits 100. So at lower framerates, there'll be a sudden jump due to Time.deltaTime steps being a lot higher, progress is suddenly >= 100, isKinematic is set to true and the player is no longer affected by collisions.
I think you're going to have to rethink a lot of the code here and do some heavy optimisations. But the other posters have laid those out already, so I don't need to.
EDIT: Misunderstood the initial question, thought that it meant you were trying to detect collisions but your code wasn't always detecting them. Didn't realise it actually meant getting the collisions to occur in the first place.

How to check if my ball stopped rolling?

I want to check when my ball stopped rolling (moving) for more then 5 seconds and then execute some code. I'm working on an android game and sometimes the ball gets stuck, the ball should be moving constantly so I want to check if it's moving and if it's not I need to respawn it.
What is the best way to do this?
Each time Update is called, you could check the magnitude of the object's velocity. If it's lower than a small amount (say 0.1 or something depending on your game's scale), add Time.deltaTime to a member variable, otherwise, set that variable to 0. If that variable is ever over 5, you know your object has been not moving (or close) for 5 seconds. Then execute your code.
You can create a simple counter for count 5 seconds and check the gameObject.getComponent.velocity.magnitude;
And create a if and check this magnitude is smaller than your wanted level
Put this as a C# script component onto your ball gameobject.
public class MyBall : MonoBehaviour{
private Transform _myTransform = null;
private Vector3 _lastPosition = null;
private void Awake(){
_myTransform = transform;
_lastPosition = _myTransform.position;
}
private void Update(){
if(_lastPosition == _myTransform.position){
Debug.Log("Did not move");
}else{
Debug.Log("Moved");
}
_lastPosition = _myTransform.position;
}
}

unity particle doen't play second time

I have a particle system defined in my game objects. When I call Play it plays (it plays for 5 seconds by design). when I call Play again nothing happens. I tried to call Stop and Clear before recalling Play but that didn't help.
Can particle systems play more than once?
My code is in this method, which is called when a button is clicked.
public void PlayEffect()
{
for (int i=0;i<3;i++)
{
NextItemEffectsP[i].Stop();
NextItemEffectsP[i].Clear();
NextItemEffectsP[i].Play();
}
}
NextItemEffectsP is an array that contains particles that I populate in the editor
You should rework how your bullet works. Have some code on the bullet Prefab to control when it gets destroyed.
private float fuse = 1.0;
private float selfDestructTimer;
void Awake() {
// Time.time will give you the current time.
selfDestructTimer = Time.time + fuse;
}
void Update() {
if (selfDestructTimer > 0.0 && selfDestructTimer < Time.time) {
// gameObject refers to the current object
Destroy(gameObject);
}
}
Then with that control set up, you'll always just create new bullets whenever the fire button is pressed.

Categories