Mandatory Assignment – making a warlock game

Written by Christoffer Jepsen, Thomas Petersen and Miki Møller.

Project description:

The idea with this project is to re-create the game from warcraft 3 called “Warlocks”, in which we focus on the usage of joystick and a numpads, whilst also support 2 player functions.

The joystick is meant to be your aiming controller and the numpad is your movement and fire pad. It will be possible to read about how we approached this issue and what solutions we came up with on the run.

Initial setup: http://imgur.com/a/GbYqQ (4x pictures of our setup)

We had to cram down 2x numpads, 2x joysticks on the raspberry pi. A potential problem we thought we would dump into was that their was

only 2x 5v spots, in turned out that it did not matter if it was a 5V spot or 3,3V spot, it did not affect the project.

 

If we take a look at picture 5 you will see how we connected the connector with the joysticks. The different ports determines which bus we read on. The joysticks is also connected with ground and volt.

The numpads are connected with 8 different ports each, you might feel like you are running out of space and that it is messy, but just keep your head cool and you should be able to clean it up and understand where which port goes. The numpad number one (picture 4 green circle) is correctly connected, but numpad number 2(picture 4 blue circle) does not seem to be working completely correctly, since the first row with 1,4,7,* does not work, we worked around this by coding around it, but please do be mindful of this.

Picture 1-3 are mostly meant as a reference point in which you can see how the full setup is when put together.

Coding in Unity with C#

To visualize the player we use the unity3D Engine. As for scripting in unity we use C#.

In our unity view we got an empty game object with the UdpServer.cs. Here we handle incoming connections. The incoming message is split using the substring method. And is handled with a switch statement to set the values for the game. Here is a snippet of the part handing player one and two’s X and Y on joysticks direction. The subtract part is to calibrate to fit the joysticks default position if its not moved.

string messageSubstring = message.Substring(0, 2);
                switch (messageSubstring)
                {
                    case "1X":
                        MovePlayer1.P1XAimDir = int.Parse(message.Substring(2)) - 81;
                        break;
                    case "1Y":
                        MovePlayer1.P1YAimDir = int.Parse(message.Substring(2)) - 86;
                        break;
                    case "2X":
                        MovePlayer2.P2XAimDir = int.Parse(message.Substring(2)) - 123;
                        break;
                    case "2Y":
                        MovePlayer2.P2YAimDir = int.Parse(message.Substring(2)) - 133;
                        break;
                    default:
                        Debug.Log("Invalid syntax sent, this was the message: "+ messageSubstring);
                        break;
                }

 

We got a game object with a player1 with a player1.cs script and equally for player two. They both contain a lot of static variables containing everything about this player like if its keypad is pressed up or not.

To show which way the player is facing we’ve attached a child object with the model of an arrow to the player parent so when it rotates it rotates with it.

To get the facing we make a vector using the joystick input, we already calibrated it to have a default value that is close to zero.

Then we rotate the player

  P1CurrentFacing = new Vector3(P1XAimDir, 0, P1YAimDir);
        transform.rotation = Quaternion.LookRotation(P1CurrentFacing);

 

To move the player we do as follows. This is if the player moves up that is the 2 on the keypad is pressed. We use translate to this and use the build in  vector for up called forward. And then we times it with the  basespeed which Is the speed we want it to move with. The Space.World perametere is important to say it translates according to its universal position in the game and not its local.

  if (p1w == true)
        {
            transform.Translate(Vector3.forward * Time.deltaTime * baseSpeed,Space.World);   
        }

 

To shoot the fireball we have another if statement. It instantiates a new game object of our fireballprefab with our player’s initial position and facing

The fireball has its own script to move and check if it hits

When someone is hit we cannot use a normal loop as that would halt the entire project and move the player in one frame but we have something called Coroutine to use this we make a IEnumerator function and start it using the StartCoroutine. It runs the code but when it reaches “yield return null;” it stops its execution for this frame and continues from were it left in the next. 

Main.py

Imports and variables

We import different functionality.

  • Time, this is used in our case to do delays to control how often something is sent or registered.
  • Socket, this is used for the udp server. Without this we can’t sent or receive data.
  • GPIO as GPIO, this is what controls the inputs on the RaspberryPi, and allows us to see and change inputs/outputs on the pins.
  • Matrixkey is a method we built ourselves, and I will be going into detail about it later in this document.
  • From smbus import SMBus, this Is used to read and write to the GPIO ports.

 

UDP_IP is just an ip address.

UDP_PORT is the port we use.

UDP_INFO is the two above combined

Sock is the socket.

def Main()

This function is used as the entry point for the program.

def GetPInput()

In this function we listen for inputs on the different pins we use.

        if player == "1": #Player one
                #Get Y-Axis
                bus.write_byte(0x48, 0x03)
                bus.read_byte(0x48)
                ValueY = bus.read_byte(0x48)
                #Get X-Axis
                bus.write_byte(0x48, 0x01)
                bus.read_byte(0x48)
                ValueX = bus.read_byte(0x48)
                #Keypad         
                v = MatrixKey.KeypadInput()
                print(v)

The if is to see whether we are looking for the player one or player two inputs. Bus.write.byte is to determine what input we are looking for. Then we look at the input and put this into the ValueY variable. The same is done for the ValueX. The MatrixKey is to look at the keypad input, this will be looked at later. Last we print what the MatrixKey value is.

    else:
                print("Player number out of range.")

This is just for errorhandling. Not really that important.

        ReturnAllValues = [player+"X"+str(ValueX), player+"Y"+str(ValueY), player+'K'+str(v)]
        return ReturnAllValues

This takes all the information and creates one variable with all of it in one. Then it adds some protocol letters so the recipient of the message can distinguish between player one and two.

def UdpServer():
        #The try is to make sure we don't hog all the ports, and close the connection in a 'nice way'.
        try:
                print("Server Started")
                while True:
                        #Player 1 is "1" and player 2 is "2", quite simple.
                        playerOne = GetPInput("1")
                        playerTwo = GetPInput("2")
                        #time.sleep(0.5)
                        for x in range(0, len(playerOne)):
                                time.sleep(0.05)
                                sock.sendto(bytes(playerOne[x].encode()), (UDP_IP, UDP_PORT))
                        #time.sleep(0.1)
                        for x in range(0, len(playerTwo)):
                                time.sleep(0.05)
                                sock.sendto(bytes(playerTwo[x].encode()), (UDP_IP, UDP_PORT))
                                print(playerTwo[x])

        except KeyboardInterrupt:
                print("Connection Closed")
                sock.close()

This is the udp server. This works by trying to send the data from the GetPInput with the parameter determining what player we want to see the input from. Then we send the bytes from the players over udp to the unity server. This all happens in a while loop to make sure it keeps happening. We have made an exception that handles when you do keyboard interruptions because we wanted to be able to shut down the service.

if __name__=="__main__":
        Main()

 

This is to make sure the Main() function is called.

def KeypadInput()

        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        MATRIX = [ [1,2,3,'A'],
                 [4,5,6,'B'],
                 [7,8,9,'C'],
                 ['*',0,'#','D'] ]

        ROW = [5,6,13,19]
        COL = [26,16,20,21]

In this function we start out by setting up a couple of variables and settings. The GPIO.setmode determines whether we are looking at all the pins or some specific pins.

The matrix is the keypad setup.

The ROW and COL are determining what pins are used for what row and what column on the keypad.

for j in range(4) :
                GPIO.setup(COL[j], GPIO.OUT)
                GPIO.output(COL[j], 1)

        for i in range(4) :
                GPIO.setup(ROW[i], GPIO.IN, pull_up_down = GPIO.PUD_UP)


        try:
                for j in range(4) :
                        GPIO.output(COL[j],0)
                        for i in range(4) :
                                if GPIO.input(ROW[i]) == 0:
                                        return (str(MATRIX[i][j]))
                                        while(GPIO.input(ROW[i]) == 0) :
                                                pass
                GPIO.output(COL[j],1)
        except KeyboardInterrupt:
                GPIO.cleanup()
        finally:
                GPIO.cleanup()

Now we get to the code that register the buttons that are being pressed. The first for loop sets up the pins. The following for loops looks at what rows and columns are being pressed, this way it can calculate what button and thus what char is being pressed.

 

Finally we have som keyboardinterrupt to make sure it cleans up after use.