UnityException: Not allowed to call this function - c#

Y'all remember that game where you had a wooden board and tried to get the ball through the maze w/o avoiding pitfalls? I am making a clone of that (except on the six sides of a cube). Instead of pitfalls, I added bombs that players have to actively disarm before they can progress. I recently added some code to make it more user friendly, but I keep on getting the error:
UnityException: You are not allowed to call this function when declaring a variable. Move it to the line after without a variable declaration. If you are using C# don't use this function in the constructor or field initializers, Instead move initialization to the Awake or Start function. UnityEngine.GameObject..ctor () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineGameObject.gen.cs:419) RandoLetter..cctor () Rethrow as TypeInitializationException: An exception was thrown by the type initializer for RandoLetter
RandoLetter is the script that creates the code for the player to destroy the bomb, but its acting up for some reason. Here is the code:
using UnityEngine; using System.Collections; using UnityEngine.UI; using System.Collections.Generic;
public class RandoLetter : MonoBehaviour { //The array list of bombs public static GameObject Bombs = new GameObject();
//Array List of Particles
public static GameObject Particles = new GameObject();
//The list of colliders on the bombs
public static Collider Triggers = new Collider();
//The symbol that shows up
int LetterID;
//The speed at which a new symbol appears
int counter, CounterMax;
//The number of symbols that appear in one instance
int LetterCount;
//Which slot in the array that the player is accessing
public int entryID;
//The symbols that appear
public GameObject[] Arrows = new GameObject[4];
//The arrow buttons
public GameObject ArrowBN;
public static GameObject ArrowBTN;
//The randomly generated code
int[] PassCode = new int[4];
//The player game object to be destroyed
public GameObject player;
//The buttons that let the player rotate
public GameObject MoveUI;
//The particle effect when the player dies
public GameObject death;
//The timer before the player or bomb expires
int DeathCounter;
//The code that the player puts in
int[] YourCode = new int[4];
int CounterToMax = 0;
//Is the bomb currently being disarmed?
public static bool disarming = false;
void RestoreStart()
{
//How much longer until the next symbol appears
CounterToMax = 0;
//Is the player attempting to disarm the bomb
disarming = false;
//How many symbols show up
LetterCount = 4;
//Which slot in the array is the player accessing
entryID = 0;
//The UI that lets the player punch in part of the code
BallMoving.BombU.SetActive(false);
//Disabling the previous UI for the code
SetUIFalse();
}
// Use this for initialization
void Start()
{
//The timer until the player expires after failing to disarm a bomb
DeathCounter = 1000;
//The slot in the array that the player is accessing
entryID = 0;
//The amount of characters in the code being presented to the player
LetterCount = 4;
//Setting the static value to the public value
ArrowBTN = ArrowBN;
//The time between arrows
counter = 50;
//Setting CounterMax (the starting value of the counter) to the amount of time represented by counter
CounterMax = counter;
}
// Update is called once per frame
void Update()
{
//If the player is attempting to disarm the bomb
if (disarming)
{
//decrementing counter
counter--;
//Disabling the UI that allows the player to move
MoveUI.active = false;
//Special code for the last letter in the sequence
if (CounterToMax == LetterCount && counter == 0)
{
Arrows[LetterID].active = false;
}
//In the first 3 letters
if (counter == 5 && CounterToMax != LetterCount)
{
Arrows[LetterID].active = false;
}
//If the counter goes lower than zero somehow (just a catch in case the code above malfunctions)
if (counter < 0)
{
counter = CounterMax;
}
//Randomly Generating the letter
if (counter == 0 && CounterToMax != LetterCount)
{
LetterID = Random.Range(1, 4);
counter = CounterMax;
#region AssignLetter
//Assigning the letter to the value in the array slot
Arrows[LetterID].active = true;
PassCode[CounterToMax] = LetterID;
CounterToMax++;
#endregion
}
}
//Re-enabling the move UI
if (!disarming)
{
MoveUI.active = true;
}
//Enabling the UI that allows the player to punch in the code
if (CounterToMax == LetterCount && disarming == true)
{
ArrowBTN.active = true;
}
}
//Enabling the player to put in a code based off of a UI element
public void AddDirection(int number)
{
YourCode[entryID] = number;
entryID++;
//If the player is at the last letter, make sure that the player's code matches the randomly generated one
if (entryID == LetterCount)
{
//Resetting the entryID (slot in the array) for the next time a player defuses a bomb
entryID = 0;
CheckCorrect();
}
}
//Checking to make sure that the player entered in the correct code
public void CheckCorrect()
{
for (int i = 0; i < LetterCount; i++)
{
//If the player failed......
if (YourCode[i] != PassCode[i])
{
//Destroy the player
DestroyPlayer();
}
}
//Otherwise destroy the bomb
DestroyBomb();
}
//Re-enabling the player's UI that allows them to move
public void SetUIFalse()
{
disarming = false;
ArrowBTN.active = false;
}
//Destroying the player
public void DestroyPlayer()
{
print("Player Destroyed!");
SetUIFalse();
for (int i = 0; i < DeathCounter; i++)
{
Destroy(player);
if (i == DeathCounter - 1)
{
//Switching the levels after the player dies
switch (RotateBlock.level)
{
case 1:
Application.LoadLevel("Level1");
break;
case 2:
Application.LoadLevel("Level2");
break;
}
}
}
}
//Destroying the bomb
public void DestroyBomb()
{
//Resetting everything for the next time the player defuses a bomb
RestoreStart();
print("In DestroyBomb");
for (int i = 0; i < DeathCounter; i++)
{
if (i == DeathCounter - 1)
{
//Allowing the player to move again
Rigidbody rb;
rb = player.GetComponent<Rigidbody>();
rb.isKinematic = false;
//Disabling the bomb
Bombs.SetActive(false);
//Enabling the particle effect
Particles.active = true;
//Destroying the collider on the bomb
Triggers.enabled = false;
}
}
}
}
Sorry if it looks like a bunch of junk, I tried to comment it as best as I could. Anyways, it has been bugging me for a while and I really appreciate any help :D

You have to create a new instance of GameObject inside a function.
Replace public static GameObject Particles = new GameObject();
with
public static GameObject Particles;
void Awake()
{
Particles = new GameObject();
}

Related

How would i pause before continuing waves in unity?

im making an enemy wave - based game and it works fine, but i want to make it so before it continues on to the next wave, the player has to press a key to let the game know they're ready, the reason i want to do this is because i want to implement a shop to purchase upgrades after waves, here is my wave script:
SpawnManager.cs
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's
// position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position,
SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
StartNextWave();
}
}
}
Set a Boolean like waitForKey=true, then in something like Update:
if( waitForKey && Input.GetKeyDown("space") ) {
waitForKey=false;
StartNextWave();
}
Listen to a button input in the Update function.
void Update()
{
if (_enemiesInWaveLeft <= 0 && InputGetKeyDown(KeyCode.Space))
{
StartNewWave();
}
}
This will cause the wave to spawn when there are no enemies left and Space is pressed down. You will need to add some UI separately to this to give the player some feedback as to when it is Ok to press the Space Button.
Also remove the StartNextWave from your EnemyDefeated funciton. That can be used to enable and disable UI for the player to let them know to press a button to continue.

How do I assign a prefab as a target to a CameraControl script?

I am doing the "Tanks!" tutorial for Unity and the guide told me to delete the tank object, as it was a Prefab and would use spawnpoints instead. I followed the videos, but it is telling me the m_Targets of CameraControl have not been assigned, as it is hidden in the inspector and is not meant to be modified according to the videos. I have rewatched all of them and this is my last resort. I have attempted modifying all the other scripts but they are not working, no matter what I do. I attempted using the completed versions of the tank and everything that was completed but it did not work, even the Camera control script itself. How can I get it to set the targets to 2 and use the prefabs of the tanks for the camera's target?
CameraControl.cs:
using UnityEngine;
namespace Complete
{
public class CameraControl : MonoBehaviour
{
public float m_DampTime = 0.2f; // Approximate time for the camera to refocus.
public float m_ScreenEdgeBuffer = 4f; // Space between the top/bottom most target and the screen edge.
public float m_MinSize = 6.5f;
/*[HideInInspector] */ public Transform[] m_Targets; // All the targets the camera needs to encompass.
private Camera m_Camera; // Used for referencing the camera.
private float m_ZoomSpeed; // Reference speed for the smooth damping of the orthographic size.
private Vector3 m_MoveVelocity; // Reference velocity for the smooth damping of the position.
private Vector3 m_DesiredPosition; // The position the camera is moving towards.
private void Awake ()
{
m_Camera = GetComponentInChildren<Camera> ();
}
private void FixedUpdate ()
{
// Move the camera towards a desired position.
Move ();
// Change the size of the camera based.
Zoom ();
}
private void Move ()
{
// Find the average position of the targets.
FindAveragePosition ();
// Smoothly transition to that position.
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
}
private void FindAveragePosition ()
{
Vector3 averagePos = new Vector3 ();
int numTargets = 0;
// Go through all the targets and add their positions together.
for (int i = 0; i < m_Targets.Length; i++)
{
// If the target isn't active, go on to the next one.
if (!m_Targets[i].gameObject.activeSelf)
continue;
// Add to the average and increment the number of targets in the average.
averagePos += m_Targets[i].position;
numTargets++;
}
// If there are targets divide the sum of the positions by the number of them to find the average.
if (numTargets > 0)
averagePos /= numTargets;
// Keep the same y value.
averagePos.y = transform.position.y;
// The desired position is the average position;
m_DesiredPosition = averagePos;
}
private void Zoom ()
{
// Find the required size based on the desired position and smoothly transition to that size.
float requiredSize = FindRequiredSize();
m_Camera.orthographicSize = Mathf.SmoothDamp (m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);
}
private float FindRequiredSize ()
{
// Find the position the camera rig is moving towards in its local space.
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
// Start the camera's size calculation at zero.
float size = 0f;
// Go through all the targets...
for (int i = 0; i < m_Targets.Length; i++)
{
// ... and if they aren't active continue on to the next target.
if (!m_Targets[i].gameObject.activeSelf)
continue;
// Otherwise, find the position of the target in the camera's local space.
Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);
// Find the position of the target from the desired position of the camera's local space.
Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;
// Choose the largest out of the current size and the distance of the tank 'up' or 'down' from the camera.
size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));
// Choose the largest out of the current size and the calculated size based on the tank being to the left or right of the camera.
size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);
}
// Add the edge buffer to the size.
size += m_ScreenEdgeBuffer;
// Make sure the camera's size isn't below the minimum.
size = Mathf.Max (size, m_MinSize);
return size;
}
public void SetStartPositionAndSize ()
{
// Find the desired position.
FindAveragePosition ();
// Set the camera's position to the desired position without damping.
transform.position = m_DesiredPosition;
// Find and set the required size of the camera.
m_Camera.orthographicSize = FindRequiredSize ();
}
}
}
Game Manager:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace Complete
{
public class GameManager : MonoBehaviour
{
public int m_NumRoundsToWin = 5; // The number of rounds a single player has to win to win the game.
public float m_StartDelay = 3f; // The delay between the start of RoundStarting and RoundPlaying phases.
public float m_EndDelay = 3f; // The delay between the end of RoundPlaying and RoundEnding phases.
public CameraControl m_CameraControl; // Reference to the CameraControl script for control during different phases.
public Text m_MessageText; // Reference to the overlay Text to display winning text, etc.
public GameObject m_TankPrefab; // Reference to the prefab the players will control.
public TankManager[] m_Tanks; // A collection of managers for enabling and disabling different aspects of the tanks.
private int m_RoundNumber; // Which round the game is currently on.
private WaitForSeconds m_StartWait; // Used to have a delay whilst the round starts.
private WaitForSeconds m_EndWait; // Used to have a delay whilst the round or game ends.
private TankManager m_RoundWinner; // Reference to the winner of the current round. Used to make an announcement of who won.
private TankManager m_GameWinner; // Reference to the winner of the game. Used to make an announcement of who won.
private void Start()
{
// Create the delays so they only have to be made once.
m_StartWait = new WaitForSeconds (m_StartDelay);
m_EndWait = new WaitForSeconds (m_EndDelay);
SpawnAllTanks();
SetCameraTargets();
// Once the tanks have been created and the camera is using them as targets, start the game.
StartCoroutine (GameLoop ());
}
private void SpawnAllTanks()
{
// For all the tanks...
for (int i = 0; i < m_Tanks.Length; i++)
{
// ... create them, set their player number and references needed for control.
m_Tanks[i].m_Instance =
Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
m_Tanks[i].m_PlayerNumber = i + 1;
m_Tanks[i].Setup();
}
}
private void SetCameraTargets()
{
// Create a collection of transforms the same size as the number of tanks.
Transform[] targets = new Transform[m_Tanks.Length];
// For each of these transforms...
for (int i = 0; i < targets.Length; i++)
{
// ... set it to the appropriate tank transform.
targets[i] = m_Tanks[i].m_Instance.transform;
}
// These are the targets the camera should follow.
m_CameraControl.m_Targets = targets;
}
// This is called from start and will run each phase of the game one after another.
private IEnumerator GameLoop ()
{
// Start off by running the 'RoundStarting' coroutine but don't return until it's finished.
yield return StartCoroutine (RoundStarting ());
// Once the 'RoundStarting' coroutine is finished, run the 'RoundPlaying' coroutine but don't return until it's finished.
yield return StartCoroutine (RoundPlaying());
// Once execution has returned here, run the 'RoundEnding' coroutine, again don't return until it's finished.
yield return StartCoroutine (RoundEnding());
// This code is not run until 'RoundEnding' has finished. At which point, check if a game winner has been found.
if (m_GameWinner != null)
{
// If there is a game winner, restart the level.
SceneManager.LoadScene (0);
}
else
{
// If there isn't a winner yet, restart this coroutine so the loop continues.
// Note that this coroutine doesn't yield. This means that the current version of the GameLoop will end.
StartCoroutine (GameLoop ());
}
}
private IEnumerator RoundStarting ()
{
// As soon as the round starts reset the tanks and make sure they can't move.
ResetAllTanks ();
DisableTankControl ();
// Snap the camera's zoom and position to something appropriate for the reset tanks.
m_CameraControl.SetStartPositionAndSize ();
// Increment the round number and display text showing the players what round it is.
m_RoundNumber++;
m_MessageText.text = "ROUND " + m_RoundNumber;
// Wait for the specified length of time until yielding control back to the game loop.
yield return m_StartWait;
}
private IEnumerator RoundPlaying ()
{
// As soon as the round begins playing let the players control the tanks.
EnableTankControl ();
// Clear the text from the screen.
m_MessageText.text = string.Empty;
// While there is not one tank left...
while (!OneTankLeft())
{
// ... return on the next frame.
yield return null;
}
}
private IEnumerator RoundEnding ()
{
// Stop tanks from moving.
DisableTankControl ();
// Clear the winner from the previous round.
m_RoundWinner = null;
// See if there is a winner now the round is over.
m_RoundWinner = GetRoundWinner ();
// If there is a winner, increment their score.
if (m_RoundWinner != null)
m_RoundWinner.m_Wins++;
// Now the winner's score has been incremented, see if someone has one the game.
m_GameWinner = GetGameWinner ();
// Get a message based on the scores and whether or not there is a game winner and display it.
string message = EndMessage ();
m_MessageText.text = message;
// Wait for the specified length of time until yielding control back to the game loop.
yield return m_EndWait;
}
// This is used to check if there is one or fewer tanks remaining and thus the round should end.
private bool OneTankLeft()
{
// Start the count of tanks left at zero.
int numTanksLeft = 0;
// Go through all the tanks...
for (int i = 0; i < m_Tanks.Length; i++)
{
// ... and if they are active, increment the counter.
if (m_Tanks[i].m_Instance.activeSelf)
numTanksLeft++;
}
// If there are one or fewer tanks remaining return true, otherwise return false.
return numTanksLeft <= 1;
}
// This function is to find out if there is a winner of the round.
// This function is called with the assumption that 1 or fewer tanks are currently active.
private TankManager GetRoundWinner()
{
// Go through all the tanks...
for (int i = 0; i < m_Tanks.Length; i++)
{
// ... and if one of them is active, it is the winner so return it.
if (m_Tanks[i].m_Instance.activeSelf)
return m_Tanks[i];
}
// If none of the tanks are active it is a draw so return null.
return null;
}
// This function is to find out if there is a winner of the game.
private TankManager GetGameWinner()
{
// Go through all the tanks...
for (int i = 0; i < m_Tanks.Length; i++)
{
// ... and if one of them has enough rounds to win the game, return it.
if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
return m_Tanks[i];
}
// If no tanks have enough rounds to win, return null.
return null;
}
// Returns a string message to display at the end of each round.
private string EndMessage()
{
// By default when a round ends there are no winners so the default end message is a draw.
string message = "DRAW!";
// If there is a winner then change the message to reflect that.
if (m_RoundWinner != null)
message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";
// Add some line breaks after the initial message.
message += "\n\n\n\n";
// Go through all the tanks and add each of their scores to the message.
for (int i = 0; i < m_Tanks.Length; i++)
{
message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
}
// If there is a game winner, change the entire message to reflect that.
if (m_GameWinner != null)
message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";
return message;
}
// This function is used to turn all the tanks back on and reset their positions and properties.
private void ResetAllTanks()
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].Reset();
}
}
private void EnableTankControl()
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].EnableControl();
}
}
private void DisableTankControl()
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].DisableControl();
}
}
}
}
Tank Manager:
using System;
using UnityEngine;
namespace Complete
{
[Serializable]
public class TankManager
{
// This class is to manage various settings on a tank.
// It works with the GameManager class to control how the tanks behave
// and whether or not players have control of their tank in the
// different phases of the game.
public Color m_PlayerColor; // This is the color this tank will be tinted.
public Transform m_SpawnPoint; // The position and direction the tank will have when it spawns.
[HideInInspector] public int m_PlayerNumber; // This specifies which player this the manager for.
[HideInInspector] public string m_ColoredPlayerText; // A string that represents the player with their number colored to match their tank.
[HideInInspector] public GameObject m_Instance; // A reference to the instance of the tank when it is created.
[HideInInspector] public int m_Wins; // The number of wins this player has so far.
private TankMovement m_Movement; // Reference to tank's movement script, used to disable and enable control.
private TankShooting m_Shooting; // Reference to tank's shooting script, used to disable and enable control.
private GameObject m_CanvasGameObject; // Used to disable the world space UI during the Starting and Ending phases of each round.
public void Setup ()
{
// Get references to the components.
m_Movement = m_Instance.GetComponent<TankMovement> ();
m_Shooting = m_Instance.GetComponent<TankShooting> ();
m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas> ().gameObject;
// Set the player numbers to be consistent across the scripts.
m_Movement.m_PlayerNumber = m_PlayerNumber;
m_Shooting.m_PlayerNumber = m_PlayerNumber;
// Create a string using the correct color that says 'PLAYER 1' etc based on the tank's color and the player's number.
m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";
// Get all of the renderers of the tank.
MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer> ();
// Go through all the renderers...
for (int i = 0; i < renderers.Length; i++)
{
// ... set their material color to the color specific to this tank.
renderers[i].material.color = m_PlayerColor;
}
}
// Used during the phases of the game where the player shouldn't be able to control their tank.
public void DisableControl ()
{
m_Movement.enabled = false;
m_Shooting.enabled = false;
m_CanvasGameObject.SetActive (false);
}
// Used during the phases of the game where the player should be able to control their tank.
public void EnableControl ()
{
m_Movement.enabled = true;
m_Shooting.enabled = true;
m_CanvasGameObject.SetActive (true);
}
// Used at the start of each round to put the tank into it's default state.
public void Reset ()
{
m_Instance.transform.position = m_SpawnPoint.position;
m_Instance.transform.rotation = m_SpawnPoint.rotation;
m_Instance.SetActive (false);
m_Instance.SetActive (true);
}
}
}
Configurations:
GameManager:
Camera Control = CameraRig (CameraControl)
Message Text = Text (Text)
Tank Prefab = Tank
--Tanks--
Size = 2
All of the spawn points are set to the positions from the video guides.
Unfortunately, some of the "Tanks!" tutorial steps have dependencies on later parts in the tutorial. Particularly, step 6: Game Managers.
Since m_Targets is exposed as a public field, it can be set from other scripts. This is exactly what the GameManager script does when it calls:
private void SetCameraTargets()
{
// Create a collection of transforms the same size as the number of tanks.
Transform[] targets = new Transform[m_Tanks.Length];
// For each of these transforms...
for (int i = 0; i < targets.Length; i++)
{
// ... set it to the appropriate tank transform.
targets[i] = m_Tanks[i].m_Instance.transform;
}
// These are the targets the camera should follow.
m_CameraControl.m_Targets = targets;
}
In other words, don't worry about the tanks not being set as camera targets yet. That is configured in step 6 of the tutorial.

Objects in Unity show up in editor but don't render on Android when script attached

I am building a little platformer, where the player can collect coins. Since there are sometimes 100+ spinning coins in one scene, I decided to render only coins that are in within a certain proximity to the player.
This worked before - but on a phone the levels with many coins tend to have some lags. With the coins the removed, my FPS was at straight 60. So this is what I did:
public class AddCoinScript : MonoBehaviour
{
public static int coinCounter = 0; // static cause we will only want ONE counter and not many instances of the coin value
private Text coinText;
private float disBetweenBirdyAndEnemy;
private GameObject birdy;
private Renderer rndr;
private BoxCollider2D bx2D;
private bool canAnimate = false;
private float startAnimDistance = 20;
private bool coinCollected = false;
void Start()
{
coinText = GameObject.Find("Coin Text").GetComponent<Text>(); // Get the Coin Text Element
coinText.text = "x" + coinCounter.ToString();
birdy = GameObject.Find("Birdy");
rndr = GetComponent<Renderer>();
bx2D = GetComponent<BoxCollider2D>();
rndr.enabled = true;
bx2D.enabled = true;
}
void FixedUpdate()
{
disBetweenBirdyAndEnemy = Vector3.Distance(birdy.transform.position, this.transform.position);
if (disBetweenBirdyAndEnemy <= startAnimDistance)
{
if(!coinCollected)
{
rndr.enabled = true;
bx2D.enabled = true;
}
}
else
{
rndr.enabled = false;
bx2D.enabled = false;
}
coinText.text = "x" + coinCounter.ToString();
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.name == "Birdy") // detect collision with ground game object
{
AddCoinAndRemoveHitBoxAndSprite();
}
}
private void AddCoinAndRemoveHitBoxAndSprite()
{
coinCounter++;
coinCollected = true;
rndr.enabled = false;
bx2D.enabled = false;
PlayCoinSound();
}
private void PlayCoinSound()
{
AudioSource aS = GameObject.Find("GetCoinSound").GetComponent<AudioSource>();
aS.Play();
}
}
The results are good. The coins render in the scene only when the player is close enough to them. However, once deployed onto my phone - the coins don't render at all any more. As if the players distance towards the coins is different on my phone than it is on my pc.
What am I missing?
I can't see anything obviously wrong with this, but if it is a distance check gone wrong, you could try disabling the entire coin and enable them only if they're on the screen.
A solution which would work with all resolutions is something along the lines of this:
private bool IsOnScreen(Vector3 worldPosition)
{
Vector3 screenPoint = camera.WorldToViewportPoint(worldPosition);
return screenPoint.x > 0
&& screenPoint.x < 1
&& screenPoint.y > 0
&& screenPoint.y < 1;
}
A separate problem that could be solved is that your coin script is responsible for far too many things. Right now every coin
Knows how many coins the player has and can modify that number
Knows about and is modifying the UI text for coins on start
Is responsible for enabling/disabling its own components, when you could just destroy the gameObject when it's collected
Finds an audio gameObject and playing a sound
Because of all these responsibilities you'll have a harder time solving issues within this class. Consider having some sort of CoinManager class which can keep track of how many coins the player has (instead of every single coin knowing about the coin count).
Your class might look something like this
public class CoinManager : MonoBehaviour
{
private Camera camera;
[SerializeField] private AudioClip coinCollectedSound;
[SerializeField] private AudioSource audioSource;
[SerializeField] private List<CoinScript> coins;
[SerializeField] private int coinCount;
[SerializeField] private Text coinLabel;
// This could be called by some spawner class that
// adds coins to the scene
public void AddCoin(CoinScript coin)
{
coins.Add(coin);
}
public void CoinCollected(CoinScript coin)
{
Destroy(coin.gameObject);
coinCount++;
audioSource.PlayOneShot(coinCollectedSound);
// Alternatively, if you have a dedicated audio source
// for coin sounds you can just use audioSource.Play();
}
private void Update()
{
// Iterate over all the coins we know about
foreach (var coin in coins)
{
// Now that we have a method that tells us whether a
// coin is on the screen or not, we can enable and disable
// all the coins at the same time in one place
coin.transform.enabled = IsOnScreen(coin.transform.position);
}
}
private void Awake()
{
// Gets the main camera in the scene
// https://docs.unity3d.com/ScriptReference/Camera-main.html
camera = Camera.main;
coins = new List<CoinScript>();
}
private bool IsOnScreen(Vector3 worldPosition)
{
Vector3 screenPoint = camera.WorldToViewportPoint(worldPosition);
return screenPoint.x > 0
&& screenPoint.x < 1
&& screenPoint.y > 0
&& screenPoint.y < 1;
}
}
I don't have Unity in front of me so the above is off the top of my head.

Memory overload on for loop

I am making an object spawn script in which on the start of the script the spawning function is called and in it is a for loop which creates an object each iteration. It first picks a random X position for it and then checks if it is in a range of another prefabs coordinates so they don't spawn too close or worse, one in each other. If it is in the same coordinates as another prefab it will return 0 and this same goes out for the Z axis too. It also picks a random Y axis rotation so it doesn't all face the same direction. After this it spawns the prefab and sets it's coordinates and rotation after which it check if the coordinates in the X or Z axis are 0, and if any of those two are 0 it goes back one iteration and the last object to be spawned is destroyed so it doesn't flood. This works perfectly but when you want to set it to spawn too much objects it floods the RAM because there is nowhere to spawn more objects. I tried finding the highest X position and highest Z position and multiplying them, and setting them both to positive, and then dividing them by the space between the prefabs but this doesn't work as it sets it to a really really high number. How would you fix this?
Script:
using UnityEngine;
using System.Collections;
public class PrefabSpawner : MonoBehaviour {
public int amountOfPrefabs;
public int maxAmountOfPrefabs;
private int currentSpawnedPrefab;
public float spaceBetweenPrefabs;
private float positionX;
private float positionZ;
private float maxPositionX;
private float maxPositionZ;
private float multipliedPosXZ;
private bool previousSpawnHadZero;
public GameObject prefab;
private GameObject point1;
private GameObject point2;
private GameObject currentSpawn;
private Vector2[] positions;
void Start () {
currentSpawnedPrefab = 0;
previousSpawnHadZero = false;
point1 = gameObject.transform.GetChild (0).gameObject;
point2 = gameObject.transform.GetChild (1).gameObject;
if (point1.transform.position.x > point2.transform.position.x)
maxPositionX = point1.transform.position.x;
else
maxPositionX = point2.transform.position.x;
if (point1.transform.position.z > point2.transform.position.z)
maxPositionZ = point1.transform.position.z;
else
maxPositionZ = point2.transform.position.z;
multipliedPosXZ = maxPositionX * maxPositionZ;
if (multipliedPosXZ < 0)
multipliedPosXZ += multipliedPosXZ + multipliedPosXZ;
maxAmountOfPrefabs = Mathf.FloorToInt (multipliedPosXZ / spaceBetweenPrefabs);
if (amountOfPrefabs > maxAmountOfPrefabs)
amountOfPrefabs = maxAmountOfPrefabs;
point1.GetComponent<MeshRenderer> ().enabled = false;
point2.GetComponent<MeshRenderer> ().enabled = false;
gameObject.GetComponent<MeshRenderer> ().enabled = false;
positions = new Vector2[amountOfPrefabs];
SpawnPrefabs (amountOfPrefabs);
}
void SpawnPrefabs (int amount) {
for (int i = 0; i < amount; i++) {
if(previousSpawnHadZero)
i -= 1;
currentSpawn = (GameObject)Instantiate (prefab);
positionX = GetRandomPositionX ();
positionZ = GetRandomPositionZ ();
currentSpawn.transform.position = new Vector3 (positionX, this.transform.position.y + currentSpawn.transform.localScale.y, positionZ);
currentSpawnedPrefab += 1;
if (positionX == 0 || positionZ == 0) {
previousSpawnHadZero = true;
currentSpawnedPrefab -= 1;
Destroy (currentSpawn);
}
if (positionX != 0 && positionZ != 0) {
previousSpawnHadZero = false;
positionX = 0;
positionZ = 0;
}
}
}
IEnumerator Pause () {
yield return null;
}
float GetRandomPositionX () {
//Finds a random position for the X axis and then checks it and returns either 0 if the position is taken or the position if not
}
float GetRandomPositionZ () {
//Finds a random position for the Z axis and then checks it and returns either 0 if the position is taken or the position if not
}
bool CheckPositionAvailable (float pos, int axis) {
//Checks if the position is available.
}
}
Code is really long to debug but the problem is clearly visible and is from the SpawnPrefabs function. Currently, when you instantiate a prefab, you check if the generated position is 0. If 0, you subtract 1 from the i in the for loop then destroy the instantiated object and then start the for loop again from the current loop-1.
So the combination of Instantiate, Destroy and repeating it over again in the for loop is causing the memory issue.
What to do:
You have to re-write the whole function and this will require modification in your whole code too. Do not instantiate and destroy object in that loop unless when needed.
1.In the Start() function, create one prefab.
2.Make it to be invisible in the scene by disabling its mesh/sprite renderer.
3.Use that prefab in the for loop to check if the generated position is valid. If it is valid, you can now create/instantiate an object in the loop.
This prevents instantiating and destroying objects in the loop when you only create objects when if (positionX != 0 && positionZ != 0) .

XNA 2D Bulletcollision (list with a list)

Continuing with my game here (a game where the player is a ship and you shoot down meteors). I am currently working on the ship shooting bullets and I am trying to remove the meteors as they get hit by the bullets. So here's what I've done. I am almost there, but I have found an error in my coding. In my game, there spawns meteors outside the right side of the map and they travel to the left and the point is to shoot them down, this is working if you shoot the meteors in right order but not otherwise. Let me explain it with a picture.
If I were to shoot down the second meteor, the meteor marked with number 1 would get destroyed
first
//spawns the enemies.
public void LoadEnemies()
{
int randY = random.Next(100, 500);
if (spawn > 1)
{
spawn = 0;
if (enemies.Count() < 4)
enemies.Add(new Enemies(Content.Load<Texture2D>("meteor"), new Vector2(1110, randY)));
}
//Here's where the error lies because of the bulletcolliding (I think)
for (int i = 0; i < enemies.Count; i++)
{
if (!enemies[i].isVisible || bulletColliding)
{
bulletColliding = false;
enemies.RemoveAt(i);
i--;
}
}
}
Collision method.
public void bulletCollision(GameTime gameTime)
{
foreach (var x in bullets)
{
foreach (var y in enemies)
{
enemy_rect = new Rectangle((int)y.position.X, (int)y.position.Y, 10, 10);
bullet_rect = new Rectangle((int)x.position.X, (int)x.position.Y, 10, 10);
if (bullet_rect.Intersects(enemy_rect))
{
bulletColliding = true;
}
}
}
}
Basically, I am clueless on how to remove the specific meteor that gets hit, and I need your help. I Appreaciate all help I get.
DonĀ“t use only bool bulletColliding. Use also integer which shows the number of enemy which should be destroyed and in each enemy class keep information of his number.
The second option can be keeping a bool ifDestroy in enemy class which will be false initially, and change it to true if the enemy should be destroyed. You can check it in loadEnemies().
Basically this is a further explanation of Sabrina Le's answer.
You want the enemy objects themselves to have a bool called bulletColliding and in your collision code it would look more like...
public void bulletCollision(GameTime gameTime)
{
foreach (var x in bullets)
{
foreach (var y in enemies)
{
enemy_rect = new Rectangle((int)y.position.X, (int)y.position.Y, 10, 10);
bullet_rect = new Rectangle((int)x.position.X, (int)x.position.Y, 10, 10);
if (bullet_rect.Intersects(enemy_rect))
{
y.bulletColliding = true;
}
}
}
}
and then your enemy loading code would look more like...
//spawns the enemies.
public void LoadEnemies()
{
int randY = random.Next(100, 500);
if (spawn > 1)
{
spawn = 0;
if (enemies.Count() < 4)
enemies.Add(new Enemies(Content.Load<Texture2D>("meteor"), new Vector2(1110, randY)));
}
//Here's where the error lies because of the bulletcolliding (I think)
for (int i = 0; i < enemies.Count; i++)
{
if (!enemies[i].isVisible || enemies[i].bulletColliding)
{
enemies[i].bulletColliding = false;
enemies.RemoveAt(i);
i--;
}
}
}
By strictly having a single bulletColliding variable, what is happening is when any meteor is hit, then it is set to true. So when your method checks the meteors sequentially it sees that bulletColliding is set to true even when it checks the first object, and the code inside the if runs. Using enemy specific booleans will let you know which enemy got hit, and will be removed accordingly.

Categories

Resources