2
\$\begingroup\$

I wrote a script for the player movement in c#. Whenever the player presses A or D, it moves him/her to the left or right by 12 units and when the player presses W or S, it moves him/her up or down by 12 units. My script works fine, but if a person starts to spam all of the keys at once, it glitches out and the player object is not in line with the level anymore. I want to have the script check if there a movement is currently happening before executing the movement on the keypress. Here is my script:

void Update () {

    transform.Translate(Vector3.forward * ForwardSpeed * Time.deltaTime);

    if (Input.GetKeyDown (KeyCode.A) && side > maxSideLeft) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x - 12, this.transform.position.y, this.transform.position.z + 10), movementSpeed);
        side -= 1;
    } else if (Input.GetKeyDown (KeyCode.D) && side < maxSideRight) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x + 12, this.transform.position.y, this.transform.position.z + 10), movementSpeed);
        side += 1;
    }

    if (Input.GetKeyDown (KeyCode.W) && level < maxLevelHeight) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x, this.transform.position.y + 12, this.transform.position.z + 10), movementSpeed);
        level += 1;
    } else if (Input.GetKeyDown (KeyCode.S) && level > minLevelHeight) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x, this.transform.position.y - 12, this.transform.position.z + 10), movementSpeed);
        level -= 1;
    }

    if (Input.GetKeyDown (KeyCode.R) || Input.GetKeyDown(KeyCode.Space)) {
        SceneManager.LoadScene ("Scene1");
        Time.timeScale = 1;
    }
}


private void MoveObjectTo(Transform objectToMove, Vector3 targetPosition, float moveSpeed)
{
    StopCoroutine(MoveObject(objectToMove, targetPosition, moveSpeed));
    StartCoroutine(MoveObject(objectToMove, targetPosition, moveSpeed));
}

public static IEnumerator MoveObject(Transform objectToMove, Vector3 targetPosition, float moveSpeed)
{
    float currentProgress = 0;
    Vector3 cashedObjectPosition = objectToMove.transform.position;

    while (currentProgress <= 1)
    {
        currentProgress += moveSpeed * Time.deltaTime;

        objectToMove.position = Vector3.Lerp(cashedObjectPosition, targetPosition, currentProgress);

        yield return null;
    }
}
\$\endgroup\$
1
\$\begingroup\$

The first thing I'd have a look at with your code is implementing the command pattern on your inputs. This will abstract the actual movement keys pressed from the act of moving itself. Allows for easy rebinds and using your key response methods for more than just the player character. Always try to avoid "magic numbers/keys" since they tend to scale poorly.

The next thing I'd recommend is handling input before you move the character. If the player enters valid input then apply the transform only if the current velocity of the player character is 0.

This will end up making your Update method look like this:

void Update()
{
  PlayerInputHandler.HandleInput();
  transform.Translate(Vector3.forward * ForwardSpeed * Time.deltaTime);
}

private InputHandler PlayerInputHandler;

Notice all the code is on one layer of abstraction, and the update method isn't actually worried about the details of how Inputs are handled. Much easier to follow!

This is a rough sketch up of what an InputHandler might look like, though keep in mind I'm not a regular to C# or Unity so a few errors might creep in:

public class InputHandler : MonoBehaviour
{
  public void OnAwake()
  {
    // Set default keys for inputs
    UpCommand = new InputCommandUp(KeyCode.W.ToString());
    DownCommand = new InputCommandDown(KeyCode.S.ToString());
    RightCommand = new InputCommandRight(KeyCode.D.ToString());
    LeftCommand = new InputCommandLeft(KeyCode.D.ToString());
  }

  public void HandleInput()
  {
    CheckInput(UpCommand);
    CheckInput(DownCommand);
    CheckInput(RightCommand);
    CheckInput(LeftCommand);
  }

  private void CheckInput(InputCommand CurrentInputCommand)
  {
    // Only handle new input if player character has come to a complete stop
    if (Event.current.Equals(Event.KeyboardEvent(CurrentInputCommand.GetKeyAsString()))
        && CurrentCharacter.ForwardSpeed == 0)
    {
      CurrentInputCommand.Execute(CurrentCharacter);
    }
  }

  private InputCommand UpCommand;
  private InputCommand DownCommand;
  private InputCommand RightCommand;
  private InputCommand LeftCommand;

  private PlayerCharacter CurrentCharacter
}

There'd obviously be a few getters and setters up there for testing and flexibility but they seemed a distraction so I ignored them for this.

The interface itself only needs two methods, one to do the thing when the key is pressed to the player, and one to provide easy access to a string representation of the key.

interface InputCommand
{
  public void Execute(PlayerCharacter CharacterToManipulate);
  public string GetKeyAsString();
}

Then an Implementation of the InputCommand might look something like this

public class InputCommandUp : InputCommand
{
  public void InputCommandUp(string InInputKey)
  {
    InputKey = InInputKey;
  }

  public void Execute(PlayerCharacter CharacterToManipulate)
  {
    if (CharacterToManipulate.ExceedsMaxLevelHeight())
    {
      CharacterToManipulate.MoveObjectTo(CharacterToManipulate.transform,
        new Vector3(CharacterToManipulate.transform.position.x, 
                    CharacterToManipulate.transform.position.y + 12, 
                    CharacterToManipulate.transform.position.z + 10), 
        CharacterToManipulate.movementSpeed);

      CharacterToManipulate.level += 1;
    }

    }
  }

  public string GetKeyAsString()
  {
    return InputKey;
  }

  private string InputKey;
}

Hopefully that'll be of some help to you! (Also I recommend reading the other programming patterns in that book as well, they can be pretty handy in a pinch).

Any questions/complete misunderstandings of the problem give me a shout.

\$\endgroup\$
0
\$\begingroup\$

What about using a timespan? I'm not sure if u can access it in unity but maybe.

Private previousFireTime As TimeSpan
Private fireTime As TimeSpan = TimeSpan.FromSeconds(0.18F)  


If GameTime.TotalGameTime - previousFireTime > fireTime Then
DoStuff() //do your stuff
previousFireTime = GameTime.TotalGameTime
End If 

GameTime.TotalGameTime is just the total running time of your game.

\$\endgroup\$
2
  • \$\begingroup\$ It's not exactly what I was looking for, and it wouldn't work in unity, but thanks anyways. \$\endgroup\$
    – alexo1001
    Nov 13 '16 at 4:50
  • 1
    \$\begingroup\$ @alexo1001 This works just fine in Unity. We'd just use Time.time. If you wanted to go this route, I'd be inclined to phrase it as nextInputAllowed = Time.time + movementDuration each time you perform a move, then check if(Time.time >= nextInputAllowed) before reading further movement commands in Update. \$\endgroup\$
    – DMGregory
    Nov 13 '16 at 16:35
0
\$\begingroup\$

I wrote this script with simple booleans in the right place to do what I wanted it to do:

public bool isMoving = false;

void Update () {

    zPosition = (int)this.transform.position.z;
    if (zPosition >= 0) {
        ScoreText.GetComponent<Text>().text = "Score: " + zPosition;
    }


    transform.Translate(Vector3.forward * ForwardSpeed * Time.deltaTime);

    if (Input.GetKeyDown (KeyCode.A) && !isDead && transform.position.x != -36) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x - 12, this.transform.position.y, this.transform.position.z + 10), movementSpeed);
        //side -= 1;
    } else if (Input.GetKeyDown (KeyCode.D) && !isDead && transform.position.x != 36) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x + 12, this.transform.position.y, this.transform.position.z + 10), movementSpeed);
        //side += 1;
    }

    if (Input.GetKeyDown (KeyCode.W) && !isDead && transform.position.y != 12) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x, this.transform.position.y + 12, this.transform.position.z + 10), movementSpeed);
        //level += 1;
    } else if (Input.GetKeyDown (KeyCode.S) && !isDead && transform.position.y != 0) {
        MoveObjectTo(this.transform, new Vector3(this.transform.position.x, this.transform.position.y - 12, this.transform.position.z + 10), movementSpeed);
        //level -= 1;
    }

    if (Input.GetKeyDown (KeyCode.R) || Input.GetKeyDown(KeyCode.Space)) {
        SceneManager.LoadScene ("Scene1");
        Time.timeScale = 1;
    }
}


private void MoveObjectTo(Transform objectToMove, Vector3 targetPosition, float moveSpeed)
{
    //Don't do anything Object is already moving
    if (isMoving)
    {
        return;
    }
    //If not moving set isMoving to true
    isMoving = true;
    StartCoroutine(MoveObject(objectToMove, targetPosition, moveSpeed));
    StopCoroutine(MoveObject(objectToMove, targetPosition, moveSpeed));
}

public IEnumerator MoveObject(Transform objectToMove, Vector3 targetPosition, float moveSpeed)
{
    float currentProgress = 0;
    Vector3 cashedObjectPosition = objectToMove.transform.position;

    while (currentProgress <= 1)
    {
        currentProgress += moveSpeed * Time.deltaTime;

        objectToMove.position = Vector3.Lerp(cashedObjectPosition, targetPosition, currentProgress);

        yield return null;
    }
    //Done moving. Set isMoving to false
    isMoving = false;
}
\$\endgroup\$
0
\$\begingroup\$

In my sinple games I have two integer variables: forward and right. Every loop these variables are reset to 0. If W is pressed 1 is added to forward. If S is pressed 1 is subtracted from forward. Then in your case I would add 12 times forward as the vector in moveObject. I would do similarly with the right variable. Keep in mind that using this method of movement means going forward and sideways at the same time moves faster than going forward, because you are going forward 12 and sideways 12. (12 squared plus 12 squared = a number more than 12)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.