Week 8: Serial Communication Between Arduino and Unity
Inspired by the possibilities I saw using p5.js, I thought I would try serial communication between the Arduino and Unity. I found this tutorial which helped me get started, but there were some missing pieces I had to add to accomplish my goal, which was to use four pushbuttons to maneuver a character up, down, left, right, and diagonally.
The Arduino side isn't complicated: I needed a strategy for sending relevant information to Unity so that the character would move only for as long as I pressed the pushbuttons. At first, I sent single bytes with Serial.write()
corresponding to the directions I wanted the character to move. But I learned I risked overflowing the buffer because I was writing a byte every loop as long as I was pressing the corresponding button.
On the Unity side, I came across a peculiar problem as well. While I was able to read the bytes from the Arduino and convert them into character movements, I had trouble moving the character at a predictable speed. In Unity, it's typical to increment the position of an object in response to player commands using something like yPos += speed * Time.deltaTime
, where Time.deltaTime
is the time that passes between each frame in a single second. In other words, if the game runs at 60fps, Time.deltaTime
is 1/60s.
But in my code, I was updating the position of the character every frame, and for every byte read by the buffer. In other words, given a buffer either filled with hundreds of bytes that each increment position or nothing at all, it was difficult to scale the speed
parameter appropriately.
Handshaking seemed to fix issues on both sides. I moved the button digital reads inside a conditional checking Serial.available() > 0
on the Arduino, and added a ping function that writes a byte to serial in the beginning of each Update()
loop in Unity. With these additions, the frequency at which the buffer is filled and read is controlled to the rate which the frame updates, meaning I can use Time.deltaTime
and speed
to scale movement.
Another improvement I tried making after discussing with Tom was to deliver the button states in 4-byte arrays and calculating the appropriate movement in Unity. This way, I can better control what happens when the user presses, for example, the up and down buttons at the same time. Instead of privileging one direction over the other, I can just "cancel" the two opposing inputs out.
The Arduino code and an excerpt of the Unity code are shared below.
int upButton = 2;
int downButton = 5;
int rightButton = 4;
int leftButton = 3;
void setup() {
Serial.begin(9600);
pinMode(upButton, INPUT);
pinMode(downButton, INPUT);
pinMode(rightButton, INPUT);
pinMode(leftButton, INPUT);
}
void loop() {
if (Serial.available() > 0) {
int inByte = Serial.read();
char commands[4];
if (digitalRead(upButton) == HIGH) commands[0] = 'W'; else commands[0] = 'O';
if (digitalRead(downButton) == HIGH) commands[1] = 'S'; else commands[1] = 'O';
if (digitalRead(rightButton) == HIGH) commands[2] = 'D'; else commands[2] = 'O';
if (digitalRead(leftButton) == HIGH) commands[3] = 'A'; else commands[3] = 'O';
Serial.write(commands);
}
}
private void Update()
{
if (_serial != null && _serial.IsOpen)
{
Ping(); // write a single char to serial
int bytesToRead = _serial.BytesToRead;
if (bytesToRead >= 4) // only do things if receiving at least a full 4-byte array
{
byte[] buff = new byte[bytesToRead];
int read = _serial.Read(buff, 0, bytesToRead);
if (read > 0)
{
for (int i = 0; i < buff.Length - 4 ; i+=4)
{
Vector2 direction = new Vector2(0, 0);
// if opposing direction buttons are pressed
// the value in that axis is 0
if (buff[i] == 'W') { direction.y += 1; }
if (buff[i+1] == 'S') { direction.y -= 1; }
if (buff[i+2] == 'D') { direction.x += 1; }
if (buff[i+3] == 'A') { direction.x -= 1; }
direction.Normalize(); // normalize the x, y directions
yPos += direction.y * speed * Time.deltaTime;
xPos += direction.x * speed * Time.deltaTime;
// restrict the player to the screen
player.transform.position = new Vector2(Mathf.Clamp(xPos, -8f, 8f), Mathf.Clamp(yPos, -4f, 4f));
}
}
}
}
}