马达方块(BlockMotor)
MCFrames 唯一的动力源:一个 6 面 × 4 姿态(24 组合)的方块,接受红石信号后沿自己的正面把整条相连的框架结构推一格,耗时 16 tick。
方块名 mcframes.motor,类 mrtjp.mcframes.motor.BlockMotor;方块实体 TileMotor。
基本属性
| 属性 | 值 | 来源 |
|---|---|---|
| 方块名 | mcframes.motor |
motor.scala:28 |
| 基类 | mrtjp.core.block.InstancedBlock |
motor.scala:28 |
| 材质 | Material.iron |
motor.scala:28 |
| 硬度 | setHardness(5f) |
motor.scala:29 |
| 抗爆 | setResistance(10f) |
motor.scala:30 |
| 脚步声 | Block.soundTypeMetal |
motor.scala:31 |
| 创造栏 | CreativeTabs.tabTransport |
motor.scala:32 |
| TileEntity | addSingleTile(classOf[TileMotor]) |
motor.scala:33 |
isSideSolid |
true(六面均实心,与框架方块相反) |
motor.scala:35-41 |
renderAsNormalBlock |
true |
motor.scala:43 |
马达方块没有物品配方——GameRegistry.addRecipe 在整个 jar 里只出现一次,属于 框架方块。马达只能靠框架方块的配方产出路径之外的途径获得(或由其他 mod 提供)。
朝向:一个字节打包 6 位
TileMotor extends InstancedBlockTile with TTileOrient with IFrame。姿态全部存在 MrTJPCore 的 TTileOrient 里,是一个 Byte:
var orientation: Byte = 0
def side = orientation >> 2 // 高 2 位:正面朝向(水平 0~3)
def rotation = orientation & 0x3 // 低 2 位:绕正面轴的滚转(0~3)
def absoluteDir(absRot: Int) = Rotation.rotateSide(side, absRot) // codechicken.lib.vec.Rotation
| 字段 | 含义 |
|---|---|
side |
水平正面索引(0~3),即"推的方向"的反向 |
rotation |
4 个滚转档位,决定贴图取哪张 |
setSide / setRotation 都会在值真正变化时回调 onOrientChanged(oldOrient)。
放置时的姿态
override def onBlockPlaced(side: Int, meta: Int, player: EntityPlayer, stack: ItemStack, hit: Vector3) {
super.onBlockPlaced(side, meta, player, stack, hit)
setSide(side ^ 1)
setRotation(Rotation.getSidedRotation(player, side ^ 1))
}
side 参数是玩家点中的那个面,side ^ 1 是它的反向——也就是"背靠支撑、朝外推"的方向。所以马达放在地板上、点地板顶面时,马达自身的 side 字段指向它所依附的那一侧。Rotation.getSidedRotation(player, side ^ 1) 依据玩家视角决定初始滚转。
交互:右键切换
override def onBlockActivated(player: EntityPlayer, s: Int): Boolean = {
if (super.onBlockActivated(player, s)) return true
if (player.isSneaking) setRotation((rotation + 1) % 4)
else setSide((side + 1) % 6)
true
}
- 潜行 + 右键:
rotation前进一档(0→1→2→3→0),切换贴图(用于换朝向的方块) - 普通右键:
setSide((side + 1) % 6)——⚠️ 这里对 6 取模,但side实际是orientation >> 2,只有 0~3 四个有效值。setSide写入时是(orientation & 0x3 | s << 2),传入 4 或 5 会把orientation推到 16 或 20(越出 1 字节无妨,但side变成 4/5,超出Rotation.sideRotations的 0~3 范围)。源码原文如此,未做范围保护。
注意 setSide 的实现是位或而非替换——orientation & 0x3 | s << 2 先清掉高 2 位再写入,所以传入 4 会得到 0b10_00 | 4 = 16。此处行为与设计意图不完全一致,属源码现状。
抓取规则(IFrame)
override def stickOut(w: World, x: Int, y: Int, z: Int, side: Int) = side == (this.side ^ 1)
override def stickIn(w: World, x: Int, y: Int, z: Int, side: Int) = side != (this.side ^ 1)
与 框架方块 的"六面全 true"不同:马达只在正面 side ^ 1 伸出,其余 5 面 stickOut 为 false(不会主动抓别人);反过来 stickIn 在除正面外的 5 面为 true(可以被侧面的框架连成一体)。
驱动逻辑
update() 每 tick 执行(InstancedBlockTile 的更新钩子):
override def update() {
if (!world.isRemote && world.getBlockPowerInput(x, y, z) > 0) {
val pos = position.offset(side ^ 1)
if (world.isAirBlock(pos.x, pos.y, pos.z)) return
if (!RelocationAPI.instance.isMoving(world, pos.x, pos.y, pos.z) &&
!RelocationAPI.instance.isMoving(world, x, y, z)) {
val blocks = MCFramesAPI.instance.getStickResolver
.getStructure(world, pos.x, pos.y, pos.z, new BlockPos(x, y, z))
val r = RelocationAPI.instance.getRelocator
r.push()
r.setWorld(world)
r.setDirection(getMoveDir)
r.setSpeed(1 / 16d)
r.addBlocks(blocks)
r.execute()
r.pop()
}
}
}
逐条拆解:
| 条件 | 作用 |
|---|---|
!world.isRemote |
仅服务端执行;客户端不重复发起 |
world.getBlockPowerInput(x,y,z) > 0 |
需要红石信号(getBlockPowerInput 在无信号时返回 0) |
目标格是空气则 return |
前面没东西就不推 |
| 目标格与自身都不在移动中 | 避免移动途中重复触发、避免自锁 |
getStructure(world, pos..., new BlockPos(x,y,z)) |
解析整条框架结构,把马达自己作为排除项传入 |
setSpeed(1 / 16d) |
速度 0.0625 格/tick → 单格位移恰好 16 tick |
push() … pop() |
栈式 API,见 API 接口 |
马达不消耗能量——红石信号是唯一输入,信号持续多久就每 tick 尝试推一次(但被 isMoving 挡住,结构动完才可能再推)。
getMoveDir = absoluteDir((rotation + 2) % 4):以 side 为基准,滚转 rotation 再加 180°。absoluteDir 委托给 CodeChickenLib 的 Rotation.rotateSide(side, absRot)(该库不在本仓库内)。
姿态同步:独立于 ForgeRelocation 的第二套通道
TileMotor 的姿态不走 ForgeRelocation 的网络层,而走 MrTJPCore InstancedBlockTile 实现的 ICustomPacketTile:
override def readDesc(in: MCDataInput) { super.readDesc(in); orientation = in.readByte() }
override def writeDesc(out: MCDataOutput) { super.writeDesc(out); out.writeByte(orientation) }
override def read(in: MCDataInput, key: Int) = key match {
case 2 => orientation = in.readByte(); markRender()
case _ => super.read(in, key)
}
def sendOrientUpdate() {
if (!world.isRemote) writeStream(2).writeByte(orientation).sendToChunk()
}
- NBT 键:
tag.setByte("orient", orientation)/tag.getByte("orient") - 描述符:MrTJPCore 基类内容之后追加 1 字节
orientation - 增量包:key = 2,只带 1 字节;发送范围是
sendToChunk()(同区块内全部玩家) onOrientChanged自动触发sendOrientUpdate(),因此每一次右键改姿态都会立刻同步
这与结构运动同步的两类包(PacketCustom(channel, 1) / 2)是完全独立的通道,见 网络协议。
渲染:TCubeMapRender + 5 张贴图
TileRenderRegistry.setRenderer(MCFramesMod.blockMotor, 0, RenderMotor)。RenderMotor extends TCubeMapRender,用 getData(w,x,y,z) 取出 (side, rotation, MultiIconTransformation) 三元组:
override def registerIcons(reg: IIconRegister) {
bottom = reg.registerIcon("mcframes:motor/bottom")
top = reg.registerIcon("mcframes:motor/top")
side = reg.registerIcon("mcframes:motor/side")
sidew = reg.registerIcon("mcframes:motor/sidew")
sidee = reg.registerIcon("mcframes:motor/sidee")
}
| 资源路径 | 用途 |
|---|---|
assets/mcframes/textures/blocks/motor/bottom.png |
底面 |
assets/mcframes/textures/blocks/motor/top.png |
顶面 |
assets/mcframes/textures/blocks/motor/side.png |
侧面(side == 2 与 side == 3 共用) |
assets/mcframes/textures/blocks/motor/sidew.png |
侧面(side == 4,西向) |
assets/mcframes/textures/blocks/motor/sidee.png |
侧面(side == 5,东向) |
getIcon 的分派:0 → bottom、1 → top、2 → side、3 → side、4 → sidew、5 → sidee——即 6 个面里 2/3 共用一张,共 5 张贴图。getInvData 固定返回 (0, 0, ...),即物品栏里总是显示默认姿态。
语言
tile.mcframes.motor.name=Debug Frame Motor
tile.mcframes.motor|0.name=Debug Frame Motor
⚠️ 显示名是 “Debug Frame Motor”(调试用马达),两条键分别是"无 metadata 形式"与 meta = 0 形式,文本相同。原文未汉化,且 src/main/resources 下没有 zh_CN.lang。
相关条目
- 框架方块 —— 马达解析结构时依赖的
IFrame实现 - MCFrames 卡扣注册表 ——
getStructure的 BFS 遍历与resolveStick判定 - 运动模型(结构与行的分解) ——
tryStartMove拿到这批方块后如何切分成行 - API 接口 ——
Relocator栈式调用与StickResolver.getStructure签名