How to have same size of objects in different screen resolutions? - unity3d

I'm working on a 2D game in Unity. I'm developing it for android and it's in portrait mode. I have placed one wall on left side of screen and one wall on right side of screen. The problem is the distance between those 2 walls is different in different screen resolutions. So, my character can't jump from one wall to other with the same velocity being applied.
I'm attaching an image to make my point clear.
What can I do so that in different devices, I get the same distance between these 2 walls? Any help will be appreciated.
EDIT: This is how I'm placing the walls right now.
screenLeft = Camera.main.ScreenToWorldPoint (new Vector3 (0f, 0f, 0f)).x;
screenRight = Camera.main.ScreenToWorldPoint (new Vector3 (Screen.width, 0f, 0f)).x;
rightWallSizeX = rightWall.GetComponent<SpriteRenderer> ().bounds.size.x;
leftWallSizeX = leftWall.GetComponent<SpriteRenderer> ().bounds.size.x;
rightWall.transform.position = new Vector3 (screenRight - rightWallSizeX/2, 0f, 0f);
leftWall.transform.position = new Vector3 (screenLeft + leftWallSizeX/2, 0f, 0f);

If you design your level using the editor:
Then the output aspect will be correct regardless its value:
As a bonus I've crafted you some code that lets the player stick to a wall and jump to the opposite side of it :D
using UnityEngine;
namespace Assets
{
public class PlayerController : MonoBehaviour
{
private const string Ground = "Ground";
private const string Wall = "Wall";
private Rigidbody2D _body;
private Vector2 _cNormal;
private GameObject _cWall;
private bool _joyAction;
private float _joyClimb;
private float _joyMove;
private State _state;
public float ForceClimb = 20.0f;
public float ForceJump = 5.0f;
public float ForceJumpFromWall = 10.0f;
public float ForceMove = 10.0f;
public float MaxMove = 15.0f;
private void Start()
{
_state = State.Air;
}
private void OnEnable()
{
_body = GetComponent<Rigidbody2D>();
}
private void Update()
{
_joyMove = Input.GetAxis("Horizontal");
_joyClimb = Input.GetAxis("Vertical");
_joyAction = Input.GetButtonDown("Fire1");
}
private void FixedUpdate()
{
var air = _state == State.Air;
var ground = _state == State.Ground;
var wall = _state == State.Wall;
if (air || ground)
{
var canJump = ground && _joyAction;
if (canJump)
{
var force = new Vector2(0.0f, ForceJump);
_body.AddForce(force, ForceMode2D.Impulse);
_state = State.Air;
}
var move = transform.InverseTransformDirection(_body.velocity).x;
if (move < MaxMove)
{
var force = new Vector2(_joyMove*ForceMove, 0.0f);
_body.AddRelativeForce(force);
}
}
else if (wall)
{
var climbing = Mathf.Abs(_joyClimb) > 0.0f;
if (climbing)
{
_body.AddForce(new Vector2(0, ForceClimb*_joyClimb));
}
else
{
var jumpingOut = _joyAction;
if (jumpingOut)
{
TryUnstickFromWall();
_body.AddForce(_cNormal*ForceJumpFromWall, ForceMode2D.Impulse);
}
}
}
}
private void OnGUI()
{
GUILayout.Label(_state.ToString());
}
public void OnCollisionEnter2D(Collision2D collision)
{
var c = collision.collider;
var t = c.tag;
if (t == Ground)
{
_state = State.Ground;
TryUnstickFromWall(); // fixes wall-sticking
}
else if (t == Wall && _state == State.Air) // jumping to wall
{
var wall = collision.gameObject;
var joint2D = wall.AddComponent<FixedJoint2D>();
var contact = collision.contacts[0];
var normal = contact.normal;
Debug.DrawRay(contact.point, normal, Color.white);
// stick 2 wall
joint2D.anchor = contact.point;
joint2D.frequency = 0.0f;
joint2D.autoConfigureConnectedAnchor = false;
joint2D.enableCollision = true;
_body.constraints = RigidbodyConstraints2D.FreezePositionX;
_body.gravityScale = 0.125f;
// save these
_cWall = wall;
_cNormal = normal;
// update state
_state = State.Wall;
}
}
public void OnCollisionExit2D(Collision2D collision)
{
if (collision.collider.tag == Ground)
{
_state = State.Air;
}
}
private void TryUnstickFromWall()
{
if (_cWall != null)
{
_body.constraints = RigidbodyConstraints2D.None;
_body.gravityScale = 1.0f;
var joint2D = _cWall.GetComponent<FixedJoint2D>();
if (joint2D != null) Destroy(joint2D);
_cWall = null;
}
}
}
internal enum State
{
Air,
Ground,
Wall
}
}
To try it:
create a layout like the one in picture 1
add a Rigidbody2D and BoxCollider2D to all of them
set their rigidbodies to be kinematic except for the player
add this script to the player
set walls tag to Wall
set ground tag to Ground
Then tweak it to your needs ...
EDIT:
You will have to encompass transform.InverseTransformDirection(_body.velocity).x with Mathf.Abs() to be correct, also at times the collision normal will be reversed making the player stuck to the wall, I let you figure this one :)

Related

Char animation breaks when trying to manipulate one arm with Obi Rope in Unity(mobile game)

I am trying to keep my character always idle-animated, even when User stretches his arm by pulling it with touchscreen. The character's arms are purely fists and body, with an obi rope in-between. Body animation works with obi-rope until any hand is being stretched,then the hand part of animation breaks, and it keeps moving but the hand is static. Same applies to another hand and legs :(
I don't know how to even describe it in 1 sentence, so googling didn't help me much. Please, help me out. I don't want to turn off animation when my character stretches arms, it look super artificial
If the code would be of any use:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerHit : MonoBehaviour
{
[SerializeField] private Image circleThisBodyPart;
private Vector3 startScale;
private Vector3 firstPosition;
private Vector3 lastPosition;
private Quaternion startRotation;
private bool isHit = false;
private bool isFirstAttack = true;
private int stage = 0;
private Vector3 posAfterMouseUp;
private void Start()
{
startRotation = transform.rotation;
startScale = transform.localScale;
firstPosition = transform.position;
}
private void LateUpdate()
{
if (isHit)
{
this.transform.rotation = startRotation;
this.transform.localScale = startScale;
if (stage == 1)
{
this.gameObject.transform.position = Vector3.MoveTowards(transform.position, new Vector3(lastPosition.x, lastPosition.y, lastPosition.z), 20f * Time.deltaTime);
if (this.transform.position == lastPosition)
{
stage = 2;
}
}
if (stage == 2)
{
this.gameObject.transform.position = Vector3.MoveTowards(transform.position, firstPosition, 20f * Time.deltaTime);
transform.localScale = startScale;
if (this.transform.position == firstPosition)
{
PlayerAttackBody.isAttack = false;
GlobalEvents.ShowPlayerCircles?.Invoke();
stage = 0;
}
}
}
}
private void OnMouseDown()
{
if (isFirstAttack)
{
GlobalEvents.StartGame?.Invoke();
isFirstAttack = false;
}
PlayerAttackBody.isAttack = true;
GlobalEvents.HidePlayerCircles?.Invoke(circleThisBodyPart);
GlobalEvents.ChangePosToCameraDistance?.Invoke(this.gameObject);
}
private void OnMouseDrag()
{
this.transform.localScale = startScale;
this.transform.rotation = startRotation;
}
private void OnMouseUp()
{
posAfterMouseUp = transform.position;
GlobalEvents.ChangePosToCameraDistance?.Invoke(null);
Vector3 currentPos = this.transform.position;
lastPosition = new Vector3(-currentPos.x, -currentPos.y+2.9f, -currentPos.z);
isHit = true;
stage = 1;
}
}

Creating top down 2D whip mechanic

What Whip should look like
I'm trying to create a whip that can extend in any direction the mouse is facing after pressing a certain button. If there are "grabbable" objects in the way such as an enemy or box, it should latch onto those objects and pull them around to collide with other objects for a certain amount of time.
I know that I need the different sprite shots of the whip extending and latching for animation, but I have no idea how to implement this in code and how to get the whip to stop short if it detects a "grabbable" object.
Attach this script to your player, this should get the job done:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public Transform player = null;
public float speed = 30f;
public string grabbableTag = "grabbable";
private LineRenderer line = null;
private float timer = 0f;
private bool grabbing = false;
private bool reached = false;
private Vector2 from = Vector2.zero;
private Vector2 to = Vector2.zero;
private Vector2 target = Vector2.zero;
private Transform grabbable = null;
private void Start()
{
player = transform;
line = new GameObject("Line").AddComponent<LineRenderer>();
line.startColor = Color.red;
line.endColor = Color.red;
// Assign a material
line.gameObject.SetActive(false);
reached = false;
grabbing = false;
}
private void Update()
{
if(grabbing)
{
Grabbing();
}
else
{
if (Input.GetMouseButtonDown(0))
{
Grab();
}
}
}
private void Grab()
{
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = Vector3.Distance(player.position, Camera.main.transform.position);
to = Camera.main.ScreenToWorldPoint(mousePosition);
from = player.position;
Vector2 direction = (to - from).normalized;
float distance = Vector2.Distance(from, to);
RaycastHit2D[] hits = Physics2D.RaycastAll(from, direction, distance);
grabbable = null;
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].transform.tag == grabbableTag)
{
grabbable = hits[i].transform;
break;
}
}
if (grabbable != null)
{
distance = Vector2.Distance(player.position, grabbable.position);
to = from + direction * distance;
}
grabbing = true;
reached = false;
target = from;
timer = 0;
line.gameObject.SetActive(true);
line.positionCount = 2;
line.SetPosition(0, from);
line.SetPosition(1, from);
}
private void Grabbing()
{
if (reached)
{
target = Vector2.Lerp(target, from, speed * Time.deltaTime);
if (target == from)
{
GrabDone(grabbable);
grabbing = false;
line.gameObject.SetActive(false);
}
}
else
{
target = Vector2.Lerp(target, to, speed * Time.deltaTime);
if(target == to)
{
reached = true;
}
}
line.SetPosition(1, target);
if (reached && grabbable != null)
{
grabbable.position = target;
}
}
private void GrabDone(Transform grabbed)
{
if(grabbed != null)
{
// Do somthing ...
Destroy(grabbed.gameObject);
}
}
}

How to Mathf.Clamp one axis y in update when the rotation is determined by mouse position in unity 3D

I'm developing a sailing simulator for a school project and Ii'm currently working on the gas handle of the boat. It's currently working so that the boat goes forward when the handle is in y axis = 0 and goes in reverse when the handle is in y axis = 90.
Currently I'm only able to test this by setting the values in the inspector because I can't get it to clamp so that it stops in those two y-rotations. The mouse moving the handle works fine as well.
My script looks the following:
public class GasHandleRotator : MonoBehaviour
{
private float _sensitivity;
private Vector3 _mouseReference;
private Vector3 _mouseOffset;
private Vector3 _rotation;
private bool _isRotating;
public AdvancedShipController vc;
public Transform gasHandle;
public Transform forwardTarget;
public Transform reverseTarget;
public static bool forwardTrue = false;
void Start()
{
_sensitivity = 0.4f;
_rotation = Vector3.zero;
}
void Update()
{
if (_isRotating)
{
// offset
_mouseOffset = (Input.mousePosition - _mouseReference);
Vector3 currentRotation = transform.rotation.eulerAngles;
currentRotation.y = Mathf.Clamp(currentRotation.y, 0, 90);
// apply rotation
_rotation.y = -( _mouseOffset.y) * _sensitivity;
// rotate
transform.Rotate(_rotation);
//transform.rotation = currentRotation;
// store mouse
_mouseReference = Input.mousePosition;
}
if (Input.GetMouseButtonDown(1))
{
//forwardTrue = true;
Cursor.lockState = CursorLockMode.None;
// rotating flag
_isRotating = true;
// store mouse
_mouseReference = Input.mousePosition;
}
if (Input.GetMouseButtonUp(1))
{
//forwardTrue = false;
Cursor.lockState = CursorLockMode.Locked;
_isRotating = false;
}
if (gasHandle.rotation == forwardTarget.rotation)
{
vc.input.Throttle = 1.0f;
}
if (gasHandle.rotation == reverseTarget.rotation)
{
vc.input.Throttle = -1.0f;
}
if (Input.GetKeyDown("e"))
{
vc.input.EngineStartStop = true;
}
}
}
I also have this short video to illustrate how I move the handle: https://youtu.be/mP1wJ9o4AJw
Have you tried moving this line
currentRotation.y = Mathf.Clamp(currentRotation.y, 0, 90);
to below these lines?
rotation.y = -( _mouseOffset.y) * _sensitivity;
// rotate
transform.Rotate(_rotation);
If i understand correctly that should fix your problem

Unity: Touch Delay is too high

I want to make a game, where you can throw items in 2D via drag and drop. This works fine, but the tracking of grabbing them is very bad. If I touch them in the top half the tracking won't or barely work.
Are there other methods to prevent this?
public class DropsBehaviour : MonoBehaviour {
//Declaring Variables
public int force = 10;
float maxSpeedPreset = 5;
float maxSpeed = 5;
float MaxThrowSpeed = 8;
GameManager gm;
Rigidbody rig;
Camera cam;
bool selected = false;
bool once = false;
void Start() {
gm = GameManager.FindMe();
rig = GetComponent<Rigidbody>();
cam = Camera.main;
}
void Update() {
if (selected) {
rig.AddForce((cam.ScreenToWorldPoint(Input.mousePosition) - transform.position) * force);
rig.velocity = Vector3.zero;
} else {
if (!once) {
maxSpeed = maxSpeedPreset * gm.gameSpeed;
rig.velocity = new Vector3(Mathf.Clamp(rig.velocity.x, -maxSpeed, maxSpeed), Mathf.Clamp(rig.velocity.y, -maxSpeed, maxSpeed), Mathf.Clamp(rig.velocity.z, -maxSpeed, maxSpeed));
} else {
maxSpeed = maxSpeedPreset;
rig.velocity = new Vector3(Mathf.Clamp(rig.velocity.x, -MaxThrowSpeed, MaxThrowSpeed), Mathf.Clamp(rig.velocity.y, -MaxThrowSpeed, MaxThrowSpeed), Mathf.Clamp(rig.velocity.z, -MaxThrowSpeed, MaxThrowSpeed));
}
}
}
void OnMouseDown() {
if (tag != "NoInteract") {
selected = true;
once = true;
}
}
void OnMouseUp() {
selected = false;
}
}
I see quite a few issues in the code snippet you posted that could cause stuttering.
First, if you are making something in 2D, it's recommended to use Rigidbody2D and Collider2D.
Then, you shouldn't edit a rigidbody's velocity by hand, as stated in the doc. That's what add force is made for.
When updating anything related to physics, you should do it in FixedUpdate, not Update.
I assume you also forgot to put " once = false " on the first pass ? Really not sure about this one.
I edited your script following the points stated before, while keeping the rigidbody 3D. I dind't tested it, but hopefully it will correct the weird drag behavior you're experiencing! (note that I removed the GameManager part since i can't know what it does)
public class DropsBehaviour : MonoBehaviour
{
public int force = 10;
private const float MaxSpeedPreset = 5;
private float _maxSpeed = 5;
private const float MaxThrowSpeed = 8;
private Rigidbody _rig;
private Camera _cam;
private bool _selected;
private bool _once;
private Vector3 _targetVelocity;
private void Start()
{
_rig = GetComponent<Rigidbody>();
_cam = Camera.main;
}
private void Update()
{
if (_selected) return;
if (!_once)
{
_maxSpeed = MaxSpeedPreset * 10;
_targetVelocity = new Vector3(Mathf.Clamp(_rig.velocity.x, -_maxSpeed, _maxSpeed),
Mathf.Clamp(_rig.velocity.y, -_maxSpeed, _maxSpeed),
Mathf.Clamp(_rig.velocity.z, -_maxSpeed, _maxSpeed));
}
else
{
_maxSpeed = MaxSpeedPreset;
_targetVelocity = new Vector3(Mathf.Clamp(_rig.velocity.x, -MaxThrowSpeed, MaxThrowSpeed),
Mathf.Clamp(_rig.velocity.y, -MaxThrowSpeed, MaxThrowSpeed),
Mathf.Clamp(_rig.velocity.z, -MaxThrowSpeed, MaxThrowSpeed));
_once = false; //Dunno if it's intended or not ?
}
}
private void FixedUpdate()
{
if (_selected)
{
_rig.AddForce((_cam.ScreenToWorldPoint(Input.mousePosition) - transform.position) * force);
_rig.velocity = Vector3.zero;
return;
}
var velocity = _rig.velocity;
var velocityChange = _targetVelocity - velocity;
_rig.AddForce(velocityChange, ForceMode.VelocityChange);
}
private void OnMouseDown()
{
if (CompareTag("NoInteract")) return;
_selected = true;
_once = true;
}
private void OnMouseUp()
{
_selected = false;
}
}
On a side note, I personnaly don't like tags, I would use a LayerMask instead since it's somewhat related to physics. I'd probably replace the OnMouse events with a Raycast too, that way you would have a lot more control on what is happening on the physic side.

Touch Controls unity 2D

I have script called PlayerCharacter to control a player on the Unity 2D Platform. It's perfect, working as usual.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[RequireComponent(typeof (Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]
public class PlayerCharacter : MonoBehaviour
{
public float speed = 1.0f;
public string axisName = "Horizontal";
private Animator anim;
public string jumpButton = "Fire1";
public float jumpPower = 10.0f;
public float minJumpDelay = 0.5f;
public Transform[] groundChecks;
private float jumpTime = 0.0f;
private Transform currentPlatform = null;
private Vector3 lastPlatformPosition = Vector3.zero;
private Vector3 currentPlatformDelta = Vector3.zero;
// Use this for initialization
void Start ()
{
anim = gameObject.GetComponent<Animator>();
}
// Update is called once per frame
void Update ()
{
//Left and right movement
anim.SetFloat("Speed", Mathf.Abs(Input.GetAxis(axisName)));
if(Input.GetAxis(axisName) < 0)
{
Vector3 newScale = transform.localScale;
newScale.x = -1.0f;
transform.localScale = newScale;
Debug.Log("Move to left");
}
else if(Input.GetAxis(axisName) > 0)
{
Vector3 newScale = transform.localScale;
newScale.x = 1.0f;
transform.localScale = newScale;
Debug.Log ("Move to Right");
}
transform.position += transform.right*Input.GetAxis(axisName)*speed*Time.deltaTime;
//Jump logic
bool grounded = false;
foreach(Transform groundCheck in groundChecks)
{
grounded |= Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));
}
anim.SetBool("Grounded", grounded);
if(jumpTime > 0)
{
jumpTime -= Time.deltaTime;
}
if(Input.GetButton("jumpButton") && anim.GetBool("Grounded") )
{
anim.SetBool("Jump",true);
rigidbody2D.AddForce(transform.up*jumpPower);
jumpTime = minJumpDelay;
}
if(anim.GetBool("Grounded") && jumpTime <= 0)
{
anim.SetBool("Jump",false);
}
//Moving platform logic
//Check what platform we are on
List<Transform> platforms = new List<Transform>();
bool onSamePlatform = false;
foreach(Transform groundCheck in groundChecks)
{
RaycastHit2D hit = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));
if(hit.transform != null)
{
platforms.Add(hit.transform);
if(currentPlatform == hit.transform)
{
onSamePlatform = true;
}
}
}
if(!onSamePlatform)
{
foreach(Transform platform in platforms)
{
currentPlatform = platform;
lastPlatformPosition = currentPlatform.position;
}
}
}
void LateUpdate()
{
if(currentPlatform != null)
{
//Determine how far platform has moved
currentPlatformDelta = currentPlatform.position - lastPlatformPosition;
lastPlatformPosition = currentPlatform.position;
}
if(currentPlatform != null)
{
//Move with the platform
transform.position += currentPlatformDelta;
}
}
}
A problem arises when I try to modify the script with a touchable controller. I have googled many times and modified the script as I could, and still it gives me no result (btw, I'm new to Unity). Then I found a tutorial from a website about making a touch controller with a GUI Texture (TouchControls). I think that tutorial is easy to learn. Here is the script
using UnityEngine;
using System.Collections;
[RequireComponent(typeof (Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]
public class TouchControls : MonoBehaviour {
// GUI textures
public GUITexture guiLeft;
public GUITexture guiRight;
public GUITexture guiJump;
private Animator anim;
// Movement variables
public float moveSpeed = 5f;
public float jumpForce = 50f;
public float maxJumpVelocity = 2f;
// Movement flags
private bool moveLeft, moveRight, doJump = false;
void Start ()
{
anim = gameObject.GetComponent<Animator>();
}
// Update is called once per frame
void Update () {
// Check to see if the screen is being touched
if (Input.touchCount > 0)
{
// Get the touch info
Touch t = Input.GetTouch(0);
// Did the touch action just begin?
if (t.phase == TouchPhase.Began)
{
// Are we touching the left arrow?
if (guiLeft.HitTest(t.position, Camera.main))
{
Debug.Log("Touching Left Control");
moveLeft = true;
}
// Are we touching the right arrow?
if (guiRight.HitTest(t.position, Camera.main))
{
Debug.Log("Touching Right Control");
moveRight = true;
}
// Are we touching the jump button?
if (guiJump.HitTest(t.position, Camera.main))
{
Debug.Log("Touching Jump Control");
doJump = true;
}
}
// Did the touch end?
if (t.phase == TouchPhase.Ended)
{
// Stop all movement
doJump = moveLeft = moveRight = false;
}
}
// Is the left mouse button down?
if (Input.GetMouseButtonDown(0))
{
// Are we clicking the left arrow?
if (guiLeft.HitTest(Input.mousePosition, Camera.main))
{
Debug.Log("Touching Left Control");
moveLeft = true;
}
// Are we clicking the right arrow?
if (guiRight.HitTest(Input.mousePosition, Camera.main))
{
Debug.Log("Touching Right Control");
moveRight = true;
}
// Are we clicking the jump button?
if (guiJump.HitTest(Input.mousePosition, Camera.main))
{
Debug.Log("Touching Jump Control");
doJump = true;
}
}
if (Input.GetMouseButtonUp(0))
{
// Stop all movement on left mouse button up
doJump = moveLeft = moveRight = false;
}
}
void FixedUpdate()
{
//anim.SetFloat("Speed", Mathf.Abs);
// Set velocity based on our movement flags.
if (moveLeft)
{
rigidbody2D.velocity = -Vector2.right * moveSpeed;
}
if (moveRight)
{
rigidbody2D.velocity = Vector2.right * moveSpeed;
}
if (doJump)
{
// If we have not reached the maximum jump velocity, keep applying force.
if (rigidbody2D.velocity.y < maxJumpVelocity)
{
rigidbody2D.AddForce(Vector2.up * jumpForce);
} else {
// Otherwise stop jumping
doJump = false;
}
}
}
}
But I have no idea how to implement the script from the tutorial (TouchControls) and assign that to my player control script (PlayerCharacter). How can I combine both scripts so that a player can control it with a touchable control?
The best thing you can do is not to drag the touch controls from the touchcontrols tutorial to the playercontroller but the other way around, use the touchcontrols tutorial script as your template.
Since your playercontroller uses floats in its input such as moveleft = 50.0f; and the touchcontrols uses moveleft = true;
the scripts are very different from each other to just merge and work.
so from that in the touchcontrols leave the update function as it is,
and only update the fixedupate function with your controls logic since
the update void, is the condition controller for right, left, up & down so to speak.
and it will also handle the actual input of the touch.
the fixed update could then control some things that the playercontroller has such as
apply force when touching a tagged object or stuff like that.
and the update only does the input condition, good advice would be to wrap the update touch code in its own function so the update is not only touch but also other game logic related code.
You should search use copy the touch control script inside the player controller while changing the right parts. For example, instead of using Input.GetKeyDown you should use the Input.GetTouch but it depends on the game you are creating. You should pay attention to that code and change some parts