Javarevisited
Published in

Javarevisited

How You Can Code Pac-Man In 2 Weeks Using Java

A step-by-step guide on tackling coding Pac-Man from scratch despite never having programmed a game before

Photo by Barbara Zandoval on unsplash.com

There is no better way to learn Object Oriented Programming in Java than to code a game.

It teaches you how to create classes and the importance of the abstraction of code to reduce redundancy. You learn how to structure a large application and the importance of making testcases. But most importantly, you learn to code in a fun and engaging way.

If you’d like to code alongside this article, you can download the project specifications and assets here.

Prerequisites

  • A knowledge of control flow (if, else, while, for, for each)
  • An understanding of classes (instance and static methods/attributes)
  • An understanding of inheritance
  • A PhD in Googling

How You Can Get Started

The code structure of all games have common thematic ideas

  • They all have a main run loop where the game has “ticks” (a measure of time in games) as well as draws to the screen
  • They all have a main Game class where the game attributes are stored eg. lives, number of fruits remaining
  • They use inheritance to minimize redundant code. For example, a GameObject class can act as the parent of all the objects drawn to the screen (pacman, ghosts, fruit and walls)

When programming a game, it’s important have a plan of how you expect your code to be structured. Every piece of code should be written with deliberate intent — a lack of plan will result in spaghetti code and will be a headache to debug. Spending a couple minutes planning now will save you hours in debugging.

Thus, read the project specifications, create a class hierarchy (UML diagram) and create a list of events that details how you want your program to execute once run. Breaking down the game into several discrete steps is crucial in scoping the game and figuring out how your code fits together.

Include what functions are called, a brief description of how they work, and what objects will be modified— you want to end up with a logical and ordered chain of events from start to finish.

Being systematic is key in programming games.

Photo by Kirill Sharkovski on Unsplash

A broad overview of how the program should run

  1. Running the program initialises an instance of the App class. settings() and setup()are automatically called, creating the game window and inistialising a Game object.
  2. The game settings JSON file is parsed as well as map.txt, which creates 2d matrix of GameObjects, storing their respective sprites and position. GameObject will be the parent type of all objects drawn to the screen
  3. All the sprites are drawn to the screen in draw()
  4. The key presses are recorded and the pacman is moved
  5. The entities for the pacman and the ghosts are “ticked”, checking for collision with each other, walls and fruit
  6. The ghosts are given individual behaviour and switch between a “scatter” and “chase” state at regular intervals
  7. The game ends if the pacman loses 3 lives or eats up all the fruit. WIN/LOSE is drawn to the screen
Class Hierarchy / UML Diagram for Pacman

The UML diagram above should be used as a guide, but feel free to make your own changes.

Try not to be overwhelmed by the scale of this project. What helped me cope was breaking up the whole project into small step. This meant that my goals were small and easy to complete, allowing me to snowball my small victories into a completing the game.

1. Create the App class

Start with writing the main loop. You will see from the project specifications, we are using PApplet and PImage. Both have extensive javadocs and helpful javadocs. PApplet will be used to create the main window for the game whereas PImage will be used to load the sprites for the entities.

Create a window to draw the sprites to the screen

PApplet will have the functions setup()and settings(). The scaffold already has some of it filled out, so you should be focusing on creating the necessary objects.

The Game should be initialised as soon as possible, ideally when the App() constructor is called. The JSON file needs to be parsed before loading map.txt, as it contains all the game settings ie. number of lives, entity speed.

Explanation of the code. Upon running App.java, the constructor App() will be called, creating an instance of the Game. Then, setup() is automatically called, setting the frameRate to 60fps. The JSON file containing the settings eg. lives, length of scatter mode, is parsed and the game is loaded from map.txt, which contains the information as to how the map is laid out.

Create the main game loop, draw().

From the PApplet documentation, you will see that this function is called once each frame — in this case, 60 times a second.

In this loop, you will need to draw the entities to the screen and have tick() functions for each entity, checking for collisions between pacman, ghosts and walls. You will need to add a check for win conditions later down the track eg. if (this.game.fruits == 0) { // do something } .

For draw(), I recommend having a look at the javadocs, especially at app.loadImage(spriteFilepath) which returns a PImage object and app.image(PImage, x, y) which renders the PImage to the screen at the specified position.

Note that according to the project specifications, pacman should alternate between an open and close mouthed sprite.

Explanation of the code. background(0,0,0); is responsible for refreshing the screen by drawing a black screen over the previous frames. draw() uses functions from PApplet and PImage

The tick() function will be covered in more depth in a later article but briefly, it is responsible for the following actions

  1. Check if the entity is still alive
  2. (Ghost) Check the current state of the game, eg. scatter or chase
  3. (Ghost) Choose a target location depending on the current state
  4. (Pacman) Take in the last keypress as a move
  5. Queue a move for the entity
  6. Check for potential collision
  7. If there is no collision, move the GameObject and update the sprite position based on the last move. Do not execute the move if it would cause a collision

draw() uses app.image() from PApplet to draw the sprite to the screen.

2. Parse the JSON settings file and map.txt

There are two files that need to be read, the first is a JSON file that has all the settings for the game, such as the number of lives, the speed of the entities, the duration of specific phases.

This particular resource is helpful in learning how to parse JSON files.

Explanation of the code. This piece of code creates a JSONParser object which is allows us to read into JSON files. The specific fields ie. map, lives, speed were all stored as instance attributes within the Game class. If there are any errors, they will be caught and the execution of the program will halt.

Once the JSON file has been parsed, the game map needs to be loaded using loadGame(). A common programming idea when coding simple games is storing all GameObjects in a list of lists (2d grid). Storing objects as a 2d grid means that each element has a specific position that can be linked to an x, y coordinate system.

Photo from programiz.com

Each GameObject should store at least the Sprite, x, y and corresponding character from map.txt.

Explanation of the code. A2d matrix of GameObjectsis created ie. an array list of array lists containing GameObjects. Each GameObject stores the sprite, location as well as the corresponding character from map.txt.

map.txt is read line by line and each character is iterated through. For each value encountered, a HashMap is used to pair the character to its corresponding filepath for the sprite. For example “g” maps to “src/main/resources/ghost.png” . PApplet is used to convert the filePath into a PImage sprite.

Each sprite is 16x16 units large, hence why x and y are incremented by 16 each iteration of the loop.

As each object has different behaviour, so they are all handled slightly differently

If the cell is a player (“p”), then a player object is created and assigned as an attribute to the game. This allows for the game to easily and directly interact with the player object without needing to find it in the 2d matrix each time.

If the cell is a ghost (“a” or “c” or “i” or “w”), then a ghost object is created and added to a list of ghosts. Adding entities to a list (this is also done with walls) makes it easier to check for entity collisions. This can be checked by iterating through the list of ghosts and walls each tick and checking to see if the pixels overlap, hence indicating collision.

If the cell is a fruit, then you can give each GameObject the instance attribute isFruit, setting it to true in this case. You can set up a check in the player tick() such that if the player passes over an object with isFruit == true, then the player eats it up and the sprite disappears. Keep a tally of the total number of fruits so that you can check for a win condition, ie. if this.fruits == 0, then all the fruits have been eaten and the player has won.

Summary

By now, you have achieved 3 major things

  1. App.java — You have set up the main run loop of the game
  2. Game.java — You have loaded in the game settings from the JSON file
  3. Game.java — You have iterated through the map.txt file, getting the corresponding characters, finding what sprite that corresponds to, initializing a GameObject with the sprite and location, and storing all the GameObjectsin a 2d matrix

You should be able to see all the entities on the screen now!

Thank you for reading! Please show your support if you’d like me to continue this series.

The current plan for the series is as follows:

  • Part 2 — how to code pacman and ghost movement
  • Part 3 — how to code the detection of pixel collision
  • Part 4 — how to code the individual ghost behaviour

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store