I'm experimenting with the CullingGroup Api, however i'm unable to achieve any success because it looks like that my onStateChanged callback will not get called.
I have 24 spheres and a cube which has the following script attached to.
using UnityEngine;
public class CullingGroupBehaviour : MonoBehaviour
{
CullingGroup localCullingGroup;
public Transform[] Spheres;
public BoundingSphere[] cullingPoints;
void Awake()
{
localCullingGroup = new CullingGroup();
GameObject[] spheres = GameObject.FindGameObjectsWithTag("Spheres");
cullingPoints = new BoundingSphere[spheres.Length];
Spheres = new Transform[spheres.Length];
for (var i = 0; i < spheres.Length; i++)
{
Spheres[i] = spheres[i].transform;
cullingPoints[i].position = Spheres[i].position;
cullingPoints[i].radius = 4.0f;
}
localCullingGroup.onStateChanged = (CullingGroupEvent evt) => Debug.Log("Changed");
localCullingGroup.SetBoundingSpheres(cullingPoints);
localCullingGroup.SetBoundingSphereCount(cullingPoints.Length);
localCullingGroup.SetBoundingDistances(new float[] { 10.0f, 50.0f });
localCullingGroup.SetDistanceReferencePoint(transform.position);
}
void FixedUpdate()
{
localCullingGroup.SetDistanceReferencePoint(transform.position);
for (var i = 0; i < Spheres.Length; i++)
{
cullingPoints[i].position = Spheres[i].position;
}
}
void OnDestroy()
{
localCullingGroup.Dispose();
localCullingGroup = null;
}
}
The expected behaviour is that when i'm moving the cube the distances should change and the lambda expression should be called yet nothing happens.
Any ideas are appreciated!
Update:
The strange thing is that visibility events are sent correctly when using a camera but distance events does not trigger
Looks like you must set a camera in order to calculate distances, which is strange because the DOC says
If targetCamera is assigned then the bounding spheres will only be
culled from the perspective of that camera.
and
To have the CullingGroup perform visibility calculations, specify the
camera it should use
Yet nothing happens when you dont set the camera.
Additional Note:
The onStateChnaged event will only trigger when a BoundingDistance was passed eg: from 1 you enter into 2 (The example only contains one)
Working Solution:
using UnityEngine;
public class CullingGroupBehaviour : MonoBehaviour
{
private CullingGroup cullingGroup;
private BoundingSphere[] bounds;
Transform[] targets;
public Transform ReferencePoint;
void Start()
{
// All the objects that have a sphere tag
var gobjs = GameObject.FindGameObjectsWithTag("Sphere");
targets = new Transform[gobjs.Length];
for(int i = 0; i < gobjs.Length; i++)
{
targets[i] = gobjs[i].transform;
}
cullingGroup = new CullingGroup();
cullingGroup.targetCamera = Camera.main;
// Will automatically track the transform
cullingGroup.SetDistanceReferencePoint(transform);
// The distance points when the event will trigger
cullingGroup.SetBoundingDistances(new float[] { 25.0f });
// Creating Boundingspheres
bounds = new BoundingSphere[targets.Length];
for (int i = 0; i < bounds.Length; i++)
{
bounds[i].radius = 1.5f;
}
// Assigning the Bounding spheres
cullingGroup.SetBoundingSpheres(bounds);
// if not set it will use all of the array elements(so below code is redundant)
cullingGroup.SetBoundingSphereCount(targets.Length);
// Assigning an event when the distance changes
cullingGroup.onStateChanged = OnChange;
}
void Update()
{
for (int i = 0; i < bounds.Length; i++)
{
bounds[i].position = targets[i].position;
}
}
void OnDestroy()
{
cullingGroup.Dispose();
cullingGroup = null;
}
void OnChange(CullingGroupEvent ev)
{
if (ev.currentDistance > 0)
{
targets[ev.index].gameObject.GetComponent<Renderer>().material.color = Color.green;
}
else
{
targets[ev.index].gameObject.GetComponent<Renderer>().material.color = Color.red;
}
}
}
Related
I have a 3D board game in Unity. I would like to move my character without having to press a key, but most importantly I would like to show a dynamic panel in canvas for whatever square the character lands on. So far I have the dice rolling and the character moving (after pressing a key) the correct amount of squares, but I am unable to figure out how to activate the panel based on the square color. Any help would be appreciated.
Here is my CharacterScript:
public class CharacterScript : MonoBehaviour
{
public Path currentPath;
public int squarePosition;
public int squares;
bool isMoving;
public GameObject PinkSquarePanel = GameObject.FindGameObjectWithTag("PinkSquare");
public GameObject CyanSquarePanel = GameObject.FindGameObjectWithTag("CyanSquare");
public GameObject WhiteSquarePanel = GameObject.FindGameObjectWithTag("WhiteSquare");
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape) && !isMoving)
{
squares = DiceNumberTextScript.diceNumber;
}
StartCoroutine(Move());
if (squares == 0)
{
ActivateSquarePanel();
}
}
IEnumerator Move()
{
if (isMoving)
{
yield break;
}
isMoving = true;
while (squares > 0)
{
Vector3 nextPosition = currentPath.squareList[squarePosition + 1].position;
while (MoveToNextSquare(nextPosition))
{
yield return null;
}
yield return new WaitForSeconds(0.1f);
squares--;
squarePosition++;
}
isMoving = false;
}
bool MoveToNextSquare(Vector3 goal)
{
return goal != (transform.position = Vector3.MoveTowards(transform.position, goal, 4f * Time.deltaTime));
}
void ActivateSquarePanel()
{
if (squarePosition.Equals(PinkSquarePanel))
{
PinkSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(CyanSquarePanel))
{
CyanSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(WhiteSquarePanel))
{
WhiteSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
}
}
And here is my PathScript:
public class Path : MonoBehaviour
{
Transform[] squareObjects;
public List<Transform> squareList = new List<Transform>();
GameObject[] PinkSquares = GameObject.FindGameObjectsWithTag("PinkSquare");
PinkSquare[] pinkList = new PinkSquare[1];
GameObject[] CyanSquares = GameObject.FindGameObjectsWithTag("CyanSquare");
CyanSquare[] cyanList = new CyanSquare[1];
GameObject[] WhiteSquares = GameObject.FindGameObjectsWithTag("WhiteSquare");
WhiteSquare[] whiteList = new WhiteSquare[1];
private void OnDrawGizmos()
{
Gizmos.color = Color.black;
FillSquares();
for (int i = 0; i < squareList.Count; i++)
{
Vector3 currentPosition = squareList[i].position;
if (i > 0)
{
Vector3 previousPosition = squareList[i - 1].position;
Gizmos.DrawLine(previousPosition, currentPosition);
if(currentPosition.Equals(PinkSquares))
{
pinkList[i] = new PinkSquare();
}
else if (currentPosition.Equals(CyanSquares))
{
cyanList[i] = new CyanSquare();
}
else if (currentPosition.Equals(WhiteSquares))
{
whiteList[i] = new WhiteSquare();
}
}
}
}
void FillSquares()
{
squareList.Clear();
squareObjects = GetComponentsInChildren<Transform>();
foreach (Transform square in squareObjects)
{
if (square != this.transform)
{
squareList.Add(square);
}
}
}
}
I believe your issue is in your comparisons, you are trying to use an Equals to compare your currentPosition which is a Vector3 type to a GameObject[] which is an array of gameObjects. As I mentioned in my comments, this comparison will always fail as an array of gameObjects can not be equal to a vector.
Instead of using these lines, try this line:
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
The full snippet of if else would look like
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
{
pinkList[i] = new PinkSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("CyanSquare")
{
cyanList[i] = new CyanSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("WhiteSquare")
{
whiteList[i] = new WhiteSquare();
}
Your CharacterScript is going to need to get the gameObject or Transform from the Path script as it is only keeping track of indexes. Your issue in this script is you are comparing an integer to a GameObject which would never be true. I would also recommend not using OnDrawGizmos() as it is an editor only script and should only be used to render editor debugging tools. The only reference to a Gizmo I see in the function is Gizmos.color = Color.black; which does nothing as you are not rendering a gizmo anywhere. I would move this code to a different function and call it from your CharacterScript. Have the return type be GameObject or Transform of the square you are on, so the CharacterSCript can check which color it lands on. Using an Integer nor Vector3 to compare to a GameObject[] will never work.
I am not sure if there are issues elsewhere in the code, but as this comparison would always fail, none of these statements would get broken into. What this means is your panels would never have the chance to get their alpha set nor get created.
Hi I am working on game that uses random terrain and I want to spawn objects onto that terrain. To do this, I have created what I have called the Surface Populator Script.
This is the script:
public SurfaceSpawnerData spawnerData;
private float randomX;
private float randomZ;
private Renderer r;
void Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
SpawnObjects();
}
void Update()
{
}
void SpawnObjects()
{
RaycastHit hit;
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
int currentObjects = spawnerData.spawnableObjects[i].currentObjects;
int numOfObjects = spawnerData.spawnableObjects[i].numberOfObjects;
if (currentObjects != numOfObjects)
{
if (Physics.Raycast(new Vector3(randomX, r.bounds.max.y + 5f, randomZ), -Vector3.up, out hit))
{
randomX = Random.Range(r.bounds.min.x, r.bounds.max.x);
randomZ = Random.Range(r.bounds.min.z, r.bounds.max.z);
if (hit.point.y >= spawnerData.spawnableObjects[i].spawnerStartHeight && hit.point.y <= spawnerData.spawnableObjects[i].spawnerEndHeight)
{
spawnerData.SpawnedObjects.Add(Instantiate(spawnerData.spawnableObjects[i].spawnablePrefab, hit.point, Quaternion.identity));
spawnerData.spawnableObjects[i].currentObjects += 1;
}
}
}
}
}
The script also gains its data from a scriptable object:
[CreateAssetMenu]
public class SurfaceSpawnerData : ScriptableObject
{
public SpawnableObjects[] spawnableObjects;
public List<GameObject> SpawnedObjects;
[System.Serializable]
public class SpawnableObjects
{
public GameObject spawnablePrefab;
public float spawnerStartHeight = 2f;
public float spawnerEndHeight;
public int currentObjects;
public int numberOfObjects;
}
}
This script currently works perfectly fine when placed inside the update method, however I do not want to do this due to its affect on performance. Therefore I am wondering if there is a way to stop the Unity start method from exiting until my SpawnObjects() function has stopped running. If this is not possible if you have any other ideas on how I run this only once without using the update function let me know.
I am relatively new to c# as a language and I'm sorry if there is an easy fix that I have missed. Any help would be appreciated. Thanks.
Since SpawnObjects is a synchronus method Start will not return until SpawnObjects finished anyway.
As far as I understand your issue is rather that anything from Physics is not available during initialization (Awake, OnEnable, Start) but only within or after the Physics block (see ExecutionOrder) so e.g. in a method like FixedUpdate or Update.
So to answer your question: You could use a Coroutine and WaitForFixedUpdate in order to make your Instantiation:
void Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
StartCoroutine(DoInstantiate());
}
private IEnumerator DoInstantiate()
{
// wait until Physics are initialized
yield return new WaitForFixedUpdate();
SpawnObjects();
}
or as you can see in ScriptReference/Coroutine you can make this shorter by directly making the Start a routine e.g. like
IEnumerator Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
// wait until Physics are initialized
yield return new WaitForFixedUpdate();
SpawnObjects();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public int delay = 3;
public int stairsNumber = 5;
public int stairsHeight = 0;
public Vector3 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start ()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update ()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsSize.x; i++)
{
for (float k = 0; k <= stairsSize.y; k++)
{
stairsPosition = new Vector3(i, stairsHeight, k);
GameObject stairs = Instantiate(stairsPrefab, stairsPosition, Quaternion.identity);
stairs.transform.localScale = new Vector3(stairsSize.x, 1 , stairsSize.y);
stairsHeight += 1;
yield return new WaitForSeconds(delay);
}
}
}
private void CalculateNextStair()
{
}
}
I messed it up. For example I want to build 5 stairs but the loops are over the stairs size and not number of stairs.
Second it's creating 10 sets of stairs not 5 stairs:
Another problem is how can I make that each stair will be build slowly ? Now it's just Instantiate slowly with delay but how can I generate each stair with delay?
Screenshot of the script inspector:
My current code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public float delay = 0.3f;
public int stairsNumber = 5;
public int stairsPositions = 0;
public int stairsStartPositionHeight = 0;
public float stairsScalingHaight = 1;
public Vector2 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsNumber; i++)
{
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsPositions, stairsPositions);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
stairsScalingHaight,
stairsSize.y);
stairsStartPositionHeight += 1;
yield return new WaitForSeconds(delay);
}
}
private void CalculateNextStair()
{
}
}
There's no reason to loop over the size of the stairs at all; you want to loop over stairsNumber, which is yet unused in your code.
Also, you don't need to change the x component of your stairs' positions. Keep it at 0f (or whatever you need).
The y and z components of your stairs positions (relative to the starting point) should both be factors of stairHeight. In this particular case, you want them to be equal to stairHeight, so that you get "square" step shapes.
private IEnumerator BuildStairs()
{
for (int i = 0; i <= stairsNumber ; i++) {
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsHeight, stairsHeight);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
1f ,
stairsSize.y);
stairsHeight += 1f;
yield return new WaitForSeconds(delay);
}
}
If you change stairSize to be a Vector3, then you can just use stairSize directly as the localScale, and increment stairsHeight by stairsSize.y instead of just 1f.
If you want to offset the starting position of your stairs, you need to include an offset. I recommend keeping it separate from the height counter until you need to add them.
Also, if you want to have rectangular sized steps, keep a widthFactor to multiply by the height to find how far each step moves horizontally.
Combining these changes might look like this:
Vector3 stairSize;
float stepWidthFactor=1f;
Vector3 stairsStartPosition;
private IEnumerator BuildStairs() {
for (int i = 0; i <= stairsNumber ; i++) {
stairsPosition = new Vector3(
stairsStartPosition.x,
stairsStartPosition.y + stairsHeight,
stairsStartPosition.z + stairsHeight*stepWidthFactor);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairsHeight += stairsSize.y;
stairs.transform.localScale = stairSize;
yield return new WaitForSeconds(delay);
}
}
In the manager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FormationsManager : MonoBehaviour
{
public Transform squadMemeberPrefab;
public int numberOfSquadMembers = 20;
public int columns = 4;
public int gaps = 10;
public Formations formations;
private int numofmembers;
// Use this for initialization
void Start()
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
// Update is called once per frame
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
for (int i = 0; i < formations.newpositions.Count; i++)
{
go = Instantiate(squadMemeberPrefab);
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
}
}
And the Formations script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Formations : MonoBehaviour
{
public List<Vector3> newpositions;
private int numberOfSquadMembers;
private int columns;
private int gaps;
private List<Quaternion> quaternions;
private Vector3 FormationSquarePositionCalculation(int index)
{
float posX = (index % columns) * gaps;
float posY = (index / columns) * gaps;
return new Vector3(posX, posY);
}
private void FormationSquare()
{
newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
for (int i = 0; i < numberOfSquadMembers; i++)
{
Vector3 pos = FormationSquarePositionCalculation(i);
Vector3 position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
newpositions.Add(position);
}
}
public void Init(int numberOfSquadMembers, int columns, int gaps)
{
this.numberOfSquadMembers = numberOfSquadMembers;
this.columns = columns;
this.gaps = gaps;
FormationSquare();
}
}
What I want to do is in the FormationsManager in the Update not only just calling GenerateSquad but to add the new once to the last/next position of the existing already formation.
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
If the value of numberOfSquadMembers is 20 first time and then I changed it to 21 add new object to the end of the formation and same if I change the value of numberOfSquadMembers for example from 20 to 19 or from 21 to 5 destroy the amount of objects from the end and keep the formation shape.
The soldiers the last line is on the right side.
So if I change the value to add more then add it to the right and if I change to less destroy from the right side. The most left line of soldiers is the first.
It is possible if you keep GameObject instances inside FormationsManager class, and then reuse them in GenerateSquad method.
In FormationsManager class, add and modify code as follows.
public GameObject squadMemeberPrefab;
List<GameObject> SquadMembers = new List<GameObject>();
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
List<GameObject> newSquadMembers = new List<GameObject>();
int i = 0;
for (; i < formations.newpositions.Count; i++)
{
if (i < SquadMembers.Count)
go = SquadMembers[i];
else
{
go = Instantiate(squadMemeberPrefab);
newSquadMembers.Add(go);
}
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
for (; i < SquadMembers.Count; i++)
Destroy(SquadMembers[i]);
SquadMembers = newSquadMembers;
}
However, I recommend you to consider GameObject Pool (Object Pool), which can thoroughly resolve such object recycle problem. For this purpose, you can use ClientScene.RegisterSpawnHandler. Go to this Unity Documentation page and search text "GameObject pool". You can see an example code there.
Hi everyone I have been making and experimenting on an 3d endless runner game and came with an issue. Here is what I meant to achieve. I have two list of gameobjects called activeTiles and deactivatedTiles. According to my idea I want to first add all the prefab tiles to the deactivatedtTiles list. Next I have a float amtTileOnScreen variable which controls the amount of tiles put in front of the player to run. Then I take a random Tile from the list of deactivated tiles to activated tiles by considering the amount of tiles o screen and put in-front of the player. Used tiles are put back to deactivated tile list and the whole cycle begins.
The question is how do I achieve this? Help would be appreciated.
Here's what I have tried.
public class TileManager : MonoBehaviour
{
public GameObject[] tilePrefabs;
private Transform playerTransform;
private float spawnZ = -12f;
private float tileLength = 24.0f;
private int amtOfTilesOnScreen = 5;
private float safeZone = 56.0f;
private GameObject spawnedTile;
public static List<GameObject> activeTiles;
public static List<GameObject> deactivatedTiles;
private int lastPrefabIndex = 0;
private Vector3 transformTiles;
// Use this for initialization
void Start ()
{
activeTiles = new List<GameObject>();
deactivatedTiles = new List<GameObject>();
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
for (int i = 0; i < amtOfTilesOnScreen; i++)
{
if (i < 1)
{
activeTiles.Add(SpawnTileAtFront(0));
}
else
{
activeTiles.Add(SpawnTileAtFront());
}
}
}
void Update ()
{
if (playerTransform.position.z - safeZone > (spawnZ - amtOfTilesOnScreen * tileLength))
{
for (int i = 0; i < tilePrefabs.Length; i++)
{
spawnedTile = SpawnTileAtFront();
deactivatedTiles.Add(spawnedTile);
Debug.Log(deactivatedTiles.Count);
}
if (activeTiles.Count < (amtOfTilesOnScreen + 1))
{
activeTiles.Add(GetRandomDeactivatedTile());
MoveTileToTheFront(GetRandomDeactivatedTile());
}
else
{
var disposeTile = activeTiles[0];
deactivatedTiles.Add(disposeTile);
DisposeActiveTiles(0);
}
}
}
private void MoveTileToTheFront(GameObject tile)
{
tile.transform.position = Vector3.forward * spawnZ;
spawnZ += tileLength;
}
private GameObject SpawnTileAtFront(int prefabIndex = -1)
{
GameObject go;
if (prefabIndex == -1)
{
go = Instantiate(tilePrefabs[RandomPrefabIndex()]) as GameObject;
}
else
{
go = Instantiate(tilePrefabs[prefabIndex]) as GameObject;
}
go.transform.SetParent(transform);
MoveTileToTheFront(go);
return go;
}
private void DisposeActiveTiles(int index)
{
GameObject unusedTile = activeTiles[index];
activeTiles.RemoveAt(index);
deactivatedTiles.Add(unusedTile);
}
private GameObject GetRandomDeactivatedTile()
{
if (deactivatedTiles.Count == 0)
return null;
int randomIndex = Random.Range(0, deactivatedTiles.Count);
GameObject unusedTile = deactivatedTiles[randomIndex];
deactivatedTiles.RemoveAt(randomIndex);
return unusedTile;
}
private int RandomPrefabIndex()
{
if (tilePrefabs.Length <= 0)
{
return 0;
}
int randomIndex = lastPrefabIndex;
while (randomIndex == lastPrefabIndex)
{
randomIndex = Random.Range(0, tilePrefabs.Length);
}
lastPrefabIndex = randomIndex;
return lastPrefabIndex;
}
}
void Update ()
{
while (playerTransform.position.z - safeZone > (spawnZ - amtOfTilesOnScreen * tileLength))
{
// we need to add a new tile in front of the player
GameObject t;
if (deactivatedTiles.Count == 0) {
// no deactivated tiles so we need to instantiate a new tile
t = SpawnTileAtFront ();
} else {
// otherwise take deactivated tile into use
t = GetRandomDeactivatedTile ();
MoveTileToTheFront (t);
}
// new tile is now active tile
activeTiles.Add (t);
// take oldest active tile and move it to deactivated list
DisposeActiveTiles(0);
}
}