RPGツクールVXAceのスクリプト言語であるRGSS3(Ruby1.9.2p0 + ツクールライブラリ)に関する個人メモ。あくまでも個人メモなので管理人以外には編集権限はありません。

×


プログラミング全般

Java経験者がRubyのために最初に覚えるべき事

Rubyは全てがオブジェクト
つまりリテラルもオブジェクトである。true、falseもそれぞれのクラスのインスタンスとして実装されている。
nullに該当するのはnilである。nilもNilClassのインスタンスである。つまりnullと異なり実体がある。
これはつまりRubyにはプリミティブ型が存在しない事を意味する。1やnilからメソッドを呼び出す事も可能。
Rubyでの真偽値
Rubyではfalseとnilのみがfalse、それ以外は全て(0も""も[]もあらゆるオブジェクトが)trueである。
Rubyにはboolean型やBooleanクラスが存在しない。ある意味で全てのオブジェクトがbooleanとして扱えると言える。これはRubyの強力な特徴である。
Rubyのクラス名は定数
定数は値を持つ。つまりクラス名定数も値を持つ。
クラス名定数の値はClassオブジェクト(つまりClassクラスのインスタンス)である。
Rubyのself
selfはJavaのthisに該当する。thisと異なりクラス定義内(Javaでいうstaticコンテキスト)を含め、あらゆる場所で使用できる。
Rubyのインスタンス変数、クラス変数
Rubyのインスタンス変数はJavaでいうインスタンスフィールドに該当する。クラス変数はstaticフィールドに該当する。
Javaと異なり変数のスコープは変更できない。Javaのprivateとprotectedの中間ぐらいで固定。
publicにアクセスする事はできないので、アクセサなどでゲッターやセッターを定義する必要がある。
Rubyは型宣言が不要
異なる型での上書きも可能。
メソッドのシグネチャも型情報が含まれないためメソッドの判別ができない。つまりオーバーロード機構が存在しない。
Rubyの演算子のほとんどはメソッドとして実装されている
つまり演算子の左辺と右辺で明確に意味が異なる。
演算子メソッドはほとんどの場合 "left_side_obj.operator_method(right_side_obj)" として評価される。例外的に !メソッド はオブジェクトの前に置くことを許される。逆に言えば !演算子 は通常のメソッド呼び出しとしても書ける。(true.! == !true == false)
演算子メソッドを再定義することができるため、演算子の結果を変更する事ができる。
Rubyのオブジェクトは複数のインスタンスを持つ
self(自身のオブジェクト)とself.singleton_class(特異クラスオブジェクト)の2つのインスタンスを持ち、self.class(Classオブジェクト)への参照を持つ。
Classオブジェクトもオブジェクトなので当然、selfとsingleton_classを持つ。
Javaではオブジェクトは、インスタンスとクラス情報(型)という組み合わせだが、Rubyでは全く異なる。
Rubyにはパッケージは存在しない
モジュールやクラスの名前空間をパッケージのように使うことができる。
モジュールの内部にクラス定義することで、モジュール名::内部クラス名 の形で名前空間に所属できる。
Rubyにはインターフェイスが無い
モジュールがインターフェイスのようにも使える。
モジュールをincludeするとJavaのインターフェイスのように継承関係を構築する。
abstractメソッドは定義できない。モジュールのインスタンスメソッドがインターフェイスのデフォルトメソッドのように振る舞う。
Rubyにstaticメソッドは存在しない
特異メソッドがその代替として使われる。
Rubyでクラスから直接メソッドを呼ぼうとした場合、クラスオブジェクトはClassクラスのインスタンスであるためClassクラスのインスタンスメソッドが探索されてしまいクラスに定義したインスタンスメソッドは探索されない。クラスに特異メソッドを定義すると、Classクラスのインスタンスメソッドの前に特異メソッドが探索されるので、Javaのstaticメソッドのように振る舞う事ができる。
staticメソッドを集めたstaticライブラリのような機能は、モジュールに特異メソッドを集める事で実現できる。
static import のようにも使いたい場合は特異メソッドをモジュール関数として定義し、includeすることで実現できる。
Rubyのイテレータ
Rubyは非常に強力な内部イテレータと完全なクロージャであるブロックを併用できるため、Javaの拡張for文のような外部イテレータはほとんど使われない。
そもそもイテレータが強力すぎてループ文自体がほとんど使われない。
Javaプログラマの観点ではイテレータは比較的使いづらい仕様であったが、Rubyのイテレータはブロック内で直接コードを書けるので直感的にわかりやすく非常に使いやすい。
Rubyのコレクション
Rubyのコレクションは実質的にArray、Hashの二種類しか存在しない。後期バージョンや拡張ライブラリではSetも使用可能。
RubyのArrayはJavaのArrayList、HashはJavaのLinkedHashMapに相当する。
実用レベルでは2種類で全く問題ない。逆に使い分けに悩む必要も無いので楽といえば楽。
Rubyのcase文はJavaのswitch文よりも高機能
case文の比較はwhenに与えられたオブジェクトの===メソッドで行われる。オブジェクトごとにその振る舞いを変更できる。
例えば case v の時、when 1 なら v == 1 の場合に実行されるが、when Integer なら v.is_a?(Integer) の場合に実行される。
評価式(v)を省略した場合、when v.odd? のようにwhenに判定文を書けるため複雑な制御構造にも対応可能。
caseは最後に評価した値を返すため、評価結果で代入する値を変えるような式はシンプルに書ける。
Rubyのメタプログラミング
Javaのリフレクションよりも強力かつ柔軟なメタプログラミングが可能。
逆に言えばそれだけ安全性が犠牲になっているので、Javaの堅牢性が好きな人には発狂モノかもしれない。そういうモンだと諦めて。
Rubyはスコープを制御できない
Javaのprivateやprotectedに相当するアクセス制御は存在しない。
Rubyで定義できるprivate、protectedは呼び出し制限(call limit)であり全くの別物。むしろアノテーションに近い。
とりあえずRubyには完全にprivateな変数やメソッドは存在しない事だけ覚悟しといた方が良い。そういうモンだと諦めて。
RubyのfreezeとJavaのfinalは同じではない
Rubyのfreezeはオブジェクトの変更を禁止する。例えば配列の内容は変更できなくなるが、変数の上書き(再代入)は可能。
Javaのfinalは変数の再代入(上書き)を禁止する。例えば変数の中身の配列は変更可能。
上記の通り、RubyのfreezeとJavaのfinalはむしろ真逆の性能をしている。
Javaで変更不可能なコレクションを定義する事はできるが、既存のコレクションを変更不可にする事はできない。
Rubyでは定数も再代入可能(つまりクラス名定数も再代入可能)である。警告こそ出るが代入自体は防がれない。そういうモンだと諦めて。
一番重要な点
厳密で堅牢なコードを書けるJavaと異なり、Rubyは自由で簡潔なコードを書ける言語である。
設計思想が異なるため、RubyにJavaのような堅牢性のあるコードを求めるのは間違いだとはっきり言わせて欲しい。
Rubyに堅牢性求めるのはキーパーにキャッチャーのプロテクターをつけさせるようなもので、そんなアホな事をするよりもRubyの自由さを楽しむ方が健全である。
この記事はQiitaでより詳しく解説している
Qiita: https://qiita.com/midpolarnight/items/938b403a4c49...
JavaとRubyの演算子優先順位の違い
  • 細かいところで違うので同じ感覚で扱うのは危険。
  • 2〜14までのRuby演算子はメソッドとして実装されているため、メソッドを再定義する事で演算子の動作を変更できる。
    • これらは実際には演算子ではなく、特定名のメソッドを特例で演算子のように構文解析しているにすぎない。
順位JavaRuby備考
1x++   x--後置インクリメントはRubyにはない
2++x  --x
+x  -x  ~  !
+(単項)  !  ~前置インクリメントはRubyにはない
Javaの 単項- は 単項+ と同じ
3**Javaにはべき乗演算子はない Math.powメソッドを使う
4-(単項)Rubyの 単項- は 単項+ やべき乗よりも下
5*  /  %*  /  %乗除演算子
6+  -+  -二項加減演算子
7<<  >>  >>><<  >>>>>はRubyにはない
8<  >  <=  >=
instanceof
Javaの関係演算子はビット演算子よりも上
instance_ofはRubyではis_a?メソッドを使う
9==  !=Javaの等価演算子はビット演算子よりも上
10&&ビット演算子AND
11^^ビット演算子XOR
12||ビット演算子OR
一部のJavaドキュメントでXORと同列に扱われているが厳密には違う
13>  >=  <  <=Rubyの関係演算子はビット演算子よりも下
14<=>  ==  ===  !=  =~  !~Rubyの等価演算子はビット演算子よりも下
<=> や ===、正規表現演算子はJavaにはない
15&&&&論理演算子AND
16||||論理演算子OR
17..  ...範囲演算子はJavaにはない
18?  :?  :三項演算子
19=  +=  -=  *=  /=  %=  &=
^=  |=  <<=  >>=  >>>=
=  +=  ||=  他Rubyの代入演算子は = と他の演算子の合成構文
JavaとRubyの改行とコメント
// Javaの場合
// 一行コメント
/*
  複数行コメント
*/
/**
 *  JavaDoc型式コメント
 */
int x = 0;             // 構文は ; で終わりとみなす
int a = 1; int b = 2;  // ワンライナー構文は ; で区切る
obj
.method();             // ; が見つかるまでは1行として評価する
# Rubyの場合
# 一行コメント
=begin
  RD型式コメント(埋め込みドキュメント)
=end
x = 0         # 構文は行末で終わりとみなす
a = 1; b = 2  # ワンライナー構文は ; で区切る
obj.          # 行末がデリミタや演算子の場合は次行に構文解析を継続する
method
obj \         # 行末に \ がある場合は次行に構文解析を継続する
.method

unless、untilの混乱しない考え方

  • 基本的な考え方として false になったら、という考えを捨てると良い。
    • どちらも true になったらどうなるかで覚えると混乱しない。
# unless は条件を満たしたら実行しない式と考える
result = 1 if a && b      # a && b を満たしたら、左辺を実行する
result = 2 unless a && b  # a && b を満たしたら、左辺を実行しない

# 下記のような条件でメソッドを抜けるような場合は、処理を進める条件と考えても良い
return if a && b      # a && b を満たしたら、処理を抜ける
return unless a && b  # a && b を満たしたら、処理を進める

# unless を if not に置き換えて考える(もしくは物理的に置き換える)のは非推奨
return unless a && b  # a && b なら、処理を進める、というシンプルな構造に対し
return if not a && b  # 次の(a && b)を、満たさなければ、処理を抜ける、という脳内の二重定義から抜け出せていない
return if (a && b).!  # (a && b)を、満たさなければ、と脳内処理の順番通りに読める分、このアクロバティックな記法の方がマシになってしまう

# until はfalseの間ループする条件式ではなく、trueになったらループを抜ける条件式と考える
i = 0
p i while (i += 1) < 10  # iが10未満の間ループする
p i until (i -= 1) <= 0  # iが0以下になったらループを抜ける

# 例えば以下のような例がわかりやすい
a = (1..100).to_a
a.shift until a.empty?  # aが空になったらループを抜ける
# ゴールを明確にする分、whileよりもむしろループ制御に向く
この記事はQiitaでより詳しく解説している
Qiita: https://qiita.com/midpolarnight/items/5551d207c64c...

誤差を出さない実数計算の考え方

  • ゲーム製作において実数を完全に排除するのは難しい。不可能ではないが可読性やメンテナンス性が失われる可能性もまた高まる。ここでは実数を積極的に使いつつも誤差をゼロに近づける基本的な考え方を列挙する。
    • あくまでもゲーム製作における考え方であり、金融計算のような誤差の許されない場面で使われるものではない点に注意。
  • 実数は言うならば概数を表すフォーマットであり内部的には当然2進数で管理されている。そのため特に10進計算において誤差が出やすいが必ず誤差が出るわけではない。実数の詳しいフォーマット解説は他に譲るが、簡単に言えば小数点以下の数字において二進分数(分数の分母が2の乗数)であればほぼ誤差が発生しない。
    • 例を挙げれば0.5(1/2)、0.25(1/4)、0.125(1/8)などだ。これらを組み合わせた0.625(5/8)などもまた誤差のない実数になる。逆に0.1(1/10)は単精度なら必ず誤差が出る数字なので誤差を気にするなら使うべきではない。
    • バイナリで表すと0.1のIEEE754単精度は 0x3dcc_cccd で実際の値が0.10000000149011612となり誤差が発生する。倍精度なら 0x7fb9_9999_9999_999a で0.1となり一見誤差は出ないが仮数部の値を見ると怪しいものだ。これが0.125の場合単精度で 0x3e00_0000、倍精度で 0x3fc0_0000_0000_0000 となりいずれも仮数部の値は0となる。仮数部の使用幅が小さいほど正確性を担保できている状態なので誤差を少なくするには仮数部の使用幅を最小限にすればいい事になる。
    • なおRGSS3のFloat型は精度は環境に依存するIEEE754とされているが当方で確認したところ sprintf "%0.60f", 0.1 として出力すると 0.100000000000000005551115123125782702118158340454101562500000 となり単精度よりも誤差が少ない値が出てきた。倍精度以上なのは確実と思われる。
  • 単純な解決方法は0.01(つまり1/100)を基準にするのではなく、0.0009765625(つまり1/1024)を基準に考えるのがよい。例えば10%を表現したいなら102/1024を使うなどだ。
    • 実際問題としてゲーム製作において百分率は可読性の向上というメリットしか存在せず誤差というデメリットの方が問題としては遥かに大きい。コンピュータで数値を扱う場合これを1024分率として≒千分率とした方が遥かに扱いやすいのは間違いない。
    • 例えばドラクエなどの有名なRPGを例にとっても、アイテムドロップ率やレアモンスター出現率などの判定には1/16や1/256、1/4096のように二進分数が使われているケースが多い。
    • 10進数が人間に思考に合わせてコンピュータに誤差を強いるのだとしたら、二進分数はコンピュータに合わせて人間の思考に誤差を強いると言い換えてもよい。
  • 上記の考え方は実数を極力使わないという基本的な対策をした上で、なおかつ実数を使う必要がある場合の対応である点に注意。
    • 例えば命中率75%の乱数判定なら rand(100) < 75 というコードで実数を完全に排除できる。
    • もしくは誤差の出ない実数計算を行うのであれば実数計算を忌避する必要はないと言い換えることも出来る。無意味に誤差を恐れるのではなく仕様を理解して誤差と向き合うための一助になれば幸いである。
  • 色々書いたが、ツクールのデータベース自体が10進数実数で管理されているので完全に誤差をなくすのはほぼ不可能なので考えるだけ無駄とも言える。
    • それでも減らしたいなら50%、25%、12.5%などの数値をなるべく使うようにするのがオススメ。+10%を使わず+12.5%や+10.9375%を使うなど。ただプレイヤーにも分かりづらくなるのでやりすぎに注意。
浮動小数点数内部表現シミュレーター
https://tools.m-bsys.com/calculators/ieee754.php
FLP02-C. 精度の高い計算が必要な場合は浮動小数点数の使用を避ける
https://www.jpcert.or.jp/sc-rules/c-flp02-c.html






Ruby関係

独自クラスへのeach実装時のイディオム

  • eachは基本イテレータとなる。eachを実装したクラスはEnumerableをincludeできる。
    • Enumerableをincludeすれば様々なイテレータメソッドを使用可能になる。
  • each実装例としてRubyで型判別を行うためのDataTypeクラスを定義する。
# 例)型判別用クラス
class DataType
  include Enumerable  # 各種イテレータを使いたいのでEnumerableモジュールをインクルード
  
  def self.[](*ary)   # DataType[Integer, String...] の形式で使うためのクラスメソッド 必須ではないがあると便利
    new(ary)
  end
  
  def initialize(ary)
    @data = ary.freeze  # 渡された型情報は変更不可能(freeze)にする
  end
  
  def [](index)       # [n]の形式で内部データにアクセスするためのメソッド 必須ではないがあると便利
    @data[index]
  end
  
  def size            # 内部データのサイズを返すメソッド 必須ではないがあると便利
    @data.size
  end
  
  def each                                     # eachを独自実装する際のイディオム
    return to_enum(:each) unless block_given?  # ブロック無しならEnumeratorを返す
    i = -1
    max = @data.size
    yield(@data[i]) while (i = i.succ) < max   # eachに渡されるブロックパラメータをyieldで返す
    self                                       # 戻り値はself
  end
# なお今回のように内部の配列をラッピングするだけなら普通に@data.eachにラッピングした方が手っ取り早い
# def each
#   block_given? ? @data.each{|v| yield(v)} : @data.each
# end
  
  def ===(other)   # case文で判定するための===メソッド定義
    zip(other).all?{|a,b| a === b }
  end
  
  def ==(other)    # ==メソッド定義 内容の比較
    @data == other.to_ary
  end
  
  def eql?(other)  # eql?メソッド定義 hashの比較
    @data.eql?(other.to_ary) if self.class == other.class
  end
  
  def to_a         # 保持しているクラス配列と同じ新しい配列を返す
    @data.dup
  end
  
  def to_ary       # 暗黙の変換は@dataを返す(Enumerable#zipなど)
    @data
  end
  
  def chk(other)   # 厳密な型チェック用メソッド 型が合わない場合に例外を発生させる
    zip(other).all? do |a,b| 
      raise TypeError, "#{b} is not #{a}", caller()[4..-1] unless a === b  # raiseの第三引数でバックトレースをずらして型エラーが出たメソッドを返す
      true  # if文(unless)自体は何も値を返さないのでここにtrueがないとall?失敗とみなされ正しくイテレートできない
    end
  end
  
end

# 使い方
# DataType[型配列] または DataType.new(型配列) で新しいオブジェクトを生成する
# === で判定した場合は型配列と右辺を順番に === 判定した結果を返す 右辺が多い場合は残りは無視される
# chk で判定した場合は判定自体は===と同じだが型が合わない場合に例外を発生させる 厳密な型チェックを行いたい場合に有用
def test(id, name, method)
  DataType[Integer, String, Symbol].chk [id, name, method]
  # 型が合わない場合に例外が発生する。例外の位置はtestメソッドを呼んだ行になる
  # トレース位置が合わなくなるので必ずメソッド定義内で使うこと
end

p test(11, "test", :test)  # true
p test(:p, "test", :test)  # TypeError例外が発生
p DataType[Integer, String, Symbol].chk([:p, "test", :test])  # メソッド定義の外で呼んでしまうとエラー位置がずれる
参照: Ruby 1.9.3 リファレンスマニュアル > Enumerableモジュール

独自クラスへの<=>実装時のイディオム

  • <=>(宇宙船演算子、もしくは三方比較演算子)を実装したクラスはComparableモジュールをincludeできる。
    • Comparableをincludeすれば様々な比較メソッド(比較演算子)を使えるようになる。
    • Array#sortなどは比較に<=>を使用するので、実装クラスはsortで並べ替えできるようになる。
class Compare_Sample
  include Comparable
  
  attr :value
  def initialize(v)
    @value = v
  end
  
  def <=>(other)
    case
    when other.class != self.class then nil  # 比較できないならnilを返す ※重要
    when @value < other.value      then -1   # 自身(左辺)が小さければ-1を返す
    when @value > other.value      then  1   # 自身(左辺)が大きければ 1を返す
    else 0                                   # それ以外(両者が同じ)は 0を返す
    end
  end
end
参照: Ruby 1.9.3 リファレンスマニュアル > Comparableモジュール

メソッドの呼び出し制限 public、protected、private

  • Rubyのメソッド定義ではpublic、protected、privateの3つの呼び出し制限を定義できる。通常何も指定しなければ基本的にはpublicとなり制限なしに利用可能。
  • RubyではJavaで言うメソッドスコープ(アクセス権)の変更はサポートされていない。private、protected、publicはあくまでもメソッドの呼び出し制限(call limit)の変更となる。
    • Javaで言うならどちらかと言えばアノテーションに近い性質を持つ。つまりコンパイラに対する静的拘束力が無く、あくまでも開発者間での呼び出し方法に関する提案/合意/お約束にすぎない。
    • ここが特にJava経験者はつまずきやすい(と思われる)。まずはRubyはメソッドのアクセス権を変更できない事を覚えておきたい。
    • Javaのprivateが強固な鍵付き扉だとすれば、Rubyのprivateはウェスタンドア(西部劇の酒場のあれ)のようなもの。入ろうと思えば誰でも(sendで)入れるし、ドアを外して(publicで再宣言して)完全にオープンにする事もできる。
    • メソッド呼び出し制限に関してはVXAceヘルプには載っていないが、Rubyのリファレンスマニュアルには載っているので、RGSS3的には使う必要が無いという判断なのかもしれない。
  • `public` メソッドは制限無しに呼び出せる。他言語のpublicと同じ。特に指定しなければ基本的にこれになる。
  • `private` メソッドはレシーバを付けて呼び出せなくなる。結果として他のコンテキストから参照できなくなる。
    • 他のコンテキストから参照できなくなるのは結果であって実装ではない点がわかりづらい。同じコンテキスト内であってもselfレシーバをつけると呼び出せなくなる。
    • 結果としてサブクラスからは普通に呼び出せる。Javaで言うとprivateよりもprotectedに近いが、前述のようにスコープ(アクセス権)の変更ではなくあくまでもメソッド呼び出し制限の変更なので注意。
    • Class#initializeメソッドは自動的にprivateで定義される。initializeは他のクラスから呼び出すことはできないがサブクラスで再定義が可能である。
  • `protected` メソッドは、サブクラスのコンテキストからのみレシーバ付きで呼び出せるprivateメソッドとして動作する。
    • つまりselfレシーバで呼び出せる。例えばメソッドの引数に自身のクラスを取るような設計で、他のクラスからは呼び出されたくないが、privateだと引数をレシーバとして使えないような限定された状況で使用される。ようするに使い道はほとんどない。
  • ちなみにメソッドと異なりインスタンス変数は全てprivateになる。そのため他クラスからアクセスするにはアクセサの指定が必要になるが、サブクラスからはそのままアクセスできる。
# 呼び出し制限の設定方法
class Sample_Class
  def private_method_test1
  end
  private :private_method_test1 # メソッド定義の後に「private シンボル」の形で定義
                                # private_method_test1はprivateになる

  private                       # private行以降に定義したメソッドは全てprivateになる
  def private_method_test2      # privateになる
  end
  def private_method_test3      # privateになる
  end

  public                        # public行以降に定義したメソッドは全てpublicになる
  def public_method_test        # publicになる
  end

  protected                     # protectedの定義方法も同様
  def protected_method_test     # protectedになる
  end

  private def private_method_test4 # こういう書き方は1.9ではできない(メソッド定義はnilを返すため private nil と解釈される)
  end                              # Ruby2.1以降ならメソッド定義がメソッド名のシンボルを返すようになったため可能だがRGSS3では無理
end
参照: Ruby 1.9.3 リファレンスマニュアル > クラス/メソッドの定義 #呼び出し制限

制限されたメソッド(private、protected)を呼び出す方法
  • Rubyでは呼び出し制限されたメソッドを呼び出す方法は色々あるので、呼び出しを制限するメリットが薄い。
class MyClass
  include MyModule  # includeはModuleクラスのprivateメソッドなのでクラス定義内(MyClassコンテキスト)なら問題なく呼び出せる
end

MyClass.include MyModule0  #=> エラー privateメソッドなのでこういう呼び出し方はできない

MyClass.send(:include, MyModule1)          # sendや__send__メソッドを使えば呼び出し制限に関係なく呼び出せる
MyClass.instance_eval{ include MyModule2 } # instance_evalでコンテキスト内に入れば普通に呼び出せる
MyClass.class_eval{ include MyModule3 }    # class_evalやmodule_evalでも可
MyClass.method(:include).call(MyModule4)   # Methodオブジェクトをcallしても呼び出せる
MyClass.class_eval{ public :include }      # includeをpublicで再定義すれば
MyClass.include MyModule5                  #=> 直接呼んでもエラーにならない

aliasとalias_methodの違い

特徴aliasalias_method備考
種別キーワードModuleのメソッドメソッドはオーバーライド可能
スコープ・クラス定義コンテキスト・クラス定義コンテキスト
・メソッド実行コンテキスト
引数・識別子
・シンボル
・シンボル
・文字列
・変数
引数の区切り(スペース),(カンマ)
動的引数不可可能
戻り値nil`self`
対象・グローバル変数
・クラスメソッド
・インスタンスメソッド
・インスタンスメソッド
※擬似的に特異メソッド
特異クラスのコンテキストでalias_methodすれば特異メソッドになる
共通仕様
別名でメソッドを複製する。instance_methodsなどで増えている事を確認できる。
参照のみのコピーではなく完全にコピー(deep copy)となるので、オーバーライドするメソッドのバックアップとしても利用できる。
複製したメソッドのアクセス制限は引き継ぐ。private_instance_methodsなどで引継ぎを確認できる。
メソッド情報
別名メソッドのメソッド情報は、Method#name は別名メソッド名になるが、Method#inspect に含まれる名前は元のメソッド名になる。callした際はちゃんと別名の方が呼ばれる。

無名クラスを作る

a = Class.new         # 無名クラスを生成
a = Class.new(Array)  # 親クラスを継承した無名クラスも生成可能

a = Class.new do      # 無名クラスにブロックを渡してメソッドを定義する
  def noname_method
  end
end

p a.class.name  #=> nil 無名クラスには名前が無い
MyClass = a     # 無名クラスを定数に代入
p a.class.name  #=> MyClass 定数名が無名クラスのクラス名になる

# Rubyのclass定義文も内部的にはClass.newで無名のサブクラスを作成し、定数に代入しているにすぎない
# 下記三つはほぼ同じ意味になる
class MyClass < MySuperClass; end

MyClass = Class.new(MySuperClass){ }  # Class.newのブロックは生成された無名クラスのコンテキストで動作する

MyClass = Class.new(MySuperClass)
MyClass.class_eval{ }                 # Class.newのブロックはclass_evalと同じコンテキストで動作する

# Class.new形式でのクラス定義例
MyArray = Class.new(Array)
p MyArray.ancestors  #=> [MyArray, Array, Enumerable, Object, Kernel, BasicObject]

# class定義文とClass.newの違い
class MyClass
  def method1; end
end
p MyClass.instance_methods(false)  #=> [:method1]
class MyClass
  def method2; end
end
p MyClass.instance_methods(false)  #=> [:method1, :method2] class定義文はクラスを開く

MyClass = Class.new{ def method1; end }
p MyClass.instance_methods(false)  #=> [:method1]
MyClass = Class.new{ def method2; end }
p MyClass.instance_methods(false)  #=> [:method2] Class.newはクラスを上書きする
class MyClass
  def method1; end
end
p MyClass.instance_methods(false)  #=> [:method2, :method1] クラスを開く場合はclass定義文を使う
無名クラスに定数名を与える際の注意点
class MyClass
  MyArray = Class.new(Array)  # クラス定義コンテキストでは問題なく動作する
  def main
    MyHash = Class.new(Hash)  #=> dynamic constant assignment(SyntaxError) メソッド実行コンテキストでは例外が出る
    # Rubyではメソッド実行コンテキストでの定数への代入は構文解析時にエラーする仕様となっている
    # これはメソッドが実行されるたびに定数が再定義されるのを防ぐため
    # 無名クラスに定数名を与える場合は注意が必要

    # Module#const_setで無理やり定義する事もできる(非推奨)
    h = Class.new(Hash)            # 一旦ローカル変数に代入
    Object.const_set(:MyHash, h)   # Objectへのconst_setでグローバルスコープにMyHashの名前を定義する

    # 構造体クラスを定義するStruct.newもClass.new同様、内部的には無名クラスの生成を行う
    # Structの場合、第一引数で指定したサブクラス名を自身の名前空間に自動的にconst_setしてくれるのでエラーにはならない
    Struct.new("MyStruct", :a, :b) #=> Struct::MyStruct を生成

    # evalで無理やり定義する事もできる(非推奨)
    eval("MyHash = Class.new(Hash)")        # エラーにならない
    class_eval("MyHash = Class.new(Hash)")  # class_evalでも可
    class_eval{ MyHash = Class.new(Hash) }  # エラーになる ブロックは構文解析時に引っかかる
  end
end
  • メソッド実行コンテキストでの定数への代入は、そのメソッドが初期化メソッドであるなど明確に一度しか実行されない事を理解した上で行うのであれば、const_setやevalなどで構文解析エラーを回避しても問題は少ないと言えなくもないが、そのような構造であるならばクラス定義コンテキストで定義した方が可読性や保守性の観点から優れるので、あえてメソッド実行コンテキストで行う理由は無い。つまり実用性の観点からも極めて非推奨である事が理解できる。

特異クラスに名前を付ける

  • 特異クラスも普通のクラスでしかないというわかりやすい例。
    • 中身は結局のところ動的に生成される無名クラスなので命名してやれば普通のクラスとして扱える。
a = Object.new
class << a   # aの特異クラスを開く
  def test1  # aの特異メソッドを定義(aの特異クラスのインスタンスメソッド定義)
  end
end
# 特異メソッドが追加されている  
p a.singleton_methods(false)  #=> [:test1]

SingletonClass = a.singleton_class  # 特異クラスにエイリアスを定義
class SingletonClass                # エイリアスでクラスを開く
  def test2                         # エイリアスにメソッド定義
  end
end
# エイリアスで定義したメソッドが追加されている  
p a.singleton_methods(false)  #=> [:test1, :test2]

日本語の定義名

  • フィールド名、メソッド名、シンボル名などには日本語名を使用できる。この機能はRubyの標準機能として実装されている。
  • クラス名やモジュール名、定数は先頭文字が英大文字である必要があるが、先頭文字以外には日本語使用可能。
  • RGSS3でも勿論、問題なく動く。役に立つかどうかは・・・ね?
    • メタデータによる追加能力を実装する場合に日本語シンボルを判定に使うとソース可読性が向上するメリットはあるので使い方次第。
module J日本語モジュール
  def self.日本語メソッド(引数)
    引数 * 5
  end
end
p J日本語モジュール.日本語メソッド(70) # 350 と出力される。

クラス定義、メソッド定義のrescue/ensure節

# クラス定義はrescue/ensure節を定義して例外処理を行うことができる
class MyClass
rescue
else
ensure
end

# モジュール定義もrescue/ensure節を定義して例外処理を行うことができる
module MyModule
rescue
else
ensure
end

# メソッド定義もrescue/ensure節を定義して例外処理を行うことができる
def mymethod
rescue
else
ensure
end


ObjectSpaceでオブジェクトを調べる

# ObjectSpace.count_objects(result_hash = {}) -> Hash
# オブジェクトの数をカウントしてハッシュ形式で返す(下記はキーワードの意味一覧)
ObjectSpace.count_objects.each{|k,v| p k}
  #=> :TOTAL     オブジェクトの総数(FREEも含む)
  #=> :FREE      開放済みオブジェクトの数
  #=> :T_OBJECT  通常オブジェクトの総数
  #=> :T_CLASS   クラスオブジェクトの総数
  #=> :T_MODULE  モジュールオブジェクトの総数
  #=> :T_FLOAT   浮動小数点数オブジェクトの総数
  #=> :T_STRING  文字列オブジェクトの総数
  #=> :T_REGEXP  正規表現オブジェクトの総数
  #=> :T_ARRAY   配列オブジェクトの総数
  #=> :T_HASH    連想配列(ハッシュ)オブジェクトの総数
  #=> :T_STRUCT  構造体オブジェクトの総数
  #=> :T_BIGNUM  大きい数字オブジェクトの総数
  #=> :T_FILE    ファイルオブジェクトの総数
  #=> :T_DATA    DATAオブジェクトの総数
  #=> :T_COMPLEX  複素数オブジェクトの総数
  #=> :T_RATIONAL 有理数(分数)オブジェクトの総数
  #=> :T_NODE    構文木ノードオブジェクトの総数
  #=> :T_ICLASS  includeクラスオブジェクトの総数

# ObjectSpace.each_object(klass) -> Enumerator
# ObjectSpace.each_object(klass){|o| ...} -> Integer
# 全てのオブジェクトから o.is_a?(klass) 関係にあるオブジェクトをイテレートする。イテレートした数を返す。

# 例)
a = ObjectSpace.each_object(Module).select{|o| o.name }.sort_by(&:name)  # 無名クラスを除外したリスト
# クラスオブジェクトのリストなのでそのままではソートできない点に注意 上記はsort_by(&:name)でオブジェクト名順にソートしている
r = a.select{|o| o.name[0..2] == 'RPG' }   # RPGで始まるクラス/モジュールのリスト
e = a.select{|o| o <= Exception }          # 例外クラスのリスト
a = a - r - e
m = a.select{|o| o.class == (Module) }  # モジュールのリストアップ
c = a.select{|o| o.class == (Class) }   # クラスのリストアップ
p c,m  # クラス、モジュールのリストを出力

逆輸入できそうも無い機能

prepond
色々探してみたが継承チェーンを直接書き換える方法は見つからなかった。
ancestorsは内部配列の複製品らしく、変更しても影響が無かった。
refine
流石にスコープを限定したモンキーパッチは不可能。
似たような動きを実装することはできるが、元のメソッドをごねごねしないといけないのでメソッドの使用目的と相反する。






RGSS3関連

RGSS3のスクリプト編集は全てモンキーパッチ

  • モンキーパッチとは既存のクラスをオープンクラスの仕組みで再定義(オーバーライド)する事。
    • 簡単に言えば既存ライブラリの修正/変更/機能追加などのパッチを当てることを言う。
  • RGSS3に機能を追加する場合、それが完全新規の機能追加だとしても、それらをゲーム内に反映させる部分で必ずモンキーパッチが必要になる。またライブラリにバグがあったとしても公式からの修正パッチは期待できないため、自分で修正する必要がある。
  • 一般的なプログラミングにおいてはモンキーパッチは最低限にするべきという風潮がある。が、RGSS3においてはそれらは無視してよい。そもそもモンキーパッチ以外に選択肢が無いのだから考えるだけ無駄だし建設的ではない。
    • ただし無闇にモンキーパッチするべきではないというのはRGSS3であっても変わらない。特に複数の公開ライブラリを利用する場合には競合に注意する必要がある。
    • オブジェクトへのextendなど、他に影響を与えない強力なモンキーパッチ手法も存在するが、そもそも多重extendはバグの温床であるため安易に多用するべきではない。

ラップメソッドを定義して擬似的にオーバーロードを実装する

  • Ruby1.9.2では型情報が存在しないためJavaのようにメソッドのオーバーロードは定義できない。
    • 逆に言うと引数の型でエラーになる事がないので、複数の型に対応したメソッドは最初から普通に書ける。
  • 引数を判別して処理を振り分けるラップメソッドを定義する事で、擬似的にオーバーロードを実装しようという試み。
  • 実行コストは重くなるので無闇に使うのは非推奨。逆に実行コストが気にならなければオーバーロードの利点を享受できる。
  • RGSS3でオーバーロードが必要になる状況というのがあまり思いつかないので備忘録に突っ込んでおく。
# 経験値取得のラップメソッド例
class Game_Party
  def gain_exp(target = :battle, exp)
    case target
      when Game_Actor then target.gain_exp(exp)
      when :battle    then battle_members.each{|actor| actor.gain_exp(exp)}
      when :all       then all_members.each{|actor| actor.gain_exp(exp)}
      when :leader    then leader.gain_exp(exp)
      when Fixnum     then $game_variables[target] += exp
    end
  end
end
# 第一引数が アクター なら、そのアクターに経験値を加算する
# 第一引数が :battle  なら、バトルメンバー全員に経験値を加算する
# 第一引数が :all     なら、メンバー全員に経験値を加算する
# 第一引数が :leader  なら、リーダー(先頭アクター)に経験値を加算する
# 第一引数が 整数     なら、第一引数を変数IDとみなして変数に経験値を加算する
# 第一引数を省略した場合、:battle として処理する
# RubyはJavaと異なりデフォルト式を与えた引数の後にもデフォルト式のない引数を定義できる

正規表現サンプル

/^[+-]?\d+(_\d+)*(\.\d+(_\d+)*)?([eE][+-]?\d+)?$/  # 浮動小数点数リテラル文字列にマッチ to_f で変換可能 指数表現や-0.0にも対応
/^[+-]?0+$/                       # 0にマッチ これは以降の正規表現におけるイレギュラーを排除する重要な役目がある
/^[+-]?0x\h+(_\h+)*$/             # 16進整数リテラル文字列にマッチ to_i(16) もしくは to_i(0) で変換可能
/^[+-]?(0o|0)[0-7]+(_[0-7]+)*$/   # 8進整数リテラル文字列にマッチ to_i(8) もしくは to_i(0) で変換可能
/^[+-]?0b[01]+(_[01]+)*$/         # 2進整数リテラル文字列にマッチ to_i(2) もしくは to_i(0) で変換可能
/^[+-]?(0d\d+|[1-9]\d*)(_\d+)*$/  # 10進整数リテラル文字列にマッチ to_i もしくは to_i(10) もしくは to_i(0) で変換可能
/\:([^\s\d]\S*)/                # シンボル文字列にマッチ($1は : を含まない、to_sym用)
/\#\{(.*)\}/                    # 式展開の中身にマッチ(instance_eval($1)用)
/<#{txt}>/                      # <> で囲まれた文字列txtにマッチ(メモ欄マッチング用)
/<#{txt}[\s\=]+([-]?[\d\._]+)>/ # <> で囲まれた文字列txt=数値の形式にマッチし、$1で数値を得る、メモ欄マッチング用)






旧記事供養所

  • 初期の頃に書いた古い内容の記事。一部間違っていたり説明が不十分だったりするところもあるので注意。

通常クラスの再定義

  • 同名でクラス定義すれば再定義できる。メソッドも同名メソッドを定義すれば内容を変更できる。
#元の定義
class Window_DebugRight < Window_Selectable
  def item_max
    return 10
  end
end

#再定義
class Window_DebugRight  # 再定義には継承元指定は不要
  def item_max           # 書き換えたいメソッドを定義
    return 15            # 数値の変更
  end

  def item_max2          # 元のクラスにないメソッドを定義すれば新しく追加される
    return 10
  end
end

元のコードを利用したい場合

  • 元コードを利用しつつ、新しいコードを追加したい場合はエイリアスを使う
#元の定義
class Window_DebugRight < Window_Selectable
  def refresh
    contents.clear
    draw_all_items
  end
end

#エイリアスを使った再定義
class Window_DebugRight              # 再定義には継承元指定は不要
  alias _original_refresh  :refresh  # 元のコードに別名を設定
  def refresh
    new_code1                        # 元コードの前に実行したいコードを記述
    _original_refresh                # 元のコードを実行
    new_code2                        # 元コードの後に実行したいコードを記述
  end
end
  • alias定義の :refresh はシンボルにする必要はないがVXAce内エディタで可読性が向上するので推奨。
  • 元定義メソッドに引数がある場合、引数もちゃんと指定しないと引数なしメソッドとして再定義されてしまうので注意。

クラス/モジュール/特異クラスの定義

  • クラスは主に同じ目的で使用するメソッドや変数をまとめて管理するために使われる。クラスは継承関係を築くことができる。
    • 例えばGame_BattlerのサブクラスとしてGame_ActorとGame_Enemyがあり、Game_Battlerではアクターとエネミーの共通項であるhp、mpなどのパラメータやスキルを使うためのメソッドなどが定義され、Game_Actorではアクター専用項であるlevelやclassなどを参照でき、Game_enemyではエネミー専用項である行動パターンやドロップアイテムなどを参照できる。
    • このように共通の変数やメソッドを持つクラスは共通項のクラスを継承した個別クラスを作ることで、管理を一元化できる。
    • クラスはnewメソッドでインスタンスを生成して使用する。クラスは同じクラスであっても複数のインスタンスを持つ。例えばGame_Actorならアクター一人一人が個別のインスタンスを持つ。クラスに定義されたインスタンス変数はこれらインスタンスごとに個別の値を持つ。
      • クラスのインスタンスが生成されるとnewメソッドの引数がそのままinitializeメソッドに渡されて実行される。基本的にインスタンス変数はinitializeメソッドを定義して初期化する事が推奨される。初期化されないインスタンス変数を参照した場合 nil になる。
  • モジュールはRGSS3では主に二つの用途で使われる。
    • 特異メソッド(後述)を定義して共有ライブラリのように利用する関数型モジュール。RGSS3の組み込みモジュールは全て関数型。
    • 共有メソッドを定義して複数のクラスにincludeして使用するMix-inモジュール。
      • クラス変数やインスタンス変数を持つModuleをMix-inした場合これらは共有されてしまうので注意が必要。Mix-inモジュールとして実装する場合にはこれらの変数は基本的に使用非推奨。
  • Moduleクラスは Classクラスと異なりインスタンス生成できないが、クラス変数のようにインスタンス変数にアクセスできる。
    • ただしClassと異なりinitializeメソッドは呼ばれないため、手動で初期化メソッドを呼ぶ必要がある。初期化されない変数はnilを返す。
    • インスタンス変数にはアクセサも指定できるが、アクセサは特異クラス(後述)に定義する必要がある。
  • Moduleクラスは Classクラスと異なり継承できない。
  • Moduleクラスは include で他のクラスにMix-inする事が出来る。これらはクラスのスーパークラスのように振舞う。
  • Moduleクラスは extend で他のクラスにMix-inした場合、クラスの特異クラス(後述)のように振舞う。
    • Object#extend でインスタンスにMix-inする事が出来る。モジュールのインスタンスメソッドをインスタンスの特異メソッドとして追加する。これはインスタンスの特異クラスへのincludeとほぼ同義、違いはMix-in時のコールバックメソッドの違いぐらい。
class Test_Class
  attr_accessor :instance_var1  # インスタンス変数の読み取り、書き込みのメソッドを定義する
  attr_reader   :instance_var2  # インスタンス変数の読み取りメソッドのみを定義する
  attr_writer   :instance_var3  # インスタンス変数の書き込みメソッドのみを定義する
  def initialize(a, b, c, d = :class_var)
    @instance_var1 = a;
    @instance_var2 = b;
    @instance_var3 = c;
    @@class_var1 = d
  end
  def class_var
    @@class_var1
  end
end

a = Test_Class.new(50, 100, 200)
p a.instance_var1  # 50 を出力
p a.class_var      # :class_var を出力
b = Test_Class.new(1, 2, 3, :b_var)
p b.instance_var1  # 1を出力 インスタンス変数はインスタンスごとに値を持つ
p b.class_var      # :b_var を出力
p a.class_var      # :b_var を出力 クラス変数はインスタンスに関わらずクラスで共有する

module Test_Module
  class << self  # モジュールのattr〜メソッドは特異クラスに定義する必要がある
    attr :instance_var1, true  # attr_accessor は attr インスタンス変数名, true の形でもOK
    attr :instance_var2        # attr_reader   は attr インスタンス変数名 の形でもOK
  end
    def init(a, b, c, d = :class_var)
      @instance_var1 = a;
      @instance_var2 = b;
      @instance_var3 = c;
      @@class_var1 = d
    end
    def class_var
      @@class_var1
    end
end

Test_Module.init(50, 100, 200, :test_var)
p Test_Module.instance_var1  # 50 を出力
p Test_Module.class_var      # :test_var を出力

class Include_Test
  include Test_Module
end

class Extend_Test
  extend Test_Module
end

a = Include_Test.new
b = Extend_Test.new
c = Object.new
c.extend Test_Module

a.is_a?(Test_Module)  # true を返す includeは継承関係が構築される
b.is_a?(Test_Module)  # false を返す Module#extendは継承関係は構築されない(b.classのsingleton_classに追加されるため反応しない)
c.is_a?(Test_Module)  # true を返す Object#extendは継承関係が構築される(c.singleton_classに追加されるため反応する?)
  • 特異クラスはRuby独自の仕組みで、クラス、モジュール、インスタンスなどが持つ事ができる特殊な内部クラス。
    • クラスに定義された特異クラスはサブクラスへ継承されるが、クラスのインスタンスからはアクセスできない。
    • モジュールに定義された特異クラスは include で Mix-in されない。
    • インスタンスに定義された特異クラスはそのインスタンスのみのスーパークラスのように動作する。
  • 特異クラスに定義したメソッドは特異メソッドとなる。
class Test_Class
  class << self  # 特異クラス定義
    attr_accessor :instance_var11  # インスタンス変数の読み取り、書き込みのメソッドを定義する 
    def init(a, b, c, d = :class_var)
      @instance_var11 = a;
      @instance_var12 = b;
      @instance_var13 = c;
      @@class_var11 = d
    end
    def class_var
      @@class_var11
    end
  end
end

class Test_Class2 < Test_Class
end

Test_Class2.init(1000, 2000, 3000, 5000)
p Test_Class2.instance_var11  # 1000 を出力 特異クラスを定義していないが継承されている
p Test_Class2.class_var       # 5000 を出力
p Test_Class.class_var        # 5000 を出力 クラス変数は継承前のクラスとも共有しているため上書きされる
  • RGSS3組み込みモジュールは上記で言う関数型モジュールで定義されているため、メソッドを再定義する場合は特異メソッドの再定義を行う必要がある。
class << DataManager # 特異クラス定義でモジュールを開く
  alias _original_make_save_header  :make_save_header # クラス指定なしで普通にエイリアス指定
  def make_save_header # 特異クラス定義中なので特異メソッドにクラス指定が不要
    header = _original_make_save_header
    header[:memo] = "メモ内容" # セーブデータのヘッダーにmemoを追加するサンプル
    header
  end
end

メソッド内で自身のメソッド名の取得

  • メソッド内から Kernel.#__method__ や __callee__ を呼び出せばメソッド名を取得できる。
def test_method
  p [__method__, __callee__]  #=> [:test_method, :test_method]
end

alias test_method2  :test_method

test_method2  #=> [:test_method, :test_method] aliasのメソッドは元のメソッド名を出力
# なおエイリアス名の方でメソッド名を取得する方法は少なくともRGSS3では無い模様

メソッド内からレシーバを参照

  • 普通はselfで参照できるため意味がない。
    • 動的メソッド定義のようなselfでレシーバを参照できない状況でレシーバを参照したい場合には使える場合がある。
def receiver  
  # method(__method__)でメソッド自身のMethodオブジェクトインスタンスを取得し、レシーバを返す
  method(__method__).receiver
end

ブロック内からレシーバを参照する

  • ブロック内からはレシーバを直接参照できないため、代替手段が必要。
    • 通常必要な情報はブロック変数に渡されるためブロック内でレシーバが必要になるケースはほぼ無い。
    • 必要に駆られて調べたが最終的にレシーバ無しで実装できたため不要になった情報。
    • 一応トリビアとして残しておくがレシーバを要求しなくても良い実装を模索する方が恐らくスマート。
# レシーバをローカル変数に入れておく 簡単だがProc化した際に変数名がわかりづらい
array = [1, 2, 3, 4, 5]
array.select{ |v| (array[0] + v).even? }

# tapを噛ませる ブロックは深くなるがブロックの外に変数を必要としない
[1, 2, 3, 4, 5].tap{ |r|                  # tapはレシーバ自身をブロックに渡しレシーバ自身を返す
  break r.select{ |v| (r[0] + v).even? }  # ブロックの評価結果を返したい場合はbreakを使う
}

# instance_evalを噛ませる レシーバのコンテキストで実行されるのでselfでレシーバを参照可能
[1, 2, 3, 4, 5].instance_eval{
  select{ |v| (self[0] + v).even? }
}

Mix-inモジュールはJavaのデフォルト付きインターフェイスのように振舞う

  • Mix-inモジュールはJavaのインターフェイスのように継承関係を構築する。モジュールをMix-inしたクラスは is_a? や kind_of? にモジュールを渡せば true が返ってくる。
  • Mix-inモジュールのメソッドはJavaのインターフェイスにおけるデフォルト付きメソッドと同じように機能する。複数の異なるクラスで共通の実装を共有したい場合に、共通するコードをまとめて一括で管理することもできるし、一部のクラスで仕様ズレが発生してもそこだけメソッドをオーバーライドすれば解決できる。
module Memo_reg
  def include?(txt)
    /<#{txt}>/i =~ @note
  end
  def get_meta(txt)
    $1 if /<#{txt}\s(.+)>/i =~ @note
  end
end

class RPG::BaseItem
  include Memo_reg
end

class RPG::Class::Learning
  include Memo_reg
end

$data_skill[20].is_a?(Memo_reg)          # true を返す Mix-inしたクラスとモジュールは継承関係にある
$data_skill[20].include?("encount_off")  # スキル20のメモ欄に <encount_off> の記述があれば true を返す
s = $game_party.members[0].class.learnings[0]  # 先頭アクターのクラス習得スキル欄の一番上の項目を取得
s.is_a?(Memo_reg)  # true を返す
s.get_meta("mag")  # 取得した項目のメモ欄に <mag 20> のような記述があれば "20" を返す

参照: RGSS3プログラミング#メモ欄からメタタグを参照する

明示的メソッドで演算子の優先順位を変える

  • Rubyの演算子はメソッドとして定義されている。これを演算子として記述すると演算子優先順位に従って処理されるという特徴がある。
  • 明示的にメソッド呼び出しとして演算子を呼び出すことで演算子優先順位よりも優先して処理することができる。
  • 一般的なプログラミングの範囲においてこのハックは役に立たないが、カッコによる優先順位や構文解析時のエラーなどを極限まで抑えて、ただ演算メソッドの羅列を'.'でjoinするだけで演算子の優先順位をも無視した完全に動作する単純な計算式文字列を生成できる点は注目に値する。
    • これはメモ欄などを用いた独自の計算式において構文エラーを検出する必要性を極限まで減らし、解析コーディングの労力をほとんど無視できる強力なイディオムになりうる。
    • とはいえ、丸ごとevalに投げてしまえば普通の計算式も構文解析の必要性は皆無なので、RGSS3環境という事を考えると実用性は低いと言わざるを得ない。
p 1 + 3 * 4    #=> 13
p 1.+(3) * 4   #=> 16 明示的メソッド処理優先
p 1::+(3) * 4  #=> 16
p 1.+(3).*(4)  #=> 16 両方メソッド処理なので左辺から順に処理される
p (1 + 3) * 4  #=> 16 当たり前だが普通にカッコで囲んだ方が簡単で分かりやすい

p 1.+ 3 * 4    #=> 13 優先順位は変化しない
p 1 +(3) * 4   #=> 13 優先順位は変化しない

SymbolとStringの比較

  • 文字列とシンボルは同じように使えるが比較では(当然だが)同一とはみなされない。
    • シンボルを文字列に変換、もしくは文字列をシンボルに変換して比較する事で同一とみなす事もできる。
    • シンボルを文字列に変換する場合、変換のたびに新しい文字列オブジェクトが作成されるため負荷とメモリ効率が悪い。
    • 文字列をシンボルに変換する場合、作成されるシンボルオブジェクトは文字列ごとに一度だけだが、シンボルオブジェクトとシンボル名を結びつけるためのシンボルリストは(Ruby1.9.2時点では)GC対象とならないため使われる予定のない不要なシンボル情報が増え続けてメモリを圧迫する問題がある。
    • 他には正規表現によるマッチングが挙げられるが、内部的にはSymbol#to_s.matchを呼び出しているため遅くなるだけでto_sと変わらない。
  • RGSS3ではシンボル情報はセーブデータにも記録されるので、String#to_sym を使い続けるのはお勧めできない。
# SymbolとStringの比較
:name == "name"         #=> false 文字列とシンボルの比較は equal?、eql?、==、=== いずれも false となる
:name == "name".to_sym  #=> true 文字列をシンボルに変換して比較
"name" == :name.to_s    #=> true シンボルを文字列に変換して比較

# GCを考慮すると短時間もしくは比較文字列の種類が少ない場合には String#to_sym の方が良い。
# 逆に長時間もしくは比較文字列の種類が多い場合には Symbol#to_s の方がメモリへの悪影響は少ない。
# 動作するプログラムに応じてケースバイケースで使い分ける必要がある。

XOR演算子

  • RubyのXOR(排他的論理和)演算子は ^ だけしかなく、TrueClass、FalseClass、NilClass、Integer(Fixnum、Bignum)にしか定義されていない。またandなどと異なり再定義可能。
    • ^ はIntegerの場合は & や | と同様ビット演算子として働く。そのため右辺にはIntegerしか置けない。
    • ^ はTrueClass、FalseClass、NilClassの場合は論理演算子として働く。そのため右辺にはあらゆるオブジェクトを置ける。
# xor演算子
true  ^ true  #=> false 
true  ^ false #=> true
false ^ true  #=> true
false ^ false #=> false 
true  ^ nil   #=> true
nil ^ true    #=> true
true  ^ 100   #=> false  オブジェクトはtrueとして扱われる
16 ^ 8     #=> 10000 ^ 01000 = 11000 = 24
16 ^ 16    #=> 10000 ^ 10000 = 00000 = 0
100 ^ true #=> can't convert true into Integer   Integerの^演算子は右辺がIntegerではない場合例外を出す
100 ^ nil  #=> can't convert nil into Integer    Integerの^演算子は右辺がIntegerではない場合例外を出す
100 ^ "5"  #=> can't convert String into Integer Integerの^演算子は右辺がIntegerではない場合例外を出す
100 ^ 5.5  #=> can't convert Float into Integer  Integerの^演算子は右辺がIntegerではない場合例外を出す
参照: Ruby基礎/応用#Arrayにxor演算子を組み込む

Enumerableに降順ソート追加

  • これがあるとゲーム製作に超便利というかなんで標準で無いんですかね?
    • と思って実装してみたら reverse! でええか・・・ってなった話。
module Enumerable
  def reverse_sort_by
    sort_by {|x| -yield(x)}
    # -yield(x) は yield(-x) でも可だがブロックパラメータが変化してしまうので非推奨
  end
end

# 10000要素のランダム配列のソート速度 100回平均
a.sort                       #=> 0.1080s
a.sort.reverse!              #=> 0.1080s
a.sort_by{|a|  a }           #=> 0.7564s
a.sort_by{|a| -a }           #=> 0.7889s
a.sort_by{|a|  a }.reverse!  #=> 0.7605s
a.reverse_sort_by{|a| a }    #=> 0.8289s
# 速度的メリットは無いが $game_party.members.reverse_sort_by(&:atk) のような書き方ができるはメリット

&: SymbolProc

  • この記法には正式名称も俗称も存在しない。当Wikiでは便宜上、SymbolProcによる短縮記法、または単に短縮記法と呼ぶ。
# 記法例
(1..20).map(&:to_s)
# 下記コードと同じ意味
(1..20).map{ |var| var.send(:to_s) }

# 引数に(&なんちゃら)を書くのはメソッドに渡すProcオブジェクトをブロックに変換する場合の書き方
# 例えば下記のコードは記法例と同じ動作をする。
test_proc = Proc.new{|var| var.to_s }  # ブロックをprocオブジェクトにして変数に代入
(1..20).map(&test_proc)                # 引数としてprocオブジェクトを渡す

# 最初の記法例は & に続けてシンボルを渡している。
# & を付けて渡した場合、それがprocオブジェクトで無い場合は暗黙的にto_procメソッドを呼び出す
# シンボルのto_procメソッドは { |var| var.send(self) } と同等のProcオブジェクトを返す。つまり最初の短縮記法は以下の様に展開される。
(1..20).map(&:to_s)            # は
(1..20).map(&(:to_s.to_proc))  # と解釈され
(1..20).map{ |var| var.send(:to_s) }  # に展開されて評価される
  • さて、こんな小難しい話がRGSS3プログラミングと何の関係があるのかと言うと、たまにこういう書き方をしているスクリプトがあって、それを解読しようにも少なくとも初期執筆時点の2022年時点ではRubyの解説ページにたどり着くのが非常に難しいという問題があった。2024年現在では「&:」でggれば容易に辿り着けるためこの解説の意味も失われているのだが折角書いたので備忘録に突っ込んでおく事にする。
    • この検索問題はgoogle検索に本格的なAI統合が行われた事で最も恩恵を受けた事柄の一つである。当時はAIについて色々言われていたが少なくともRubyistにとっては非常に重要な分水嶺だったと言える。
  • 一応公式オンラインヘルプにも書かれているのだが非常にわかりにくい。実際凄く便利になると言う使い方の例は思いつかないが、少なくともこの知識が今後の役に・・・立ったらいいなぁという希望的観測。

追記:
 sort_byやmax_byを使うようなケースでは短縮記法の方が可読性が向上する事が多いようだ。例えばアクターリストを攻撃力順で昇順ソートするような場合など。ただし降順ソートでは短縮記法は使えないためreverseで反転させるか使用時に逆順で参照する必要がある。もしくは前述のreverse_sort_byを使う。
# sortでのatk昇順ソート
$game_party.battle_members.sort{ |a, b| a.atk <=> b.atk}
# sort_byでのatk昇順ソート
$game_party.battle_members.sort_by{ |actor| actor.atk }
# SymbolProcによる短縮記法でのatk昇順ソート
$game_party.battle_members.sort_by(&:atk)

# sortでのatk降順ソート
$game_party.battle_members.sort{ |a, b| b.atk <=> a.atk}
# sort_byでのatk降順ソート
$game_party.battle_members.sort_by{ |actor| -actor.atk }
# SymbolProcによる短縮記法でのatk降順ソート
$game_party.battle_members.sort_by(&:atk).reverse!
$game_party.battle_members.reverse_sort_by(&:atk)
# 昇順ソートした配列をreverse_each
$game_party.battle_members.sort_by(&:atk).reverse_each{|actor| p actor.atk}  # 降順に出力
# 昇順ソートした配列を逆順参照
$game_party.battle_members.sort_by(&:atk)[-1]  # atkが一番高いメンバーを参照


# SymbolProcによる短縮記法での敏捷が最も高いアクターの取得
$game_party.battle_members.max_by(&:agi)

# SymbolProcによる短縮記法での大文字小文字を無視した昇順ソート
["Array", "proc", "lambda", "Range"].sort_by(&:downcase)  #=> ["Array", "lambda", "proc", "Range"]
["Array", "proc", "lambda", "Range"].sort                 #=> ["Array", "Range", "lambda", "proc"]
# Enumerable#inject, Enumerable#reduce の引数にはProcオブジェクトだけでなくメソッドシンボルを直接渡すことができる。
# このようにシンボルを直接渡せるのはこの二つのメソッドだけ
[1,2,3].inject(0, :+)  #=> 6
[1,2,3].reduce(9, :-)  #=> 3
[1,2,3].inject(0, &:+) #=> 6 Procオブジェクトとしても渡せるが無意味に遅くなる






スクリプトエディタに関するメモ

エディタに外部ソフトを使う

  • VXAceのスクリプトエディタは非常に低機能で使いづらい。いつも使ってるエディタで開発した方が楽。
  • 一番手軽なのはスクリプトを全てrbファイルに書き出して、検索用にgrepを使う方法。
    • 書き出しには下記コードを一度だけ実行すればOK。ゲームフォルダのscriptsフォルダに全スクリプトファイルを自動的に書き出してくれる。
    • 書き出したスクリプトファイルはscriptsフォルダごと別のフォルダに移す事。そうしないとゲーム配布時に同梱されてしまう。
  • お気に入りのエディターで新規コードを開発し、内容をコピペしてVXAceに実装するという手間をかけても開発は劇的に楽になる。
    • テスト用のコードは専用のクラスをVXAce内に作り、そっちで実行すると良い。
  • 管理人は JmEditor2 + JGREP2 を使っている。
    • JmEditor2 は ctags日本語対応版を導入して基本設定2でパスを設定、Rubyのチェックを入れればソースブラウザ機能を使えるようになる。
    • 特異メソッドや ! ? 付きメソッドにも対応しているのでオススメ。
  • Notepad++ も割りとオススメ。関数リストのパース周りが残念なので使うなら次項参照。
RGSS3スクリプトを一括でファイルに出力する
dir = "scripts"  # スクリプト保存フォルダの指定
Dir.exists?(dir) or
Dir.mkdir(dir)

$RGSS_SCRIPTS.each do |id,name,dump,text|
  n = name.gsub(/ /, '').strip    # 名前から全角スペースと前後の空白文字を除外
  next if n.empty? || text.empty?
  
  File.open("#{dir}\\#{n}.rb", "wb") do |f|
    puts "#{f.path}  successed!" if f.write(text) > 0
  end
end
p :stop    # これが表示されたら処理終了
rgss_stop  # プログラムの停止

# 実行は一度で十分なので、書き出し成功したら上記コードを削除、もしくはコメントアウトする事

Notepad++の関数リストにmodule名や特異メソッドを表示する

  • Notepad++(v8.9.2 64bit時点)の欠点として、関数リストにmoduleが載らない、特異メソッドがすべてselfになるなどの問題がある。
  • functionList\ruby.xml を開き、パーサを書き換える事で上記問題を解決できる。
    • おまけで末尾の ! ? も表示するように修正。
    • 下記変更を反映するには再起動が必要。
<NotepadPlus>
  <functionList>
    <!-- ======================================================== [ Ruby ] -->

    <parser
      displayName="Ruby"
      id         ="ruby_syntax"
    >
      <!-- within a class-->
      <classRange
        mainExpr    ="^(class|module)\x20\K.*?(?=\n\S|\Z)"
      >
        <className>
          <nameExpr expr="(\<\<\s)?\w+(\:\:\w+)*" />
        </className>
        <function
          mainExpr="^\s*def\s+\w+(\.\w+)?[\!\?]?"
        >
          <functionName>
            <funcNameExpr expr="def\s\K\w+(\.\w+)?[\!\?]?" />
          </functionName>
        </function>
      </classRange>
      <!-- without class-->
      <function
        mainExpr="^\s*def\s+\w+(\.\w+)?[\!\?]?"
      >
        <functionName>
          <nameExpr expr="def\s\K\w+(\.\w+)?[\!\?]?" />
        </functionName>
      </function>
    </parser>
  </functionList>
</NotepadPlus>





Wiki記法に関するメモ

Wikiにそのまま書けない文字

  • Wiki構文と認識されてしまう文字の入力方法。
# を &#35; と入力する(プラグイン化回避) &#x23 のようにhexコードでも可能
例)Module&#35;include => Module#include

> を &gt; と入力する(リンク化回避、表結合回避)
例)[[公式リファレンス &gt; class Object>>https://docs.ruby-lang.org/ja/1.9.3/class/Object.html]]

| を &#124; 、^ を &#94 、~ を &#126 と入力する(表区切り、表タイトル、表結合回避)
|&#126 表タイトルにならない|&#124; 表区切りにならない|
|&gt; 結合しない|&#94 結合しない|
|&nbsp; でスペースも書ける|  通常は行頭スペースは無視される|
~ 表タイトルにならない| 表区切りにならない
> 結合しない^ 結合しない
  でスペースも書ける通常は行頭スペースは無視される
数値文字参照対応表
hexchcodehexchcodehexchcodehexchcodehexchcode
0x20&#320x28(&#400x3A:&#580x5B[&#910x60`&#96
0x21!&#330x29)&#410x3B;&#590x5C\&#920x7E~&#126
0x22"&#340x2A*&#420x3C<&#600x5D]&#93
0x23#&#350x2B+&#430x3D=&#610x5F_&#95
0x24$&#360x2C,&#440x3E>&#620x5E^&#94
0x25%&#370x2D-&#450x3F?&#630x7B{&#123
0x26&&#380x2E.&#460x40@&#640x7C|&#124
0x27'&#390x2F/&#470x7D}&#125
文字実体参照対応表
  • 文字参照は大文字小文字を区別するので注意。一部大文字参照も使えるものもある。_
hexchcodehexchcodehexchcodehexchcodehexchcode
0x20 &nbsp;0x28(&lpar;0x3A:&colon;0x5B[&lsqb;0x60`&grave;
0x21!&excl;0x29)&rpar;0x3B;&semi;0x5C\&bsol;0x7E˜&tilde;
0x22"&quot;0x2A*&ast;0x3C<&lt;0x5D]&rsqb;
0x23#&num;0x2B+&plus;0x3D=&equals;0x5E^&Hat;
0x24$&dollar;0x2C,&comma;0x3E>&gt;0x5F_&lowbar;
0x25%&percnt;0x2D&minus;0x3F?&quest;0x7B{&lcub;
0x26&&amp;0x2E.&period;0x40@&commat;0x7C|&vert;
0x27'&apos;0x2F/&sol;0x7D}&rcub;
参照: https://html.spec.whatwg.org/multipage/named-chara...

シンタックスハイライトの色を変更する方法(管理者限定)

  • マイページ > 管理画面 > デザイン > CSSカスタマイズ(上級者向け)
  • 下記コードを足して色を好みで入れ替える。
/* シンタックスハイライト強制上書き 全モード共有 */
.str { color: #808 !important; }  /* string */
.com { color: #080 !important; }  /* comment */
.kwd { color: #00f !important; }  /* keywords */
.lit { color: #800 !important; }  /* literal */
.pln { color: #000; }             /* plain text */
.pun { color: #088 !important; }  /* punctuation */
.typ { color: #008 !important; }  /* class name */

/* シンタックスハイライト強制上書き |RB| に対応 他の言語に対応したい場合は lang-rb 部分を調べて書き換える */
pre.lang-rb span.str { color: #808 !important; }  /* string */
pre.lang-rb span.com { color: #080 !important; }  /* comment */
pre.lang-rb span.kwd { color: #00f !important; }  /* keywords */
pre.lang-rb span.lit { color: #800 !important; }  /* literal */
pre.lang-rb span.pln { color: #000; }             /* plain text */
pre.lang-rb span.pun { color: #088 !important; }  /* punctuation */
pre.lang-rb span.typ { color: #008 !important; }  /* class name */
色分け例(Ruby)
# comment 
plain text  CONST_NAME    # plain text
"string" 'string'         # string
class def nil true false  # keywords 
100  1.1                  # literal 
[1,2,3,4,5]               # punctuation & literal 
ClassName                 # class name
  • SeesaaWikiのシンタックスハイライトはGoogle Code Prettifyで実装されている。(prettify.js)
    • 恐らくバージョンは一番古い(2006verの)ままでバージョンアップされていない。(最新版は2013/3ver)
/* SeesaaWiki の prettify.js で使えるキーワードと初期カラー、言語ごとに有効なキーワードが異なる */
.str { color: #080; }  /* string content          文字列 */
.kwd { color: #008; }  /* keywords                キーワード */
.com { color: #800; }  /* comment                 コメント */
.typ { color: #606; }  /* data type               型、クラス名 */
.lit { color: #066; }  /* literal                 主に数値リテラル */
.pun { color: #660; }  /* punctuation             構文区切り文字 */
.pln { color: #000; }  /* plain text              プレーンテキスト */
.tag { color: #008; }  /* markup tag name         HTMLタグ */
.atn { color: #606; }  /* markup attribute name   HTML属性名 */
.atv { color: #080; }  /* markup attribute value  HTML属性の値 */
.dec { color: #606; }  /* declaration             ドキュメント宣言 */
参照: Google Code Prettifyでソースコードを読みやすく色分けする
参照: google-code-prettify

シンタックスハイライトの問題点
  • Rubyのコードで以下の問題がある。
:symbol        # シンボルは判別できない
/abc/          # 正規表現リテラルは文字列だったり判別不能だったり
(/abc/)        # カッコの中だと文字列として判別してる???
/* comment */  # punctuation + plaintext扱い Rubyだと正規表現構文解析エラーで例外が発生する例
日本語文字     # なぜかpunctuation  RGSS3的にはイベント内容を直接書けば色分けされるので便利
"#{a}"         # 式展開は全部文字列
@instance_var  # インスタンス変数はリテラル判定
@@class_var    # クラス変数は判別できない
1..10          # 範囲リテラルは判別できない
1.upto(10)     # uptoがなぜかリテラル 恐らく数値リテラル+.(小数点)以降の英数字がまとめてリテラル扱いにされている
-1             # 負の数は判別できない
10_2000_5000   # リテラル中のアンダーバーは判別できない






フリーエリア

管理人/副管理人のみ編集できます

広告募集中