I want to recreate the Tanks! game from the Unity tutorial series as multiplayer. I already watched the Network series for that as well. But I have a problem with implementing the shooting right. The tanks can charge up to increase the launch force of the bullet. It works for the host but the client gets stuck in firing.
The code:
[ClientCallback]
private void Update()
{
if (!isLocalPlayer)
return;
if (m_CurrentLaunchForce >= MaxLaunchForce && !m_Fired)
{
m_CurrentLaunchForce = MaxLaunchForce;
Debug.Log("Max force achieved! Firing!");
CmdFire();
}
else if (Input.GetButtonDown("Fire1"))
{
m_Fired = false;
m_CurrentLaunchForce = MinLaunchForce;
Debug.Log("Start charging up!");
}
else if (Input.GetButton("Fire1") && !m_Fired)
{
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
Debug.Log("Charging up!");
}
else if (Input.GetButtonUp("Fire1") && !m_Fired)
{
Debug.Log("Firing with low force!");
CmdFire();
}
}
[Command]
private void CmdFire()
{
m_Fired = true;
Rigidbody shellInstance = Instantiate(ShellPrefab, FireTransform.position, FireTransform.rotation);
shellInstance.velocity = m_CurrentLaunchForce * transform.forward;
m_CurrentLaunchForce = MinLaunchForce;
NetworkServer.Spawn(shellInstance.gameObject);
m_Fired = false;
}
The client which is not the host gets stuck in the second if case :
if (m_CurrentLaunchForce >= MaxLaunchForce && !m_Fired)
I checked the variables in the Debugger, and the currentLaunchForce gets never resetted to minLaunchForce.
I found the solution by myself. I implemented another function "fire()" which is called from the Update function first, which then again calls the command "cmdfire()". In the fire command I reset the variables and in the command I just tell the server to spawn the projectile for alle clients with the force as parameter.
private void fire()
{
m_Fired = true;
CmdFire(m_CurrentLaunchForce);
m_CurrentLaunchForce = MinLaunchForce;
m_Fired = false;
startReloadTime();
}
private void CmdFire(float launchForce)
{
Rigidbody bulletInstance = Instantiate(BulletPrefab, FireTransform.position, FireTransform.rotation);
bulletInstance.velocity = launchForce * transform.forward;
NetworkServer.Spawn(bulletInstance.gameObject);
}
It seems that if you use certain variables to check certain conditions on the client, you also have to reset them on the client himself, like in my case:
private float m_CurrentLaunchForce;
private bool m_Fired;
And then just tell the server to spawn your objects with options like force, position, rotation as function parameters to verify them, depending on how concerned you are about cheating.
Related
Okay, this is probably a dumb question but I'm new to this. I have an enemy AI that walks toward the player only when the enemy is visible to the player and the space key is pressed. I want to make a second if statement that makes the enemy run if the player presses the space bar a second time while the enemy is walking or if the enemy is within 2 meters of the of the players current position.
{
public NavMeshAgent enemy;
public Transform player;
public float speedWalk = 6f;
public float speedRun = 60f;
public float groundDrag;
public float playerHeight;
bool isWalking;
Renderer m_Renderer;
void Move(float speed)
{
enemy.speed = speed;
}
private void Start()
{
m_Renderer = GetComponent<Renderer>();
isWalking = false;
enemy.speed = speedWalk;
}
private void OnBecomeInvisible()
{
enabled = false;
}
//DelayEnemyChase
IEnumerator delayChase()
{
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
}
//Visible by camera
void OnBecameVisible()
{
enabled = true;
//starts walking towards player position
if ((Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible) && (isWalking == false))
{
StartCoroutine(delayChase());
isWalking = true;
}
//starts walking towards player position
else if ((isWalking == true) && (Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible))
{
Move(speedRun);
enemy.SetDestination(player.position);
isWalking = false;
}
}
private void Update()
{
//sees if enemy is visible + space bar is pressed
OnBecameVisible();
}
}
This is confusing the heck out of me, this is what I have and it's not workign at all. Any help is appreciated!!!!
The main issue is that GetKey is fired every frame as long as the button stays pressed!
You rather want to use GetKeyDown in order to track only the first key press.
Then you currently also start and run multiple concurrent Coroutines!
I would rather use a kind of state routine and do e.g.
private void OnBecomeInvisible()
{
StopAllCoroutines();
enemy.enabled = false;
}
private void OnBecameVisible()
{
enemy.enabled = true;
Move(0f);
enemy.SetDestination(enemy.transform.position);
StartCoroutine (StatesRoutine());
}
private IEnumerator StatesRoutine ()
{
// wait until the space is pressed the first time
// here it depends on what exactly you want to do
// you can either already track if the key is still pressed already
yield return new WaitUntil (() => Input.GetKey(KeyCode.Space));
// or rather wait until the key goes down the first time after having become visible
//yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
// Then for the second press we definitely wait until it gets down again instead of
// only checking if the button is still pressed
// except again your use case actually wants that behavior
yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
Move(speedRun);
enemy.SetDestination(player.position);
}
Some things still depend on your exact needs though, in particular what shall happen if the enemy becomes invisible. For now I assume you wanted to reset the behavior and start the process of handling space clicks from scratch.
I am creating a FPS game, my player has abilities and her ultimate is Jett's ultimate from Valorant (Her weapon switches to 5 knives that she can throw at enemies) I've managed to make the weapon switch to the knives but when I press the Mouse Left-Click nothing happens. I disabled the script that controls the gun so that the player does shoot bullets, have muzzle flash etc but whether its enabled or disabled the knives don't work. I added debug logs and they arent being called, I use the same button to shoot the gun as throwing the knife. I have also tested it without the 'readyToThrow' variable and 'totalThrows' variable.
Update Method:
void Update()
{
if (Time.time >= QabilityTimer && Input.GetKeyDown(KeyCode.Q))
{
Dash();
QabilityTimer = Time.time + Qcooldown;
}
if (Time.time >= EabilityTimer && Input.GetKeyDown(KeyCode.E))
{
SpeedBoost();
EabilityTimer = Time.time + Ecooldown;
}
if (meterButton.currentProgress == meterButton.maxProgress)
{
ultReady = true;
// Debug.Log("ult ready");
if (Input.GetKeyDown(KeyCode.X))
{
//disable the gun script whilst ult is active.
GameObject gun = GameObject.Find("Guns");
gun.GetComponent<Guns>().enabled = false;
weaponSelection.EunhaUltKnife();
EunhaUltimate();
}
}
if (ultActive)
{
ultTimer -= Time.deltaTime;
}
if (ultTimer <= 0)
{
ResetUltimate();
}
}
Ultimate Method:
public void EunhaUltimate()
{
ultActive = true;
if (Input.GetKeyDown(KeyCode.Mouse0) && readyToThrow && totalThrows > 0)
{
Debug.Log("working");
readyToThrow = false;
GameObject projectile = Instantiate(objectToThrow, attackPoint.position, cam.rotation);
Rigidbody projectileRB = projectile.GetComponent<Rigidbody>();
Vector3 forceToAdd = cam.transform.forward * throwForce + transform.up * throwUpwardForce;
projectileRB.AddForce(forceToAdd, ForceMode.Impulse);
totalThrows--;
Invoke(nameof(ReserThrow), throwCooldown);
}
}
Looks like you can only get into the EunhaUltimate() if you're holding down X:
if (meterButton.currentProgress == meterButton.maxProgress)
{
ultReady = true;
// Debug.Log("ult ready");
if (Input.GetKeyDown(KeyCode.X))
{
//disable the gun script whilst ult is active.
GameObject gun = GameObject.Find("Guns");
gun.GetComponent<Guns>().enabled = false;
weaponSelection.EunhaUltKnife();
EunhaUltimate();
}
}
Try moving the call to EunhaUltimate() outside of that IF statement. You'll need to have a trigger to show when you've pushed X to trigger the ultimate, but it looks like you're currently doing that inside EunhaUltimate(), with the ultActive = true; line.
Other than setting ultActive = true;, all your EunhaUltimate() seems to do is to check the keyboard press, so I'd recommend rewriting your first snippet as follows:
void Update()
{
if (Time.time >= QabilityTimer && Input.GetKeyDown(KeyCode.Q))
{
Dash();
QabilityTimer = Time.time + Qcooldown;
}
if (Time.time >= EabilityTimer && Input.GetKeyDown(KeyCode.E))
{
SpeedBoost();
EabilityTimer = Time.time + Ecooldown;
}
if (meterButton.currentProgress == meterButton.maxProgress)
{
ultReady = true;
// Debug.Log("ult ready");
if (Input.GetKeyDown(KeyCode.X))
{
//disable the gun script whilst ult is active.
GameObject gun = GameObject.Find("Guns");
gun.GetComponent<Guns>().enabled = false;
weaponSelection.EunhaUltKnife();
ultActive = true; // <--- This is a change
}
}
if (ultActive)
{
EunhaUltimate(); // <-- this is a change
ultTimer -= Time.deltaTime;
}
if (ultTimer <= 0)
{
ResetUltimate();
}
}
Try using Input.GetKey(KeyCode.Mouse0) or Input.GetKeyUp(KeyCode.Mouse0) instead Input.GetKeyDown(KeyCode.Mouse0).
i have a gameobject that goes up if it stays in collision for 5 Secs, the problem is it only work once , i tried calling the OnGUI in the update when ever the ToggleGUI = true but did't work
public float elapsedTime = 0f;
bool ToggleGUI = false;
bool isCreated = false;
Vector3 firstpos;
private void Update()
{
if(ToggleGUI == true)
{
OnGUI();
}
}
void OnTriggerStay(Collider other)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= 5.0f)
{
ToggleGUI = true;
}
}
void OnTriggerExit(Collider other)
{
elapsedTime = 0f;
}
void OnGUI()
{
if (ToggleGUI == true)
{
if (!isCreated)
{
firstpos = transform.position;
firstpos.y += 2f;
transform.position = firstpos;
isCreated = true;
}
}
}
after isCreated is set to true there is no place in your code where that variable is set again to false, therefore it can get inside the condition that moves the gameobject just once for the lifecycle of the script.
FYI when you pass a bool to an if you don't need to write ==true to have it execute when that variable is true, the name of the variable is enough
Your code seems a bit to complex for what you are trying to achieve.
If following your description what you want is just that the object goes up by 2f units every time you have stayed within a collider for 5 seconds I would simply use a Corouinte like e.g.
// The currently running Coroutine
private Coroutine _routine;
private int currentTriggers;
private void OnTriggerEnter(Collider other)
{
currentTriggers++;
// Start a routine if none is running already
if(_routine == null)
{
_routine = StartCorouine (Routine());
}
}
private void OnTriggerExit(Collider other)
{
currentTriggers--;
// If there are still other triggers do nothing
if(currentTriggers > 0) return;
// Cancel the routine if one is running
if(_routine != null)
{
StopCoroutine(_routine);
_routine = null;
}
}
IEnumerator Routine()
{
// As the name says wait 5 seconds
yield return new WaitForSeconds (5f);
// This is only reached if the routine wasn't stopped before the time passed
// Then move your object up
transform.position += Vector3.up * 2f;
_routine = null;
}
In general be very careful with your method names!
OnGUI is a special built-in event message that is called for handling events such as Keyboard or mouse input events, Repaint calls from the UI, etc. It might get called multiple times within a single frame and is absolutely not what you want to do here and for sure you should not call this method "manually" from Update.
Using c#, im trying to fire a bullet every 3 seconds, so heres my workflow:
fire button is pressed
only fire if bool fireAgain is true
set bool fireAgain = false
start timer
timer finished = bool fireAgain = true
When debugging it seems to all work properly, but when I test it, Im still able to shoot like 10 bullets a second. So somehow it just doesnt care about the bool FireAgain being false and shoots anyway even if according to debug bool fireAgain is false at that moment.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain == true && Input.GetMouseButtonDown(0))
{
StartCoroutine("LoopRotation");
fireAgain = false;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
while (fireAgain == false && timer < bulletTime)
{
fireAgain = false;
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
} if (timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
And here is the code for the Coroutine:
IEnumerator LoopRotation()
{
pivot.transform.Rotate(triggerAngle,0,0);
GameObject bullet = ObjectPooler.SharedInstance.GetPooledObject();
if (bullet != null) {
bullet.transform.position = SSpawn.transform.position;
bullet.transform.rotation = SSpawn.transform.rotation;
bullet.SetActive(true);
}
yield return new WaitForSeconds(.1f);
pivot.transform.rotation = Quaternion.Slerp(transform.rotation, originalRotationValue, Time.deltaTime * rotationResetSpeed);
StopCoroutine("LoopRotation");
}
Enumerator LoopRotation was originally just to pivot the weapon a few degrees forwards and then backwards so it looks like a wack when you cast a spell, but now its also the shoot function, as it creates bullets.
You have a while loop within Update => this loop will completely run in one single frame => "immediately" will increase the timer until it is big enough => "immediately" will set your bool flag to true again!
What you rather would do is e.g.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
timer = 0;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
else if(timer < bulletTime)
{
// Only increase this ONCE per frame
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
if(timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
}
}
}
As alternative you could use Invoke and skip the timer in Update completely:
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
Invoke (nameof(AllowFireAgain), bulletTme);
}
}
private void AllowFireAgain()
{
fireAgain = true;
}
Note that your Coroutine doesn't make much sense to me. You are only rotating exactly once and only a really small amount.
The StopCoroutine in the end is unnecessary.
Also note: for debugging fine but later you should avoid to have Debug.Log running every frame in a built application. Even though the user doesn't see it the log is still created into the player log file and causes quite an amount of overhead.
I have a 2D shooting game on Unity using C# now and I want to increase the fire rate of the ship for 5 seconds when it gets a power up item. It kinda works but when the ship gets the power up, the fire rate does not change until the button is released and pressed again. Is there a way to change the fire rate as soon as it gets the power up even while the button is being pressed? Also, the power up function is something that I came up with and if there is a better way to make the power up functions, that will be very helpful too. Thanks in advance :)
void Update(){
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireRate);
}
}
void PowerUp()
{
Upgrade = true;
timeLeft = +5f;
if (Upgrade == true)
{
fireRate = 0.1f;
}
if (timeLeft <= 0)
{
Upgrade = false;
fireRate = 0.5f;
}
}
You should pass an reference type to the Fire coroutine instead of a float fireRate.
Just wrap fireRate in a class should work:
class FireData
{
public float fireRate = 0.1;
}
Then in your script,
FireData fireData = new FireData { fireRate = 0.5f };
void Update(){
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireData);
}
}
void PowerUp()
{
Upgrade = true;
timeLeft = +5f;
if (Upgrade == true)
{
fireData.fireRate = 0.1f;
}
if (timeLeft <= 0)
{
Upgrade = false;
fireData.fireRate = 0.5f;
}
}
In Fire() coroutine, use this fireData.fireRate to get the fireRate.
By the way, I think your power up functions is good enough.
But the way of using coroutine is not correct.Don't call InvokeRepeating on the same function multiple times.
if (Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire", 0.000001f, fireRate);
}
Instead, you should use a bool value to control when the fire will start.
if (Input.GetKeyDown(KeyCode.Space)){
fireData.Firing = true;
}
if(fireData.Firing)
{
InvokeRepeating("Fire", 0.000001f, fireRate);
}
Also remember to add a logic to stop firing via StopCoroutine.