Objects not pooling correctly - c#

I'm having a problem pooling some objects. I've looked at a starter file we got in class to recreate the scene but different, but I'm not having the correct results and I can't seem to figure it out :/
What I have is:
Environment which has 3 child object pools (ground, dew, corona). Ground has 3 childs (planes), dew and corona both have 10 childs (models).
What I'm trying to do is move the first object in an array to the last place of the array and changing the z position to move it forward once the player has ran past it.
My ground objects correctly go forward once my character has passed a certain position, the dews and coronas however do not, and I don't understand why.
This is the code I'm using:
public class Environment : MonoBehaviour
{
List<Transform> dews = new List<Transform>();
List<Transform> coronas = new List<Transform>();
List<Transform> grounds = new List<Transform>();
Transform player;
int propsCount = 0;
int groundCount = 0;
// Use this for initialization
void Start()
{
player = GameObject.Find("Player").transform;
dews = GameObject.Find("DewPool").GetComponentsInChildren<Transform>().ToList();
coronas = GameObject.Find("CoronaPool").GetComponentsInChildren<Transform>().ToList();
grounds = GameObject.Find("GroundPool").GetComponentsInChildren<Transform>().ToList();
dews.RemoveAt (0);
coronas.RemoveAt (0);
grounds.RemoveAt (0);
}
// Update is called once per frame
void Update()
{
// Voeg hier code toe voor Object Pooling!
Vector3 nieuwePos;
if (player.transform.position.z > dews[0].transform.position.z + 5)
{
dews[0].transform.position = nieuwePos = new Vector3(dews[0].transform.position.x, dews[0].transform.position.y, dews[0].transform.position.z + 50);
dews.Add(dews[0]);
dews.RemoveAt(0);
}
if (player.transform.position.z > coronas[0].transform.position.z + 5)
{
coronas[0].transform.position = nieuwePos = new Vector3(coronas[0].transform.position.x, coronas[0].transform.position.y, coronas[0].transform.position.z + 55);
coronas.Add(coronas[0]);
coronas.RemoveAt(0);
}
if (player.transform.position.z > grounds[0].transform.position.z + 40)
{
grounds[0].transform.position = nieuwePos = new Vector3(grounds[0].transform.position.x, grounds[0].transform.position.y, grounds[0].transform.position.z + 120);
grounds.Add(grounds[0]);
grounds.RemoveAt(0);
}
}
}
All the pools containing the objects are positioned at 0, the dews and coronas start at Z-index 7.5 and 10 respectively and the copies are always 5 Z further.
Player is an empty gameobject positioned at 0 - 0 - 4.4 containing the model Ethan.
The first dew and corona correctly get pushed backwards, the rest is very buggy.
If anyone can help out it would be greatly appreciated, thanks in advance!

Fixed it by adding
for (int i = dews.Count-1; i > 0; i-=2) {
dews.RemoveAt (i);
}
for (int i = coronas.Count-1; i > 0; i-=2) {
coronas.RemoveAt (i);
}
into my start() function, my objects had another child while my ground objects did not.
Probably not the best solution but for now I'm okay with this :-)

Related

Instantiate predefined number of object along a raycast in Unity

I have a raycast that's being rendered every frame based on 2 points, and those 2 points change position each frame.
What I need is a system that doesn't need a direction, or a number of objects, but instead takes in 2 points, and then instantiates or destroys as many objects as necessary to get the instantiated objects from one side to another minus spaceBetweenPoints. If you wanted you could think of it as an Angry Birds Style slingshot HUD, except without gravity, and based on raycasts.
My Script:
public int numOfPoints; // The number of points that are generated (This would need to chnage based one the distance in the end)
public float spaceBetweenPoints; // The spacing between the generated points
private GameObject[] predictionPoints; // The prefab to be gernerated
private Vector2 firstPathStart; // The starting point for the raycast (Changes each frame)
private Vector2 firstPathEnd; // The ending point for the raycast (Changes each frame)
void start()
{
predictionPoints = new GameObject[numOfPoints];
for (int i = 0; i < numOfPoints; i++)
{
predictionPoints[i] = Instantiate(predictionPointPrefab, firePoint.position,
Quaternion.identity);
}
}
void Update
{
Debug.DrawLine(firstPathStart, firstPathEnd, UnityEngine.Color.black);
DrawPredictionDisplay();
}
void DrawPredictionDisplay()
{
for (int i = 0; i < numOfPoints; i++)
{
predictionPoints[i].transform.position = predictionPointPosition(i * spaceBetweenPoints);
}
}
Vector2 predictionPointPosition(float time)
{
Vector2 position = (Vector2)firstPathStart + direction.normalized * 10f * time;
return position;
}
The current system simply takes in a starting position, a direction, and then moves a preset number of objects in that direction based on time. This way of doing it also causes problems because it's endess instead of only going till the end of the raycast: (Pardon my drawing skills)
Blue line = raycast
Black dots = instantiated prefab
Orange dot = raycast orange
Green dot = end of raycast
Notes:
direction is the momentum which I set in the editor, I needed it to put together what I currently have working, but it shouldn't be necessary when running based on points.
If you ask me I would say it is kinda easy if you know little bit of Math trickery. I'm not saying that I'm very good at Math, but once you get it it's kind of easy to pull off next time. Here if I try to explain everything, i won't be able to explain clearly. Take a look as the code below I've commented the whole code so that you can understand easily.
Basically I used a Method called Vector2.Lerp() Liner Interpolation, which means that this method will return value between point1, and point2 based on the value of 3rd argument t which goes from 0 to 1.
public class TestScript : MonoBehaviour
{
public Transform StartPoint;
public Transform EndPoint;
public float spaceBetweenPoints;
[Space]
public Vector2 startPosition;
public Vector2 endPosition;
[Space]
public List<Vector3> points;
private float distance;
private void Update()
{
startPosition = StartPoint.position; //Setting Starting point and Ending point.
endPosition = EndPoint.position;
//Finding the distance between point
distance = Vector2.Distance(startPosition, endPosition);
//Generating the points
GeneratePointsObjects();
Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
}
private void OnDrawGizmos()
{
//Drawing the Dummy Gizmo Sphere to see the points
Gizmos.color = Color.black;
foreach (Vector3 p in points)
{
Gizmos.DrawSphere(p, spaceBetweenPoints / 2);
}
}
private void OnValidate()
{
//Validating that the space between two objects is not 0 because that would be Raise an exception "Devide by Zero"
if (spaceBetweenPoints <= 0)
{
spaceBetweenPoints = 0.01f;
}
}
private void GeneratePointsObjects()
{
//Vlearing the list so that we don't iterate over old points
points.Clear();
float numbersOfPoints = distance / spaceBetweenPoints; //Finding numbers of objects to be spawned by dividing "distance" by "spaceBetweenPoints"
float increnment = 1 / numbersOfPoints; //finding the increment for Lerp this will always be between 0 to 1, because Lerp() takes value between 0 to 1;
for (int i = 1; i < numbersOfPoints; i ++)
{
Vector3 v = Vector2.Lerp(startPosition, endPosition, increnment * i); //Find next position using Vector2.Lerp()
points.Add(v);//Add the newlly found position in List so that we can spwan the Object at that position.
}
}
}
Update: Added, How to set prefab on the positions
I just simply Destroyed old objects and Instantiated new Objects. But remember instantiating and Destroying object frequently in your game in unity will eat-up memory on your player's machine. Os I would suggest you to use Object-Pooling. For the reference I'll add a link to tutorial.
private void Update()
{
startPosition = StartPoint.position; //Setting Starting point and Ending point.
endPosition = EndPoint.position;
//Finding the distance between point
distance = Vector2.Distance(startPosition, endPosition);
//Generating the points
GeneratePointsObjects();
//Update: Generating points/dots on all to location;
InstenciatePrefabsOnPositions();
Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
}
private void InstenciatePrefabsOnPositions()
{
//Remove all old prefabs/objects/points
for (int i = 0; i < pointParent.childCount; i++)
{
Destroy(pointParent.GetChild(i).gameObject);
}
//Instantiate new Object on the positions calculated in GeneratePointsObjects()
foreach (Vector3 v in points)
{
Transform t = Instantiate(pointPrefab);
t.SetParent(pointParent);
t.localScale = Vector3.one;
t.position = v;
t.gameObject.SetActive(true);
}
}
Hope this helps please see below links for more reference
OBJECT POOLING in Unity
Vector2.Lerp
I hope I understood you right.
First, compute the A to B line, so B minus A.
To get the number of needed objects, divide the line magnitude by the objects' spacing. You could also add the diameter of the prediction point object to avoid overlapping.
Then to get each object position, write (almost) the same for loop.
Here's what I came up with, didn't tested it, let me know if it helps!
public class CustomLineRenderer : MonoBehaviour
{
public float SpaceBetweenPoints;
public GameObject PredictionPointPrefab;
// remove transforms if you need to
public Transform start;
public Transform end;
private List<GameObject> _predictionPoints;
// these are your raycast start & end point, make them public or whatever
private Vector2 _firstPathStart;
private Vector2 _firstPathEnd;
private void Awake()
{
_firstPathStart = start.position;
_firstPathEnd = end.position;
_predictionPoints = new List<GameObject>();
}
private void Update()
{
_firstPathStart = start.position;
_firstPathEnd = end.position;
// using any small value to clamp everything and avoid division by zero
if (SpaceBetweenPoints < 0.001f) SpaceBetweenPoints = 0.001f;
var line = _firstPathEnd - _firstPathStart;
var objectsNumber = Mathf.FloorToInt(line.magnitude / SpaceBetweenPoints);
var direction = line.normalized;
// Update the collection so that the line isn't too short
for (var i = _predictionPoints.Count; i <= objectsNumber; ++i)
_predictionPoints.Add(Instantiate(PredictionPointPrefab));
for (var i = 0; i < objectsNumber; ++i)
{
_predictionPoints[i].SetActive(true);
_predictionPoints[i].transform.position = _firstPathStart + direction * (SpaceBetweenPoints * i);
}
// You could destroy objects, but it's better to add them to the pool since you'll use them quite often
for (var i = objectsNumber; i < _predictionPoints.Count; ++i)
_predictionPoints[i].SetActive(false);
}
}

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) .

How can I display sprites in random places every launching of game in Unity[c#]?

I'm making bubble shooter replica in unity and I encountered a problem with initalizing the bubbles in random places, should I create an array of them in the background class?
For example i have class Sprite1 and Sprite2 and I want to display them in random places in the background how can I do it?
This is how it looks.
http://imgur.com/a/DGpZj
And below it's my attempt to display it by for loop, but dont know the method.
void Start () {
Random.Range(0f,4.5f);
for (int i = 0; i < 100; i++)
{
Sprite.Create(?)
}
}
Displaying your bubbles randomly in the background all depends on what your "randomly" word means: if it like randomly on a grid, completely randomly, can they overlay other bubbles, ...
So here's a first approach to your problem using a grid generation: to generate your bubbles, you can either use the Instantiate method or create new GameObjects.
Your game seems to be a 2D one so I highly recommend you looking for the Unity UI tutorials. Once you'll understand the basics of the UI system, create a Canvas and add an empty game object to it. Afterward you can assign it the below script:
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform))]
public class TestScript: MonoBehaviour
{
[SerializeField]
private float m_BubbleSize;
[SerializeField]
private int m_BubbleColumns;
[SerializeField]
private int m_BubbleRows;
[SerializeField]
private Sprite[] m_BubbleSprites;
private GameObject[][] m_Bubbles;
void Start()
{
GenerateBubbles();
}
private void GenerateBubbles()
{
// Position of the most top left bubble
Vector3 initialPosition = GetComponent<RectTransform>().position;
initialPosition.y += GetComponent<RectTransform>().rect.height * 0.5f;
initialPosition.y -= 0.5f * m_BubbleSize;
initialPosition.x -= (m_BubbleColumns - 1) * 0.5f * m_BubbleSize;
initialPosition.x -= 0.25f * m_BubbleSize;
// Rows height: comes from https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
float rowsHeight = Mathf.Sqrt(6.0f * m_BubbleSize * m_BubbleSize) / 3.0f;
// Bubbles references array
m_Bubbles = new GameObject[m_BubbleColumns][];
for(int i = 0; i < m_Bubbles.Length; i++)
{
m_Bubbles[i] = new GameObject[m_BubbleRows];
}
// Generation
for(int x = 0; x < m_Bubbles.Length; x++)
{
for(int y = 0; y < m_Bubbles[x].Length; y++)
{
GameObject bubble = new GameObject("Bubble_" + x.ToString() + y.ToString(), new System.Type[] { typeof(RectTransform), typeof(Image), typeof(CircleCollider2D) });
bubble.transform.SetParent(transform);
if(y % 2 == 0)
{
bubble.GetComponent<RectTransform>().position = initialPosition + new Vector3(x * m_BubbleSize, -y * rowsHeight, 0.0f);
}
else
{
bubble.GetComponent<RectTransform>().position = initialPosition + new Vector3(0.5f * m_BubbleSize + x * m_BubbleSize, -y * rowsHeight, 0.0f);
}
bubble.GetComponent<RectTransform>().sizeDelta = new Vector2(m_BubbleSize, m_BubbleSize);
bubble.GetComponent<Image>().sprite = m_BubbleSprites[Random.Range(0, m_BubbleSprites.Length)];
bubble.GetComponent<CircleCollider2D>().radius = m_BubbleSize * 0.5f;
}
}
}
}
To complete your game here are the few (maybe not exhaustive list) steps you will need to go through:
Populating a list of the potential "cell" where bubbles can be
Creating a pool of bubble to be fired (with random shuffling)
Writing the shooting + rebounds dynamic (Unity 2D Physic can help with that)
Detecting collision after bubble has been fired
Finding nearest possible "cell"
Searching if enough bubbles are connected (and if so destroying them)
Searching if some of the bubbles aren't connected anymore (and if so deleting them too)
OPTIONAL Implementing a scoring system (and saving the scores)
OPTIONAL Implementing apparition of new bubbles row on top every X bubbles fired
...
Hope this helps,

Code doesn't get played on restart? (Android)

so i made a endless runner on android(i know original right? hmmm..) And everything works fine at first. However when i die and press the restart button with the following code:
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
The game stops generating rooms? Any idea why? This is my generating rooms code:
void AddRoom(float farhtestRoomEndX) {
//pick a random room from the array and instantiate it.
int randomRoom = Random.Range(0, AllRooms.Length);
GameObject room = (GameObject)Instantiate(AllRooms[randomRoom]);
//Find the floor, and pos to instantiate(half of the screen) and add it to active rooms.
float roomWidth = room.transform.FindChild("Floor").localScale.x;
float roomCenter = farhtestRoomEndX + roomWidth * 0.5f;
room.transform.position = new Vector3(roomCenter, 0, 0);
ActiveRooms.Add(room);
}
void GenerateRoomIfRequired() {
//Create a new list with rooms to be deleted, and make a bool to check if rooms need to be added
List<GameObject> DeleteRooms = new List<GameObject>();
bool AddRooms = true;
//Save the playerPos, , calculate the point after when the room needs to be removed and calculate the starting pos for instantiating a new room
float playerX = transform.position.x;
float removeRoomX = playerX - screenWidthInPoints;
float addRoomX = playerX + screenWidthInPoints;
//store the point where the room ens, this is used to instantiate a new room aswell
float farthestRoomEndX = 0;
// use the floor to get the room width and calculate the roomStartX and roomEndX
foreach (var room in ActiveRooms) {
float roomWidth = room.transform.FindChild("Floor").localScale.x;
float roomStartX = room.transform.position.x - (roomWidth * 0.5f);
float roomEndX = roomStartX + roomWidth;
//If there is a room that starts after addRoomX, delete it. or ends to the left of removeRoomX, delete it
if (roomStartX > addRoomX)
AddRooms = false;
if (roomEndX < removeRoomX)
DeleteRooms.Add(room);
//the most right point of the level.
farthestRoomEndX = Mathf.Max(farthestRoomEndX, roomEndX -0.01f);
}
//remove rooms that are marked as removal, and if addRooms is still true, add a new room
foreach (var room in DeleteRooms) {
ActiveRooms.Remove(room);
Destroy(room);
}
if (AddRooms)
AddRoom(farthestRoomEndX);
}
The weird part is everything runs great the first time. (The code above is beeing called from a fixedupdate.)

Finding the closer player with NavMesh

In my scene, I have my NavMesh in the center (the yellow one) with 3 cubes set to be tracked. I want the NavMesh to find the closest one with the shortest path to it out of all the cubes and start following it. I have written code to do so, yet it acts weird and I don't see anything wrong with it, but obviously something is. What's happening is when I click play and leave the cubes as is, the NavMesh does, in fact, find the path to the closest cube and start heading towards it (cube3), but when it almost gets to it, the NavMesh takes a hard turn and starts heading towards cube 1 which is obviously not the closer one.
Here is my code. PathLength works just fine. I think the problem lies within the CalcPath function.
float PathLength(Vector3 target){
NavMeshPath path = new NavMeshPath ();
myNavMesh.CalculatePath (target, path);
int i = 1;
float currentPathLength = 0;
Vector3 lastCorner;
Vector3 currentCorner;
lastCorner = path.corners [0];
while (i < path.corners.Length) {
currentCorner = path.corners [i];
currentPathLength += Vector3.Distance (lastCorner, currentCorner);
Debug.DrawLine (lastCorner, currentCorner, Color.red,1f);
lastCorner = currentCorner;
i++;
}
return currentPathLength;
}
void CalcPath(){
Vector3 closestTarget = Vector3.zero;
float lastPathLength = Mathf.Infinity;
float currentPathLength = 0;
foreach (GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
currentPathLength = PathLength (player.transform.position);
if (currentPathLength < lastPathLength) {
closestTarget = player.transform.position;
}
lastPathLength = currentPathLength;
}
myNavMesh.SetDestination (closestTarget);
}
You do have an issue in CalcPath. I'll try to give an example to show you what's wrong. Say the distances to the players are as follows: [5,20,10]. Obviously Player A is closest, but CalcPath will return Player C. I'll go through your loop to show why:
First Iteration:
currentPathLength : 0 -> 5
closestTarget : null -> PlayerA.transform.position
lastPathLength : Mathf.Infinity -> 5
Second Iteration:
currentPathLength : 5 -> 20
closestTarget : PlayerA.transform.position -> PlayerA.transform.position (unchanged)
lastPathLength : 5 -> 20
Third Iteration: (this is where your problem lies)
currentPathLength : 20 -> 10 (this is less than lastPathLength)
closestTarget : PlayerA.transform.position -> PlayerC.transform.position
lastPathLength : 20 -> 10
To fix this issue, instead of storing lastPathLength, store a minimum path length and only change your closestTarget when you have a new minimum.
I was comparing the current path length to the last path length and not storing the shortest path and comparing it. I set a new variable called "closestTargetLength" and compared that to the current path length, and works perfect.
float PathLength(Vector3 target){
NavMeshPath path = new NavMeshPath ();
myNav.CalculatePath (target, path);
int i = 1;
float currentPathLength = 0;
Vector3 lastCorner;
Vector3 currentCorner;
lastCorner = path.corners [0];
while (i < path.corners.Length) {
currentCorner = path.corners [i];
currentPathLength += Vector3.Distance (lastCorner, currentCorner);
Debug.DrawLine (lastCorner, currentCorner, Color.red);
lastCorner = currentCorner;
i++;
}
return currentPathLength;
}
void CalcPath(){
Vector3 closestTarget = Vector3.zero;
float closestTargetLength = Mathf.Infinity;
float lastPathLength = Mathf.Infinity;
float currentPathLength = 0;
foreach (GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
currentPathLength = PathLength (player.transform.position);
if (currentPathLength < closestTargetLength) {
closestTarget = player.transform.position;
closestTargetLength = currentPathLength;
}
lastPathLength = currentPathLength;
}
myNav.SetDestination (closestTarget);
}

Categories

Resources