框架方块(BlockFrame)

MCFrames 的核心方块:一个 8 格产出的"连接器",本身不搬运任何东西,只声明"我能在这些面上抓住别的方块"(IFrame)。它自己不启动移动——真正推动结构的是 马达方块。

方块名 mcframes.frame,类 mrtjp.mcframes.frame.BlockFrame。

基本属性

继承 MrTJPCore 的 BlockCore(普通方块基类),构造时执行 GameRegistry.registerBlock(this, getItemBlockClass, name)。

属性 值 来源
方块名 mcframes.frame frame.scala:33
基类 mrtjp.core.block.BlockCore with IFrame frame.scala:33-34
材质 Material.wood frame.scala:33
抗爆 setResistance(5.0f) frame.scala:35
硬度 setHardness(2.0f) frame.scala:36
脚步声 Block.soundTypeWood frame.scala:37
创造栏 CreativeTabs.tabTransport frame.scala:38
物品类 getItemBlockClass = classOf[ItemBlockFrame] frame.scala:40
渲染 getRenderType = RenderFrame.renderID(ISimpleBlockRenderingHandler) frame.scala:76

形状相关的三个开关:isSideSolid = false、isOpaqueCube = false、renderAsNormalBlock = false——它不是实心方块,是个细框。

抓取规则(IFrame)

override def stickOut(w: World, x: Int, y: Int, z: Int, side: Int) = true
override def stickIn(w: World, x: Int, y: Int, z: Int, side: Int) = true

六个面全部为 true。含义是双向的:

  • stickOut:我能不能在这个面上抓住别人
  • stickIn:我能不能被别人(在这个面上)抓住

IFrame 的 javadoc 有一条硬性约定:这两个方法必须在客户端与服务端返回相同结果,因为结构解析在两侧都要跑一遍。

选中框与射线检测

override def getSelectedBoundingBoxFromPool(w: World, x: Int, y: Int, z: Int) =
  Cuboid6.full.copy.add(new Vector3(x, y, z)).toAABB

override def collisionRayTrace(world, x, y, z, start: Vec3, end: Vec3) =
  MCFramesAPI.instance.raytraceFrame(x, y, z, 0, start, end)

选中框是整格满立方(方块很细,但选中框给满格,方便点选),而射线检测走 ModelRayTracer.raytraceModel 对真实几何求交,mask 传 0(渲染全部 6 个面)。mask = 0 对应下面模型的 64 种组合中的"六面全有"。

物品:ItemBlockFrame

ItemBlockFrame extends ItemBlock,getMetadata(meta) = meta 原样返回(不裁剪),func_150936_a 直接 return true(canPlaceBlockAt 恒真,任何位置都能放)。

onItemUse 有一层外部接管机制:

if (ItemBlockFrame.placements.exists(_.onItemUse(item, player, world, x, y, z, side, new Vector3(hitX, hitY, hitZ)))) {
  SoundLib.playBlockPlacement(world, x, y, z, field_150939_a)
  true
} else super.onItemUse(...)

遍历所有已注册的 IFramePlacement,任一个返回 true 就短路——放音、返回 true,方块本体根本不会被放置。IFramePlacement 的 javadoc 明说用途是让方块落到 Forge Multipart 结构里时能被并入该多方块 Tile。ItemBlockFrame.placements 由 MCFramesAPI.registerFramePlacement 追加,见 API 接口。

渲染:64 种朝向组合的运行时代码生成

模型不是 JSON/贴图拼的,而是运行时用 CodeChickenLib 的 CCModel 现算出来并缓存。

模型来源

RenderFrame 在对象初始化时解析 OBJ:

private val model = parseModel("frame")   // /assets/mcframes/obj/frame.obj
// 另有 private val models —— 长度 64 的 CCModel 缓存数组,索引即 6 位面 mask
// (源码原文为 new Array[CCModel] 加参数 64 的写法)

private def parseModel(name: String) = {
  val m = combine(parseObjModels(
    this.getClass.getResource("/assets/mcframes/obj/" + name + ".obj").openStream(),
    7, InvertX).values())
  m.apply(new Scale(1.00075, 1.00075, 1.00075))
  m.apply(new Translation(Vector3.center))
  m
}

第三个参数 7 是 OBJ 的缩放级别,随后又乘一次 1.00075 并居中。models 是长度 64 的缓存数组,索引就是 6 位面 mask:

def getOrGenerateModel(mask: Int) = {
  var m = models(mask & 0x3f)
  if (m == null) { m = FrameModelGen.generate(model, mask); models(mask & 0x3f) = m }
  m
}

mask & 0x3f 保证越界值被安全截断。64 个模型按需懒生成,第一次用到某组合时才计算。

FrameModelGen 的四步管线

常量:w = 2.0/16.0(peg 直径)、l = 16.0/16.0(peg 长度)、i = 1.0/16.0(抬高量)、u = v = 0.5(贴图中心)。

generate(box, mask) 按顺序做四件事:

  1. generateSinglePeg —— 造出一根 peg。dw = w/2.0、dl = l/2.0,一个 4 顶点的 quadModel,四个顶点分别是 (dw, i, -dl)、(dw, i, dl)、(-dw, i, dl)、(-dw, i, -dl),UV 用 u ± dw、v ± dl,最后整体 Translation(u, 0, v) 移到格心。peg 贴着方块某一面,长度 l = 1.0 正好伸出整格。
  2. generateQuartRotated —— 把它和一份绕 Z 轴 90° 的副本 combine,副本额外 Translation(0, 0.01, 0) 微抬 0.01 格避免 z-fighting。四根 peg 组成十字。
  3. generateEightRotated —— 对整体施加 Rotation(math.Pi / 4, 0, 1, 0).at(Vector3.center),即绕 Y 轴 45°,把十字变成八角形,视觉上是个圆环。
  4. generateBackface —— combine(Seq(m, m.backfacedCopy())),正反两面都渲染。

然后按 mask 挑面:

var b = Seq.newBuilder[CCModel]
b += box
for (s <- 0 until 6)
  if ((mask & 1 << s) == 0) b += generateSided(m.copy, s)
finishModel(combine(b.result()))

mask 的位是"不要渲染"(位为 0 才生成)——语义与常见的 OpenGL stencil mask 相反。generateSided 用 Rotation.sideRotations(side) 绕中心转到对应面。原始 OBJ 的 box 始终无条件加入。

最后 finishModel:shrinkUVs(0.0005) → computeNormals() → computeLighting(LightModel.standardLightModel)。

贴图与渲染

  • 图标:reg.registerIcon("mcframes:frame")(全资源 assets/mcframes/textures/blocks/frame.png)
  • getIcon(side, meta) 对所有面与 metadata 一律返回同一个 RenderFrame.icon
  • renderWorldBlock 走 CodeChickenLib 的 CCRenderState:resetInstance() → lightMatrix.locate(world,x,y,z) → setBrightnessInstance(...) → 渲染 → state.lightMatrix.access = null(收尾清理光照矩阵访问器)
  • 若 rb.hasOverrideBlockTexture 为真,用 getOrGenerateModel(0).render(..., new IconTransformation(rb.overrideBlockTexture)) 走覆盖贴图路径
  • renderInventoryBlock → renderInvBlock:在 (-0.5, -0.5, -0.5) 处以 mask 0 渲染物品栏模型;shouldRender3DInInventory = true

配方:全 jar 唯一一条

MCFramesRecipes.initRecipes() 里只有一个 ShapedOreRecipe:

GameRegistry.addRecipe(
  new ShapedOreRecipe(
    new ItemStack(blockFrame, 8),
    "sls", "lsl", "sls",
    's': JC, Items.stick,
    'l': JC, "logWood"
  )
)
项 值
产出 mcframes:frame × 8
形状 sls / lsl / sls
s Items.stick
l 矿词典名 logWood(任意原木)

'l': JC 用了 java.lang.Character 的别名 JC 来构造 Character 键。产出 8 个是这个 mod 唯一的配方,也是唯一使用矿词典的地方——见 API 接口 一节末尾的 grep 证据。

框架之间如何互相黏住

框架抓住方块的完整规则(IFrame 双向判定 + 配对的"卡扣集合" + 结构 BFS 遍历)见 MCFrames 卡扣注册表。

相关条目