I'm working on a project in the Unity engine and I've come across an issue that I cannot understand. I have an ammo counter that decreases the amount of ammo available by 1, each time the player clicks the left mouse button. Instead of doing this though, it decreases by half (rounded down). I'm not entirely sure why this happens as the only decremental value is " ammoCount -= 1; " My code for the script is below:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class ShootGun : MonoBehaviour {
public Text Ammo;
public int ammoCount = 30;
void FixedUpdate ()
{
if(Input.GetButtonDown("Fire1"))
{
for(int i = 1; i <= ammoCount; i++)
{
ammoCount -= 1;
Debug.Log ("Ammo remaining: " + ammoCount);
Ammo.text = " " + ammoCount;
Vector3 gunRayOrigin = transform.position;
float gunRayDistance = 50f;
Ray gunRay = new Ray ();
gunRay.origin = gunRayOrigin;
gunRay.direction = Vector3.down;
if(Physics.Raycast(gunRayOrigin, gunRay.direction, gunRayDistance)) {
Debug.Log("Bullet Hit");
}
}
}
}
}
Any help would be greatly appreciated, as I'm still getting the hang of C#.
You are decrementing it inside a for loop.
If we simplify down to this:
for(int i = 1; i <= ammoCount; i++)
{
ammoCount -= 1;
}
and take an initial ammoCount of 6, the first time you enter the loop you have:
i = 1, ammoCount = 6
But then you subtract one from ammoCount, and the loop adds one to i. So the next loop you have
i = 2; ammoCount = 5
But then you subtract one from ammoCount, and the loop adds one to i. So the next loop you have
i = 3; ammoCount = 4
But then you subtract one from ammoCount, and the loop adds one to i. So the next loop you have
i = 4; ammoCount = 3
And the loop completes (because now i <= ammoCount is no longer true). And ammoCount is half what it was.
It looks like Input.GetButtonDown("Fire1") only returns true when the button has been pressed and released, so this is a "push the button and fire all ammo" scenario, rather than "fire until out of ammo or the key is released".
So you are wanting it to keep firing while there is any ammo left (hint hint).
You're decrementing the counter in a for loop. By decreasing the loop's upper bound and increasing the iteration variable i you basically halve the upper bound (ammoCount).
In this case, I see no reason why a loop would be necessary.
FixedUpdate is an event that happens in a fixed interval and should be used mainly for scenarios when you're dealing with physics. I don't think this is the case (some may argue that you're using raycast, but I'm not sure personally if that counts). You want to react on the users button press, so in my opinion you should use Update event instead.
GetButtonDown() returns true if the user pressed and released the button. What you should be doing instead is to use GetButton() to know if the button is held down. If yes, keep firing, if not, do nothing. Otherwise you have to press the button repeatedly, which again is probably not the desired behavior.
Let's now look at what happens when you press the button. Every time the user does the action, you start to decrease the ammo count until it reaches zero. Not sure what kind of game you're creating, but that doesn't sound right. What I'd expect instead would be to decrease the count while the user keeps the button pressed. In the Update event just check if ammo count is higher than zero and if yes, do the raycast check if it hit.
It would look like this:
void Update()
{
if(Input.GetButton("Fire1"))
{
if (ammoCount > 0)
{
ammoCount--;
// do raycast and so on
}
}
}
Related
Using yield return new WaitForSeconds(waitTime);
within an IEnumerator, what is the shortest wait time other than 0? I have tried using a float number and have tried as low as 0.00001f for the waitTime, however i'm not sure if there is a limit or not?
The purpose is that I am having a player's coins added one unit at a time, so the meter is 'filling up' rather than instant. I have tried searching but I cannot seem to find the answer to what the shortest limit is for WaitForSeconds, so if anyone knows I'd greatly appreciate the answer.
Additionally, my code is as follows if anyone has any input on how I can speed up the process without making it instant, as it's just not quite quick enough and the player is having to sit for a while waiting for the coins to be added to the meter at the end of the game.
IEnumerator AddCoins()
{
yield return new WaitForSeconds(1);
while (userGainedCoins > 0)
{
if (addingSoundPlaying == false)
{
addingSound.Play();
addingSoundPlaying = true;
}
if (userGainedCoins == 1)
{
addingSound.Stop();
}
userCoins += 1;
userGainedCoins -= 1;
PlayerPrefs.SetInt("User Coins", userCoins);
yield return new WaitForSeconds(waitTime);
}
addingSoundPlaying = false;
}
I think it doesn't have a minimum time, it just waits until it reaches a certain amount before waiting. If you want, you can just add multiple coins at a time. I usually try not to mess with these kinds of things, and instead of messing with the coins, I would just mess with the value it displays. You could have a script that holds the number of coins and increases/decreases it based on the timestep. Something like this:
public int coins = 100;
public int displayedCoins = 0;
private void Update() {
int change = Mathf.CeilToInt(300 * Time.deltaTime);
if (displayedCoins < coins)
displayedCoins = Math.Min(displayedCoins + change, coins);
else
displayedCoins = Math.Max(displayedCoins - change, coins);
}
and with this you can just set the amount and forget about it, and it works with gaining and losing coins. You can also change the change value to something like int change = Mathf.CeilToInt(Mathf.Abs(coins - displayCoins) * Time.deltaTime) to make it have an ease-out effect, or whatever you want. (I didn't test this code.)
I have two cheesbar in the game... once finished the other appears...and there is "eat button". when a mouse approaches to chees that button is being activated and make continueeating bool active. And I press and hold the button , cheese is being eaten and when cheese finished , second game object appears which means second cheese bar, somewhere, but start as a half cheesebar ...so even if it has SetActive(false); in the beginning , it is decreasing as if it was activated... how can I correct this.
thanks in advance.
void Start ()
{
eatingcheese = maxcheese;
eatingcheese2 = maxcheese2;
}
void Update ()
{
cheesebar.fillAmount = eatingcheese / maxcheese;
cheesebar2.fillAmount = eatingcheese2 / maxcheese2;
if (continueeating)
{
eatingcheese--;
eatingcheese2--;
hungerscript.gettinghungery+=1.5f;
}
if (eatingcheese <=0)
{
cheesebar.gameObject.SetActive(false);
cheesebar2.gameObject.SetActive(true);
}
}
public void eating()
{
continueeating = true;
}
public void stopeating()
{
continueeating = false;
}
I notice that in the code
if (continueeating)
{
eatingcheese--;
eatingcheese2--;
hungerscript.gettinghungery+=1.5f;
}
You decrement eatingcheese2 whether or not cheesebar2 is active. You could check to see if the cheese is ready to be eaten and only proceed with decrementing eatingcheese2 if it is. You could use an integer to track which cheese you are currently eating to do that, and a switch statement to decrement either value based on that state. Or if you are sure you'll only ever have two cheeses, you can use a bool and an if statement.
Note that Update() is framerate dependent and you are dealing with eatingcheese numbers that will be very large (since they decrement every update while being eaten). I would suggest using a float from 1 to 0 representing whole and fully eaten cheese, and I would also recommend multiplying the amount eaten each step by Time.deltaTime so avoid the issue of scaling too fast and unpredictably.
I've been raking my brain for the last day on how to calculate a full rotation with how to count a full rotation of an object along the X axis thats using the base circular drive from SteamVR.
I thought a simple 3d cube, with the mesh turned off in the the path of the rotation with collision code on it would be a barebone way of doing it, but it doesn't even seem to be registering the detection when the object hits the placed cubes, and i know its not because of me being stupid, as its recycled code from a working part of the project.
Below i have a small piece of code that basically detects when the object has reached the end of the rotation, and then increments the Count by one.
My main problem is that sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by. Im wondering how i can stop it and increment only by one, until another full rotation has been made?
EDIT: to be more clear in case there is any confusion, Once the angle is clocked in between 359 and 360, i want it to increment once, whereas currently if you get the angle to sit anywhere in between 359-360 it will carry on adding one to the rotation count, despite no full rotation having been made, so im trying to figure out how to make my code only increment once, and once it does increment once it resets the position to zero, so therefore no more Increments can happen. It's a crank mechanism in VR, along the X axis.
Any help is appreciated, Thanks!
float Test;
float RotationCount = 0;
// Start is called before the first frame update
void Start()
{
// Test = transform.localRotation.eulerAngles.x;
}
// Update is called once per frame
void Update()
{
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
else
{
// Debug.Log("Nope");
}
if (Test == 0)
{
Debug.Log("Yes");
}
Test = transform.localRotation.eulerAngles.x;
}
void count()
{
RotationCount++;
}
sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by.
well in
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
what happens if your angle is e.g. 359.5?
It is very difficult to just take a current rottaion and know whether it was turned more or less than a certain angle.
I'ld rather store the last rotation, compare it to the current one and add the difference to a variable. Than if the variable exceeds 360 a full rotation was done. Since I also don't like to calculate anything with Quaternion and eulerAngles, way simplier is to use the methods provided by Vector3.
For the local rotation around X (== transform.right) I would use the angle between the current and the last transfrm.up vector.
Something like
public class RotationCheck : MonoBehaviour
{
public int RotationCount;
public float rotatedAroundX;
public Vector3 lastUp;
public UnityEvent On3TimesRotated;
private void Awake()
{
rotatedAroundX = 0;
// initialize
lastUp = transform.up;
}
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
if (rotatedAroundX >= 360.0f)
{
Debug.Log("One positive rotation done", this);
RotationCount++;
rotatedAroundX -= 360.0f;
}
else if (rotatedAroundX <= -360.0f)
{
Debug.Log("One negative rotation done", this);
RotationCount--;
rotatedAroundX += 360.0f;
}
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
}
}
}
You can use a UnityEvent to get the same thing the Button uses for onClick so you can reference callbacks there via the inspector.
BUT if you don't care about the single rotations but actually only wnat the final RotationCount >= 3 I would actually use
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
RotationCount = Mathf.RoundToInt(rotatedAroundX / 360.0f);
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
rotatedAroundX = 0;
}
}
which directly reduces the value by one if rotated under the 360 mark instead of waiting for a full negative rotation
*as you can see instead of reaching 3 the RotationCount is reset to 0. This is where the On3TimesRotated event is/would get fired.
So when my character gets hit by the enemies fire breath, I want to create the feel of the character being set on fire. So while the character is on fire I want him to lose a specific amount of health for a specific amount of time.
For example; lets say he is on fire for 3 seconds and I want to make him lose 30 health for being on fire, how would I evenly distribute losing 30 health for 3 seconds? I dont want the 30 damage to be applied instantly to the health, I want it to slowly tick away at the players health so that at the 3 second mark 30 damage has been dealt.
The game is being made with c#.
Thanks.
This is just like moving Gameobject over time or doing something over time. The only difference is that you have to use Mathf.Lerp instead of Vector3.Lerp. You also need to calculate the end value by subtracting the value you want to lose over time from the current value of the player's life. You pass this into the b or second parameter of the Mathf.Lerp function.
bool isRunning = false;
IEnumerator loseLifeOvertime(float currentLife, float lifeToLose, float duration)
{
//Make sure there is only one instance of this function running
if (isRunning)
{
yield break; ///exit if this is still running
}
isRunning = true;
float counter = 0;
//Get the current life of the player
float startLife = currentLife;
//Calculate how much to lose
float endLife = currentLife - lifeToLose;
//Stores the new player life
float newPlayerLife = currentLife;
while (counter < duration)
{
counter += Time.deltaTime;
newPlayerLife = Mathf.Lerp(startLife, endLife, counter / duration);
Debug.Log("Current Life: " + newPlayerLife);
yield return null;
}
//The latest life is stored in newPlayerLife variable
//yourLife = newPlayerLife; //????
isRunning = false;
}
Usage:
Let's say that player's life is 50 and we want to remove 2 from it within 3 seconds. The new player's life should be 48 after 3 seconds.
StartCoroutine(loseLifeOvertime(50, 2, 3));
Note that the player's life is stored in the newPlayerLife variable. At the end of the coroutine function, you will have to manually assign your player's life with the value from the newPlayerLife variable.
I suppose, what you are looking for is a Coroutine. Check out here and here for the documentation. It will allow you to do your custom health reducing actions separately from update function. Using coroutines you can make something happening by ticks, and you can determine how much time the tick is.
You could use couroutines. Something like this:
void OnHitByFire()
{
StartCoroutine(DoFireDamage(5f, 4, 10f));
}
IEnumerator DoFireDamage(float damageDuration, int damageCount, float damageAmount)
{
int currentCount = 0;
while (currentCount < damageCount)
{
HP -= damageAmount;
yield return new WaitForSeconds(damageDuration);
currentCount++;
}
}
So this is what I ended up doing. It causes the character on fire to lose 30 health and you can see the health ticking down instead of it happening over intervals.
IEnumerator OnFire()
{
bool burning = true;
float timer = 0;
while (burning)
{
yield return new WaitForSeconds(0.1f);
hp -= 1;
timer += 0.1f;
if (timer >= 3)
{
burning = false;
}
}
}
Here's some background. I'm working on game similar to "Collapse." Blocks fill up at the bottom and when all twelve blocks have been filled they push up on to the playfield. I have a counter called (intNextSpawn) that not only tells when to "push up" the next row, as well as calculating vectors for the graphics. It resets to 0 when the blocks have been pushed up.
I've added some debug text on the screen to try and see what is happening, but I can't seem to hunt down the issue. It almost seems like it is still incrementing the counter while trying to randomize the the block that's supposed to appear (things acting out of order). I end up getting "blank" blocks and it causes some really screwy effects while testing. It gets worse when jack up the speed.
I'm willing to post any additional code that might help. Below are the two main blocks where this is could happening. Is there something I might be doing wrong or may there be a way I can prevent this from happening (if that's what it's doing)?
Any help would be greatly appreciated.
Edit... The first code block is in the "Update" method
// Calculate time between spawning bricks
float spawnTick = fltSpawnSpeed * fltSpawnSpeedModifier;
fltSpawn += elapsed;
if (fltSpawn > spawnTick)
{
// Fetch a new random block.
poNextLayer[intNextSpawn] = RandomSpawn();
// Increment counter
intNextSpawn++;
// Max index reached
if (intNextSpawn == 12)
{
// Push the line up. Returns true if lines go over the top.
if (PushLine())
{
gmStateNew = GameState.GameOver;
gmStateOld = GameState.Playing;
}
// Game still in play.
else
{
// Reset spawn row to empty bricks.
for (int i = 0; i < 12; i++)
poNextLayer[i] = new PlayObject(ObjectType.Brick, PlayColor.Neutral, Vector2.Zero);
intNextSpawn = 0; // Reset spawn counter.
intLines--; // One less line to go...
}
}
fltSpawn -= spawnTick;
}
private bool PushLine()
{
// Go through the playfield top down.
for (int y = 14; y >= 0; y--)
{
// and left to right
for (int x = 0; x < 12; x++)
{
// Top row contains an active block (GameOver)
if ((y == 14) && (poPlayField[x, y].Active))
// Stop here
return true;
else
{
// Not bottom row
if (y > 0)
{
// Copy from block below
poPlayField[x, y] = poPlayField[x, y - 1];
// Move drawing position up 32px
poPlayField[x, y].MoveUp();
}
// Bottom row
else
{
// Copy from spawning row
poPlayField[x, y] = poNextLayer[x];
// Move drawing position up 32px (plus 4 more)
poPlayField[x, y].MoveUp(4);
// Make the block active (clickable)
poPlayField[x, y].Active = true;
}
}
}
}
// Game still in play.
return false;
}
So, the largest part of your problem is that you are decrementing some timers based on the size of a tick, and then running some comparisons. Wrather than do all that for timers (especially with the rounding and precision loss of float, etc...) instead just to time-basic comparisons.
Example, you do fltSpawn += elapsed and later fltSpawn -= spawnTick which will lead to floating point rounding / precission errors.
Try something more like this:
int64 spawnDelay = 1000000; // 10,000 ticks per ms, so this is 100ms
...
if (DateTime.Now() > nextSpawn)
{
// Fetch a new random block.
poNextLayer[intNextSpawn] = RandomSpawn();
...
// At some point set the next spawn time
nextSpawn += new TimeSpan(spawnDelay * spawnSpeedModifier);
}