What have you found for these years?

2008-04-09

R: [程式] 關於SLG系統的寫法

 作者  godfat (godfat 真常)                                  看板  GameDesign
標題 Re: [程式] 關於SLG系統的寫法
時間 Wed Apr 9 17:00:20 2008
───────────────────────────────────────

不是很清楚你想作成什麼樣子,根據不同的遊戲系統,
我想寫出來的東西可以大相逕庭。你可以從你希望怎麼操作你的東西開始思索,
像是我會希望能夠這樣做(in Ruby):

> unit_pool = {} # a hash map

> godfat = Unit.new "godfat"
> godfat.hp = godfat.mp = 10

> # active ability
> godfat.obtain_ability Heal.new(:level => 10, :cooldown => 29)

> # passive ability
> godfat.obtain_ability Flying.new(:height => 2)

像這樣就可以任意指定某個單位有哪些能力,事後也能修改單位的屬性和能力。
存起來以便事後 clone 出來:

> unit_pool["godfat"] = godfat

治療自己:

> action = godfat.abilities["heal"].targets(godfat)
> action.apply

反治療:

> action.unapply

這裡有一個重點,就是 unapply 不可能自己生出來,
必須針對每一個 ability 都寫一次。除非不用這種 undo 法,
而是把整個狀態記錄下來,然後重新寫回去,像是:

> before_heal = godfat.dump_state
> action.apply

反治療:

> godfat.restore_state before_heal

不過我是不太建議這種作法,因為這樣你就必須知道被影響的單位有哪些,
像是在這裡只有影響到 godfat, 所以只要這樣做就好了。
要全面回復,可能就會有:

> targets_to_restore = action.target_list
> before_heals = targets_to_restore.map{ |t| t.dump_state }
> action.apply

反治療:

> targets_to_restore.zip( before_heals ).each{ |t, s| t.restore s }

唔,zip 和 map 的用法我就先不說了,總之這樣會有很多要處理的。
或是乾脆全場記下來,可能還簡單些:

> last_step = game.dump_state
> action.apply

反治療:

> game.restore_state last_step

不過我不知道你是不是要做這麼複雜的 undo,
還是只是單純取消之前下達的命令,例如換人之類的,而不是說「悔棋」
如果只是要取消之前下達的命令,那根本不用 undo, 只要不要執行就好了...
等到「回合結算」的時候才真的去執行,那就很單純。

其他程式碼:(我隨手寫的,所以其實很粗糙)

class Unit
attr_reader :name, :abilities
attr_accessor :cooldown, :hp, :mp

def initialize name
@name = name
@abilities = {}
@cooldown = @hp = @mp = 0
end

def obtain_ability ability
ability.owner = self
@abilities[ability.name] = ability
end
end

class Ability
attr_accessor :owner
attr_reader :name, :target_list

def targets *list # 這表示引數可以無限多
@target_list = list
self
end

def apply
end

def unapply
end

protected
def initialize name
@name = name
end
end

class Heal < Ability
def initialize opts
super "heal"
@opts = opts
end

def apply
self.owner.mp -= (opts[:level] * 1.5).round
self.target_list.each{ |t| t.hp += opts[:level] * 2 }
self.owner.cooldown += opts[:cooldown]
end

def unapply
self.owner.cooldown -= opts[:cooldown]
self.target_list.each{ |t| t.hp -= opts[:level] * 2 }
self.owner.mp += (opts[:level] * 1.5).round
end

private
attr_reader :opts
end

class Flying < Ability
# 略...
end

上面那個 unapply, 其實可以寫得更抽象化一點,像是:

> apply_steps << subtract 'self.owner.mp', '(opts[:level] * 1.5).round'
> << lambda{ self.target_list.each{ |t| ....略 } }
> << addition 'self.owner.cooldown', 'opts[:cooldown]'

然後 apply / unapply 就能這樣寫:

> def apply
> apply_steps.each &:apply
> end

> def unapply
> apply_steps.reverse_each &:unapply
> end

然後 subtract 的 unapply 當然就是 addition,
addition 的 unapply 當然就是 subtract.

中間的 lambda 會複雜許多,所以我就不寫了...

當然這樣是有點走火入魔啦,只是要大量使用 undo 的話就得做徹底一點。



補充:

這邊每 apply 一次,就會洗掉上一個 target_list,
所以如果需要直接放入 stack 做 undo list 的話,

Ability#targets 可能要這樣寫:

class Ability
def targets *list
@target_list = list
self.clone
end
end

這樣就可以直接放到 stack 去不怕影響到別人了
(當然,那個 clone 在 Heal/Flying 那些 class 裡要定義)

--
#!/usr/bin/env ruby [露比] /Programming (Kn|N)ight/ 看板《Ruby》
# if a dog nailed extra legs that http://www.ptt.cc/bbs/Ruby/index.html
# walks like an octopus, and Welcome ~Ruby@ptt~
# talks like an octopus, then ◢█◣ http://www.ruby-lang.org/
# we are happy to treat it as █ http://www.ruby-doc.org/
# if it were an octopus. ◥ ◤ http://www.rubyforge.org/

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.135.28.18

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0