13.12.2015, 14:11
(
Последний раз редактировалось Sasino97; 13.12.2015 в 18:11.
Причина: Added the link to Yashas' Overloading Tutorial
)
Object-Oriented Programming in PAWN
What is Object-Oriented Programming
Quote:
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods. A distinguishing feature of objects is that an object's procedures can access and often modify the data fields of the object with which they are associated (objects have a notion of "this" or "self"). |
If you are totally new to object-oriented programming I suggest you to read the whole wikipedia article.
Implementing it in PAWN
PAWN is not an object-oriented programming language, however it has a very useful feature that I will be using in this tutorial: tags.
Probably every PAWN scripter, even a beginner, knows the most used tag: Float:, which SA-MP native functions use for the 3D positioning system, health, etc.
However not everybody knows that you can define your own tags, and in fact this is a feature which is not very used by PAWN coders.
My PAWN class model is based on a few simple macro definitions and the use of tags (to prevent calling of methods on an object of a different class).
Downsides
- No inheritance as well as no interfaces
- No visibility setting (private, public, protected)
- No constructor/method overloading (if you want to implement it, read this)
- Only static-like syntax (e.g. ClassName.method(object, arg) instead of object.method(arg))
Let's get started!
PHP код:
/******************** class ClassExample *******************/
#define ClassExample. ClassExample_
#define ClassExample_max ClassExample:1024
Then add these two definitions:
ClassExample. will now be precompiled as ClassExample_ to avoid compiler errors (as . is an undefined symbol).
Then we define the maxium number of instances for this class.
Fields
PHP код:
// Fields
stock ClassExample.myInteger[ClassExample.max];
stock Float:ClassExample.myFloat[ClassExample.max];
stock ClassExample.myString[ClassExample.max][64];
Note: I used the "stock" keyword instead of "new" simply to avoid compiler warnings if later we aren't using those variables. I will always use "stock" vars and functions in this tutorial.
Constructor
PHP код:
// Constructor
stock ClassExample:ClassExample(i, Float:f, s[])
{
static ClassExample:this = ClassExample:-1;
this++;
//
ClassExample.myInteger[this] = i;
ClassExample.myFloat[this] = f;
format(ClassExample.myString[this], 64, s);
//
return this;
}
As before, we define it as a stock function.
First of all, we use the ClassExample: tag to make it return a value with that tag.
Then we call the constructor "ClassExample". You can call it whatever you like, but if you are tidy you will use the same constructor name for all your classes, for example I will always call it <NameOfTheClass>.
Then let's add some arguments to the constructor, in this example, an integer, a float and a string.
Inside the constructor we create a static local variable tagged ClassExample: to keep the instance (object) identifier, we initialize it to -1, then increment it. Every instance of ClassExample will now have a different id.
Finally, let's set the object fields to the arguments of the constructor and return the object handler.
Methods
PHP код:
// Methods
stock ClassExample.toString(ClassExample:this)
{
new ret[128];
format(
ret,
128,
"ClassExample %d\n\t\
myInteger: %d\n\t\
myFloat: %.4f\n\t\
myString: %s\n\n",
_:this, // _: removes the ClassExample: tag, transforming it to a printable integer
ClassExample.myInteger[this],
ClassExample.myFloat[this],
ClassExample.myString[this]
);
return ret;
}
This method, called "toString", will return a string containing the state of the object.
Getter/Setter Methods
PHP код:
// Getter/Setter methods
stock ClassExample.getMyInteger(ClassExample:this)
{
return ClassExample.myInteger[this];
}
stock ClassExample.setMyInteger(ClassExample:this, i)
{
ClassExample.myInteger[this] = i;
}
stock Float:ClassExample.getMyFloat(ClassExample:this)
{
return ClassExample.myFloat[this];
}
stock ClassExample.setMyFloat(ClassExample:this, Float:f)
{
ClassExample.myFloat[this] = f;
}
stock ClassExample.getMyString(ClassExample:this)
{
return ClassExample.myString[this];
}
stock ClassExample.setMyString(ClassExample:this, s[])
{
format(ClassExample.myString[this], 64, s);
}
/************************************************************/
Instantiating, calling methods and setting field values
PHP код:
main()
{
// creating instances
new ClassExample:a = ClassExample(2015, 25.524, "Hello World!");
new ClassExample:b = ClassExample(125, 7.52, "Goodbye World!");
new ClassExample:c = ClassExample(521, 2.52, "Hello Again World!");
// calling toString
printf(ClassExample.toString(a));
printf(ClassExample.toString(b));
printf(ClassExample.toString(c));
// changing and testing fields directly
printf("We incremented a's integer directly\n");
ClassExample.myInteger[a] ++;
format(ClassExample.myString[a], 64, "Happy new year!");
printf(ClassExample.toString(a));
// changing and testing fields by getter and setter methods
printf("b.getMyString: '%s'", ClassExample.getMyString(b));
printf("We are now setting b's string value by calling its setter method\n");
ClassExample.setMyString(b, "Changed value!");
printf("b.getMyString: '%s'", ClassExample.getMyString(b));
}
Output:
Код:
[15:08:16] ClassExample 0 myInteger: 2015 myFloat: 25.5240 myString: Hello World! [15:08:16] ClassExample 1 myInteger: 125 myFloat: 7.5199 myString: Goodbye World! [15:08:16] ClassExample 2 myInteger: 521 myFloat: 2.5199 myString: Hello Again World! [15:08:16] We incremented a's integer directly [15:08:16] ClassExample 0 myInteger: 2016 myFloat: 25.5240 myString: Happy new year! [15:08:16] b.getMyString: 'Goodbye World!' [15:08:16] We are now setting b's string value by calling its setter method [15:08:16] b.getMyString: 'Changed value!'
I want to show you a very basic, uncomplete and untested house system made with a House class for demostrating purposes.
PHP код:
/*
* This is an example on how to mimic a very basic Object-Oriented programming in the PAWN language
* by SaSiNO97_ Aka Sasinosoft
*/
#include <a_samp>
/******************** class House *******************/
#define House. House_
#define House_max House:256
// Fields
stock House.owner[House.max][MAX_PLAYER_NAME];
stock House.price[House.max];
stock Float:House.x[House.max];
stock Float:House.y[House.max];
stock Float:House.z[House.max];
// Static final field example
// - static because it's not depending on a single House object (no [House.max])
// - final because it's defined as "const" and can't be changed
stock const House.invalidOwnerName[MAX_PLAYER_NAME] = "#@!/*-";
// Constructor
stock House:House(price, Float:x, Float:y, Float:z)
{
static House:this = House:-1;
this++;
//
format(House.owner[this], MAX_PLAYER_NAME, House.invalidOwnerName);
House.price[this] = price;
House.x[this] = x;
House.y[this] = y;
House.z[this] = z;
Create3DTextLabel("House", 0x33AA33FF, x, y, z, 5.0, 0, true);
//
return this;
}
// Methods
stock House.toString(House:this)
{
new ret[128];
format(
ret,
128,
"House %d\n\t\
Owner: %s\n\t\
Price: $%d\n\t\
Position: (%.4f, %.4f, %.4f)\n\n",
_:this,
House.owner[this],
House.price[this],
House.x[this],
House.y[this],
House.z[this]
);
return ret;
}
stock bool:House.buy(House:this, playerid)
{
if(!IsPlayerConnected(playerid))
return false;
if(!strcmp(House.owner[this], House.invalidOwnerName))
{
SendClientMessage(playerid, 0xFFFFFFFF, "[ERROR]This house is already owned.");
return false;
}
if(GetPlayerMoney(playerid) < House.price[this])
{
SendClientMessage(playerid, 0xFFFFFFFF, "[ERROR]You can't afford this house.");
return false;
}
if(!IsPlayerInRangeOfPoint(playerid, 1.5, House.x[this], House.y[this], House.z[this]))
{
SendClientMessage(playerid, 0xFFFFFFFF, "[ERROR]You aren't close to this house.");
return false;
}
new nickname[MAX_PLAYER_NAME];
GetPlayerName(playerid, nickname, MAX_PLAYER_NAME);
GivePlayerMoney(playerid, -House.price[this]); //
format(House.owner[this], MAX_PLAYER_NAME, nickname);
SendClientMessage(playerid, 0xFFFFFFFF, "[CONGRATULATIONS]You now own this house!");
return true;
}
stock bool:House.sell(House:this, playerid)
{
if(!IsPlayerConnected(playerid))
return false;
new nickname[MAX_PLAYER_NAME];
GetPlayerName(playerid, nickname, MAX_PLAYER_NAME);
if(strcmp(House.owner[this], nickname) != 0)
{
SendClientMessage(playerid, 0xFFFFFFFF, "[ERROR]This house is not yours.");
return false;
}
if(!IsPlayerInRangeOfPoint(playerid, 1.5, House.x[this], House.y[this], House.z[this]))
{
SendClientMessage(playerid, 0xFFFFFFFF, "[ERROR]You aren't close to this house.");
return false;
}
GivePlayerMoney(playerid, floatround(House.price[this]*0.7, floatround_ceil)); //
format(House.owner[this], MAX_PLAYER_NAME, House.invalidOwnerName);
SendClientMessage(playerid, 0xFFFFFFFF, "[CONGRATULATIONS]You sold your house!");
return true;
}
// Example of a static method (a method that is not linked to a specific object of the class)
stock House.printAll()
{
for(new House:i=House:0; i<House.max; i++) // Loop through all Houses
{
if(House.x[i] != 0.0) // check if it was created
{
printf(House.toString(i)); // prints it state
}
}
}
/************************************************************/
main()
{
printf("House Class Example by SaSiNO97_!");
}
public OnGameModeInit()
{
// Let's create some anonymous objects of the class House (positions are totally random, so don't look for them inside the game :P)
House(15000, 1520.0, 214.0, 15.0);
House(25000, 1620.0, 244.0, 12.0);
House(50000, 1120.0, 714.0, 14.0);
House(36000, 1120.0, 714.0, 14.0);
House(14000, 1220.0, 614.0, 24.0);
House.printAll();
}
public OnPlayerCommandText(playerid, cmdtext[])
{
if(!strcmp(cmdtext, "/buy", true))
{
for(new House:i=House:0; i<House.max; i++) // Loop through all Houses
{
if(IsPlayerInRangeOfPoint(playerid, 1.5, House.x[i], House.y[i], House.z[i])) // If player is in range of a house
{
House.buy(i, playerid); // Call the method "buy"
break; // Exit the loop
}
}
return 1;
}
if(!strcmp(cmdtext, "/sell", true)) // Similar to previous
{
for(new House:i=House:0; i<House.max; i++)
{
if(IsPlayerInRangeOfPoint(playerid, 1.5, House.x[i], House.y[i], House.z[i]))
{
House.sell(i, playerid);
break;
}
}
return 1;
}
return 0;
}