Unity: Stop RigidBody2D from pushing each other - 2d

I have a player and a few NPCs.
The NPCs have random movement, and I control my players movement. They both have RigidBody2D to deal with physics and BoxCollider2D to deal with Collisions.
However, when I walk into a NPC my player pushes it. Same thing if a NPC moves into my player while the player stands still.
I can't set the mass of either object to some extreme number since that will interfere with how they behave with other objects in my game.
What I want:
When an NPC collides with the player, the NPC stops (I get this effect if I set player mass to ex. 1000, but then the player can push the NPC, which I dont want), and the NPC acts as a "wall", i.e it doesnt move, but nor can the player push it around. How can I do this?
EDIT: So I created my own method for it:
void OnCollisionEnter2D(Collision2D other){
if (other.gameObject.name == "Player") {
collidedWithPlayer = true; //we only move if !collidedWithPlayer
isMoving = false; //stop moving
myRigidBody.mass = 1000; //turn NPC into "wall"
}
}
void OnCollisionExit2D(Collision2D other){
if (other.gameObject.name == "Player") {
collidedWithPlayer = false;
waitCounter = waitTime; //stop NPC from starting to move right after we exit
myRigidBody.mass = 1;
}
}
I mean this works, but is there no native method to do this?

What you are trying to do is essentially use a "realistic" physics engine to create rather unrealistic physics. That's why it's not supported by Unity's built-in functions. Furthermore, you are correct in assuming that messing with the object masses is not a good idea.
Here's one suggestion that avoids playing with mass. It's a bit kludgey, but give it a try and see if it works for you. (I assume your player rigidbody is not Kinematic?)
Step 1: Create 2 new layers; call them NPCWall and PlayerWall. Setup 2D physics so that player collides with NPCWall and NPC collides with PlayerWall, but player does not collide with NPCs. (If your NPCs and player are on the same layer, then of course put them on 2 separate layers.)
Step 2: Create an NPCWall prefab that uses the same kind of collider as the NPCs. I assume you only have one size of NPC. Likewise, create a PlayerWall prefab that uses the same kind of collider as the player. Set the NPCWall prefab to NPCWall layer, and PlayerWall prefab to PlayerWall layer.
Step 3: We can't parent the NPCWall to the NPC, because it would end up as part of the rigidbody. Therefore add a simple script to the NPCWall and PlayerWall:
public class TrackingWall
{
//This invisible wall follows an NPC around to block the player.
//It also follows the player around to block NPCs.
Transform followTransform;
public void Init(Transform targetTrans)
{
followTransform = targetTrans;
transform.position = followTransform.position;
transform.rotation = followTransform.rotation;
}
void Update()
{
if (followTransform == null)
Destroy(gameObject);
transform.position = followTransform.position;
transform.rotation = followTransform.rotation;
}
}
Step 4: In the NPC and player scripts:
TrackingWall myWallPrefab;
void Start()
{
[whatever else you are doing in Start()]
TrackingWall myWall = Instantiate<TrackingWall>(myWallPrefab);
myWall.Init(transform);
}
Obviously, for NPCs, myWallPrefab should be set to the NPCWall prefab, and for players, myWallPrefab should be set to the PlayerWall prefab.
In theory this should give each character an impenetrable, immovable wall that only moves when they do, prevents other characters from pushing them, and cleans itself up when they are destroyed. I can't guarantee it will work though!

Related

JavaFX 3D set rotation to "absolute position"

I am playing around, trying to make a little JavaFX application to visualize data received via the serial port from an arduino-based board and some sensors.
After adding some live-updating LineGraphs, I am currently trying to display the roll, pitch and yaw values received from the micro-controller, by rotating a simple box-element.
I have one thread calling a function every x ms, which stores the incoming data into an ObservableList with an changeListener and calls the controller based function to update/rotate the orientation of the box.
Since the calculation of the angles is allready done on the micro-controller, I would like to rotate the box to the received absolute orientation.
From what I've understand so far, I can't simply rotate from any previous orientation to a new absolute one, but only change the orientation relatively to the previous one.
I came up with the following idea to just subtract the last roll/pitch/yaw values from the penultimate one of the observableList.
Data dataTmp = observableList.get(observableList.size()-2);
Data dataTmp2 = observableList.get(observableList.size()-1);
newRoll = dataTmp2.getRoll() - dataTmp.getRoll();
newPitch = dataTmp2.getPitch() - dataTmp.getPitch();
newYaw = dataTmp2.getYaw() - dataTmp.getYaw();
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.setToPosition(newRoll, newPitch, newYaw);
}
});
//...
This only works out to a certain extent. I still want to rotate to the absolute position received from the micro-controller.
So my question is this: Is there a way to reset the orientation of the box to e.g. 0, 0, 0 from where I could rotate to my new absolute orientation? Simply removing the box and adding a new one did not work out at all.
group.getChildren().remove(box);
box = new Box(300,50,300);
group.getChildren().add(box);
Thank you in advance for any ideas or even solutions. If you need more information or code snippets let me know.
Referring to this example, an onMouseMoved handler rotates the red Box around the x and y axes as the mouse moves. The following onKeyPressed handler restores the red Box to its original position when the Z key is pressed. Each handler uses the setAngle() method of the Rotate class.
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.Z) {
content.rx.setAngle(0);
content.ry.setAngle(0);
content.rz.setAngle(0);
}
});
Similarly, your setToPosition() implementation can invoke setAngle() to establish the new roll, pitch and yaw values.
Before:
After:
More subtly, verify that you synchronize access to any data shared between your data acquisition thread and the JavaFX application thread. This example illustrates a Task<Canvas>, while your application might instead implement a Task<Point3D>, where a Point3D holds the roll, pitch and yaw values.

Calculating bank angle between two objects

I have a drone following a path for movement. That is, it doesn't use a rigidbody so I don't have access to velocity or magnitude and such. It follows the path just fine, but I would like to add banking to it when it turns left or right. I use a dummy object in front of the drone, thinking I could calculate the bank/tilt amount using the transform vectors from the two objects.
I've been working on this for days as I don't have a lot of math skills. Basically I've been copying pieces of code trying to get things to work. Nothing I do works to make the drone bank. The following code manages to spin (not bank).
// Update is called once per frame
void Update () {
Quaternion rotation = Quaternion.identity;
Vector3 dir = (dummyObject.transform.position - this.transform.position).normalized;
float angle = Vector3.Angle( dir, transform.up );
float rollAngle = CalculateRollAngle(angle);
rotation.SetLookRotation(dir, transform.right);// + rollIntensity * smoothRoll * right);
rotation *= Quaternion.Euler(new Vector3(0, 0, rollAngle));
transform.rotation = rotation;
}
/// <summary>
/// Calculates Roll and smoothes it (to compensates for non C2 continuous control points algorithm) /// </summary>
/// <returns>The roll angle.</returns>
/// <param name="rollFactor">Roll factor.</param>
float CalculateRollAngle(float rollFactor)
{
smoothRoll = Mathf.Lerp(smoothRoll, rollFactor, rollSmoothing * Time.deltaTime);
float angle = Mathf.Atan2(1, smoothRoll * rollIntensity);
angle *= Mathf.Rad2Deg;
angle -= 90;
TurnRollAngle = angle;
angle += RollOffset;
return angle;
}
Assuming you have waypoints the drone is following, you should figure out the angle between the last two (i.e. your "now-facing" and "will be facing" directions). The easy way is to use Vector2.Angle.
I would use this angle to determine the amount I'll tilt the drone's body: the sharper the turn, the harder the banking. I would use a ratio value (public initially so I can manipulate it from the editor).
Next, instead of doing any math I would rely on the engine to do the rotation for me - so I would go for Transform.Rotate function.In case banking can go too high and look silly, I would set a maximum for that and Clamp my calculated banking angle between zero and max.
Without knowing exactly what you do and how, it's not easy to give perfect code, but for a better understand of the above, here's some (untested, i.e. pseudo) code for the solution I visualize:
public float turnSpeed = 7.0f; //the drone will "rotate toward the new waypoint" by this speed
//bankSpeed+turnBankRatio must be two times "faster" (and/or smaller degree) than turning, see details in 'EDIT' as of why:
public float bankSpeed = 14.0f; //banking speed
public float turnBankRatio = .5f; //90 degree turn == 45 degree banking
private float turnAngle = 0.0f; //this is the 'x' degree turning angle we'll "Lerp"
private float turnAngleABS = 0.0f; //same as turnAngle but it's an absolute value. Storing to avoid Mathf.Abs() in Update()!
private float bankAngle = 0.0f; //banking degree
private bool isTurning = false; //are we turning right now?
//when the action is fired for the drone it should go for the next waypoint, call this guy
private void TurningTrigger() {
//remove this line after testing, it's some extra safety
if (isTurning) { Debug.LogError("oups! must not be possible!"); return; }
Vector2 droneOLD2DAngle = GetGO2DPos(transform.position);
//do the code you do for the turning/rotation of drone here!
//or use the next waypoint's .position as the new angle if you are OK
//with the snippet doing the turning for you along with banking. then:
Vector2 droneNEW2DAngle = GetGO2DPos(transform.position);
turnAngle = Vector2.Angle(droneOLD2DAngle, droneNEW2DAngle); //turn degree
turnAngleABS = Mathf.Abs(turnAngle); //avoiding Mathf.Abs() in Update()
bankAngle = turnAngle * turnBankRatio; //bank angle
//you can remove this after testing. This is to make sure banking can
//do a full run before the drone hits the next waypoint!
if ((turnAngle * turnSpeed) < (bankAngle * bankSpeed)) {
Debug.LogError("Banking degree too high, or banking speed too low to complete maneuver!");
}
//you can clamp or set turnAngle based on a min/max here
isTurning = true; //all values were set, turning and banking can start!
}
//get 2D position of a GO (simplified)
private Vector2 GetGO2DPos(Vector3 worldPos) {
return new Vector2(worldPos.x, worldPos.z);
}
private void Update() {
if (isTurning) {
//assuming the drone is banking to the "side" and "side" only
transform.Rotate(0, 0, bankAngle * time.deltaTime * bankSpeed, Space.Self); //banking
//if the drone is facing the next waypoint already, set
//isTurning to false
} else if (turnAngleABS > 0.0f) {
//reset back to original position (with same speed as above)
//at least "normal speed" is a must, otherwise drone might hit the
//next waypoint before the banking reset can finish!
float bankAngle_delta = bankAngle * time.deltaTime * bankSpeed;
transform.Rotate(0, 0, -1 * bankAngle_delta, Space.Self);
turnAngleABS -= (bankAngle_delta > 0.0f) &#63 bankAngle_delta : -1 * bankAngle_delta;
}
//the banking was probably not set back to exactly 0, as time.deltaTime
//is not a fixed value. if this happened and looks ugly, reset
//drone's "z" to Quaternion.identity.z. if it also looks ugly,
//you need to test if you don't """over bank""" in the above code
//by comparing bankAngle_delta + 'calculated banking angle' against
//the identity.z value, and reset bankAngle_delta if it's too high/low.
//when you are done, your turning animation is over, so:
}
Again, this code might not perfectly fit your needs (or compile :P), so focus on the idea and the approach, not the code itself. Sorry for not being able right now to put something together and test myself - but I hope I helped. Cheers!
EDIT: Instead of a wall of text I tried to answer your question in code (still not perfect, but goal is not doing the job, but to help with some snippets and ideas :)
So. Basically, what you have is a distance and "angle" between two waypoints. This distance and your drone's flight/walk/whatever speed (which I don't know) is the maximum amount of time available for:
1. Turning, so the drone will face in the new direction
2. Banking to the side, and back to zero/"normal"
As there's two times more action on banking side, it either has to be done faster (bankSpeed), or in a smaller angle (turnBankRatio), or both, depending on what looks nice and feels real, what your preference is, etc. So it's 100% subjective. It's also your call if the drone turns+banks quickly and approaches toward the next waypoint, or does things in slow pace and turns just a little if has a lot of time/distance and does things fast only if it has to.
As of isTurning:
You set it to true when the drone reached a waypoint and heads out to the next one AND the variables to (turn and) bank were set properly. When you set it to false? It's up to you, but the goal is to do so when the maneuver is finished (this was buggy in the snippet the first time as this "optimal status" was not possible to ever be reached) so he drone can "reset banking".For further details on what's going on, see code comments.Again, this is just a snippet to support you with a possible solution for your problem. Give it some time and understand what's going on. It really is easy, you just need some time to cope ;)Hope this helps! Enjoy and cheers! :)

Game Maker: create an ennemy at a random position on the ground

I know a bit about coding (in Python, C and XHTML) and I'm trying to understand the basics of Game Maker. I have created a room with enemies moving, colliding to the walls and all, but now, I'd like to randomly spawn enemies in the room as long as they are on a ground. For now, it only works when I spawn them randomly.
Here is the code I put in the Create event of the obj_enemy but obviously something isn't working since it does not spawn any enemy at all.
Also, don't know if it matters, but if I haven't already placed myself an obj_enemy in the room, they do not spawn neither...
// INIT //
dir = -1; // direction
movespeed = 3; // movement speed
hsp = 0; // horizontal speed
vsp = 0; // vertical speed
grav = 0.5; // gravity
// CREATE //
// Find a random X position in the room
var randx = random(room_width);
// Find a random Y position in the room
var randy = random(room_height);
// If the random position is empty
if position_empty (randx, randy)
{
// If there is a block
// 16 pixels under
// the random Y position
// (the sprite of obj_enemy is 32x32 pixels)
if place_meeting (randx, randy+16, obj_block01)
{
// If there is less than 4 ennemies
if instance_number (obj_ennemy) <= 4
{
// Create an ennemy
instance_create(randx, randy, obj_ennemy);
}
}
}
This is the create event of obj_enemy. if there are no obj_enemy's in the room then this code will never get run!
You either need to start with at least one enemy in the room or create a controller object in charge of creating enemies that you put into the room instead (I recommend this approach).
Also even if the code does get run then the chances of spawning an enemy just over a wall in the correct location is quite small so you will have to run the program many times before you see it happen. To avoid this just put the spawn code into a while true loop and break from it once 4 enemies have been spawned:
while (instance_number (obj_ennemy) <= 4)
{
// Find a random X position in the room
var randx = random(room_width);
// Find a random Y position in the room
var randy = random(room_height);
// If the random position is empty
if position_empty (randx, randy)
{
// If there is a block
// 16 pixels under
// the random Y position
// (the sprite of obj_enemy is 32x32 pixels)
if place_meeting (randx, randy+16, obj_block01)
{
// Create an ennemy
instance_create(randx, randy, obj_ennemy);
}
}
}

Add penalty to previous path on point graph to prevent u-turns? A* Pathfinding Project

I am using Aron Granberg's A* Pathfinding Project. So I have a point graph set up in this picture:
I have each car choosing a random node on the point graph and following a path to it.
Right now, the cars traveling on the road have a tendency to make u-turns to get to their next destination. For example, suppose the original path was from point A to point B. Once the car reaches point B, it then randomly chooses to travel to point C.
Is there any way where I can force it to always take the yellow path instead of the purple path to prevent u-turns? I was thinking something along the lines of setting penalties for the previous path, but the penalties would have to only apply to a single agent as I don't want the penalties to apply to the other cars on the road. Is there a feasible way to do this? I have included my pathfinding code below.
using UnityEngine;
using System.Collections;
using System.Linq;
//Note this line, if it is left out, the script won't know that the class 'Path' exists and it will throw compiler errors
//This line should always be present at the top of scripts which use %Pathfinding
using Pathfinding;
public class newPathfind : MonoBehaviour {
//The point to move to
private Vector3 targetPosition;
private bool pathComplete;
public bool ManualList = false;
//public int members = 0;
//private int x = 0;
public GameObject target1;
public GameObject target2;
public GameObject target3;
public GameObject target4;
public GameObject target5;
private GameObject[] Waypoints;
private GameObject[] oneway;
private GameObject[] twoway;
public static int randomPoint;
private Seeker seeker;
private CharacterController controller;
//The calculated path
public Path path;
//The AI's speed per second
public float speed = 500;
//The max distance from the AI to a waypoint for it to continue to the next waypoint
public float nextWaypointDistance = 3;
//The waypoint we are currently moving towards
private int currentWaypoint = 0;
public void Start () {
seeker = GetComponent<Seeker>();
controller = GetComponent<CharacterController>();
Vector3 randomLoc = Vector3.zero;
if (ManualList) {
Waypoints = new GameObject[5];
Waypoints[0] = target1;
Waypoints[1] = target2;
Waypoints[2] = target3;
Waypoints[3] = target4;
Waypoints[4] = target5;
} else {
//Waypoints = GameObject.FindGameObjectsWithTag("network");
twoway = GameObject.FindGameObjectsWithTag ("network");
oneway = GameObject.FindGameObjectsWithTag ("oneway");
Waypoints = oneway.Concat (twoway).ToArray ();
}
do {
randomPoint = Random.Range (0, Waypoints.Length-1);
randomLoc = Waypoints[randomPoint].transform.position;
targetPosition = new Vector3 (randomLoc.x, gameObject.transform.position.y, randomLoc.z);
} while ((Vector3.Distance(gameObject.transform.position, targetPosition) < 50f));
//Start a new path to the targetPosition, return the result to the OnPathComplete function
seeker.StartPath (transform.position,targetPosition, OnPathComplete);
}
public void OnPathComplete (Path p) {
Debug.Log ("Yey, we got a path back. Did it have an error? "+p.error);
if (!p.error) {
path = p;
//Reset the waypoint counter
currentWaypoint = 0;
pathComplete = true;
}
}
public void FixedUpdate () {
if (path == null) {
return;
}
if (currentWaypoint >= path.vectorPath.Count && pathComplete == true) {
Debug.Log ("End Of Path Reached");
Start();
pathComplete = false;
return;
}
//Direction to the next waypoint
Vector3 dir = Vector3.zero;
if (currentWaypoint >= 0 && currentWaypoint < path.vectorPath.Count) {
dir = (path.vectorPath [currentWaypoint] - transform.position).normalized;
}
if (dir != Vector3.zero) {
transform.rotation = Quaternion.LookRotation (dir);
}
dir *= speed * Time.fixedDeltaTime;
controller.SimpleMove (dir);
//Check if we are close enough to the next waypoint
//If we are, proceed to follow the next waypoint
if (Vector3.Distance (transform.position,path.vectorPath[currentWaypoint]) < nextWaypointDistance) {
currentWaypoint++;
return;
}
}
public void OnDisable () {
seeker.pathCallback -= OnPathComplete;
}
}
I couldn't find how to do this using Aron Granberg's A* Pathfinding Project, but essentially you'll need to change the cost of the last visited node to maybe about 3-5 times its normal cost (would probably need experimenting) before calling seeker.StartPath again.
I would assume that the pathfinder has some way of asking you what the cost for each node is. If the node is the same as the last node the unit visited, you would return a multiple of its actual cost. Of course, you will need to know for which unit it is pathing for.
Closest thing I could find (knowing nothing about the library) was GraphUpdates where you are able to apply penalties to various nodes:
GraphUpdateObject guo = new GraphUpdateObject(myBounds);
guo.addPenalty = 10000; //Here goes the penalty to apply, you need quite large values usually
AstarPath.active.UpdateGraphs (guo);
Not sure how you would tie this with a unit other than updating the graph before calling the seeker for a particular unit and making sure nobody else calls the seeker until that's done (very inefficient).
Difficult question... One thing you could do (for a car already traveling) is evaluate a straight line on the path of the car to the next intersection. That would be the next logical time that the driver would make a decision to recalculate their path. Doing it this way would tell the car to continue using the seeker until it found the starting node of the path (in this case it would drastically reduce your u-turn behavior) which happens to be at the next intersection making your hypothetical much less likely and closer mimick driving behavior.
Also on busy roads you may want to consider using the filter called alternative path... If you add this to your existing car prefab, you can create a penalty for too many other units taking the same path. If you set it to ...say 100, unless it was the only realistic path, after 10 cars took the same path likely you would have cars taking alternate routes
Just my two cents
My edited two cents, adding weights to roads, unless it is the entirety of the road (ie dirt vs. pavement) isnt going to be too feasible. Unless I have missed something in the docs (let me know if I did because I want to use it) you are unable to get the weight for a given node/waypoint.You can however choose between different paths with different penalties. (IE you cant do path.vectorPath[currentWaypoint].weight, but you can do path.GetTagPenalty and choose an alternate path)
Angle Penalty in the "Advanced" section may help here.

How to trace the missing pixels when using drawLine

We know that for drawing on an image in qt, qpainter is used. Recently, I used drawLine() function to draw whatever an user is scribbling. This was done by passing the lastPoint and currentPoint from the mouseMoveEvent to a custom function which implements drawLine(). I have passed the arguments for that custom function as given below:
void myPaint::mouseMoveEvent(QMouseEvent *event) {
qDebug() << event->pos();
if ((event->buttons() & Qt::LeftButton) && scribbling) {
pixelList.append(event->pos());
drawLineTo(event->pos());
lastPoint = event->pos();
}
}
Now with the help of qDebug() I noticed that some pixels are missed while drawing but the drawing is precise. I looked up the source of qt-painting where I saw that drawLine() was calling drawLines() which was making use of qpainterPath to have a shape drawn on the image.
My question is that, is there anyway to track these "missed" pixels or any approach to find all the pixels which have been drawn?
Thanks!
void myPaint::drawLineTo(const QPoint &endPoint) {
QPainter painter(image); //image is initialized in the constructor of myPaint
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
painter.drawLine(lastPoint, endPoint);
modified = true;
lastPoint = endPoint; //at the mousePressEvent, the event->pos() will be stored as
// lastPoint
update();
}
For a start, don't draw in a mouseEvent(). Actually handling a mouseevent should be done as quick as possible. Also, it is not a good idea to look at the Qt source, it can be confusing. Rather assume that what Qt gives you work, and first try to answer "What I am doing wrong?". As I said drawing in a mouse event is definitely wrong.
Your description is really subjective, maybe an image of your output is better. Are you trying to emulate a pen (like in windows paint)? In this case do the mouse button has to be down ? is that the purpose of your variable scribbling?
There is more. following the documentation, QMouseEvent::buttons() always return a combination of all buttons for mouse move event. Which make sense : the mouse movements are independent of the buttons. It means
if ((event->buttons() & Qt::LeftButton)
will always be true.
Let's assume you want to draw the path of your mouse when the left button is pressed. Then you use something like :
void myPaint::mousePressEvent(QMouseEvent *event){
scribbling = true;
pixelList.clear();
}
void myPaint::mouseReleaseEvent(QMouseEvent *event){
scribbling = false;
}
void myPaint::mouseMoveEvent(QMouseEvent *event) {
if ( scribbling) {
pixelList.append(event->pos());
}
}
void myPaint::paintEvent(){
QPainter painter(this)
//some painting here
if ( scribbling) {
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
// here draw your path
// for example if your path can be made of lines,
// or you just put the points if they are close from each other
}
//other painting here
}
If after all of this you don't have a good rendering, try using float precision (slower), ie QMouseEvent::posF() instead of QMouseEvent::pos().
EDIT :
"I want to know whether there is any way to calculate all the sub-pixels between any two pixels that we send as arguments to drawLine"
Yes there is. I don't know why you need to do such thing but is really simple. A line can be characterized with the equation
y = ax + b
Both of the endpoints of the line p0 = (x0, y0) and p1 = (x1, y1) satisfy this equation so you can easily find a and b. Now all you need to do is increment from x0 to x1 by the amount of
pixels you want (say 1), and to compute the corresponding y value, each time saving point(x,y).
So will go over all of the points saved in pixelList and repeat this process for any two consecutive points.

Resources