作者NDark (溺於黑暗)
看板GameDesign
标题[心得] 容易被遗忘的游戏设计模组(4)
时间Thu Feb 11 19:32:08 2010
"最容易被遗忘的游戏设计模组"之四:
Update data by first time/changed/every frame
最後一个讨论题目。
我们要一再再重申游戏程式设计的大部分讨论都是立基於每个画格的挣扎之中。
即便是有更好的硬体。运算量也都会因为慾望无穷而被更好的声光效果而挤压。
前一个讨论中提出的压缩率-程式码在画格中的挣扎-这战斗就像柯南一样永无止尽。
意思是一个画格中有好几件事情在抢运算量。抢的等级是在1/30秒甚至1/60之间。
这里我们不是要讨论
"把AI(人工智能)或是读档等工作利用奇淫绝技切割在好几个画格完成"
这件事情。(延後那些不急的事)
而是-我们要去搾掉那些不该作的事情。
结合前面几节的讨论内容来想像一个情境:
"在过关画面或是战斗胜利的结算画面有一个分数的栏位
分数不断的往上跳动,数秒之後到达结果的分数值。"
这段文字的背後运作是像这样的。
(这里我们就先不管浮点数精度与整数互换的问题了,那不是我们的重点)
有一个结果分数值的变数 ScoreResult (最终要跳到的数字)
一个目前显示分数的变数 ScoreNow (还没跳到的数字)
显示函式中作的事情
{
...
DrawText( ScoreNow ) ;
...
}
显示之前我们要依照目前的进度来决定 ScoreNow
因此我们用另一个变数用来设定跳动的速度 :
ScoreJump = 100 (/1sec ) ;// 每秒跳100
当然还要带入第一节讨论的参数 _FPSNow
更新函式中作的事情
{
...
ScoreNow = ScoreNow + ( ScoreJump / _FPSNow ) ;
if( ScoreNow >= ScoreResult )
{
ScoreNow = ScoreResult ;// 避免分数超过之前偷偷算好的总分
// 发出事件说已经算完了 这用到第二节事件处理的概念
}
...
}
我们把更新跟绘图分开来了,还用到第三节分段的概念。
实务上,通常我们不会定死一个跳动的速度,(先假设我们不用任意键skip跳动特效)
因为万一ScoreResult非常大,或非常小,就会导致跳太久或是跳太快,
两者都造成我们辛苦设计的跳动特效失效。
因此完成跳动特效有另一个设计 设定要跳多久-ScoreJumpTime
跳动速度的计算就会变成 ScoreResult / ( ScoreJumpTime * _FPSNow )
总共多少要跳 * ( 跳多久 / FPS比例 )
或是变成 ( ScoreResult / ScoreJumpTime ) / _FPSNow
每秒跳多少 换算成每个frame
例如
设定3秒要跳完100,那麽第一个画格(FPS:30)的时候,应该要跳动
ScoreJumpThisFrame = ( 100.0 / 3.0 ) / 30 = 1.1 ;
应该要完成1 of 90画格的进度
ScoreNow += ScoreJumpThisFrame ; // 更新目前的分数值
第二画格(假设是FPS:60)的时候,
ScoreJumpThisFrame = 100.0 / 3.0 / 60 = 0.556 ;
// 100/3事实上可以先算起来放着了 就是更新速率(每秒)
// fps变快了所以每个画格的改变量反而变少了
接下来想像一个的状态。我们的显示数字是整数的,
(但是运算使用浮点数的原因是避免除法有遗漏。)
在某情况下可能每个画格更新不到1
画格 : 真实分数 : 显示分数
0 : 0.0 : 0
1 : 1.1 : 1
2 : 1.656 : 1 // 这个画格显示的地方没更新
再极端一点假定我们最小跳动数值是100,但我们这次遇到的状况就是在三秒跳完100。
(假设FPS保持在30)
秒 0 1/30 2/30 ... 1.03 1.06 ... ...........2.97 3.0
画格 0 1 2 31 32 89 90
实际数字 0 1.1 2.2 33.3 34.4 98.9 100
显示数字 0 0 0 0 0 到这都还是0 100
经过这一连串的情境。最後一行有没有发现一个事实。
也就是在某种情况之下(其实这情况常常发生),显示是可以不用动的。
数字从1.1变成2.2对於运算单元是个很小的负担
但是当我们要把1.1这个数字变成"1.1"这个字串却是"相对"大的记忆体与计算量。
尤其是当这个字串不动的时候。
更不用说我们可能为了不动的数字拼凑/组合了整个字串像是
"您今天的总分是" + 0 + "分,恭喜恭喜!!"
终於要讲到今天的主题也就是资料更新频率。
除了绘图之外,其实很多时候资料相对於FPS来讲是几乎静止的。
以沙漠商旅C为例,当买进一个货物,或是货物量有变化的时候
我才有必要因为重量的变化,去更新我整个商旅的负重,来检查是否超重这件事。
换言之,我不用每个画格都去统计每样货物总重量,
每个成员及运输工具的负重。
因为事件不会频繁地在每个画格中出现。
即便是在室外定期会消耗食物,也只要在消耗的时候去统计。
在以下两个状况都满足之时方才更新资料
1.需要显示他的时候(看不到不用更新)
2.资料变动的时候(事件)
与FPS相同的,这个设计机制是广泛到超出我们的想像的。
我们必须要对我们要处理的运算/资料作分类,分出他们需要更新的状况。
以"叫医生来看你"来比喻大致上可以分为
Update data by
first time 疫苗-出生後就打一次。
changed 感冒-当感冒的时候才去。
every frame 加护病房-持续观察,不管他有没有变化。
因此沙漠商旅C的更新选单这件事会把每个选单(因为每个选单要更新的对象不同)
独立一个函式,并建立成以下这个架构:
Update(*)Menu()
{
// 每个画格都会进来
if( true == Changed )
{
// 改变过才要更新的部分
Changed = false ;// 设定旗标让下次进来的时候略过
}
// 每个画格都更新的部分
}
因此
在沙漠商旅C的商旅选单中
"商旅名称" "商旅称号" 这种字串就是第一类,从档案读进来的时候更新一次。
商旅名称 "小虎队" 这个字串就是当玩家keyin修改了才去更新选单上的资料。
商旅存在时间 "3天3夜12小时47分钟" 这个字串可能每分每秒都在跳,
也不适合独立出一个事件,也许你就可以考虑每个画格去更新他。
--
"May the Balance be with U"(愿平衡与你同在)
视窗介面游戏设计教学(
http://0rz.tw/V28It ),讨论,分享。欢迎来信。
视窗程式设计(Windows CLR Form)游戏架构设计(Game Application Framework)
游戏工具设计(Game App. Tool Design )
电脑图学架构及研究(Computer Graphics)论文代读(含投影片制作)
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.96.77.176
※ 编辑: NDark 来自: 140.96.77.176 (02/11 19:32)
1F:推 ddavid:推 02/11 19:42
2F:推 wangm4a1:推 02/11 19:49
3F:→ NDark:...那麽就新年快乐啦.... 02/12 00:13
4F:推 reizarc:类似 observer design pattern 很久没翻这方面的书了 >w< 02/12 02:44
5F:→ reizarc:另外利用 event priority 做 arbitration 也满常见的 02/12 02:44
6F:推 linjack:新年快乐推 02/12 09:36
7F:推 zzzdeath:迟来的拜年推 02/22 14:45