Prevent collisions in an animated game - c#

I have been trying to program a way of preventing my character from touching a wall so he couldn't go trough it but I can't find a proper way of doing like you can see in this video (that I recorded). Sorry about the (decent) mic quality : https://youtu.be/jhTSDgSXXa8. Also I said prevent the collision but rather it detects it and stops but you can go through.
The collision code is :
foreach (PictureBox pbMur in pbListeMurs)
{
if (pbCharacterCat.Bounds.IntersectsWith(pbMur.Bounds))
{
if (pbCharacterCat.Right > pbMur.Left)
{
bWalkRight = false;
bIdle = true;
}
}
}
Thank you ! :D

I'm not sure how you are using bIdle and walkRight, but these types of boolean flags are easy to get wrong and it turns your whole code into a complete mess as you typically try to plug holes and end up springing new ones in the process.
First of all, why do you even need them? Wouldn't this be enough?
var newPotentialCharacterBounds =
GetNewBounds(pbCharacterCat.Bounds, movementDirection);
var collidedWalls = pbListeMurs.Where(wall =>
wall.Bounds.IntersectsWith(newPotentialCharacterBounds));
if (!collidedWall.Any())
{
pbCharacterCat.Bounds = newPotentialCharacterBounds
}
//else do nothing
How does this work? Well, the premise is that your character can't start in an invalid position, and if it is never allowed to reach an invalid position then you never need to undo movements or reset positions.
I'd propose you create an enumeration that describes all possible directions:
enum Direction { Up, Down, Left, Right };
When the corresponding direction command is given, get the potential new position of the character (newPotentialCharacterBounds and GetNewBounds). If that position collides with anything, simply do nothing, if it doesn't, move!
UPDATE: Pseudocode follows:
//event handler for move right fires, and calls:
TryMove(pbCharacterCat, Direction.Right)
//event handler for move left fires and calls:
TryMove(pbCharacterCat, Direction.Left)
//etc.
private static Rectangle GetNewBounds(
Rectangle current, Direction direction)
{
switch (direction)
{
case Direction.Right:
{
var newBounds = current;
newBounds.Offset(horizontalDelta, 0);
return newBounds;
}
case Direction.Left:
{
var newBounds = current;
newBounds.Offset(-horizontalDelta, 0);
return newBounds;
}
//etc.
}
//uses System.Linq
private bool TryMove(Control ctrl, Direction direction)
{
var newBounds =
GetNewBounds(ctrl.Bounds, direction);
var collidedWalls = pbListeMurs.Where(wall =>
wall.Bounds.IntersectsWith(newBounds));
if (!collidedWall.Any())
{
ctrl.Bounds = newBounds;
return true;
}
//Can't move in that direction
Debug.Assert(collidedWall.Single); //sanity check
return false;
}
Becuase TryMove returns if the movement was successful or not, now you can leverage that information; different sound effects for instance, etc.

Related

OnGUI and Coroutine moving an object: really strange behavior

In the title it is not easy to summarize this behavior in the best way.
I have an Editor and a Monobehaviour executed in [ExecuteInEditMode].
I need to move objects by selecting them with the mouse. I thought I didn't encounter any difficulties as I perform this operation with both a character and sprite. But I was wrong. When I select a 3D object (not the character) I can pick and drag it but if I hold the mouse button down without moving it it tends to slip away as if it were falling but on the Y and Z axes. If I deselect its collider the issue does not happen but obviously the collider is necessary for my operations.
I have removed all the unnecessary lines from the code, hoping to solve the problem, but it remains.
void OnGUI() {
if (moveChar) {
StartCoroutine("WaitChar");
} else if (moveSpot) {
StartCoroutine("WaitSpot");
}
Event e = Event.current;
Vector3 mousePosition = e.mousePosition;
mousePosition.y = Screen.height - mousePosition.y;
Ray ray = cam.ScreenPointToRay(mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
mousePositionWorld = hitInfo.point;
if (e.rawType == EventType.MouseDown) {
switch (e.button) {
case 0:
if (isSelectedSphere) {
moveSpot = true;
}
break;
case 1:
if (isSelectedChar) {
moveChar = true;
}
break;
}
} else if (e.rawType == EventType.MouseUp) {
switch (e.button) {
case 0:
if (moveSpot) {
moveSpot = false;
}
break;
case 1:
if (moveChar) {
moveChar = false;
}
break;
}
}
}
}
public IEnumerator WaitChar() {
character.transform.position = mousePositionWorld;
yield return new WaitForSeconds(0.1f);
}
public IEnumerator WaitSpot() {
MoveSpot.transform.position = mousePositionWorld;
MoveSpot.transform.localScale = Vector3.one * ((new Plane(cam.transform.forward,
cam.transform.position).GetDistanceToPoint(
MoveSpot.transform.position)) / radiusPoint);
yield return new WaitForSeconds(0.1f);
}
I have simplified everything with two public variables to choose the object to move.
What I do is very simple, I don't understand why I get this behavior. With the character I have no problem (not even with the sprites, which for the moment I removed from the code). When I click with the right mouse button I run the coroutine that moves the character to the cursor coordinates and continues like this until I release the button. No problem, the character moves along with the cursor without problem.
However, if I execute the same operation (left button) with a 3D object like Sphere, Cylinder, Cube it moves following the mouse but if I don't move the cursor and don't release the button the object slides away with the Y and the Z axes that increase in negative. As I said, if I deactivate the collider the object remains at the cursor position.
The problem is that the spheres cannot be selected through public variables but directly from the script so I need the collider.
I probably don't know Unity well enough to know the reason for this behavior.
Can you clarify this for me?
OnGui is bad (let me explain)
First, OnGui is part of Unity's IMGui system (stands for IMmediate Gui), and has been heavily depreciated in favor of uGui, which is a lot easier to work with for a number of reason. And one of those reasons is....
OnGui is called more than once per frame
Because OnGui is called multiple times per frame, that means that Event.current hasn't changed, meaning moveChar hasn't changed, meaning your coroutine gets started more than once.
And there's no real way around this. Its going to be almost impossible to do what you want using OnGui. It was never meant to handle this sort of logic.
Everything you have should be done inside Update() instead and replacing things like e.rawType == EventType.MouseDown with their Input relatives, eg. Input.GetMouseDown(0).

Get Movement Direction of Mouse

I know this has been Asked but in my Case i dont know if i Can use the MouseEventArgs since its a Rendering / Input Loop where Im trying to use it Here is the Whole Rendering / Input Loop, I can only Move with the Keyboard which is Rather inconvinient.
public void update()
{
//--do raycast--//
raycast();
//=========================//
//=====take user input=====//
//=========================//
KeyboardState state = Keyboard.GetState();
bool lArrowKeyDown = state.IsKeyDown(Keys.Left) || state.IsKeyDown(Keys.A);
//MouseLeft
if (lArrowKeyDown)
{
rotate(rotSpeed);
}
//MouseRight
bool rArrowKeyDown = state.IsKeyDown(Keys.Right) || state.IsKeyDown(Keys.D);
if (rArrowKeyDown)
{
rotate(-rotSpeed);
}
//MouseUp
bool uArrowKeyDown = state.IsKeyDown(Keys.Up) || state.IsKeyDown(Keys.W);
bool shift = state.IsKeyDown(Keys.LeftShift);
if (uArrowKeyDown)
{
move(moveSpeed);
}
if (uArrowKeyDown && shift)
{
move(moveSpeed+0.01);
}
bool dArrowKeyDown = state.IsKeyDown(Keys.Down) || state.IsKeyDown(Keys.S);
//MouseDown
if (dArrowKeyDown)
{
move(-moveSpeed);
}
//=========================//
//=====user input end======//
//=========================//
}
This is the Whole Rendering / Input Loop, Would it be Possible to add a MouseEventArgs? Im unsure of if a MouseEventArgs Loops too when this Loops. Could someone help me Here?
I’m not entirely sure what you are asking for, but if you want to get movement direction from a mouse event, unless the mouse event has a last position and new position property, you need to keep track of the last position (x and y value) and use it with the current position to calculate the angle.
For calculating the angle, see
Calculating the angle between the line defined by two points
I can provide a better answer with more information.

Unity2D: How to implement proper ladder climbing with a raycast controller?

I've been slowing grinding out programming features for my character controller, and one of the last things to implement is ladder climbing. The code I've come up with over the past 2 days mostly works, but presents some issues:
Sometimes the character bounces after climbing while holding the up arrow, at the point with which a 1-way platform meets the end of the ladder.
Once the character is all the way up, resting on the 1-way platform, it cannot go back down. This can be remedied with a longer ladder collider, but that makes issue #1 worse.
If the character is on the ground near a ladder and jumps, but presses the up arrow at the very last second, it gets flung considerably far in a certain direction.
If the character is on a rope and jumps off the rope, but holds the up arrow before doing so, it goes slightly higher, but is noticeable. Not holding the up arrow, but rather just pressing the left or right arrow and jumping, yields a regular jump. Perhaps this has something to do with the x or y input axes?
The way my classes work is that I have a Controller2D class which handles most of the raycasting stuff and movement methods, a Player class for input that calls the movement methods, and a parameters class that sets default world parameters, like gravity and jump height, but can be overridden if the character is in a certain volume, such as water or a ladder collider (this allows me to set gravity to 0 while climbing so the character can move properly). Almost all of my ladder code is contained in the OnTriggerEnter/Stay/Exit functions, and I'm just not sure if this is the best way to do it.
I tried making a ClimbUp/ClimbDown method, and shooting a raycast from the center of the player, which would allow him or her to climb up, but this was not overly successful (there were too many issues compared to using OnTriggerStay).
I'm wondering if someone who has successfully implemented ladder climbing in a raycast controller, or anybody who might have advice, would be willing to point me in the right direction for this task.
Here's some of the script:
public void ClimbLadder(float y)
{
ClimbingOnLadder = true;
SetVerticalForce(y);
}
public void JumpWhileClimbing(float x)
{
if (ClimbingOnLadder)
{
ClimbingOnLadder = false;
overrideParameters = null;
AddForce(new Vector3(x, Parameters.jumpVelocity * .70f, 0));
JumpAfterClimbing = true;
print(Parameters.jumpVelocity);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Ladder")
{
CanClimb = true;
}
}
private void OnTriggerStay2D(Collider2D other)
{
if (other.tag == "Ladder")
{
if (ClimbingOnLadder)
{
var newParameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if (newParameters == null)
{
return;
}
overrideParameters = newParameters.NewParameters;
Debug.Log("overriding");
if (ClimbingOnLadder && State.IsCollidingBelow)
{
ClimbingOnLadder = false;
overrideParameters = null;
}
}
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Ladder")
{
CanClimb = false;
ClimbingOnLadder = false;
JumpAfterClimbing = false;
}
var newParameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if (newParameters == null)
{
return;
}
overrideParameters = null;
}

Moving an enemy back and forth in Monogame / XNA

I am trying to achieve a very basic 2D game right now where an enemy is on the screen and it bounces back and forth between set points (50 and 500) kind of like a space invaders sort of thing. My issue is I can only get it to go right, but then not come back towards the left and repeat.
I was messing around with coding it myself before bothering to look into it and actually figure it out but I thought I had something that would work, but well it doesn't, my issue is I don' get why.
My code is supposed to work like a switch, two if statements within the Update loop, one comes on the other goes off, one moves it right the other moves it left, I thought that was fine but it doesn't do that. It moves it right just fine but then the left part just doesn't work.
So, why does the following code not work?
namespace _2D_game_num1
{
class Enemy
{
int health;
Vector2 enemy_location = new Vector2(50, 50);
Vector2 enemy_speed = new Vector2(1, 1);
Player player = new Player("dummy");
public Enemy()
{
health = 100;
}
public void UpdateLocation()
{
//Vector2 player_pos = player.GetLocation();
//if (player_pos.X < 200)
// Using the players location to figure out where the enemy should move
bool right = true;
bool left = false;
if (right)
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X == 500)
{
right = false;
left = true;
}
}
if (left)
{
enemy_location.X -= enemy_speed.X;
if (enemy_location.X == 50)
{
right = true;
left = false;
}
}
}
public Vector2 GetLocation()
{
return enemy_location;
}
}
}
And then in the main Game class I have it so enemy1.UpdateLocation(); is within the Update section correctly (along with my players movement which works fine).
Try this:
public void UpdateLocation()
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X <= 50 || enemy_location.X >= 500)
{
enemy_speed.X = new Vector2(-enemy_speed.X, enemy_speed.Y);
}
}
What we are doing here is moving the enemy based on it's current speed. Then if the location is on the left or right of the screen, change direction.
Sometimes it pays to keep things simple. Get rid of the left and right flags, as they are just confusing things. When you're dealing with Update methods you're typically changing the state of something. The way you had it before, the left and right state was getting reset every time UpdateLocation is called.
Btw, you might want to consider passing in GameTime to your Update methods. Typically, when you're moving things around in real-time you'll want to multiply movement by some kind of deltaTime to keep things smooth on all devices. You're probably getting away with it because by default it'll be a fixed frame rate, but that may not always be the case, so it's a good habit to get into.

Freeing stuck objects after collision

I have run into a slight issue with the collision resolution in my game engine. If two objects collide and that collision causes the velocity to go to zero, the edges of the objects will overlap each other and they'll be stuck.
Is there a way to implement a catchall for this kind of a situation? i.e. move the objects just enough in the right direction so they are not stuck.
Here is how I am checking collisions and moving objects. When update is called on an entity, it moves the (x,y).
public static void Update()
{
for (var iterator = 0; iterator < PhysicsEntities.Count; iterator++)
{
for (var index = iterator + 1; index < PhysicsEntities.Count; index++)
{
if (!Collision.ResolveCollision(PhysicsEntities[iterator],
PhysicsEntities[index], Detection)) continue;
PhysicsEntities[iterator].Update();
PhysicsEntities[iterator].Collided = true;
PhysicsEntities[index].Update();
PhysicsEntities[index].Collided = true;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}
}
Here is the update function for the entities:
public void Update(bool velocity = false)
{
if(!Movable) return;
if (!Collided)
{
var moveX = Velocity.X / Universe.UpdateInterval;
var moveY = Velocity.Y / Universe.UpdateInterval;
Position.Move(moveX, moveY);
BoundingBox.Move(moveX, moveY);
}
if(velocity) UniversalForces();
}
private void UniversalForces()
{
Velocity.Scale(1 - Universe.Friction);
Velocity.Add(Universe.GravityMag, Universe.GravityDir);
}
Finally, here is a image of one simulation where the objects get stuck. As you can see, it is just the edges that are getting stuck:
The quick solution is to move both objects back to the previous tic's position and any other object that causes a collision with to move back as well. It works, but it looks messy and causes some behavior that looks really bad - things like pushing directly at a wall leaves a gap, but angling towards a wall leaves a smaller gap. Very messy.
The better solution is to move both objects back just far enough along their negative velocity vector so that they are no longer touching. Usually some dot product math can give you what you need for this, though iterating backwards can work (slow).
Long story short, don't ever allow objects to overlap. Take care of it before it happens and you avoid stuck jitters, can't move stuff, etc.
When one object collides with another, get it to backtrack up its own movement vector so that the distance between the centroids of both objects is equal to the two radius. This works if its a circle - if its a complex polygon you really need to do edge collision detection instead of bounding sphere detection. If the bounding spheres collide, then you move to complex edge detection. In the end the trick is the same; check for collision then back up the movement vector until you find the exact (or nearly exact) point of collision.
I was able to figure it out with the suggestions people made. Some of the changes I made include having each object collide with any other object only once per update, and moving the object after the collision until it is no longer colliding. Here is the code that I used to do that, feel free to use this on any project and let me know if you have any questions about it.
public static void Update()
{
foreach (var a in PhysicsEntities)
{
foreach (var b in PhysicsEntities)
{
if (a.Equals(b) ||
!Collision.ResolveCollision(a, b, Detection) || b.Collided) continue;
while (Detection == Detection.BoundingBox ?
Collision.BoundingBox(a, b) :
Collision.PixelPerfect(a, b))
{
const float moveBy = .5F;
var moveX = a.Position.X > b.Position.X ? moveBy : -moveBy;
var moveY = a.Position.Y > b.Position.Y ? moveBy : -moveBy;
if (a.Movable)
{
a.Move(moveX, moveY);
a.Velocity.Scale(-1);
}
else if (b.Movable)
{
b.Move(moveX * -1, moveY * -1);
b.Velocity.Scale(-1);
}
}
a.Update();
b.Update();
a.Collided = a.Movable;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}

Categories