I am working with Unity3d to create an iPhone game. Because iPhone is highly limited in performance than PC, I want to keep things economic. In my game I have a very long stairway and there is a character walking on it. If the character is above one step some height, the step will be destroyed. But i could not get a reference to a single gameObject. How could I achieve this? Thank you very much!
function buildFirstStair () {
for (var y = 0; y < 80; y++) {
for (var x = 0; x < 80; x++) {
if (x == y) {
var step = Instantiate(cube, Vector3(0, x*0.25, y*0.25), Quaternion.identity);
}
}
}
}
You can put all instance in a List, and delete them after.
Something like:
function Create()
{
List<GameObject> mylist;
for (float y = 0; y < 80; y++)
for (float x = 0; x < 80; x++) {
if (x == y) {
mylist.push((GameObject) Instantiate(...), Quaternion.identity));
}
}
}
And to delete you do something like
foreach object in mylist
{
Destroy(object);
}
Sorry for c# code but I'm a c# user.
Related
For a sailing game I'm working on, I've added functionality to programmatically create damage holes in a mesh (e.g. cannonball holes in a sail). This is largely based on the method here (link to the example code here)
private void MessWithMesh() {
filter = this.transform.parent.gameObject.GetComponent<MeshFilter>();
mesh = filter.mesh;
filter.mesh = GenerateMeshWithHoles();
}
private IEnumerator GenerateTrisWithVertex() {
// Destroying the sail won't work until this has finished, but it only takes a second or two so I don't think anybody will notice.
trisWithVertex = new List<int>[origvertices.Length];
for (int i = 0; i <origvertices.Length; ++i)
{
trisWithVertex[i] = ArrayHelper.IndexOf(origtriangles, i);
yield return null;
}
yield return null;
}
Mesh GenerateMeshWithHoles()
{
float damageRadius = 1f;
Transform parentTransform = this.transform.parent.transform;
Hole[] holes = this.GetComponentsInChildren<Hole>();
foreach (Hole hole in holes) {
Vector3 trackPos = hole.transform.position;
float closest = float.MaxValue;
int closestIndex = -1;
int countDisabled = 0;
damageRadius = hole.diameter;
for (int i = 0; i <origvertices.Length; ++i)
{
Vector3 v = new Vector3(origvertices[i].x * parentTransform.localScale.x, origvertices[i].y * parentTransform.localScale.y, origvertices[i].z * parentTransform.localScale.z) + parentTransform.position;
Vector3 difference = v - trackPos;
if (difference.magnitude < closest)
{
closest = difference.magnitude;
closestIndex = i;
}
if (difference.magnitude < damageRadius)
{
for (int j = 0; j <trisWithVertex[i].Count; ++j)
{
int value = trisWithVertex[i][j];
int remainder = value % 3;
trianglesDisabled[value - remainder] = true;
trianglesDisabled[value - remainder + 1] = true;
trianglesDisabled[value - remainder + 2] = true;
countDisabled++;
}
}
}
// If no triangles were removed, then we'll just remove the one that was closest to the hole.
// This shouldn't really happen, but in case the hole is off by a bit from where it should have hit the mesh, we'll do this to make sure there's at least a hole.
if (countDisabled == 0 && closestIndex > -1) {
Debug.Log("Removing closest vertex: " + closestIndex);
for (int j = 0; j < trisWithVertex[closestIndex].Count; ++j)
{
int value = trisWithVertex[closestIndex][j];
int remainder = value % 3;
trianglesDisabled[value - remainder] = true;
trianglesDisabled[value - remainder + 1] = true;
trianglesDisabled[value - remainder + 2] = true;
}
}
}
triangles = ArrayHelper.RemoveAllSpecifiedIndicesFromArray(origtriangles, trianglesDisabled).ToArray();
mesh.SetTriangles(triangles, 0);
for (int i = 0; i <trianglesDisabled.Length; ++i)
trianglesDisabled[i] = false;
return mesh;
}
When a cannonball hits the sail, I add a Hole object at the location of the impact, and I call MessWithMesh. The holes are often generated correctly, but many times they're only visible from one side of the sail (it looks fully intact from the other side). It's often visible from the opposite side of the sail that the cannonball impacted (the far side, not the near side), if that's at all helpful. The ship I'm using is this free asset.
I'm not really familiar with meshes, so I don't really understand what's going on.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelRenderer : MonoBehaviour
{
public Mesh mesh;
public Material material;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
// Start is called before the first frame update
void Start()
{
}
void Update()
{
int limit = size;
for (int x = 0; x < limit; x++)
{
for (int y = 0; y < limit; y++)
{
for (int z = 0; z < limit; z++)
{
Graphics.DrawMesh(mesh, new Vector3(x*2 +offset, y*2 + offset, z * 2 + offset), Quaternion.identity, material, 0);
}
}
}
}
}
Now this renders fine for size 10~ but if i go for 100+ (which is obviously low for which is required for a planet) i get massive fps drops presumably because of 10000's of batches per frame. Am i going the wrong way here? Or is it an occlussion issue or something similar i am running into here?
Draw calls can really hurt your performance. You can use batching and GPU instancing, but I think the best solution is using CombineMeshes. By combining 1000s of meshes into 1, you can get that many fewer draw calls.
Unity has a limit of slightly less than 2^16=65536 vertices per mesh. This prevents you from having a giant voxel as only one mesh. I suggest that you combine groups of a few thousand meshes ("chunks") and when you need to update the voxel data, only redo the mesh groups whose data changed. This not only gets around the vertex limit, but also saves performance by recombining only the meshes that need it.
In a minecraft-like game that I worked on, I made a script that generated cube blocks without drawing any hidden faces (e.g. two blocks touching each other wouldn't generate faces between them), but this will only work if your meshes are cubes and the code is messy.
I think the best solution would be to make two MonoBehaviours.
A Chunk class could handle may voxels, but not the whole planet, and the VoxelRenderer can handle spawning as many chunks are needed to make the whole planet. Make a prefab with a MeshRenderer, this Chunk component, and the right material assigned.
Please note that this is just a quick skeleton of what you'd need.
Chunk:
public class Chunk : MonoBehaviour
{
public Mesh mesh;
// Start is called before the first frame update
void UpdateChunk()
{
CombineInstance[] ci = new CombingInstance[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE];
for(int x = 0; x < VoxelRenderer.CHUNK_SIZE;x++){
//add 2 more for loops for y and z
ci[x*CHUNK_SIZE*CHUNK_SIZE + y*CHUNK_SIZE + z].Transform =...
}
GetComponent<MeshFilter>().mesh = new Mesh();
GetComponent<MeshFilter>().mesh.CombineMeshes(ci);
}
VoxelRenderer:
public class VoxelRenderer : MonoBehaviour
{
public const int CHUNK_SIZE = 8;
public GameObject chunkPrefab;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
private Chunk[,,] chunks;
// Start is called before the first frame update
void Start()
{
int limit = size;
int limitDividedByChunkSize = Mathf.RountToInt(limit/(float)CHUNK_SIZE);
chunks = new Chunk[limitDividedByChunkSize,limitDividedByChunkSize,limitDividedByChunkSize];
for (int x = 0; x < limit; x+=CHUNK_SIZE)
{
for (int y = 0; y < limit; y+=CHUNK_SIZE)
{
for (int z = 0; z < limit; z+=CHUNK_SIZE)
{
GameObject g = Instantiate(chunkPrefab, new Vector3(x*2*CHUNK_SIZE +offset, y*2*CHUNK_SIZE + offset, z * 2*CHUNK_SIZE + offset));
Chunks[x,y,z] = g.GetComponent<Chunk>();
}
}
}
}
//this updates all voxel chunks, for minor changes to the voxel data use chunks[x,y,z].UpdateChunk();
void UpdateAllVoxelChunks()
{
for (int x = 0; x < chunks.Length; x++)
{
for (int y = 0; y < chunks[x].Length; y++)
{
for (int z = 0; z < chunks[y].Length; z++)
{
Chunks[x,y,z].UpdateChunk();
}
}
}
}
}
I am sure that everybody knows about this script, http://wiki.unity3d.com/index.php/Floating_Origin, that fixes problems with floating origin easily.
The problem is that the script is outdated and does not move the particle effects created by visual effect graph.
I was trying to rewrite it but I cant seem to make an array to store all the particles, like with the previous one, thus I can't continue from there.
Here is my code:
// Based on the Unity Wiki FloatingOrigin script by Peter Stirling
// URL: http://wiki.unity3d.com/index.php/Floating_Origin
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.VFX;
using UnityEngine.Experimental.VFX;
public class FloatingOrigin : MonoBehaviour
{
[Tooltip("Point of reference from which to check the distance to origin.")]
public Transform ReferenceObject = null;
[Tooltip("Distance from the origin the reference object must be in order to trigger an origin shift.")]
public float Threshold = 5000f;
[Header("Options")]
[Tooltip("When true, origin shifts are considered only from the horizontal distance to orign.")]
public bool Use2DDistance = false;
[Tooltip("When true, updates ALL open scenes. When false, updates only the active scene.")]
public bool UpdateAllScenes = true;
[Tooltip("Should ParticleSystems be moved with an origin shift.")]
public bool UpdateParticles = true;
[Tooltip("Should TrailRenderers be moved with an origin shift.")]
public bool UpdateTrailRenderers = true;
[Tooltip("Should LineRenderers be moved with an origin shift.")]
public bool UpdateLineRenderers = true;
private ParticleSystem.Particle[] parts = null;
VisualEffect[] visualEffect = null;
void LateUpdate()
{
if (ReferenceObject == null)
return;
Vector3 referencePosition = ReferenceObject.position;
if (Use2DDistance)
referencePosition.y = 0f;
if (referencePosition.magnitude > Threshold)
{
MoveRootTransforms(referencePosition);
if (UpdateParticles)
MoveParticles(referencePosition);
if (UpdateTrailRenderers)
MoveTrailRenderers(referencePosition);
if (UpdateLineRenderers)
MoveLineRenderers(referencePosition);
}
}
private void MoveRootTransforms(Vector3 offset)
{
if (UpdateAllScenes)
{
for (int z = 0; z < SceneManager.sceneCount; z++)
{
foreach (GameObject g in SceneManager.GetSceneAt(z).GetRootGameObjects())
g.transform.position -= offset;
}
}
else
{
foreach (GameObject g in SceneManager.GetActiveScene().GetRootGameObjects())
g.transform.position -= offset;
}
}
private void MoveTrailRenderers(Vector3 offset)
{
var trails = FindObjectsOfType<TrailRenderer>() as TrailRenderer[];
foreach (var trail in trails)
{
Vector3[] positions = new Vector3[trail.positionCount];
int positionCount = trail.GetPositions(positions);
for (int i = 0; i < positionCount; ++i)
positions[i] -= offset;
trail.SetPositions(positions);
}
}
private void MoveLineRenderers(Vector3 offset)
{
var lines = FindObjectsOfType<LineRenderer>() as LineRenderer[];
foreach (var line in lines)
{
Vector3[] positions = new Vector3[line.positionCount];
int positionCount = line.GetPositions(positions);
for (int i = 0; i < positionCount; ++i)
positions[i] -= offset;
line.SetPositions(positions);
}
}
private void MoveParticles(Vector3 offset)
{
var particles = FindObjectsOfType<ParticleSystem>() as ParticleSystem[];
foreach (ParticleSystem system in particles)
{
if (system.main.simulationSpace != ParticleSystemSimulationSpace.World)
continue;
int particlesNeeded = system.main.maxParticles;
if (particlesNeeded <= 0)
continue;
bool wasPaused = system.isPaused;
bool wasPlaying = system.isPlaying;
if (!wasPaused)
system.Pause();
// ensure a sufficiently large array in which to store the particles
if (parts == null || parts.Length < particlesNeeded)
{
parts = new ParticleSystem.Particle[particlesNeeded];
}
// now get the particles
int num = system.GetParticles(parts);
for (int i = 0; i < num; i++)
{
parts[i].position -= offset;
}
system.SetParticles(parts, num);
if (wasPlaying)
system.Play();
}
var particles2 = FindObjectsOfType<VisualEffect>() as VisualEffect[];
foreach (VisualEffect system in particles2)
{
int particlesNeeded = system.aliveParticleCount;
if (particlesNeeded <= 0)
continue;
bool wasPaused = !system.isActiveAndEnabled;
bool wasPlaying = system.isActiveAndEnabled;
if (!wasPaused)
system.Stop();
// ensure a sufficiently large array in which to store the particles
if (visualEffect == null || visualEffect.Length < particlesNeeded)
{
visualEffect = new VisualEffect().visualEffectAsset[particlesNeeded];
}
// now get the particles
int num = system.GetParticles(parts);
for (int i = 0; i < num; i++)
{
parts[i].position -= offset;
}
system.SetParticles(parts, num);
if (wasPlaying)
system.Play();
}
}
}
On the line(this is a wrong line and everything below it too)
visualEffect = new VisualEffect().visualEffectAsset[particlesNeeded];
, I need to create a similar array to the line (correct one, but for the old particle system)
parts = new ParticleSystem.Particle[particlesNeeded];
that creates array full of particles (but with VisualEffect class).
If I can fix this one, there should not be any problem with the rest.
I think that solving this problem will help literally thousands of people now and in the future, since limitation for floating origin in unity are horrible and majority of people working in unity will need floating origin for their game worlds, with VFX graph particles.
Thanks for the help.
My question has been answered here:
https://forum.unity.com/threads/floating-origin-and-visual-effect-graph.962646/#post-6270837
I'm using a script that looks at the tiles around my GameObject to see if it can detect a collider, through Physics2D.OverlapBox. My problem is that my Player should be ignored by the OverLapBox, as I've set it to look at layers it isn't in, but it is detected everytime.
My Player is in the "Default" layer.
private void SpawnBasicWalls()
{
int layersToScan = LayerMask.GetMask("Floor", "Wall");
//for each tile around this tile
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
Vector2 targetPos = new Vector2(transform.position.x + x, transform.position.y + y);
Collider2D hit = Physics2D.OverlapBox(targetPos, Vector2.one * 0.8f, layersToScan);
//if there isn't a tile around
if (!hit)
{
//Add a wall in that empty adjacent tile.
GameObject goWall = Instantiate(dungMan.wallPrefab, targetPos, Quaternion.identity) as GameObject;
goWall.name = dungMan.wallPrefab.name;
goWall.transform.SetParent(dungMan.transform);
}
else
{
Debug.Log(hit);
}
}
}
//Once it's done, the gameobject is useless and thus is destroyed
Destroy(gameObject);
}
If anyone can tell me what I'm doing wrong, I'd be very grateful.
Physics2D.OverlapBox(targetPos, Vector2.one * 0.8f, layersToScan) is basically calling Physics2D.OverlapBox(point, size, angle) - which means you are sending the layers (casted to int) as an angle.
You need to use one of the overloads that receive a layermask, and make sure you pass it in the right parameter.
I want to have an infinitely explorable map. The plan is to create categories of game tiles (roads, obstacles, buildings), and randomly choose a category of game tile to be added when the player approaches the edge of the existing set of tiles. Tiles will also be destroyed once the player is 2 grid squares away from that tile. Currently I am using a multidimensional array that requires a size initializer.
What I have so far:
public class GameManager : MonoBehaviour
{
private GameObject[,] tileArray;
public GameObject groundTile;
public GameObject player;
private int tileSize = 80;
private int nextFarX = 1;
private int nextFarZ = 1;
private int nextNearX = -1;
private int nextNearZ = -1;
private float padding = .1f;
private int arrayOffset;
private int arrayDimension;
// Use this for initialization
void Start ()
{
arrayDimension = 200;
arrayOffset = arrayDimension / 2;
tileArray = new GameObject[,];
this.AddCubeAt(0, 0);
}
// Update is called once per frame
void Update () {
var x = Convert.ToInt32(player.transform.position.x / tileSize);
var z = Convert.ToInt32(player.transform.position.z / tileSize);
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
var checkX = x + i;
var checkZ = z + j;
if (tileArray[checkX + arrayOffset, checkZ + arrayOffset] == null)
{
//player is less than 2 tiles away from this grid, add a tile
this.AddCubeAt(checkX, checkZ);
}
}
}
// feels like a hack, but it will remove tiles that are not touching the tile that the player occupies
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
if (i == 0 | i == 5 | j == 0 | j == 5)
{
if (tileArray[x + (i-2) + arrayOffset, z + (j-2) + arrayOffset] != null)
{
Destroy(tileArray[x + (i - 2) + arrayOffset, z + (j - 2) + arrayOffset]);
tileArray[x + (i - 2) + arrayOffset, z + (j - 2) + arrayOffset] = null;
}
}
}
}
}
private void AddCubeAt(int x, int z)
{
var pos = new Vector3(x * tileSize, 0, z * tileSize);
var rot = Quaternion.identity;
GameObject newCube = (GameObject)Instantiate(groundTile, pos, rot);
tileArray[x + arrayOffset, z + arrayOffset] = newCube;
}
}
What is a better way to approach this?
You should familiarize yourself with Graph Data Structure (not adjacency matrix implementation). It's much more appropriate for this task. And, I would solve this
Tiles will also be destroyed once the player is 2 grid squares away from that tile
in another way: Every time player changed his position I would start DFS on target depth (in your case it's 2) and remove found tiles.
Decided to go with a simple Dictionary and methods to query/update it:
private GameObject RetrieveTileAt(int x, int z)
{
string key = string.Format("{0}.{1}", x, z);
if (tileDictionary.ContainsKey(key))
{
return tileDictionary[key];
}
else
{
return null;
}
}
private void InsertTileAt(int x, int z, GameObject tile)
{
string key = string.Format("{0}.{1}", x, z);
tileDictionary[key] = tile;
}
It is not an infinitely sized grid, (int min + int max)squared, but it should be far more than I need.