(Unity2D) Increasing and Decreasing Stat Bar Issues - unity3d

I have several stat bars, attached to objects, that shorten in length over time. If the PC collides with the object, the stat bar should steadily increase in length instead.
Instead, the bar lengthens to its fullest extent, and doesn't begin to shorten again until the PC interacts with another stat bar object; at which point the bar goes back to the length it had been previously.
This is the code for the progression of the bars:
void Update() {
if (contact) {
Increase();
Debug.Log("increasing");
} else if (contact == false) {
Decrease();
Debug.Log("decreasing");
}
}
void Decrease() {
if (filled > 0) {
filled -= 0.006f;
timerBar.fillAmount = filled / maxTime;
}
}
void Increase() {
if (filled < maxTime) {
timerBar.fillAmount = (filled += 0.006f);
}
}
In a separate script, I am keeping the conditions for the definitions of "contact".
void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
barProg.contact = true;
Debug.Log("is touching");
}
}
void OnCollisionExit2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
barProg.contact = false;
Debug.Log("is not touching");
}
}
Here are the images regarding the script attachments and colliders:
"yellowBar (progBar)" etc. are referring to the scripts attached to the canvas images of the statbars. So the little yellow depleting line (a statbar) is "yellowBar", and it references the script attached to the individual stat bar (progBar).
Shown below is an example of what I'm talking about. I refill the blue bar, at which point it stays the same until I go to refill the yellow bar, at which point the blue bar goes back to the amount it had been when I refilled it.

your going to want something like this, this is pseudo so bear with me....
bool healing=false;
public void OnTriggerEnter2D(Collisider2D col)
{
if(col.tag=="Player")
{
healing = true;
}
}
public void OnTriggerExit2D(Collisider2D col)
{
if(col.tag=="Player")
{
healing = false;
}
}
public void Update()
{
if(healing)
{
if(filled<maxTime)
{
timerBar.fillAmount += 0.006f;
}
}
}
bnwhose health your increasing, so each one needs the script.
you PC must be tagged "Player" to catch the collision.
your objects will need colliders, i assume they already have them.
and either the object or pc, will need a rigidbody. it seem you have most of this already. the reason this works is beacuae of the boolean. it sets it true when you enter the trigger and false when you leave it. so its only tru for that item while your touching that item, then inupdate while its true, we increase the healthbar.
putting this script on each of your objects directly will keep them from conflicting with each other, or updating the rong one.
good luck!

I have several suggestions to hopefully get you on the right track. This script should be attached the objects that fill up the status bars. you need to write a separate script to actually adjust the status bars based on the fillpercentage, but I'll leave that to you. This script is ONLY responsible for tracking how full, not displaying anything. This is good practice to separate your logic from your UI, since you can change one without affecting the other.
Also, you should avoid using "magic numbers" in your code. Make variables instead. see https://en.wikipedia.org/wiki/Magic_number_(programming)
Furthermore, you can switch between increasing and decreasing using a bool, rather than calls to separate methods. This keeps the logic in the same place and makes changing it in the future easier.
public class ColoredObject : Monobehaviour {
//bool to switch increasing and decreasing
bool increasing = false;
//can set this value in inspector to adjust speed it fills
//can even have different fill speeds for each color
public float fillSpeed;
//stores how full the bar is
float percentageFull = 100;
//if player contacts, start increasing
void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
increasing = true;
}
}
//if player exits, start decreasing
void OnCollisionExit2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
increasing = false;
}
}
void Update () {
UpdateFill()
}
public void UpdateFill() {
if (increasing) {
//fill based on time passed since last frame
percentageFull += fillSpeed * Time.deltatime;
}
else {
//empty based on time passed since last frame
percentageFull -= fillSpeed * Time.deltatime;
}
// keep it between 0 and 100%
percentageFull = Mathf.Clamp(percentageFull, 0f, 100f);
}
}
Let me know how it goes, and I can try to help you fine-tune it if this wasn't exactly what you wanted.
EDIT: if you want different increasing and decreasing speed just add another variable, and replace fillSpeed in the decreasing section.

Related

how to make the end of the game in pakman when all the dots have been eaten?

How to make the end of the game in pakman when all the dots have been eaten?
This is the end game code now
void OnTriggerEnter2D(Collider2D co)
{
if (co.name == "PacMan")
{
Destroy(co.gameObject);
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
This is the point eating code
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
If what you're asking is "how do I let the game know the level is over and trigger the end" then just have a variable to hold how many dots are in the level, and every time you eat one and that trigger collider fires, have a counter go up. When the counter equals the total, level ends.
In your class for the dots you could use something like
public class YourDotClass : MonoBehaviour
{
// Keeps track of all currently existing instances of this class
private readonly static HashSet<YourDotClass> _instances = new HashSet<YourDotClass>();
private void Awake ()
{
// Register yourself to the instances
_instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the instances
_instances.Remove(this);
// Check if any instance still exists
if(_instances.Count == 0)
{
// => The last dot was just destroyed!
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
}
However, you should really rethink/restructure your code and think about who shall be responsible for what.
Personally I would not like the dots be responsible for increasing scores and end the game .. I would rather have a component on the player itself, let it check the collision, increase its own points and eventually tell some manager to end the game as soon as all dots have been destroyed ;)
I think you could use something like this. Just store all of the pacdots in an array and once the array is empty you could end the game.
GameObject[] pacdots = GameObject.FindGameObjectsWithTag("pacdot");
void OnTriggerEnter2D(Collider2D collision)
{
// pacman collided with a dot
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
if (pacdots.length == 0)
{
// All dots hit do something
}
}
}

how to work with playerprefs in a character selection menu in unity

I have a character selection menu in which you should have enough coins to buy (unlock) characters. Once one of the characters is selected, your coins must be decreased according to it's price and when you restart the game and that character is selected again, it must not be locked and your coins must not be decreased either. I tried different ways but none of them worked. I accidentally came to this video. here is my script;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEditor;
public class MainMenu3 : MonoBehaviour {
int SceneIndex;
public Button BluePlane;
public Button WhitePlane;
public GameObject wood;
public static int character_number;
public int isPlanesold;
// Use this for initialization
void Start () {
isPlanesold = PlayerPrefs.GetInt ("isPlanesold");
if (!PlayerPrefs.HasKey ("Number")) {
PlayerPrefs.SetInt ("Number", 1);
}
character_number = PlayerPrefs.GetInt ("Number");
UIManager2.coin_score = PlayerPrefs.GetInt ("Score");
SceneIndex = SceneManager.GetActiveScene ().buildIndex;
}
public void Blueplane () {
character_number = 1;
SceneManager.LoadScene ("Menu2");
}
public void BuyWhitePlane () {
if (UIManager2.coin_score >= 1 && isPlanesold ==0) {
WhitePlane.interactable = true;
PlayerPrefs.SetInt ("isPlanesold", 1);
character_number = 2;
UIManager2.coin_score--;
wood.SetActive (false);
PlayerPrefs.SetInt ("Score", UIManager2.coin_score);
PlayerPrefs.SetInt ("Number", character_number);
SceneManager.LoadScene ("Menu2");
}
else
WhitePlane.interactable = false;
}
void Update () {
if (Input.GetKeyDown (KeyCode.Escape))
SceneManager.LoadScene (SceneIndex - 1);
}
when for example I earn 1 coin by BluePlane that's already unlocked by default and click on WhitePlane button, it works fine but when I restart the game, the button returns to WhitePlane.interactable = false; I know what should I do but I really don't know how!
Ok from what I can see you have 2 problems.
Firstly, you're not giving isPlanesolda default value. When you get a PlayerPref for the first time you assign it a default value incase no value for it currently exists.
Secondly, I think its because you don't have an 'already unlocked' option for BuyWhitePlane, only 'not unlocked' and 'can't unlock'. If isPlaneSoldgets set to 1 (like it does when you buy the plane) you can't select the WhitePlane again (ie, when you reload the game).
Also I would call PlayerPrefs.Save() after the user makes a purchase incase the game exits prematurely or crashes, this way the user will keep their progress.
Therefore, I think your script should look something like this:
public class MainMenu3 : MonoBehaviour
{
[SerializeField] Button BluePlane;
[SerializeField] Button WhitePlane;
[SerializeField] GameObject wood;
int character_number;
int isPlanesold;
int score;
void Start()
{
isPlanesold = PlayerPrefs.GetInt("isPlanesold", 0); // I guess you're using this like a true/false? So the plane won't to be sold at first, so it would be zero. Could also convert to a bool with Convert.ToBoolean()
character_number = PlayerPrefs.GetInt("Number", 1);
score = PlayerPrefs.GetInt("Score", 0);
}
public void Blueplane()
{
PlayerPrefs.SetInt("Number", 1);
PlayerPrefs.Save();
SceneManager.LoadScene("Menu2");
}
public void BuyWhitePlane()
{
if (isPlaneSold == 1)
{
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.Save();
SceneManager.LoadScene("Menu2");
}
else if (score >= 1 && isPlanesold == 0)
{
PlayerPrefs.SetInt("isPlanesold", 1);
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.SetInt("Score", score--);
PlayerPrefs.Save();
// wood.SetActive(false); If you're immediately changing scenes, whats the point in doing this?
SceneManager.LoadScene("Menu2");
}
else
{
Debug.Log("You don't have the score!");
// Do stuff, maybe an UI error message?
// If you wanted to make the button not intractable if the user didn't have the score you could check the score value in start and if it was 0 set WhileButton.intractable = false;
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex - 1);
}
If you're planning to add more planes and isPlaneSold's value will increase past 1 then I would recommend adding a method to you don't end up repeating the same
PlayerPrefs.SetInt("isPlanesold", 1);
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.SetInt("Score", score--);
PlayerPrefs.Save();
code.
On a side note, using PlayerPrefs to store information like this (ie the users unlocks and coins) is bad because a PlayerPref's data is in an insecure fire somewhere in the users device. It should only be used to store stuff that the user can already freely (such as settings). A better solution would be to use BinaryFormatter, I would recommend this video to learn how to use them.
Furthermore, I can't see the rest of your code so I can't be sure but as you're saving UIManager2.coinscore and character_numberin PlayerPrefsthey don't need to be static? Static variables can cause some strange behaviour if you don't know what you're doing, and as a rough guideline should be avoided.
Hope this helps and best of luck!

How do I stop my objects from going through walls when I pick them up and drop them down?

When I pick an object up I can glitch it through the map making it fall out of the world. This happens when I pick it up and drop the object half way through the floor. The outcome I receive is not what I was expecting what can I do to fix this. Also yes the colliders and rigidbody's are setup correctly.
public GameObject PressEtoInteractText;
public bool pickup, inrange;
public Collider Playercol;
public Vector3 guide;
private GameObject temp;
private Rigidbody rb;
void Update()
{
if (Input.GetKeyDown(KeyCode.E) && inrange == true)
{
PressEtoInteractText.SetActive(false);
pickup = true;
}
if (Input.GetMouseButtonDown(0) && pickup == true)
{
pickup = false;
Playercol.isTrigger = true;
}
UpdatePickUpFollow();
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Interact")
{
PressEtoInteractText.SetActive(true);
temp = other.gameObject;
inrange = true;
}
if (other.gameObject.tag == "Interact" && temp.transform.position == guide)
{
return;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.name != "Interact")
{
PressEtoInteractText.SetActive(false);
inrange = false;
}
}
public void PickUp()
{
rb = temp.GetComponent<Rigidbody>();
rb.MovePosition(transform.position += guide);
Playercol.isTrigger = false;
}
public void UpdatePickUpFollow()
{
if (pickup == true)
{
PickUp();
}
}
If you deactivate the objects collider with tempCol.enabled = false; it will not register any collisions and you can just push or pull it through walls all day. Make sure to remove that from your script.
Also, using MoveTowards can cause issues with collision detection. In my experience it is best to use AddForce or MovePosition to move the Rigidbody component instead of modifying the Transform directly. Try Rigidbody.MovePosition(Vector3 position). Maybe this works better for you.
Here is a link to the documentation page. It's basically your exact use case:
Rigidbody.MoveTowards Unity Docs
(Hint: notice how they use FixedUpdate instead of regular Update. You should also always do this when working with Rigidbodies because it is synced with physics updates.)
//Edit:
This is a little implementation of your code in a cleaner and hopefully correct way. I have not tested this and there is propably something I missed. Use this as a reference to build your own working solution.
//set in inspector
public Transform guidePosition;
public GameObject pressEToInteractText;
private bool isObjectInRange;
private GameObject inRangeObject;
private bool isObjectPickedUp;
private Rigidbody pickedUpRigidBody;
/* check if button is pressed.
* If no object is in hand -> pick up;
* if object is in hand -> drop;
*/
private void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.E) && isObjectInRange && !isObjectPickedUp)
{
PickupObject();
}
if(Input.GetKeyDown(KeyCode.E) && isObjectPickedUp)
{
DropObject();
}
if (isObjectPickedUp)
{
PickedUpObjectFollow();
}
}
//save references
private void PickupObject()
{
pickedUpRigidBody = inRangeObject.GetComponent<Rigidbody>();
isObjectPickedUp = true;
}
//remove saved references
private void DropObject()
{
isObjectPickedUp = false;
pickedUpRigidBody = null;
}
//move position to guidePosition
private void PickedUpObjectFollow()
{
pickedUpRigidBody.MovePosition(guidePosition.position);
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Interact") && !isObjectPickedUp)
{
pressEToInteractText.SetActive(true);
isObjectInRange = true;
inRangeObject = other.gameObject;
}
}
private void OnTriggerExit(Collider other)
{
if(other.CompareTag("Interact") && other.gameObject == inRangeObject)
{
pressEToInteractText.SetActive(false);
isObjectInRange = false;
inRangeObject = null;
}
}
You could do multiple things. Unfortunately the way VR tracks the hands and there is no way to stop ur hands in real life your hand will defy the physics in the application.
I would either make the item drop from the users hand when it gets close enough to a certain area that you dont want it going through.
Or you can make it that if it does pass through a space you can report the item right above the floor it went through.
Ive looked long and hard for an answer to this and it doesnt seem like it can at this point be blocked from moving through if its currently grabbed.

Stopping the update function in Unity

I have some code that when it executes, it pushes a character forward. The issue is that the character never stops moving and continues forever. Is there a way to stop the character from moving after 2 seconds? Here is the code I'm using:
public class meleeAttack : MonoBehaviour
{
public int speed = 500;
Collider storedOther;
bool isHit = false;
void Start()
{
}
void Update()
{
if (isHit == true )
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
}
}
void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player" && Input.GetKeyUp(KeyCode.F))
{
storedOther = other;
isHit = true;
}
}
}
I'm not sure if there's a way to stop the update() function so it stops the character movement.
The Update function is part of the life cycle of a Unity script. If you want to stop the Update function to be executed, you need to deactivate the script. For this, you can simply use:
enabled = false;
This will deactivate the execution of the life cycle of your script, and so prevent the Update function to be called.
Now, it looks like you are applying a force to your character to make it move. What you might want to do then is, after two seconds, remove any force present on your character. For that, you can use a coroutine, which is a function that will not just be executed on one frame, but on several frames if needed. For that, you create a function which returns an IEnumerator parameter, and you call the coroutine with the StartCoroutine method:
bool forcedApplied = false;
void Update()
{
if (isHit == true && forceApplied == false)
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
forceApplied = true;
StartCoroutine(StopCharacter);
isHit = false;
}
}
IEnumerator StopCharacter()
{
yield return new WaitForSeconds(2);
storedOther.GetComponent<Rigidbody>().velocity = Vector3.zero;
forceApplied = false;
}
Those might be different ways to achieve what you want to do. It's up to you to choose what seems relevant to your current gameplay and to modify your script this way.

How to assert a spawn is "filled"?

I have two spawn spots where a player will show up upon connection.
I need that, when one of the players connects on one of the spots, any other player will always spawn at the other spot.
Here's some visual in case it helps: https://goo.gl/Y0ohZC
Thanks in advance,
IC
You can add those two possible spawn spots as empty game objects.
Then I'd make a boolean array and set its states to true or false depending on if the spot is occupied. The spots aren't directly stored in this array so you should make another array.
In C# this would approximately look like this:
public GameObject[] Spots = new GameObject[2]; //Drag the spots in here (In the editor)
bool[] OccupiedSpawnSpots = new bool[2];
int mySpotNumber = -1;
void Start() {
PhotonNetwork.ConnectUsingSettings ("v1.0.0");
}
void OnGUI() {
GUILayout.Label (PhotonNetwork.connectionStateDetailed.ToString ());
}
void OnJoinedLobby()
{
Debug.Log ("Joined the lobby");
PhotonNetwork.JoinRandomRoom ();
}
void OnPhotonRandomJoinFailed()
{
Debug.Log ("No room found. Creating a new one...");
PhotonNetwork.CreateRoom (null);
}
void OnPhotonPlayerConnected(PhotonPlayer player)
{
//If we are the MasterClient, send the list to the connected player
if (PhotonNetwork.isMasterClient)
GetComponent<PhotonView>().RPC("RPC_SendList", player, OccupiedSpawnSpots);
}
void OnApplicationQuit()
{
//If I am outside and others want to connect, my spot shouldn't be still set as occupied:
//I mean if the application quits I'm of course going to be disconnected.
//You have to do this in every possibility of getting disconnected or leaving the room
OccupiedSpawnSpots[mySpotNumber] = false;
//Send the changed List to the others
GetComponent<PhotonView>().RPC("RPC_UpdateList", PhotonTargets.All, OccupiedSpawnSpots);
}
[RPC]
void RPC_SendList(bool[] ListOfMasterClient)
{
OccupiedSpawnSpots = ListOfMasterClient;
//Get the free one
if (OccupiedSpawnSpots[0] == false)
{
//Spawn your player at 0
SpawnMyPlayer(0);
//Set it to occupied
OccupiedSpawnSpots[0] = true;
}
else //so the other must be free
{
//Spawn your player at 1
SpawnMyPlayer(1);
//Set it to occupied
OccupiedSpawnSpots[1] = true;
}
//Send the changed List to the others
GetComponent<PhotonView>().RPC("RPC_UpdateList", PhotonTargets.All, OccupiedSpawnSpots);
}
[RPC]
void RPC_UpdateList(bool[] RecentList)
{
OccupiedSpawnSpots = RecentList;
}
void SpawnMyPlayer(int SpotNumber) {
// Check if spawnspots are set OK
if (Spots == null) {
Debug.LogError ("SpawnSpots aren't assigned!");
return;
}
mySpotNumber = SpotNumber;
// The player object for the network
GameObject myPlayerGO = (GameObject)PhotonNetwork.Instantiate ("PlayerController",
Spots[SpotNumber].transform.position,
Spots[SpotNumber].transform.rotation, 0);
// Enable a disabled script for *this player only, or all would have the same camera, movement, etc
//((MonoBehaviour)myPlayerGO.GetComponent("FPSInputController")).enabled = true;
// Set a camera just for this player
//myPlayerGO.transform.FindChild ("Main Camera").gameObject.SetActive (true);
//standbyCamera.SetActive(false);
}
Note: If you have more than two players, it gets a lot more difficult. This code isn't optimized so maybe you'll find a better one, but it should give you the right idea.
Please ask if anything isn't clear to you...