Just so we are all on the same page, In the lesson, it is shown how to get the playable area (xMin,xMax,yMin,yMax) with ViewportToWorldPoint(), and, get user input with Input.GetAxis(), then how to calculate a new position for the player using Time.deltaTime. I dug a little into the documentation (as I tend to do), and for anyone wanting to implement this, I have some ideas to improve the results. I totally understand that the methods shown in the training video are specifically chosen to be straightforward and easy to understand for someone new the programming and game design. These are suggestions for how to improve it for someone who wants to go beyond the basics.
The Problem: your horizontal movement is separate from your vertical movement, so moving straight left/right and moving straight up/down will be slower than moving diagonally. Think of your inputs as a square joystick, and you want to make it work like a circular joystick instead to give it an arcade feel. What I do is convert the inputs from the user into a Vector2, clamp the magnitude to 1 (unit circle), then multiply that by the maximum speed the player can move (times delta time of course). This makes all directions of movement the same maximum speed.
The Problem: It is a little clunky to call ViewportToWorldPoints(), take only the X values, then call again and take only the Y values. It is also clunky to store bounds as 4 variables (xMin, xMax, yMin, yMax), when there is already a structure specifically designed to do this: Rect.
The Problem: I don’t like to perform a bunch of calculations all over the place that can be difficult to understand what is going on. I like to break up my math into discrete function calls, and make these calls wherever I can to make it easier to read what is going on. Of course, the compiler may very well inline these functions to make it run faster, and I’m fine with that, but readability is king for me. I’ll post my entire Player class as it is right now (having not completed the entire project, only videos up to this point), so others can review it, offer feedback, ask questions, and of course, offer suggestions for how I may improve this even more.
using UnityEngine;
public class Player : MonoBehaviour {
/// <summary>
/// Maximum linear movement speed for the player, in world units per second.
/// </summary>
[SerializeField] float moveSpeed = 10f;
/// <summary>
/// Bounds for the player movement, in Viewport space where (0f, 0f, 1f, 1f) is the entire space.
/// </summary>
[SerializeField] Rect shipScreenBounds = new Rect(0f, 0f, 1f, 0.9f);
/// <summary>
/// Player Ship movement boundaries calculated from the viewport of the camera, in world units.
/// used in Move method to constrain player movement to play area
/// </summary>
Rect bounds;
/// <summary>
/// Holds the component SpriteRenderer for this sprite.
/// </summary>
SpriteRenderer mySpriteRenderer;
// Use this for initialization
void Start ()
{
// Get our SpriteRenderer.
mySpriteRenderer = GetComponent<SpriteRenderer>();
// Get the extents of the player ship. x and y will be 1/2 the actual size of the sprite on screen.
Vector3 spriteExtents = mySpriteRenderer.bounds.extents;
// Get our playfield area, adjust it to pad for our ship sprite extents, and assign to our rect
bounds = PadRectForSpriteExtents(GetViewportInWorldUnits(shipScreenBounds), spriteExtents);
}
/// <summary>
/// using the main camera, calculates bounds for player movement, and returns these values as a rect
/// </summary>
private Rect GetViewportInWorldUnits(Rect usableArea)
{
Camera gameCamera = Camera.main;
Vector3 lowerLeft = gameCamera.ViewportToWorldPoint(new Vector3(usableArea.xMin, usableArea.yMin, 0));
Vector3 upperRight = gameCamera.ViewportToWorldPoint(new Vector3(usableArea.xMax, usableArea.yMax, 0));
return new Rect(FlattenVector3(lowerLeft), FlattenVector3(upperRight - lowerLeft));
}
/// <summary>
/// Pads a bounding rectangle by a size so that an object clamped to the rect can't leave the viewport
/// </summary>
/// <param name="rect"></param>
/// <param name="size"></param>
/// <returns></returns>
private Rect PadRectForSpriteExtents(Rect rect, Vector3 extent)
{
rect.xMin += extent.x;
rect.xMax -= extent.x;
rect.yMin += extent.y;
rect.yMax -= extent.y;
return rect;
}
/// <summary>
/// Turns a Vector3 into a Vector2 by dropping the z coordinate.
/// </summary>
/// <param name="point">3d point to convert to 2d</param>
/// <returns></returns>
private Vector2 FlattenVector3(Vector3 point)
{
return new Vector2(point.x, point.y);
}
/// <summary>
/// Turns a Vector2 into a Vector3. Assumes a z location of 0f unless specified
/// </summary>
/// <param name="point">2d point to convert</param>
/// <param name="z">z coordinate to use for the conversion; assumes 0f if not specified</param>
/// <returns></returns>
private Vector3 UnflattedVector2(Vector2 point, float z = 0f)
{
return new Vector3(point.x, point.y, z);
}
// Update is called once per frame
void Update ()
{
Move();
}
/// <summary>
/// handle player input to move player ship.
/// </summary>
private void Move()
{
// get joystick horizontal position
float deltaX = Input.GetAxis("Horizontal");
// get joystick vertical position
float deltaY = Input.GetAxis("Vertical");
// map the square joystick to a unit circle (clamp the magnituce of the vector to 1)
Vector2 direction = Vector2.ClampMagnitude(new Vector2(deltaX, deltaY), 1f);
// direction * speed/second * elapsed_seconds = how far to move this frame.
// Add that to the current position to get the new desired position.
Vector2 newPosition = (direction * moveSpeed * Time.deltaTime) + FlattenVector3(transform.position);
// clamp the desired position to our play field, and assign our new position to the sprite.
transform.position = ClampVector2ToRect(newPosition, bounds);
}
/// <summary>
/// Clamps a Vector2 point to be within the bounds of a Rect.
/// </summary>
/// <param name="point">Point to constrain</param>
/// <param name="rect">bounds to constraing the point to</param>
/// <returns>Vector2 of a point within the bounds of rect.</returns>
private Vector2 ClampVector2ToRect(Vector2 point, Rect rect)
{
return new Vector2(Mathf.Clamp(point.x, rect.xMin, rect.xMax), Mathf.Clamp(point.y, rect.yMin, rect.yMax));
}
}