I'm trying to play SoundEffectInstances of loaded .wav files in my game, but I hear no sound whatsoever.
I have a class "ETSound"; for which each object holds one sound. So one ETSound object might hold the 'menu open' sound, and another might hold the 'tank firing' sound... Etc.
Anyway, the ETSound constructor looks like this:
public ETSound(SoundEffect se, float volume, float pitch, bool looped, int soundPriority) {
soundTemplate = se;
this.volume = volume;
this.pitch = pitch;
this.looped = looped;
if (soundPriority > 0) {
if (soundPriority > 64) soundPriority = 64;
instanceArray = new SoundEffectInstance[soundPriority];
nextInstanceIndex = 0;
for (int i = 0; i < soundPriority; ++i) {
SoundEffectInstance sei = soundTemplate.CreateInstance();
sei.Volume = volume;
sei.Pitch = pitch;
instanceArray[i] = sei;
}
}
}
This basically sets up some parameters, and creates an array of sound effect instances according to the supplied SoundEffect.
Then, I'm calling the Play() function of ETSound:
public void Play() {
if (instanceArray[nextInstanceIndex].State != SoundState.Stopped) instanceArray[nextInstanceIndex].Stop();
instanceArray[nextInstanceIndex].Play();
if (++nextInstanceIndex >= instanceArray.Length) nextInstanceIndex = 0;
}
However, nothing happens. I hear nothing.
Can anyone tell me what's going wrong? Thanks.
Sorry, everyone... Turns out the .wav file I was using to test was corrupt... A tough bug to find, but I got it. Thanks anyway.
Related
Update: This issue may be caused by binaryformatter's issues with editing data in existing fields. A comment that I can no longer find described alternate methods which I am currently implementing. Will update after these methods are attempted if it is a solution to the problem.
I should start by saying I am a student so please go easy on me, I got reported when I first started coming here and hope to keep learning. I an new to unity, my C# is decent, but full of gaps since my schooling was rather terrible. I have watched hundreds of hours of unity tutorials and am studying the new concepts I learn for 4 hours every night after I get out of work, so if you see something just let me know.
This is actually a problem I have had for a while, but thought I fixed months ago. I was attempting to save games for the first time, and read into binary formatting and such to save. I had problems getting it up, but I I managed to get it to save and pull properly from a file. I verify that the data going into the file is correct, and the data coming out is correct, and even made the data private with a control function so nothing will access and change it without jumping through my debug. And yet after I leave the scope where I define the data it changes, without anything accessing my update function.
To break it down I have a class called PlayerType that stores all player information including my scenemanager, and it serializes this and saves to a file as a list. I create a for loop using the current length of the loaded list using an instance of the saveload class (this is what holds the list of save games and the access to the file) and it loops through instantiating my buttons in the order. Slot 1 will click to save game 1 and so to speak. The issue I am having is clicking slot 1 clicks slot 16, so does slot 2. In face, it seems practically random which buttons go to which slot. I should say here I am not sure whether it is actually going to the wrong slot, or simply renaming the player names wrong, but either way it does not appear this should be the case.
Here is my load function
public void Load() //Loads file from .gd file after checking if exists, then deserializes it back into a list
{
accessDataPath(false);
Debug.Log("Size of Save File" + listSize);
for (int i = 0; i < listSize; i++)
{
Debug.Log("First Loop " + SavedGames[i].returnName());
}
foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
{
Debug.Log("Second Loop" + player.returnName());
}
}
and here is my accessDataPath function
public void accessDataPath(bool isSave)
{
//This stucture checks if file exists or not, then checks if saving or loading for 4 outcomes
if (isSave && SavedGames == null)//if saving and save file to update has nothing in it
{
Debug.Log("Error attempted to save null list in SaveLoad.accessDataPath!");
}
if (File.Exists(Application.persistentDataPath + "/SavedGames.gd"))//if the file exists
{
if (isSave)//if you are saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
bf.Serialize(file, SavedGames);
file.Close();
}
else //if you are not saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
SavedGames = (List<PlayerType>)bf.Deserialize(file);
file.Close();
listSize = SavedGames.Count;
}
}
else//if the file does not exist
{
if (isSave)//if you are saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/SavedGames.gd");
bf.Serialize(file, SavedGames);
file.Close();
}
else//if you are not saving
Debug.Log("Error Loading File. Does not Exist!");//Display Later that file does not exist to user
}
updateNames();
}//End accessDataPath
here is the function that updates the names
public void updateNames()
{
for (int i = 0; i < listSize; i++)
{
SavedGames[i].updateName("Player " + i);
Debug.Log("Bump " + i);
}
}
As you can see I verify if it exists, then check if I am saving or loading. The save function calls with a true and SavedGames is the list that is pulled from the file. Now After I run the name change I can check to see if it worked, and it does. Running the check here they all come back as the proper name, however by the time it gets back to my load function and runs my first loop they are wrong, it never leaves this section of code but as soon as it exits the scope they change to be almost random.
Now I had this problem for a while, but thought it had to do with my file maybe having corrupt data, so I deleted the save game and created new ones to test. The numbers changed, but were still not right. This tells me that somehow the file is effecting the names even though I rename them almost immediately after they come out. I am not used to loading or saving to files so I am not sure where to start here.
I will post more of my code below in case some of it stands out as being blatantly wrong, I also appreciate advice on structure as I took college classes on game programming but they were terrible and filled with gaps even if you don't spot the answer to my problem.
First I have my menu system setup to be toggled on or off, called by onclick events.
public class SaveMenu : MonoBehaviour {
public GameObject menu;
bool isActive;
bool isSave;
PopulateSave playerSaves;
public static SaveMenu instance;
void Awake()
{
isSave = false;
instance = this;
menu = GameObject.Find ("SaveList");
menu.SetActive (false);
isActive = false;
playerSaves = menu.GetComponentInChildren<PopulateSave> ();
}
public bool getActive()
{
return isActive;
}
public void toggleMenu(bool saveLoad)
{
if (isActive)
{
menu.SetActive (false);
isActive = false;
} else
{
menu.SetActive (true);
isActive = true;
}
bool isSave = saveLoad;
menu.transform.Find("Scroll View").transform.Find("Viewport").transform.Find("Content").transform.Find("NewSave").gameObject.SetActive(isSave);
Debug.Log(isSave);
}
public void updateSave()
{
playerSaves.startList();//Seems redundant but is used to make this access public with limited use
}
public bool getSave()
{
return isSave;
}
}
I call populatesaves as a child after everything is created because I had issues with it not existing when I tried to bring it in via the inspector. PopulateSaves is where most of my functional code is.
public class PopulateSave : MonoBehaviour{
public Button NewSave;
public Button OldSaves; // This is our prefab object that will be exposed in the inspector
void Awake()
{
GameManager.instance.saveStorage.Load();
Populate();
}
void Update()
{
}
void Populate()
{
Button newObj = Instantiate(NewSave, transform); // Create GameObject instance
newObj.name = "NewSave";
startList();
}
public void startList()
{
clearList();
for (int i = 1; i < GameManager.instance.saveStorage.returnCount() + 1; i++)
{
createButton(i);
}
Debug.Log("Done creating");
}
public void updateList(PlayerType newSave)
{
Button newObj;
newObj = (Button)Instantiate(OldSaves, transform);
//GameManager.instance.saveStorage.Save(i);
}
public void clearList()
{
GameObject[] gameObjects;
gameObjects = GameObject.FindGameObjectsWithTag("SaveSlot");
for (var i = 0; i < gameObjects.Length; i++)
Destroy(gameObjects[i]);
Debug.Log("Done Destroying");
}
public void ButtonClicked(int slot)
{
if (SaveMenu.instance.getSave())
{
Debug.Log("Save");
GameManager.instance.saveStorage.Save(slot);
}
else
{
Debug.Log("load");
GameManager.instance.currentPlayer.newPlayer(slot);
}
}
public void createButton(int i)
{
// Create new instances of our prefab until we've created as many as is in list
Button newObj = (Button)Instantiate(OldSaves, transform);
//increment slot names
newObj.name = "Slot " + i;
newObj.GetComponentInChildren<Text>().text = "Slot " + i;
newObj.onClick.AddListener(() => ButtonClicked(i));
}
}
Now I also have never written a listener with a script like I have here, could that section be assigning the wrong number to them? I checked to make sure all my indexes had the right numbers, if not being 1 off. One of my loops might use a setup that is 1 off but I am more worried about getting this off the ground and fixing the specifics at this point.
Here is my player storage
[System.Serializable]
public class PlayerType {
string playerName;
SceneManagement currentScene;
public PlayerType()
{
playerName = "Starting Name";
}
public void updateName(string name)
{
//Debug.Log("New Name: " + name);
playerName = name;
}
public void updateScene(SceneManagement newScene)
{
currentScene = newScene;
}
public string returnName()
{
return playerName;
}
public SceneManagement returnScene()
{
return currentScene;
}
public void newPlayer(int slotSave)
{
//Tmp player has wrong name from this point, initiated wrong?
//Debug.Log("New Name to update" + tmpPlayer.returnName());
this.updateName(GameManager.instance.saveStorage.returnSaves(slotSave).returnName());
this.updateScene(GameManager.instance.saveStorage.returnSaves(slotSave).returnScene());
}
}
Update:
Bump correctly goes through displaying 0-16
then Size of Save File displays 17 total saves
First loop then starts by outputting 'First Loop Player 15' a total of 16 times
Then it displays 16 so the last one is correct, though one off I guess.
Second loop does the same as first, unsurprisingly.
I left the call to updateNames in but commented out the lines and ran it including taking out bump.
It starts with 17 saves again and the 16 time iteration of player 15, however this time around the last one displays 'Temp Name' which I only define once at the beginning of my sceneManagement script for the current player, it should have never been saved, and even if it had been should have been overwritten by my name loop, at least that was my intent.
SceneManagement
[System.Serializable]
public class SceneManagement : MonoBehaviour {
DialogueManager dialogueManager;
PlayerType currentPlayer;
bool isSave;
bool isActive;
string sceneName;
int lineCount;
void Start()
{
isSave = false;
//loads player object for the current save
currentPlayer = new PlayerType();
currentPlayer.updateName ("Temp Name");
//This loads the prologue from the DB and sets the dialoguemanager up, defaults to prologue for now but can be updated to another scene later
isActive = true;
sceneName = "Prologue";
string conn = "URI=file:" + Application.dataPath + "/Text/Game.sqlite3";
List<string> sceneLines = new List<string>();
List<string> sceneCharacters = new List<string>();
int tmpInt;
string tmpString = "NOTHING";
int count = 0;
lineCount = 1;
IDbConnection dbConn;
dbConn = (IDbConnection)new SqliteConnection (conn);
dbConn.Open (); //Open database connection
IDbCommand dbCmd = dbConn.CreateCommand();
string sqlQuery = "SELECT Line, Flags, Character, Image, Text, Color FROM Prologue";
dbCmd.CommandText = sqlQuery;
IDataReader reader = dbCmd.ExecuteReader ();
while (reader.Read ()) {
tmpInt = reader.GetInt32 (0);
tmpString = reader.GetString (1);
sceneCharacters.Add(reader.GetString (2));
tmpInt = reader.GetInt32 (3);
sceneLines.Add(reader.GetString (4));
tmpString = reader.GetString (5);
//Debug.Log (tmpString);
//Debug.Log (count);
count++;
}
reader.Close ();
reader = null;
dbCmd.Dispose ();
dbCmd = null;
dbConn.Close ();
dbConn = null;
dialogueManager = new DialogueManager(sceneCharacters, sceneLines, lineCount);
}
//These are the returnvalues that might be used, may or may not be kept depending on future use.
public string getSceneName()
{
return sceneName;
}
public int getLineCount()
{
return lineCount;
}
public bool getSave()
{
return isSave;
}
//These return the lines for displaymanager, preventing it from directly interacting with dialoguemanager
public string getNextLine()
{
return dialogueManager.getNextLine();
}
public string getNextCharacter()
{
return dialogueManager.getNextCharacter();
}
//This function sets up visibility and is only accessed to properly display the screen after the scene has been started.
public void startScene ()
{
GameManager.instance.screenDisplay.changeVisible(true);
}
void Update()
{
if (Input.GetKeyDown ("space") & isActive) {
dialogueManager.incrementCurrentLine ();
GameManager.instance.screenDisplay.changeText(dialogueManager.getNextLine ());
GameManager.instance.screenDisplay.changeCharacter(dialogueManager.getNextCharacter ());
}
if (Input.GetKeyDown ("escape")) {
if (!PauseMenu.instance.getActive () && !SaveMenu.instance.getActive ())
{
PauseMenu.instance.toggleMenu ();
}
else if (SaveMenu.instance.getActive())
{
SaveMenu.instance.toggleMenu (true);
PauseMenu.instance.toggleMenu ();
}
else if (PauseMenu.instance.getActive())
{
PauseMenu.instance.toggleMenu ();
}
}
}
public void initialScreen()
{
SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnLevelFinishedLoading;
}
void OnLevelFinishedLoading (Scene scene, LoadSceneMode mode)
{
GameManager.instance.screenDisplay.initialScreen(dialogueManager.getNextLine(), dialogueManager.getNextCharacter(), null, null,
null, null, null, null);
}
public PlayerType returnPlayer()
{
return currentPlayer;
}
}
while im at it here is my GameManager too, though I havent messed with it a ton. Mostly using it as a DontDestroyOnLoad thing to hold everything else at this point.
public class GameManager : MonoBehaviour{
public static GameManager instance;
public DisplayScreen screenDisplay;
public SceneManagement managerScene;
public SaveLoad saveStorage;
public PlayerType currentPlayer;
//leave out until load data is setup
//PlayerType currentPlayer;
void Awake()
{
instance = this;
DontDestroyOnLoad (transform.gameObject);
managerScene = gameObject.AddComponent(typeof(SceneManagement)) as SceneManagement;
screenDisplay = gameObject.AddComponent (typeof(DisplayScreen)) as DisplayScreen;
saveStorage = gameObject.AddComponent (typeof(SaveLoad)) as SaveLoad;
//initialize player and sceneManager here, but only load through main menu options
//of new game or load game
}
void update()
{
}
}
I have continued troubleshooting and brought it down to a particularly confusing part for me. I took out all debugging logs and added 3 loops into my load function, they are these.
Debug.Log("LOAD CALLED");
accessDataPath(false);
for (int i = 0; i < listSize; i++)
{
Debug.Log("First Loop " + SavedGames[i].returnName());
SavedGames[i].updateName("Updated Player " + (i + 1));
}
for (int i = 0; i < listSize; i++)
{
Debug.Log("Second Loop " + SavedGames[i].returnName());
}
foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
{
Debug.Log("Third Loop " + player.returnName());
}
So the first one displays player 15, then correctly displays the first loops 1-15 then temp name again, still havent 'figured out why that is popping up but I think it is related. Then the second loop iterates all wrong. Literally changed and back to back the loops are wrong, the only difference being it left the scope of the for loop. I ran a third loop using foreach to see if the type of call made a difference and it does not.
Even changing the name, and then immediately calling a loop to check shows that the values are changing. I think it might have something to do with how I am storing the objects, but I am not sure how the problem could be arising. Its not going null, and the names are changing to be the same every time so it isn't completely random. I am posting this here just after I found this hoping that soon I will solve it, but am also hoping if I do not someone else might spot something. I have the next 3 hours to work on it so I will be trying this entire time checking back every now and then. Thanks in advance for anyone that might glance at it for me.
Ok so I finally got the implementation done. I had to swap around a ton of my code, and ended up doing a wrapper object/list combination using JSON. Essentially the problem is that binary formatter messes up objects after you de-serialize them. Every time I updated my objects values they would randomly change on me for no reason without being accessed.
This was surprising as about half the posts I read on saving say its good, others like This one say that it is bad practice. I had done some research initially but had not come across the negative aspects of using it. I am still having problems, but Json is successfully retaining data and my objects are not messing up. I am pretty sure this was the problem as the only sections that I changed were my objects value structure to public for the serializing, and implemented the json structure into my SaveLoad script. This video was very helpful for the overall structure and getting started, and This thread helped me with troubleshooting when I ran into several problems.
I should also note that one thing I did not catch for a while. While Json can load lists, the initial object to be loaded must not be a list. I was attempting to save a list of my PlayerType directly into a folder, which it will not do. I ended up creating a quick object that contained my list and then saving the object. Since everywhere I read said that lists were fine it took a while to discover that this was causing part of my problem. It was not giving me any errors, just returning a blank string which most threads said was because it was not public or serializable.
Anyway here is to hoping my struggles and searches for answers might help as the things I found were quite scattered and hard to come across.
Let me start by saying that this is my first time opening up a question here. If I'm unclear in my code formatting or have expressed myself poorly, please let me know and I'd be happy to adjust.
I'm doing conceptual design for a tool to be used in Unity (C#) to allow us to "stream" the output of an AudioSource, in real-time, and then have that same audio be played back from a different GameObject. In essence, we would have a parallel signal being stored in a buffer while the original AudioSource functions as one would expect, sending it's playing clip into the mixer just as is normal.
To achieve this I'm trying to use the audio thread and the OnAudioFilterRead() function. To extract the floating point audio data to pipe into OnAudioFilterRead() i'm using AudioSource.GetOutputData, storing that into an array and then supplying the array to the audio filter. I'm also creating an empty AudioClip, setting it's data from the same array and playing that AudioClip on the new GameObject.
Right now I have the audio being played from the new GameObject, but the result is very distorted and unpleasant, which i'm chalking up to one or more of the following;
The audio buffer is being written to/read from in an unsynched manner
Unitys samplerate is causing problems with the clip. Have tried both 44.1khz and 48khz with subpar results, as well as playing around with import settings on clips. Documentation for Unity 2018.2 is very weak and a lot of older methods are now deprecated.
Aliens.
Ideally, I would be able to stream back the audio without audible artefacts. Some latency i can live with (~30-50ms) but not poor audio quality.
Below is the code that is being used. This script is attached to the GameObject that is to receive this audio signal from the original emitter, and it has its own AudioSource for playback and positioning.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class ECO_receiver : MonoBehaviour {
public AudioSource emitterSend;
private AudioSource audioSource;
public AudioClip streamedClip;
public bool debugSampleData;
private float[] sampleDataArray;
public float sampleSize;
public float sampleFreq;
private int outputSampleRate;
public bool _bufferReady;
void Start () {
audioSource = GetComponent<AudioSource>();
sampleSize = emitterSend.clip.samples;
sampleFreq = emitterSend.clip.frequency;
sampleDataArray = new float[2048];
streamedClip = AudioClip.Create("audiostream", (int)sampleSize, 1, (int)sampleFreq, false);
audioSource.clip = streamedClip;
audioSource.Play();
_bufferReady = true;
}
private void FixedUpdate()
{
if (emitterSend.isPlaying && _bufferReady == true)
{
FillAudioBuffer();
}
else if (!emitterSend.isPlaying)
{
Debug.Log("Emitter is not playing!");
}
if (debugSampleData && sampleDataArray != null && Input.GetKeyDown("p"))
{
for (int i = 0; i < sampleDataArray.Length; i++)
{
Debug.Log(sampleDataArray[i]);
}
}
else if (sampleDataArray == null)
{
Debug.Log("No data in array!");
}
}
void FillAudioBuffer()
{
emitterSend.GetOutputData(sampleDataArray, 0);
streamedClip.SetData(sampleDataArray, 0);
_bufferReady = false;
}
void OnAudioFilterRead(float[] data, int channels)
{
if (!_bufferReady)
{
for (int i = 0; i < data.Length; i++)
{
data[i] = (float)sampleDataArray[i];
}
_bufferReady = true;
}
}
}
Greatly appreciate any wisdom I might be granted! Thank you!
i am trying to make some types of pickups spawn inside a given area, although some usually get stuck within the walls, how would i fix this?
Code in question for moving objects
for (int x = 0; x < garbage.Length; x++)
{
if (x < 5)
{
garbage[x].transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
}
}
Fixed it using Physics.OverlapSphere. Thanks.
You could have a while loop inside your if statement, so it would be like
int attempts = 0;
while(garbage[x].transform.position == /*[the range of coordinates for the wall]*/ || attempts = 0)
{
garbage[x].transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
attempts += 1;
}
You can try OnCollisionStay to tackle this with collisions. OnCollisionStay can be very cpu heavy if not used carefully, so you may want to think of a better way if you can.
You will have to create a new script using the following code, which you will attach to your power-up prefab.
bool keepChecking = true;
void OnCollisionStay(Collision collision)
{
if(keepChecking)
{
if(collision.gameobject.tag == "Wall")
{
collision.gameobject.transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
}
else
{
keepChecking = false;
}
}
}
https://docs.unity3d.com/ScriptReference/Collider.OnCollisionStay.html
Read that link and make sure your objects have all the requirements. Your wall and Power-Up should have colliders, and at least one of these two should have a rigid body. None of these objects should be kinematic.
Let me know if this works for you.
I am writing an application in Unity which will be required to capture an image from a camera every frame (at ~60fps), and send the resultant data to another service running locally.
The issue is, I am aware that capturing the rendered data from the camera can cause massive frame rate drops (as explained in this article) when using the GetPixels() method. The article explains that "GetPixels() blocks for ReadPixels() to complete" and "ReadPixels() blocks while flushing the GPU" which is why the GPU and CPU have to sync up, resulting in a lag.
I have produced a sample project with a script attached which simply outputs frames to a file as a PNG to replicate the functionality of the program I wish to create. I have done my best to implement what is described in the article, namely allowing the GPU to render a frame, then wait a few frames before calling GetPixels() so as not to cause the GPU and CPU to forcefully sync up. However, I really haven't made any progress with it. The project still plays at about 10-15fps.
How can I achieve a realtime capture of 60 frames per second in Unity?
using System;
using System.Collections;
using System.IO;
using UnityEngine;
namespace Assets
{
public class MyClass: MonoBehaviour
{
private const float reportInterval = 0.5f;
private int screenshotCount = 0;
private const float maxElapsedSecond = 20;
private string screenshotsDirectory = "UnityHeadlessRenderingScreenshots";
public Camera camOV;
public RenderTexture currentRT;
private int frameCount = 0;
private Texture2D resultantImage;
public void Start()
{
camOV.forceIntoRenderTexture = true;
if (Directory.Exists(screenshotsDirectory))
{
Directory.Delete(screenshotsDirectory, true);
}
if (!Application.isEditor)
{
Directory.CreateDirectory(screenshotsDirectory);
camOV.targetTexture = currentRT;
}
}
// Update is called once per frame
public void Update()
{
//Taking Screenshots
frameCount += 1;
if (frameCount == 1)
{
TakeScreenShot();
}
else if (frameCount == 3)
{
ReadPixelsOut("SS_"+screenshotCount+".png");
}
if (frameCount >= 3)
{
frameCount = 0;
}
}
public void TakeScreenShot()
{
screenshotCount += 1;
RenderTexture.active = camOV.targetTexture;
camOV.Render();
resultantImage = new Texture2D(camOV.targetTexture.width, camOV.targetTexture.height, TextureFormat.RGB24, false);
resultantImage.ReadPixels(new Rect(0, 0, camOV.targetTexture.width, camOV.targetTexture.height), 0, 0);
resultantImage.Apply();
}
private void ReadPixelsOut(string filename)
{
if (resultantImage != null)
{
resultantImage.GetPixels();
RenderTexture.active = currentRT;
byte[] bytes = resultantImage.EncodeToPNG();
// save on disk
var path = screenshotsDirectory + "/" + filename;
File.WriteAllBytes(path, bytes);
Destroy(resultantImage);
}
}
}
}
The article implies that it is possible, but I haven't managed to get it to work.
Many thanks in advance for your help.
I am not sure if OP still need the answer. But in case someone in the future getting the same problem, Let me share what i found.
https://github.com/unity3d-jp/FrameCapturer
This is a plugin designed for rendering animation video in Unity editor. But it can also work in standalone. In my case, i take some part of it, and make my app stream Motion Jpeg. I did it with 30fps, never tried 60fps
I'm trying to create something like particle system on XNA 4, C#.
I've created a function that makes particles move from each other if they get close enough. It contains cycle in cycle and therefore it is laggy. Without this function, program starts to lag with 400-500 of particles, and with function - nearly 180.
Question 1. Can I improve perfomance by creating background thread to process these particle collisions?
So, created thread with timer working inside. When I launch my game, it starts fine but when number of particles gets more than nearly 130-150, there appears a runtime error Collection has changed. Unable to perform enumeration." (it is a translation from another language, not the exact message).
Collection "neighbours" - local variable in a function Particle.RunAwayFromNeighbours - is being changed during the enumeration process, then, i think, one thread is somehow calling function while previous call is not completely performed. A strange thing.
I tried to use Threading.Monitor class to synchronize calls, but I have very few experience of multi-thread programming so I think i made something wrong. It didn't solve the problem, it's still the same error with the same count of particles.
I also tried to use lock operator but it's still the same situation.
Question 2. How do I synchronize threads, finally?
There is class Player, he "owns" some particles. Other thread works in this class. Code below is written using Monitor class.
Code ( ... is not necessary part):
class Player
{
...
Thread CollisionThread;
static double check_period=100;
System.Timers.Timer checktimer = new System.Timers.Timer(check_period);
public void StartCollisionChecking()
{
checktimer.AutoReset = true;
checktimer.Elapsed += (o, e) => { CheckCollisions(); };
CollisionThread = new Thread(this.CheckCollisionCycle);
CollisionThread.Start();
} //call in LoadContent
void CheckCollisionCycle()
{
checktimer.Start();
} //call 1 time in new thread
void CheckCollisions()
{
for (int i = 0; i < Army.Count - 1; i+=2 )
{
var p = Army[i];
p.RunAwayFromNeighbours();
}
} //called in CheckCollisionCycle
}
class Particle : VO
{
static float neighbour_search_radius = 2;
static float run_speed_q = 0.1f;
IEnumerable<Particle> CheckForNeighbours()
{
return owner.GetArmy().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius);
}
public void RunAwayFromNeighbours()
{
object x = new object() ;
Monitor.Enter(x);
try
{
var neighbours = CheckForNeighbours();
foreach (Particle p in neighbours)
{
Vector2 where_to_run = location.GetXY() - p.location.GetXY();
speed += where_to_run * run_speed_q;
}
}
finally
{
Monitor.Exit(x);
}
}
Chances are that neighbours is being modified during your foreach, try doing:
foreach (Particle p in neighbours.toList())
And see if that sorts it.
This will have you iterate over a copy of neighbours. It's not the best solution (you should avoid this collision in the first place) but it's a quick workaround to see if that's actually where the fault lies.
I solved the problem by making 2 things:
1. I added bool variable, that shows whether thread has finished its work or not, into class Particle. It looks like this:
public bool thread_completed = true;
public void RunAwayFromNeighbours()
{
thread_completed = false;
object x = new object() ;
Monitor.Enter(x);
try
{
var neighbours = CheckForNeighbours().ToList();
foreach (Particle p in neighbours)
{
Vector2 where_to_run = location.GetXY() - p.location.GetXY();
speed += where_to_run * run_speed_q;
}
}
finally
{
Monitor.Exit(x);
thread_completed = true;
}
2. I changed line
return owner.GetArmy().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius).ToList();
to line
return owner.GetArmy().ToList().Where(a => Vector2.Distance(a.location.GetXY(), location.GetXY()) < neighbour_search_radius);