游戏开发论坛

 找回密码
 立即注册
搜索
查看: 2511|回复: 5

浅谈游戏逻辑

[复制链接]

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
发表于 2008-6-4 04:14:00 | 显示全部楼层 |阅读模式
一款游戏的核心逻辑,分为:

1 属性数值
2 游戏规则
3 定时器
4 时序(每frame的update)

开门见山,
让我们简单一点,不考虑字符串等。只考虑浮点数(或者整数)。


游戏中的各种属性,可以用一个集合 X 来表示。
集合的每一个元素,就是一个属性。可以用 a, b, c...表示。


下面讨论游戏规则。
游戏规则可以用一些“函数”来表示,这是无可厚非的。
关键是,具体怎么表示?表示出来了之后,如何求解?

一般有2,3种表示方法:


1 经典风格:


例如,我们考虑属性 a 和时间 t 的关系:
a = a(t). 可以这样用函数"a(t)"来表示。

举个实际例子,角色的位移 s(t) = 积分[0,t] ds(t) dt.

我们再举个不带时间 t 的例子,比如,攻击力 和 武器攻击力 的关系:
AP总和 = AP自身 + AP武器
如果再考虑时间参数,则有 AP总和(t) = AP自身(t) + AP武器(t)

这种经典风格,能力是有限的。不是所有的方程式,都可以求解的!

这里举个初2水平的例子,来说明经典风格的局限性。
假设有3个属性, U, I, R. 随时间变化(怎么变化是未知的!)
现在假设 U,I,R 之间的关系是: U == I * R.
即: U(t) = I(t) R(t) ;
假设游戏开始的时候,他们的数值是已知的,即在 t=0 的时候:
U(0) = U0;  I(0) = I0;  R(0) = R0.
此外,我们已知,R(t) == F(t). (...电阻随时间变化!)

然后,由于游戏的每一frame都要“刷新”一次,
比如,经过了 dt 时间,我们要求得 0+dt 时刻的数值,
那我们来算一算,一共有几个未知数,几个方程...
未知数:U(0+dt), I(0+dt).
方程:U(0+dt) = I(0+dt) * F(0+dt) ;
结论是,根本就求不出来解。。。于是。。。我们继续往下看。。。


2 现代风格(具体又分为2种!):


实际上,这根本不是什么新鲜玩艺儿,开发过游戏的话,那么你已经知道了。
只不过,我用数学语言描述出来。

我们经常这样定义游戏规则(属性之间的关系):
比如,属性 a 随时间的变化 a(t). 现在, a(t) 这个函数是未知的。比如,
考虑一个在鼠标控制下的角色的位移,你不可能知道玩家何时点击鼠标!

现在我们这样描述 a 随时间的变化:

a ( t + dt ) == f ( a(t) ) .

一般来说,我们是 Update( dt ) 这样编码的。这样的好处很多。
比如角色的位移,你可以每隔 dt 时间段,查询一次玩家的鼠标状态,然后,
更新角色的位移(即通过 微分 的形式)。

再来看看多个属性之间的关系,你的游戏不可能这么简单的吧?比如,群体PK...
假设 a b 这2个属性之间的关系,可以这样表示:
a ( t + dt ) == fa ( a(t), b(t) ) ;
b ( t + dt ) == fb ( a(t), b(t) ) .
这是一个 同 步 的 表达式,此外,还有各种各样的非同步表达式,比如:
a ( t + dt ) == fa ( a(t), b(t) ) ;
b ( t + dt ) == fb ( a( t + dt ), b(t) ) .

那边的那个谁,不要笑!我们很多朋友的代码,都可以用这种式子来描述。
我在“面向对象带来的陷阱”一文中,指出了这种问题。

“楼主怎么老是发这种脑can帖子阿!我的代码什么时候出问题了阿!来,我给你看看!”
那么,我们就来看一看,代码到底出了什么问题。您的代码或许是这样:
A::Update(dt)
{
     m_val = Func_A ( m_val, pB->m_val );
}
B::Update(dt)
{
     m_val = Func_B ( pA->m_val, m_val );
}
然后再主循环中,您是这么写的(简单起见不用 List,一样的):
pA->Update(dt);
pB->Update(dt);

“代码看完了,楼主倒是说说,我错在哪里啊!?我明明按照你的 同步 公式写的嘛!”
。。。估计阿,周围看帖子的朋友,已经看出来了,请您再好好想想,不用急。呵呵。

为什么 不 同 步 ,就不必多费口舌了,怎么修改代码,也不必说了,
我下面想说的是。。。“需要同步吗”???

如果啊,如果,函数 fa, fb 在定义域中是 可 微 的。那么:
当 dt->0 时, fb ( a( t + dt ), b(t) ) == fb ( a(t), b(t) ).
我们这里的 dt,一般只有几十毫秒,所以,上式近似成立。即,
同步也好,不同步也好,无所谓!
Notice!! 注意注意,我是说,当 fa, fb 是可微的 的时候啊!不要断章取义哦~~


上面说了,根据时间差 dt 来描述游戏逻辑。
此外,还有一种表示方法是,使用“时序”而不是“时间差”。

单击鼠标发射一个子弹,等等情况,可以使用这种方法。即对timeGetTime()的数值漠不关心!
比如, 属性 q 可以表示为:
  q ( n + 1 ) == g ( q(n) ) .
这里用 函数“ g ”来描述下一时刻的 q 和 当前的 q 的关系。其实也可以写成:
  q_next == g ( q_current ) .
  
也可以考虑 多个 属性的方程,比如, p, q 这2个属性:
p ( n + 1 ) == gp ( p(n), q(n) ) .
q ( n + 1 ) == gq ( p(n), q(n) ) .
也就是:
p_next == gp ( p_current, q_current ) .
q_next == gq ( p_current, q_current ) .

在使用“时序”的时候,特别是多线程编码,请注意一下,时序的同步问题。
例如 CPU 的时钟脉冲,和 keybord 的时钟脉冲,肯定不同步,所以:
写 多个 时序方程的时候,必须搞清楚,是不是同一个时序(是否同步)?
----如果不是,那么,对不起,很难分析这些方程之间的关系!那么下面说一下时序的同步:

假设,我们在每次 Update( dt ) 中,更新游戏逻辑。既然是时序,则我们忽略 dt 参数。
那么,编码的时候,必须保证:
(1) 每个子系统都采用 m_next 和 m_val 2个变量,这我说了第2遍了
(2) 每 frame 更新且只更新 一遍 所有的 子系统!

键盘,鼠标的时序,不可能和我们同步,这时候,我们可以有多种解决方案。
典型的有,win32消息队列。 direct input即时取得设备状态。等等。具体先不讨论。
当然话说回来,如果你根本 不 需 要 同 步 的 时 序,那就算了,当我没说。


接下来,
下面我们具体看一下 求 解 方 法 (程序算法) :


1 基于“经典风格”的算法:

举个例子,morphing动画的插值。假设从顶点 v1 过渡到 顶点 v2,
则某一时刻的顶点 v(t) = v1 * (1-t) + v2 * t (这里假设从 t=0 开始播放动画)
这种算法,大多数情况都是不适用的!
当需要和人类玩家的keyboard,mouse交互的时候,这种算法根本行不通。
所以,除了用来计算“插值动画”(貌似不属于游戏逻辑),没有什么太大的实际价值。。。


2 基于“现代风格”的算法:

上面说了,游戏规则的数学表达式,分为 "经典" 和 "现代" 这两种风格。
实际上,上面说的,只是数学表达式,而不是实际代码。。。

我们先看 "现代"风格如何编码求解:
呵呵,上文介绍"现代风格"的时候已经说了编码问题,那么不必再次重复。
正如你 刚刚 所了解的那样!(说白了,就是 Update(时间差) 函数 ^_^)

问题是,"经典风格" 的数学表达式,如何采用 "Update(时间差)"函数,即"现代风格"编码。
为了解决这个问题,我们当然要把“经典风格表达式”转化成“现代风格表达式”咯!

怎么转换? -___-
这个问题。。。应该在数学课上研究。。。这里只给出实例。。。

我们来看一个,把“经典”转化为“现代”的实例!

考虑一群怪物(存入List),相互厮杀(大面积杀伤),他们的属性包括:
生命力 HP
攻击力 AP
防御力 DP
偷取生命因子 TH
盾牌尖刺攻击力 DA

以上属性,可能都是随时间变化的,所以还需要考虑时间参数 t .

多数情况,AP, DP, TH, DA 不随时间 t 变化。那么,
比如,我们求得攻击伤害表达式(假如求怪物 i 的):
Total_Damage_AP_i (t) == AP_i (t) * ( t - 0 ) .
不过,我们应该专业一些,更通用地,用微积分来表示:
Total_Damage_AP_i (t) == 定积分[0,t] AP_i (t) * dt .

类似地,我们把 AP,DP,TH,DA 这些参数全部加上去,
可以获得,某个怪物 i 的生命 HP_i 在 t 时刻 的表达式:

HP_i(t) == HP_i(0) - 求和[k,i以外] ( 积分[0,t]AP_k(t)dt + 积分[0,t]DA_k(t)dt )
  + 积分[0,t]DP_i(t)dt + 积分[0,t]TH_i(t)dt .


请注意,这里为了简单化,我假设:每个怪物,攻击周围所有怪物,他们的盾牌尖刺也是如此。


然后,我们对这个式子,进行“变形”:

HP_i(t+dt) == HP_i(t) - 求和[k,i以外] ( AP_k(t)dt + DA_k(t)dt )
  + DP_i(t)dt + TH_i(t)dt .
  
看到没有!这是什么?这是“现代风格”的表达式!
即然有了现代风格的表达式,那么编码,就不用说了吧?


下面再来看一下,AI和玩家的操作问题。假设AI和人类玩家通过同样的接口和逻辑交互。
到目前为止,我还没有讨论这个问题。在“经典”“现代”中都没有提到。
那么,接下来,我们就“经典”“现代”一起讨论:
先给出“经典”表达式,然后,变形为“现代”表达式,在然后,就可以编码咯!~~

。。。话说,表达式是什么样子的啊?
其实,和您刚才看见的那个长长的微积分式子 HP_i(t) == ...... 是一样的!
我没有开玩笑,比如说吧,AP_i (t) 这个函数里面,可以加入输入(AI或人类输入)判断代码。
这里的变量名,函数名的意义,我们要注意一下, 诸如 AP_i(t) 等,包含了输入在内;
而,没有包含输入的,就是说,本身的攻击力,可以用小写字母比如 ap_i(t) 来表示。
AI或人类的输入呢,就用 I(t) 来表示吧。注意每个人类/AI玩家操纵 1 个怪物,那么就是 I_i(t) .

这里的 I(t) 我们要重点研究一下。问题在于,输入,分2种:(1) 连续的 (2) 间断的(离散的)。
请注意输入的物理形式,由于输入可能是 AI/人类,所以,对于DirectInput这种东西,
是由游戏的其他模块预先处理好的,转化为 游戏核心逻辑 所能够认识的信息,输入进来。
在 游戏核心逻辑 和 外围代码之间,是一个“输入池”(Input Pool),存储每时每刻的外界输入。
----具体怎么编码,我们先放着不谈,先来集中精力看看,用数学怎么处理输入 I(t) :

(1) 连续的输入

先来个最简单的例子,说的专业一点,叫做“单位跃阶函数”。
这很直接,当前有输入,那么 I(t) == 1 , 没有输入,那 I(t) == 0 .
那么,怪物 i 的瞬时攻击力 AP_i(t) == I(t) * ap_i(t) .

复习一下,这里小写的 ap_i 表示怪物本身的攻击力。这个值一般不随时间做规律性变化,
但是会随着装备,武器而变化,比如在某个时刻 t,你可以 monsters->get_ap(); 取得当前攻击力.

不过,假如你制作一些动作性强的游戏,你希望人类/AI可以输入不同的“攻击强度”。
那么,I(t)可以这样设计:没有输入 I(t)==0, 如果攻击强度为 a, 那么 I(t)==a .

假如一群怪物群殴。我们写出完整的 HP 计算公式( 偷取生命 TH(t) 也和攻击输入I(t)有关哦 ):
(前面说了:这里为了简单化,我假设:每个怪物,攻击周围所有怪物,他们的盾牌尖刺也是如此)

HP_i(t) == HP_i(0) - 求和[k,i以外] ( 积分[0,t] I_k(t)*ap_k(t)dt + 积分[0,t]DA_k(t)dt )
  + 积分[0,t]DP_i(t)dt + 积分[0,t] I_k(t)*th_i(t)dt .
  

接下来我们的任务是,将“经典表达式”转化为“现代表达式”:

HP_i(t+dt) == HP_i(t) - 求和[k,i以外] ( I_k(t)*ap_k(t)dt + DA_k(t)dt )
  + DP_i(t)dt + I_k(t)*th_i(t)dt .

既然变形成了“现代表达式”那么我们还要考虑----是否有“同步”问题??

我们不妨假设,不采用 m_next变量,就用一个 m_val 来存储各属性。那么伪代码:
void Update( dt )
{
HP_i  :=  HP_i - 求和[k,i以外] ( I_k * ap_k * dt + DA_k * dt )
   + DP_i * dt + I_k * th_i * dt .
HP_s  :=  HP_s - 求和[k,s以外] ( I_k * ap_k * dt + DA_k * dt )
   + DP_s * dt + I_k * th_s * dt .
}
现在用“数学武器”分析这个代码,可以用下面的方程描述:
HP_i(t+dt) == HP_i(t) - 求和[k,i以外] ( I_k(t)*ap_k(t)dt + DA_k(t)dt )
  + DP_i(t)dt + I_k(t)*th_i(t)dt .
HP_s(t+dt) == HP_s(t) - 求和[k,s以外] ( I_k(t)*ap_k(t)dt + DA_k(t)dt )
  + DP_s(t)dt + I_k(t)*th_s(t)dt .  
哦,搞了半天,没有同步问题啊。。。虽然没有写 m_next,但这里并没有同步问题.

----为什么?(在“现代表达式”情况下)为什么不写m_next,也没有同步问题?
这是因为----运气好----可以这么说吧。不要忘记了,这是从“经典表达式”变形而来的!
经典表达式,我说了,能力是有限的(不太复杂),那么,变形成“现代表达式”之后,
一般没有“同步问题”。

但是,如果不是从“经典”变形而来的“现代”,那么,往往存在着“同步问题”。
由于输入函数 I(t) 一般是不可导(不可微)的,所以,这种情况必须用m_next。


(2) 间断的(离散的)输入。

这种输入模式相当有意思,
涉及到“单位冲击函数”,消息队列,PeekMsg,消息时间锉,同步,时序 和 非时序的关系。
还涉及到,数学表达式是怎样的?Input Pool是又怎样的?对于游戏逻辑来说,是怎样处理的?

有人说:“你列个数学表达式不就OK了吗?编码问题就不用说了”
----可是,就这个数学表达式本身,就有不止1种写法。。。
(未完待续)
sf_20086441410.gif

149

主题

4981

帖子

5033

积分

论坛元老

Rank: 8Rank: 8

积分
5033
QQ
发表于 2008-6-9 18:54:00 | 显示全部楼层

Re:浅谈游戏逻辑

这里举个初2水平的例子,来说明经典风格的局限性。
假设有3个属性, x, y, z. 随时间 t 变化,即 x(t), y(t), z(t).
现在假设 x,y,z 之间的关系是: x = 3y ;  y = 2z.
即: x(t) = 3y(t) ;  y(t) = 2z(t).
假设游戏开始的时候,他们的数值是已知的,即在 t=0 的时候:
x(0) = x0;  y(0) = y0;  z(0) = z0.
然后,由于游戏的每一frame都要“刷新”一次,
比如,经过了 dt 时间,我们要求得 0+dt 时刻的数值,
那我们来算一算,一共有几个未知数,几个方程...
未知数:x(0+dt), y(0+dt), z(0+dt).
方程:x(0+dt) = 3y(0+dt) ;  y(0+dt) = 2z(0+dt).
结论是,根本就求不出来解。。。于是。。。我们继续往下看。。。

这里不太明白……
就这个方程组来说:
x(t) = 3y(t) ;  y(t) = 2z(t).
我觉得如果用其他的形式描述也仍然解不出来啊……
即使已知某个t时刻xyz的值,由于没有定义这些值和t的关系,所以是不可能知道任意t时xyz的值的,问题是规则定义不完整,而不是表达形式有问题。

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-6-9 19:35:00 | 显示全部楼层

Re:浅谈游戏逻辑

"即使已知某个t时刻xyz的值,由于没有定义这些值和t的关系"
这些值和 t 的关系,是不定义的。这些值将由外界(如人类)进行任意修改

实在抱歉,一些东西我没有说清楚。涉及到的东西比较多。
说来话长。。。我要表达的是,经典物理学无法解决的“3体问题”,不过写帖子写昏了:
...我修改后如下:
============================================================
假设有3个属性, U, I, R. 随时间变化(怎么变化是未知的!)
现在假设 U,I,R 之间的关系是: U == I * R.
即: U(t) = I(t) R(t) ;
假设游戏开始的时候,他们的数值是已知的,即在 t=0 的时候:
U(0) = U0;  I(0) = I0;  R(0) = R0.
此外,我们已知,R(t) == F(t). (...电阻随时间变化!)
============================================================
这个系统随时间的演化是无法求解的。而诸如一个 2 体问题,就可以求解:

N == 0.5 F;  (阻力==0.5*动力)
如果已知 动力F 随时间的变化 F(t),则可以求得阻力:
N == 0.5 F(t).
且可以求得 N( t0 + dt ) == 0.5 * F( t0 ).

但是,对于上面的 3 体问题(U == I R),这样是绝对求解不出来的!
(比如我们经常听说一些“混沌电路”)

经典物理中采用静态的方式(和时间无关)定义数值间的关系,在很多情况下导致无法求解。
这就导致了“复杂”科学的产生。。。通常使用关于 t ,或者 时序 n 的方程来定义系统行为比如:
X(n+1) == Y(n) * Z(n)
Y(n+1) == ...
Z(n+1) == ...
然后观察系统的状态变迁。。。
这样的复杂系统会出现一些“状态圈”“吸引子”

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-6-9 20:01:00 | 显示全部楼层

Re:浅谈游戏逻辑

问题是规则定义不完整

按道理说, U == I * R  ,这个规则十分完整把?

但是,如果你改变某一时刻的 R 数值,会发现, U, I 的未来是不确定的!?


写电路的题目,往往容易写错,道理就在这里。
实际题目中,往往假定:“修改R不会影响U的数值”(一个简单的电路,也没有内电阻 r )

你会认为,“哦,U 是常量啊”,不!,比如稍候我们 改变 U ,然后求解 I.
你就必须假定“修改 U 不会影响 R”...

实际上,专业的电学中,引入了“电压源”“电流源”的概念。
电压源的电压是永远不变的,电流元的电流永远不变。
电压源加上电阻 r 则刻画了一个电池模型。
。。。否则,连最简单的电路也无法求解。。。

149

主题

4981

帖子

5033

积分

论坛元老

Rank: 8Rank: 8

积分
5033
QQ
发表于 2008-6-10 12:28:00 | 显示全部楼层

Re:浅谈游戏逻辑

物理已经快忘光了……?
我对你原文的理解是,“经典风格”是一种表达形式,而这种形式会导致一些问题无解。但是,你的例子的无解不是因为它的表达形式,而是因为这个问题本身就是无解的,只要知识没有增长,那么不管采用什么表达形式都是无解的。

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-6-11 05:21:00 | 显示全部楼层

Re: Re:浅谈游戏逻辑

sjinny: Re:浅谈游戏逻辑

物理已经快忘光了……?
我对你原文的理解是,“经典风格”是一种表达形式,而这种形式会导致一些问题无解...


你说的的确没错拉!(典型程序员思维,不过我喜欢~)
但那只是“表面形式”而不是“实际语义”。

和程序员不同,物理学的目的,是“描述”现有的世界,
而不是创造一个虚拟世界。 即,用一种方程描述这个宇宙?


不论方程如何,都不影响我们的宇宙!

对于一个电路系统的行为,却无法用方程求解。。
难道,“问题无解”?? 那你面前的电路是什么??


我第一次听说“混沌”的时候我奇怪“数学方程,要么有解,要么无解”
于是我一直觉得那些写书的人搞“奇怪理论”。

但今天,我不说这样的话了,因为我实际感觉到了。

正因为“经典物理”的表达形式造成许多复杂问题的无解。
现代的“复杂”科学已经不用这种方式了。

比如近年来研究“boolean 网络”(2进制网络)比较热门。
其实,他们那个“复杂”系统,在我们程序员看来,都是“玩具”。
实际上,我写出“面向对象的陷阱”一文,也正是阅读过那方面的书。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-5-28 02:49

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表