I am working on a 2048 style game, and applying ML-Agents to see if it learns how to play the game. The game works and is functional and playable, unless there are super fast movements. I can recreate this by spamming the input keys a bunch, but the ML-Agent will create this error every time. In ShiftPanels.cs(shown after this description), I have a GameObject.Find() when moving left to find the current panel on the screen in UI. This is null sometimes, only when we are going super fast. Another thing is the gameBoardArray[x,yj] is not updated at this point. Below is all the scripts used for moving the panels, I only included moving left because everything else is mainly the same, just modified some math, etc… Is ShiftPanels.cs the cause? How do I fix it? Appreciate the help in advance!!!
PanelPhysics.cs:
using UnityEngine;
public class PanelPhysics : MonoBehaviour
{
[SerializeField] private ShiftPanels shiftPanelsScript;
[SerializeField] private CombinePanels combinePanelsScript;
[SerializeField] private PanelInstantiator panelInstantiatorScript;
[SerializeField] private UpdatePanelColors updatePanelColorsScript;
[SerializeField] private GameState gameStateScript;
public GameObject panel;
public GameObject game;
[HideInInspector] public int[] fibonacciArray = new int[] { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584 };
public int[,] gameBoardArray = new int[4, 4];
[HideInInspector] public bool canShift = false;
[HideInInspector] public bool canCombine = false;
[HideInInspector] public bool isMoveActive = false;
public void ResetGame()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
gameBoardArray[i, j] = 0;
Destroy(GameObject.Find("Panel_Prefab_" + (i * 4 + j)));
}
}
panelInstantiatorScript.SpawnRandomPanel();
panelInstantiatorScript.SpawnRandomPanel();
updatePanelColorsScript.UpdateColors();
}
public void MovePanelsLeft()
{
isMoveActive = true;
ResetVariables();
shiftPanelsScript.ShiftPanelsLeft();
combinePanelsScript.CombinePanelsLeft();
shiftPanelsScript.ShiftPanelsLeft();
if ((canShift) || (canCombine))
{
panelInstantiatorScript.SpawnRandomPanel();
updatePanelColorsScript.UpdateColors();
}
gameStateScript.CheckLoseCondition();
gameStateScript.CheckWinCondition();
isMoveActive = false;
}
public void ResetVariables()
{
shiftPanelsScript.iterator = 0;
shiftPanelsScript.previousTilesIterator = 0;
canShift = false;
canCombine = false;
}
}
ShiftPanels.cs:
using UnityEngine;
public class ShiftPanels : MonoBehaviour
{
[SerializeField] private PanelPhysics panelPhysicsScript;
[HideInInspector] public int iterator = 0;
[HideInInspector] public int previousTilesIterator = 0;
public void ShiftPanelsLeft()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (panelPhysicsScript.gameBoardArray[i, j] != 0)
{
for (int k = 1; k < 4; k++)
{
//decrement row from right to left
if ((i - k >= 0) && (panelPhysicsScript.gameBoardArray[(i - k), j] != 0))
{
previousTilesIterator++;
}
}
int shiftingInt = Mathf.FloorToInt(iterator / 4) - previousTilesIterator;
int newPosX = i - shiftingInt;
GameObject currentPanel = GameObject.Find("Panel_Prefab_" + iterator);
Vector3 currentOldPos = currentPanel.transform.position;
Vector3 currentNewPos = new Vector3(currentPanel.transform.position.x - 480 * shiftingInt, currentPanel.transform.position.y, currentPanel.transform.position.z);
if (currentNewPos != currentOldPos)
{
panelPhysicsScript.gameBoardArray[newPosX, j] = panelPhysicsScript.gameBoardArray[i, j];
panelPhysicsScript.gameBoardArray[i, j] = 0;
currentPanel.transform.position = currentNewPos;
currentPanel.name = "Panel_Prefab_" + ((newPosX * 4) + j);
if (!panelPhysicsScript.canShift)
panelPhysicsScript.canShift = true;
}
}
previousTilesIterator = 0;
iterator++;
}
}
iterator = 0;
}
CombinePanels.cs:
using Google.Protobuf.WellKnownTypes;
using System;
using TMPro;
using UnityEngine;
public class CombinePanels : MonoBehaviour
{
[SerializeField] private PanelPhysics panelPhysicsScript;
[SerializeField] private TestScriptAgent testScriptAgentScript;
public void CombinePanelsLeft(bool shouldUpdatePanels = true)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if ((panelPhysicsScript.gameBoardArray[i, j] != 0) && (i + 1 <= 3) && (panelPhysicsScript.gameBoardArray[(i + 1), j] != 0))
{
GameObject currentPanel = GameObject.Find("Panel_Prefab_" + (i * 4 + j));
GameObject nextPanel = GameObject.Find("Panel_Prefab_" + ((i + 1) * 4 + j));
int currentIndex = Array.IndexOf(panelPhysicsScript.fibonacciArray, panelPhysicsScript.gameBoardArray[i, j]);
int nextIndex = Array.IndexOf(panelPhysicsScript.fibonacciArray, panelPhysicsScript.gameBoardArray[(i + 1), j]);
if ((currentIndex == (nextIndex + 1)) || (currentIndex == (nextIndex - 1)))
{
panelPhysicsScript.canCombine = true;
if (shouldUpdatePanels)
{
int newNum = panelPhysicsScript.gameBoardArray[i, j] + panelPhysicsScript.gameBoardArray[(i + 1), j];
panelPhysicsScript.gameBoardArray[i, j] = newNum;
panelPhysicsScript.gameBoardArray[(i + 1), j] = 0;
currentPanel.GetComponentInChildren<TMP_Text>().text = panelPhysicsScript.gameBoardArray[i, j].ToString();
Destroy(nextPanel);
testScriptAgentScript.AddCombineReward(newNum);
}
}
else if ((panelPhysicsScript.gameBoardArray[i, j] == 1) && (currentIndex == nextIndex))
{
panelPhysicsScript.canCombine = true;
if (shouldUpdatePanels)
{
panelPhysicsScript.gameBoardArray[i, j] = 2;
panelPhysicsScript.gameBoardArray[(i + 1), j] = 0;
currentPanel.GetComponentInChildren<TMP_Text>().text = panelPhysicsScript.gameBoardArray[i, j].ToString();
Destroy(nextPanel);
testScriptAgentScript.AddCombineReward(2);
}
}
}
}
}
}
ML-Agents Script:
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using UnityEngine;
public class TestScriptAgent : Agent
{
[SerializeField] private PanelPhysics panelPhysicsScript;
private bool isAxisInUse = false;
public override void OnEpisodeBegin()
{
panelPhysicsScript.ResetGame();
}
public override void CollectObservations(VectorSensor sensor)
{
isAxisInUse = false;
sensor.AddObservation(panelPhysicsScript.gameBoardArray[0, 0]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[0, 1]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[0, 2]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[0, 3]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[1, 0]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[1, 1]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[1, 2]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[1, 3]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[2, 0]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[2, 1]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[2, 2]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[2, 3]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[3, 0]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[3, 1]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[3, 2]);
sensor.AddObservation(panelPhysicsScript.gameBoardArray[3, 3]);
}
public override void OnActionReceived(ActionBuffers actions)
{
if ((!isAxisInUse) && (!panelPhysicsScript.isMoveActive))
{
isAxisInUse = true;
if (actions.DiscreteActions[0] == 0)
{
Debug.Log("LEFT");
panelPhysicsScript.MovePanelsLeft();
}
if (actions.DiscreteActions[0] == 1)
{
Debug.Log("RIGHT");
panelPhysicsScript.MovePanelsRight();
}
if (actions.DiscreteActions[1] == 0)
{
Debug.Log("DOWN");
panelPhysicsScript.MovePanelsDown();
}
if (actions.DiscreteActions[1] == 1)
{
Debug.Log("UP");
panelPhysicsScript.MovePanelsUp();
}
}
}
public override void Heuristic(in ActionBuffers actionsOut)
{
ActionSegment<int> discreteActions = actionsOut.DiscreteActions;
isAxisInUse = false;
int horizontalAxis = (int)Input.GetAxisRaw("Horizontal");
int verticalAxis = (int)Input.GetAxisRaw("Vertical");
//MLAgent actions can only be positive integers, so we 'transpose' from -1 to 0
//and vise-versa, so we don't have to manually modify values when on Heuristic
if (horizontalAxis == -1)
horizontalAxis = 0;
else if (horizontalAxis == 0)
horizontalAxis = -1;
if (verticalAxis == -1)
verticalAxis = 0;
else if (verticalAxis == 0)
verticalAxis = -1;
discreteActions[0] = horizontalAxis;
discreteActions[1] = verticalAxis;
}
public void AddCombineReward(float rewardNum)
{
rewardNum /= 2584;
AddReward(+rewardNum);
}
public void AddWinReward()
{
AddReward(+2f);
EndEpisode();
}
public void AddLoseReward()
{
AddReward(-2f);
EndEpisode();
}
}