Introduction
In this post, we are going to look at creating some form fields (with a dive into more object oriented methods) , which have been used to create a menu and an options screen to change some of the settings.
Getting Started
Recommended
I recommend that you go through the previous posts for this project, although completing the Arduino part of the project is not essential.
The Code
Download
Processing
What’s New
The first thing I wanted to do in this stage was to move some of the game operations away from pronguino sketch into a new class Game. Also added a starting Menu, which allows us to change Options before we play.
File | Description |
---|---|
Game | Class to encapsulate game operations. – Options – State – Begin – Pause – Resume |
Form | Contains a number of form control classes – Frame – ToggleControl – InputControl – SliderControl – ButtonControl |
Menu | Class to encapsulate menu operation – Play – Options – Exit – Status Message |
What’s Changed
Most of the files have had a minor change due to moving Options into Game as a child property, below are the significant changes …
File | Description |
---|---|
pronguino | Implemented new Game class. Implemented new Menu class Handle serial errors as status messages Added further mouse/keyboard events Added game events |
Ball | Added applyOptions() method |
Constants | Added new menu/option states Added new status message colours Add new colours constants |
Debug | Cleared constructor |
Options | Added form controls for options Add file handling for options file Add various methods/events |
Paddle | Added applyOptions() method |
Player | Added name propertyAdded applyOptions() methodAdded serve() method |
Scoreboard | Remove score propertyAdded player propertyDisplay player.score on scoreboardDisplay player.name above scoreboard |
Challenge: I have started adding TODO’s at top of some of the files, adding items I’d like to do, should do, or may do in the future. Sometimes when I am putting the code together for these posts it is easy to get carried away, but I have find the cut-off point, so feel free to scan these and have a go yourself.
Play
The rules haven’t change, keys and controls are still the same, now you can select Options from the Menu and change your name(s), set speed, or switch on debugging without changing the code!
Forms
When a Processing application starts it runs setup()
once, and then runs the code continuously in the loop draw()
method, where the objects get displayed. With two new “forms” being added I have added some further state constants ….
...
static final int STATE_MENU = 5; // Game menu showing
static final int STATE_OPTIONS = 6; // Game options showing
...
… which get set accordingly … at the start …
void setup()
{
...
...
game.state = Constants.STATE_MENU;
...
}
… and when Options button clicked …
void optionsButtonClicked() {
game.state = Constants.STATE_OPTIONS;
}
.. and these are used to determine what gets displayed during draw()
method …
// Display ..
switch(game.state) {
case Constants.STATE_MENU:
background(game.options.backgroundColour);
menu.display();
break;
case Constants.STATE_OPTIONS:
background(game.options.backgroundColour);
game.options.display();
break;
default:
...
// Game Objects
...
break;
}
In preparation for adding controls onto the form, we need to know when certain events happen while the form is shown, these events can only be triggered in the main sketch (pronguino) and need to be cascaded down to the form … i.e.
void mouseClicked() {
...
...
if (game.state == Constants.STATE_MENU) {
// Cascade down
menu.clicked();
}
}
.. and so the clicked()
method is called in Menu class … it is in this method, the form still doesn’t know what has been clicked so checks each controls clicked()
method to identify, and act accordingly.
void clicked() {
...
// Check if options button clicked ..
if(this.optionsButton.clicked()) {
// Call event method
optionsButtonClicked();
}
...
}
If any of the controls used on a form have the validate()
method and valid
property implemented, a valid()
method can be added to check all the relevant controls, which in this case is used to disabled the OK button on the options form, to prevent invalid data being saved …
boolean valid() {
// Currently only input controls need validation ...
for (int id=0; id < Constants.PLAYER_COUNT; id++) {
// ... this will return false if any of the controls are invalid
if (!this.playerNameInputs[id].valid || !this.playerUpInputs[id].valid || !this.playerDownInputs[id].valid) {
return false;
}
}
// returns true on completion of loop, nothing invalid
return true;
}
Controls
Processing is great for fast visual imagery and for games too, sometimes though you need to be able to interact with an application to adjust settings or enter data etc. There are a number of libraries available for such form controls, these would have been written in Java (which is what Processing language is based on).
As an exercise I decided to put some together using the “out-of-the-box” language, (will have a look at building own Java library another time) this allows me to further explore keyboard and mouse events, graphic/text combinations, and is also a good example to demonstrate some object-oriented methods.
Control | Description | Enabled | Disabled |
---|---|---|---|
Button | Simple push button. | ||
Input | Simple text entry. | ||
Toggle | Used for true/false values | ||
Slider | Choose value within a range by dragging slider along gauge. | ||
Frame | Used to group related controls / text. | N/A |
In this section will look into behavior and drawing of the controls, a further look into how they are put together will be covered more in Object-Orientation …
All the controls that extend the Control class will have the following properties … although not all are necessarily used …
Property | Type | Description |
---|---|---|
x | float | X co-ordinate |
y | float | Y co-ordinate |
w | float | Width |
h | float | Height |
label | String | Label |
hasFocus | boolean | Has control got focus |
disabled | boolean | Is control disabled |
Also all the controls will share common functionality through these Control methods … although not all are necessarily called … and some may even override implementation …
Method | Returns | Description |
---|---|---|
over() | boolean | Returns true if the mouse cursor is over the control. |
display() | void | Draws the control. |
clicked() | boolean | Returns true if the mouse button is clicked over the control. |
Button
… the ButtonControl requires an additional property …
Property | Type | Description |
---|---|---|
caption | String | text displayed on button |
.. and overrides the following method from Control …
Method | Returns | Description |
---|---|---|
display() | void | Draws the button |
After declaring the ButtonControl, we create it in the containing “form” class … in this case Options…
...
ButtonControl okButton;
...
this.okButton = new ButtonControl(305,450,75,25,"OK");
...
… the additional property is set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
class ButtonControl extends Control {
...
String caption;
...
ButtonControl(float x, float y, float w, float h, String caption) {
// Call the constructor of the superclass
super(x, y, w, h);
// Sets own properties
this.caption = caption;
}
...
}
When the mouseClicked()
event is triggered in the pronguino sketch, and the state
is set to STATE_OPTIONS
, the following method is called in the Options class …
void clicked() {
...
if(this.okButton.clicked()) {
this.okButtonClicked();
}
...
}
.. which will then check with the following method in the Control class whether this control has been clicked()
…
boolean clicked() {
this.hasFocus = this.over();
return this.hasFocus;
}
.. it will do this by calling the over()
method in the Control class, which will check to see if the mouseX
and mouseY
are within the boundary of the control, ignoring this check if control disabled…
boolean over() {
if(this.disabled) {
return false;
}
if (mouseX >= this.x && mouseX <= this.x+this.w &&
mouseY >= this.y && mouseY <= this.y+this.h) {
return true;
} else {
return false;
}
}
When the button gets drawn, it uses the disabled
property and over()
method to determine which colours to use, one notable method begin used here is textAlign()
… it tells the following text()
method how to interpret the x, y parameters .. in this case the CENTER
point is in the middle of the text.
void display() {
// Border colour depending on disabled state
stroke(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
// Draw button, colour depending on whether mouse over
fill(this.over() ? Constants.CONTROL_COLOUR_ENABLED : Constants.CONTROL_COLOUR_BACKGROUND);
rectMode(CORNER);
rect(this.x, this.y, this.w, this.h, 5);
// Text, colour depending on disabled state / or mouse over
fill(this.over() ? Constants.CONTROL_COLOUR_BACKGROUND : this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
textAlign(CENTER,CENTER);
text(this.caption, this.x+(this.w/2), this.y+(this.h/2)-2);
}
Input
… the InputControl requires additional properties …
Property | Type | Description |
---|---|---|
value | String | Value being input/edited |
maxLength | float | Maximum length of value |
valid | boolean | Is value a valid input |
… and the following additional methods …
Method | Returns | Description |
---|---|---|
value(String) | void | Sets the value property with a String |
value(char) | void | Sets the value property with a String converted from a char |
validate() | void | Sets the valid property |
typed(char) | void | Event method called when key typed. |
.. and overrides the following method from Control …
Method | Returns | Description |
---|---|---|
display() | void | Draws the input control |
After declaring the InputControl, in this case an array, we create it in the containing “form” class … in this case Options…
...
InputControl[] playerNameInputs;
...
this.playerNameInputs[Constants.PLAYER_ONE] = new InputControl(450,120,200,20, "Name", 10, this.names[Constants.PLAYER_ONE]);
...
… with the exception of valid
, … the additional properties are set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
InputControl(float x, float y, float w, float h, String label, int maxLength, String value) {
super(x, y, w, h, label);
this.maxLength = maxLength;
this.value(value);
}
… a setter method value(...)
is used to set the value
…
void value(String value) {
this.value = value;
this.validate();
}
… which also calls the validate()
method, which sets the valid
property accordingly …
void validate() {
this.valid = (this.value.length() > 0);
}
When the mouseClicked()
event is triggered in the pronguino sketch, and the state
is set to STATE_OPTIONS
, the following method is called in the Options class …
void clicked() {
...
for (int id=0; id < Constants.PLAYER_COUNT; id++) {
this.playerNameInputs[id].clicked();
...
}
...
}
.. which will then check with the following method in the Control class whether this control has been clicked()
… however, note how this method is called slightly differently, we are not interested in the result of method, as we don’t have to do anything at this level.
Note: This is a good way to get around the fact that the following method implementations are not allowed i.e. two methods of the same name that have different return types …
void clicked() { ... }
boolean clicked() { ... }
.. as with ButtonControl the clicked()
method in Control class will get called .. however, this time we are more interested in the hasFocus
property …
boolean clicked() {
this.hasFocus = this.over();
return this.hasFocus;
}
When the keyTyped()
event is triggered in the pronguino sketch, and the state
is set to STATE_OPTIONS
, the following method is called in the Options class …
void typed(char typedKey) {
...
for (int id=0; id < Constants.PLAYER_COUNT; id++) {
this.playerNameInputs[id].typed(typedKey);
...
}
}
.. which will then call typed(char typedKey)
in the InputControl class, if the control has focus, adds typedKey
to value, checking the maxLength
, or if BACKSPACE
is typed, removes last character from value, continuously checking if value is valid…
void typed(char typedKey) {
if(this.hasFocus) {
if(typedKey==BACKSPACE && this.value.length() > 0) {
this.value = this.value.substring(0, this.value.length()-1);
} else {
if(this.value.length() < this.maxLength && typedKey != BACKSPACE) {
this.value += typedKey;
}
}
}
this.validate();
}
Using basic drawing methods, coloured based on disabled
and valid
properties, note when drawing the value, adds a _ afterwards when focused, acting as a cursor…
void display() {
// Label
fill(Constants.CONTROL_COLOUR_LABEL);
textAlign(RIGHT, TOP);
text(this.label, this.x, this.y);
// Border
if(this.valid) {
stroke(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
} else {
stroke(Constants.CONTROL_COLOUR_INVALID);
}
// Input area
fill(Constants.CONTROL_COLOUR_BACKGROUND);
rectMode(CORNER);
rect(this.x, this.y, this.w, this.h, 5);
// Value
fill(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
textAlign(LEFT,CENTER);
// Add a 'cursor' after value if it has focus
text(this.value + (this.hasFocus ? '_' : ""), this.x+10, this.y+(this.h/2)-1);
}
Challenge: Have a go at either amending or extending the InputControl, to allow only number relating characters i.e. 0-9, ‘.’, ‘-‘ etc, extending is probably the better option, then you don’t ‘break’ InputControl .. just create a new one called say ‘NumberInputControl’ ? You could add a FrameControl titled ‘Net’ and put all the net related options here ?
Toggle
… the ToggleControl requires an additional property …
Property | Type | Description |
---|---|---|
value | boolean | On / Off |
… and the following additional method …
Method | Returns | Description |
---|---|---|
value(boolean) | void | Sets the value property with a boolean |
.. and overrides the following methods from Control …
Method | Returns | Description |
---|---|---|
clicked() | boolean | Returns true if the mouse button is clicked over the control. |
display() | void | Draws the toggle control |
After declaring the ToggleControl, we create it in the containing “form” class … in this case Options…
...
ToggleControl debugEnabledToggle;
...
this.debugEnabledToggle = new ToggleControl(220,120,40,20, "Enabled", this.debug.enabled);
… the additional property is set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
ToggleControl(float x, float y, float w, float h, String label, boolean value) {
super(x, y, w, h, label);
this.value(value);
}
… a setter method value(...)
is used to set the value
…
void value(boolean value) {
this.value = value;
}
When the mouseClicked()
event is triggered in the pronguino sketch, and the state
is set to STATE_OPTIONS
, the following method is called in the Options class …
void clicked() {
...
this.debugEnabledToggle.clicked();
...
}
.. when the clicked()
method in the ToggleControl class gets called .. first it checks with the superclass to see if control has been clicked, if it has, it uses the logical NOT operator to invert the current boolean value.
boolean clicked() {
if(super.clicked()) {
this.value(!this.value);
return true;
} else {
return false;
}
}
Using basic drawing methods, coloured based on disabled
, valid
and value
properties…
void display() {
// Label
fill(Constants.CONTROL_COLOUR_LABEL);
textAlign(RIGHT, TOP);
text(this.label, this.x, this.y);
// Outline
stroke(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
fill(Constants.CONTROL_COLOUR_BACKGROUND);
rectMode(CORNER);
rect(this.x, this.y, this.w, this.h, this.h/2);
// Switch
noStroke();
ellipseMode(CORNER);
if(this.value) {
fill(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ON);
ellipse(this.x+(this.w/2)+2, this.y+2, this.h-3, this.h-3);
} else {
fill(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_OFF);
ellipse(this.x+2, this.y+2, this.h-3, this.h-3);
}
}
Slider
… the SliderControl requires additional properties …
Property | Type | Description |
---|---|---|
value | float | Value being set |
minValue | float | Minimum value allowed. |
maxValue | float | Maximum value allowed. |
gaugeW | float | Width of ‘gauge’ in pixels |
sliderButton | SliderButtonControl | Slider button |
… and the following additional methods …
Method | Returns | Description |
---|---|---|
value(float) | void | Sets the value property with a float |
pressed() | void | Event method called when mouse button pressed. |
released() | void | Event method called when mouse button released. |
dragged() | void | Event method called when mouse button pressed and dragged. |
.. and overrides the following method from Control …
Method | Returns | Description |
---|---|---|
display() | void | Draws the slider control |
After declaring the SliderControl, we create it in the containing “form” class … in this case Options…
...
SliderControl ballSpeedSlider;
...
this.ballSpeedSlider = new SliderControl(140, 340, 30, 20, "Ball", 1.0, 10.0, this.ballSpeed);
...
… the additional properties are set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
SliderControl(float x, float y, float w, float h, String label, float minValue, float maxValue, float value) {
super(x, y, w, h, label);
this.minValue = minValue;
this.maxValue = maxValue;
this.value(value);
this.gaugeW = ((this.maxValue - this.minValue) / 2) * this.w;
this.sliderButton = new SliderButtonControl(this, map(this.value, this.minValue, this.maxValue, this.x+(this.w/2), this.x+this.gaugeW-(this.w/2)), this.y, this.w, this.h); }
… a setter method value(...)
is used to set the value
…
void value(float value) {
this.value = value;
}
When the mousePressed()
, mouseReleased()
or mouseDragged()
events are triggered in the pronguino sketch, and the state
is set to STATE_OPTIONS
, the following methods are called in the Options class …
void pressed() {
...
this.ballSpeedSlider.pressed();
...
}
void released() {
...
this.ballSpeedSlider.released();
...
}
void dragged() {
...
this.ballSpeedSlider.dragged();
...
}
.. which will then call the pressed()
, released()
or dragged()
respectively in the SliderControl class … as it its the child class SliderButtonControl that is more interested in these events happening the call is passed on …
void pressed() {
this.sliderButton.pressed();
}
void released() {
this.sliderButton.released();
}
void dragged() {
this.sliderButton.dragged();
}
.. and then the display()
method draws the control, note the call to the child sliderButton.display()
method.
void display() {
// Label
fill(Constants.CONTROL_COLOUR_LABEL);
textAlign(RIGHT, CENTER);
text(this.label, this.x, this.y);
// Gauge
stroke(this.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_ENABLED);
line(this.x, this.y, this.x+this.gaugeW, this.y);
// Slider Button
this.sliderButton.display();
}
SliderButtonControl
The SliderButtonControl is a child class of SliderControl, and only used by its parent to manage the buttons behaviour … it has no use outside of this …
class SliderControl extends Control {
private class SliderButtonControl extends Control {
// So we can access parent properties
SliderControl parent;
...
}
...
}
… the SliderButtonControl requires additional properties …
Property | Type | Description |
---|---|---|
grabbed | boolean | Set when mouse button pressed or released. |
parent | SliderControl | Access to parents properties i.e. value, x, y etc. |
… and the following additional methods …
Method | Returns | Description |
---|---|---|
pressed() | void | Event method called when mouse button pressed. |
released() | void | Event method called when mouse button released. |
dragged() | void | Event method called when mouse button pressed and dragged. |
.. and overrides the following methods from Control …
Method | Returns | Description |
---|---|---|
over() | boolean | True if mouse cursor over control. |
display() | void | Draws the slider control |
After declaring the SliderButtonControl, we create it in its parent class … SliderControl…
...
SliderButtonControl sliderButton;
...
this.sliderButton = new SliderButtonControl(this, map(this.value, this.minValue, this.maxValue, this.x+(this.w/2), this.x+this.gaugeW-(this.w/2)), this.y, this.w, this.h);
...
… the additional properties are set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
SliderButtonControl(SliderControl parent, float x, float y, float w, float h) {
super(x, y, w, h);
this.parent = parent;
this.grabbed = false;
}
.. when the pressed()
method in the SliderButtonControl class gets called .. the grabbed
flag gets set according to result of over()
…
void pressed() {
this.grabbed = this.over();
}
.. when the released()
method in the SliderButtonControl class gets called .. the grabbed
flag gets set to false …
void released() {
this.grabbed = false;
}
.. when the dragged()
method in the SliderButtonControl class gets called .. the x co-ord of the button gets continuously calculated by keeping the mouseX
value within the bounds of its parent, using the constrain()
method, and then calculates the new value for SliderControl, based on the new x co-ord …
void dragged() {
if(this.grabbed) {
this.x = constrain(mouseX, this.parent.x+(this.w/2), this.parent.x+this.parent.gaugeW-(this.w/2));
this.parent.value(round(map(this.x, this.parent.x+(this.w/2), this.parent.x+this.parent.gaugeW-(this.w/2), this.parent.minValue, this.parent.maxValue)));
}
}
… due to this controls x,y being at the center, a slightly different version of the over() method was required, so it gets overridden here …
boolean over() {
if(this.disabled) {
return false;
}
if (mouseX >= this.x-(this.w/2) && mouseX <= this.x+(this.w/2) &&
mouseY >= this.y-(this.h/2) && mouseY <= this.y+(this.h/2)) {
return true;
} else {
return false;
}
}
.. and then the display()
method draws the slider button …
void display() {
// Slider Button
fill(this.parent.disabled ? Constants.CONTROL_COLOUR_DISABLED : Constants.CONTROL_COLOUR_SLIDER);
rectMode(CENTER);
rect(this.x, this.y, this.w, this.h, this.h/2);
// Value
fill(this.parent.disabled ? Constants.CONTROL_COLOUR_BACKGROUND : Constants.CONTROL_COLOUR_ENABLED );
textAlign(CENTER, CENTER);
text(str(this.parent.value), this.x, this.y);
}
Challenge: Have a go at creating a new, more specific control to adjust the size of the ball, you could create a new file to add this into, called, say … GameForm, keeping Form to be used for generic type controls. You could call this control something like BallSliderControl, that extends SliderControl (also adding BallSliderButtonControl, which would be the ball itself), adding additional properties such as ballRadius, and overriding methods such as over() and display(), where, instead of showing value, change the size of ‘ball’ etc. You could also do something similar with the the paddleHeight, drawing the paddle as the ‘gauge’ ?
Frame
The FrameControl requires an additional property …
Property | Type | Description |
---|---|---|
title | String | Set when mouse button pressed or released. |
… no additional methods are required, just the following overridden method …
Method | Returns | Description |
---|---|---|
display() | void | Draws the frame control |
After declaring the FrameControl, we create it in the containing “form” class … in this case Options…
...
FrameControl debugFrame;
...
this.debugFrame = new FrameControl(80,100,300,200,"Debugging");
...
… the additional property is set in the controls constructor, the other properties are set by calling super(...)
which calls the relevant constructor in the Control class.
FrameControl(float x, float y, float w, float h, String title) {
super(x, y, w, h);
this.title = title;
}
.. the display method simply draws a rounded rectangle, note when drawing the title, a background coloured rectangle is drawn first, based on the width of the title in pixels, based on the current font size using the method textWidth()
.
void display() {
// Border
stroke(Constants.CONTROL_COLOUR_FRAME);
noFill();
rectMode(CORNER);
rect(this.x, this.y, this.w, this.h, 5);
// Title background
noStroke();
fill(Constants.CONTROL_COLOUR_BACKGROUND);
rect(this.x+10, this.y-10, textWidth(this.title)+2, 20);
// Title
fill(Constants.CONTROL_COLOUR_FRAME);
textAlign(LEFT,CENTER);
text(this.title, this.x+10, this.y);
}
Object-Orientation
Object-Orientation is a huge subject … and definitely not completely covered here, however, will cover some of the concepts within the context of this project.
Object
An object is an entity that has state and behaviour, in this project for example we have the following objects along with examples of some of their state and behaviour…
Object | State | Behaviour |
---|---|---|
Ball | Colour Speed | can move. can bounce. |
Game | State In play ? | can pause. can resume. |
Input Control | Value Has focus ? | can be clicked. can be typed in. |
.. each one of these is an instance of a class …
Class
A class is basically a blueprint from which an object is instantiated … each class can include fields, methods and constructors, these are some examples of classes used in this project …
Class | Fields | Methods | Constructors |
---|---|---|---|
Player | score name | serve() | Player(index) {…} |
Paddle | player speed | move() | Paddle(Player player) {…} |
Note: I have used the term ‘fields’, however there are other terms used with different meaning depending on the context i.e. properties, attributes etc, as Processing ignores scoping keywords such as private and public etc, any field declared in a class will be exposed as properties anyway.
.. in most cases objects are instantiated with the new
keyword … so for players to be created …
for (int index=0; index < Constants.PLAYER_COUNT; index++) {
players[index] = new Player(index);
}
..and the paddle …
Player(int index) {
...
// Create Paddle
this.paddle = new Paddle(this);
...
}
Encapsulation
The aim of encapsulation is to prevent some internal fields of the object being changed directly by “hiding” them, and then provide (or not provide, as they maybe fields that are just used internally) the means to access them via ‘getter’ and ‘setter’ methods.
As mentioned in the note above, Processing ignores scoping keywords such as private
and public
, so we can not really implement encapsulation, however, I will still go through some examples, as I am hoping to convert the form controls into a Java library, and this is where encapsulation will really matter.
A good example to show is the InputControl class …
class InputControl extends Control {
String value;
boolean valid;
...
// Constructor
InputControl(float x, float y, float w, float h, String label, int maxLength, String value) {
...
this.setValue(value);
}
...
// Setter Method
void setValue(String value) {
this.value = value;
this.validate();
}
...
// Validation
void validate() {
this.valid = (this.value.length() > 0);
}
… here, I know that I can ‘get’ the value
quite easily from an object of this class …
for(int id = 0; id < Constants.PLAYER_COUNT; id++) {
this.names[id] = this.playerNameInputs[id].value;
...
}
.. however, the trouble starts if I wanted to ‘set’ the value directly, for example …
for(int id = 0; id < Constants.PLAYER_COUNT; id++) {
this.playerNameInputs[id].value(this.names[id]);
}
.. if I were to implement the above, the validate()
method would not get called, allowing invalid data to be entered, not good! So we add a ‘setter’ method …
void setValue(String value) {
this.value = value;
this.validate();
}
.. now, when I use the following, which has been implemented, the validate()
method gets called …
for(int id = 0; id < Constants.PLAYER_COUNT; id++) {
this.playerNameInputs[id].setValue(this.names[id]);
}
If this class was to be written in Java, it should look something like this …
class InputControl extends Control {
private String value; // Hide the value, preventing access directly
...
private boolean valid; // This should only be set internally
...
// Constructor
InputControl(float x, float y, float w, float h, String label, int maxLength, String value) {
...
this.setValue(value);
}
...
// Setter method for value
public void setValue(String value) {
this.value = value;
this.validate();
}
// Getter method for value
public String getValue() {
return this.value;
}
...
// Getter method for valid
public boolean isValid() {
return this.valid;
}
// Validation, only to be called internally
private void validate() {
this.valid = (this.value.length() > 0);
}
Abstraction
Abstraction is a bit like saying, here is what you have got to do, I don’t care how you’re going to do it ,but I might do some of it for you.
Interface
An interface is in essence a contract for any class that implements it has to follow, in this project we have the following interface …
interface IControl {
boolean over();
boolean clicked();
void display();
}
.. so any class that implements this interface, has to create each method, whether it be abstract or non-abstract…
abstract class Control implements IControl {...}
It maybe that in the future, I added some kind of read-only or visual control that doesn’t need over()
and clicked()
, just display()
… I could then split the above interface to something like …
interface IControl {
void display();
}
interface IClickableControl {
boolean over();
boolean clicked();
}
.. and the classes that implemented these would look like this …
// A visual only control
abstract class Control implements IControl {...}
// A Clickable control, but also needs to be a visual one
abstract class ClickableControl implements IControl, IClickableControl {...}
.. using interfaces is useful for expanding on classes without breaking existing code, and also if we were to have had a method that needs to do something with any of our controls, we could use …
void someMethod(IControl someControl) { ... }
… which would accept as an argument any object that was created from a class that implements IControl.
Abstract Class
An abstract class is basically creating our base (or subclass) class from which other classes will be derived from, in the case of this project, we have the Control class, which is ….
- defining the common fields (i.e. x, y, w, h).
- defining and implementing non-abstract methods with common functionality (i.e. over, clicked)
- defining abstract method, that needs to be implemented by derived classes (i.e. display)
abstract class Control implements IControl {
float x;
float y;
float w;
float h;
String label;
boolean hasFocus = false;
boolean disabled = false;
...
boolean over() {
if(this.disabled) {
return false;
}
if (mouseX >= this.x && mouseX <= this.x+this.w &&
mouseY >= this.y && mouseY <= this.y+this.h) {
return true;
} else {
return false;
}
}
...
boolean clicked() {
this.hasFocus = this.over();
return this.hasFocus;
}
...
abstract void display();
}
Abstract classes cannot be instantiated, so doing something like this would not be allowed …
Control control = new Control(...);
Inheritance
Inheritance is where one class extends another, inheriting all of its properties and methods, these are the various types of inheritance …
Single Inheritance
There isn’t an example of single inheritance in this project, where one class is extended only once by another class.
Multiple Inheritance
Processing or Java does not support multiple inheritance, where one class can extend two or more other classes.
Hierarchical Inheritance
Hierarchical inheritance, is where many classes are derived from the same class, in the Forms file, all of the controls here are extended from the Control class.
Below is a simple example, showing the relevant code of the ButtonControl class, in this case all of the properties will be available, the over()
and clicked()
methods do not need to be implemented, but their functionality is inherited from the Control class, the only code that needs to be added is any additional properties of its own i.e. caption
, and to implement the abstract method display()
..
class ButtonControl extends Control {
// Extra property required for button control
String caption;
...
...
// Implements the abstract method defined in Control
void display() {
...
// Code to draw button
...
}
Multilevel Inheritance
Multilevel inheritance is where a class is derived from another already derived class, although there are no examples of this type, a challenge was set earlier in post that would use this type of inheritance, here is a bit of a spoiler …
class BallSliderControl extends SliderControl {...}
Polymorphism
Polymorphism, the word, is Greek, which translates roughly to “multiple or many forms”, so in the context of object-orientation we are transforming a class into different (but related) classes by changing the underlying behaviour of its methods. There are two types of polymorphism …
Compile Time
Compile time polymorphism is acheived using overloading, below, shows two overloaded constructors for the Control class, this allows any controls extending this to implement in two different ways, in this example a control with a label, and a control without …
Control(float x, float y, float w, float h, String label) { ... }
Control(float x, float y, float w, float h) { ... }
..so in the case of the ToggleControl, for example, the constructor calls super (…) which uses the relavant constructor of Control class (the superclass), and then sets its own properties…
class ToggleControl extends Control {
boolean value;
...
ToggleControl(float x, float y, float w, float h, String label, boolean value) {
super(x, y, w, h, label);
this.value(value);
}
...
}
.. and in the case of the FrameControl, for example, which doesn’t display a label
, it has it’s own property title
, it only requires the use of x
, y
, w
and h
, which get set by calling super(...)
which uses the relevant constructor of Control…
class FrameControl extends Control {
String title;
...
FrameControl(float x, float y, float w, float h, String title) {
super(x, y, w, h);
this.title = title;
}
...
}
As well as constructors other methods can be overloaded, as in this case with the InputControl, we have setValue( ... )
, the first overload accepts a String, and then validates, the second one allows a char to be passed in, and calls the other with char converted…
void setValue(String value) {
this.value = value;
this.validate();
}
...
void setValue(char value) {
this.setValue(str(value));
}
Run Time
Run time polymorphism is acheived by overriding methods, where a subclass provides the specific implementation of a method declared in its superclass.
One example is in the SliderButtonControl, where the over()
method needs to do the check for mouse over a little different from what was in Control class …
boolean over() {
// code to do something different than what was implemented on Control
}
… another example is in the ToggleControl, with clicked()
, however, this time instead of replacing the implementation completely, it checks with the parent method first using super.clicked()
, and continues with setting the value if true …
boolean clicked() {
if(super.clicked()) {
this.value(!this.value);
return true;
} else {
return false;
}
}
.. and finally, with all the classes dervied from Control, they are to be drawn differently by overriding the method display()
, which in the Control class had no implementation at all …
void display() {
// draw the specific control here
}
File Handling (JSON)
Up until now all the options of the game have been hard coded into the Options class constructor, and we now have form to change some of them, but we need to keep these settings for when we close game and play again next time.
Below is the contents of options.json file located in the /data folder, it is formatted using JSON (JavaScript Object Notation) …
{
"debug": {
"playerHitsBall": true,
"paddleHitsBoundary": false,
"ballReposition": true,
"ballHitsBoundary": false,
"enabled": false
},
"players": [
{
"name": "Bill",
"id": 0,
"up": "q",
"down": "a"
},
{
"name": "Ben",
"id": 1,
"up": "p",
"down": "l"
}
],
"ballSpeed": 5,
"paddleSpeed": 6
}
Reading
.. the the Options class, the method read()
loads the file into a JSONObject
…
...
JSONObject optionsData;
...
this.optionsData = loadJSONObject("data/options.json");
.. and uses various methods to get the values of the keys from optionsData
…
...
JSONObject debugOptions = optionsData.getJSONObject("debug");
this.debug.enabled = debugOptions.getBoolean("enabled");
...
this.ballSpeed = optionsData.getFloat("ballSpeed");
...
JSONArray playersOptions = optionsData.getJSONArray("players");
...
Writing
.. the the Options class, the method write()
sets the values of the keys in optionsData
…
this.optionsData.getJSONObject("debug").setBoolean("enabled",this.debug.enabled);
...
this.optionsData.setFloat("paddleSpeed", this.paddleSpeed);
...
playerOption.setString("name", names[id]);
...
… and saves optionsData
back to the file …
saveJSONObject(this.optionsData, "data/options.json");
Conclusion
Ok, so that was quite a chunky post, but hopefully quite a useful one, learning that Processing is not all about using graphics for games and art, and an exploration of object-orientation in Processing.
What next? There is something missing, something that all video games should have …