侧边栏壁纸
  • 累计撰写 793 篇文章
  • 累计创建 1 个标签
  • 累计收到 1 条评论
标签搜索

目 录CONTENT

文章目录
MC

备份

Dettan
2021-07-10 / 0 评论 / 0 点赞 / 168 阅读 / 6,672 字
温馨提示:
本文最后更新于 2022-07-23,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
/ MC / 备份

GUI箱子存东西的地方是 TileEntityChest , 绘制界面的地方在 GuiChest# 发射器dispenser这个类里的onBlockActivated方法里`playerIn.displayGUIChest((TileEntityDispenser)tileentity);`但是displayGUIChest方法的参数是`public void displayGUIChest(IInventory chestInventory)`所以说现在是把tileEntityDispenser当成IInventory用了,这还不算完,`ILockableContainer ilockablecontainer = (ILockableContainer)chestInventory;`又把IInventory转成ILockableContainer了. 呜呜呜

分析植物魔法GUI
1. mod主文件里 注册网络处理函数
NetworkRegistry.INSTANCE.registerGuiHandler(Botania.instance, new GuiHandler());
2. 实现GUIHandle类,这个类实现了fml.common.network.simpleimpl.IGuiHandle接口,类放在了botania.common.network包里。
感觉这里有点局限性。
3. 

public class ContainerFlowerBag extends Container                //实现背包的鼠标操作,拖拽,丢弃什么的

public class GuiFlowerBag extends GuiContainer                  //绘制工作

public class InventoryFlowerBag implements IItemHandlerModifiable     //有一个ItemStack保存背包里的物品。背包里装的物品的操作。

然后自己实现这三个类就可以了。

显示层 GuiContainer 绘制这样一个界面,接收鼠标输入并简单处理。


控制层
数据层
植物魔法和zzzz的教程的gui一致

网络

植物魔法用NetworkWarrper类注册数据包。
定义了许多数据包,数据包里有方法指向处理这个数据包的函数。
\net\minecraft\network\NetworkManager.java 里的channelRead0(channelHandlerContext ,packet)会调用数据包的processPacket方法。
服务器发包函数
C:\Users\liu\.gradle\caches\minecraft\net\minecraftforge\forge\1.12.2-14.23.5.2838\snapshot\20171003\forgeSrc-1.12.2-14.23.5.2838-sources.jar!\net\minecraft\network\NetHandlerPlayServer.java:898
实体视线移动,实体look at,删除实体,实体速度.包都十分简单.
数据包和数据包的解析
在packet network 里:
定义了各种网络封包类,里面有读写函数还有一个处理封包的函数


public void processPacket(INetHandlerPlayClient handler)

{

    handler.handleBlockAction(this);

}
INetHandlerPlayClient 是一个接口,定义了许多方法,都是处理包的函数。



/**

 * Updates the NBTTagCompound metadata of instances of the following entitytypes: Mob spawners, command blocks,

 * beacons, skulls, flowerpot

 */

void handleUpdateTileEntity(SPacketUpdateTileEntity packetIn);



/**

 * Triggers Block.onBlockEventReceived, which is implemented in BlockPistonBase for extension/retraction, BlockNote

 * for setting the instrument (including audiovisual feedback) and in BlockContainer to set the number of players

 * accessing a (Ender)Chest

 */

void handleBlockAction(SPacketBlockAction packetIn);



/**

 * Updates the block and metadata and generates a blockupdate (and notify the clients)

 */

void handleBlockChange(SPacketBlockChange packetIn);



/**

 * Prints a chatmessage in the chat GUI

 */

void handleChat(SPacketChat packetIn);



/**

 * Displays the available command-completion options the server knows of

 */

void handleTabComplete(SPacketTabComplete packetIn);




这个接口的实现在包client.network 里,server.network 里也有许多这种实现类,想必是所有的包都在这了,截取什么操作只要在这些类里下断点就行。client和server用的包不一样,
network.play.client 里的packet是client发送server 的。处理类在minecrft.network包里叫NetHandlePlayerServer,之所以这么叫还是因为服务器也就是处理玩家动作的。如果玩家一动不动,只发位置包就行了
network.play.server 里的packet是server发送client 的。处理类在minecraft.client.network包里叫NetHandlePlayClient。
处理包的函数都在上面说的类里,都是集中在一块的。但是发送包的地方确实分散在整个游戏逻辑里的,调用的NetworkManager.sendPacket(new somePacket),一句话就能发送出去。
.异步与同步
(注:这里的"异步"并非是指通常意义上的异步(Async),而是一种比喻,与同步相对应,泛指服务器允许客户端在短时间内与服务器不同步的行为,事实上,有一个专业的术语来描述这种行为:轨迹推测法(Dead Reckoning))C/S模式的多人游戏多允许客户端与服务器间适当异步运行,比如Minecraft中实体的移动就并非绝对同步的,服务器会在实体移动时发送它的的移动方向和速度等信息,而精确的大规模同步则是每隔一段时间进行一次,在大规模同步之前,实体的移动在客户端就是异步的,客户端会根据服务器之前的信息来计算实体的移动,因此在网络环境较差的情况下,玩家会看见实体走一段距离后突然又瞬移到另一个位置,这就是客户端收到了实体移动的信息,但没及时收到精确同步的信息.之前提到的damageItem也是如此,客户端无需等待服务器的信息便可先异步降低物品耐久度,等收到服务器的同步信息后再确定物品准确耐久度,但倘若网络环境差,就会出现物品用坏后过几秒突然又恢复的情况.说完异步再说同步,Minecraft中依赖同步的有实体的创建,玩家对实体的攻击,实体的死亡等等...对于依赖同步的行为来说,在糟糕的网络状况下可能会出现玩家进行操作很久后才有响应的情况,比如打开箱子,高延迟时开箱子过很久才会出界面.

简单通信包装类

简单网络通信包装类(SimpleNetworkWrapper)——简单地进行网络通信
在实际开发过程中,我们常常需要用到网络通信。用到网络通信的地方有很多,例如按下按键释放技能,同步玩家属性,监听图形界面按钮按下,让客户端播放特效等等。那么怎么样进行网络通信呢?
简单网络通信包装类 SimpleNetworkWrapper
为了方便的进行网络通信,Forge提供一个简单的网络通信包装类——SimpleNetworkWrapper。这个类位于net.minecraftforge.fml.common.network.simpleimpl包下。
那么,怎么使用这个类呢?首先我们需要获取一个SimpleNetworkWrapper对象。
1. NetworkRegistry.INSTANCE.newSimpleChannel(channel);
复制代码
在此解释一下channel(频道)参数,该参数是一个字符串(String),表示网络通信频道名,只有在相同的频道,数据才能互相传输。
以上代码可以获取到一个SimpleNetworkWrapper对象,然后我们将这个对象储存至一个静态字段中以便在模组的任何地方调用,比如这样:
1. @Mod(modid = ChinaCraft.MODID, name = ChinaCraft.NAME, version = ChinaCraft.VERSION) 2. public final class ChinaCraft { 3.     public static final String MODID = "chinacraft"; 4.     public static final String NAME = "ChinaCraft 2"; 5.     public static final String VERSION = "0.0.1"; 6. 7.     private static SimpleNetworkWrapper network; 8. 9.     @EventHandler 10.     public void init(FMLInitializationEvent event) { 11.         network = NetworkRegistry.INSTANCE.newSimpleChannel(MODID); 12.     } 13. 14.     public static SimpleNetworkWrapper getNetwork() { 15.         return network; 16.     } 17. }
复制代码
这样,我们就可以使用ChinaCraft.getNetwork()方法在模组的任何地方调用SimpleNetworkWrapper对象了。
消息类和消息处理类
接下来,我们开始编写我们的消息类和消息处理类。消息类需要实现IMessage接口,该类必须要有一个无参构造方法,并且需要实现fromBytes和toBytes方法。
1. 2. public class RedPacketMessage implements IMessage { 3. 4.     public RedPacketMessage() {} //必须要有一个无参构造方法 5. 6.     @Override 7.     public void fromBytes(ByteBuf buf) {} //将字节流转换为信息 8. 9.     @Override 10.     public void toBytes(ByteBuf buf) {} //将信息转换为字节流 11. }
复制代码
可以看到,fromBytes和toBytes方法都有一个ByteBuf类型的参数,ByteBuf类中提供了很多read和write方法,那么我们该如何使用呢?举个例子:
1. public class RedPacketMessage implements IMessage { 2. 3.     private String sender; 4. 5.     public RedPacketMessage() {} 6. 7.     public RedPacketMessage(String sender) { 8.         this.sender = sender; 9.     } 10. 11.     @Override 12.     public void fromBytes(ByteBuf buf) { 13.         char chars[] = new char[buf.readInt()]; //读取字符串长度 14.         for(int i=0;i<chars.length;i++) //读取字符 15.             chars = buf.readChar(); 16.         sender = String.valueOf(chars); //创建字符串对象 17.     } 18. 19.     @Override 20.     public void toBytes(ByteBuf buf) { 21.         buf.writeInt(sender.length()); //写入字符串长度 22.         for(char c:sender.toCharArray()) //写入字符串 23.             buf.writeChar(c);  24.     }
复制代码
提示:可以看到ByteBuf类有些不方便使用,所以可以使用MC实现的PacketBuffer类和Forge提供的ByteBufUtils类。
接下来,我们需要为这个消息编写一个处理类,来处理收到的消息,消息处理类需要实现IMessageHandler接口,并且需要实现onMessage方法。此外,该接口还是一个泛型接口,拥有两个泛型形参REQ extends IMessage和REPLY extends IMessage,其中REQ表示收到的消息类,REPLY表示回复的消息类。
1. public class RedPacketMessageHandler implements IMessageHandler<RedPacketMessage, IMessage> { 2. 3.         @Override 4.         public IMessage onMessage(RedPacketMessage message, MessageContext ctx) { 5.             return null; //当你不需要回复消息时,可以将REPLY填写为IMessage,并且返回null; 6.         } 7. }
复制代码
接下来,我们需要将消息类和消息处理类注册到SimpleNetworkWrapper对象中。
1. network.registerMessage(handler,message,id,side);
复制代码
解释一下registerMessage方法的四个参数,第一个参数handler需要一个IMessageHandler对象,第二个参数message则是对应的消息类的类对象,第三个参数id表示该消息的id,范围为0~255,且不得重复,第四个参数side表示收到消息的对象,Side.SERVER表示消息由客户端发送,服务器接收,Side.CLIENT表示消息由服务端发送,客户端接收。
发送消息
那么,注册好了以后我们怎么发送消息呢?SimpleNetworkWrapper类实现了五个发送方法,分别有不同的作用。
1. 2.     /** 3.      * 发送消息给所有玩家(服务端调用该方法) 4.      */ 5.     public void sendToAll(IMessage message) 6. 7.     /** 8.      * 发送消息到指定玩家(服务端调用该方法) 9.      */ 10.     public void sendTo(IMessage message, EntityPlayerMP player) 11. 12.     /** 13.      * 发送消息到指定范围内的所有玩家(服务端调用该方法) 14.      */ 15.     public void sendToAllAround(IMessage message, NetworkRegistry.TargetPoint point) 16. 17.     /** 18.      * 发送消息到指定世界内的所有玩家(服务端调用该方法) 19.      */ 20.     public void sendToDimension(IMessage message, int dimensionId) 21. 22.     /** 23.      * 发送消息到服务器(客户端调用该方法) 24.      */ 25.     public void sendToServer(IMessage message)
复制代码
例如:
1. ChinaCraft.getNetwork().sendToServer(new RedPacketMessage());
复制代码
ByteBufUtils优化的字符串处理
1. public class RedPacketMessage implements IMessage { 2. 3.     private String sender; 4. 5.     public RedPacketMessage() {} 6. 7.     public RedPacketMessage(String sender) { 8.         this.sender = sender; 9.     } 10. 11.     @Override 12.     public void fromBytes(ByteBuf buf) { 13.         sender = ByteBufUtils.readUTF8String(buf); 14.     } 15. 16.     @Override 17.     public void toBytes(ByteBuf buf) { 18.         ByteBufUtils.writeUTF8String(buf,sender); 19.     }
复制代码
本篇教程到这就结束了,感谢您的阅读。

注解载入器 Annotation Loader
Mouse edited this page on 13 Jul 2017 · 2 revisions
在开发中,我们需要不断提高开发者的效率,以使开发进度加快。为此,我们在ChinaCraft2中实现了一个注解载入器。
为什么使用注解呢?俗话说得好:“反射,反射,程序员的欢乐。”使用注解确实便于阅读和修改,下面来看ChinaCraft2中的注册例子。
@RegBlock(value = {"copper", "ore"}, oreDict = {"oreCopper"}) Block COPPER_ORE = new BlockCCOre().setHarvestLevelReturnBlock("pickaxe", 1);
该代码是cn.mccraft.chinacraft.initCCBlocks类中的代码,可以看到在COPPER_ORE字段上我们使用了RegBlock注解,该注解表示COPPER_ORE是一个需要注册的方块,确实是十分方便阅读和修改。下面我们来看一看RegBlock的实现。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RegBlock {
    /**     * 该参数将自动设置方块的registryName和unlocalizedName
     * The params to build registryName and unlocalizedName.
     * @see cn.mccraft.chinacraft.util.NameBuilder
     */String[] value();

    /**     * 添加矿物词典
     * All {@link net.minecraftforge.oredict.OreDictionary} values to be registered.
     */String[] oreDict() default {};

    /**     * 设置方块的ItemBlock类
     */Class<? extends Item> itemClass() default ItemBlock.class;

    /**     * 是否自动注册ItemBlock
     */boolean isRegisterItemBlock() default true;

    /**     * 是否自动注册渲染器
     */boolean isRegisterRender() default true;
而如何自动注册这些方块呢?我们在cn.mccraft.chinacraft.block.BlockLoader中实现了自动注册的方法,代码如下:
/** * Auto loader of all blocks annotated with {@link RegBlock} in {@link CCBlocks}.
 * 自动加载{@link CCBlocks}中被{@link RegBlock}注释的方块。
 */@SuppressWarnings("unused")
public class BlockLoader implements ILoader { //ILoader接口是我们自己实现的加载器接口,与方块注册无关系
    @Load //该注解是我们自己实现的加载器注解,与方块注册无关系
    public void registerBlocks() { //注册CCBlocks中的所有方块
        for (Field field : CCBlocks.class.getFields()) {
            field.setAccessible(true);
            RegBlock anno = field.getAnnotation(RegBlock.class);
            if (anno==null) continue;

            try {
                Block block = (Block) field.get(null);
                GameRegistry.register(block.setRegistryName(NameBuilder.buildRegistryName(anno.value())).setUnlocalizedName(NameBuilder.buildUnlocalizedName(anno.value()))); //设置方块注册名和未本地化名并注册

                //Register item block.
                //注册方块物品
                if(anno.isRegisterItemBlock()) {
                    Class<? extends Item> itemClass = anno.itemClass();
                    Constructor<? extends Item> con = itemClass.getConstructor(Block.class);
                    con.setAccessible(true);
                    GameRegistry.register(con.newInstance(block).setRegistryName(block.getRegistryName()).setUnlocalizedName(block.getUnlocalizedName()));
                }

                Arrays.asList(anno.oreDict()).forEach(s -> OreDictionary.registerOre(s, block)); //添加矿物词典
            } catch (Exception e) {
                ChinaCraft.getLogger().warn("Un-able to register block " + field.toGenericString(), e);
            }
        }
    }

    @Load(side = Side.CLIENT)
    @SideOnly(Side.CLIENT)
    public void registerRenders() { //渲染器和方块注册要分开,因为服务端并不需要注册渲染器
        for (Field field : CCBlocks.class.getFields()) {
            field.setAccessible(true);
            RegBlock anno = field.getAnnotation(RegBlock.class);
            if (anno==null) continue;

            if(!anno.isRegisterRender()||!anno.isRegisterItemBlock()) continue;

            try {
                Block block = (Block) field.get(null);
                registerRender(block,0);
            } catch (Exception e) {
                ChinaCraft.getLogger().warn("Un-able to register block " + field.toGenericString(), e);
            }
        }
    }

    @SideOnly(Side.CLIENT)
    private void registerRender(Block block, int meta)
    {
        Item item = Item.getItemFromBlock(block);
        ModelLoader.setCustomModelResourceLocation(item, meta, new ModelResourceLocation(block.getRegistryName(), "inventory"));
    }
}
以上就是ChinaCraft2中方块的注解载入器的内容。同样的,我们也实现了物品载入器,有兴趣的读者可以阅读cn.mccraft.chinacraft.item.ItemLoadercn.mccraft.chinacraft.util.loader.annotation.RegItem的源码。


服务端
integrated 集成的
dedicated 分开的

MinecraftServer 是抽象类,
IntegratedServer 和 DedicatedServer 是它的两个实现类
Intergrated类
Minecraft.java
public void launchIntegratedServer(String folderName, String worldName, @Nullable WorldSettings worldSettingsIn)
{
    this.integratedServer = new IntegratedServer(this, folderName, worldName, worldSettingsIn, yggdrasilauthenticationservice, minecraftsessionservice, gameprofilerepository, playerprofilecache);
}
这个方法又在 GuiCreatWorld  GuiMainMenu  和 loadExistingWorld 时被调用

 launchIntegratedServer 

DedicatedServer 是单独启动的 在MinecraftServer的Main() 方法里



pathFinder

用的A*算法,是Dijkstra(往四周均匀查找)算法和BFS(最佳优先搜索,往目标查找)的结合。 BFS算法十分快,但会导致搜索到的路径不是最优路径,所以要结合起来。
每次进行主循环时,它检查f(n)最小的结点n,其中f(n) = g(n) + h(n)。g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价
启发式函数h(n)告诉A*从任意结点n到目标结点的最小代价评估值。选择一个好的启发式函数是重要的。
算法思路
有两个集合,OPEN集和CLOSED集。其中OPEN集保存待考查的结点。开始时,OPEN集只包含一个元素:初始结点。CLOSED集保存已考查过的结点。开始时,CLOSED集是空的。如果绘成图,OPEN集就是被访问区域的边境(frontier)而CLOSED集则是被访问区域的内部(interior)。每个结点同时保存其父结点的指针因此我们可以知道它是如何被找到的。
在主循环中重复地从OPEN集中取出最好的结点n(f值最小的结点)并检查之。如果n是目标结点,则我们的任务完成了。否则,结点n被从OPEN集中删除并加入CLOSED集。然后检查它的邻居n’。如果邻居n’在CLOSED集中,那么它是已经被检查过的,所以我们不需要考虑它*;如果n’在OPEN集中,那么它是以后肯定会被检查的,所以我们现在不考虑它*。否则,把它加入OPEN集,把它的父结点设为n。到达n’的路径的代价g(n’),设定为g(n) + movementcost(n, n’)。
(*)这里我忽略了一个小细节。你确实需要检查结点的g值是否更小了,如果是的话,需要重新打开(re-open)它。
有 viewed 和 isAssigned 这两个状态位,左边那个表示被操作过了,即选出了周围的点并已遍历;右边那个表示被添加到了path里。


private Path findPath(PathPoint pathFrom, PathPoint pathTo, float maxDistance)

    {

        pathFrom.totalPathDistance = 0.0F; 

        pathFrom.distanceToNext = pathFrom.distanceManhattan(pathTo); //这个距离是xyz距离的和,没有平方。

        pathFrom.distanceToTarget = pathFrom.distanceToNext; 

        this.path.clearPath();

        this.closedSet.clear(); 

        this.path.addPoint(pathFrom); 

        PathPoint pathpoint = pathFrom; 

        int i = 0;



        while (!this.path.isPathEmpty())  //路径不等于空时进入循环

        {

            ++i;



            if (i >= 200) 控制寻路时间,超时停止

            {

                break;

            }



            PathPoint pathpoint1 = this.path.dequeue(); 从path里取出一个来。????



            if (pathpoint1.equals(pathTo))  找到路了

            {

                pathpoint = pathTo;

                break;

            }



            if (pathpoint1.distanceManhattan(pathTo) < pathpoint.distanceManhattan(pathTo))  比较新选出来的点,距离短的就选用。

            {

                pathpoint = pathpoint1;

            }



            pathpoint1.visited = true;  设置为已浏览

            int j = this.nodeProcessor.findPathOptions(this.pathOptions, pathpoint1, pathTo, maxDistance);  获取pathpoint1下一步所有可选的未浏览过的点,九宫格其余的八个



            for (int k = 0; k < j; ++k)   遍历所有可选点。

            {

                PathPoint pathpoint2 = this.pathOptions[k];

                float f = pathpoint1.distanceManhattan(pathpoint2); 

                pathpoint2.distanceFromOrigin = pathpoint1.distanceFromOrigin + f;

                pathpoint2.cost = f + pathpoint2.costMalus;

                float f1 = pathpoint1.totalPathDistance + pathpoint2.cost;



                //&&左边是基本条件,右边满足其一即可。                  所有没有被关联过的                       不管关没关联,只要花费小

                if (pathpoint2.distanceFromOrigin < maxDistance && (!pathpoint2.isAssigned() || f1 < pathpoint2.totalPathDistance))  

                {

                    pathpoint2.previous = pathpoint1;

                    pathpoint2.totalPathDistance = f1;

                    pathpoint2.distanceToNext = pathpoint2.distanceManhattan(pathTo) + pathpoint2.costMalus;



                    if (pathpoint2.isAssigned()) 看point的index如果>0就是被关联了。

                    {

                        //修改pathpoint2的距离,并在路径里重排,就是往左放。

                        this.path.changeDistance(pathpoint2, pathpoint2.totalPathDistance + pathpoint2.distanceToNext); 

                    }

                    else

                    {

                        pathpoint2.distanceToTarget = pathpoint2.totalPathDistance + pathpoint2.distanceToNext;  修改距离

                        this.path.addPoint(pathpoint2);  添加到path里,同时会给pathpoint的index赋值,而不是默认的-1,所以isAssigned()会返回false。

                    }

                }

            }

        }



        if (pathpoint == pathFrom)

        {

            return null;   查找失败

        }

        else

        {

            Path path = this.createPath(pathFrom, pathpoint);  创建路径

            return path;

        }

    }


    private Path createPath(PathPoint start, PathPoint end)

    {

        int i = 1;



        找到头节点

        for (PathPoint pathpoint = end; pathpoint.previous != null; pathpoint = pathpoint.previous)

        {

            ++i;  记录下用了几步。

        }



        PathPoint[] apathpoint = new PathPoint[i];

        PathPoint pathpoint1 = end;

        --i;



        for (apathpoint[i] = end; pathpoint1.previous != null; apathpoint[i] = pathpoint1)

        {

            pathpoint1 = pathpoint1.previous;

            --i;

        }



        return new Path(apathpoint);

    }


源码分析

Minecraft.class
(Game Loop)这个东西,简单来说,就是每一秒钟游戏都会对一段程序循环运算几十遍(通常是25~30遍),更进一步了解引擎原理的人会知道通常游戏循环分为两部分:Update(数据运算,也有写作Tick(帧),因为一次游戏循环算一帧)和Render(图像绘制,也有写作Draw)Minecraft类的runTick方法以及MinecraftServer类的tick方法就相当于Update,在runTick中,游戏会将玩家所在的世界中的所有实体的onUpdate方法调用一遍.(tick更为复杂一点,它要将所有世界的实体依次调用一遍)onUpdate方法会执行对实体的运算...另外,Update和Render并非一一对应关系,即不一定每次Render时都有一次Update.
run() >> init() : 显示图标,createDisplay,根据显示宽高初始化frameBuffer,初始化各种类,资源的,声音的...
< 之后就是游戏主循环调用了runGameLoop()之后就是各种捕捉异常了
runGameLoop():多线程的任务机制,这里是取任务,
synchronized (this.scheduledTasks)
{
while (!this.scheduledTasks.isEmpty())
{
Util.runTask(this.scheduledTasks.poll(), LOGGER);
}
之后是runTick()函数,这是一个主要函数,里面会this.ingameGUI.updateTick(),this.entityRenderer.getMouseOver(1.0F);this.tutorial.onMouseHover(this.world, this.objectMouseOver);    this.playerController.updateController(); this.renderEngine.tick();
GlStateManager.pushMatrix();//保存当前矩形状态
net.minecraftforge.fml.common.FMLCommonHandler.instance().onRenderTickStart(this.timer.renderPartialTicks);
this.entityRenderer.updateCameraAndRender(this.isGamePaused ? this.renderPartialTicksPaused : this.timer.renderPartialTicks, i);
this.toastGui.drawToast(new ScaledResolution(this));
displayDebugInfo(i1)
# GUI
主循环里有个displayGuiScreen(GuiScreen guiScreenIn)函数,参数为Gui类.主类叫GuiScreen,定义了gui的按钮位置,还有内容,还有点击按钮之后的逻辑.然后每一页的GUI都是一个子类,定义了自己的内容,如GuiMainMenu()是进入游戏里显示的那个页面,还有死亡页面,异常页面等等.
各个类里能找到各个按钮对应的逻辑.
gui里怎么调用openGL显示的看不懂,等着再看.
# runTick()
游戏主循环
# 判断点击的地方是否可以放置木牌:
boolean flag = iblockstate.getBlock().isReplaceable(worldIn, pos);//判断是不是草那一类可以被替换的方块.
//facing 表示点击的是哪一面.
if (facing != EnumFacing.DOWN && (iblockstate.getMaterial().isSolid() || flag) && (!flag || facing == EnumFacing.UP))
//if(面不朝下&&否定不是固体也不是草的情况 &&,只有一种情况为假:是草且faceingUp的时候)
# 按钮点击(以GuiMainScreen为例)
1. 初始化的时候有一个按钮列表,存放按钮类的实例,实例里包括按钮的id
> `this.buttonList.add(new GuiButton(4, this.width / 2 + 2, j + 72 + 12, 98, 20, I18n.format("menu.quit")));`
2. 有一个mouseClicked函数
`protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException`
第一行是调用父类的这个方法:`super.mouseClicked(mouseX, mouseY, mouseButton);`
3. 跟踪过去发现在方法里直接遍历按钮列表里的按钮看点击的谁,找到之后执行`this.actionPerformed(guibutton);`把找到的按钮当做参数传进去.
4. 跟踪`antionPerformed`发现这是一个空方法,是被子类重写的.又回到这个子类,就是开头的类,找到`actionPerformed(guibutton)`函数终于发现了根据按钮id执行对应动作的语句
```
if (button.id == 0)
{
this.mc.displayGuiScreen(new GuiOptions(this, this.mc.gameSettings));
}
if (button.id == 5)
{
this.mc.displayGuiScreen(new GuiLanguage(this, this.mc.gameSettings, this.mc.getLanguageManager()));
}
if (button.id == 1)
{
this.mc.displayGuiScreen(new GuiWorldSelection(this));
}
if (button.id == 2)
{
this.mc.displayGuiScreen(new GuiMultiplayer(this));
}
```
### 问题:为什么执行super.mouseClicked(mouseX, mouseY, mouseButton)之后跑到父类里,然后在父类里调用actionPerformed(guibutton)会执行子类的这个方法?
答:super好像是吧父类的语句拿过来执行的,其实还是在子类里.
寻找blockstates的源码(找了好几天)
modelBakery.java:258  >>
return new ResourceLocation(location.getResourceDomain(), "blockstates/" + location.getResourcePath() + ".json");


需要的技能

玩家自定义建筑:
读取文件
封包
gui
文本框,接受玩家的文本
onItemUse会在onItemRightClick之前触发.onItemUse是否会阻止onItemRightClick的发生取决于onItemUse的返回值,EnumActionResult.SUCCESS会阻止onItemRightClick的发生,其余值会允许其被触发.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public ActionResult<ItemStack> onItemRightClick(ItemStack par1ItemStack, World par2World,
EntityPlayer par3EntityPlayer, EnumHand hand) {
if (!par2World.isRemote) {
EntityTNTPrimed entity = new EntityTNTPrimed(par2World, par3EntityPlayer.posX,
par3EntityPlayer.posY + par3EntityPlayer.getEyeHeight(), par3EntityPlayer.posZ, par3EntityPlayer);// getEyeHeight方法是获取物体的"眼高",即头部到脚底的距离
float angle = (par3EntityPlayer.rotationYaw / 180F) * 3.141593F; // 水平方向的角度
float angle2 = (-par3EntityPlayer.rotationPitch / 180F) * 3.141593F; // 垂直方向的仰角
final float speed = 2f; // TNT飞行速度 - 抱歉我卖了个萌
entity.motionY = speed * MathHelper.sin(angle2); // 算出三个方向上的速度,为了方便阅读我先计算的Y轴分速度
entity.motionX = speed * MathHelper.cos(angle2) * -MathHelper.sin(angle); // 根据仰角算出速度在XZ平面上的投影,再正交分解投影
entity.motionZ = speed * MathHelper.cos(angle2) * MathHelper.cos(angle);
par2World.spawnEntityInWorld(entity); // 放置实体咯
}
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, par1ItemStack);
}
每一个世界都有一个实体列表(loadedEntityList字段)和玩家实体列表(playerEntities字段,包括所有位于此世界的玩家实体,玩家实体会同时存在于两个列表当中),用以维持实体循环,此外,为了进一步优化性能,Minecraft为每一个Chunk还准备了16个子实体列表(Chunk类的entityList字段),若将一个Chunk(16x256x16个砖块)按高度(Y轴)均分成16个区域的话,每个区域内的实体会属于一个代表该区域内实体的子列表中(地表下的实体会被归为最下面的子列表,256高度以上的实体同理),子列表主要用于优化数据存取以及使用AABB盒搜索实体.

示例代码

如何让玩家自动瞄准怪物的头部?

public Location setDirection(Vector vector) {

double x = vector.getX();

double z = vector.getZ();

if (x == 0.0D && z == 0.0D) {

this.pitch = (float)(vector.getY() > 0.0D ? -90 : 90);

return this;

} else {

double theta = Math.atan2(-x, z);

this.yaw = (float)Math.toDegrees((theta + 6.283185307179586D) % 6.283185307179586D);

double x2 = x * x;

double z2 = z * z;

double xz = Math.sqrt(x2 + z2);

this.pitch = (float)Math.toDegrees(Math.atan(-vector.getY() / xz));

return this;

}

}


vector是玩家头部坐标到怪物头部坐标的向量
获取在一定范围内的所有实体

world.getEntitiesWithinAABB(Entity.class, new AxisAlignedBB(-10, -10, -10, 10, 10, 10).offset(center));


这句选择了一个长(正)方体区域,边长为20
center是长方体的正中心,可以是玩家的坐标如果只想要某种实体的话,把Entity.class替换成<实体类名>.class就行了比如说全部玩家就是EntityPlayer.class
创建一个已点燃的TNT实体
if(!world.isRemote)
{
EntityTNTPrimed entity = new EntityTNTPrimed(world, x, y, z, player);
entity.fuse = 80;
world.spawnEntityInWorld(entity);
}
world是它所属的世界,xyz是坐标,player是TNT实体的放置者.但重点不是这些,而是那个world.isRemote.
击飞实体123456789101112131415@Overridepublic boolean hitEntity(ItemStack par1ItemStack,    EntityLivingBase par2EntityLivingBase,    EntityLivingBase par3EntityLivingBase) {    par1ItemStack.damageItem(1, par3EntityLivingBase);    if (par3EntityLivingBase.worldObj.isRemote) {        return true;    }    float Angle = (par3EntityLivingBase.rotationYaw/ 180F) * 3.141593F;    float x = 3f * -MathHelper.sin(Angle);    float y = 1f;    float z = 3f * MathHelper.cos(Angle);    par2EntityLivingBase.setVelocity(x, y, z);    return true;}(EntityLivingBase 代表所有实体,玩家怪物动物,经验球,船 都是,EntityPlayer 代表玩家)


类库

scala  引用了许多scala的库。
jinput  游戏手柄
jline   console输入输出
jna   java native access  让java调用本地代码变的十分简单
ICU4J  International Components for Unicode  为软件应用提供Unicode和全球化支持的一套成熟、广泛使用的C/C++和Java类库集。功能:编码互转,各国字符串排序规则,对数字货币时间日期利率的格式化,时间计算,性能强的正则,混序混合文字处理,文本边界(段落,句子)。
objc-bridge : 和javaScript有关
jopt : 命令解析。command
在智能方面,此次公布预售的4款Ali智联版本车型, 1. 标配了SKY EYE天眼系统 2. BYOD多用户随心控 3. 360°全景影像高配车型标配
1. 满足L2级别自动驾驶的AI Pilot智能驾驶辅助系统 2. 包括具备识别行人能力的AEB主动刹车 3. 带排队跟车功能的全速域ACC自适应巡航 4. LKA车道保持 5. TJA交通拥堵辅助功能


0

评论区