XNA multi class collision - class

I am trying to make the enemy spawning / movement system for a side scrolling game.
What is supposed to happen is the enemies are to spawn and move right until they collide with a specific object then they will travel in the opposite direction.
I have done research and asked question to get me this far:
I have made an enemy class and then made a list that can add more enemies and set their individual spawning positions. Then I have made an EnemyBlock class so I can set where the various collision objects will be (In this case they are just brick squares).
I am having some trouble
A. In making the enemies turn around when they leave the game screen.
And B. writing collision code for the enemies and enemyblocks as when I try and write collision code in the Game1.cs Update section under foreach (Enemy enemy in enemies) it doesn't pick the the enemyblock's rectangle. If that makes sense. Im sorry but I am pretty new to XNA code.
Game1.cs code
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
public Rectangle bounds;
List<Enemy> Enemies = new List<Enemy>();
List<EnemyBlock> Blocks = new List<EnemyBlock>();
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
Blocks.Add(new EnemyBlock(Content.Load<Texture2D>("02Brick"), new Vector2(50, 100)));
Enemies.Add(new Enemy(Content.Load<Texture2D>("Mario_Sprite"), new Vector2(100, 100), new Vector2(1, 0)));
Blocks.Add(new EnemyBlock(Content.Load<Texture2D>("02Brick"), new Vector2(500, 100)));
Blocks.Add(new EnemyBlock(Content.Load<Texture2D>("02Brick"), new Vector2(50, 200)));
Enemies.Add(new Enemy(Content.Load<Texture2D>("Mario_Sprite"), new Vector2(100, 200), new Vector2(1, 0)));
Blocks.Add(new EnemyBlock(Content.Load<Texture2D>("02Brick"), new Vector2(400, 200)));
foreach (Enemy enemy in Enemies)
{
enemy.bounds = new Rectangle((int)(enemy.position.X - enemy.texture.Width), (int)(enemy.position.Y - enemy.texture.Height), enemy.texture.Width, enemy.texture.Height);
}
foreach (EnemyBlock block in Blocks)
{
block.bounds = new Rectangle((int)(block.position.X - block.texture.Width), (int)(block.position.Y - block.texture.Height), block.texture.Width, block.texture.Height);
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
foreach (Enemy enemy in Enemies)
{
if (!GraphicsDevice.Viewport.Bounds.Contains(enemy.bounds))
{
enemy.velocity = -enemy.velocity;
enemy.position += enemy.velocity;
}
else
{
enemy.position += enemy.velocity;
}
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
foreach (Enemy enemy in Enemies)
{
spriteBatch.Draw(enemy.texture, enemy.position, Color.White);
}
foreach (EnemyBlock block in Blocks)
{
spriteBatch.Draw(block.texture, block.position, Color.White);
}
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Enemy class code
class Enemy
{
public Texture2D texture;
public Rectangle bounds;
public Vector2 position;
public Vector2 velocity;
public Enemy(Texture2D Texture, Vector2 Position, Vector2 Velocity)
{
texture = Texture;
position = Position;
velocity = Velocity;
}
}
EnemyBlock class code
class EnemyBlock
{
public Texture2D texture;
public Rectangle bounds;
public Vector2 position;
public EnemyBlock(Texture2D Texture, Vector2 Position)
{
texture = Texture;
position = Position;
}
}

Your code seems to be very well structured and organized. Makes it a lot easier to be able to find any of these problems your having so thanks and keep your code organized. It's a good habit and it makes larger projects much easier.
Anyways I think I might have the answer to your problem.
The reason your rectangles aren't being picked up is because you are initializing the rectangles for your enemies and blocks in your load content method.
This is fine except that you don't appear to update the position of the rectangles. You update the velocity and position of the enemies and blocks though you check for collision with the bounding rectangles.
This means that your enemies will be able to move off screen and through objects because your the draw position has moved but the rectangle hasn't meaning it won't detect the collision.
I believe other problem is with specifying the rectangle position. I believe your code maybe a bit off but if it works fine then leave it. But if the collision detection is a little off then try the following. Your code
enemy.bounds = new Rectangle((int)(enemy.position.X - enemy.texture.Width), (int)(enemy.position.Y - enemy.texture.Height), enemy.texture.Width, enemy.texture.Height);
should look like
enemy.bounds = new Rectangle(enemy.position.X, enemy.position.Y, enemy.texture.Width, enemy.texture.Height);
based on how you are drawing your images.
The constructor of the rectangle specifies the top left corner's x, y position and the width/height of the rectangle. Rectangle r = new Rectangle(100, 100, 200, 200) creates a rectangle at 100, 100 and 200 wide and 200 tall.
This will match up with how you draw your enemies and blocks.
And lastly the collision code is fairly simple:
foreach(Enemy enemy in Enemies)
{
foreach(EnemyBlock block in Blocks)
{
if(enemy.bounds.Contains(block.bounds))
{
//what to do on collision
}
}
}
I hope this is all correct and helps.

I'm not proficient on XNA, anyway,
try removing negation, because the code looks like it will always reverse the velocity while not on viewport's edge
if (GraphicsDevice.Viewport.Bounds.Contains(enemy.bounds))
{
enemy.velocity *= -1;
}
enemy.position += enemy.velocity;
with this, the enemy velocity will reverse only when it reaches the boundary.
No need to put an else statement since the enemy will always be moving.
As for the collision with enemy blocks, here is a good tutorial from MSDN on making 2D side scrolling games like mario. CLICK HERE
in the tutorial everything is treated as a tile object on 2d array map and on update, it checks against a tile of the array where the enemy is currently standing (enemy's position)

Related

Best way to create a 2d top down race track procedurally

I am attempting to create a 2d top-down car racing game. This game will have a random road map each time the player plays the game. I have thought about doing this in two different ways: A tilemap, or just generate the roads by placing different prefabs (straight roads, turns, etc). I have decided to go with the prefab route.
The way I believe it should work is to have prefab square "tiles" which have their own colliders set on the edges so I can tell if a player goes off the track in which case they blow up. I would have a MapGenerator Script which will generate an initial random map by keeping track of the last tile placed (including its location and road type: left turn, straight, right, etc). This script will then keep adding onto the road randomly as the player gets closer and closer to the end which makes it an infinite road.
I just want to know if this is just not efficient or if I am thinking of this completely wrong.
Here are a couple of images showing my road tiles which I made in photoshop and then one prefab for a straight road (take note of the colliders on its edges).
A similar game to one I want to make is Sling Drift which I can provide the link if you want. I don't know the policy on adding links to forum chat.
Also, here is my code for the map generator:
//Type of tyle, types are normal (straight road or horizontal road) and turns
public enum MapTileType
{
NORMAL,
N_E,
N_W,
S_E,
S_W
}
//structure for holding the last tile location and its type.
public struct TypedTileLocation
{
public TypedTileLocation(Vector2 pos, MapTileType tyleType)
{
m_tileType = tyleType;
m_position = pos;
}
public Vector2 m_position;
public MapTileType m_tileType;
}
public class MapGenerator : MonoBehaviour
{
//Map Tiles
public GameObject m_roadTile;
public GameObject m_turnNorthWestTile;
//holds all the tiles made in the game
private List<GameObject> m_allTiles;
//Map Tile Widths and Height
private float m_roadTileWidth, m_roadTileHeight;
//Used for generating next tile
TypedTileLocation m_lastTilePlaced;
private void Awake()
{
//store the initial beginning tile location (0,0)
m_lastTilePlaced = new TypedTileLocation(new Vector2(0,0), MapTileType.NORMAL);
//set height and width of tiles
m_roadTileWidth = m_roadTile.GetComponent<Renderer>().bounds.size.x;
m_roadTileHeight = m_roadTile.GetComponent<Renderer>().bounds.size.y;
m_allTiles = new List<GameObject>();
}
// Start is called before the first frame update
void Start()
{
SetupMap();
}
void SetupMap()
{
//starting at the beginning, just put a few tiles in straight before any turns occur
for (int i = 0; i < 6; ++i)
{
GameObject newTempTile = Instantiate(m_roadTile, new Vector2(0, m_roadTileHeight * i), Quaternion.identity);
m_lastTilePlaced.m_tileType = MapTileType.NORMAL;
m_lastTilePlaced.m_position.x = newTempTile.transform.position.x;
m_lastTilePlaced.m_position.y = newTempTile.transform.position.y;
m_allTiles.Add(newTempTile);
}
//now lets create a starter map of 100 road tiles (including turns and straigt-aways)
for (int i = 0; i < 100; ++i)
{
//first check if its time to create a turn. Maybe I'll randomly choose to either create a turn or not here
//draw either turn or straight road, if the tile was a turn decide which direction we are now going (N, W, E, S).
//this helps us determine which turns we can take next
//repeat this process.
}
}
void GenerateMoreMap()
{
//this will generate more map onto the already existing road and then will delete some of the others
}
// Update is called once per frame
void Update()
{
}
private void OnDrawGizmos()
{
}
}
Thanks!
Have you tried splines? They let you make curvy paths like race tracks easily. If not, here is a video that might help: https://www.youtube.com/watch?v=7j_BNf9s0jM.

Why Does My Mesh Combiner Script Make Child Objects Invisible And Change Their Placements?

To further optimize my game, I wanted to combine the meshes of the hallways so that the frame rate would be higher. the code is intended to take all of the children inside of an empty game object(titled walls, floors, etc.) and combine their meshes into one. However, whenever I ran the script, all of the child objects would appear in completely random positions and were invisible. How can I make so that the objects all appeared at their original position and were visible?
How I set up the code is that I would place any repeated objects in an empty gameObject to easily categorize them (titled as Walls, Floors). Afterwards, I would assign the script to the empty gameObject and expect every repeated object in the empty gameObject to combine.
Here's an example:
Here's The Code:
using UnityEngine;
using System.Collections;
// Copy meshes from children into the parent's Mesh.
// CombineInstance stores the list of meshes. These are combined
// and assigned to the attached Mesh.
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class CombineMesh : MonoBehaviour
{
void Update()
{
if(Input.GetKeyDown(KeyCode.J))
{
CombineMeshes();
}
}
void CombineMeshes()
{
Quaternion oldRot = transform.rotation;
Vector3 oldPos = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.SetActive(false);
i++;
}
var MeshFilter = transform.GetComponentInChildren<MeshFilter>();
MeshFilter.mesh = new Mesh();
MeshFilter.mesh.CombineMeshes(combine);
GetComponentInChildren<MeshCollider>().sharedMesh = MeshFilter.mesh;
transform.gameObject.SetActive(true);
transform.rotation = oldRot;
transform.position = oldPos;
}
}
Answering late in case anyone happens to be searching for this information.
Unity's documentation is uncharacteristically unhelpful, as is the internet in general.
Lengthy Explanation
Kayra's Code Corrected
Working Code: Helper Functions
Working Code: CombineMeshes
Lengthy Explanation:
Two things to keep in mind are
Meshes are defined via a Vector3[](see meshFilter.mesh.vertices), describing the coordinates of each vertex relative to its meshFilter's transform (so local space). This is very important later on.
There is no black magic involved - math is not black magic ;)
First, you should understand "transform.localToWorldMatrix" and "transform.worldToLocalMatrix"
These are very misleading names - all these Matrix4x4 actually do in Unity, is describe the linear transformation between
{position: Vector3.zero, rotation: Quaternion.identity, scale: Vector3.one} and
the given transform's {position, rotation, scale}.
Its telling you by how much to move in which direction, by how much to rotate in which direction, and by how much to scale.
In fact: transform.localToWorldMatrix == transform.worldToLocalMatrix.inverse -- they're the exact same thing, just flipped around.
See "Khan Academy" or "3blue1brown" on YouTube for great explanations of Linear Transformation
We usually don't use Matrices in Unity, because we can access/modify the position, rotation, and scale directly -- for us, it's just a different way of storing the same information.
I wrote some working code to visualise what's actually happening in Unity:
/// Demonstration of how transform.localToWorldMatrix works - very important for understanding CombineInstance.transform
/// Place two 3D objects in your scene, attach this script somewhere, and assign the 3D Objects to the public variables
/// This script will auto-run inside a coroutine 2 seconds after Start(), because I use the InputSystem package and maybe you still use if(Input.getKeyDown()).
/// It's the easiest way I know of to guarantee working code in your scene
public class StackOverflowExample : MonoBehaviour
{
public Transform parent;
public Transform child;
private IEnumerator exampleMethod;
/// <summary>
/// Just setting some conditions, in case you try out this code
/// </summary>
private void Start()
{
parent.position = new Vector3(5f, 0f, 3f);
child.parent = parent;
child.localPosition = new Vector3(2f, 0f, 2f);
//Only doing this because I use the InputSystem package, and others maybe don't (yet...)
exampleMethod = TransformMatrixExample();
StartCoroutine(exampleMethod);
}
//Only doing this because I use the InputSystem package, and others maybe don't (yet...)
private IEnumerator TransformMatrixExample()
{
//wait for 2 seconds just because...
yield return new WaitForSeconds(2f);
//parent.position = parent.position - parent.localToWorldMatrix.GetPosition();
// Commented out on purpose. Would result in 0,0,0 parent.position, because parent has no gameObject parent...
//and therefore break the demonstration:
child.position = child.position - parent.localToWorldMatrix.GetPosition();
// child.localPosition (see the inspector) now states (-3f,0f,-1f)
// however, child.Position (just unparent the gameObject in the inspector) is now (2f,0f,2f)
}
}
When you tell each CombineInstance what Matrix4x4 to use in
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
you are describing by how much to offset each soon-to-be-combined mesh's vertices' coordinates before combining it.
Once again, we usually access/modify the position, rotation, and scale directly - Mesh.CombineMeshes() uses a 4x4 matrix.
Here Matrix4x4.GetPosition() returns the vector leading from Vector3.zero to parent.position - which is contained inside that matrix
We are effectively moving the child object to the place it would be if the parent were at coordinates (0f,0f,0f).
The same thing happens with rotation and scale.
In the while-loop in your code, there's a statement:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
The problem with this is that if the parent object (or the mesh you want to merge into) is not positioned at Vector3.zero, all the meshes will still pretend otherwise and offset themselves by the wrong amount. That is why you have to move the parent.transform.position to vector3.zero before assigning to the CombineInstances[].
So in my code example above:
parent.position = new Vector3(5f, 0f, 3f);
child.localPosition = new Vector3(2f, 0f, 2f);
If I first move the parent to V3.zero, the results given by transform.localToWorldMatrix.GetDistance():
combine[i].transform = meshFilters[i].transform.localToWorldMatrix :
parent's offset: V3(0f,0f,0f) --> parent.position
child's offset: V3(2f,0f,2f) --> child.position
This works because now, the vector from child.position to V3.zero == vector from child.position to parent.position.
If I were to use the transforms without first moving the parent to V3.zero, I would get the following results:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix :
parent's offset: V3(5f, 0f, 3f) --> parent.position
child's offset: V3(7f, 0f, 5f) --> parent.position + child.localPosition
Because transform.localToWorldMatrix returns the vector from zero to that transform.position.
Remember Point1 of things to remember?
Vertex coordinates are defined in local space relative to their meshFilter's transform.
In other words:
all of parent mesh's vertices will offset by an additional V3(5f,0f,3f) --> parent.position
all of child mesh's vertices will be offset by an additional V3(7f,0f,5f) --> parent.position + child.localPosition
The statement is effectivley telling Unity the following (pseudocode):
foreach (Vector3 v in meshFilters[i].mesh.vertices)
{
v += meshFilters[i].localToWorldMatrix.GetPosition();
//and now append my vertex to the new MeshFilter's mesh...
//in other workds: Pretend that my parent is at 0,0,0 and I'm in the right spot already
}
The exact same principle holds true for rotation and scale. You can probably decipher that from the working code later on.
kayra yorulmaz's Corrected Code
I'm assuming that CombineMesh.cs is attached to the "Floors" gameObject,
and that "Floors" transform.rotation = (0f,0f,0f).
The Quaternion operations will be explained shortly.
So the kayra yorulmaz's code would have to be written as follows:
using UnityEngine;
// Copy meshes from children into the parent's Mesh.
// CombineInstance stores the list of meshes. These are combined
// and assigned to the attached Mesh.
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))] //because otherwise line 72 might throw an exception...
public class CombineMesh_Corrected : MonoBehaviour
{
void Update()
{
if(Input.GetKeyDown(KeyCode.J))
{
CombineMeshes();
}
}
void CombineMeshes()
{
Vector3 transformOffset = transform.position;
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
Quaternion rotationOffset = Quaternion.FromToRotation(transform.eulerAngles, meshFilters[i].transform.eulerAngles);
meshFilters[i].transform.position -= transformOffset;
meshFilters[i].transform.rotation = Quaternion.Euler(meshFilters[i].transform.eulerAngles) * Quaternion.Inverse(rotationOffset);
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.SetActive(false);
//we already stored the 4x4Matrix in combine[i].transform, so it's safe to change back now
meshFilters[i].transform.position += transformOffset;
meshFilters[i].transform.rotation *= rotationOffset;
i++;
}
MeshFilter meshFilter = transform.GetComponent<MeshFilter>();
meshFilter.mesh = new Mesh();
meshFilter.mesh.CombineMeshes(combine);
GetComponentInChildren<MeshCollider>().sharedMesh = meshFilter.mesh;
transform.gameObject.SetActive(true);
}
}
Working Code Example
However, it you create your own Matrix4x4 to describe the necessary linear transformation, you don't have to touch the gameObject's transforms at all.
Remember that Vertices (and therefore the meshes you're combining) are described relative to the meshFilter's transform.
So if we create a Matrix4x4 for each child meshFilter, describing how that child.meshFilter.transform is located relative to parent.meshFilter.transform, we can tell Unity where to place the vertices for the combined mesh:
So here's is the code based off what I just wrote for my own project.
Necessary Helping Functions
using UnityEngine;
public static class StackoverflowHelpers
{
/// <summary>
/// Returns the difference between quaterions, treated as local rotations because of the order...
/// https://answers.unity.com/questions/810579/quaternion-multiplication-order.html
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Quaternion FromTo(Quaternion from, Quaternion to)
{
return Quaternion.Inverse(from) * to;
}
public static Quaternion Add(Quaternion start, Quaternion difference)
{
return start * difference;
}
public static Quaternion Subtract(Quaternion start, Quaternion difference)
{
return start * Quaternion.Inverse(difference);
}
public static Vector3 RatioBetween( Vector3 fromScale, Vector3 toScale)
{
return new Vector3(
toScale.x/fromScale.x,
toScale.y/fromScale.y,
toScale.z/fromScale.z );
}
}
Actual CombineMesh Method
using System.Collections.Generic;
using UnityEngine;
public static class StackOverflow_CombineMesh
{
public static void Simple(List<MeshFilter> _meshFilters, bool _deleteOriginals = true)
{
CombineInstance[] combineInstances = new CombineInstance[_meshFilters.Count];
Transform parent = _meshFilters[0].transform;
for (int i = 0; i < _meshFilters.Count; i++)
{
/// set up the matrix describing the step from the parent mesh to the child mesh
Transform child = _meshFilters[i].transform;
Vector3 posOffset = child.position - parent.position;
posOffset.x *= 1/parent.localScale.x;
posOffset.y *= 1/parent.localScale.y;
posOffset.z *= 1/parent.localScale.z;
Matrix4x4 ParentToChildMatrix = Matrix4x4.TRS(
posOffset,
StackoverflowHelpers.FromTo(parent.rotation, child.rotation),
StackoverflowHelpers.RatioBetween(parent.lossyScale, child.lossyScale));
combineInstances[i].mesh = _meshFilters[i].mesh;
combineInstances[i].transform = ParentToChildMatrix;
child.gameObject.SetActive(false);
}
_meshFilters[0].mesh = new Mesh();
_meshFilters[0].mesh.CombineMeshes(combineInstances, true, true);
_meshFilters[0].gameObject.SetActive(true);
}
Bear in mind that meshes in unity have a maximum number of vertices (65,535 to be exact) - if you cross that limit your mesh won't render properly after all.
Have fun, keep learning!
Gecko
I think this is a common issue with Unity mesh combine, try changing this line (assuming this is all on the Parent game object):
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
to this:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix * transform.worldToLocalMatrix
where transform.worldToLocalMatrix is the parent object. You could also try something like:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix * meshFilters[i].transform.parent.transform.worldToLocalMatrix;
Depends on how you have it set up

How to add a spawner on a side of moving camera in unity2D

I'm creating a 2D racing game in Unity2D and unsure how to approach on coding my spawner for objects e.g. power-up, rewards and obstacles ahead on the right side of my moving camera. The camera follows my vehicle and moves from left to right. Currently objects spawn in the game, but only spawn on a point and continue to spawn in the same point when the camera has scrolled way past. Apologies, if the answer was simple.
Here are the scripts I used:
coin.cs is attached to my coin prefab
public class coin : MonoBehaviour
{
public Camera mainCamera;
Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
mainCamera = GameObject.FindObjectOfType<Camera>();
rb = this.gameObject.GetComponent<Rigidbody2D>();
transform.position = new Vector2(mainCamera.pixelWidth / 32, getRandomHeight());
}
float getRandomHeight(){
return Random.Range(-(mainCamera.pixelHeight / 2) / 100, (mainCamera.pixelHeight / 2) / 100);
}
private void OnTriggerEnter(Collider coll){
if (coll.gameObject.tag == "Player"){
Destroy(this.gameObject);
}
}
// Update is called once per frame
void Update()
{}
}
and coinSpawner.cs is attached to a gameObject in the scene
public class coinSpawner : MonoBehaviour
{
float LastSpawnTime = -500f;
float NextSpawnTime = 3;
public Object CoinPrefab; // declare coinPrefab
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Time.time - LastSpawnTime > NextSpawnTime)
{
NextSpawnTime = Random.Range(3, 10);
LastSpawnTime = Time.time;
Instantiate(CoinPrefab);
}
}
Instantiate(CoinPrefab);
spawns this object without any information of position or parent object. This it will always be spawned at Scene root and 0,0,0.
Then this line
transform.position = new Vector2(mainCamera.pixelWidth / 32, getRandomHeight());
in coin always places it to the absolute position depending on your pixelWidth which doesn't change over time.
You are saying the Camera is moving so you should rather take it's transform.position into account.
You have basically two(three) ways to go here:
You can make the spawner GameObject a child of your Camera - so it is automatically moved along with it - and move it to the right to have the desired offset. Then simply spawn new objects at this GameObjects position. Then you can do it in coinSpawner:
Instantiate(CoinPrefab, transform.position, Quaternion.Identity);
Or get the Camera reference and use it's position + the desired offset either also only in coinSpawner like
// reference this already via the Inspector if possible
[SerializeField] Camera _camera;
// Otherwise get the reference on runtime
void Awake()
{
if(!_camera)_camera = Camera.main;
}
float getRandomHeight => Random.Range(-(_camera.pixelHeight / 2) / 100, (_camera.pixelHeight / 2) / 100;
and then do something like
Instantiate(CoinPrefab, new Vector2(_camera.transform.position.x + mainCamera.pixelWidth / 32, getRandomHeight()), Quaternion.Identity);
This would actually be more efficient since the entire coin script would be reduced to only the OnTriggerEnter part and not every coin would use FindObjectOfType and GetComponent when spawned.
Basically the same as above but sticking to your code structure simply do
transform.position = new Vector2(mainCamera.transform.position.x + mainCamera.pixelWidth / 32, getRandomHeight());
I wouldn't do it this way though due to efficiency as said.

OnTrigger Events Work With Delay

I've got a bullet script with a particle system and a decal.
I think that it has something to do with these events not being able to fire in time or with fps in update. Not sure yet.
So, it's being late.
The ellow points are where the particles start to play. They should be right on these wooden walls. There should be three particles working and three bullet holes, kinda bullet penetrating one wall and getting destroyed on the second one.
THE QUESTION IS HOW TO MAKE IT WORK NORMAL, SO THAT THE TRIGGERS WORK WHEN NEEDED AS WELL AS THE PARTICLES AND THE DECALS? Maybe there's a way to excellerate the code to work on time? Or maybe there's another problem with that?
The screenshot:
The Code:
public class BulletScript : MonoBehaviour {
public bool isThrough = true;
public float BulletSpeed = 100;
public float CurrentDamage;
public float EnterLuft = -0.005f;
public float ExitLuft = 0.05f;
public GameObject woodParticle;
private ContactPoint CollisionPoint;
public GameObject BulletMarkPref;
Rigidbody bullet;
private void Start()
{
bullet = this.GetComponent<Rigidbody>();
}
void FixedUpdate () {
bullet.velocity = Vector3.forward * BulletSpeed;
//this.transform.Translate(Vector3.forward * BulletSpeed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
Transform hitPoint = this.transform;
LevelObject.LvlObjectType objectType = other.gameObject.GetComponent<LevelObject>().objType;
if(objectType == LevelObject.LvlObjectType.obstacle)
{
if (isThrough)
{
Instantiate(woodParticle, hitPoint.localPosition, Quaternion.LookRotation(-hitPoint.forward)).GetComponent<ParticleSystem>().Play();
LeaveBulletMark(this.transform, true);
}
else
{
Instantiate(woodParticle, hitPoint.localPosition, Quaternion.LookRotation(-hitPoint.forward)).GetComponent<ParticleSystem>().Play();
LeaveBulletMark(hitPoint, true);
Destroy(this.gameObject);
}
}
else if(objectType == LevelObject.LvlObjectType.obstacle)
{
Destroy(this.gameObject);
}
else if(objectType == LevelObject.LvlObjectType.wall)
{
LeaveBulletMark(hitPoint, true);
Destroy(this.gameObject);
}
}
private void OnTriggerExit(Collider other)
{
Transform hitPoint = this.transform;
Instantiate(woodParticle, hitPoint.localPosition, hitPoint.rotation).GetComponent<ParticleSystem>().Play();
LeaveBulletMark(hitPoint, false);
}
void LeaveBulletMark(Transform hitPoint, bool ifEnter)
{
GameObject TemporaryBulletMarkHandler;
TemporaryBulletMarkHandler = Instantiate(BulletMarkPref, hitPoint.localPosition, Quaternion.LookRotation(ifEnter ? hitPoint.forward : CollisionPoint.normal)) as GameObject;
isThrough = false;
TemporaryBulletMarkHandler.transform.Translate(hitPoint.forward * (ifEnter ? 0.005f : -0.005f));
}
}
I don't think your problem is something simple with the code. There is an inherent issue with calculating fast moving objects like bullets with true physics calculations especially if they are small. Often between physics updates, they pass through wall colliders completely without registering.
You have to think of it like this to see the problem. The bullet isn't tracked continuously along its trajectory. It has a starting location, a formula for its movement and it calculates a new location at each physics update. You could fire a bullet straight at a wall, and in one update the bullet would be several meters in front of the wall, and in the next, it would be several meters behind the wall without ever triggering a collision. This is why so many game use ray tracing to calculate bullet trajectories. The bullet movement isn't perfectly accurate, especially for long shots, but obstructions to the bullet path are registered.
By default unity's Physics Engine runs at 50 frames per second. A modern bullet travels between 1200 and 1700 m/s. That gives you a distance between 24 and 34 meters traveled between frames. Even a small object falling at terminal velocity (54 m/s) might pass through a collider unnoticed. If you made a 1-meter thick box collider, you would likely register a falling object but not a bullet.
I think you could do some clever combination of ray tracing and bullet physics to get the best of both worlds. Maybe you could ray trace from the bullet at each fixed update or there may be some better technique already invented for this exact situation that I don't know about.

Spawn sprites on action

I'm trying to make a Pinata GameObject, that when clicked bursts and gives a variable number of Gift GameObjects with various images and behaviors in them.
I'm also not sure what the unity vocabulary for this is so as to look this up in unity docs.
Can anyone please lend me a hand here? Thanks!
There are several ways to handle this.
The simple way is to use Object.Instantiate, Object Instantiation is the vocab you're after.
This will create a copy of a predefined Unity object, this can be a gameobject or any other object derived from UnityEngine.Object, check the docs for more info https://docs.unity3d.com/ScriptReference/Object.Instantiate.html.
In your case, your Pinata would have an array, or list, of prefabs. These prefabs are created by you with a certain behaviour and sprite for each one. When the Pinata bursts, you instantiate random prefabs at random positions surrounding the Pinata, up to you how to position these objects.
Something along these lines should do the trick:
class Pinata : Monobehaviour
{
public GameObject[] pickupPrefabs;
public int numberOfItemsToSpawn; //This can be random
//any other variables that influence spawning
//Other methods
public void Burst()
{
for(int i = 0; i < numberOfItemsToSpawn; i++)
{
//Length - 1 because the range is inclusive, may return
//the length of the array otherwise, and throw exceptions
int randomItem = Random.Range(0, pickupPrefabs.Length - 1);
GameObject pickup = (GameObject)Instantiate(pickupPrefabs[randomItem]);
pickup.transform.position = transform.position;
//the position can be randomised, you can also do other cool effects like apply an explosive force or something
}
}
}
Bare in mind, if you want the game to be consistent, then each behaviour prefab would have there own predefined sprite, this would not be randomised. The only thing randomised would be the spawning and positioning.
If you did want to randomise the sprites for the behaviours then you'd have to add this to the Pinata class:
public class Pinata : Monobehaviour
{
//An array of all possible sprites
public Sprite[] objectSprites;
public void Burst()
{
//the stuff I mentioned earlier
int randomSprite = Random.Range(0, objectSprites.Length - 1);
SpriteRenderer renderer = pickup.GetComponent<SpriteRenderer>();
//Set the sprite of the renderer to a random one
renderer.sprite = objectSprites[randomSprite];
float flip = Random.value;
//not essential, but can make it more random
if(flip > 0.5)
{
renderer.flipX = true;
}
}
}
You can use Unity random for all your random needs, https://docs.unity3d.com/ScriptReference/Random.html
Hopefully this'll lead you in the right direction.