Is there an easy way to get out of this IF loop? - c#

I'm learning how to program and I feel that I'm always trapped with this kind of loops problems.
the question is, what would be the best way to get out of the if when the grouped bool is always true on Update (every frame). I need to execute the EUREKA only once.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroupedWordsEyes : MonoBehaviour
{
void Update()
{
//Check if words are grouped
bool grouped = CompareTags_Word1.colliding == true;
if (grouped)
{
Debug.Log("EUREKAAAA!");
//get out of the loop!
}
}
}

It sounds like you want that Update is only called until the condition matches the first time.
You can simply disable that component by setting enabled to false.
This way Update is no longer called by Unity.
public class GroupedWordsEyes : MonoBehaviour
{
void Update()
{
//Check if words are grouped
if (CompareTags_Word1.colliding)
{
Debug.Log("EUREKAAAA!");
enabled = false;
}
}
}

It seems like you want the code within the if to execute only when "colliding" changes.
Then you need to remember the previous value and check for changes:
EDIT changed code based on comments by OP
public class GroupedWordsEyes : MonoBehaviour
{
private bool previousGrouped = false; // assume that at first there is no collision
void Update()
{
//Check if words are grouped
bool grouped = CompareTags_Word1.colliding == true;
if (grouped != previousGrouped )
{
// always remember a change
previousGrouped = grouped;
// only when just collided
if (grouped)
{
Debug.Log("EUREKAAAA!");
}
}
}
}

EDIT: Updating answer as I misunderstood the question.
You've got several options and it entirely depends on how you've architected.
One option is to deactivate (or even completely destory) the component. After you've done the one thing you need to do, call this.enabled = false - this will disable the component and no scripts will be run. This depends on architecture as this will also disable any other functions or child components attached to this one. You can mitigate this by ensuring this script only contains specific pieces that you want to be able to disable.
Create a flag that indicates whether the action has already been done.
if (grouped && !hasDoneEureka)
{
// do the thing
hasDoneEureka = true;
}
Note that this will ensure you only run what you need to once, but probably isn't optimal since while it won't "do the thing" every time, it will CHECK every frame.

Related

Doubst about Managin player actions and cooldown time

So i got this action object witch contains a button, when i press that button the action coroutine starts, sets the cooldown of the action and enabled=false the button.
Im trying to implement a cooldown system so every time the turn ends action.currentCooldown -=1.
Im yet to implement my turn manager and i must admit im a bit clueless about it, i guess it must have a state(allyTurn, enemyTurn) and a coroutine to update all action cooldown when the turn changes.
Also what i want to do is asing a List to each unit and then display each action buttons.
here are some screenshots of the code (keep in mind its just a first draft and im still learning the basics)
Hereis the action object
here is the code for the action
I apreciate all the help i can get
So there are a couple ways to implement what you are looking for. The way that I would want to go for is using events. You could create events for each state change (i.e. AllyTurnStart and EnemyTurnStart):
public class ExampleClass : MonoBehaviour
{
bool AllyTurn = true;
UnityEvent AllyTurnStart;
UnityEvent EnemyTurnStart;
BaseAction AllyAction;
BaseAction EnemyAction;
void Start()
{
// This will setup the event listeners that get called
if (AllyTurnStart == null)
AllyTurnStart = new UnityEvent();
if (EnemyTurnStart == null)
EnemyTurnStart = new UnityEvent();
// This is for example purposes but assigning actions to the events can happen anywhere
AllyAction = new BaseAction()
EnemyAction = new BaseAction()
AllyTurnStart.AddListener(AllyAction.StartOfTurnEvents);
EnemyTurnStart.AddListener(EnemyAction.StartOfTurnEvents);
}
//This method is meant to simulate switching turns back and forth.
void NextTurn()
{
if(AllysTurn)
{
AllyTurnStart.Invoke()
}
else
{
EnemyTurnStart.Invoke()
}
//This switches whos turn it is
AllysTurn = !AllysTurn;
}
}
public class BaseAction : Monobehaviour
{
int Cooldown;
public StartOfTurnEvents()
{
// Here you can reduce the cooldown, Check if it is ready, etc.
}
}
Hope this helps.

How do I get a button that is held down to change what it is doing after a power up ends?

In the game I am trying to make. When I am holding my fire button and I pick up a power up SuperShot, it continues to fire my regular laser unless I release my fire button and press it again. The same thing happens again when the power up ends. I have researched and I cant seem to figure out a way to check on where the power up is active while holding the key down.
private void Fire()
{
if (Input.GetButtonDown("Fire1"))
{
if (SuperShotIsActive)
{
superShotFiringCoroutine = StartCoroutine(SuperShotFireContinuously());
}
else if (!SuperShotIsActive)
{
firingCoroutine = StartCoroutine(FireContinuously());
}
}
if (Input.GetButtonUp("Fire1"))
{
StopCoroutine(firingCoroutine);
StopCoroutine(superShotFiringCoroutine);
}
}
You might be able to fix this by looking away from the button itself and into the code of the actual firing of the weapon.
//psuedocode-ish example. Just for the concept
//not necessarily the best idea to use a for loop but maybe it works for you.
FireContinuously()
{
for(int foo = 0; foo < ammo; foo++)
{
if(!SuperShotIsActive)
{
//normal firing code you have goes here
}
else
{
//supershot code goes here
}
}
}
In FireContinuously() (which I assume is a loop of some sort) you can add a check before each shot to see if the player now has the powerup and the necessary logic to change to the appropriate SuperShotFireContinuously(). Do the opposite for SuperShotFireContinuously() to change to FireContinuously().

How to stop the execution of the code until I read a value from PlayerPref in Unity

Currently I am using Toggles to select a list of images and rather ran into an interesting problem.
I am using a Toggle Group called Radio Group and have 3 toggles under it. Each time when a toggle is selected the command
PlayerPrefs.SetInt("SaveToggleId", id);
is run. In this the id number is 0 for toggle 1, 1 for toggle 2 and so on.
So when I try to read this data the next time , the following set of code always reads 0 when used in Start and the correct value when used in Awake
toggleGroupId = PlayerPrefs.GetInt("SaveToggleId");
toggleGroupObject = GetComponent<ToggleGroup>();
SelectStartingToggle(toggleGroupId);
When I used this code in conjuction with the Debug.log() statements in various places what I found is when used in Start , it first reads from the function associated when the first toggle is selected and therby stores 0 . But when I use it in Awake it reads the right value stored in PlayerPrefs and selects the correct initial value
My explanation would be that because Awake is executed before Start , it has ample time to read from PlayerPrefs which gives the correct value. Also when I used only the number in the Start() as follows
SelectStartingToggle(3);
it correctly selected the right toggle whereas when I used PlayerPref instead of number ,it chose the wrong value.
Is my explanation correct or am I missing something?Also how to make sure the code execution is halted until the data from PlayerPref is read. Here is the full code:
public class RadioButtonSystem : MonoBehaviour
{
ToggleGroup toggleGroupObject;
private int toggleGroupId;
// Start is called before the first frame update
private void Awake()
{
toggleGroupId = PlayerPrefs.GetInt("SaveToggleId");
toggleGroupObject = GetComponent<ToggleGroup>();
Debug.Log("SaveToggleId........" + toggleGroupId);
SelectStartingToggle(toggleGroupId);
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void onSelectedToggle1()
{
SaveToggleId(0);
}
public void onSelectedToggle2()
{
SaveToggleId(1);
}
public void onSelectedToggle3()
{
SaveToggleId(2);
}
public void SelectStartingToggle(int id)
{
Toggle[] toggles = GetComponentsInChildren<Toggle>();
toggles[id].isOn = true;
}
public void SaveToggleId(int id)
{
PlayerPrefs.SetInt("SaveToggleId", id);
Debug.Log("SaveToggleId........saving..........." + PlayerPrefs.GetInt("SaveToggleId"));
}
/* Toggle GetSelectedToggle()
{
Toggle[] toggles = GetComponentsInChildren<Toggle>();
foreach (var t in toggles)
if (t.isOn) return t; //returns selected toggle
return null; // if nothing is selected return null
}*/
}
Playerprefs are saved upon OnApplicationQuit(). If you want to save it immediately, call PlayerPrefs.Save(). After PlayerPrefs.SetInt().
Btw, from the unity scripting api:
This function will write to disk potentially causing a small hiccup, therefore it is not recommended to call during actual gameplay.
In the edit/project settings there is a tab called script execution order, where you can set the order in which your scripts will be executed. For example, you have a "LoadManager" script, you set its priority to 1, and you set everything that relys on it to a greater number like 10.
If you fo this, nothing eill start executing until your manager script finished.

Balancing calls inside of a EditorWindow OnGUI method causing problems

I'm making a system to balance calls inside of the OnGUI method of a EditorWindow.
I'm doing the following:
public void Update()
{
Repaint();
}
Inside of my OnGUI method I'm calling this Balancer. I have one list with the callbacks (List).
So the idea is simple, some callvaxc
I'm skipping some repaint frames for the callback that has the complete GUI, and calling on each repaint for other callbacks (for example, a marquee label or dispalying gifs).
By some reason, this error happens "Getting control 0's position in a group with only 0 controls when doing repaint"
private int m_repaintCounter;
public void Draw()
{
Event e = Event.current;
try
{
foreach (var action in m_actions)
{
try
{
// Test 1
// MainAction is a class that inherits from Action (class MainAction : Action)
if (action is MainAction)
{
bool isDesignedType = e.rawType == EventType.Repaint || e.rawType == EventType.Layout;
if (isDesignedType)
++m_repaintCounter;
if (!(m_repaintCounter == 20 && isDesignedType))
continue;
else
m_repaintCounter = 0;
}
// Test 2
action.Value();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
catch
{
// Due to recompile the collection will modified, so we need to avoid the exception
}
}
But if I comment the "Test 1" everythings works fine.
On the ctor of the class we need to specify a callback to a GUI method, for example:
public Balancer(Action drawAction)
{
m_actions = new List<Action>();
m_actions.Add(drawAction);
}
So we could do easily (inside the EditorWindow):
private Balancer m_balancer;
public void OnEnable()
{
m_balancer = new Balancer(Draw);
}
public void Draw()
{
// This block will be called every 20 repaints as specified on the if statment
GUILayout.BeginHorizontal("box");
{
GUILayout.Button("I'm the first button");
GUILayout.Button("I'm to the right");
// This marquee will be called on each repaint
m_balancer.AddAction(() => CustomClass.DisplayMarquee("example"));
}
GUILayout.EndHorizontal();
}
// Inside of the Balancer class we have
// We use System.Linq.Expressions to identify actions that were added already
private HashSet<string> m_alreadyAddedActions = new HashSet<string>();
public void AddAction(Expression<Action> callback)
{
if(!m_alreadyAddedActions.Add(callback.ToString()))
return;
m_actions.Add(callback.Compile());
}
I can't figure this out. I couldn't find any information on the internet. Can anyone help me?
Ok, so, OnGui (IMGui) is awful to work with. If you aren't using it for an editor script, use the new 4.6 UI (UGui) instead.
Now then. The problem.
OnGui is called at least twice every frame. One of those is to calculate layouts and the other is to actually draw stuff ("repaint").
If the number of things, size of things, or anything else changes between these two calls then Unity will error with "Getting control 0's position in a group with only 0 controls when doing repaint."
That is: you cannot change UI state in the IMGui system at any point after Layout and before Repaint.
Only, only, only change state (and thereby which objects are being drawn) during Event.current == EventType.Repaint and only, only, only change state for the next frame (alternatively, do the changes during Event.current == EventType.Layout, provided that this same, new state will result in the same code path during Repaint). Do not, under any circumstances, make changes during Repaint that were not present during the previous Layout.

HashSet of objects whose properties need to be the keys

I have a Unity project in which various Cords are stretched between various Hooks. The player can grab one end of a cord from its hook, carry it, and attach it to another. As part of this functionality, the hook needs to keep track of which cords are attached to it, and when the player tries to attach a new one, check that doing so won't 'disappear' the cord (ie. they're not hooking both ends to the same place, and the cord won't merge with another by being attached to the same hooks at both ends).
Each Cord object has start and end properties that refer to the Hooks to which it's attached. Currently, I've been keeping track with a List:
public class Hook : MonoBehaviour {
List<Cord> attached = new List<Cord>();
public void Attach(Cord c) {
if (!ConflictTest(c))
attached.Add(c);
}
public void Detach(Cord c) {
attached.Remove(c);
}
bool ConflictTest(Cord toAttach) {
foreach (Cord c in attached) {
// canonically, a cord is grabbed by its 'end', so we only check its 'start'
if (c.start == toAttach.start || c.end == toAttach.start)
return true;
}
return false;
}
}
I realised that this linear search in ConflictTest(), which is actually executed at every OnMouseEnter() and OnMouseDown() isn't ideal. I would like to change the data structure of attached to a HashSet, but is there a way I can set things up so I can do the test in terms of the HashSet's Contains() method (ie. attached.Contains(toAttach.start))? Since I need to refer to the start and end properties of each member of the set, it seems I either need to a separate set for each property (but then how do I coordinate additions and removals?) or to somehow allow the properties to be keys. Any ideas?
One way to do this is to expand your List<Cord> into a Dictionary<Hook, Cord>, where the key represents the Hook end point of the value Cord that is not the current one. This Hook recorded is guaranteed to be unique since a Hook will only ever have at most one Cord validly connecting it to another Hook. Here's how this might look (extra commenting for clarity):
public class Hook : MonoBehaviour {
// Stores a Cord and its "other" Hook
Dictionary<Hook, Cord> attached = new Dictionary<Hook, Cord>();
// Add a Cord by retrieving its "other" Hook and using it as a key
public void Attach(Cord c) {
if (!ConflictTest(c)){
attached.Add(GetOtherHook(c), c);
}
}
// Remove a cord based on its "other" Hook
public void Detach(Cord c) {
attached.Remove(GetOtherHook(c));
}
// Determine whether current connections contain this Cord's "other" Hook
bool ConflictTest(Cord toAttach) {
return attached.ContainsKey(GetOtherHook(toAttach));
}
// Retrieves the "other" (non-current Hook) a cord is attached to
private Hook GetOtherHook(Cord c) {
return c.end != this ? c.end : c.start;
}
}
Hope this helps! Let me know if you have any questions.

Categories

Resources