框架方块(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) 按顺序做四件事:
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正好伸出整格。generateQuartRotated—— 把它和一份绕 Z 轴 90° 的副本combine,副本额外Translation(0, 0.01, 0)微抬 0.01 格避免 z-fighting。四根 peg 组成十字。generateEightRotated—— 对整体施加Rotation(math.Pi / 4, 0, 1, 0).at(Vector3.center),即绕 Y 轴 45°,把十字变成八角形,视觉上是个圆环。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.iconrenderWorldBlock走 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)处以 mask0渲染物品栏模型;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 卡扣注册表。
相关条目
- 马达方块 —— 唯一会主动触发移动的方块
- MCFrames 卡扣注册表 ——
StickRegistry.resolveStick的判定顺序与latch registry配置 - 配置文件 ——
config/MCFrames.cfg的 3 条默认卡扣对