I am developing a FPS in Unity.
How can I prevent the player from picking up recovery items when the player's HP is 100 (full)?
Currently, players can pick up recovery items even when their HP is full, and I want to limit the conditions for getting them.
The first thing I can think of is to temporarily disable the collision on the recovery item side, when the player's HP is 100.
I want to use collider.enabled = false; in an if statement, how do I write it?
Or am I doing it wrong?
Please surpport me.
HPItem.cs (This is a HPItem)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HPItem : MonoBehaviour
{
public FPSController fPSController;
public int reward = 100;
public AudioClip getSound;
<summary>
</summary>
<param name="collision"></param>
[SerializeField]
public Vector3 speed;
void Start()
{
fPSController = GameObject.Find("Player").GetComponent<FPSController>();
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
AudioSource.PlayClipAtPoint(getSound,transform.position);
Destroy(gameObject, getSound.length);
fPSController.AddHP(reward);
}
}
}
FPSController.cs(This is a Player)
int playerHP = 100, maxPlayerHP = 100;
public Slider hpBer;
public void AddHP(int amount)
{
playerHP += amount;
if (playerHP > 100)
{
playerHP = 100;
}
}
It would not be better to check the onTriggerEnter function, once you know that it is the player you check the value of life, if it is 100 you do not collect it
Check the health of the player when the collision happens, only collect if health is not full.
Not a perfect solution (i.e. if player loses health while standing on the health item, they won't pick it up), but you could use OnTriggerStay() for that.
FPSController.cs:
// Add public getter for heath
public int Health => playerHP;
HPItem.cs:
...
void OnTriggerEnter(Collider other)
{
// Add check for health to this if statement.
// && statements are evaluated left to right, so
// no danger of NullReferenceException
if (other.gameObject.tag == "Player" &&
other.GetComponent<FPSController>().Health != 100)
{
AudioSource.PlayClipAtPoint(getSound,transform.position);
Destroy(gameObject, getSound.length);
fPSController.AddHP(reward);
}
}
...
Related
im making an enemy wave - based game and it works fine, but i want to make it so before it continues on to the next wave, the player has to press a key to let the game know they're ready, the reason i want to do this is because i want to implement a shop to purchase upgrades after waves, here is my wave script:
SpawnManager.cs
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's
// position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position,
SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
StartNextWave();
}
}
}
Set a Boolean like waitForKey=true, then in something like Update:
if( waitForKey && Input.GetKeyDown("space") ) {
waitForKey=false;
StartNextWave();
}
Listen to a button input in the Update function.
void Update()
{
if (_enemiesInWaveLeft <= 0 && InputGetKeyDown(KeyCode.Space))
{
StartNewWave();
}
}
This will cause the wave to spawn when there are no enemies left and Space is pressed down. You will need to add some UI separately to this to give the player some feedback as to when it is Ok to press the Space Button.
Also remove the StartNextWave from your EnemyDefeated funciton. That can be used to enable and disable UI for the player to let them know to press a button to continue.
I am building a little platformer, where the player can collect coins. Since there are sometimes 100+ spinning coins in one scene, I decided to render only coins that are in within a certain proximity to the player.
This worked before - but on a phone the levels with many coins tend to have some lags. With the coins the removed, my FPS was at straight 60. So this is what I did:
public class AddCoinScript : MonoBehaviour
{
public static int coinCounter = 0; // static cause we will only want ONE counter and not many instances of the coin value
private Text coinText;
private float disBetweenBirdyAndEnemy;
private GameObject birdy;
private Renderer rndr;
private BoxCollider2D bx2D;
private bool canAnimate = false;
private float startAnimDistance = 20;
private bool coinCollected = false;
void Start()
{
coinText = GameObject.Find("Coin Text").GetComponent<Text>(); // Get the Coin Text Element
coinText.text = "x" + coinCounter.ToString();
birdy = GameObject.Find("Birdy");
rndr = GetComponent<Renderer>();
bx2D = GetComponent<BoxCollider2D>();
rndr.enabled = true;
bx2D.enabled = true;
}
void FixedUpdate()
{
disBetweenBirdyAndEnemy = Vector3.Distance(birdy.transform.position, this.transform.position);
if (disBetweenBirdyAndEnemy <= startAnimDistance)
{
if(!coinCollected)
{
rndr.enabled = true;
bx2D.enabled = true;
}
}
else
{
rndr.enabled = false;
bx2D.enabled = false;
}
coinText.text = "x" + coinCounter.ToString();
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.name == "Birdy") // detect collision with ground game object
{
AddCoinAndRemoveHitBoxAndSprite();
}
}
private void AddCoinAndRemoveHitBoxAndSprite()
{
coinCounter++;
coinCollected = true;
rndr.enabled = false;
bx2D.enabled = false;
PlayCoinSound();
}
private void PlayCoinSound()
{
AudioSource aS = GameObject.Find("GetCoinSound").GetComponent<AudioSource>();
aS.Play();
}
}
The results are good. The coins render in the scene only when the player is close enough to them. However, once deployed onto my phone - the coins don't render at all any more. As if the players distance towards the coins is different on my phone than it is on my pc.
What am I missing?
I can't see anything obviously wrong with this, but if it is a distance check gone wrong, you could try disabling the entire coin and enable them only if they're on the screen.
A solution which would work with all resolutions is something along the lines of this:
private bool IsOnScreen(Vector3 worldPosition)
{
Vector3 screenPoint = camera.WorldToViewportPoint(worldPosition);
return screenPoint.x > 0
&& screenPoint.x < 1
&& screenPoint.y > 0
&& screenPoint.y < 1;
}
A separate problem that could be solved is that your coin script is responsible for far too many things. Right now every coin
Knows how many coins the player has and can modify that number
Knows about and is modifying the UI text for coins on start
Is responsible for enabling/disabling its own components, when you could just destroy the gameObject when it's collected
Finds an audio gameObject and playing a sound
Because of all these responsibilities you'll have a harder time solving issues within this class. Consider having some sort of CoinManager class which can keep track of how many coins the player has (instead of every single coin knowing about the coin count).
Your class might look something like this
public class CoinManager : MonoBehaviour
{
private Camera camera;
[SerializeField] private AudioClip coinCollectedSound;
[SerializeField] private AudioSource audioSource;
[SerializeField] private List<CoinScript> coins;
[SerializeField] private int coinCount;
[SerializeField] private Text coinLabel;
// This could be called by some spawner class that
// adds coins to the scene
public void AddCoin(CoinScript coin)
{
coins.Add(coin);
}
public void CoinCollected(CoinScript coin)
{
Destroy(coin.gameObject);
coinCount++;
audioSource.PlayOneShot(coinCollectedSound);
// Alternatively, if you have a dedicated audio source
// for coin sounds you can just use audioSource.Play();
}
private void Update()
{
// Iterate over all the coins we know about
foreach (var coin in coins)
{
// Now that we have a method that tells us whether a
// coin is on the screen or not, we can enable and disable
// all the coins at the same time in one place
coin.transform.enabled = IsOnScreen(coin.transform.position);
}
}
private void Awake()
{
// Gets the main camera in the scene
// https://docs.unity3d.com/ScriptReference/Camera-main.html
camera = Camera.main;
coins = new List<CoinScript>();
}
private bool IsOnScreen(Vector3 worldPosition)
{
Vector3 screenPoint = camera.WorldToViewportPoint(worldPosition);
return screenPoint.x > 0
&& screenPoint.x < 1
&& screenPoint.y > 0
&& screenPoint.y < 1;
}
}
I don't have Unity in front of me so the above is off the top of my head.
Hello guys i work on some reflex tapping game in unity and i need script for every 2 or more prefab destroy to add speed on my player and i need that constatly adding speed for 2 prefbas. Can someone help me whit that i do like this:
void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Sphere"))
{
count = count + 1;
if(count >= highScore)
{
highScore = count;
PlayerPrefs.SetInt("highscore", highScore);
PlayerPrefs.Save();
}
SetCount();
if(count == 2)
{
rb.AddForce(0, 0, 50 * forwardForce * Time.deltaTime);
}
}
I would recommend making Speed a property of your Player class:
public class Player : MonoBehaviour
{
public float Speed;
}
You would need to make sure to multiply by this property when you move the player (I'm assuming your code is similar to this):
rigidbodyReference.AddForce(0, 0, 50 * Speed * Time.deltaTime);
Then, I would make a static GameManager class that will keep track of the number of prefabs:
public static class GameManager : MonoBehaviour
{
public static PrefabCount;
}
Finally, I would add a speedIncrement variable (exposed to the inspector) as a property of your Player class and modify your OnTriggerEnter method:
public float speedIncrement;
// ...
void OnTriggerEnter(Collider other)
{
// This will reduce unnecessary nesting in your code to make it easier to read
if (!other.gameObject.CompareTag("Sphere"))
return;
// Same thing as GameManager.PrefabCount = GameManager.PrefabCount + 1
GameManager.PrefabCount++;
if (GameManager.PrefabCount >= highScore)
{
highScore = GameManager.PrefabCount;
PlayerPrefs.SetInt("highscore", highScore);
PlayerPrefs.Save();
}
// Use the Modulus operator to determine if the PrefabCount is evenly divisible by 2
if (GameManager.PrefabCount % 2 == 0)
Speed += speedIncrement; // Increase speed by whatever value set in the inspector
}
The part with touching the object and playing the animation is working fine now i want to add the walls script part.
In this case i change a cube height.
What i need to do is that only when the player touch an object it will raise/change the height of another object.
Son in the first case i find when the player is touching an object:
using UnityEngine;
using System.Collections;
using System.Reflection;
public class DetectPlayer : MonoBehaviour {
GameObject target;
int counter = 0;
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "ThirdPersonController") // "Platform"
{
Debug.Log("Touching Platform");
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "ThirdPersonController") // "OnTop Detector"
{
counter = 0;
Debug.Log("On Top of Platform");
target = GameObject.Find("Elevator");
GameObject findGo = GameObject.Find("ThirdPersonController");
GameObject findGo1 = GameObject.Find("Elevator");
findGo.transform.parent = findGo1.transform;
GameObject go = GameObject.Find("CubeToRaise");
go.GetComponent<RaiseWalls>();
Debug.Log("The button clicked, raising the wall");
StartCoroutine(playAnim(target));
}
}
void OnTriggerExit(Collider other)
{
GameObject findGo = GameObject.Find("ThirdPersonController");
findGo.transform.parent = null;
}
IEnumerator playAnim(GameObject target)
{
Animation anim = target.GetComponent<Animation>();
foreach (AnimationState clip in anim)
{
// do initialisation or something on clip
clip.speed = 1;
}
while (true)
{
if (counter == 1)
break;
anim.Play("Up");
while (anim.IsPlaying("Up"))
{
yield return null;
}
anim.Play("Down");
while (anim.IsPlaying("Down"))
{
yield return null;
}
yield return null;
counter++;
}
}
void OnGUI()
{
GUI.Box(new Rect(300, 300, 200, 20),
"Times lift moved up and down " + counter);
}
}
At this part i'm calling the second script RaiseWalls:
using UnityEngine;
using System.Collections;
public class RaiseWalls : MonoBehaviour
{
public GameObject gameObjectToRaise;
public float speed;
// Use this for initialization
void Start()
{
speed = 2;
}
void Update()
{
gameObjectToRaise.transform.localScale += new Vector3(0, 50, 0);
}
}
GameObject go = GameObject.Find("CubeToRaise");
go.GetComponent<RaiseWalls>();
Debug.Log("The button clicked, raising the wall");
Now the DetectPlayer is attached on one game object.
The RaiseWalls script is attached on another game object.
On the RaiseWalls script i want to set the speed of the object height change. Now it's changing the height by 50 but many times. I want it to change it by 50 but in slow motion like a slowly building/raising wall. Like electronic fence that raise from bottom to top effect.
Second problem i want that first it will raise the wall or walls once it finished raising the walls move to the next part in the DetectPlayer script:
StartCoroutine(playAnim(target));
Steps:
When the player is touching the object in DetectPlayer script raise the wall/s in RaiseWalls script in specific speed.
When the walls raised only then make the StartCoroutine.
So, you want to raise the wall 50 in total, but with a controllable speed.
First make a counter in RaiseWalls:
float raiseAmount;
Also record the total to raise it, which makes it easier to change later:
float raiseTotal = 50;
Then, in your Update, raise it by a little, but record how much was raised
if(raiseAmount < raiseTotal ) // i.e. we haven't raised it fully
{
// work out how much to raise it
float raiseThisFrame = speed * Time.DeltaTime; // to account for frame rate
// now we cap it to make sure it doesn't go over 50
if(raiseAmount + raiseThisFrame > raiseTotal )
{
raiseThisFrame = raiseTotal - raiseAmount;
}
// add raiseThisFrame to raiseAmount
raiseAmount += raiseThisFrame;
gameObjectToRaise.transform.localScale += new Vector3(0, raiseThisFrame, 0);
}
This is unity 2d angry bird style game example. I working on resetter codes. Resetter code below (I changed some parts). I using ball on this project instead of birds. So, I want to if ball is stoped after to throw load new ball to catapult. But It doesn't work correctly. First of all. I wrote if ball count == 0 clone the object. But its alwaws clonning when ball was stoped. On the other hand I cant use cloned object for throwing. Cloned object not on the catapult. Sorry for my bad english but this problem my last
phase on this game.
for example
https://www.dropbox.com/s/h3gwinw8chyeh59/error.png?dl=0
This is game tutorial link.
http://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/making-angry-birds-style-game
using UnityEngine;
using System.Collections;
public class Resetter : MonoBehaviour {
public Rigidbody2D projectile; // The rigidbody of the projectile
public float resetSpeed = 0.025f; // The angular velocity threshold of the projectile, below which our game will reset
private float resetSpeedSqr; // The square value of Reset Speed, for efficient calculation
private SpringJoint2D spring; // The SpringJoint2D component which is destroyed when the projectile is launched
public int BallCount; // I using this value for I need new ball or not
public Rigidbody2D projectileSD; // Ball
void Start ()
{
BallCount = 1;
// Calculate the Resset Speed Squared from the Reset Speed
resetSpeedSqr = resetSpeed * resetSpeed;
// Get the SpringJoint2D component through our reference to the GameObject's Rigidbody
spring = projectile.GetComponent <SpringJoint2D>();
}
void Update () {
// If the spring had been destroyed (indicating we have launched the projectile) and our projectile's velocity is below the threshold...
if (spring == null && projectile.velocity.sqrMagnitude < resetSpeedSqr) {
// ... call the Reset() function
// Reset ();
if (BallCount == 0 );
{
ObjeyiKlonla();
}
}
}
void ObjeyiKlonla() { // Clone object
Rigidbody2D clone;
clone = Instantiate (projectileSD, transform.position, transform.rotation) as Rigidbody2D;
clone.velocity = transform.TransformDirection (Vector3.forward * 1);
BallCount ++;
}
void OnTriggerExit2D (Collider2D other) {
// If the projectile leaves the Collider2D boundary...
if (other.rigidbody2D == projectile) {
// ... call the Reset() function
//Reset ();
}
}
void Reset () {
// The reset function will Reset the game by reloading the same level
//Application.LoadLevel (Application.loadedLevel);
BallCount =0;
}
}
Try this.
void Update(){
if(projectile.GetComponent <SpringJoint2D>() && projectile.velocity.sqrMagnitude < resetSpeedSqr)
{
if (BallCount <= 0 );
{
ObjeyiKlonla();
}
}
}