This is more of a Ruby question, but it is relevant to Gosu.
I'm working on a clone of Zelda 2, and I want to reduce the hassle of passing a window variable between methods. Originally, I used a global variable to do this, but recently I modularized my code by splitting it into several files and now I need a way to pass that variable around. I'm a lazy programmer, so I only want to load this variable once per file and then use its reference variable. That is, I set
W = (window variable) and then I call
W where needed.
My current solution is to have an empty module named CommonValues (in a file Shared.rb) which is written to using Ruby's built-in
#instance_variable_set, and related methods. If a file needs to access these variables, I put
required_relative 'Shared' at the start of the file, then load a variable as needed. I'm not only doing this for the window variable, but for quest and media folders as well.
The problem with this approach is that it creates a lookup up loop with all the
require_relative operations. I'm not sure if I can work around this, or if this approach to sharing variables is itself flawed. (Ruby is my first real language and I've been coding for two months now.)
Here's my code.
05> require_relative 'Shared'
08> module StageArea
12> W = CommonValues::WINDOW
13> STAGES_FOLDER = File.join CommonValues::QUEST_FOLDER, 'Graphics/Stages'
07> require_relative 'Shared'
08> require_relative 'StageArea'
11> module Quest
13> QUEST_FOLDER = File.join '../Quests', 'Original Quest'
14> CHEATS_FOLDER = File.join QUEST_FOLDER, 'Cheats'
15> DATA_FOLDER = File.join QUEST_FOLDER, 'Data'
16> FONTS_FOLDER = File.join QUEST_FOLDER, 'Fonts'
17> GRAPHICS_FOLDER = File.join QUEST_FOLDER, 'Graphics'
18> MUSIC_FOLDER = File.join QUEST_FOLDER, 'Music'
19> SCENES_FOLDER = File.join QUEST_FOLDER, 'Scenes'
20> SCRIPTS_FOLDER = File.join QUEST_FOLDER, 'Scripts'
21> SOUNDS_FOLDER = File.join QUEST_FOLDER, 'Sound Effects'
22> VOICE_FOLDER = File.join QUEST_FOLDER, 'Voice Effects'
24> CommonValues.const_set(:QUEST_FOLDER, QUEST_FOLDER)
15> require 'rubygems'
16> require 'gosu'
20> require_relative 'Shared'
21> require_relative 'Quest'
28> class Main < Gosu::Window
29> def initialize
32> CommonValues.const_set(:WINDOW, self)
33> CommonValues.const_set(:FRAME_RATE, (1000/self.update_interval).round.to_f)
The error message I get is:
User@COMPUTER ~/Game Engine
<module:StageArea>': uninitialized constant CommonValues::WINDOW (NameError)<top (required)>'
from ~/Game Engine/StageArea.rb:8:in
from ~/Game Engine/Quest.rb:8:in
from ~/Game Engine/Quest.rb:8:in
Quest, which requires
StageArea, which tries to call two variables from
CommonValues that are set in
Main sets these variables before they are ever called, the error must relate to the peculiarities of
require_relative. To make sure of this, I placed a
binding.pry (pry is an alternative interactive ruby interpreter to irb) on line 11 of StageArea.rb and injected
CommonValues.const_set(:WINDOW, "window"). I then successfully called
CommonValues::WINDOW and received
"window". So it seems the parsing of
W = CommonValues::WINDOW happens before
CommonValues.const_set(:WINDOW, self) in
Main.rb is actually run.
I know Gosu will be phasing out the window variable, but I'll get the same problem from the other variables as well. Is this a good way to tackle the problem, or is there a better solution? I appreciate any alternative ideas :) .
Why can't you use a global variable?
I made a bad assumption. When I first split my code into several files, I was getting uninitialized variable errors for $WINDOW. So I looked up the exact scope of globals and read that they can be called anywhere within the program. Well, I assumed "program" meant program file (i.e. only in main.rb) and so tried to implement this other method. Plus, I always hear how bad globals are, so I thought I better just forget about them. But after hearing some more thoughts on it, a global window variable doesn't sound too bad. As for the other folder variables, I changed my approach and just hard-coded them into
Shared.rb for now, instead of spreading them over several files which would have become confusing later anyhow.
You can take a look at
require 'gosu/preview', which is a preview of how the interface will look soon- many methods are now on the module level, such as
draw_quad, and constructors don't need
window arguments anymore.
Ooh, when did that happen? I missed the news somehow. :D
I'm still not sure about color constants and wanted to make it ONE BIG STEP.
0xff0000 being solid red and all,
0xff_ff0000 being the invisibly transparent one. Backwards compatibility for that is a big, fun puzzle. One approach would be to make things constructed with the
window argument flip all colors internally. %)
ARGB is not as standard an ordering as RGB/RGBA, but it is a hell of a lot better than TRGB. Although TRGB allows stuff like 0xff0000 for red, it really isn't that much better, I don't think, and it is just as likely to confuse people, just in a different way. Ruby people can be using the Color.rgb, Color.rgba, and Color.argb to do it however they want, so I don't think we matter in the argument :D.
TRGB, if anything, is supposed to be the exception. My design would be based on the assumption that 90% of color constants have full alpha.
But yeah, and to make it even better, Ruby has 0xff_ff000 for red. Even when I still used monospaced fonts, I often got lost in eight hex digits. But C++11 makes it possible to have "ff0000"_rgb now, so maybe I'll just go with that and deprecate the implicit conversion altogether.
Eventually, changes will have to break backwards compatibility in the name of progress, no? Best to do it now, before too many people get really invested in Gosu. It feels like more and more people use this engine all the time ^__^
But silently breaking all current games would probably scare all current users, me included, off forever. :( This would be hell to debug.
But with bundler etc, is this really a problem? They could always just install the older version of Gosu and use that.
Though I will admit that subsequent builds of Gosu which break my game makes me hesitant to upgrade, so it's not a simple thing.
But how would I upgrade my larger projects when the time comes? I could only go over every occurence of
0x... and hope to catch everything. If I forgot one place, I wouldn't notice unless I paid lots of attention while test playing. :(
Thanks. I saw that file before but didn't look into using it. But I think I will now. :)
You do seem to be overengineering. Most people (e.g. Chingu and I've seen it other places too) use "$window = self" in the initializer to have global access to the window, although as jlnr says, this is now unnecessary. They could equally have used "@@instance = self" and made an accessor on the class to access it ("Main.instance"). Use of const_set is really a bad smell - it is critical in certain meta-programming, but that doesn't mean it should generally be used for this sort of thing. A bit like global variables - you should never use them unless the alternative is impossible or incredibly messy ("Never use globals" is rather too dogmatic!).
Thanks, that sounds reasonable. I keep telling myself, "Make something that works first instead of making something perfect," because I know that I'll never get anything accomplished otherwise. And what I know now will probably seem stupid to me later. Those folder variables are really only defined once, so it doesn't make sense for them to be created all over the place. I put them all in
Shared.rb for now and later will be retrieving them from a external file.
Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill