回顾并为今天的内容铺垫背景
我们刚开始为游戏主角编写一些程序逻辑,因为我们之前已经完成了大部分引擎方面的开发,现在可以专注在角色身上。这个角色的移动方式会有些特别,与大多数游戏角色的运动机制不太一样。我们当前正在实现的控制方式在其它游戏中几乎没有见过,因此会显得有些不寻常。我们也需要经过反复调试,才能让这种方式看起来自然、操作起来顺畅,但这正是游戏开发的乐趣所在。
现在角色的“头部”和“身体”是分离的状态,这种设计在视觉上看起来可能有些怪异,但这只是我们实现运动逻辑的一部分暂时手段。我们的设想是,让角色的身体部分始终锁定在屏幕上的某一个特定位置。虽然目前使用的是网格系统(grid),因为很多关卡场景会需要网格式布局,但实际上不强制使用网格,未来也可能会有非网格的设计。然而,不管布局如何变化,角色的身体都会始终锁定在某一个固定的点位上。
我们已经画出了一些橙黄色的参考方块,角色的身体会贴合在这些方块上。当前的目标是,让身体总是处于这些点位之一,而当我们移动角色的头部时,角色的身体并不会立即跟随,而是会“倾斜”过去。只有当头部的移动超过中点时,角色的身体才会真正地“跳跃”到下一个点位。这不是代码中的瞬移,而是真正表现为角色用跳跃的方式跳过去。
头部的移动是可以自由控制的,虽然最终控制方式会与现在略有不同,但整体感觉类似。我们还计划加入一个逻辑:如果玩家停止移动,头部会自动回到身体的原始位置,即“脖子”的位置。
接下来的开发任务包括:
- 编写正式的代码逻辑,确保身体始终锁定在指定的点位上,而不是现在这种临时的实现;
- 实现身体的拉伸与旋转,使其在视觉上与头部保持连接状态,避免出现头部像是悬浮的情况;
- 编写“跳跃”动画的逻辑,让身体在从一个位置跳到另一个位置时看起来自然、连贯;
- 可能还需要几天时间完成这些内容,因为我们每天只进行一个小时的开发;
- 由于这将是游戏中与玩家交互的核心机制,因此必须确保这一套移动方式手感良好、效果美观。
虽然这不会是最终的版本,后期仍会根据整体设计不断进行调整,但我们现在需要一个比较理想的第一版本,确保机制基本稳定,再继续推进后续开发。移动机制如果做得不好,整个游戏的体验就会受到严重影响。
每个人对角色操作手感都有自己的看法,有人可能觉得自己的偏好是“唯一正确”的,这其实是个误区。不同的设计方向代表不同的游戏风格,我们只需要坚持让自己满意、合理的实现方向。
我们已经开始在 world_move.cpp
中编写了基础代码,当前主要是让身体根据头部位置寻找最近的可站立点,并使用我们已有的通用移动代码靠近这个点位。如果身体已经处于目标点上,就保持不动。这个逻辑目前只是原型实现,存在一些问题,尤其是它过于“模拟化”(analog),对于我们这种设计不太合适。
接下来,我们还会继续调整,使其在逻辑和表现上都更加贴合我们对角色运动的最终构想。
黑板讲解:站立模式与移动模式作为两种独立状态
我们目前比较担心的一个问题是角色的身体现在只是简单地跟着头部移动,并没有明确区分角色的状态阶段。举个例子,现在我们有一个头部和一个身体,身体处在某个固定点位上。而我们现在的代码逻辑仅仅是在不停地计算“我要去哪里”,然后让身体往那个目标点移动。
目前这套逻辑只有一个阶段:移动阶段。也就是说,系统始终在考虑当前位置和目标位置,然后不停地尝试靠近目标。如果目标点正好和当前位置一致,就没有移动发生,看起来像是角色停住了,但其实只是碰巧目标和当前位置重合而已。
这种方式存在很多问题,比如移动不够精准、存在过冲(overshoot)、缺乏平稳的停靠逻辑等。而且这套逻辑对“是否到达目的地”没有概念,只是不断追赶目标点。如果想实现“到达即停止”的效果,只能人为设定一个阈值(epsilon),例如靠近目标点到一定距离内就认为到达了,但这显然不是理想的做法。
因此,我们现在打算将角色状态明确分为两个阶段:
- 站立状态(Stand):表示角色当前处于某个点位上,没有发生跳跃或移动;
- 移动状态(Movement):表示角色正在从当前位置跳跃或移动到另一个点位。
通过引入明确的状态区分,我们可以让代码更加清晰地知道角色此刻处于哪种行为模式,从而更好地控制动作触发和过渡逻辑。这也将为后续更复杂的动画表现(如跳跃、蓄力等)打好基础。接下来将会进行这方面的重构与完善。
黑板讲解:有限状态机(Finite State Machines)
有些人曾经问我们能不能解释一下“有限状态机”(Finite State Machine,FSM)。虽然我们之前没来得及讲,现在可以稍微谈一下。我们认为“有限状态机”这个词本身被过度神化了,它听起来像是一个很正式的理论结构,但实际上,在实际编程中,并没有必要那么正式,甚至那种“形式化”的东西反而会成为负担。
如果去读编译器相关的文献,会看到非常严谨的“有限状态机”的定义,比如确定性和非确定性状态机等。这些在理论计算机科学里确实有它们的价值,适合做一些形式分析或模型验证等用途。但在我们真正编写代码、实际开发中,它们并没有太多直接的帮助。
原因在于,这种思维方式从问题的反方向出发。就像面向对象编程(OOP)一样,把“状态”这个概念看得过于核心、过于神圣,然后试图让一切逻辑都围绕状态展开,这是一种本末倒置的方式。状态并不是代码的出发点,也不是第一要务,我们认为更合理的方式是:在自然开发过程中,代码中自然而然地会显露出某些“状态”的特性,我们再顺势而为去引入状态控制。
而不是一开始就设计一个庞大的状态机结构,把问题往一个固定框架里塞进去,那样反而会限制灵活性,容易陷入类似面向对象设计那样的误区。所以我们认为,“状态”应该是自然演化出来的,而不是一开始就强加的框架。
这并不意味着状态机本身是错的。事实上,任何稍复杂的程序最终都会有一些代码段看起来像是状态机,或者从定义上说就是一个状态机。比如,在代码中写一个 enum
类型来代表几种不同的状态,再配一个 switch
语句来处理各种状态间的跳转逻辑,这种结构就是最标准的状态机体现。这是计算的基本原理之一,因此自然会出现在各种程序中。
但我们强调的是:不要以“设计状态机”的名义去限制思维,而是先解决问题,等问题演变出需要状态的结构时,再用枚举加切换逻辑去表达,这样写出来的代码更自然、清晰、灵活。
因此,在当前角色控制系统中,我们希望通过引入一个简单的状态逻辑,让角色的身体存在“静止”和“移动”两种模式。在“静止”模式下,无论头部如何移动,身体都保持不动;只有在满足特定条件时,身体才切换到“移动”状态,并跳跃到下一个位置。
这个思路实现起来也很简单,我们已经在代码的 sim_region
文件中开始实现它。后续会继续扩展这一结构,逐步完善角色的行为系统。
查看 game_sim_region.h 和 game_world_mode.cpp:实现在两种移动模式之间切换的能力
我们引入了一个“模拟运动模式”(sim movement mode)的概念,用于控制角色身体的行为。当前设置了两种模式:
- Planted(扎根):表示身体固定在地面上,不移动。
- Hopping(跳跃):表示身体将从当前位置跳跃到另一个位置。
在角色逻辑中,当我们描述角色的身体(比如 Hiro Body
)时,可以根据当前的运动模式进行判断和处理。在 Planted
模式下,身体不应该进行任何移动操作;而在 Hopping
模式下,身体则会执行跳跃动作。
为了实现这一逻辑,我们在初始化时将运动模式设为 Planted
,也就是说,一开始角色身体是静止的。我们把之前实现的追踪目标点的代码(即朝目标点前进的逻辑)保留在 Hopping
模式下,在 Planted
模式下则完全不执行移动。
接着我们要思考:如何触发从“静止”模式到“跳跃”模式的切换?
为此,我们引入了一个条件判断机制。在每一帧更新时,我们会计算当前“头部”的位置所对应的最近地面点,并将其与“身体”当前的位置所对应的最近地面点进行比较。如果两者不相同,意味着头部和身体所处的位置不一致,说明角色可能想要移动,此时我们就可以切换到 Hopping
模式。
为了实现这个判断,我们将原有的“最近点”计算逻辑从原位置中提取出来,使其在两种模式下都能使用。但只有当处于 Hopping
模式时,才会真正触发跳跃行为。
此外,我们也考虑了使用一个“最小距离阈值”的判断方式来决定是否要跳跃。例如可以计算目标点与当前身体位置之间的距离(使用平方距离以避免不必要的开方运算),如果距离足够远就触发跳跃。这个计算方式使用的是两点之间的 Delta,然后对其求平方长度,用来避免昂贵的开方操作。虽然现在的处理器中平方根运算已经不再那么昂贵了,但由于旧习惯,我们仍然选择避免频繁使用。
总之,我们目前已经实现了基础的两态运动模式控制逻辑——通过距离判断来切换模式,并根据模式决定是否进行跳跃,接下来将会进一步完善跳跃逻辑的细节。
length 才对
「现在已经没人关心平方根了」α
我们现在继续完善角色身体的运动模式切换机制。为了使系统能够根据实际情况在“跳跃”(Hopping)和“静止”(Planted)之间自由切换,我们引入了一个容差值(tolerance),用于判断身体是否足够接近目标点从而停止移动。
具体流程如下:
- 我们首先计算身体当前位置与目标点(通常是头部所对应的地面点)之间的距离差,即“身体位移向量”(body delta)。
- 然后对该向量计算长度平方(body distance),目的是避免调用昂贵的平方根运算。虽然现代处理器已经足够快,平方根的计算性能开销很小,甚至 sin 和 cos 也不是太大问题,但比起内存访问,这些运算已经不值得过度优化,因此这里只是沿用旧习惯。
接下来,根据这个距离平方值来控制状态的转变:
- 如果距离大于预设容差(例如一厘米),我们认为目标发生了偏移,角色应进入跳跃模式(Hopping),开始朝目标点移动;
- 如果在跳跃过程中,当前位置与目标点之间的距离小于容差,我们就认为已经“到达”,于是切换回静止模式(Planted)。
这样我们就完成了一个基本的自动往返状态切换系统。
但是在实际运行中,发现角色进入跳跃模式后无法成功切换回静止状态。为了解决这个问题,我们开始调试代码。
首先怀疑的问题是:角色的阻尼(drag)不足,导致移动过程不能自然减速到足够接近目标点,从而距离始终大于容差,导致状态无法切换回 Planted。
另一个问题是调试体验不佳,我们注意到无法点击选中某些实体(例如角色身体),这使得观察状态非常困难。这表明当前的选择系统存在缺陷,很多实体不能被正确高亮和查看,这在后续需要重点修复。
继续排查发现,代码中还有一些残留的逻辑,比如实体碰撞(entity collision)被错误地设置为“无碰撞”(null collision),这可能是导致实体状态异常的原因之一。我们将这些无关或旧的逻辑清除,以避免影响行为判断。
总结来说,我们实现了一个基于容差值的自动状态切换系统,但在运行中发现状态切换存在问题,目前初步推测可能是阻尼参数不足或其他系统逻辑干扰,后续将继续调试以解决问题,同时也要改善实体调试系统,确保能够可视化查看所有关键组件的状态。
调试器:进入移动代码并发现我们在两个模式间来回震荡
我们目前遇到的问题是角色在切换到“静止(Planted)”模式后仍然无法真正停下来,会在“跳跃(Hopping)”和“静止”模式之间不断来回切换,形成**震荡(oscillation)**状态。
为了排查这个问题,我们给“进入跳跃”和“回到静止”两个逻辑分支都设置了断点,观察程序的实际运行流程。最终发现确实是在两个状态之间来回反复切换。推测原因是因为当前的容差判定机制太粗糙,没有考虑到物理模拟带来的实际运动惯性。
我们意识到角色的移动系统中启用了物理模拟,也就是说,即使当前角色的位置已经足够接近目标点(满足容差条件),由于之前的加速度已经停止但仍然存在速度(velocity),在没有任何阻力(drag 为 0)的情况下角色仍然会因为速度惯性向前滑动,从而滑出目标点,再次触发进入跳跃模式,形成循环。
为了解决这个问题,我们想到了一种简单且有效的方法:在进入“静止”模式的瞬间,手动将速度归零。这样角色就会立即停止移动,不再依赖自然减速或者阻力拖拽,从而避免在边缘位置震荡。
总结当前的核心改动和优化思路:
- 问题核心:物理系统导致角色在接近目标点后仍继续滑行,造成状态反复切换。
- 临时解决方法:在进入“静止”状态后,将速度强制设置为零。
- 原因分析:我们之前只判断距离是否足够接近,却忘了处理角色当前仍具有速度的情况。
- 未来优化方向:可能需要引入更完善的状态过渡系统,包括判断速度、加速度等动态参数,或者调整 drag 系数以更真实地反映角色停止的意图。
通过这一调整,角色应该就能在正确时机真正停下来,避免来回震荡的问题。
运行游戏并发现游戏停下来了
经过调整后,问题基本解决了。现在角色在接近目标点时确实能够停下来,虽然仍然存在因为过度推进(overshoot)而导致的位置微小偏差,但这种情况已经得到了显著改善。我们可以看到,角色在达到目标后不会再像之前那样持续滑动,已经有效地停止了移动。
目前的核心问题是,角色依然会有轻微的过度推进问题,这意味着即使停止了,角色的位置可能会偏离目标一点点,但整体已经避免了之前的震荡问题。
接下来,计划进一步解决这种“过度推进”现象,可能需要调整速度和加速度的计算,或者引入更精确的拖拽(drag)控制来确保角色完全停在目标位置。
总结:当前的改动解决了角色状态反复切换的问题,角色可以停下来,但仍需进一步优化过度推进的现象。
黑板讲解:跳跃与方向改变
目前的目标是将角色的移动方式转变为更符合动画风格的流畅运动,而不是使用之前那种基于加速度和速度的离散式控制方法。我们希望通过更程序化的方式来控制移动,而这种方式可能会带来不同的感觉和效果,但也可能是一种错误的方向,因此还需要进一步的实验和调整。
目前游戏设计的要求是,角色的移动要从一个站位跳到另一个站位,且头部的控制由玩家操作,这一设计已经确定。所以,我们需要确保当角色的头部移动到目标位置时,角色的身体能够相应地进行跳跃,并且跳跃动作应该有好的手感。问题出在,如果玩家在跳跃过程中改变了头部的位置,应该如何处理这一情况。
举个例子,如果角色从站位A跳到站位B,但在跳跃过程中,玩家将头部移到了其他方向(比如回到A或向C),那么在跳跃时,身体应该如何反应?我们有两种可能的处理方式,这两种方式会对游戏的操作体验产生不同的影响:
-
严格控制跳跃:一旦跳跃开始,角色的身体必须完成跳跃,即使玩家将头部移动到其他地方,跳跃仍会继续,身体会按照最初的方向完成跳跃,这种方式类似于篮球的风格:一旦开始行动就无法停止,玩家可以通过移动头部进行躲避或改变方向,但身体的移动必须完成。
-
允许空中控制:在跳跃过程中,玩家可以控制角色的头部,进而影响身体的方向,使得角色可以在空中调整目标,甚至可以跳到其他位置。这种方式给玩家更多的控制自由度,允许他们在跳跃过程中对角色的运动路径进行调整,可能会让游戏感觉更加灵活,但也可能导致操作的感觉不够有力。
这两种方式有着深远的游戏设计影响。严格控制跳跃可能让游戏更加具有挑战性,增加了玩家决策的重量(例如,跳到错误的地方可能导致踩到陷阱或踩到敌人)。但这种方式可能让玩家感到游戏过于沉重,缺乏灵活性。相对而言,允许空中控制会让玩家感觉控制更加自由,但可能会导致游戏失去一些挑战性,特别是在跳跃带来的后果方面。
因此,决定是否允许空中控制需要考虑游戏的整体节奏和难度设计。允许空中控制的同时,可能需要平衡这一机制带来的灵活性和游戏的挑战性,尤其是在设计跳跃失败或跳跃惩罚的情况下。
黑板讲解:重力轨迹与抛物线
现在的目标是实现一种抛物线插值方法来模拟跳跃动作,因为跳跃的轨迹在空中会受到重力的影响,而重力的作用产生的运动轨迹就是抛物线。为了简单明了地说明这一点,假设我们在没有空气阻力的真空环境中,重力作用下物体的轨迹是抛物线形状。这意味着,物体跳跃时的轨迹是与时间的平方成正比的,也就是一个抛物线方程。
从物理学的角度来看,这个抛物线是由重力产生的加速度所决定的。重力加速度是一个常量,通常在地球表面是9.8米每秒的平方。加速度是位移的二阶导数,这意味着它是物体位置随时间变化的速度变化的速率。由于重力是一个恒定加速度,因此物体的运动轨迹会遵循抛物线规律。
具体而言,物理学中的位移方程可以通过积分得到。位置是时间的二次函数,这是因为加速度是常数。通过两次积分重力加速度得到的位置函数,就是一个二次方程,也就是抛物线的数学形式。因此,任何在重力作用下的物体,其运动轨迹都将呈现抛物线形状。
在物理学中,描述抛物线运动的基本方程是通过加速度公式得到的。假设物体在垂直方向上受重力作用,忽略空气阻力,那么其位移公式可以通过以下几个步骤推导得出。
-
加速度:物体受重力作用时,垂直方向上的加速度是恒定的,即 a = − g a = -g a=−g,其中 g g g 是重力加速度,通常取 g = 9.8 m/s 2 g = 9.8 \, \text{m/s}^2 g=9.8m/s2。
-
速度:速度是加速度的积分,表示物体位置随时间变化的速率。由于加速度是常数,我们可以通过积分加速度得到速度:
v ( t ) = ∫ a d t = − g t + v 0 v(t) = \int a \, dt = -gt + v_0 v(t)=∫adt=−gt+v0
其中, v 0 v_0 v0 是初始速度,通常在物体开始跳跃时为零。
-
位移:位移是速度的积分,表示物体的位置。通过对速度方程进行积分,可以得到物体的位置方程:
y ( t ) = ∫ v ( t ) d t = − 1 2 g t 2 + v 0 t + y 0 y(t) = \int v(t) \, dt = -\frac{1}{2} g t^2 + v_0 t + y_0 y(t)=∫v(t)dt=−21gt2+v0t+y0
其中, y 0 y_0 y0 是初始位置,通常在跳跃开始时设为零。
这个方程 y ( t ) = − 1 2 g t 2 + v 0 t + y 0 y(t) = -\frac{1}{2} g t^2 + v_0 t + y_0 y(t)=−21gt2+v0t+y0 描述了物体在重力作用下的垂直运动轨迹,也就是抛物线形状。
- t t t 是时间,表示物体运动的时间。
- g g g 是重力加速度。
- v 0 v_0 v0 是物体的初始速度。
- y 0 y_0 y0 是物体的初始位置。
运动轨迹的特点:
- 抛物线轨迹:在忽略空气阻力的情况下,物体在重力作用下的运动轨迹是抛物线形状。物体先上升达到最高点,然后开始下落。
- 时间的平方关系:位移与时间的平方成正比,这使得轨迹呈现典型的抛物线形态。
总结起来,重力作为恒定加速度的影响,使得物体的运动轨迹呈现出一个抛物线的形态,这也是为什么我们在模拟跳跃时,会使用抛物线插值方法的原因。
「这就是……数学」β
既然已知重力导致物体运动轨迹呈抛物线,那么就可以直接在角色的运动中加入一个抛物线轨迹,使角色从一个位置跳跃到另一个位置,进入其弹道阶段。同时,跳跃的时间需要根据游戏设计来设定。这个设计的思路是,角色的运动不应该作为一个整体来控制,而是分成两个部分:一个部分是以更典型的游戏方式进行移动,这种移动是较为自由且略显随意的;而另一个部分则是非常具体且无法直接控制的,它的运动路径已经预设,并且玩家不能干预其移动方向。
因此,角色的运动机制就是这样设计的,一个部分由玩家控制,另一个部分则会按照设定的轨迹(例如抛物线轨迹)进行移动,这样的设计带来了不同的控制感受。
编辑 game_world_mode.cpp:为角色添加抛物线跳跃动作
当前的目标是实现一个抛物线跳跃动作的模拟,通过修改角色的移动模式。首先,角色在“站立模式”下不会有任何变化,玩家不会进行任何移动。在玩家需要进行跳跃时,会切换到“跳跃模式”,并使用一个“tMovement”变量来控制跳跃过程。这个“tMovement”变量将从0到1变化,表示跳跃的进度。
在实现上,首先需要知道每帧的时间增量(delta time),这个值已经存在于代码中(命名为DT
)。通过这个时间增量,可以控制跳跃进度的变化。当跳跃开始时,tMovement
变量会被初始化为0,表示跳跃刚开始。随着时间推移,tMovement
逐渐增加,当其达到1时,跳跃完成,角色落地,切换回站立模式。
实现过程中,跳跃的过程使用了“从0到1”的参数化方法,这样可以简化跳跃过程的处理,并让我们更容易控制。具体来说,在跳跃的过程中,角色会沿着一个抛物线轨迹从起始点移动到目标点。为了做到这一点,首先需要确定目标点的位置,这个位置是角色跳跃的终点。
然后,通过线性插值的方式,角色的当前位置会根据时间增量和tMovement
变量的变化,平滑地从起始点移动到目标点。在这段过程中,可以调整速度来控制跳跃的快慢。为了加快跳跃速度,可以在tMovement
代码中加入一个倍速因子,这样就能实现更快的跳跃。
然而,单纯的线性插值并不能真正模拟跳跃的抛物线效果,这只是角色从起始点到目标点的平滑过渡。为了加入抛物线的效果,接下来需要引入物理上的抛物线运动。通过在运动过程中加入垂直方向的加速度,模拟重力的影响,使得角色的移动轨迹呈现出典型的抛物线形状。
简单来说,角色的移动将从最初的线性插值过渡到带有抛物线效果的运动,以实现更加自然的跳跃动画。这样,角色的跳跃不再是简单的直线跳跃,而是受重力影响的曲线跳跃,带来更真实的游戏体验。
黑板讲解:用于计算跳跃抛物线的公式
我们的目标是模拟一个跳跃动作,使物体从起点以抛物线的轨迹移动到终点。我们已经有起点(from)和终点(to)两个点,而为了构造出完整的抛物线轨迹,我们还需要一个额外的参数——跳跃的高度。这个高度决定了抛物线的弧度,也就是中间的最高点。
我们先从数学角度分析这个问题。由于我们想要的是一条抛物线轨迹,所以函数的基本形式应该是一个二次函数,即:
f ( t ) = a t 2 + b t + c f(t) = a t^2 + b t + c f(t)=at2+bt+c
其中 t t t 是一个从 0 到 1 的归一化参数,表示从起点到终点的进程。
我们已经知道以下两个条件:
- 当 t = 0 t = 0 t=0 时,位置应为起点 P from P_{\text{from}} Pfrom
- 当 t = 1 t = 1 t=1 时,位置应为终点 P to P_{\text{to}} Pto
将这些条件代入公式:
-
f ( 0 ) = a ⋅ 0 2 + b ⋅ 0 + c = c = P from f(0) = a \cdot 0^2 + b \cdot 0 + c = c = P_{\text{from}} f(0)=a⋅02+b⋅0+c=c=Pfrom
→ 因此我们知道 c = P from c = P_{\text{from}} c=Pfrom -
f ( 1 ) = a ⋅ 1 2 + b ⋅ 1 + c = a + b + P from = P to f(1) = a \cdot 1^2 + b \cdot 1 + c = a + b + P_{\text{from}} = P_{\text{to}} f(1)=a⋅12+b⋅1+c=a+b+Pfrom=Pto
→ 整理后得 a + b = Δ = P to − P from a + b = \Delta = P_{\text{to}} - P_{\text{from}} a+b=Δ=Pto−Pfrom
接下来,我们发现虽然只用了两个条件,但由于公式中有三个未知数(a, b, c),我们实际上只剩下一个自由度可以调节(a 和 b 的组合必须满足 a + b = Δ a + b = \Delta a+b=Δ)。这个自由度就可以用来控制跳跃的“弯曲程度”,也就是抛物线的弧高。
我们可以自由选取其中一个(比如 a),另一个就自动由差值决定(b = Δ - a)。这意味着我们可以根据希望的跳跃高度来调整 a 向量的方向和大小,从而间接地控制抛物线的形状。
这种构造方式的意义在于:
- 起点和终点已经确定;
- 抛物线的中间形状可以通过一个额外参数(如最大高度)来控制;
- 无需完全解析求解,只需要理解 a 和 b 向量之和等于 Δ(位移差)即可;
- 通过构造出任意两个向量,只要它们的和等于目标位移,就能得到一条合法的跳跃轨迹。
总结来说,我们采用了一种将跳跃运动参数化的方法,从已知的起点、终点以及一个自由度(跳跃高度)出发,构建出一个满足起点终点条件的抛物线插值函数。这个方法既直观又灵活,非常适合在游戏开发中调试和控制角色跳跃的手感和视觉表现。
在 game_world_mode.cpp 中实现该跳跃公式
我们现在将跳跃动作的公式真正应用到实际代码中。在这之前我们使用的是线性插值(lerp
)来实现跳跃位置的更新,但为了更真实地模拟物体在重力作用下的跳跃轨迹,我们用一个二次方程来替代线性插值。
我们把公式直接写进代码中。这个公式的基本形式是:
position ( t ) = a ⋅ t 2 + b ⋅ t + P from \text{position}(t) = a \cdot t^2 + b \cdot t + P_{\text{from}} position(t)=a⋅t2+b⋅t+Pfrom
其中:
- t t t 是跳跃的归一化时间值,范围从 0 到 1;
- a a a 和 b b b 是控制跳跃轨迹形状的两个向量;
- P from P_{\text{from}} Pfrom 是起跳点的位置。
在实现中,我们先定义了一个时间变量 t t t,然后代入到公式中去计算当前位置。这个公式清晰地表达了跳跃动作的轨迹。我们只需计算出当前的 t t t,就可以得到角色在跳跃过程中的位置。
接下来是关于 a a a 和 b b b 的设置。我们之前已经得出一个结论: a + b = Δ a + b = \Delta a+b=Δ,其中 Δ \Delta Δ 是从起点到终点的总位移。因此我们实际上只需要设置其中一个向量(例如 a a a),另一个向量 b b b 就可以通过:
b = Δ − a b = \Delta - a b=Δ−a
来自动推导出来。这给我们带来了极大的灵活性和调试的便利。
举个例子,如果我们先不设置 a a a(也就是 a = 0 a = 0 a=0),那么整个跳跃就由 b ⋅ t b \cdot t b⋅t 和 P from P_{\text{from}} Pfrom 决定,效果类似线性插值。然后我们可以逐渐调整 a a a,也就是调整抛物线弯曲程度,从而控制跳跃的弧度——也就是跳多高。这可以用来“调跳跃手感”。
在调试中我们需要确保不要误删公式本身,否则计算会失败。比如刚刚出现了一个失误,把公式删掉了导致结果出错,一旦恢复公式后,跳跃轨迹便如预期运行。
这个实现方法简洁直观,适合在实际游戏中用来控制角色跳跃路径和动画。通过调节一个参数就能控制整个轨迹的弧度,非常适合做游戏体验的细致调优。
运行游戏并查看当 A 为 0 时的运动效果
我们发现,如果将公式中的二次项(也就是 a ⋅ t 2 a \cdot t^2 a⋅t2)的系数 a a a 设置为 0,那么整个公式就会退化为线性插值(lerp)形式。
我们原本的跳跃公式是:
position ( t ) = a ⋅ t 2 + b ⋅ t + P from \text{position}(t) = a \cdot t^2 + b \cdot t + P_{\text{from}} position(t)=a⋅t2+b⋅t+Pfrom
当 a = 0 a = 0 a=0 时,公式简化为:
position ( t ) = b ⋅ t + P from \text{position}(t) = b \cdot t + P_{\text{from}} position(t)=b⋅t+Pfrom
这个形式实际上就是标准的线性插值公式,表示从起点 P from P_{\text{from}} Pfrom 沿着方向 b b b(也就是终点减去起点的位移)以线性方式前进, t t t 从 0 到 1 变化时,位置就从起点线性过渡到终点。
也就是说,如果我们不在跳跃轨迹中添加任何曲线(即没有抛物线部分),那结果就是一个直线移动的轨迹,这也正是 lerp
的定义。因此,这个二次方程是一种更通用的方式——当我们不需要弯曲轨迹时,它退化为线性插值;而当我们想要创建一个弧形轨迹时,只需调整 a a a 的值,即可产生向上或向下弯曲的抛物线轨迹。
这个特性在实际游戏开发中非常实用。它说明我们可以通过简单地调整一个参数( a a a)来在直线移动和跳跃轨迹之间切换,极大地方便了动画调节与跳跃手感的微调。这样一来,我们拥有了一个既统一又灵活的跳跃轨迹生成方式。
编辑 game_world_mode.cpp:使跳跃动作呈抛物线
我们决定要调节跳跃中的哪一部分呈现抛物线形状。显然,最希望呈现抛物线的部分就是跳跃的高度,也就是角色在空中上下起伏的部分。我们希望这个高度变化是平滑的、符合重力作用的自然轨迹,因此这个部分必须是抛物线曲线。
虽然我们目前尚未在代码中处理 Z 轴(通常在 3D 空间中高度会使用 Z 轴表示),但由于还没引入 Z,我们暂时把高度效果放在 Y 轴上,也就是用 Y 轴来代表跳跃中的垂直方向。
这样可以看到,当我们在 Y 轴上引入一个二次项 a ⋅ t 2 a \cdot t^2 a⋅t2 来控制高度的变化时,角色的跳跃轨迹就会在垂直方向上形成一个漂亮的弧线,从而看起来更自然。屏幕上的效果会表现为角色先向上移动,然后在跳跃到最高点后再下落,符合真实的物理行为。
总结来说:
- 我们希望控制跳跃“高度”部分呈现抛物线;
- 暂时将这个高度的抛物线作用在 Y 轴;
- 抛物线通过设置 Y 分量的二次项 a ⋅ t 2 a \cdot t^2 a⋅t2 来实现;
- 轨迹在视觉上向上拱起,然后下降,模拟自然跳跃效果;
- 虽然最终可能需要使用 Z 轴处理真实高度,这里用 Y 是为了临时展示轨迹效果。
这种设计思路使得我们可以非常灵活地调整跳跃的“弧度”,只需调整一个参数,就能控制跳跃高度和轨迹形状,方便动画表现和手感调试。
运行游戏并看到抛物线跳跃效果
我们将跳跃动作中的一部分运动分量放入了抛物线部分,这意味着角色现在的跳跃是沿着一个抛物线轨迹进行的。具体来说,我们将跳跃过程中的高度变化这一部分,用一个二次函数来表示,使其具有自然的拱形轨迹。这种方式让跳跃看起来更像真实物理下的自由落体运动。
在当前实现中,水平运动(即角色从起点到终点的直线位移)仍然保持线性插值(linear interpolation),也就是线性运动。只有垂直方向的运动,也就是“跳的高度”,采用了抛物线插值。这种做法使得角色在跳跃时看起来既平滑又自然:路径呈弧形,起跳后上升到顶点,然后再落下。
由于临时关闭了碰撞检测系统,所以角色的跳跃暂时不受任何障碍限制,这让我们可以自由地测试抛物线轨迹,即使跳出正常区域也不会触发阻挡或碰撞逻辑。虽然我们可能还没完全理清一些底层逻辑,例如为什么某些跳跃会直接穿越边界等,但目前的测试目的是验证跳跃轨迹是否按照期望变成了抛物线。
因此,当前的实现逻辑如下:
- 起点和终点之间的水平运动依然使用线性插值计算,保持简单;
- 跳跃的垂直高度则使用了一个二次函数,使其形成抛物线轨迹;
- 由于抛物线只作用于高度分量(目前是 Y 轴),角色的跳跃轨迹会在视觉上呈现拱形;
- 碰撞检测被关闭,方便自由测试跳跃逻辑,不受物理阻挡影响;
- 整体跳跃路径是“线性水平方向 + 抛物线垂直方向”的合成效果。
这个方式提供了一种简洁且可调节的跳跃实现,我们只需调节抛物线高度参数即可获得不同手感的跳跃效果,非常适合用于游戏中角色的跳跃动画调试。
简要讲解如何将算法简化为更少的变量
我们要记住一个非常实用的思路:在面对复杂的数学问题或游戏中动画调节时,不一定非要一开始就解出完整的解析式。我们可以选择先把问题拆解简化,尽可能将方程中无法确定的部分缩减为少数几个关键变量,然后通过手动调整这些变量的方式来完成调优。
这种思维方式的关键在于,不必执着于推导出一个完整封闭的数学解,而是更侧重于实用、可控和可调性。例如我们刚才所做的,就是一个典型案例:我们没有一开始就试图完全解析一个跳跃轨迹的完整数学表达式,而是通过分析,明确哪些参数可以确定,哪些还不确定,然后通过实际测试和调整,把跳跃动作调成我们想要的样子。
这个方法的核心思想可以总结如下:
- 问题先简化:先解决已知条件对应的方程部分,比如起点、终点,以及常量项。
- 保留可调项:把无法精确求解或需要依据体验微调的部分,作为参数保留下来。
- 变量调节控制行为:通过不断尝试和观察结果,调整这些参数,直到得到想要的行为表现。
- 数学为工具而非束缚:即便没有完整解方程的能力,也可以凭借理解和控制关键变量,达成目标。
在我们实现跳跃轨迹的例子中,这种思路就显得尤为高效。我们将整个跳跃分解为线性移动加上一个抛物线的高度变化,并只保留一个主要参数(抛物线的“高度”)作为调节对象。剩下的部分可以自动平衡,确保最终的跳跃从起点准确到达终点。
这种方式非常适合游戏开发过程中那些需要兼顾数学合理性与可玩性、灵活性的问题。它鼓励我们从实际出发,以调节为目的,利用数学表达式作为控制手段,而不是硬性约束。通过这种方法,我们可以更快速、更直观地实现复杂运动或动画系统,同时还能保持足够的精度和物理合理性。
问答环节
那个跳跃动作太酷了,可以多演示一下吗?
目前的跳跃效果还远称不上酷炫,整体看起来比较粗糙,比如角色的头部甚至没有正确地附着在身体上,导致视觉上显得不协调,破坏了跳跃动作的整体观感。
在交互方面也存在问题,例如当前居然可以选中头部这部分,看起来并不合理。我们并不清楚为什么现在的系统允许我们去单独选中头部,很可能是选择逻辑本身出现了问题,可能需要我们回过头去检查相关的选择系统代码。很有可能是之前做修改时对选择机制影响较大,或者某些更新中破坏了实体结构之间的绑定和继承关系。
从整体结构上看,这些问题反映出:
- 模型结构不完整:头部没有正确绑定到身体的骨骼或节点上,说明模型层级结构或动画绑定存在问题。
- 选择系统异常:允许用户选中不应该直接操作的部分,例如角色的一部分独立于整体被选中,反映出选择逻辑未考虑层级归属或父子结构。
- 功能残缺或丢失:例如碰撞检测和物理系统可能被临时关闭或禁用,导致当前测试跳跃功能时出现一些意料之外的行为,比如角色穿出地图、跳出合法区域等。
- 调试状态未清理:很多临时关闭的系统可能没有恢复到正常状态,比如碰撞检测未开启,渲染层级没修复,导致视觉效果不完整,逻辑行为不准确。
这些都说明当前系统仍处于一种实验调试阶段,功能未整合、状态未统一,后续需要对各个系统进行逐一排查、重构与修复,特别是模型绑定和选择逻辑,应该优先处理,以确保基本交互的完整性和一致性。整体而言,跳跃系统虽然数学逻辑和行为模型逐步清晰,但在具体实现细节层面仍需大量打磨和修正,才能最终达到理想效果。
你是如何将头部分离出来的?
目前我们的角色模型实际上由两个独立的实体组成:一个是“头部”实体,另一个是“身体”实体。它们在逻辑和物理上并没有绑定在一起,因此在表现上看起来像是两个分离的对象。
由于没有建立父子层级关系或其他类型的绑定关系,这两个实体在运动时是彼此独立的,比如当身体进行跳跃时,头部不会自动跟随它移动。这种结构对于动画、交互和物理模拟来说是非常不利的,会导致一系列的问题:
- 视觉不一致:角色在跳跃、移动时,头部和身体的不同步会显得非常奇怪,缺乏统一的动作表现,破坏了沉浸感。
- 物理行为分离:如果头部和身体都参与物理模拟,它们可能会受到不同的力或发生穿插、碰撞等不一致的问题,进一步加剧表现上的不自然。
- 选择与交互混乱:因为是两个实体,用户在选择角色时可能只能选中其中之一,这会干扰正常的选择逻辑,例如玩家点击头部时系统不认为是在操作角色本体。
- 动画难以同步:动画播放无法统一控制,头部和身体的动作无法协调,从而导致角色动画出现断裂、不连贯等现象。
当前这种结构应该是临时状态,或是为调试某些功能而做的过渡性处理。为了解决这个问题,需要:
- 建立“头部”与“身体”的绑定关系,例如通过骨骼系统将头部附着到身体的某个节点。
- 在逻辑上将两者合并为一个实体,或通过父子层级结构实现统一控制。
- 确保物理系统和渲染系统都能识别和处理这个统一的角色对象。
- 在选择系统中屏蔽对子部件的单独选择,或者将选择代理到整个角色对象。
总之,当前的实体分离状态虽然可以用于原型测试和调试,但在正式功能中必须进行整合,才能实现一个完整、协调、自然的角色跳跃与交互系统。
你觉得为每次跳跃计算所需“力”和方向以达到某个高度是否值得?还是说这工作量太大而回报不明显?
我们讨论是否应该通过计算施加的力和方向,来使角色每次跳跃都能达到特定高度。结论是:在我们的场景中不值得这么做,原因如下:
我们承认,从理论上讲,如果游戏系统以物理模拟为核心(例如基于真实力学的弹跳、反作用力、摩擦、刚体交互等),那么计算跳跃所需的力和方向是非常必要的。这种情况下,角色运动不能随意通过代码直接修改位置,必须通过施加合适的力,才能让系统中的碰撞、推动、反弹等交互成立,保持物理一致性。
但在我们的游戏结构中,并不依赖物理模拟系统。我们采用的是基于逻辑控制的跳跃系统,即我们直接计算路径、位置,通过插值函数(例如线性插值或二次曲线)来控制角色从起点跳到终点,中间模拟出一个抛物线。整个过程不需要真实的重力或力的计算。因此我们不需要去反推出“为了达到某个高度,应该施加多大向上的力”这种信息。
换句话说,我们不是用物理系统来“推动”角色跳跃,而是直接告诉角色“你现在该在这个位置”。在这种逻辑驱动的系统中,如果去引入额外的力学计算,不仅工程量大,系统复杂度高,而且对我们实际的表现几乎没有提升,甚至还可能导致更多的调试和维护问题。
总结如下:
- 如果是纯物理驱动的系统,必须通过力来控制跳跃,那样可以带来系统上的一致性和物理交互能力。
- 我们当前系统是逻辑驱动、不依赖真实物理模拟,直接计算跳跃路径就可以满足视觉和功能需求。
- 所以,我们选择更简洁的逻辑路径控制,而不是求解施力方向与大小。这在当前框架下更加高效实用。
我猜宠物和敌人也需要以类似方式移动?是否还有很多工作需要完成才能适配它们?
我们考虑将相同的跳跃移动系统应用到其他实体,例如跟随者(familiar)和敌人。因为这些实体需要以与主角类似的方式进行移动,所以还需要进行以下工作才能让系统兼容:
-
路径规划逻辑的开发
我们需要设计路径规划系统,使其能够识别跳跃只能发生在特定方格(如合法跳跃平台或路径)之间。换句话说,这个系统需要明白不是任何两点之间都能跳跃,跳跃只能在满足特定规则的格子之间进行。 -
跳跃路径限制
对于每一个目标跳跃格子,系统需要判断是否存在一个有效的跳跃路径(例如,有没有可以落脚的平台,有没有空间进行上升和下降等),避免生成非法路径。 -
运动系统的通用化
当前的跳跃系统是为主角构建的,接下来需要将其抽象成通用模块,使得敌人和跟随者也能复用这一跳跃逻辑。包括抛物线参数的配置、起点终点的选择、状态控制等,都需要从主角专用变为可配置和通用。 -
行为系统的协调
对于敌人或跟随者的AI逻辑,需要支持根据路径规划结果发起跳跃动作。例如,敌人看到目标在另一平台时,应能自动判断出跳跃路径并发起跳跃,而不是单纯走直线。 -
状态同步与动画系统适配
敌人与跟随者在跳跃时需要切换到跳跃状态,这涉及动画播放、行为冻结(如不能攻击或转向)等。需要确保他们跳跃的动画和行为表现与主角保持一致或根据角色差异做适当调整。 -
多实体控制与同步
同一时间可能会有多个实体跳跃,因此系统还需处理好多跳跃实例之间的调度、防止穿插重叠、状态冲突等问题。
最后提到的“清理额外的头部”,说明当前在系统中可能因为调试或实体管理问题,存在重复或多余的角色头部对象。这部分也需要通过清理多余实体或修复实体系统中的BUG来完成。
总结:
- 需要为敌人和跟随者添加专属的路径规划系统,并确保它们理解跳跃限制;
- 把跳跃行为从主角专用转为通用;
- 处理AI控制、动画切换、多实体状态同步;
- 修复现有实体系统中残留的临时或错误对象(如多余的头部)。
这一整套流程,是将系统从“主角跳跃功能”扩展为“通用跳跃逻辑”的关键步骤。
可以清理屏幕上多余的头部和身体图像吗?还是说之后还会用到?
在当前的开发状态中,屏幕上的“头部”和“身体”仍然是实际存在的实体,虽然它们的功能和用途可能还没有完全确定。具体来说:
-
头部与身体的用途
- 头部实体可能会跟随主角,作为游戏中的某种辅助对象。这意味着它不仅是一个视觉元素,可能还会参与到一些游戏机制或动画中。
- 至于另一个实体(可能是目标或其他互动对象),它的确切用途尚不明确,但有可能是某种目标指示物或者类似的功能,之后可能会被去除或重构。
-
其他不必要的元素
- 当前屏幕上还显示了一些看似没有实际用途的小“板块”,这些板块可能是某种可以发射的物品,尽管现在没有明显的目的。这些可能是在开发过程中临时添加的元素,未来有可能被去除或重新设计。
-
头部与身体的连接问题
- 目前,头部并没有固定在身体上,可能是由于程序设计的问题。为了确保更自然的表现,理想情况下,头部应该始终保持在身体的顶部。这个问题可能是由于碰撞检测、实体绑定或其他物理处理上的疏忽,之后需要进行修复或调整,以便实现更符合预期的视觉效果。
总结:
目前屏幕上的头部和身体仍是实际存在的对象,但它们的具体作用和连接方式可能还需要进一步的调整。某些不必要的物品或功能(如可以发射的小板块)会根据后续需求进行删减或优化。
为什么头部没有一直待在身体上方?这是 bug 吗?
目前,头部和身体是两个不同的实体,它们各自独立移动。头部和身体的分离并不是一个BUG,而是设计上的选择。身体会跟随头部的运动,但两者依然是分开的实体。未来是否将它们合并为一个实体,将根据实际构建的实体系统来决定。开发过程中,实体系统的设计会影响最终的决策。
现在的目标是让身体根据头部的位置进行跟随,使得两者之间的连接能够随着头部的移动而延伸,但依然保持它们独立控制的状态。这个设计的思路是让它们在独立控制的同时,保持一种连贯的运动表现,而不是将它们完全合并成一个单一的实体。
在写过程式代码时,我常常写出很深的嵌套结构。应该避免这种情况吗?有没有方法能简化?
深度嵌套的代码确实在编辑器中的处理可能会有些困难,因为编辑器通常不太擅长有效地帮助管理这类代码,可能会导致阅读和修改变得更加麻烦。虽然嵌套代码本身没有本质上的问题,但如果它对开发造成了不便,那么最好的做法是将一些嵌套的逻辑提取到独立的函数中。通过这种方式,代码不仅变得更加清晰,而且可以提升可维护性。因此,如果嵌套代码让你感觉麻烦,简单地将它分解成函数并不会有什么大问题。
这开始有点像《节奏地牢》(Crypt of the Necrodancer)了
这款游戏的设计与《Crypt of the NecroDancer》并没有太多相似之处。虽然只是简短地玩过《Crypt of the NecroDancer》,但就目前的了解而言,这两者在设计理念和玩法上没有太多的关联。至于“Trap Master”游戏,虽然没有具体描述,但看起来它的设计与前述游戏并不相同。
我错过了很多。你是在为这个游戏制作引擎吗?还是这些东西只是目前给你/我们用的?
这款游戏的引擎是从零开始开发的,包括渲染器,甚至可以使用软件渲染,这挺酷的。不过,目前编译模式是调试模式,且在软件渲染时似乎没有清除操作,这可能是一个问题。尽管在软件渲染中没有清除应该也不算严重问题,因为本不应该有空白区域,但还是不确定为什么没有进行清除操作。通过查看 render.cpp
文件,发现清除操作被写入,但它可能并没有正确地清理屏幕的背景,这可能导致渲染问题。因此,虽然系统目前在这种状态下可以运行,但仍有一些细节上的问题需要进一步调试和修复。
调试器:进入 RenderCommandsToBitmap 并查看 Clear 的处理过程
在调试过程中,发现屏幕没有正确清除,经过分析后发现问题出在清除颜色的 alpha 值上。原本应该清除屏幕的颜色是透明的(alpha 为 0),而这个值导致了屏幕没有完全清除。因此,这个问题看起来是由于设置了错误的清除颜色导致的,这样的错误显然会影响屏幕的渲染效果。为了修复这个问题,需要将 alpha 值设置为正确的值,而不是 0,这样屏幕清除操作才能正常执行。
软件渲染模式clear 之前已经清理过了
编辑 game_render.cpp:设置传递给 Clear 的透明度参数
经过进一步分析,发现将 alpha 值设置为 0 是合适的,因为在清除操作时,目标的 alpha 值需要设置为某个特定值。因此,虽然这并不完美,但从技术角度来看,这种设置是正确的。为了确保清除操作正常进行,可以将颜色值设置为一个临时的有效值,而这正是导致问题的根源。
运行游戏并看到软件渲染器成功清屏
通过进一步检查性能,发现硬件渲染通过 OpenGL 运行非常流畅,而软件渲染的表现也相当不错。实际上,如果 Windows 不存在延迟,软件渲染的帧率应该能够达到每秒 60 帧。然而,部分性能瓶颈来自于 Windows 系统本身的处理延迟,此外,可能还需要使用 P 缓冲区(P Buffer)来优化屏幕的传输过程,进而提升 OpenGL 提交的效率。
Z 轴被定义后,跳跃的上下运动会有所不同吗?
目前需要解决的问题是 Z 轴的处理一直存在一些问题,现在已经意识到它应该如何调整。接下来需要对 Z 轴进行一些调整,以确保它能够按照预期工作。
C#开发游戏的限制有哪些?你整体上喜欢 C
使用 C# 开发游戏存在许多限制。首先,C# 是一种垃圾回收语言,这意味着内存管理由系统自动处理,而开发者无法直接控制。其次,C# 是通过解释器运行的,虽然它可以通过即时编译来优化,但依然比低级语言如 C 或 C++ 在性能上有差距。C# 是一种高级语言,存在许多抽象层,这使得开发者无法直接控制 CPU 的操作,背后有很多复杂的中间层。
开发者普遍不喜欢这种语言,因为它在开发过程中增加了额外的抽象层,减少了对硬件的直接控制。
如果不是面向对象风格,你用了 C++ 的哪些部分?
如果不使用面向对象编程(OOP)风格,我们主要使用函数重载、运算符重载以及在某些情况下使用构造函数和析构函数对进行块作用域管理。这些是主要用到的功能。
我现在用的是 Microsoft Natural Ergonomic 4000 键盘,想换成非人体工学的机械键盘,有什么建议?
对于微软自然系列的键盘,特别是微软自然4000型号,它的空间键确实有一些难以操作的问题,很多人对此表示困扰。个人来说,虽然我更倾向于使用非人体工学的机械键盘,因为设计上更符合我的打字习惯,但如果有人能够设计一款真正合理的人体工学键盘,我会考虑尝试。不过,普遍感觉是,设计这些自然或者人体工学键盘的设计者似乎并不经常打字,至少从我的经验来看是这样。
这游戏做完时会有多少个 A?(注:疑为玩梗)
在这段内容中,提到了游戏完成后的目标并没有明确指出是否指向像 AAA 游戏那样的规模。至于游戏的规模,似乎并没有特别强调要达到 AAA 游戏的标准,更多的是关注实际的开发过程和当前所面对的挑战。
此外,关于底部一排的树被裁剪的部分,提出了当前图形显示的问题,可能是由于视角或渲染设置的问题,导致树的顶部被裁剪掉。这可能需要调整渲染设置或视角来解决。
总的来说,开发中的关注点不仅仅是实现大规模的游戏内容,而是处理一些细节上的问题,如图形显示和游戏逻辑的调整。
的确是会被编译的
讨论了 C# 编译和运行时的特点。C# 虽然可以进行即时编译(JIT,Just-In-Time Compilation),但它并不直接编译成机器码,而是编译成字节码。这个字节码在运行时由 .NET 虚拟机(CLR)执行,而不是生成直接的机器码。因此,开发者无法像在低级语言中那样完全控制生成的机器代码,也无法准确预测垃圾回收器的运行时机和方式,因为垃圾回收是由系统自动管理的。
此外,C# 编译器和垃圾回收机制都由运行环境控制,开发者无法干预这些内部机制的具体实现。这意味着,开发者的控制力受到限制,而这些机制的表现可能会因不同的 C# 实现版本而有所不同。因此,虽然 C# 可以被编译并优化,但仍存在许多抽象层,开发者无法直接控制程序的底层行为。
你对 Apple 的 Swift 看法如何?现在它已经开源了,你怎么看?
这段内容提到没有看到 Swift,可能是在讨论一种编程语言或开发环境,并表达了对 Swift 语言的不了解或缺乏接触。这里没有深入讨论 Swift 具体的特点或应用,而是仅提到自己并未接触过它。
为什么我要投总统初选时还要声明一个政党?
在华盛顿州,之所以必须声明一个政党才能参加总统初选,原因是根据华盛顿州最高法院的判决(如果不是联邦法院的话)。这项判决要求选民在参与初选时声明政党,以便确定选民所能投票的党派。
查看资源:查看树的位图,发现树像是被裁剪的原因是缺少一个 1 像素的边缘区域
至于原先的问题,关于图像资产的问题,之所以看起来像是被裁剪了,是因为图像的实际结束位置就在这个地方,而这一部分正好位于一个像素的边缘区域,这部分不会被渲染。如果这些图像资产按照最终游戏中所要求的格式进行打包(即加上这个一像素的边缘区域),那么就不会出现裁剪的情况。