![]() |
![]() +- Save-Point (https://www.save-point.org) +-- Forum: Games Development (https://www.save-point.org/forum-4.html) +--- Forum: Tutorials (https://www.save-point.org/forum-19.html) +--- Thread: ![]() |
RE: Ruby Scripting - kyonides - 07-17-2023 To Encapsulate? Or Not to Encapsulate?
That is the... Controversy!
![]() On another board I wound up talking about breaking encapsulation. ![]() ![]() The term itself is defined as: Wikipedia Wrote:Encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof: That is limiting how you call and manipulate a given portion of code belonging to the object. Keep in mind that an object can be either a class or a module in ![]() The second part handles the way you implement your internal and external calls inside blocks called methods in Ruby and functions in C, C++ and Javascript. This is some sort of task management. A single method is NOT supposed to take care of EVERYTHING. A bad example that is linked to the lack of modularity is any main method in scene scripts found in RMXP. main contains almost everything. (The usual exception would be its update method.) That changed in VX and VX ACE because they do feature many helper methods like pre_start and start or terminate there. Now every method has a task or a group of related tasks to perform inside every method. How do we know we are breaking the encapsulation? ![]() ![]() ![]() ![]() You are only supposed to access the methods already provided by the script and that's it! ![]() And it is totally true that we got the attr_reader and attr_writer or attr_accessor methods to quickly make its variables accessible for free. But there's a catch! ![]() ![]() In order to make a point here, I will need to come up with some mockup code to better illustrate it. Code: class Game_System There I created a new traps method belonging to the Game_System class that is accessible via the $game_system global variable. Am I breaking the encapsulation principle here? ![]() Apparently not... but I could be in no time. ![]() ![]() Well, it could be as EASY as making the following call. Code: $game_system.traps[10] = BearTrap.new There I have already altered the actual contents of the traps Array without caring about checks and controls. ![]() Such cases demand from us the creation of custom methods to ensure we won't be leaving blank spaces or any foreign object that should not be found there like a number or a boolean. ![]() A good but quite limiting example of this could be the $game_party.add_actor method that will limit the number of heroes that can join our party in RMXP or RMVX. ![]() ![]() A scripter's life is hard, guys!
![]() RE: Ruby Scripting - kyonides - 08-22-2023 This is something everybody should know about Patches and Bug Fixes.
Patches could also be named bug fixes depending on the issues that might have appeared while working on modifications of existing scripts. In other cases they might just alter or even replace the way the "original" script was supposed to work. ALWAYS treat them as separate scripts. You'll thank me later on. Especially true if any patch you have included in the script editor ends up giving you headaches because it's not compatible with any other script there. Also true if it has some bugs that need some fixing. If the Patch includes any mention of class SomeName or module AnotherName, it NEVER belongs to a script call event command. Create a new section in the script editor and paste it there. Patches and Bug Fixes should always be placed in any free section BELOW their "parent" scripts, the scripts they partially modify or extend with new features. RE: Ruby Scripting - kyonides - 08-28-2023 self && @instance_variables
In RGSSx scripts it is not impossible to find calls that begin with a self reserved word. What is self? ![]() As we had learned long time ago, self simply means the same class or module. Why is it there? ![]() In modules like Kernel or RPG::Cache alias Cache and the File class, you cannot call certain methods UNLESS you include the actual module or class name somehow. While defining stuff pertaining to your module or class, you can avoid using its actual name and simply replace it with self. It is way more practical than typing the same name over and over again. ![]() Why can't you call them in any other way? ![]() That is because the method has become what we call a singleton method. Only a single instance will get access to the data it contains at any given moment. What instance? The module or class itself! HEY! That's not true! ![]() ![]() I've seen it used in other circumstances and it's not referring to the module or class at all. ![]() ![]() For starters, yeah... ![]() ![]() What happened there is that some of those calls are seeking hidden methods, those written in C or C++, and there is no other way to access them or else they could be treated as simple local variables. Code: self.contents.font.size = 32 In this example, the use of self is optional. Just use contents and it will work fine. ![]() ![]() What takes place there is that contents alone forces ![]() ![]() ![]() Code: contents = Bitmap.new(32, 32) The first call would just create a local variable that will cease to exist the moment that method finishes its execution. The latter is ye old and trustworthy self.contents assignment method and it will keep the Bitmap object stored there for future references. This is What Actually Made Me Write This Review!
There is a caveat that we scripters should know about just in case we still ignored it for any reason. What happens very often in RGSS1, namely on RMXP, during battle is a very unusual phenomenon in ![]() classes like Game_Battler make several references to methods like self.damage instead of using @damage directly. Why does this ever happen? ![]() Well, ![]() ![]() Hrm, I suppose ![]() ![]() If you are planning to pre-calculate some basic damage, that might be helpful indeed. ![]() ![]() ![]() ![]() Other implementations like RGSS3 actually provides us with calls to methods in place of variables to let you calculate base prices, for instance. ![]() ![]() ![]() ![]() And anybody can later alias the method at will. ![]() The Conclusion
![]() ![]() And they might be right about that ![]() ![]() RE: Ruby Scripting - kyonides - 11-18-2023 Moronic Design: Ruby & The Embedded Methods
Months ago there was a discussion on a topic that made some people not used to Ruby's nuances pretty mad indeed. The topic at hand was defining a method inside another method, pretty much embedding it without caring about the consequences of such a poor design. I heavily critized it because it will never ever make any sense in ![]() Now I am going to cover that here to let you know what actually happens when you try to imitate one of the effects, namely treating Ruby's methods as JavaScript's functions thinking they both are first class citizens in their respective programming languages. The results might heavily disappoint some of you for not reflecting your wishes or fears as accurately as it was expected. Actually, the outcome is quite predictable while still retaining its nonsensical nature intact. ![]() Conclusions:
The Proof
We need to come up with some test code to make sure we can prove what I have exposed above as the only valid conclusions I could come up with after running it several times in a row. So here it is! Sample Code Code: class A Now let us run it by pasting the following snippet right on a ![]() Test Code Code: print a = A.new Side Note: If you print both the a.hello or a.bye calls, you will not get any error messages. Even so, removing the print call like I did in the code above can make you throw an error for relying on a rescue call inside an IRB binding. What this means is that a console window running the Ruby interpreter is not supposed to execute such a rescue statement. ![]() Inline rescue calls work normally even while running the IRB on a console window aka shell. What we are going to do now is test it on vanilla RMXP (or VX or ACE if you prefer) and also on HiddenChest game engine and just see what happens then. ![]() Ruby 1.8's Output - vanilla RGSS1 Code: #<A:0x41a3481> What we should notice after reading the output above is that whenever a method gets defined, meaning being parsed and evaluated first, it will always return a nil object as its normal outcome. Ruby 2.7's Output - HiddenChest Code: #<A:0x00007f1d4cf54810> OK, this is very similar to the first output, except that Ruby 2.7 returns a :bye symbol after defining any given method. For some reason still unknown to your servitor, it doesn't return anything readable after printing the bye method's return value. Note: After other people also ran the code and reviewing the default implementation of Ruby called either MRI or YARV depending on the version, using two consecutive print calls like I originally did will return a nil value or a whitespace accordingly. In both cases you can either get a bye string representing the method's name by calling the usual print function or the actual :bye "symbol" (actually a string that keeps its typical colon at the beginning) if you call the p function instead. RE: Ruby Scripting - kyonides - 04-16-2024 Using or Abusing of Game Logic:
The Perfect Excuse for Covering a Clear Case of Friendly Fire
Normally, we wouldn't combine a game skill or plot to cover a sinister plot that involves a casualty caused by some friendly fire. Why would anybody ever do that, right? ![]() Yet, somebody unintentionally managed to come up with the perfect scenario where the heroic ![]() ![]() ![]() ![]() Code: $game_party.members.each do |mem| That code above lets you apply a skill as an item, because that's how VX ACE implemented both of them, on a given target. In this case the target is a hero, starting from the party leader ![]() This is the first quake alarm that should shake us like crazy... if we ever care about these petty details, that is. ![]() IF you pay close attention ![]() You see, both the actual attacker and its victim is... the same guy! The hero himself! ![]() So now they all can claim that they were hit by an ally! ![]() That's what we'd call an extreme but also laughable instance of friendly fire taking place under the hood. ![]() How do I know that? ![]() That's an easy one, guys! ![]() I know that because both the attacker and the target are the same mem or party member ![]() What Actually Happens There
Let's say your skill is physical by definition. That would mean that ![]() ![]() ![]() ![]() ![]() ![]() Of course, if you simply don't mind ![]() ![]() ![]() My Suggestion
You better pick an enemy from the database to cast the spell instead. And yes, it's that simple. ![]() NOTES Here the caster or attacker must be a Game_Battler or one of its child classes like Game_Enemy. item doesn't need to be redefined every single time you pick another hero. Code: caster = Game_Enemy.new(0, 1) # Enemy ID is 1 here It doesn't have to be enemy #1 ![]() ![]() Keep in mind that now your heroes would no longer need to ![]() ![]() RE: Ruby Scripting - kyonides - 12-21-2024 Revisiting Strings & Symbols
Not long ago, a furry Wulfo noticed something about using strings and symbols in Ruby, either by running a vanilla RMXP game or my own custom engine HiddenChest. Without further delay, let's read his comments thoroughly. Some Wulfo Once Said
Honestly, that's old news to me. ![]() And there's something I'd really like to add to his remarks there. Adding the condition to not convert a string into a string if it's already a string object is quite ![]() "string".to_s by itself will simply return self, this means it will return "string" after processing that line. It won't transform anything. Instead, it will return its default value, the string itself!! ![]() I suspect this change might have happened in Ruby version 1.9 or later. Based on old comments online, it seems that the Ruby on Rails aka Rails community and base code pushed Ruby core developers into implementing more and more symbols in the default MRI code to reduce the amount of unfrozen strings that needed to be frozen at runtime. The Advantage of Relying on Symbols Not Strings Symbols are unique by definition. Once created, there'll be just a single copy of it for the rest of the code's execution. This is great if you need unique identifiers for very specific things in your custom script. I can give you my Terms module script as an example. By using a single language symbol like :eng or :deu or :spa you can make many different arrays or hashes of strings quickly change your game's graphical user interface or GUI in no time. You'd only need to change a single instance variable in Terms module and you're done! ![]() ![]() ![]() RE: Ruby Scripting - kyonides - 03-24-2025 Effects of Relying on a Bad Explanation:
Getters, Setters & Wrapper Methods
When someone else was explaining how the getter and setter methods work in Ruby, the next thing this guy mentioned was that they were some sort of wrapper methods created around the corresponding variables to let the program access them. Later on you could also read that it limit the circumstances under which the variables can be altered by mistake, but is that true? I've got some sad news for all those people that once thought it was true. No, that's not how it works and it doesn't prevent you from changing the value at any inconvenient moment and those methods can be accessed from ANYWHERE at ANY GIVEN TIME. Thus, some var=(val) method isn't a wrapper of @some, it's its actual setter method. It simply was manually created instead of using Module's attr_writter shortcut. Even if the setter included a conditional statement, it'd remain as a setter. If you don't want any scene or window or sprite to be able to alter those variables, then don't define their setter methods. ![]() ![]() Is it a good idea of calling a manually defined getter or setter method a wrapper? Nope, it isn't. It'd be quite terrible for setters. You better reserve that term for other instances where you'll definitely need it. It'd be a wrapper method in Ruby IF:
Is there a way to easily prove that those getters and setters don't behave like mere wrappers? Yes, there is! Let's use a simple test code. Code: class Person The code above would define 2 methods for the same @age variable, its getter and its setter in that specific order. They are supposed to print something on screen on both RMXP and RMVX. In RMVX ACE it would require you to open the console window first. But there's a catch! ![]() What that means is that now we won't ever be able to print anything on the popup window or the console window depending on which RM engine you're using. ![]() NOTE I have added the initialize method to the Person class to make sure that the @age variable will never return a nil value, which is the default return value of any variable that has not been assigned a different value at some point. Other Ways to Deal With It Later on ![]() The code I had proposed would look like this then: Code: class Person What does that altered line of code do there? Well, it's pretty simple indeed. If the @age has not been predefined, it will pick 1 as its default value. If at some point the value of @age has been altered, let's say that now it's 15, it will ignore 1 and return 15 instead. ![]() Yet, don't forget that the attr_ methods are still in use so they won't ever print anything on a popup window or the console window if any. If it were able to print anything, you'll soon notice that the age getter method would print a nil (an empty string "") instead of a number as its current age. ![]() Is that the only way we could have done it? ![]() ![]() ![]() Code: class Person As you can see, the interpreter would have to always make the check in a separate line before printing the @age variable via print and then returning the value of @age... IF the attr_reader or attr_writer or attr_accessor methods had not been used there or had been #commented out. You shouldn't underestimate that piece of code. It does work as intended, but the ||= operator makes it easy to skip it. Nonetheless, there's a caveat! ![]() The ||= operator won't let you preassign the value before printing the previous value of @age. ![]() RE: Ruby Scripting - kyonides - 03-30-2025 Your Verbose Code Might Be Wasting Your Computing Time!
The present post will consist of an actual request that asked any scripter to alter the default actors' behavior in VX ACE in battle. The Default Behavior & Its Actual Code Actors' actions are based on AGI just like it happens to enemies. Code: module BattleManager What the maker does is to create an array of battlers before adding the party members and the enemy troopers depending on the battle start type: normal, surprise or preemptive. Then it sorts them all based on their actions's lowest speed. (It uses the battler's AGI and perhaps the ATK speed as well.) The Desired Behavior Actors' actions order should be based on their party index instead. Enemy actions would remain unaltered. The Solution Originally Posted by Another Scripter Code: module BattleManager By just looking at the code, any experienced scripter would soon notice that the OP has replaced 3 lines of code with 7 custom ones. These lines are very slight variations of the original code. First, let's mention the merits this scriptlet has. It's quite readable, methodical, and it works as intended. The downside is that it's not as concise as it should be. It doesn't show any signs of it being a slightly creative solution. So it's just a simple one. NOTE: For some reason the actors ALWAYS go first, even if no one had asked for this feature at all. Can that piece of code be shortened somehow? The answer is yes, it can! ![]() The Compact Code I'm Proposing Code: module BattleManager The method's first line already assigns the party members to the battlers' actions array. It's followed by a similar array focused on the enemies only. Both check if there's a flag that might exclude them from being processed while sorting the actions. Then let's say it has found some enemies and the next 2 lines proceed to find the actions and sort them by their corresponding speed as usual. Finally, it just add the sorted array of troopers to the original list of battlers. OK, this scriptlet is no longer as newcomer-friendly as the previous one for sure, yet, it handles the creation of arrays or assignment of existing ones to the @action_battlers array without packing too much stuff inside that method. And it doesn't create arrays that are quite similar nor does it imitate other arrays' behaviors or method calls unnecessarily. NOTE: Actors STILL go first. Enemies will also have to wait for the party to finish attacking them before they launch their counterattack. Stop Assuming You Know All About How the Requestor Wants to Alter a Given Feature What would happen if one day the original requestor, an average forumer, finally realizes that your code grants the actors an inexcusable advantage over their enemies? If that ever happens, you'd be forced to refactor your code to take that situation into account from that point onwards. How could we solve that issue? Well, we could alter the original code to include a way to easily check who should go first. In this particular case we'll assume the change will be PERMANENT and should be defined before the game loads. Now let's take a look at this code: Code: module BattleManager There are 4 changes applied to the scriptlet that allows the game developer to predefine the team that will attack first.
NOTE: Here I let you treat the other battlers as the ones that will be sorted by their actions' speed. If that's not what you want to implement, rename the lines that alter other_battlers as battlers. Now it will only sort the first group by their party or enemy indexes. Thoughts on Refactoring [Pieces of] Code Normally, we wouldn't touch a functional scriptlet and modify it just to make it compact, but the original code gave me the perfect excuse to do so here. The thing is that a scripter should get to a development stage where he or she should be able to realize on his or her own that they gotta be more ways to deal with an issue than just sticking to the engine or its scripts' default features or coding style. The usual reasons to revisit a script might be stuff like adding or removing features, fixing bugs, renaming methods because their current names are misleading or have been repurposed, adding exceptions or conditional statements to prevent happy-go-lucky aficionados or some happy-trigger apprentices from triggering events or script calls at a bad moment, etc. One special circumstance when you might want to change your code would be to get rid of nested loops like defining a for loop inside another one. The only exception to this rule might be nesting 3 for or each loops to iterate over all tiles in the X, Y and Z layers in the RM series. ![]() ![]() |