Ruby Scripting - Printable Version +- 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: Ruby Scripting (/thread-7532.html) |
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. What is encapsulation? Is it a bad thing at all? 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 Ruby. Even numbers are objects because they do own several methods. 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? Have you asked a scripter to give you at least a hint on how to access any of the current scene's methods from the Game_Party or the Spriteset_Map or vice versa? Guess what? You broke it! You are only supposed to access the methods already provided by the script and that's it! Oh yes, I know that it would make custom patches very difficult to be crafted under certain circumstances. Even so, you could accomplish the same goal in a totally indirect way instead. 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! What happens if you call the variable directly? 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. How could you do it? 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. Yes, that means you got to create more and more wrapper methods to keep access to $game_system or $game_party methods safe. 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! Huh? What do you mean by that? I've seen it used in other circumstances and it's not referring to the module or class at all. Oh, you mean those instances like self.contents and self.bitmap found in window scripts. For starters, yeah... Don't worry about it! That is easy to explain indeed. 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. Is there any difference between using one option or the other? You got me here. Yes, there is one! What takes place there is that contents alone forces Ruby to look for any instance of either a local variable OR a method called contents. While self.contents already informs the interpreter that you are looking for a method, be it a class or instance method. (Class methods are singletons by default.) But there is a catch! There is always a catch! 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 Ruby scripting. This is one of the very few places where this has ever happened in decades. classes like Game_Battler make several references to methods like self.damage instead of using @damage directly. Why does this ever happen? Well, I guess that scripters were not fully aware of how @instance variables were supposed to be used in a script. Thus, what I mean here is that they did something totally weird by calling self.damage instead of simply typing @damage whenever they want to calculate the actual damage or healing any given Game_Actor or Game_Enemy is about to receive at any moment. But is it something really bad at all? Hrm, I suppose that depends on what you want to accomplish there. If you are planning to pre-calculate some basic damage, that might be helpful indeed. Otherwise using a plain @damage instance variable would suffice there, letting you to hit that bad mobster as hard as you can. Go for it, Arshes! Other implementations like RGSS3 actually provides us with calls to methods in place of variables to let you calculate base prices, for instance. And nope, they do not use the self pseudo variable at all. They may or may not have the same name as the actual variable that is going to be used there. You see, once you choose to use a method there, you are free to name it they way you prefer to your heart's content. And anybody can later alias the method at will. The Conclusion
This is one of those cases where there is no way to get quite conclusive about what you are supposed to do now because people can tell you at a certain point that it is a matter of style or taste. And they might be right about that no matter how weird that might sound like. 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 Ruby nor RGSS any version at all. 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 script section located before the Main script and lets launch the game after saving our changes to the script editor. 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. This is true for the 2.7 release at least. 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. Will both engines return the same results? 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 platoon can no longer blame anybody else, like a devious foe, for hurting them. Why? I'll tell you why in a moment. 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 himself. 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 to that seemingly innocent piece of code, it has a serious flaw. A logical one indeed. 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 in that specific order. What Actually Happens There
Let's say your skill is magical by definition. That would mean that mages who normally have high MATK stats could be seriously hurt by that skill while fighters like Orson wouldn't. If it's physical by nature, then Orson would be the first casualty here. Of course, this might not take place if they also have high DEF or MDEF stats. Of course, if you simply don't mind about these technicalities, well, you're good to go! 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 , obviously. Keep in mind that now your heroes would no longer need to worry about not cleaning their guns properly following all local regulations. |