Tilemaps.SetTile method only partially working when in Update() method - c#

I'm completely new (<1 week) to Unity, C# and game development generally. I'm starting out making a 2D isometric, grid-based CRPG and have hit a major wall with Unity's tilemaps.
I have a component which creates a tilemap tile underneath the parent GameObject using Tilemap.SetTile. As the GameObject moves around the grid, the tile should move with it and delete the previous tile. The tile can then create a moving obstacle node for pathfinding (which is working fine with static obstacles).
The problem is that the script actually does briefly work, just not for very long. If the GameObject moves along a path 'n' cells in a grid, the script will render 'n/2' cells every time the script is run, then stop working altogether. There's no error messages, freezing or anything, the tiles just aren't being drawn, and I know from logging in the console that the script is correctly tracking where the tiles should be drawn.
The obstacle script looks like this:
private void SetObstacleTile()
{
currentCell = obstacleMap.WorldToCell(transform.position);
if (currentCell != previousCell)
{
//set the new tile
obstacleMap.SetTile(currentCell, obstacleTile);
// erase previous
obstacleMap.SetTile(previousCell, null);
// save the new position for next frame
previousCell = currentCell;
Debug.Log(currentCell);
}
}
private void Update()
{
SetObstacleTile();
}
... and the movement currently looks like this:
void Update()
{
if (Vector3.Distance(transform.position, destination) > 0.01f)
transform.position = Vector3.MoveTowards(transform.position, destination, movementSpeed * Time.deltaTime);
}
My gut feeling is that it's something to do with how the Update() functions in the character movement and obstacle tiles component are interacting with each other but I don't have the experience to know how close I am to the problem. Any help with what could be causing this would be massively appreciated.

Related

Unity OnTriggerEnter2D not registering

I am new to using unity and am having a real problem getting trigger collisions to register.
public void OnTriggerEnter2D(Collider2D other)
{
print("collide");
if (other.CompareTag("Fish"))
{
print("Caught");
}
}
I have 2D polygon colliders and a rigid body on both items. I have also got 1 set a trigger(have tried having both as trigger). However one UI item is a sprite and the other is an image.
Both items are also tagged with "fish"
Would really appreciate any help.
Thanks
There are four things I can think of which need to happen so that OnTriggerEnter gets called:
The two objects' colliders need to actually be overlapping. Just because their rendered pixels are overlapping, doesn't mean their colliders are overlapping. This can be checked in the editor. Their colliders are indicated with a green outline in the Scene tab. If you don't see anything, make sure the button labeled Gizmos is turned on.
The two objects need to be in physics layers which are set to collide with each other. You can check this in Edit > Settings > Physics2D > Layer Collision Matrix.
Both objects need to have Rigidbody2D components attached. Make sure these aren't Rigidbody components, as those are for 3D physics.
The object which contains the OnTriggerEnter2D event needs to have isTrigger = true.
I've tried several things to do.
First I've checked recommendations from another post.
In nutshell:
Checked if I have rigid body at least for one of objects
Checked layers
Checked tags
Played with "is trigger".
Finally, the solution was to add script to object via create and add component button and not to drop written script to it. Have no idea, but for me that was the solution. Even it was same script.
Both a Sprite and an Image can collide with another Image. What might be wrong is that your sprite may look like touching the image however in the scene the canvas might be far away, so camera may deceive you. Here is the sample code for my tests:
Script that moves the Sprite:
private Rigidbody2D _rigidbody;
private void Awake() => _rigidbody = GetComponent<Rigidbody2D>();
private void FixedUpdate()
{
if (Input.GetKey(KeyCode.A))
{
var movement = -transform.right * Time.fixedDeltaTime * 250;
_rigidbody.MovePosition(transform.position + movement);
}
if (Input.GetKey(KeyCode.D))
{
var movement = transform.right * Time.fixedDeltaTime * 250;
_rigidbody.MovePosition(transform.position + movement);
}
}
Trigger script on image:
private void OnTriggerEnter2D(Collider2D other)
{
Debug.Log(other.name);
}
Nothing helps for me, but in my case I didn't know there was a place in code that breaks collision layers (that is do not change the collision matrix settings visually):
Physics2D.IgnoreLayerCollision(...
Check that too and make sure it is not called.

How to stop player from automatically jumping on a moving platform?

I made a moving platform that goes vertically top to bottom and then bottom to top and so on. Platform moves fine but placing my player on it makes my player unstable.
When platform moves from top to bottom, my player kinda bounces on it. When platform moves from bottom to top, it remain stable on the way but when it reaches top point, my player makes a jump by itself.
Its get even worse when i increase the speed of my platform. I don't know if its due to unity 2d physics effect or what. I have tried to use physics material 2D on my player object and platform by setting bounce to 0 and friction to 50 but nothing seems to work. Any one have idea how to disable physics effect of moving platform? Following is my code for moving platform:
public class BrickMoveVErtical : MonoBehaviour {
public Vector3 positionOne;
public Vector3 positiontwo;
public Vector3 nextposition;
/*Empty object is already made on unity editor and its parent of platform(Plank) and other
empty object "pointB". Point "B" is already mapped on editor and platform is set to go from
its original pos to point B */
public Transform plankTranform;
public Transform positionBTransform;
public float speed;
// Use this for initialization
void Start () {
positionOne = plankTranform.localPosition;
positiontwo = positionBTransform.localPosition;
nextposition = positiontwo;
}
// Update is called once per frame
void Update () {
move();
}
private void move() {
plankTranform.localPosition = Vector3.MoveTowards(plankTranform.localPosition,nextposition,Time.deltaTime*speed);
if(Vector3.Distance(plankTranform.localPosition,nextposition)<0.1)
{ changeMovementPlank(); }
}
void changeMovementPlank() {
nextposition = nextposition != positionOne ? positionOne : positiontwo;
}
}
I had similar problems with fast platforms in my game, the character would be left out in the air or slide off, depending on the direction of the platform.
I found a workaround for the problem by simply changing the character transform.parent to the platform it was jumping on, whenever the player exists the platform collider, simply return the players original transform.parent.
Note that this might cause other bugs or problems that will need to optimize, depending on your game that is.
You can use this code to set/unset the parent of a GameObject:
void OnTriggerEnter2D(Collider2D other)
{
if(other.gameObject.tag == "PlatformGroup")
player.SetParent(other.gameObject.transform)
}
void OnTriggerExit2D(Collider2D other)
{
if(other.gameObject.tag == "PlatformGroup")
player.SetParent(null);
}
What you should do is:
Create an empty GameObject, and you make your platform child of this
empty GameObject. Tag it as PlatformGroup.
You add the script to move the platform to this EmptyGame Object,
instead of using it in the platform itself.
Then add a collider to empty gameobject(PlatformGroup) and another to the Player. Set the one of the player as trigger.
Add the code above to your player controller.
Now, when the Player jumps over the Platform if will become a child of the same GameObject of the platform and they will move together without any deformations in the GameObject. Also when the player leaves the platform, walking or jumping out of it, it will stop being child.

Unity: How can I programmatically solve an animated sprite with multiple colliders causing scoring issues?

I have an animated sprite which has 8 frames of animation. Each frame has a PolygonCollider2D attached as an element of an array thus providing it with accurate collision detection no matter which frame is playing.
When I had only one collider on the sprite and it passed through the "score" object the player's score increased by 1 point.
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Point")
{
score++;
scoreText.text = score.ToString();
return;
}
}
Polygon Collider 2D array in Unity inspector
Each collider in the array becomes active in turn...
public void SetColliderForSprite(int spriteNum)
{
colliders[batColliderIndex].enabled = false;
batColliderIndex = spriteNum;
colliders[batColliderIndex].enabled = true;
}
Now that each frame of the animation has its own collider I find that the score is increasing at an alarming rate as each frame plays multiple times whilst inside the "score" game object, triggering the point scoring logic multiple times, before the player leaves said object.
I'm wondering what the ideal programmatic solution is in order to ensure that on entering the "point" collider object the player only gets a score increment once before exiting the area?
Thanks in advance for any suggestions (and my apologies if this has been answered elsewhere).
EDIT: I was thinking about my post below and I think there is a simpler solution. You should be able to animate your collider shape rather than having 1 collider per animation frame. For example, see this question on How to update 2d colliders with sprite animation.
(And as a side note, you can also have multiple polygon colliders on each part of the sprite, and animate the colliders to move accordingly. For example, see this post on Animating Polygon Collider 2D. This alternative approach might make the suggestion I make in my post below simpler.)
One suggestion I have is to only increment the score when the first "Point" trigger is detected and then not incrementing the score again until the object has fully left. This can be implemented by keeping track of how many colliders (or which specific colliders) have entered and exited the object. Something like the following:
private const string PointTag = "Point";
private int _triggerCount;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == PointTag)
{
bool firstCollision = (_triggerCount == 0);
if (firstCollision)
{
score++;
scoreText.text = score.ToString();
}
_triggerCount++;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == PointTag)
{
_triggerCount--;
}
}
However, once a collision is in progress, you will likely have to change the behaviour so that all colliders stay active until the object has fully left the collision area. This is because if you disable the collider, the OnTriggerExit2D event will not fire.
This solution may not be acceptable depending on how accurately you need to detect that the VampireBat has left the collision area.
If it doesn't need to be very accurate, you can simplify the solution by adding a 9th collider with a unique tag that covers the entire area of the sprite's animation. By using this 9th collider to determine when the player has left the collision area, you would not need to worry about affecting the code related to showing the appropriate collider per animation frame. (But note that the code example here would have to be modified so that the OnTriggerExit2D uses this 9th collider object.)
If it needs to be more accurate, I think there is likely a solution similar to what I suggested based on which colliders are currently active, the details of your collider shapes & animation frames, and OnTrigerEnter2D, OnTriggerStay, and OnTriggerExit2D. But note that you may also need to be careful that a collision is not re-triggered immediately. (For example, the object may have fully left the collision area, but the next animation frame might trigger a collision again immediately.)
Thanks for the response. Your edit got me to thinking about child objects. I added a child collider (with an index value of 9) then checked for a collision with that.
child object in inspector
Referenced the child collider in the collider array.
Collider array in inspector
After that it was just a matter of referencing it in the player controller script.
// Update is called once per frame
void Update () {
//Player input
if (Input.GetMouseButtonDown(0))
{
bat.velocity = new Vector2(0, 4);
//ADD BAT FLAP SOUND LATER!!!
}
scoreText.text = score.ToString();
Debug.Log("SCORE: " + score);
}
//Point increment function
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Point" && batColliderIndex == 9)
{
score++;
return;
}
}
I now have pixel perfect collision detection against obstacles and a score which increments correctly.
Thanks again.

Creating an endless spinner in Unity

I want a gameobject spinning around its y-axis. This spinner should have a initial movement direction and when colliding with something, it should change its direction.
I created a little picture to show what I mean, but I want the behaviour for a 3D game.
So I just started with the rotation of the spinner,
public class Spinner : MonoBehaviour
{
[SerializeField]
private float movementSpeed; // speed when moving
[SerializeField]
private float rotationSpeed; // speed when rotating
private Rigidbody rigid;
private Vector3 movementDirection; // the direction the spinner is moving
private void Start()
{
rigid = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
transform.Rotate(0, rotationSpeed, 0); // rotate the spinner around its y-axis
}
private void OnCollisionEnter(Collision col)
{
// set new direction
}
}
How can I move the spinner, that it moves along a direction and whenever it collides with something, it changes its direction. It should never stop moving or rotating.
I would like to point out a few things:
The Unity Physics Engine will make collisions to absorb part of the force which moves your spinner, so unless you keep adding "artificial" forces to the spinner it will eventually stop.
The "air friction" in your scene will also reduce the force of your
spinner, so it will slow it down. You should add a material to the
spinner which has 0 Dynamic Friction
Based on the comment you left in #reymoss' answer, you may consider
to add the bouncy material to the walls, and not to the spinning
GameObject.
To sum up, the issue here is if you want a GameObject to bounce against a wall using the Physics Engine, the object will eventually stop, because some of the forces will be absorbed in each collision. That means you will need to keep adding external forces every time a collision takes place, to keep it moving endlessly.
Something you can try is,
1- Add a bouncy material to the walls and remove the dynamic friction of your spinner. In the following link you can learn about this:
https://docs.unity3d.com/Manual/class-PhysicMaterial.html
2- Add to the Spinner a Collider, so when it detects a collision with a wall (you can tag the walls as so for example) add an additional force to the spinner in the direction it is already moving, so it will compensate the energy lost during the collision.
void OnTriggerExit(Collider other) {
if(other.tag == "wall") {
rigidbody.AddForce(rigidbody.velocity.normalized * Time.deltaTime * forceAmount);
}
}
The idea is to let the Engine decide the direction of the Spinner after the collision with the wall, and as soon as the GameObject leaves the trigger, you add an additional force in the current direction.
Then you can play with the Bounciness of the wall's material and with the forceAmount, until your spinner moves as you have in mind.
Note: Since you will have 2 materials, one in the walls and another in the spinner, maybe playing with the Friction Combine and Bounce Combine you will be able to avoid the force lost during collisions, so you will not need to add the external force I mention in the second step. However I have never tried this.
Let me know if it works. I can't try myself the solution until I arrive home.
If you give the object an initial velocity, attach a collider, create and assign a Physics Material 2D to the collider (to apply bounciness), and attach Colliders to the walls, you can have it bounce around with minimal code.
private void Start()
{
rigid = GetComponent<Rigidbody>();
rigid.velocity = new Vector3(2f, 3f, 1f) // initialize velocity here
}

Unity3D OnPointerClick is based on cam position?

Ok so I'm completely lost on how to approach this problem. I have a implementation of IPointerClickHandler that goes something like this :
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log(gameObject.name);
//Othe rstuff
}
So at least every object with this attached to it should print out its name to the console. Now I have 2 screenshot to further Explain the Problem.
The two tiles with the lilipop and the briefcase do not fire OnPointerClick when the camera is in this position.
Move the cam a bit to the right and the event is firec correcrtly.
What have I checked/tried:
Nothing is blocking the object
The camera has a 2D Physics Ray caster attached to it. Its layers are correct.
The tiles have BoxCollider2Ds.
Edit: Did a simple raycast test; THis is the code attached to the camera. This hits from any position as long as even part of the collider is vissible on screen. While if even a part of the collider is near the edge of the viewport or outside of it. OnPointerClick does not work. How do I solve this?
public class RayCasterTest : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(pos, Vector2.up, 0.0f);
if (hit.collider != null)
Debug.Log(hit.collider.gameObject.name);
}
}
Ok so how do I explain this. I have a big 2dBoxcollider behind everything. I use it as camera bounds. Now for some reason from time to time the EventSystem would hit it and not the correct collider that is definitely on top if it. Setting this collider to a layer excluded from the Physics3d raycaster seems to have fixed the problem.

Categories

Resources