The question is answered, right answer was accepted
Call a method of a class derived from MonoBehaviour from a class that isn't.
I'm creating a version of Minesweeper. I have a class that derives from MonoBehaviour called GameManager and a class that doesn't derive from MonBehaviour called Grid. When GameManager creates an instance of Grid, it passes itself in through Grid's constructor so that Grid will be able to call a public method in GameManager. However, whenever Grid calls a method of GameManager, Unity crashes.
Here's Grid's constructor:
public Grid ( GameManager gm, int rowNum, int colNum, int depth, int bombCount )
{
m_dummy = new Cell( this, Cell.CellState.clear, false );
m_rowNum = rowNum;
m_colNum = colNum;
m_depth = depth;
m_bombCount = bombCount;
m_cellsToClear = ( rowNum * colNum ) - bombCount;
m_gm = gm;
create ();
init();
}
This is where GameManager creates an instance of Grid:
m_grid = new Grid( this, 10, 10, 10, 25 );
Here's where Grid calls the method from GameManager:
int cRow = cell.getRow();
int cCol = cell.getCol();
int cDepth = cell.getDepth();
m_gm.cubeCleared( cRow, cCol, cDepth );
Here's the method from GameManager that is called:
public void cubeCleared ( int row, int col, int depth )
{
Destroy( getCube( row, col, depth ).gameObject );
print( "cube cleared at " + row + ", " + col + ", " + depth );
}
Everything in all my scripts works well and there are no compile errors. I tried creating a very simple method in GameManager that simply prints something and calling it from Grid and Unity still crashes.
Is there something about classes derived from MonoBehaviour that doesn't let classes that aren't derived from it call their methods?
[EDIT] Upon further analysis I found the following in addition to the problem described:
The game includes an auto-clear function that will clear away any cells surrounding a cell that is clicked on and has no mines next to it. I'm implementing that with the following function:
public void cellClear ( Cell cell )
{
int cRow = cell.getRow();
int cCol = cell.getCol();
int cDepth = cell.getDepth();
m_gm.cubeCleared( cRow, cCol, cDepth );
if ( --m_cellsToClear == 0 )
{
// you win
}
else if ( cell.getAdjBombNum() == 0 )
{
for ( int dr = -1; dr <= 1; dr++ )
{
for ( int dc = -1; dc <= 1; dc++ )
{
for ( int dd = -1; dd <= 1; dd++ )
{
int r = cell.getRow() + dr;
int c = cell.getCol() + dc;
int d = cell.getDepth() + dd;
Cell tCell = getCell( r, c, d );
tCell.lClick();
}
}
}
}
}
The method cell.lClick() changes some states in the cell and calls cellClear() if the cell didn't have any mines and wasn't flagged. cellClear() is recursively called until there are either no more cells left to clear, or the cleared cell doesn't have any empty cells next to it. The method works when I delete the recursiveness (meaning that the auto clear functionality if taken out) or when I delete the call to GameManager (m_gm.cubeCleared).
The problem isn't an infinite loop like I had suspected when I first came across the problem as it runs fine when I delete the reference to the GameManager. It also no longer appears to be the call to GameManager itself anymore. It's only when it tries to call GameManager as it loops through itself auto-clearing cubes.
I've solved my problem by creating a list of the cubes I need to clear in my Grid class and then adding to it any cube that needs to be cleared. Then in my Game$$anonymous$$anager class's Update method, I take one item from the list and clear it per update cycle. That way it destroys an object once per update and doesn't slow the system down. It also frees me up to do any effects on the objects being destroyed.
Answer by _Game_Dev_Dude_ · Nov 19, 2015 at 03:45 PM
I am not really sure why the editor is crashing. You can try looking into the unity log and see what it is saying. Either way this is not the best way to do this in my opinion. In my opinion it would be better for you to add delegates to your Grid class that the GameManager would then subscribe to and act upon them.
I've played around with it some more, and it seems I was mistaken in my initial analysis of the problem. There's a second part to the problem I didn't mention as I hadn't considered it part of the problem. I'll update the original post with the details as well as include them here.
I'm creating a version of $$anonymous$$inesweeper. That game includes an auto-clear function that will clear away any cells surrounding a cell that is clicked on and has no $$anonymous$$es next to it. I'm implementing that with the following function:
public void cellClear ( Cell cell )
{
int cRow = cell.getRow();
int cCol = cell.getCol();
int cDepth = cell.getDepth();
m_gm.cubeCleared( cRow, cCol, cDepth );
if ( --m_cellsToClear == 0 )
{
// you win
}
else if ( cell.getAdjBombNum() == 0 )
{
for ( int dr = -1; dr <= 1; dr++ )
{
for ( int dc = -1; dc <= 1; dc++ )
{
for ( int dd = -1; dd <= 1; dd++ )
{
int r = cell.getRow() + dr;
int c = cell.getCol() + dc;
int d = cell.getDepth() + dd;
Cell tCell = getCell( r, c, d );
tCell.lClick();
}
}
}
}
}
The method cell.lClick() changes some states in the cell and calls cellClear() if the cell didn't have any $$anonymous$$es and wasn't flagged. cellClear() is recursively called until there are either no more cells left to clear, or the cleared cell doesn't have any empty cells next to it. The method works when I delete the recursiveness (meaning that the auto clear functionality if taken out) or when I delete the call to Game$$anonymous$$anager (m_gm.cubeCleared).
The problem isn't an infinite loop like I had suspected when I first came across the problem as it runs fine when I delete the reference to the Game$$anonymous$$anager. It also no longer appears to be the call to Game$$anonymous$$anager itself anymore. It's only when it tries to call Game$$anonymous$$anager as it loops through itself auto-clearing cubes.
@Game_Dev_Dude
To test my theory on what the problem is I created a new empty project an put in the following two very simple pieces of code.
using UnityEngine;
using System.Collections;
public class Game$$anonymous$$anager : $$anonymous$$onoBehaviour {
Test t;
// Use this for initialization
void Start ()
{
t = new Test( this, 1000 );
}
// Update is called once per frame
void Update ()
{
if ( Input.Get$$anonymous$$ouseButtonDown( 0 ) )
{
t.recursiveTest();
}
}
public void test ()
{
print( "I'm doing something." );
}
}
public class Test
{
private int m_testNum;
private Game$$anonymous$$anager m_gm;
public Test ( Game$$anonymous$$anager gm, int testNumber )
{
m_gm = gm;
m_testNum = testNumber;
}
public void recursiveTest ()
{
for ( int i = 0; i < m_testNum; i++ )
{
m_gm.test();
}
}
}
If I set the testNumber to 100, then there's no problems. I click anywhere on the screen and the result is instantaneous. If I increment it to 1000, then Unity sits unresponsive for a bit and then is able to follow through. If I set it to 10000, then Unity crashes. From this test I gather that what I'm doing is hanging up Unity for enough time that it crashes. The question now is why does it hold up Unity for so long?