Movement Cleanup
Earlier, I put a PlayerInput component on both Rush and Mega Man. While they used the same script, so moved in tandem, it felt odd that technically they moved independently. Could I move the PlayerInput to the parent object instead?
The answer ended up being “yes,” by utilizing transform.parent to reference the parent’s PlayerInput on the children. Since the Mega Man script was responsible for shooting, it needed a tweak to use transform.parent.GetComponent<PlayerInput>() instead, in order to set the events for shooting. Otherwise… it all seemed to work. Movement was now controlled by the parent object instead.
The shooting still bothered me for two reasons:
- The bullet originated from the middle of Mega Man, rather than his gun.
- He looks like he’s shooting when the button is held down… but ultimately just sat there.
So, I created an empty game object as a child of Mega Man called “Gun”. I moved this freely to me aligned with the muzzle of his gun, and moved all the shooting logic in Mega Man’s script to the Gun instead (removing the Mega Man script entirely). This did, however, mean now I needed to use transform.parent.parent to get the grandparent’s PlayerInput. Ugly, and there might be a better way, but it works for now. Now bullets originate from the Gun object, which is in front of the muzzle of Mega Man’s gun. Nice.
For the auto fire, that took a little bit of creativity. I don’t want to spawn a bullet every frame, but I do want the fire rate to be configurable. I figured maybe every 0.2 seconds will work for now. If I look at how long the player has held down the fire key, I can determine if I need to create a new bullet object. However, given frames are inconsistent, I can’t just check absolute values (e.g. elapsedTime is a multiple of 0.2), so instead I’ll track when the next bullet is due to fire, and create an object if we’ve moved past that time point.
Written out, this doesn’t make a lot of sense probably. Basically, I need to:
- Track when the player started holding fire.
- Calculate the elapsed time since firing.
- If that time is greater or equal to when the next bullet should be fired, fire.
- Increment the time for the next bullet by the interval we choose (0.2 seconds).
In code, it looks something like this:
public double autoShootInterval = 0.2d;
private double _shootStartTime;
private double _nextAutoShootTime;
private void ShootAnim(InputAction.CallbackContext context)
{
_animator.SetBool(_shootAnimId, _shootAction.inProgress);
if (_shootAction.inProgress)
{
_shootStartTime = context.startTime;
_nextAutoShootTime = autoShootInterval;
}
}
private void FixedUpdate()
{
if (_shootAction.inProgress)
{
double elapsedTime = Time.realtimeSinceStartup - _shootStartTime;
if (elapsedTime > _nextAutoShootTime)
{
_nextAutoShootTime += autoShootInterval;
CreateBullet();
}
}
}
private void CreateBullet()
{
Instantiate(weapon.gameObject, transform.position, Quaternion.identity);
}
So, when they press the button, we track the time in _shootStartTime so we can calculate elapsed time. We also set _nextAutoShootTime to our initial value (0.2). Then, as we update, if they’re still holding the button, calculate the time they’ve been holding it and see if we’ve passed our next auto shot time. If we have, shoot, and increment the next auto shot time.
Took some thinking, but seems to work!
Boundaries
The official Unity tutorials (at least the essentials) used Rigidbodies and Body Colliders to restrict movement. They created an off-screen wall which you collide into. I implemented that initially, but it had more physics to it than I’d like. Likely I could remove the physics, prevent the sprites from rotating, etc… but restricting movement in the controller script seemed better.
In order to do that, I needed the min/max x and y coordinates. This was one of the reasons I wanted to put the PlayerInput for movement on the parent object: so the min/max coordinates would apply to both Mega Man and Rush. In game mode, I just moved around and watched my coordinates in the inspector, keeping track of what seemed like a reasonable min/max. With those in hand, I made some readonly variables to hold them.
Then, I just capped the position when updating:
_body.position = new Vector2(
Mathf.Clamp(_body.position.x, MIN_X, MAX_X),
Mathf.Clamp(_body.position.y, MIN_Y, MAX_Y));
_body.linearVelocity = _movement * moveSpeed;
This kind of worked, but something odd was happening. I could go beyond the min/max if I held the button down. After some playing around, it appears that it’s because I’m using the linearVelocity, and the vector (direction) is still pushing it beyond the boundary. It snaps back when you stop moving. So, I made a change to update _movement so the vector doesn’t have a movement in a direction if the boundary has been met. It yielded a much longer, uglier piece of code:
float xPosition = _body.position.x;
float yPosition = _body.position.y;
float xDirection = _movement.x;
float yDirection = _movement.y;
if (xPosition <= MIN_X)
{
xPosition = MIN_X;
if (xDirection < 0)
{
xDirection = 0;
}
}
if (xPosition >= MAX_X)
{
xPosition = MAX_X;
if (xDirection > 0)
{
xDirection = 0;
}
}
if (yPosition <= MIN_Y)
{
yPosition = MIN_Y;
if (yDirection < 0)
{
yDirection = 0;
}
}
if (yPosition >= MAX_Y)
{
yPosition = MAX_Y;
if (yDirection > 0)
{
yDirection = 0;
}
}
_body.position = new Vector2(xPosition, yPosition);
_body.linearVelocity = new Vector2(xDirection, yDirection) * moveSpeed;
This worked better, but still wasn’t perfect. There’s still a tiny “bounce” before he snaps back in position. It’s like, one frame. Not perfect, but tolerable for now, I think. I’ll probably look for other solutions here, as this method feels nasty to me.
I think what I want to work on next is an infinitely scrolling background. Ideally, it’s has parallax movement, but we’ll start simple with a basic rolling image.
Leave a comment