I want to create a simple flight physics sim, arcade-y like game, in 2D.
I don't want something too realistic, just something fun.
Unfortunately, my code leads to a game, where my plane acts more like a missile in space, than an aerodynamic plane.
I've checked for questions around the internet, the closest one I found was this.
Which the guy wrote about this game.
The game looks a bit like what I want to do.
I couldn't find any practical code I can use to make my plane more realistic, so I'm looking for help.
Please, do not send me to learn aerodynamics or to something not useful, I do have a simple understanding, and I would like some practical help, not theoretical one.
The main issue I have, is when I want to pitch up/down the plane, the plane just rotates itself with the Torque force, however, it doesn't change it's direction quickly with the air flow (since there's no 'real' air flow...)
I've tried playing with the rigidbody drag, and angular drag, and the parameters, I've added a drag force of my own... I am missing something and I don't know what. I was also looking at these formulas, I didn't find the solution.
Thanks in advance,
Here is my code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlaneController : MonoBehaviour {
public float thrustFactor = 0.01f;
public float maxThrustFactor = 1.0f;
public float liftFactor = 0.1f;
public float turnFactor = 0.1f;
public float gravityFactor = 0.1f;
public float dragFactor = 0.1f;
public float thrust;
public float liftForce;
private Vector2 planeVelocity;
private Rigidbody2D transformRigidbody;
// Use this for initialization
void Start () {
thrust = 0;
liftForce = 0;
transformRigidbody = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate () {
planeVelocity = transformRigidbody.velocity;
PlaneGravity();
PlaneThrust();
PlaneDrag();
PlaneTurn();
PlaneLift();
}
void PlaneLift()
{
float attackAngle = transform.eulerAngles.z;
if ((attackAngle > 0 && attackAngle <= 45) || (attackAngle > 90 && attackAngle <= 135))
liftForce = Mathf.Sin(attackAngle) * liftFactor * planeVelocity.sqrMagnitude;
else if ((attackAngle > 45 && attackAngle <= 90) || (attackAngle > 135 && attackAngle <= 180))
liftForce = Mathf.Cos(attackAngle) * liftFactor * planeVelocity.sqrMagnitude;
transformRigidbody.AddForce(Vector2.up * Mathf.Abs(liftForce) * Time.fixedDeltaTime);
}
void PlaneThrust()
{
if (Input.GetKey("w"))
{
thrust += thrustFactor;
thrust = Mathf.Min(thrust, maxThrustFactor);
}
if (Input.GetKey("s"))
{
thrust -= thrustFactor;
thrust = Mathf.Max(0, thrust);
}
transformRigidbody.AddForce(transform.right * thrust * Time.fixedDeltaTime);
}
void PlaneGravity()
{
transformRigidbody.AddForce(gravityFactor * Vector2.down * Time.fixedDeltaTime);
}
void PlaneDrag()
{
transformRigidbody.AddForce(-transform.right * planeVelocity.sqrMagnitude * Time.fixedDeltaTime);
}
void PlaneTurn()
{
if (Input.GetKey("up"))
{
transformRigidbody.AddTorque(-turnFactor * Mathf.Abs(planeVelocity.sqrMagnitude) * Time.fixedDeltaTime);
}
if (Input.GetKey("down"))
{
transformRigidbody.AddTorque(turnFactor * Mathf.Abs(planeVelocity.sqrMagnitude) * Time.fixedDeltaTime);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
Vector3 newPosition = transform.position;
if (other.name == "LeftCollider")
{
newPosition.x += 17;
}
if (other.name == "RightCollider")
{
newPosition.x -= 17;
}
transform.SetPositionAndRotation(newPosition, transform.rotation);
}
private void OnCollisionExit2D(Collision2D collision)
{
if(collision.transform.name == "Ground")
{
transformRigidbody.freezeRotation = false;
}
}
}
Related
I want to press the key once
My cube moves to the right and rotates 90 degrees
The rotation is well done
But it does not move well
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour
{
public float speed;
public float time;
public GameObject contactPoint;
private Rigidbody rig;
private void Start()
{
rig = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
}
if (Input.GetKeyDown(KeyCode.A))
{
StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
}
}
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
Quaternion fromAngle = contactPoint.transform.rotation;
Quaternion toAngle = Quaternion.Euler(transform.eulerAngles - byAngle);
for (float t = 0; t < 1; t += Time.deltaTime / inTime)
{
rig.MovePosition(transform.position + (dir * speed * Time.deltaTime));
rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));
yield return null;
}
}
}
Your main issue is: You should do Physics related things like movement of a Rigidbody only in FixedUpdate.
For Coroutines Unity provides WaitForFixedUpdate which does exactly what the name says, making sure the code after it is executed in a FixedUpdate physics call.
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
yield return new WaitForFixedUpdate();
// here I would also use "rig" instead of "transform"
Quaternion fromAngle = contactPoint.transform.rotation;
Quaternion toAngle = Quaternion.Euler(rig.eulerAngles - byAngle);
for (float t = 0; t < 1; t += Time.deltaTime / inTime)
{
yield return new WaitForFixedUpdate();
// here I would also use "rig" instead of "transform"
rig.MovePosition(rig.position + (dir * speed * Time.deltaTime));
rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));
}
}
beyond that it is a bit unclear what exactly you define as not move well. You also should somehow make sure that only one routine is running at a time. Either by terminating already running routines like
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
StopAllCoroutines();
StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
}
if (Input.GetKeyDown(KeyCode.A))
{
StopAllCoroutines();
StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
}
}
or preventing new routines from starting until the current one is finished using a flag like
private bool alreadyRotating;
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
if(alreadyRotating) yield break;
alreadyRotating = true;
......
alreadyRotating = false;
}
I was trying to achieve this kind of player ball movement:
Catch Up (Ketchapp)
From my side I have tried and record a video of my current implementation:
CatchUpBallMovementDemo
Two kinds of problem, I was facing:
ball making so much jerk while moving on the plain track that I hope you have clearly noticed in my recorded video
when ball reach left or right edge and you try to swipe its making jerk again rather than remain restricted because clamping related code already added
I have just created a demo project so here I am providing the link for it so personally you can check and provide me a suggestion for making ball movement perfect.
Demo Project Source Link: CatchUpBallDemo
Demo Project SIZE 20MB
What at present making jerk in ball movement that I can't able to decide, whether its following camera jerk, whether ball not moving properly though I have created a plain track for checking purpose.
Ball Inspector Detail:
Complete code added within the working demo project. Share your suggestions with me to solve this.
Code Scripts:
BallController
[RequireComponent (typeof(Rigidbody))]
public class BallController : MonoBehaviour
{
//
private Rigidbody myRigidBody;
private bool isJumper;
private bool allowSpeedIncrease;
private BallInputHandler ballInputHandler;
private float speed;
private float speedMilestone;
private float jumpCounter;
private float scoreElapsedTime;
[SerializeField]
private bool isGrounded;
//
public float ballHorzRange;
public float ballStartSpeed;
public float ballTopSpeed;
public float smoothnessValue;
public float smoothnessX;
private void Awake ()
{
DoOnAwake ();
}
private void DoOnAwake ()
{
ballInputHandler = GetComponent<BallInputHandler> ();
myRigidBody = GetComponent<Rigidbody> ();
speed = ballStartSpeed;
speedMilestone = ballStartSpeed;
}
public void Start ()
{
DoOnStart ();
}
private void DoOnStart ()
{
// assinging player transform to camera to follow
Camera.main.GetComponent<CameraFollow> ().FollowPlayer (transform);
}
void Update ()
{
// slowly increase ball moving speed
if (allowSpeedIncrease) {
speed += Time.deltaTime;
if (speed >= speedMilestone) {
allowSpeedIncrease = false;
speed = speedMilestone;
}
}
}
void FixedUpdate ()
{
// do jumping
if (isJumper) {
jumpCounter++;
if (jumpCounter >= 3) {
isJumper = false;
jumpCounter = 0;
}
myRigidBody.AddForce (Vector3.up * 700f);
}
// applying continuous forward velocity
Vector3 nextVelocity = myRigidBody.velocity;
nextVelocity.x = ballInputHandler.horizontalInput * smoothnessX;
nextVelocity.z = speed;
if (isGrounded) {
nextVelocity.y = 0;
} else if (!isJumper) {
nextVelocity.y -= speed * 0.1f;
}
myRigidBody.velocity = nextVelocity.normalized * speed;
ClampingBallMovement ();
}
// ball horizontal movement limitation
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
}
void OnTriggerEnter (Collider other)
{
if (other.CompareTag (GameConstants.TAG_TRACK_SPAWNER)) {
GameController.Instance.SpawnPlateform ();
} else if (other.CompareTag (GameConstants.TAG_TRACK_DESTROYER)) {
Destroy (other.transform.parent.gameObject);
}
}
}
BallMeshRolling
public class BallMeshRolling : MonoBehaviour
{
private Vector3 ballLastPosition;
void Start ()
{
ballLastPosition = transform.parent.position;
}
void Update ()
{
// implementation-1
float speed = Vector3.Distance (transform.parent.position, ballLastPosition) * 30f;
transform.RotateAround (transform.position, Vector3.right, speed);
// float dragDifference = (transform.position.x - ballLastPosition.x) * 30f;
// transform.RotateAround (transform.position, Vector3.forward, dragDifference);
ballLastPosition = transform.parent.position;
}
}
CameraFollow
public class CameraFollow : MonoBehaviour
{
//
private Vector3 newPos;
private Vector3 initialPosition;
//
public Transform player;
public Vector3 offSet;
void Awake ()
{
initialPosition = transform.position;
}
void LateUpdate ()
{
if (!player)
return;
newPos = player.position + offSet;
newPos.x = ReMap (newPos.x);
newPos.y = Mathf.Clamp (newPos.y, initialPosition.y, initialPosition.y + 1f);
// transform.position = newPos;
transform.position = Vector3.Lerp (transform.position, newPos, 10f * Time.deltaTime);
}
public void FollowPlayer (Transform target)
{
player = target;
ResetCamera ();
}
public float ReMap (float value, float from1 = -4f, float to1 = 4f, float from2 = -2.5f, float to2 = 2.5f)
{
return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
}
public void ResetCamera ()
{
transform.position = initialPosition;
}
}
I could solve the second problem by simply adding this to BallController.ClampingBallMovement():
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
// I ADDED THIS
// Clamp the velocity as well
if (currRigidbodyPos.x <= -ballHorzRange && myRigidBody.velocity.x < 0 || currRigidbodyPos.x >= ballHorzRange && myRigidBody.velocity.x > 0)
{
myRigidBody.velocity = new Vector3(0, myRigidBody.velocity.y, myRigidBody.velocity.z);
}
}
you clamped the position but did not clamp the velocity as well.
I could not reproduce the first jerking arround on my PC.
I have long background for game so I have made two partition of it and started implementation of scrolling one after another.
But during scrolling, I was facing this kind of issue - one part get overlapped on other part, following image will give you more clearance about my point:
Because of overlapping problem, its not looking smooth.
Here is the code that I have used for implementation:
public class LayerScroller : MonoBehaviour
{
public Layer[] backgroundLayers;
public Layer[] starLayers;
void Start ()
{
}
void Update ()
{
// for (int i = 0; i < backgroundLayers.Length; i++) {
// backgroundLayers [i].MoveLayer ();
// }
for (int i = 0; i < starLayers.Length; i++) {
starLayers [i].MoveLayer ();
}
}
}
public class Layer : MonoBehaviour
{
public Transform prevGroupLayer;
public float speed;
public void MoveLayer ()
{
Vector3 currPosition = transform.position;
currPosition.y -= speed;
transform.position = Vector3.Lerp (transform.position, currPosition, Time.deltaTime * 10f);
// transform.Translate (Vector3.right * -speed * Time.deltaTime);
if (transform.position.y <= -GameConstants.BACKGROUND_LENGTH) {
Vector3 currentPosition = transform.position;
currentPosition.y = (prevGroupLayer.position.y + (GameConstants.BACKGROUND_LENGTH)) - speed * Time.deltaTime;
transform.position = currentPosition;
}
}
}
LayerScroller script is controlling all child layers and each layer contains Layer script.
Complete hierarchy looking like this:
Please give some guidance for solving this overlapping problem, definitely I am doing some small mistake.
I am new to unity and don't know a lot of stuff. I've been watching tutorials and I saw one in which the guy created a replica of famous 'Chilly Snow'. The game is complete but the movement of ball isn't like the one in chilly snow. The ball starts orbiting continuously when I press mouse button. I wanted to know how to create that kind of movement, so that the ball turns left and right in a curve but doesn't go in to an orbit. I googled a lot but wasn't able to find my required result. I would really appreciate if anyone could point me in the right direction. Images are attached.Chilly Snow | Movement of my ball
public class movement : MonoBehaviour {
private float points;
public float playerSpeed;
private float rotationSpeed;
public Text score;
private bool isMovingLeft;
public GameObject player;
public bool isDead;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
transform.Translate (Vector3.down * playerSpeed * Time.deltaTime);
if (Input.GetMouseButtonDown (0))
{
Time.timeScale = 1;
isMovingLeft = !isMovingLeft;
rotationSpeed += 0.5f * Time.deltaTime;
}
if (Input.GetMouseButton (0))
{
rotationSpeed = 1f;
}
if (isMovingLeft) {
rotationSpeed += 1.5f * Time.deltaTime;
transform.Rotate(0,0,rotationSpeed);
} else
transform.Rotate(0,0, - rotationSpeed);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
Here is how I would approach it without doing a rotation... using your code.
public class movement : MonoBehaviour {
private float points;
public Text score;
public GameObject player;
public bool isDead;
private float currentXSpeed;
private float targetSpeed;
public float maxXSpeed;
public float speedChange;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
targetSpeed = maxXSpeed;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
if(Input.GetMouseButtonDown(0))
{
Time.timeScale = 1;
targetSpeed = -targetSpeed;
}
currentSpeed = mathf.MoveTowards(currentSpeed, targetSpeed, speedChange * Time.deltaTime);
Vector3 movementDirection = new Vector3(currentSpeed, Vector3.down.y * playerSpeed, 0.0f);
transform.Translate (movementDirection * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
You need something like sinusoidal movement or any other graph you fancy.
An example would be for this is like;
gameObject.transform.Translate(Vector3.right * Time.deltaTime*cubeSpeed);
gameObject.transform.position += transform.up * Mathf.Sin (Time.fixedTime * 3.0f ) * 0.1f;
Above pseudo is for 2D graph simulation, can be adapted to your situation.
The object is always moving to right and going up and down while making a sinusoidal movement. Because the up and down speed is not fixed hence you get the sinusoidal or like sinusoidal movement.
In your case, while the object is always going down it will make the sinusoidal movement to left and right.
Your movement is based on the rotation so, if you give this sinusoidal speed as your rotation speed, you can achieve this.
Another aproach can be lerp or slerp
Lerp allows you to make kinda smooth transactions between 2 vectors.
Like moving from pointA to pointB in X seconds.
For rotation you will need Quaternion.Lerp There is a great answer on Unity Answers you can check that if you haven't before.
Hope this helps! Cheers!
I'm trying to program AI for a pong game and i'm trying to make the paddle game object follow the y co-ordinate movement of the ball game object.
The problem is that the paddle ends up just moving up and down and not actually following the ball.
This is the script i used to make it follow.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AIMove : MonoBehaviour {
public int speed;
public bool validUp = true;
public bool validDown = true;
void Awake()
{
}
void Update()
{
GameObject sphere = GameObject.Find("Sphere");
Transform spherePosition = sphere.GetComponent<Transform>();
float spherePos = spherePosition.position.y;
if (spherePos < (0) && validUp == (true))
{
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
else if (spherePos > (0) && validDown == (true))
{
transform.Translate(Vector3.down * speed * Time.deltaTime);
}
if (transform.position.y >= (2.3F))
{
validUp = false;
}
else if (transform.position.y <= (-2.3F))
{
validDown = false;
}
else
{
validUp = true;
validDown = true;
}
}
}
I think you do not fully understand how your code works. Let's take a look inside your Update method.
if (spherePos < (0) && validUp == (true))
{
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
What you are saying here is that if the position of the sphere on the Y axis is below 0, the AI paddle should move up. This contains no condition on whether the sphere is moving upwards or downwards. So, the sphere could be moving downwards, and you would still be moving the AI paddle upwards.
else if (spherePos > (0) && validDown == (true))
{
transform.Translate(Vector3.down * speed * Time.deltaTime);
}
This contains the same kind of problems.
Instead, you could do something like this.
[SerializeField]
private float speed;
private Transform sphere;
private void Start()
{
// In general, I'd recommend avoiding
// GameObject.Find, but we'll use it for now.
sphere = GameObject.Find("Sphere").transform;
}
private void Update()
{
if(sphere.position.y >= transform.position.y)
{
transform.Translate(Vector.up * speed * Time.deltaTime);
}
else if(sphere.position.y <= transform.position.y)
{
transform.Translate(Vector.down * speed * Time.deltatime);
}
}
This would basically compare the paddle's position on the Y axis to the sphere's position on the Y axis and move the paddle accordingly, to the correct direction. Then you just have to adjust the speed value as you want.
Note that this only moves the paddle based on the sphere's position on the Y axis alone. It does not take into account how the sphere is going to bounce off the walls or anything like that.