I am working on a game, that has a keypad puzzle. A specific key combination lights up one by one, which the player must repeat to solve that puzzle. I am going to let the player know what the combination is by activating and deactivating some GameObjects systematically, one by one. As it suggests, there is some time delay between the deactivation of one GameObject and the activation of another. The problem is, in my code, all the GameObjects activate simultaneously instead of one by one, after a delay.
Here is the code:
public string Generate(int length, float delay)
{
// Variables for logic
string combination = "";
int prev = -1; int current = 0;
int rnd = 0;
for (int i = 0; i < length; i++)
{
rnd = Random.Range(0, BUTTONS);
while (rnd == prev)
{
rnd = Random.Range(0, BUTTONS);
}
prev = current;
current = rnd;
combination += current.ToString();
// Activation and Deactivation
StartCoroutine(GenerateDelay(delay, current));
}
return combination;
}
IEnumerator GenerateDelay(float delay, int index)
{
ButtonClicks[index].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[index].SetActive(false);
}
The loop counter specifies the length of the combination. I believe there is something wrong with the Coroutine I made? Since all the objects activate simultaneously.
Here is the result in the game as well:
We can see, only one button should turn green at a time, but all of them do in this case. Any solutions?
You start all your Coroutines parallel so things happen at the same time.
StartCoroutine does not delay the method which calls it (unless it is yielded as well.
You would need to run the entire loop within a Coroutine in order to delay it as well.
You could simply split up the creation of the combination and while you already return it you start the visualization in the background in parallel
public string Generate(int length, float delay, Action<string> onCombination)
{
// Variables for logic
var combination = List<int>();
var prev = -1;
for (var i = 0; i < length; i++)
{
int rnd;
do
{
rnd = Random.Range(0, BUTTONS);
}
while (rnd == prev);
prev = rnd;
combination.Add(rnd);
}
StartCorouine (ShowCombination(combination, delay));
return string.Join("", combination);
}
private IEnumerator ShowCombination (IEnumerable<int> combination, float delay)
{
foreach(var i in combination)
{
ButtonClicks[i].SetActive(true);
yield return new WaitForSeconds(delay);
ButtonClicks[i].SetActive(false);
}
}
something alot easier then a coroutine is invoking a function, basically create a function to activate/deactivate the wanted object and whenever you want to call it do:
Invoke("FUNCTIONNAME", TIME);
and it will run the function specified after the TIME.
Related
Suppose, a polymer has N monomers in its chain. I want to simulate its movement using the bead-spring model. However, there was no periodic boundary condition applied. Coz, points were generated such that they never cross the boundary.
So, I wrote the following program.
Polymer chain simulation with Monte Carlo method
I am using 1 million steps. The energy is not fluctuating as expected. After several thousand steps the curve goes totally flat.
The X-axis is steps. Y-axis is total energy.
Can anyone check the source code and tell me what I should change?
N.B. I am especially concerned with the function that calculates the total energy of the polymer.
Probably, the algorithm is incorrect.
public double GetTotalPotential()
{
double totalBeadPotential = 0.0;
double totalSpringPotential = 0.0;
// calculate total bead-energy
for (int i = 0; i < beadsList.Count; i++)
{
Bead item_i = beadsList[i];
Bead item_i_plus_1 = null;
try
{
item_i_plus_1 = beadsList[i + 1];
if (i != beadsList.Count - 1)
{
// calculate total spring energy.
totalSpringPotential += item_i.GetHarmonicPotential(item_i_plus_1);
}
}
catch { }
for (int j = 0; j < beadsList.Count; j++)
{
if (i != j)
{
Bead item_j = beadsList[j];
totalBeadPotential += item_i.GetPairPotential(item_j);
//Console.Write(totalBeadPotential + "\n");
//Thread.Sleep(100);
}
}
}
return totalBeadPotential + totalSpringPotential;
}
Problem of this application is that simulations (Simulation.SimulateMotion) are run in separate thread in parallel to the draw timer (SimulationGuiForm.timer1_Tick) and share the same state (polymerChain) without any sync/signaling, so some mutations of polymerChain are skipped completely (not drawn) and when the simulation is finished (far before the finish of the drawing) the timer1_Tick will redraw the same polymerChain. You can easily check that by adding counter to Simulation and increasing it in the SimulateMotion:
public class Simulation
{
public static int Simulations = 0; // counter
public static void SimulateMotion(PolymerChain polymerChain, int iterations)
{
Random random = new Random();
for (int i = 0; i < iterations; i++)
{
Simulations++; // bump the counter
// rest of the code
// ...
And checking it in timer1_Tick:
private void timer1_Tick(object sender, EventArgs e)
{
// ...
// previous code
if (Simulation.Simulations == totalIterations)
{
// breakpoint or Console.Writeline() ...
// will be hit as soon as "the curve goes totally flat"
}
DrawZGraph();
}
You need to rewrite your application in such way that SimulateMotion either stores iterations in some collection which is consumed by timer1_Tick (basically implementing producer-consumer pattern, for example you can try using BlockingCollection, like I do in the pull request) or performs it's actions only when the current state is rendered.
I have 4 colors. I want to make it so that the player can't be the same color 2 times in a row. When a player collides with an object, the RandomColor() is called. So this function is called many times during the game and sometimes the player does not change his color.
using UnityEngine;
public class ColorManager : MonoBehaviour {
public SpriteRenderer player;
public string playerColor;
public Color[] colors = new Color[4];
private void Awake()
{
RandomColor();
}
public void RandomColor()
{
int index = Random.Range(0, 4);
switch (index)
{
case 0:
player.color = colors[0]; //colors[0] is orange
playerColor = "orange";
break;
case 1:
player.color = colors[1]; //colors[1] is pink
playerColor = "pink";
break;
case 2:
player.color = colors[2]; //colors[2] is blue
playerColor = "blue";
break;
case 3:
player.color = colors[3]; //colors[3] is purple
playerColor = "purple";
break;
}
}
}
Tried using while loop, do while loop, but I'm obviously doing it wrong, since I receive the same color twice in a row sometimes. It would be great if anyone figures it out and explains how/why it works, because I spent a big chunk of time on the issue and I am very curious.
First, you need a function that can generate a random number with exclusion. Below is what I use for that:
int RandomWithExclusion(int min, int max, int exclusion)
{
int result = UnityEngine.Random.Range(min, max - 1);
return (result < exclusion) ? result : result + 1;
}
Each time you call it, you need to store the result in a global variable so that you will pass that to the exclusion parameter next time you call it again.
I modified the function so that you don't have to do that each time it is called. The new RandomWithExclusion function will do that for you.
int excludeLastRandNum;
bool firstRun = true;
int RandomWithExclusion(int min, int max)
{
int result;
//Don't exclude if this is first run.
if (firstRun)
{
//Generate normal random number
result = UnityEngine.Random.Range(min, max);
excludeLastRandNum = result;
firstRun = false;
return result;
}
//Not first run, exclude last random number with -1 on the max
result = UnityEngine.Random.Range(min, max - 1);
//Apply +1 to the result to cancel out that -1 depending on the if statement
result = (result < excludeLastRandNum) ? result : result + 1;
excludeLastRandNum = result;
return result;
}
Test:
void Update()
{
Debug.Log(RandomWithExclusion(0, 4));
}
The last number will never appear in the next function call.
For your specific solution, simply replace
int index = Random.Range(0, 4);
with
int index = RandomWithExclusion(0, 4);
What you need to do is add a judgement if the RandomColor returns a same color as prev one, just call it again, easy right?
This is using Unity3D. I have three coroutines: GetJSONFromSelectedSubreddit(), LoadMoreMemes(), and a function in a separate script that needs to be able to access the array of memes through the GetNewMemes() function (must return type Meme[]). LoadNewMemes() produces. The thing is, LoadMoreMemes() requires the json to work, so they have to run in the mentioned order. If you need the functions, here they are:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
StartCoroutine(GetJSONFromSelectedSubreddit());
StartCoroutine(LoadMoreMemes());
}
IEnumerator GetJSONFromSelectedSubreddit()
{
gettingJSON = true;
WWW requester = new WWW("https://www.reddit.com/r/" + selectedSub + "/new.json?sort=new&count=25&after=" + postIndex);
yield return requester;
subJSON = requester.text;
json = new JSONObject(subJSON);
gettingJSON = false;
}
IEnumerator LoadMoreMemes()
{
while (gettingJSON)
yield return new WaitForSeconds(0.1f);
for (int i = 0; i < memesAtATime; i++)
{
yield return StartCoroutine(GetUserPostKarma(json["data"]["children"][i]["data"]["author"].str));
string sourceURL = json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str;
sourceURL = sourceURL.Replace("&", "&");
yield return StartCoroutine(GrabImage(sourceURL));
Meme currentMeme = new Meme(
json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str,
authorPostKarma,
(int) json["data"]["children"][i]["data"]["score"].i,
json["data"]["children"][i]["data"]["permalink"].str,
json["data"]["children"][i]["data"]["title"].str,
currentBitmap
);
Debug.Log(currentMeme.cost);
memes[i] = currentMeme;
}
}
Here's the other script:
void Start ()
{
RedditCommunicator redditCommunicator = GetComponent<RedditCommunicator>();
redditCommunicator.GetNewMemes("me_irl", 1);
Meme[] memes = redditCommunicator.GetCurrentMemes();
Debug.Log(memes[0].currentScore);
redditCommunicator.SpawnOneMeme(memes[0]);
}
Each function works fine on its own, but they need to wait for each other to finish, as well as run in the correct order to work. I'd like the functions to stay separate so I can call them individually in the future. memes is a private variable, and the one I'd like to pass to the other script calling these functions. If you don't think I've tried my options Googling and solving this on my own, just believe me, I've done my best. Thanks for your help in advance. If you need more information, just ask me for it. The current state of this code is it returns memes to early, before the coroutines can finish, resulting in empty memes.
You can yield a Coroutine in an IEnumerator which will halt the progression of that Coroutine until that Coroutine is done. Like this:
void Start()
{
StartCoroutine(DoThings((text) => {
Debug.Log("Dothings told me: " + text);
}));
}
IEnumerator DoThings(Action<string>() callback)
{
yield return StartCoroutine(DoThisFirst());
callback("Returning a value mid-method!");
yield return StartCoroutine(ThenThis());
Debug.Log(3);
}
IEnumerator DoThisFirst()
{
yield return new WaitForSeconds(1);
Debug.Log(1);
}
IEnumerator ThenThis()
{
yield return new WaitForSeconds(1);
Debug.Log(2);
}
Problem is that GetJSONFromSelectedSubreddit and LoadNewMemes methods are called as two "parallel" coroutines in GetNewMemes method.
If you do not need to run a coroutine "asynchronously", you can just enumerate through it:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
var enumerator = GetJSONFromSelectedSubreddit();
while (enumerator.MoveNext());
enumerator = LoadNewMemes();
while (enumerator.MoveNext());
}
I need to create a class (or classes, if need be) that loads levels randomly every time the user clicks on the "Next Button" and once all levels have been loaded, we stop loading and close the application. I got the code set up but I am still not getting the result I am looking for which is:
User clicks on a button.
Load a random level
That levels gets stored in an array list
Once the user is done with that level he/she presses the "Load Next Level" button
Load the next random level
But first, we check if the random level is not the same as before.
If it's not, then we repeat steps 2-5, else we go to step 8
If the levels have all been visited then we quit the application
The problem I am having is that my game loads the same level every time I hit play and it doesn't go to the next scene after I am done with the current one. This is what I have so far:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class SceneManager : MonoBehaviour
{
public static bool userClickedNextButton; //This flag is raised by the other classes that have the GUI button logic
protected const int MAX = 2;
private ArrayList scenesWereAlreadyLoaded = new ArrayList();
void Update()
{
if (userClickedNextButton)
{
//by default the game starts at 0 so I want to be able to
//randomly call the next two scenes in my game. There will
//be more levels but for now I am just testing two
int sceneToLoad = Random.Range(1, 2);
if (!scenesWereAlreadyLoaded.Contains(sceneToLoad))
{
scenesWereAlreadyLoaded.Add(sceneToLoad);
Application.LoadLevel(sceneToLoad);
}
userClickedNextButton = false;
}
if (scenesWereAlreadyLoaded.Count > MAX) { Application.Quit(); }
}
}
You create a list with your level numbers, then remove the currently loaded level. You repeat that until the list is empty.
Also don't use ArrayList, it's very old and deprecated type, from the ages before .NET/Mono had Generic support. Better use a Generic List<T>, which is type safe and faster than ArrayList.
using UnityEngine;
using System.Collections;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
[ExecuteInEditMode]
public class SceneManager : MonoBehaviour
{
// not best idea to have it static
public static bool userClickedNextButton;
protected const int MAX = 50;
private List<int> scenes = new List<int>();
void Start() {
// Initialize the list with levels
scenes = new List<int>(Enumerable.Range(1,MAX)); // This creates a list with values from 1 to 50
}
void Update()
{
if (userClickedNextButton)
{
if(scenes.Count == 0) {
// No scenes left, quit
Application.Quit();
}
// Get a random index from the list of remaining level
int randomIndex = Random.Range(0, scenes.Count);
int level = scenes[randomIndex];
scenes.RemoveAt(randomIndex); // Removes the level from the list
Application.LoadLevel(level);
userClickedNextButton = false;
}
}
}
As per the Unity3D documentation (http://docs.unity3d.com/Documentation/ScriptReference/Random.Range.html), range returns an integer between min (included) and max (excluded), so, in your case, Random.Range(1,2) will always returns 1.
Try with this
int = sceneToLoad = Random.Range(1,3)
There is an easier way to do so. You can prepare an array with random unique numbers. When you want to load a new level just increment the index of the array.
Here's a code that can help:(Attach this script to an empty gameobject in your first scene)
using UnityEngine;
using System.Collections;
public class MoveToRandomScene : MonoBehaviour {
public Texture nextButtonTexture; // set this in the inspector
public static int[] arrScenes;
public static int index;
void Start () {
index = 0;
arrScenes = GenerateUniqueRandom (10, 0, 10); //assuming you have 10 levels starting from 0.
}
void OnGUI {
// Load the first element of the array
if (GUI.Button(new Rect (0,0,Screen.width/4,Screen.height/4),nextButtonTexture))
{
int level = arrScenes [0] ;
Application.LoadLevel (level);
}
}
//Generate unique numbers (levels) in an array
public int[] GenerateUniqueRandom(int amount, int min, int max)
{
int[] arr = new int[amount];
for (int i = 0; i < amount; i++)
{
bool done = false;
while (!done)
{
int num = Random.Range(min, max);
int j = 0;
for (j = 0; j < i; j++)
{
if (num == arr[j])
{
break;
}
}
if (j == i)
{
arr[i] = num;
done = true;
}
}
}
return arr;
}
}
For the other scenes you just need to create this script and attach it to an empty game object whenever you want to load a new random scene:
void OnGUI {
if (GUI.Button(new Rect (0,0,Screen.width/4,Screen.height/4),nextButtonTexture))
{
if (MoveToRandomScene.index == 9) {
// Load again your first level
Application.LoadLevel(0);
}
// else you continue loading random levels
else {
MoveToRandomScene.index++;
int level = MoveToRandomScene.arrScenes[MoveToRandomScene.index];
Application.LoadLevel(level);
}
}
}
I implemented code that would check whether the ufo's X position is greater than the width of the game screen. Then if it is, the ufo is dead.
I have also implemented code that generates a new number if the ufo is dead, in which case, when the right number is generated, then the saucer should fly across the screen.
I don't know why it isn't doing that. The saucer only flys across once, randomly.
The code for the saucer is below:
if (ufo.alive == true)
{
// also, we need to make it move
ufo.Xpos = ufo.Xpos + 1;
if (MissileFired != null)
{
// if you miss, and the ufo carries on, it will go forever.
//so
if (ufo.Xpos > 1000)
{
// kill the ufo if it goes off the screen.....
ufo.alive = false;
}
the code to generate the random number is:
if (ufo.alive == false)
{
Random random = new Random();
int randomNumber = random.Next(0, 100);
{
if (randomNumber == 1)
ufo.alive = true;
}
As you can see, I don't know why it doesn't generate the ufo again after it has flown across and off the screen.
Dont create a new random every cycle.
take out of your Update method and declare it as:
class Game1: Game {
public static readonly Random random = new Random(DateTime.Now.Millisecs);
....
}
....
if (ufo.alive == false) {
int randomNumber = Game1.random.Next(0, 100);
if (randomNumber == 1)
{
ufo.alive = true;
ufo.Xpos = 0;
}
}