I'm currently working on a practice project which involves stacking of blocks. It is sort of similar to the game of stackers (the arcade game), except my version uses free-form movement instead of grid-based movement.
I have 3 prefabs for the blocks: SingleBlock, DoubleBlock, TripleBlock.
I structured each of them like this: the parent is an empty gameobject with my MovementScript that moves them left/right, while the children are the block sprite with a BoxCollider2D.
The MovementScript is attached to the empty game object(the parent) so that the block set moves uniformly left/right, for what it's worth.
For the actual stacking logic, I'm using Raycast2D to detect if there is a block below. But the problem is the results I get is unexpected.
Here is a snippet of my current code:
foreach(Transform t in currentBlockSet.transform)
{
// get all the children of this blockset. to do this, we use the transform beause it is IEnumerable
GameObject block = t.gameObject;
RaycastHit2D hit = Physics2D.Raycast(block.transform.position, Vector3.down, rayCastLength); // 0.5f raycast length
//Debug.DrawRay(block.transform.position, Vector3.down * rayCastLength);
if(hit.collider != null)
{
// this means there is a block below, we hit something
Debug.Log("True");
}
else
{
Debug.Log("False");
}
}
This code is called each time the player stops the current blockset that is moving by the way.
The problem is I always get true in my logs even though I purposely didn't align the block set properly. I never get false even if I'm way off with my alignment. Why is this so?
I do like to mention that there's nothing else in the scene. It is just the blocks, so there can't be another object to collide with.
Is there something wrong with the logic or how I'm using Raycast2D?
Appreciate any help.
I believe what is happening is that they Raycast2D is detecting the block it is shooting from. Since you are using the block's transform.position as the origin of the Raycast, this means that the Raycast will be shooting from the center of the block.
To test this, I slightly modified your code so it would fire a Raycast in the same way, but instead of logging just "True" I logged the name of the object that was hit.
void Update ()
{
RaycastHit2D hit = Physics2D.Raycast(this.transform.position, Vector3.down, 0.5f); // 0.5f raycast length
if (hit.collider != null)
{
Debug.Log(hit.collider.gameObject.name);
}
else
{
Debug.Log("False");
}
When run, the object that was detected by the Raycast, as expected, was the object the Raycast was originating from.
To solve this issue, I would suggest adding an empty child GameObject to your blocks called "RaycastOrigin". Position this GameObject just underneath the block such that it is outside of the block's box collider. Then you fire your Raycast from the "RaycastOrigin" instead of the blocks transform.position That way, the Raycast would not hit the block its shooting from, but rather a block below it.
Related
In my game you can use, axes, pickaxes, hoes, etc. Each to gather a different type of material (ore, wood, crops, etc).
My old system I just set a tiny collider on the tile in front of my player, and then in OnTriggerEnter2D on my nodes, trees, farm-tiles, etc. I checked what category of my Active Item that was entering (below example on my mining-nodes):
void OnTriggerEnter2D(Collider2D other)
{
if(other.gameObject.CompareTag("ActiveItem"))
{
ItemObject activeItemObj = GameManager.manager.activeItem.GetComponent<InventoryItem>().item;
if(activeItemObj.category == "pickaxe")
{
if (!isHit)
{
hit();
}
}
}
}
However this always felt kind of ugly, plus no matter how small the "front collider" is, there is always a chance to hit the tile intersection and hit as many as 4 tiles at the same time.
So instead I wanted to use RayCastHit2D, so I created these methods in my PlayerController Script:
public GameObject GetTileInFront()
{
Vector2 frontRay = new Vector2(transform.position.x + lastMove.x, transform.position.y + lastMove.y + 0.5f);
RaycastHit2D hit = Physics2D.Raycast(frontRay, Vector2.zero);
if (hit.collider != null)
{
Debug.Log("Target Position: " + hit.collider.gameObject.name);
return hit.collider.gameObject;
}
return null;
}
public void ActionInFront(string activeItem)
{
if (activeItem.Equals("pickaxe"))
{
if (GetTileInFront().CompareTag("Node"))
{
GetTileInFront().GetComponent<NodeManager>().hit();
}
}
}
Now I obviously need to create checks like this for every type of tool/tile.
Which leads me to my question:
Which of these 2 methods would be better for performance? I am completely self tought so I always worry I am making some obvious blunder that I cant see.
I definently like the precision of the RayCast better (even though it forces all my colliders to be at least 1x1 in size due to checking 1 tile in front).
Your solution looks OK to me!
Raycasts have the potential to be abused to the point where they're very expensive. The three things you'll want to be mindful of:
How long is the raycast? Keeping it under a tiles length should be good in your case.
What types of geometry is in your scene?: Mesh colliders can cause Raycasts to chug, but in a 2D game where I imagine everything is either a square or a circle you should be good.
How frequently do they occur? Calling a Raycast every frame can be rather expensive, but only calling it when your player is doing the action should be fine.
You mentioned that you were worried your solution will force your colliders to be at least 1x1, but there are ways to get around that. Right now what you're doing is just checking right at a point in front of your player. Instead you may want to consider casting a ray from the player in that direction and it could hit anything a long the way, even if it's smaller than a 1x1 tile. You can do this by changing the second argument of your raycast to something like this:
Physics2D.Raycast(transform.position, Vector2.up * .5f);
The Raycast function takes the point it will be shot from as the first argument, and the direction/length as the second argument (leaving the length 0 like did will just check the point at the first argument).
It's worth noting that if you try to do this with your current solution, all that will happen is that you're character will try to mine himself as it will be the first thing the ray hits! You'll need to add a LayerMask to your raycast to determine what the ray can and can't hit (or if you're lazy, just make sure the ray starts from a point outside the player's collision).
I'm new to unity and still learning. I have an object (a tile) at location (-35, 0, -35) and a second object at (-35, 1, -35). I am firing a raycast from the tile upwards towards the second object (from the tile) using the below code and logging it to see the result. The unfortunate thing is that it is not detecting it. This is the code:
Vector3 upward= transform.TransformDirection(Vector3.up);
bool test = Physics.Raycast(transform.position, upward, 2);
Debug.Log(test);
unfortunatly though, the output always returns false. Is there something I am not understanding?
Thanks,
Because of the collider that i had on the object, the raycast started inside the mesh collider and was not detecting it because it was not hitting the collider, only exiting it.
I need to move some transforms with attached colliders to a specific position, then check if one of them is hit by raycast.
I've done that the naive way (pseudo code) :
foreach(object in objects){
actual_position = object.transform.position
object.transform.position = object.new_position
}
if(Physics.Raycast(...)) objectHit();
// Then I revert each of them them back to their actual_position
After testing multiple times with the same context (same positions between tests for each objects), the raycast sometimes miss, sometimes not (~50/50).
Done a bit of research and found that in the Raycast doc page :
If you move colliders from scripting or by animation, there needs to
be at least one FixedUpdate executed so that the physics library can
update its data structures, before a Raycast will hit the collider at
it's new position.
So I calmed my anger and started looking for a solution.
This thread has a way using a coroutine to wait the next tick :
Raycast doesn't work properly
I'm affraid it won't work for me, as I need the objects to get back to their real position instantly.
This process can happen multiple times per frame (each time a player fire his weapon)
Is there a way to force the colliders update ?
If not... should I make my own raycasts and colliders ? :(
Thanks
Another workaround is to deactivate and activate Gameobject (attached collider) immediately. In this case collider position will be updated in single frame.
Another solution is
Physics.autoSyncTransforms and Physics.SyncTransform
Maybe you can try this out (adding to your pseudo-code):
foreach(object in objects)
{
actual_position = object.transform.position;
object.transform.position = object.new_position;
StartCoroutine(CheckToRevertOnNextFixedUpdate(object, actual_position));
}
IEnumerator CheckToRevertOnNextFixedUpdate(object, actual_position)
{
yield return new WaitForFixedUpdate();
if(Physics.Raycast(...)) objectHit();
// Then I revert each of them them back to their actual_position
}
Essentially this delays your check to the next FixedUpdate() for each object - and reverts each of them if needed. I hope this is not overcomplicated, since you only add a few lines of code.
I'm also assuming that moving the object's position for 1 FixedUpdate() frame would not have a visual effect of the the object teleporting to the new position and back. However, you can always move the collider only, and then move the rest of the transform there after the FixedUpdate().
Performance-wise, the best method seems to be updating the RigidBody.position:
private Rigidbody Rigidbody;
void Start()
{
Rigidbody = gameObject.GetComponent<Rigidbody>();
}
void Upate()
{
//.... your code
Rigidbody.position = newPosition;
}
Much faster then deactivate/activate or Physics.SyncTransform().
I'm making a platformer (sidescroller), and right now I'm making a grenade-launcher for the player.
It spawns grenades which is a prefab, this prefab has a Circle Collider 2D which is the blast radius for the explosion.
When a grenade is spawned I run this (amongst other things);
// Add the initial force
rBody.AddForce(new Vector2(forceX, forceY) *300f);
Invoke("Explode", 2.5f);
I'm having trouble figuring out how I should handle the Explode function.
I would like to find all the GameObjects that are colliding with the Circle Collider 2D at that point, but I can't find a way to do it.
I would like to be able to do something like this (not real code but you understand what I'm trying to do)
void Explode () {
collidingObjects = circleCollider.getCollisions();
foreach(collidingObject as entity) {
if(entity = 'player')
player.pushLeftOrRight()
elseif(entity = 'enemy')
grenade.dealDamage(grenade.damage)
}
Debug.Log("Explode");
Destroy(gameObject);
}
I'm guessing that it wouldn't work so can anyone point me in the right direction?
In your grenade's script, use OnTriggerEnter or OnCollisionEnter to log collisions; basically just have a HashSet for each grenade that is updated and keeps track of what it is colliding with. Then Explode just has to iterate through this set when you call it.
I am trying to build a 2D space shooter game where the player will remain constant at the bottom of the screen and will move left and right. When the player fires a bullet(projectile), the bullet is going up just fine, but it is coming back again. Also if two bullets collide, they react to the collision.
I have two questions here:
How can I ensure that the bullet does not come back? (One way is to destroy it after a few seconds, but not sure if this is the right way)
How do I avoid collision between the bullets.
Here is my code snippet:
void Update () {
if (Input.GetButtonDown("Fire1"))
{
Rigidbody2D clone = (Rigidbody2D)Instantiate(bullet, transform.position + transform.up, transform.rotation);
clone.velocity = transform.TransformDirection(Vector3.up * 20);
}
}
I am very new to Unity and somehow wrote this code by looking into Unity Forums.Any help will be very much appreciated.
Thanks
More details:
I have created the bullet and added Rigidbody2D and a BoxCollider2D for this object. Made the bullet object a prefab and I dragged it under the Player object. Now bullet is a child of the Player object. Attached the script above to the Player object.
To answer your immediate questions:
1) I am not too sure about how you have the scene setup or about using the Unity 2D tools but usually if your bullets are coming back down then I would assume that you still have gravity applied to the rigidbody -- so make sure that you have that unchecked.
2) With Unity3D for objects to interact with each other they need rigidbodies attached. This is vital, say, if you want your lasers to destroy Monster #1. However, you only need one rigidbody attached to an object to have a desired effect. I would suggest removing the rigidbody from your laser and attach rigidbodies to objects ( Monster #1 ) that you want to be affected by the laser fire. ( That should work, if not there are other options -- one using layers and ignoring particular objects on those layers ).
Tips:
Here are some extra things. When you are creating objects and they fly off screen over time they will build up. Imagine you are in a large battle and you instantiated hundreds of lasers -- it will eventually be an issue for performance. So always consider Destroying objects after a certain amount of time.
Also, after looking at your code, I can't tell whether the velocity of the object is based upon its current position and nothing is being added. What I think is happening is when you instantiate an object it may be moving up, but because there is no incrementer, the object ( as it gets higher ) slows until it is at a rate which it is falling. For example this
clone.velocity = transform.TransformDirection(Vector3.up * 20);
should probably do this:
clone.velocity += transform.TransformDirection(Vector3.up * 20);
Heres http://docs.unity3d.com/Documentation/ScriptReference/Vector3-up.html, to read up on velocity. The problem is is that you need to continuously apply a constant forward motion so things in motion will tend to stay in motion. If not gravity will pull it down.
Also, heres a bit of code that I've used in the past that's created a pretty cool shooting laser effect thing:
public class Laser : MonoBehaviour {
public int fireRate = 70;
void Start(){
InvokeRepeating("FireLaser", 0.01f, 0.009f);
Destroy( gameObject, 2.0f );
}
void FireLaser(){
transform.Translate( Vector3.up * fireRate * Time.deltaTime );
}
}
edit: it's 3 a.m. and I hate proofreading.