C# lambda call in for loop references to last object - c#

I've researched this problem and I've tried fixing the code as instructed in a few questions before (such as this one) but it still won't work. Though I must say that all the answers I checked were from 2009-2010 so they might be obsolete.
This is the culprit code:
foreach(Entity player in players)
{
if(player.actions.Count > 0)
{
Entity temp = player;
player.isDoingAction = true;
Debug.Log(player.name + " started action");
player.actions.Dequeue().Execute(() => { temp.isDoingAction = false; Debug.Log(temp.name + " finished"); });
}
}
This prints the following:
Player1 started action
Player2 started action
Player2 finished
Player2 finished
When it should print:
Player1 started action
Player2 started action
Player1 finished
Player2 finished
Or something similar.
This code runs in a Unity coroutine function.
A bit larger snip from the code:
GameManager.cs
private IEnumerator RunTurn()
{
...
...
...
for(int i = 0; i < phases; i++)
{
//Assign action to each player
foreach(Entity player in players)
{
if(player.actions.Count > 0)
{
Entity temp = player;
player.isDoingAction = true;
Debug.Log(player.name + " started action");
player.actions.Dequeue().Execute(() => { temp.isDoingAction = false; Debug.Log(temp.name + " finished"); });
}
}
//Wait for each player to finish action
foreach(Entity player in players)
{
while(player.isDoingAction == true)
{
Debug.Log("Waiting for " + player.name);
yield return null;
}
}
}
...
...
...
}
Action.cs
public override void Execute(System.Action callback)
{
Move(callback);
}
private void Move(System.Action callback)
{
...
...
...
//Move target entity
target.MoveToPosition(newPosition, mSpeed, callback);
target.location = newLocation;
...
...
...
}
Entity.cs
public void MoveToPosition(Vector3 position, float speed, System.Action callback)
{
StartCoroutine(CoMoveToPosition(position, speed, callback));
}
//Move to position
private IEnumerator CoMoveToPosition(Vector3 position, float speed, System.Action callback)
{
while(position != transform.position)
{
transform.position = Vector3.MoveTowards(transform.position, position, speed * Time.deltaTime);
yield return null;
}
//Move finished so use callback
callback();
}
Solution
It turns out there is a bug in Unity with coroutines and anonymous lambda callbacks. Check this link for more.
Working piece of code:
foreach(Entity player in players)
{
if(player.actions.Count > 0)
{
player.isDoingAction = true;
Debug.Log(player.name + " started action");
System.Func<Entity, System.Action> action = new System.Func<Entity,System.Action>(p =>
new System.Action(() => { p.isDoingAction = false; Debug.Log(p.name + " finished"); }));
player.actions.Dequeue().Execute(action(player));
}
}

You can capture the value the following way:
var action = new Func<Entity, Action>(p =>
new Action(() => { p.isDoingAction = false; Debug.Log(p.name + " finished")); })(player);
player.actions.Dequeue().Execute(action);

Try
player.actions.Dequeue().Execute(temp =>
{ temp.isDoingAction = false;
Debug.Log(temp.name + " finished");
});

Related

delay in update method in Unity

I am making a badminton simulator in unity, where the opponent is a set of video clips. I am trying to add some delay to my update method so theres some time between two clips of the opponent. However this delay only applies to the video clips and not the shuttle that arrives from behind the video.
My Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class Video_Player : MonoBehaviour
{
public VideoPlayer activeCam, otherCam;
public List<VideoClip> playlist = new List<VideoClip>();
public GameObject shuttle;
VideoClip nextClip;
private bool Timer;
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
Timer=false;
// setup an event to automatically call SwitchCams() when we finish playing
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
}
void Update()
{
if (playlist.Count == 0)
return;
if(!Timer)
{
StartCoroutine(CountDown(5));
if (nextClip == null && activeCam.time >= activeCam.clip.length - 0.1)
{
PrepareNextPlaylistClip();
shuttle.SetActive(false);
}
if(activeCam.time >= 1.0f && activeCam.time <= 2.95f)
{
Debug.Log("start:"+activeCam.time);
shuttle.SetActive(true);
}
else
//if(activeCam.time >= 2.95f || activeCam.time <= 1.0f)
{
Debug.Log("end:"+activeCam.time);
shuttle.SetActive(false);
}
}
}
void SwitchCams(VideoPlayer thisCam)
{
activeCam = otherCam;
otherCam = thisCam;
activeCam.targetCameraAlpha = 1f;
otherCam.targetCameraAlpha = 0f;
Debug.Log("new clip: " + nextClip.name);
nextClip = null;
}
void PrepareNextPlaylistClip()
{
nextClip = playlist[0];
otherCam.clip = nextClip;
otherCam.Play();
playlist.RemoveAt(0);
}
//delay couroutine
IEnumerator CountDown(float delay)
{
Timer = true;
yield return new WaitForSeconds(delay);
Timer= false;
}
// randomize the video playlist
public static void Shuffle<T>(IList<T> playlist)
{
int n = playlist.Count;
while (n > 1)
{
n--;
int k = Random.Range(0, n);
T value = playlist[k];
playlist[k] = playlist[n];
playlist[n] = value;
}
}
}
Forgive me if I'm misunderstanding your code but rather than having it all in Update() couldn't you just have it in an IEnumerator like this?
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
//Run the function on start
StartCoroutine(Function());
}
IEnumerator Function()
{
while(true)
{
if(playlist.Count == 0)
{
//If you have no clips left exit out of the loop
break;
}
if(nextClip == null)
{
//If you have clips left load the next clip
shuttle.SetActive(false);
PrepareNextPlaylistClip();
}
yield return new WaitForSeconds(1); //This is your delay
//Execute the code you want to run after the delay here
}
}

Trying to increment a variable in a Coroutine on pressing a UI button

I am making a simple quiz game for children. In which i am extracting questions from a json file. Each question will have a 10 second timer and if the button is pressed next question is displayed. the problem i am facing is that i can't seem to figure out a way to control "yeild return waitforseconds()". i have tried multiple approaches but none seems to work, it either skips the next question too fast if i try to change the value in waitforseconds(), or it gets stuck on the same question if try incrementing the questionCounter variable. I have been searching for a while a but i can't find anything. i have a deadline of tomorrow, i could really use some help.
//this is my question changing method
IEnumerator QuestionRoutin(Course c, int n)
{
while (qCounter < 3)
{
print(qCounter);
r = UnityEngine.Random.Range(0, 6);
//Question.text = c.Ques_Data[r].Quesion;
//RandomButton(c, r);
//amil = c.Ques_Data[r].Answer;
if(checkq.Count==0)
{
checkq.Add(r);
Question.text = c.Ques_Data[r].Quesion;
RandomButton(c, r);
amil = c.Ques_Data[r].Answer;
}
else if (checkq.Contains(r)==false)
{
checkq.Add(r);
Question.text = c.Ques_Data[r].Quesion;
RandomButton(c, r);
amil = c.Ques_Data[r].Answer;
}
else
{
continue;
}
if (f == true) //f is a global bool
{
f = false;
qCounter++;
//yield return new WaitForSeconds(1.0f);
}
else
{
yield return new WaitForSeconds(10);
qCounter++;
}
}
print("questions are finished");
}
//this a function attach to my buttons
public void CheckAnswer(Text button_txt)
{
//time_1 = 10.0f;
if (button_txt.text == amil)
{
flag = true;
Animator anim = Correct_Panel.GetComponentInChildren<Animator>();
if (anim != null)
{
Correct_Panel.SetActive(true);
bool isRightAnsPressed = anim.GetBool("isRightAnsPressed");
anim.SetBool("isRightAnsPressed", !isRightAnsPressed);
Invoke("disableCorrectPanel", 1.0f);
score_value = score_value + 10;
score.text = "Score: " + score_value;
//have tried all these 3 things
//StartQuestionRoutine(c1, n,true);
//qCounter++;
// flag = true;
}
}
now ik you,ll say coroutine will restart again if i call it in the button, but nothing else seems to work. how else will ik if my button is pressed or not? Please i could really use some help.
have you try to stop coroutine before to relaunch it:
private IEnumerator coroutine;
:
:
if(coroutine != null) StopCoroutine(coroutine);
coroutine = StartCoroutine(QuestionRoutin(.....));

Turn Based Game in unity C# in android

Good Day! I have this code but I have an error, for example (I set two players me, and 1 computer). I take the first turn, and the dice respawn with a value of 4 (just an example), the game piece then move from 1st to 4th tile when I touch the screen, when computer turns, it also move from 1st to 4th tile (because I set the result to 4 just an example). Now its my turn again, the dice never respawn and it doesn't wait to touch the screen if (Input.GetMouseButtonDown(0)) and move again by 4...
public class singlePlay : MonoBehaviour {
//Player
public GameObject[] playerprefab;
//Player Clone
public GameObject[] playerprefabC;
//Game Cards and Dice
public GameObject[] situationCard;
public GameObject dice;
int diceresult;
//Game Cards and Dice clone
public GameObject diceclone;
public int currentPlayer;
public int compPlayer;
public int playerTurn;
public string compPlayerstring;
public string playerTurnstring;
//GUI Boolean
bool play = false;
//Game Boolean
bool pieces = false;
bool giveturn = false;
bool myturn = false;
bool diceSpawn = false;
bool moving = false;
bool routine = false;
bool checking = false;
bool compturn = false;
//icon1
public GameObject[] icon;
//population
int[] population = new int[3];
//Tile
public GameObject[] Tile;
int[] playerTile = new int[3]; //current location
int[] playerTileUp = new int [3]; // updated location after dice roll
bool endTurn = false;
void Update ()
{
if (giveturn == true) {
int h = 0;
Giveturn(h);
giveturn = false;
}
if (play == true) {
if (pieces == true){
SpawnPlayer();
pieces = false;
}
if (myturn == true){
compturn = false;
if(diceSpawn == true) {
dice.transform.position = new Vector3(0,0,-1);
diceclone = Instantiate(dice, dice.transform.position, Quaternion.identity) as GameObject;
diceSpawn = false;
}
if (Input.GetMouseButtonDown(0))
{
Debug.Log("click");
diceresult = 4;
Destroy(diceclone);
moving = true;
Updateposition(diceresult);
}
}
else
{
Debug.Log("comp");
myturn = false;
diceresult = 4;
moving = true;
Updateposition(diceresult);
}
}
}
void Giveturn(int k)
{
Debug.Log("" + k);
currentPlayer = k;
if (k == playerTurn) {
Debug.Log("Yes");
compturn = false;
myturn = true;
diceSpawn = true;
moving = false;
}
else
{
Debug.Log("No");
compturn = true;
myturn = false;
moving = false;
}
}
void Updateposition(int diceresult)
{
if (moving == true) {
playerTileUp[currentPlayer] = playerTile[currentPlayer] + diceresult;
Debug.Log("" + playerTileUp[currentPlayer]+ " " +currentPlayer);
routine = true;
StartCoroutine(MyMethod());
}
moving = false;
}
IEnumerator MyMethod()
{
if (routine == true) {
if (myturn == true) {
compturn = false;
}
else
{
myturn = false;
}
int f = playerTile[currentPlayer] + 1;
Debug.Log(" " + currentPlayer );
while (f <= playerTileUp[currentPlayer]) {
Debug.Log("waiting");
yield return new WaitForSeconds(1);
Debug.Log(" " + Tile[f]);
playerprefabC[currentPlayer].transform.position = Tile[f].transform.position;
Debug.Log(" " + currentPlayer);
f++;
}
checking = true;
TrapCheck();
}
routine = false;
}
void TrapCheck()
{
if (checking == true) {
if (playerTileUp[currentPlayer] == 8) {
Debug.Log("Trap spawning");
Instantiate(situationCard[0], situationCard[0].transform.position, Quaternion.identity);
population[currentPlayer] = population[currentPlayer] -1;
}
playerTile[currentPlayer] = playerTileUp[currentPlayer];
Endturn();
myturn = false;
compturn = false;
checking = false;
}
}
void Endturn()
{
currentPlayer++;
Debug.Log(" " + currentPlayer);
if (currentPlayer > compPlayer) {
currentPlayer = 0;
}
Giveturn(currentPlayer);
}
}
There are few things that I could see wrong there already. First while the coroutine is running, it seems you are not preventing the update from running since play remains true. In TrapCheck, you call EndTurn which call GiveTurn and sets myTurn (true) and compTurn (false) booleans. But those two are reset in TrapCheck, myTurn is set back to false. You need to rethink the logic of your class.
A solution would be to use delegate. This would remove many of your boolean that you set and reset. Here is a basic idea:
Action currentUpdate;
bool playerTurn = true;
void Start(){
SetTurn();
}
void Update(){
if(currentUpdate != null)currentUpdate();
}
void SetTurn(){
// Prepare initial setting for moving
if(playerTurn == true){ currentUpdate = PlayerTurn; }
else{ currentUpdate = CompTurn; }
playerTurn = !playerTurn;
}
void PlayerTurn(){
// Check input
// Get dice value
currentUpdate = Move;
}
void CompTurn(){
// Get dice value
currentUpdate = Move;
}
void Move(){
if(position != target){
}else{
SetTurn();
}
}
This is fairly simplified but once you get the thing about delegate (maybe you already know), this will make it all so much more flexible.

Delete duplicate gameobject on restart

I have the following code which creates a main menu :
public class EscapeGUI : MonoBehaviour {
public GUISkin MySkin;
public bool pauseToggle = false;
public bool showGUI = false;
public bool levelLoaded = false;
static string filePath;
private List<string> list = new List<string>();
private string line;
void Update() {
if (!levelLoaded) {
showGUI = true;
Time.timeScale = 0;
Debug.Log ("NO LEVEL LOADED");
} else {
if (Input.GetKeyDown (KeyCode.Escape)) {
pauseToggle = !pauseToggle;
if (pauseToggle) {
Time.timeScale = 0;
showGUI = true;
} else {
Time.timeScale = 1;
showGUI = false;
}
}
Debug.Log("FILEPATH IS " + filePath);
Debug.Log("LEVEL IS LOADED");
}
}
void OnGUI() {
if (showGUI) {
GUI.skin = MySkin;
GUILayout.BeginArea (new Rect (Screen.width / 4, Screen.height / 4, 400, Screen.width / 2));
GUILayout.BeginHorizontal ();
if (levelLoaded){
if (GUILayout.Button ("Resume")) {
Time.timeScale = 1;
showGUI = false;
pauseToggle = false;
}
}
GUILayout.EndHorizontal ();
GUILayout.BeginHorizontal ();
if (levelLoaded){
if (GUILayout.Button ("Restart")) {
Application.LoadLevel (0);
showGUI = false;
pauseToggle = false;
Time.timeScale = 1;
levelLoaded = true;
Debug.Log ("Game is restarted with this level: " + filePath);
}
}
GUILayout.EndHorizontal ();
GUILayout.BeginHorizontal ();
if (GUILayout.Button ("Load")) {
filePath = EditorUtility.OpenFilePanel("Select JSON file",Application.streamingAssetsPath,"txt");
Debug.Log ("Game is loaded with this level: " + filePath);
StreamReader reader = new StreamReader(filePath);
while ((line = reader.ReadLine()) != null)
{
list.Add(line);
//Debug.Log(line);
}
//Do this as soon as the JSON is checked and found to be OK.
GameObject.Find("Preserved Variables").SendMessage("setFilePath", filePath);
Time.timeScale = 1;
levelLoaded = true;
showGUI = false;
pauseToggle = false;
}
GUILayout.EndHorizontal ();
GUILayout.BeginHorizontal ();
if (GUILayout.Button ("Quit")) {
Application.Quit();
}
GUILayout.EndHorizontal ();
GUILayout.EndArea ();
}
}
}
A game is created by importing a JSON file (in the code its just txt for testing, i have not implemented the JSON part yet), in this JSON file the game flow will be described.
So basically when a player clicks load, I want the game to be playable and then when he clicks restart because of the 'Application.LoadLevel (0);' code everythings gets deleted, thus I don't know anymore what the current file (level) it was.
So I created an empty gameobject called 'Preserved Variables' and I put a C# script component in this, the script looks like this:
public class PreservedVariables : MonoBehaviour {
public string filePath;
public static PreservedVariables instance;
void Awake() {
if(instance){
Destroy(this);
} else {
DontDestroyOnLoad(this);
instance = this;
}
}
void setFilePath(string fp) {
filePath = fp;
}
string getFilePath() {
return filePath;
}
}
Now the problem is that when I run this and ingame i click 'load', i select my file and so far everything is good. But then when I click "restart" I get the following 2 problems:
1) the main menu shows me only the 'load' and 'quit' as it is only supposed to show when there is no game loaded (so this only happens at startup), however i think this will be fixed by fixing nr.2 (see below)
2) As soon as i click restart after loading a file, the gameobject 'Preserved Variables' is made again but this time it does not have a script component attached. (the original gameobject has its FilePath updated correctly though).
If I may I would like to ask an extra small question to this, how do I exactly retrieve the filepath variable again from the empty gameobject 'Preserved Variables' so that I can use it in my restart code?

Make GUI.Box stay for 3 seconds

I'm working on a project in Unity using C# scripting. The GUI.Box will appear at the top the screen. The box will disappear when the player leaves the spot. How can I make the box stay there for an additional 3 seconds after the player leaves the designated spot?
Danpe's code corrected (working code):
bool shown = false;
void OnGUI () {
if (car.transform.position.y>=43 && car.transform.position.y<=44)
{
shown = true;
}
else if (shown)
{
StartCoroutine(DisapearBoxAfter(3.0f));
}
if(shown)
{
GUI.Box(new Rect((Screen.width/2)-200,0,400,30) , "King of the hill");
}
}
IEnumerator DisapearBoxAfter(float waitTime) {
// suspend execution for waitTime seconds
yield return new WaitForSeconds(waitTime);
shown = false;
}
void Update () {
OnGUI ();
}
bool shown = false;
void OnGUI () {
if (car.transform.position.y>=43 && car.transform.position.y<=44)
{
shown = true;
}
else if (shown)
{
StartCoroutine(DisapearBoxAfter(3.0));
}
if(shown) {
GUI.Box(new Rect((Screen.width/2)-200,0,400,30) , "King of the hill");
}
}
IEnumerator DisapearBoxAfter(float waitTime) {
// suspend execution for waitTime seconds
return yield WaitForSeconds (waitTime);
shown = false;
}
void Update () {
OnGUI ();
}
function Start ()
{
ShowBox ();
}
function ShowBox ()
{
// show label
show = true;
// cancel invoking method if already set to call after 3 seconds
CancelInvoke("HideBox");
// will call HideBox () after 3 sec
Invoke ("HideBox", 3.0F);
}
function HideBox ()
{
// dont show label
show = false;
}
function Update ()
{
if (car.transform.position.y>=43 && car.transform.position.y<=44)
{
ShowBox ();
}
}
function OnGUI ()
{
if(shown)
{
GUI.Box(new Rect((Screen.width/2)-200,0,400,30) , "King of the hill");
}
}

Categories

Resources