I have such a script:
public class SystemLives : MonoBehaviour
{
public int lives;
public int maxLives;
public Image[] Live;
public Sprite FullHearts;
public Sprite EmptyHearts;
public void setLives(int time)
{
lives += Mathf.RoundToInt(Time.deltaTime * time);
if (lives > maxLives)
{
lives = maxLives;
}
for (int i = 0; i < Live.Length; i++)
{
if (i < lives)
{
Live[i].sprite = FullHearts;
}
else
{
Live[i].sprite = EmptyHearts;
}
}
}
public void TakeHit(int damage)
{
lives -= damage;
if (lives <= 0)
{
Debug.Log("Игра окончена");
}
for (int i = 0; i < Live.Length; i++)
{
if (i < Mathf.RoundToInt(lives))
{
Live[i].sprite = FullHearts;
}
else
{
Live[i].sprite = EmptyHearts;
}
}
}
n which there are functions for subtracting and adding life, I hung this script on an empty element, I call it like this:
public GameObject ScriptLives;
private SystemLives systemLives;
public int times=1;
public void Btn()
{
foreach (var s in strings)
{
if (s.Compare() == true)
{
b++;
}
else
{
prob++;
}
}
systemLives = ScriptLives.GetComponent<SystemLives>();
systemLives.setLives(times);
systemLives = ScriptLives.GetComponent<SystemLives>();
systemLives.TakeHit(prob);
Debug.Log($"{b}");
}
I call it like this: It turns out to take lives, but for some reason it is not added. maybe the problem is in the first script? please tell me what the reason may be and how it can be fixed?
You're calling
lives += Mathf.RoundToInt(Time.deltaTime * time);
but Time.deltaTime is the time between frame draws. Even an awful game might still have 10 fps, which means Time.deltaTime is 0.1 seconds. You're passing 1 in as the argument to that function, so (0.1*1) = 0.1.
Then you take 0.1, round it to an integer, so it rounds to 0. Then you increment lives by zero.
I really can't tell what the goal is here with the time dependency on adding lives, so unfortunately I've got no suggestions, but hopefully this helps.
An easy way to ask your question is to use InvokeRepeating, which works as follows:
public void Start()
{
InvokeRepeating(nameof(AddLife), 0f, 2f);
}
public void AddLife() // add 1 life every 2 sec
{
life = Mathf.Min(++life, maxLives);
Debug.Log("Life: "+life);
}
If you want to be time dependent, change the life adjustment algorithm to Time.deltaTime to Time.time.
public void Update()
{
SetLife(3f); // set life every 3 sec for e.g.
}
public void setLives(float repeatTime)
{
life = (int) (Time.time / repeatTime);
}
Related
I have an array that I am shuffling and then assigning to buttons. Depending on the item in the array, the button will perform a different function. This is currently my code, and it works, but is extremely inelegant.
I am currently shuffling the array first, then assigning the first 2 items of the shuffled array to the button text, and then using if statements to check whether or not the string matches so that it will execute a specific code for that string.
There is definitely a better way to do this, but I can't seem to figure it out.
public TextMeshProUGUI firstChoiceText;
public TextMeshProUGUI secondChoiceText;
public GameObject player;
public string[] upgrades =
{
"Speed Up",
"Fire Rate Up",
"Damage Up",
"Max Health Up"
};
public void Shuffle(string[] array)
{
for (int i = 0; i < array.Length; i++)
{
string tmp = array[i];
int rand = Random.Range(0, array.Length);
array[i] = array[rand];
array[rand] = tmp;
}
firstChoiceText.text = upgrades[0];
secondChoiceText.text = upgrades[1];
}
// Start is called before the first frame update
void Start()
{
Shuffle(upgrades);
}
public void FirstChoice()
{
Debug.Log("first choice clicked");
if (firstChoiceText.text == "Speed Up")
{
player.GetComponent<PlayerController>().playerSpeed += 1;
}
else if (firstChoiceText.text == "Fire Rate Up")
{
player.GetComponent<PlayerController>().fireRate -= 0.05f;
}
else if (firstChoiceText.text == "Damage Up")
{
player.GetComponent<PlayerController>().playerDamage *= 1.1f;
}
else if (firstChoiceText.text == "Max Health Up")
{
GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5);
}
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
public void SecondChoice()
{
Debug.Log("second choice clicked");
if (secondChoiceText.text == "Speed Up")
{
player.GetComponent<PlayerController>().playerSpeed += 1;
}
else if (secondChoiceText.text == "Fire Rate Up")
{
player.GetComponent<PlayerController>().fireRate -= 0.05f;
}
else if (secondChoiceText.text == "Damage Up")
{
player.GetComponent<PlayerController>().playerDamage *= 1.1f;
}
else if (secondChoiceText.text == "Max Health Up")
{
GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5);
}
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
One solution would be to create a Dictionary<string, Action<Player>> where your Action delegate corresponds to your string key and call the method via Action delegate based on the string key of the Dictionary, also moved your shuffle logic into it's own private method to reduce code duplication:
//Dictionary to hold string key/Action delegate pairs
private Dictionary<string, Action<Player>> _actions = new Dictionary<string, Action<string>>
{
{"Speed Up", (player) => player.GetComponent<PlayerController>().playerSpeed += 1;},
{"Fire Rate Up", (player) => player.GetComponent<PlayerController>().fireRate -= 0.05f;}
{"Damage Up", (player) => player.GetComponent<PlayerController>().playerDamage *= 1.1f;},
{"Max Health Up", (player) => { GameManager.maxHealth += 5;
player.GetComponent<PlayerController>().Heal(5); } }
};
//You could reduce your First And Second Choice down to using the
//dictionary to call the cooresponding Action delegate:
public void FirstChoice()
{
Debug.Log("first choice clicked");
_actions[firstChoiceText.text](player);
DoShuffle();
}
public void SecondChoice()
{
Debug.Log("second choice clicked");
_actions[secondChoiceText.text](player);
DoShuffle();
}
//Moved this into a method to reduce repetitive code
private void DoShuffle()
{
Time.timeScale = 1;
gameObject.SetActive(false);
Shuffle(upgrades);
}
You could create a base class for upgrades, and then use inheritance to split up all the logic that is now in the if statements to separate upgrade classes deriving from this base class. In the Unity world you might want to use ScriptableObjects for this.
public abstract class Upgrade : ScriptableOject
{
public abstract void Apply(PlayerController player);
}
[CreateAssetMenu(menuName = "Upgrades/Speed Up", fileName = "Speed Up")]
public sealed class SpeedUp : Upgrade
{
public override void Apply(PlayerController player)
{
player.playerSpeed += 1;
}
}
[CreateAssetMenu(menuName = "Upgrades/Fire Rate Up", fileName = "Fire Rate Up")]
public sealed class FireRateUp : Upgrade
{
public override void Apply(PlayerController player)
{
player.fireRate -= 0.05f;
}
}
Then you could create one scriptable object asset for each upgrade, and then assign all of them to your script into an Upgrade[] field.
[SerializeField] private TextMeshProUGUI firstChoiceText;
[SerializeField] private TextMeshProUGUI secondChoiceText;
[SerializeField] private PlayerController player;
[SerializeField] private Upgrade[] upgrades;
private Upgrade firstUpgrade;
private Upgrade secondUpgrade;
public void ApplyFirstUpgrade()
{
Debug.Log("first choice clicked");
ApplyUpgrade(firstUpgdade);
}
public void ApplySecondUpgrade()
{
Debug.Log("second choice clicked");
ApplyUpgrade(secondUpgrade);
}
private void Awake() => RandomizeUpgrades();
private void RandomizeUpgrades()
{
firstUpgrade = GetRandomUpgrade();
secondUpgdade = GetRandomUpgrade(firstChoice);
}
private Upgrade GetRandomUpgrade() => upgrades[Random.Range(0, upgrades.Length)];
private Upgrade GetRandomUpgrade(Upgrade ignore)
{
if(upgrades.Length < 2)
{
Debug.LogError("At least 2 upgrades need to be assigned before calling GetRandomUpgrade.", this);
return;
}
Upgrade resultCandiate = GetRandomUpgrade();
if(resultCandiate != ignore)
{
return result;
}
return GetRandomUpgdate(ignore);
}
private void ApplyUpgrade(Upgrade upgrade)
{
upgrade.Apply();
Time.timeScale = 1;
gameObject.SetActive(false);
RandomizeUpgrades();
}
The benefit with this sort of approach is that you can add more abilities easily without having to make any modifications to existing code, and without ending up with one giant class with hundreds of lines of code.
Im working on a AI combat system where each AI has a secondary collider with "Trigger" enabled. here is my script so far
public float health = 100;
public int isrunning = 1;
public GameObject currenttarget;
public int attackspeed;
public int damage;
public int newdamage = 0;
void Start()
{
StartCoroutine(DoDamage());
}
public void TakeDamage(float x)
{
this.health = this.health - x;
}
public IEnumerator DoDamage()
{
isrunning = 0;
yield return new WaitForSeconds(attackspeed);
Debug.Log("loop");
newdamage = damage;
isrunning = 1;
}
private void OnTriggerStay(Collider other)
{
if ( other.gameObject.CompareTag("AI"))
{
other.GetComponent<Framework>().TakeDamage(newdamage);
newdamage = 0;
}
}
private void Update()
{
if (isrunning==1)
{
StartCoroutine(DoDamage());
}
}
// Update is called once per frame
}
When I place three objects with this script where there damage is set to 5 and attack rate to 1, The result that I want out of this would be: A.100 B.100 C.100 (1 Second) A.80 B.80 C.80 However what I find is that the other.GetComponent().TakeDamage is only applying to one object at a time rather then being applied to the other two objects as I want. is this how the OnTriggerStay should be working? and if so are there any workarounds for this?
In my way, I suggest you to use a List of object, whenever the eneny attack(OnTriggerEnter) the player, you can push them to the list, then use the foreach loop in the Update Func to do some Function like take damage,... , after the enemy stop attack(OnTriggerExit) you can Pop its out of the List.
I'm making a game similar to the 2013 style Cookie Clicker. I'm stuck with making the auto generate over time script. I want to be able to access a script that makes "Muffins" automatically.
I've tried to make the script multiple times but I can't seem to get the muffin count to change.
public bool MakingMuffins = false;
public static int MuffinIncrease = 1;
public int InternalIncrease;
void Update () {
InternalIncrease = MuffinIncrease;
if (MakingMuffins == false)
{
MakingMuffins = true;
StartCoroutine(MakeTheMuffin());
}
}
IEnumerator MakeTheMuffin ()
{
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
and my other main file to start the method.
public void StartAutoMuffin()
{
if (InternalPlayerMuffins >=bakerycost){
playSound.Play();
StartBakery.SetActive(true);
InternalPlayerMuffins -= bakerycost;
bakerycost *= 2;
turnOffButton = true;
themps += 1;
thelevel += 1;
}else{
Debug.Log("Cant do anything");
}
}
StartBakery is a Game Object that should start the auto make after I press the button. It is set inactive as default.
Here is more code from my main script to help understand.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GlobalGameMechanics : MonoBehaviour
{
public static bool turnOffButton = false;
public AudioSource muffinSound;
public AudioSource playSound;
public static int GlobalPlayerDiamonds;
public int InternalPlayerDiamonds = 1200;
public GameObject PlayerDiamondDisplay;
public GameObject TotalMPS;
public GameObject Cost;
public GameObject Level;
public GameObject MPS;
public GameObject FakeCost;
public GameObject FakeLevel;
public GameObject FakeMPS;
public int InternalPlayerMuffins = 15;
public int bakerycost = 25;
public int thelevel = 0;
public int themps = 0;
public int currentMuffins;
public GameObject ShopPanel;
public GameObject fakeButton;
public GameObject realButton;
public GameObject StartBakery;
void Start()
{
}
void Update()
{
GlobalPlayerDiamonds = InternalPlayerDiamonds;
PlayerDiamondDisplay.GetComponent<Text>().text = "" + InternalPlayerDiamonds;
TotalMPS.GetComponent<Text>().text = "/Sec " + themps;
GlobalMuffins.MuffinCount = InternalPlayerMuffins;
Cost.GetComponent<Text>().text = "" + bakerycost;
Level.GetComponent<Text>().text = "Level " + thelevel;
MPS.GetComponent<Text>().text = "MPS " + themps;
FakeCost.GetComponent<Text>().text = "" + bakerycost;
FakeLevel.GetComponent<Text>().text = "Level " + thelevel;
FakeMPS.GetComponent<Text>().text = "MPS " + themps;
currentMuffins = InternalPlayerMuffins;
if (currentMuffins >= bakerycost)
{
fakeButton.SetActive(false);
realButton.SetActive(true);
}
if (turnOffButton == true)
{
realButton.SetActive(false);
fakeButton.SetActive(true);
turnOffButton = false;
}
}
So far I have created 1 button that I want to increase the "InternalPlayerMuffins" By 1 every second. And then I want to have Button 2 to increase by +5 /sec. I'm just having trouble and been stuck for a few days.
I cant make the muffins increase at all on its :/
Your are close, it seems like you forgot to loop your coroutine endlessly like so:
IEnumerator MakeTheMuffin() {
while (true) {
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
Though I may be wrong on that, since I am not so sure if GlobalMuffins.MuffinCount is the integer that you want to increase every second.
But generally the idea is the same, you want would want a coroutine, a target interval, a target integer and the value you want to increase by, like so:
public class YourMuffinMaker : MonoBehaviour {
public float intervals;
public int increment;
private int muffinCount;
void Start() {
muffinCount = 0;
StartCoroutine(CreateMuffinEveryIntervals());
}
private IEnumerator CreateMuffinEveryIntervals() {
while (true) {
muffinCount += increment;
yield return new WaitForSeconds(intervals);
}
}
}
This answer that was posted is an alternative to Coroutine as well.
i can use InvokerRepeating whick calls method every time u need, for example
void Start()
{
InvokeRepeating("CreateNewMuffin", 0f, 1f);
}
I increase a value from 0 to 1 and decrease it back to 0.
private Image cycleBar;
private float currentTime = 0;
private float cycleDuration = 5;
private void Start()
{
cycleBar = GetComponent<Image>();
UpdateCycleBar();
}
private void Update() // TimeHandler
{
currentTime = Mathf.PingPong(Time.time * 1 / cycleDuration, 1); // handle the time
UpdateCycleBar();
}
private void UpdateCycleBar()
{
// the missing part
}
So now the value got its logic. How can I visualize it like this example here
When reaching the value 1, the bar has to move on to the right "zero number". After that it just resets on the left side again.
You can try this approach. But don't forget to call Initialize(float value, TimeMod mod) method if you want initialize it from Dusk for example.
using System;
using UnityEngine;
using UnityEngine.UI;
public class CycleBar : MonoBehaviour
{
[SerializeField]
private Image cycleBar;
private TimeMod currentMod = TimeMod.AM;
public void Initialize(float value, TimeMod mod)
{
currentMod = mod;
cycleBar.fillAmount = GetProgressbarValue(value);
}
public void UpdateValue(float value)
{
CheckTimeMod(value);
cycleBar.fillAmount = GetProgressbarValue(value);
}
private void CheckTimeMod(float value)
{
if (Mathf.Abs(value - 1) < 0.01f)
{
currentMod = TimeMod.PM;
}
if (Mathf.Abs(value) < 0.01f)
{
currentMod = TimeMod.AM;
}
}
private float GetProgressbarValue(float value)
{
switch (currentMod)
{
case TimeMod.AM:
return value / 2;
case TimeMod.PM:
return 0.5f + Mathf.Abs(value-1) / 2;
default:
throw new ArgumentOutOfRangeException("currentMod", currentMod, null);
}
}
public enum TimeMod
{
AM,
PM
}
}
And controller:
using UnityEngine;
public class Controller : MonoBehaviour
{
[SerializeField]
private CycleBar cycleBar;
private void Update() // TimeHandler
{
var value = Mathf.PingPong(Time.time, 1); // handle the time
cycleBar.UpdateValue(value);
}
}
But if it possible, use more simple way with the range [-1;1]. For example you can use Slider from UnityEngine.UI
If it helps you, mark please this post as Correct Answer.
you can create a new image in UI, then on 2D resize mode, resize it to your complete state. now you can scale X from zero to 1 in your code.
I'm creating a game designed for vision-impaired gamers. My score text currently shows up during the gameplay. Once you lose it loads a new scene (End screen), but I want the score to stay when the end scene is loaded and then have it reset to 0 when the game screen is loaded again, then removed if the player decides to go back to the main menu.
This is what loads the next scene.
public int amount;
public void ChangeScene(int changeTheScene)
{
SceneManager.LoadScene(changeTheScene);
}
void Start()
{
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
amount += 1;
Debug.Log(amount);
if (amount == 10)
{
SceneManager.LoadScene(2);
}
}
}
}
And this is how i track the score
void Start()
{
count = 0;
SetCountText();
float x = Random.Range(325f, -600f);
float y = Random.Range(250f, -450f);
Debug.Log(x + "," + y);
prefab.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);
loseObject.GetComponent<Lose>().amount = 0;
}
public void move()
{
float x = Random.Range(325f, -600f);
float y = Random.Range(250f, -450f);
Debug.Log(prefab.transform.position.x + "," + prefab.transform.position.y);
prefab.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, y);
loseObject.GetComponent<Lose>().amount = 0;
count = count + 1;
SetCountText();
}
void SetCountText()
{
countText.text = "Count: " + count.ToString();
}
}
I'm not entirely sure if this would work, but to answer your question, I'm guessing this would do what you'd like.
using UnityEngine;
//This would be attached to said gameobject that displays the amount.
public class Amount : MonoBehaviour
{
public static Amount instance = null;
void Awake()
{
if (instance == null)
instance = this;
if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
}
//Here would be what's needed to set the amount's parent.
public class SetThatAmountsParentToThisGameObject : MonoBehaviour
{
void SetAmountParent()
{
Amount.instance.transform.SetParent(transform);
}
}
//And here you would destroy said gameobject that displays the amount.
public class DestroyThatAmount : MonoBehaviour
{
void DestroyIt()
{
Destroy(Amount.instance.gameObject);
}
}
Be advised that I haven't tested this and therefor am not sure if this will actually work. I'm writing it as an answer because you asked for an example and code is much cleaner and easier to read in answers.