How is OOP used in actual game logic?

Hello!

I am fairly new to OOP and I have created a Player module which fits only my game. I will write a sample of what I have as I have no intention of releasing it to public.

Main Class Sample
local Player = {}
Player.__index = Player


function Player:new()
  local NewPlayer = {}
  setmetatable(NewPlayer, Player)
  NewPlayer.Health = 100
  NewPlayer.Race = "Human"
  NewPlayer.NickName = "Nickname"
  return NewPlayer
end
return Player

I also have some sub classes of Player like: Certain Characters, let’s take a random character as I am not spoiling my creation :wink:

Sub Class Pewdiepie
local Pewds = {}
local Player = require(script.Parent)
Pewds.__index = Pewds
setmetatable(Pewds, Player)

function Pewds:new()
local NewPewds = Player:new()
NewPewds.Name = "Pewdiepie"
NewPewds.Health = 500
NewPewds.NickName = "Pewds"
NewPewds.Race = "Epic Human"
end
return Pewds

So, I have all of this subclasses of my desired races, yet, I have no idea how to integrate this with the default player object.
Simply, I do not know how to give these attributes to the player object himself, thus making these classes look not so useful to me.
Is there a way to give the , for example, Pewds race to a player once he joins. And if there is, is it done in client or the server? If it is done from both, do I have to create multiple of these objects externally?
I am sorry these questions sound so unclear, at least to me, since I am so new to OOP i have no idea how it’s done properly.

3 Likes

So far, you only have data fields whose values vary from class to class. For OOP to be really useful, you should have some reason to want to think of things in your game as objects, with their own different fields and methods. Neither example class has methods. There might be a method for damaging Players, reducing their health. This method can keep track of and notify other parts of the game of when a Player dies, which should only happen when the health goes below 0. The Pewds class can overwrite this method with one that calls the method of the superclass on itself, and also plays a voice line. A Pewds object might have methods and fields that the superclass doesn’t, like a number of fans and a method for manipulating that field. A different derived class might not overwrite the damage method, but implement others.

If all of your player derived classes are only going to vary in the values of their fields, then OOP might not be right for you. Why not just have all of this information as a dictionary, and then refer to that instead?

As for actually using what you have so far, you’d have to associate a “player object” with each Player who joins. You could have a dictionary of Players to “player objects”. This way you can associate different data and functionality with each Player. Other systems in your game then need a way to access these “player objects”, so they can call their methods and use their fields.

2 Likes

My bad, I didn’t include functions here. I have some like, ChaneRace(), AddCash() (yes, there’s a money variable too) and the like.
Let’s say I have a player, he enters then I want to associate these variables, race, nickname and health to him/her, how do I do that?

I know I have to do a Player:new() after ofc requiring it, but where is it done? In client or server? and if so,

how do I apply these variables like health and nickname so that I can actually do a Player.Health = 0 or Player:ChangeRace() from another script, while referring to the object I initially made in the script I declared Player:new()

1 Like

There’s no good built-in way of transferring object references between the client and server, since RemoteEvents can’t transfer tables with metatables properly. Also, there’s no easy way of keeping client and server references to the same object synchronized. You could spend a whole lot of effort making a system that does this for you, but I don’t think it would be easy. So I recommend having every object exist only on the client or the server. If the same thing needs to be referred to as an object on both sides, you’ll need to have different objects representing the same thing. That means having a Player server-class and a Player client-class. This also helps with keeping track of what the client is allowed to do, by just not having methods that do illegal things like e.g. changing other players’ money. A Player server-object and client-object can still refer to the same Player Instance[1], and the same Character Model, and some of the same fields and methods, making the abstraction still somewhat useful.

I would make a PlayerManager script. If you absolutely want that to use some kind of OOP paradigm, it should be a singleton class. The PlayerManager is responsible for instantiating (creating) one player object per Player Instance in the game, and probably destroying it when the Player leaves. It should also have an interface for letting other scripts look up these player objects. To keep track of player objects, I recommended a dictionary where the keys are Player Instances, and the values are player objects. Here’s a simple version of a PlayerManager, implemented as a ModuleScript:

PlayerManager
local PlayerClass = require(game.ServerStorage.PlayerClass)

local PlayerManager = {}
local playerObjectsDict = {}

function PlayerManager:GetPlayerObject( playerInstance )
	return playerObjectsDict[playerInstance]
end

game.Players.PlayerAdded:Connect(function( playerInstance )
	playerObjectsDict[playerInstance] = PlayerClass.new( playerInstance )
end)

game.Players.PlayerRemoving:Connect(function( playerInstance )
	playerObjectsDict[playerInstance]:Destroy()
	playerObjectsDict[playerInstance] = nil
end)

return PlayerManager

It calls the PlayerClass.new constructor with the Player Instance as a parameter, because I think each of your PlayerClass objects should keep track of its own reference to the Player Instance that it “belongs to”. If you want the ability to set the playerObject to an instance of some other, derived class at a later point, then you could destroy the old object and overwrite its location in the dictionary. This is problematic because other scripts might have an old reference to it, meaning you need to keep track of whether a player object has been destroyed, and old refs to it might sit around in memory forever. This is why I don’t think your inheritance approach for different “player types” is good.

Once you have the PlayerManager script, you can require it and call local playerObject = PlayerManager:GetPlayerObject( somePlayerInstance). Then all the methods and fields are available to the calling script, so you can do playerObject:GiveReward() or print(playerObject.Nickname) or whatever you’ve implemented in the specific class.

[1]: By that I mean the kind of Player Instance that would be in game.Players.

Hope this helps. It’s pretty long-winded, and by no means the only way you can go about it. To be honest, I think your idea of going with an OOP approach is misguided. But if you insist, then I don’t mind helping you out :slight_smile: Most of your questions aren’t really about OOP specifically, but about software architecture and how to connect different parts of your games logic. That means it will be helpful even if you don’t go with the OOP paradigm in the future. Let me know if you have other questions, and I’ll answer when I have time.

6 Likes

Object-oriented programming (OOP) is one of the most important programming techniques today. It is applicable to most practical applications built in businesses. Most popular programming languages ​​and programming frameworks like Java, PHP, .NET, ruby ​​support object-oriented programming. Most programmers have learned about object-oriented programming at university, but the basic principles of object-oriented programming are sometimes unclear leading to misuse and improper programming philosophy. object oriented.

In this article, I will summarize the basic principles of object-oriented programming to help you get an overview of OOP and how to apply it.

Class and object

OOP has 3 basic principles we will explore in detail the following:

Encapsulation

Encapsulation is the rule that requires the internal state of an object to be protected and avoid access from external code (i.e. external code cannot directly see and change the state of that object). ). Any access to this internal state is required through a public API to ensure the state of the object is always valid because the public APIs are responsible for performing validity checks as well as the update order. Update the status of the object.

In general, the object status is invalid because: it has not been checked for validity, the steps are performed in the wrong order or are omitted, so there is an important rule in OOP to keep in mind. The object’s internal state is private and only accessible through public / protected method / property. When using objects we do not need to know how they work inside, we just need to know what public APIs are and this ensures that what changes the object will be checked by the internal logic rules. , avoid objects being used incorrectly.

The principle of packaging like this everywhere we can see for example the tablet design, we only know it cures this and that and some other key ingredients that are specific to what it is. totally unknown.

Inheritance

When we start building the application we will start designing the classes, usually we will see that there are cases where some classes seem to be related to others, they have quite similar properties. For example: 3 classes AndroidPhone, IPhone, WindowsPhone

Each class represents a different type of smartphone but has the same properties. Instead of copying these properties, it is better to put them in a place that can be used by other classes. This is done by inheritance in OOP: we can define the parent class - base class (in this case Smartphone) and have subclasses derived from it (derived class), creating a relationship. parent / child system.

Now, subclasses can inherit 3 properties from the parent class. If the functions of the superclass are fully defined, the programmer will not have to do anything in the subclass. If a child class wants a function that is different from the definition in its parent class, it can override the function defined on this parent class.

Polymorphism

For most programmers, the Inheritance and Package in OOP are quite easy to understand while the Polymorphism when approaching will find it a bit more difficult to understand. However, this is a property that can be said to contain most of the power of object-oriented programming. Put it simply: Polymorphism is a concept that two or more classes have the same methods but can be implemented in different ways:

AndroidPhone stored with Google Drive Iphone stored on iCloud WindowsPhone uses SkyDrive.

Because all are smartphones, if we write a function that uses the Smartphone type as a parameter, when calling the function we can pass an AndroidPhone, Iphone or WindowsPhone object because they are inherited from the Smartphone class should be accepted. (in a nutshell an AndroidPhone, Iphone, WindowsPhone is also a Smartphone). Besides, this function does not even need to care which smartphone is passed in because it only needs to know that the object that is handling here is Smartphone with the public method / property defined. If the child classes do not redefine (overrides) the CloudStore () method, the CloudStore () method on the parent class (Smartphone) will be called. If the subclass overrides the parent’s CloudStore () method as in the image above, the CloudStore () method on the subclass will be called even though the code in the function is manipulating a Smartphone-like object.

Such polymorphism is a very powerful property because it gives the code a high degree of generalization. We do not need to create methods for each type that inherits from the Smartphone parent class, but simply receives a Smartphone type variable and can work with any class that inherits from it. The only thing not done here is using methods that are only declared on subclasses. For example, if we have a method on the IPhone class called OpenSiri () but is not declared on the Smartphone class, then wanting to call it will be forced to cast from Smartphone to IPhone before calling.

Interface

Inheritance-based polymorphism is not always the best option. It is clear that the 3 lower layers (Iphone, Laptop, FingerprintScanner) are all things that are accessible by fingerprint but they perform in different ways. These classes share a common action called biometricAuth (). If we try to combine all three layers into a common layer, it is not good because it is difficult to find their common ground in addition to being accessible by fingerprint.

So instead of using Inheritance here, we can use another technique, Interface. An interface is simply a contract indicating that your code will execute and support a specific public API. However, how these public APIs are implemented is not shown on the interface, but rather on the interface implementation class. Basically the contract is a list of public methods / properties that will certainly be implemented in your class.
Applying the above example, we can create an interface called IBiometricAuth with a method called BiometricAuth (). Next for the Iphone, Laptop, FingerprintScanner classes implement this IBiometricAuth interface as shown below

Because each of the above classes implements the IBiometricAuth interface, we can guarantee that they all have the BiometricAuth () method, and the method declaration will be exactly the same as the one defined on the IBiometricAuth interface. Similar to Polymorphic Inheritance, using Interface allows us to declare methods that accept parameters of type IBiometricAuth but accept any objects passed that its type implements this interface IBiometricAuth. The IBiometricAuth implementation class does not need to have the same parent class except the IBiometricAuth interface. In the above method, we can call any method defined on the interface IBiometricAuth of the object that is passed in, regardless of the actual type: No matter whether it is an Iphone, a Laptop, or FingerprintScanner, as long as it supports the IBiometricAuth interface, can call the BiometricAuth () method, which is extremely flexible and flexible.

16 Likes

This might give you some guidance and how to set things up:

Also since Miners Haven* was open-sourced it might be worth taking a look at the code in there to see a real example of how it works.

*I’m assuming it would be OOP programmed, but my internet is terrible and I cant download it to check. Please correct me if I’m wrong.

2 Likes