I have a sprite, Player. I update Player's position the following way:
_position += (_direction * _velocity) * (float)gameTime.ElapsedGameTime.TotalSeconds;
Where _position, _direction and _velocity are Vector2s.
I have a simple collider(BoxCollider) which simply generates a rectangle (BoundingBox) given the position and dimensions of the collider.
In Initialize(), I create a new List<BoxCollider> and fill it with colliders for the level.
On Update(), I pass the list to Player to check for collisions.
The collision check method:
public void CheckPlatformCollision(List<BoxCollider> colliders, GameTime gameTime)
{
var nextPosition = _position + (_direction * _velocity) * (float)gameTime.ElapsedGameTime.TotalSeconds;
Rectangle playerCollider = new Rectangle((int)nextPosition.X, (int)nextPosition.Y, BoundingBox.Width, BoundingBox.Height);
foreach(BoxCollider collider in colliders)
{
if(playerCollider.Intersects(collider.BoundingBox))
{
nextPosition = _position;
}
}
Position = nextPosition;
}
Right now, every way I've tried to implement gravity has failed. If Player is dropped from too high, nextPosition becomes too far away from Player and leaves it stuck in mid-air.
I'm also having problems with horizontal collisions as well, the issue being similar: Player stops too soon, leaving a space between. Sometimes I've had Player stick to the side of the collider.
What I would like to know is:
How do I properly implement gravity & jumping with _position, _direction, and _velocity? How do I properly handle collisions both horizontally and vertically?
For gravity, add this just before you update _position:
_velocity += gravity * (float)gameTime.ElapsedGameTime.TotalSeconds;
Where gravity is something like new Vector2(0, 10).
For jumping, you need to set the vertical component of the velocity when the player presses the jump button:
if (jumpPressed && jumpAvailable)
{
_velocity.Y = -10; // Numbers are example. You need to adjust this
jumpAvailable = false;
}
And you need to reset jumpAvailable when the player touches the floor.
Colliding is a much complicated thing. But if you look for "XNA implement collision" on the internet you will find a lot of answers.
There are many ways. One of them is pushing back the player to the border of the boxcollider, instead of not letting him move, like you did in your code. The code would be:
if (IntersectsFromTop(playerCollider, collider.BoundingBox))
{
_position.Y = collider.BoundingBox.Y - BoundingBox.Height;
}
else if (IntersectsFromRight(playerCollider, collider.BoundingBox))
{
_position.X = collider.BoundingBox.X + collider.BoundingBox.Width;
}
// And so on...
The helper methods can implemented like:
private static bool IntersectsFromTop(Rectange player, Rectangle target)
{
var intersection = Rectangle.Intersect(player, target);
return player.Intersects(target) && intersection.Y == target.Y && intersection.Width >= intersection.Height;
}
private static bool IntersectsFromRight(Rectange player, Rectangle target)
{
var intersection = Rectangle.Intersect(player, target);
return player.Intersects(target) && intersection.X + intersection.Width == target.X + target.Width && intersection.Width <= intersection.Height;
}
// And so on...
The rationnale behind that code can be explained with a picture:
In that picture width and height correspond to the intersection, in purple.
Related
I'm trying to make a simple snake game on unity that can be played on a mobile device. I got to the point where the snakes head is able to move continuously in the direction that the user swiped in, but the movement of the snake's head is kind of choppy and I'd like it to be more fluid.
I have this in the public class:
Vector2 dir = Vector2.right;
My start function looks like this:
void Start() {
dragDistance = Screen.height * 5 / 100; //dragDistance is 5% height of the screen
InvokeRepeating("Move", 0.3f, 0.075f);
}
Inside of my Update function I have this:
if (Mathf.Abs(lp.x - fp.x) > dragDistance || Mathf.Abs(lp.y - fp.y) > dragDistance)
{//It's a drag
//check if the drag is vertical or horizontal
if (Mathf.Abs(lp.x - fp.x) > Mathf.Abs(lp.y - fp.y))
{ //If the horizontal movement is greater than the vertical movement...
if ((lp.x > fp.x)) //If the movement was to the right)
{ //Right swipe
dir = Vector2.right;
}
else
{ //Left swipe
dir = -Vector2.right;
}
}
else
{ //the vertical movement is greater than the horizontal movement
if (lp.y > fp.y) //If the movement was up
{ //Up swipe
dir = Vector2.up;
}
else
{ //Down swipe
dir = -Vector2.up;
}
}
}
And after the Update function I have:
void Move()
{
// Move head into new direction
transform.Translate(dir);
}
Thanks for your help.
This is simply due to how you are currently moving.
Using InvokeRepeating to move your character will be super choppy as you are just moving "forward" by a certain amount every time InvokeRepeating repeats.
You really shouldn't be using that to move your character. Typically you want to you some form of Rigidbody and physics to move. Perhaps try something like this :
public Rigidbody2D rb;
void FixedUpdate() {
rb.MovePosition((rb.position + dir) * Time.fixedDeltaTime);
}
That could be a very simple answer. Check out some more details on that method here.
Also just try looking up some tutorials on movement in Unity.
The problem is you aren't moving every frame. You could fix this by moving in the update() like so:
transform.Translate(dir * speed * Time.deltaTime);
Instead of doing InvokeRepeating and Move().
But what if you want to make the snake stop at each square like it does now, but move smoothly from square to square? You would need to save the snake's next position in your move() function and then smoothly move the snake there in Update(). It would look something like this:
void Move()
{
// Move head to next position
prevSquare = transform.position; // keep track of the previous square
// so we can use lerp() later on
nextSquare = transform.position + dir;
progress = 0; // progress is the percent distance between the two squares
isMoving = true; //we probably need isMoving so we can decide what to do if they
//swipe while snake is moving, I'll leave it to you to figure
//out
}
update()
{
...
if(isMoving)
{
progress = progress + time.deltaTime;
transform.position = Vector2.Lerp(prevSquare, nextSquare, progress);
if(progress > 1.0f)
{
isMoving = false;
}
}
}
I have this red apple that needs to move only inside the green apple line. If it tries to move beyond line it will be stopped at the green line and can' move beyond. How can I do that?
This is the code I use to move a red apple.
Camera mainCamera;
float zAxis = 0;
Vector3 clickOffset = Vector3.zero;
// Use this for initialization
void Start()
{
mainCamera = Camera.main;
mainCamera.gameObject.AddComponent<Physics2DRaycaster>();
zAxis = transform.position.z;
}
public void OnBeginDrag(PointerEventData eventData)
{
clickOffset = transform.position - mainCamera.ScreenToWorldPoint(new Vector3(eventData.position.x, eventData.position.y, zAxis));
}
public void OnDrag(PointerEventData eventData)
{
//Use Offset To Prevent Sprite from Jumping to where the finger is
Vector3 tempVec = mainCamera.ScreenToWorldPoint(eventData.position) + clickOffset;
tempVec.z = zAxis; //Make sure that the z zxis never change
/*
Debug.Log(tempVec.x);
if (tempVec.x < 90)
{
tempVec.x = 90;
}
if (tempVec.x > 310)
{
tempVec.x = 310;
}
*/
transform.position = tempVec;
}
public void OnEndDrag(PointerEventData eventData)
{
}
Don't use code for this use colliders on both sides.
I recommend to use the polygon collider with this (although it's a bit buggy). Then when you're dragging the apple you don't set the position but you set the velocity based on the difference between the mouse and the apple.
Unfortunately, high-velocity objects are not always resolved properly in Unity. In the case that unity doesn't always resolve the collision properly I recommend raycasting the difference from the apple to the cursor, if you get a hit instead of using the cursor position you can now use the hit position.
NOTE: Make sure it's not normalized because you want the velocity to be more if the cursor is further away from the fruit.
You could add a mesh renderer onto the big green apple. Then in your script you can check for collision and make the small apple react to the collision the way you'd like.
Reference the picture for easier understanding of the case.
.
I'm making a 3D game and want to be able to move the board up and rotate it on the Z axis using a single finger. If the finger is slid up - the board goes up. If the finger is slid left or right, the board tilts left or right.
The whole point is so that the board brings the ball that sits on top of it upwards, and when the board is tilted/rotated to the side, the ball starts sliding and falling off to the side.
The board and the ball have rigidbodies attached.
What I've tried so far:
Using a slider that, when the value is changed the board rotates with
Quaternion.Euler.
Using the IDragHandler interface and changing the board position to
the EventData received when a touch is registered.
Tried it with buttons that modify the board position vertically by
some distance - it happens instantaneously and the ball falls through
the board.
Created a UI image/joystick and used touch/event data (from
IDragHandler) to move the board transform whenever the UI joystick is
moved.
I've tried some more variants, unfortunately I can't recall them right now and none of what I have already tried works.
I figured it out. I added a image(UI) game object to an empty game object so it can move from 0 on the X and 0 on the Y axis. Added 3 transforms - 1 tilted to the left, 1 tilted to the right and 1 in the middle of the top of the screen to serve as guides in the game and then disabled them so they don't show up in the actual game. Attached a script to my Image UI gameobject and assigned the necessary transforms in the inspector and voila, it works.
using UnityEngine;
using UnityEngine.EventSystems;
public class MoveStick : MonoBehaviour, IDragHandler, IEndDragHandler {
public Transform leftTarget;
public Transform rightTarget;
public float rotateSpeed;
[Space]
Vector2 joyStartPosition;
public Transform stick;
public Transform topTarget;
public float speed;
bool goingUp;
void Start()
{
joyStartPosition = transform.position;
goingUp = false;
}
public void OnDrag(PointerEventData eventData)
{
transform.position = eventData.position;
if (transform.localPosition.y > 80f || transform.localPosition.x > 50 || transform.localPosition.x < -50)
{
goingUp = true;
var step = speed * Time.deltaTime;
stick.position = Vector3.MoveTowards(stick.position, topTarget.position, step);
Debug.Log("Going up: " + goingUp);
}
else if (transform.localPosition.y < 80f)
{
goingUp = false;
Debug.Log("Going up: " + goingUp);
}
if (transform.localPosition.x > 80 && transform.localPosition.y > 80)
{
stick.rotation = Quaternion.RotateTowards(stick.rotation, rightTarget.rotation, rotateSpeed * Time.deltaTime);
}
else if (transform.localPosition.x < -80 && transform.localPosition.y > 80)
{
stick.rotation = Quaternion.RotateTowards(stick.rotation, leftTarget.rotation, rotateSpeed * Time.deltaTime);
}
}
public void OnEndDrag(PointerEventData eventData)
{
transform.position = joyStartPosition;
eventData.position = joyStartPosition;
Debug.Log("Joystick is not moving");
goingUp = false;
}
}
There is still a lot to smooth and improve, however as a base code it works fine.
I have a ball which rotates around the point 0,0,0 in the Z-axis. When the space button is pressed, the ball has to go inside the large circle. Now my code looks like this. When you press space, the ball does not behave as they should. I want to know how to make a balloon down exactly down
that's how the ball should behave ->
behavior image
my code:
void Update () {
if (Input.GetKeyDown (KeyCode.Space)) {
transform.position = new Vector3 (transform.position.x - 1, transform.position.y - 1, 0);
} else {
transform.RotateAround(new Vector3(0,0,0), new Vector3(0,0,1), 2);
}
}
Your code to 'jump' the orbit doesn't do what you want because Transform.RotateAround modifies both the rotation and the position of the object's transform.
So jumping to (position - 1,1,0) in the world is going to return wildly different results every time.
What you want to do instead is calculate the (Vector) direction from the object to the centre of orbit (the difference), then scale that down to how far you want it to move, then apply it to the position.
private Vector3 _orbitPos = Vector3.zero;
private float _orbitAngle = 2f;
private float _distanceToJump = 2f;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var difference = (_orbitPos - transform.position).normalized * _distanceToJump;
transform.Translate(difference);
}
transform.RotateAround(_orbitPos, Vector3.forward, _orbitAngle);
}
This will move the object to be orbiting 2 units closer when space is pressed immediately.
If you wanted to have a smooth transition instead of a jump, look into using Mathf.Lerp, Vector3.Lerp and the routines involved.
I have a class below that I attach to a object in order to make it rotate around its pivot. I sent the pivot of the sprite via the inspector.
This works exactly how I want it too, BUT the issue I am having is that whenever I touch and drag it, and then touch and drag it again, it snaps to a new position.
What I would like for it to do is, when it is rotated and then rotated again, the sprite stays in its same rotation and not snap to a new position and I would like the angle of the sprite to be reset to 0. The next then is that I want the angle to continually rotate. So if I rotate it in the positive direction, the angle should keep increasing in the positive direction and not change..Such as 0---> 360 ----> 720 -----> etc, etc. And then when the mouse is released, the sprite stays in the same position but the angle is now set back to 0. And then when clicked again to rotate, it rotates from that exact position.
Here is my code thus far which works well for rotating, but I would like to modify it to achieve the above scenario. Any help with this?
public class Steering : MonoBehaviour {
float prevAngle,wheelAngle,wheelNewAngle = 0;
public SpriteRenderer sprite;
void Start () {
}
void Update () {
}
public float GetAngle(){
return wheelAngle;
}
void OnMouseDrag(){
Vector3 mouse_pos = Input.mousePosition;
Vector3 player_pos = Camera.main.WorldToScreenPoint(this.transform.position);
mouse_pos.x = mouse_pos.x - player_pos.x;
mouse_pos.y = mouse_pos.y - player_pos.y;
wheelNewAngle = Mathf.Atan2 (mouse_pos.y, mouse_pos.x) * Mathf.Rad2Deg;
if (Input.mousePosition.x > sprite.bounds.center.x) {
wheelAngle += wheelNewAngle - prevAngle;
} else {
wheelAngle -= wheelNewAngle - prevAngle;
}
this.transform.rotation = Quaternion.Euler (new Vector3(0, 0, wheelAngle));
Debug.Log (wheelAngle);
prevAngle = wheelNewAngle;
}
void OnMouseUp(){
prevAngle = wheelNewAngle;
wheelAngle = 0;
}
}
By angle of the sprite, do you mean the rotation? I'm not sure how the position is changing if there's nothing in your code doing that. Does it always move to the same position? I'm having a little trouble visualizing how your system is supposed to look but I hope this helps.
It looks like you might want to store the previous mouse position so you can get the relative amount to rotate each frame.
At the top:
Vector3 prevMousePos = Vector3.zero;
This method will help get the position when the player pressed:
void OnMouseDown(){
prevMousePos = Input.mousePosition;
}
Then in OnMouseDrag() get the difference between the two mouse positions to get the relative position (if you moved the mouse left, right, up, or down since pressing):
Vector3 mouseDiff = Input.mousePosition - prevMousePos;
With this it will use the relative mouse position after pressing instead of the current one, which should smooth things out.