精華區beta C_Chat 關於我們 聯絡資訊
※ 引述《ZMTL (夜風/瀟湘 VR板已經開板!)》之銘言: : 嗯,雖然我是APP工程師,但大學不是唸本科畢業後才半路出家的, : 對這點真亂數、假亂數以前耳聞過討論卻沒什麼概念,剛好跟遊戲有關想到就問一下。 : 首先舉例使用假亂數表的遊戲,以下有稍微簡化過程: : 1.魔物獵人:世界 : 「煉金」功能是拿X個珠子生成三顆新的珠子,存檔讀檔結果不會變。 : 後來被發現有一張表,像這樣 : A B C : D E A : A A C : 如果你是這次煉金出來是ABC,下次煉金出來是DEA,下下次煉金出來是AAC : 那你可以先不練金,去打兩場任務出來就會變成AAC。 : (實際上打任務推進的序列是1、1、2輪迴,按下不提) : 細節:https://forum.gamer.com.tw/C.php?bsn=5786&snA=137873 : 2.神奇寶貝 : 「生蛋」功能是公母方配種生出子代,特定變因固定下存檔讀檔結果遺傳項不會變。 : 父母都有 A B C D E F六項能力,分別遺傳父母的哪幾項在變因固定下是不會變的, : 但變因不包括父母是誰,所以可以確認會遺傳哪一項後再更換父母取得特定遺傳的子代, : 進階一點用法就是找到第XXXX次會生出色違後, : 用低步數就生出來的神奇寶貝跳過中間不需要的部分, : 在指定的位子再更會為要的神奇寶貝快速取得色違。 : 細節:https://home.gamer.com.tw/creationDetail.php?sn=3427102 : 那問題來了, : 如果說是避免玩家用SL大法來硬洗出想要的成果,卻反而造成未來成果會被預測, : 難道單機遊戲做不到真正在產出結果當下進行亂數,或者亂數表假亂數表有什麼優點嗎? : 其實對這問題有疑問好久了w : 很多人說MHW洗珠子無聊會消耗熱情,但經過PM的洗禮我真的覺得還好XD : 順帶一提,很多線上遊戲/網路遊戲的都市傳說有時候我不會完全不信的原因也是這個。 整串下看下來好像沒多少人點到重點 在業界,遊戲除了博奕類例外,八成都會使用亂數表,不使用亂數表的是少數, (有些博奕類開發還要亂數產生器還要特別用買的..,確保幾乎不可回朔) 幾個重點: 1. 效能 2. 可回朔性 3. 可驗證性 4. 多人遊戲 你如果是一個簡單的大富翁遊戲,只需要前進時丟丟骰子就好, 那就跟效能問題無關,你隨便寫、隨便CALL任何隨機函數都OK。 但問題是現在的遊戲是需要很大量的亂數運算,越精緻的遊戲越是如此, 那一槍一刀打出去都還要算浮動傷害的、還有一大堆的物理計算, 很顯然的不能單純的使用各語言內建的rand()。 rand()你去爬看看 Library,展開來行數其實都不少,大部分都是以時間做種的 橢圓算算法。 而你使用亂數表的話,只需要在LOADING的時候建立一張表,之後需要亂數的時候, 直接用查詢的方式即可,計算跟查詢,兩者效能天差地遠。 而且使用計算的方式還有一個問題,那就是需要多執行緒的場合容易有問題 例如丟骰子A先生與B先生看誰比較大 如果是A先丟完,B再丟,那就雙方有輸有贏沒甚麼問題, 但如果是A跟B同時丟的話,依照各種亂數產生器的原理不同, 是極有可能出現A跟B同時丟都永遠會是一樣數字的情況。 再來是遊戲的亂數為什麼會需要可回朔性跟可驗證性? 因為你如果使用原生亂數產生器的方式,你很難實現REPLAY跟存檔, 這是一個很大的設計重點,不管你的遊戲是否實裝這功能,你都 必須要有這部分的耦合性設計,也就是確保未來可以擴充這些功能。 你如果每次都用即時運算的方式產生亂數,你很難確保每次S/L或是REPLAY 都能完美的重現每個亂數的產生,即便你種子明明餵的都一樣, 但有時候跑出來的東西就是他X的鬼打牆,想必每個程式人都有這經驗。 想想看,遊戲終於嘔心瀝血的完成了,終於可以讓你的肝休息, 結果老闆突然要你改成使用亂數表並且支援以上功能, 那是何其大的手術工程!? 你不就.. ◢···· 崩╰(〒皿〒)╯潰····◣ 還有牽扯到網路連線的問題,如果不使用亂數表,使用原生亂數, 光是讓要多位玩家的同個時間誤差內的亂數確保一至,那就已經是個嚴重問題。 例如假設我砍一刀,我本機端計算傷害是 48763 ,等到你接收到封包了, 你才開始重新計算一次,然後又只用時間來做種,可是剛好這封包撞到鴿子, 結果你的本機端算出來是 5566520,請問該怎麼辦? 或是我也可以不用計算,直接接收你計算好的數值就好,但我也不能驗證, 要是你開掛丟過來的數值變成 4876399999 我不就得被秒? 或要是你的封包丟過來要100MS, 那我是不是每次做完一個動作都要暫停畫面再等100MS才能繼續? 否則我沒辦法確保需要的亂數一至,因為是即時運算的亂數, 所以我只能等跑等跑等跑等跑...我是在玩遊戲還是在看他X的幻燈片? 難道我不能在某些地方事先預測或計算出你會產生出甚麼數值嗎? 例如一把機槍,滑鼠點一次下去,一秒自動射擊5槍,產生5個浮動傷害 你開一槍,我這邊難道都得等你5個傷害的數值封包全到後才能計算人物傷害嗎? 有了亂數表不就可以簡化成,你開槍->我接收到你開槍了, 然後我這邊用同樣的條件查詢跟演算,預先計算出未來一秒的5個傷害, 這樣是不是省下整整一秒的時間? 對網路遊戲有絕對的效能提升。 且因為在LOADING產生的亂數表大家都確保拿到同樣的一張, 所以我還可以驗證這5個傷害是正確的。 還有很多細節講不完,其實當初我也是剛你差不多, 進了公司之後被電過, 發現自己不能在用以前在學時那種作業式的方式跟邏輯寫程式, 很多地方實務上寫的方法都跟課堂上的完全不一樣。 例如A先生有5元 B先生有7元,兩人共有多少錢? 當然我們可能會: int A=5,B=7; return A+B; 就完事了。 但是實務上人家會要求你例如 先設計一個物件person,然後把A跟B變成物件 person A = new person(); person B = new person(); A.money = 5; A.money = 7; return A.getMoney()+B.getMoney(); 你可能問說幹嘛要這麼麻煩? 例如哪天公司要你修改成 A先生有5美金、10台幣、7日圓 B先生有7美金、 9台幣、6日圓 ,並且增加CDEFG先生小姐... 那我不是只要修改person這個物件就好? 我同時只要重載getMoney( String );就好 因為A跟B繼承了person,不用針對A跟B再個別修改 只要getMoney( 美金 ),我就能取得多少美金... 對於程式的維護跟擴充絕對有幫助, 那個好的實務設計體現在現實面就是可以大幅減少你未來的爆肝時間 等等以上只是舉個例。 總之實務上,遊戲設計使用亂數表好處絕對大於你即時運算, 況且你真的需要即時運算的場合並不多。 玩家S/L大法刷東西那是玩家的事情,那並不是你站在公司開發者要替玩家著想的事情。 所以說最後有兩本書建議寫程式的人必買! 必買! 必買! 很重要要說三次 ! 特別是你有實務需求的更是要買! 這兩本根本就該列為必讀的教科書, 因為這兩本是真正從實務面上教你如何寫程式。 就是 大話設計模式 ISBN:9789866761799 (必讀) 大話資料結構 ISBN:9866072118 (推薦) -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 61.230.200.156 ※ 文章網址: https://www.ptt.cc/bbs/C_Chat/M.1520435009.A.E14.html ※ 編輯: extemjin (61.230.200.156), 03/07/2018 23:06:16
bnn: 廣告文(X 03/07 23:05
emptie: 原來是書商(誤 03/07 23:06
prismwu: 妳要用先講結論當作第一句 03/07 23:07
ayubabbit: 原來是業配(X 03/07 23:08
kungfutofu: 好喔 來買書 03/07 23:09
SilentRain24: 身為寫扣人士這篇我推 03/07 23:13
seaEPC: Design Patterns跟Data structure確實很重要 03/07 23:14
seaEPC: 不過最可怕的是接他人的code然後發現裡面滿滿return A+B; 03/07 23:15
LiNcUtT: 感謝解惑~ 03/07 23:22
SPDY: 推專業清晰的講解 03/07 23:25
zseineo: 猝不及防的葉配XDDDD 03/07 23:26
guogu: 可是合成那種就不需要大量重複的運算,那邊用rand就沒差? 03/07 23:27
當然這不是絕對的,看情況簡單CALL個RAND()也行, 但是如果牽涉到網路、多人遊戲、REPLAY、SAVE等問題,就算無關效能問題, 你大部分的情況還是得乖乖使用亂數表。 再來還有一個很基本的問題,對於正常玩家,我使用即時運算跟亂數表,有甚麼區別? 就像如果你是使用遊戲引擎內建的腳本語言的rand()來寫遊戲的話, 那實作上絕大部分都是基於亂數表的亂數產生器, 或是該引擎其實會提供不同的亂數產生器 ,只是你不知道,但也不太需要知道,因為這些老問題引擎開發商早就幫你解決了。
zseineo: 防玩家SL也不一定是替玩家著想啦,有時候玩家會選擇不好 03/07 23:27
※ 編輯: extemjin (61.230.200.156), 03/07/2018 23:41:07
zseineo: 玩但簡單無腦的玩法(某些SL),不過這又跟其他設計方面 03/07 23:28
zseineo: 又關就是了 03/07 23:28
arrenwu: 你說的情況 只要運算都在伺服器就可以不用亂數表了吧? 03/07 23:29
zseineo: *有關 03/07 23:29
arrenwu: 我以為本地端主要管的是rendering 03/07 23:29
aaaaajack: 有點疑惑,照理說這些東西都以server算的為主吧 03/07 23:30
一個簡單的反問,那如果是單純的區網遊戲呢? 線上遊戲絕大部分的情況SERVER大多情況一般來說只做DATA紀錄、玩家分發、 以及驗證等等基本的動作而已,光是做些動作就已經夠操SERVER了。 當然例如抽卡轉蛋這種需要比較高安全性的大多會在SERVER, 主要是沒這麼即時情況或遊戲,最重要往往牽涉到交易,要比較高安全性 ,所以放在SERVER端運算。 但是大部分網路需求較高的動作遊戲進行中,SERVER甚至不會時時都保持連線的, 可能固定時間或是有向SERVER發送要求,才會來驗證一些資料。 ※ 編輯: extemjin (61.230.200.156), 03/08/2018 00:08:32
aaaaajack: 沒事,大概懂了 03/07 23:42
sdd5426: 簡單來說寫的程式碼要有sense 不能像學生那樣什麼都用最 03/07 23:45
sdd5426: 簡單的想法 03/07 23:45
aaaaajack: 不過這篇講的是同步的問題,但原po問單機遊戲... 03/07 23:49
sdd5426: 他有提到啊 使用函數不好實作replay等 03/07 23:53
linzero: replay直接把所有得到的亂數存起來不就好了? 03/08 00:00
aaaaajack: 那段我覺得他在亂講,rand沒這問題... 03/08 00:02
know12345: 猝不及防賣書XDDD 03/08 00:03
LiNcUtT: 實作上絕大部分都是基於亂數表的亂數產生器 < 這不對吧 03/08 00:04
holymars: 實作上都是deterministic的PRNG演算法..然後讓generator 03/08 00:05
holymars: 同步.... 03/08 00:05
aaaaajack: 或者說rand跟亂數表根本是一樣的吧 03/08 00:05
是啊,就邏輯上來說是一樣的
LiNcUtT: 絕大部分的亂數產生器都是算出來的,不是查表啦 03/08 00:05
holymars: 會寫成先骰好一張表再整張sync,應該是對PRNG有所誤解 03/08 00:06
holymars: 平白浪費流量在傳無用資訊 同樣的演算法下你傳10000個值 03/08 00:06
我基本上就是說這個
seaEPC: 會出現不同結果我覺得比較像撞到multi-thread執行順序問題 03/08 00:06
holymars: 和傳一個值,在deterministic的PRNG算法下結果完全一樣 03/08 00:06
holymars: 不是 PRNG最常踩到的陷阱是硬體和lib的實作問題... 03/08 00:07
LiNcUtT: h大說的比較有道理 03/08 00:07
holymars: 你「以為」演算法一樣,實際上要保證不同平臺不同硬體的 03/08 00:07
holymars: PRNG演算法跑出一模一樣的結果,通常踩到雷都是這個 03/08 00:08
holymars: 至於保證rand()呼叫的順序和次數是相同的,這是另一個問 03/08 00:09
seaEPC: 看reply指的是哪種囉,我指的是像在同個條件/輸入下重跑 03/08 00:09
holymars: 題,不管你有沒有隨機因素,event發生的次數和順序本來 03/08 00:09
holymars: 就要保證是同步且正確的.. 03/08 00:10
seaEPC: 所以沒寫好的情況下就會出包啊 工作上還真遇過... 03/08 00:11
holymars: 同步沒寫好,應該不止有隨機的部份會出包,是全部都會出 03/08 00:13
holymars: 包才對... 03/08 00:13
沒錯 ※ 編輯: extemjin (61.230.200.156), 03/08/2018 00:21:37
seaEPC: 我們那邊是收前端資料解碼後丟去DB更新,基本上多個threads 03/08 00:25
seaEPC: 分別處理自己所屬資料,是不用管誰必須先做的 03/08 00:26
Logan2934: 神奇寶貝 MHW 本來都是卡匣遊戲,可能整個遊戲就只要 03/08 03:16
Logan2934: 骰0~9,為了省空間以及加速,就自製了個很簡化的rand 03/08 03:16
Logan2934: (),結果就一路繼承到現在 03/08 03:16
Neil000: 書商推 03/08 07:58
yshinri: 同樣是表, 寫程式時先建好表跟實際執行時先求一萬個亂數 03/08 08:45
yshinri: cache 成表還是有差, 你在講的表多半是後者這種 03/08 08:45
yshinri: 那後者這種表其實除了你提的優點之外, 其他方面都比前者 03/08 08:47
yshinri: 來的好, 但我想不只這裡推文, 原 PO 多少也以為建表是 03/08 08:47
yshinri: 前者那種建表, 這才是這一串開頭在問的問題 03/08 08:47
yshinri: 然後我提的神魔的例子則是真的少數用前者這種方法建表 03/08 08:48
yshinri: 的奇葩例子 03/08 08:49
yshinri: replay 這種事情就算使用先 cache 的表, 只要把開頭的 03/08 08:57
yshinri: 種子給紀錄下來, 之後 replay 回放時重新產生就行了 03/08 08:57
yshinri: 至於區網連線遊戲的隨機傷害這種東西, 你該傳的不是 03/08 08:57
yshinri: 「我打了你, 你幫我算個傷害」而該是「我打了你多少點」 03/08 08:58
yshinri: 那這個隨機傷害怎麼產生就跟什麼同步的無關了 03/08 08:59
yshinri: 當然這種狀況的 replay 就必須把每次的傷害都紀錄下來 03/08 08:59
hanmas: 推專業 不過物件導向學校也是有教的吧 03/08 09:35
stfang925: 推推~~ 03/08 10:17
j456789a: 感謝偉大的物件導向讓我們減少更多爆肝時間 03/08 11:15
gn00063172: 推推 03/08 12:20
GKki2012: 專業推推推 03/08 14:33
linja: 專業推。實務上封包收發真的會產生很多問題 03/08 17:48
PassthebyLai: 給推 03/08 20:25