Minecraft(我的世界)中文论坛

标题: ---===从零基础开始,到精通插件===---#10.1在MC里吃鸡玩绝地求生!

作者: berry64    时间: 2017-12-24 20:55
标题: ---===从零基础开始,到精通插件===---#10.1在MC里吃鸡玩绝地求生!
本帖最后由 berry64 于 2018-6-23 16:41 编辑

回到目录

-------------------------------------------------------------------------------------------------
教程 X —— 在MC里也能吃鸡!?
第一部分:设计&加入系统
↑好尬
由于本教程是我的插件-SteadyShot的拓展开发教程,所以先厚颜无耻打一波广告:
前置插件地址: [管理|综合|编程|机制]SteadyShot——可以自定义的小游戏竞技场插件![全版本]

如何吃鸡:打开物品栏拿出熟鸡肉右键 ——tada成功吃鸡
教程到此结束
本教程将依照我深(mo)思(ming)熟(qi)虑(miao)的思考顺序分步骤开发(所以会非常乱QwQ)




于是乎, 先从创建一个java工程开始这里由于我是导入的工程而并非jar包,但是效果差不多,
eclipse→右键→新建→java工程,之后右键java工程→属性→Java Build path 导入2个,分别是服务器主文件(我的是paperspigot 1.12.2) 以及SteadyShot jar (我的是工程文件)

P.S 我的拓展命名为MCBG(MineCraft BattleGrounds)

因为本拓展是已需要前置插件的插件写成的,所以理所当然的有个plugin.yml以及继承JavaPlugin的主类
主类里的onEnable我也加了一个检测前置插件的机制(虽然plugin.yml里面如果有写服务端会自动识别)以及在SteadyShot里面注册本竞技场的代码(详情请看SteadyShot发布贴)

本竞技场名为MCBG

创建新类:MCBGArena,继承net.berry64.SteadyShot.Arena.ArenaBase类 《-手动划重点
之后根据IDE提示自动添加方法,当然如果你的IDE没有提示也可以自己写


ok,编程暂时告一段落,我们来规划一下设计,这时候就要用到祖传的windows画图了
为了符合MC的方块,我们不用圆圈而用方块代替“毒”
P.S本来想加个步骤规划一下机器语言和实践方式,不过不太符合我风格所以就算了

于是乎! 我们开始写代码吧,先从玩家加入机制开始,ArenaBase已经定义好了minPlayers和maxPlayers变量,也帮我们从配置文件里面读取了,只需直接调用即可。稍微语言规划一下, 我们需要在edit里面实现设置主传送点和地图大小, 并且在玩家加入时判断玩家数量是否超过最大以及是否已经开始游戏,如果是return false 禁止加入。

这里我希望定义一个全局位置(大厅传送点)以及一个全局timer, 因为本游戏只会拥有一个使用中的计时器。
之后,我需要在addPlayer方法里写入这些东西:
- 确认玩家数量是否超额&确认游戏不在进行中
- 如果玩家是第一个,把竞技场状态从空设置到等待中
- 向本部以及全局玩家列表里添加玩家
- 开启倒计时
于是乎,代码这样:
  1. package net.berry64.SteadyShotAddons.MCBG;

  2. import java.io.File;

  3. import org.bukkit.Location;
  4. import org.bukkit.configuration.file.YamlConfiguration;
  5. import org.bukkit.entity.Player;
  6. import org.bukkit.event.Listener;
  7. import org.bukkit.scheduler.BukkitRunnable;
  8. import org.bukkit.scheduler.BukkitTask;

  9. import net.berry64.SteadyShot.Arenas.ArenaBase;
  10. import net.berry64.SteadyShot.Arenas.ArenaState;

  11. public class MCBGArena extends ArenaBase implements Listener{
  12.         Location lobbyLocation = null;
  13.         BukkitTask timer = null;
  14.         
  15.         @Override
  16.         public boolean startUp() {
  17.                 // TODO Auto-generated method stub
  18.                 return false;
  19.         }

  20.         @Override
  21.         public void shutDown() {
  22.                 // TODO Auto-generated method stub
  23.                
  24.         }

  25.         @Override
  26.         public boolean edit(Player editor, String[] args) {
  27.                 // TODO Auto-generated method stub
  28.                 return false;
  29.         }

  30.         @Override
  31.         public boolean addPlayer(Player p) {
  32.                 if(players.size() >= maxPlayers || state == ArenaState.IN_GAME)  //检测玩家数量&竞技场状态
  33.                         return false;
  34.                
  35.                 if(players.size() <= 0)  //更改状态
  36.                         state = ArenaState.WAITING;
  37.                
  38.                 listAdd(p);
  39.                 this.sendMessage("§6"+p.getName()+" §a加入了游戏,当前人数 §e"+players.size()+"§a/§6 "+maxPlayers);
  40.                 if(timer == null && players.size() >= minPlayers) {
  41.                         this.sendMessage("§a游戏将会在 §660 §a秒后开始");
  42.                         timer = new BukkitRunnable() {
  43.                                 int secsleft = 60; //一分钟
  44.                                 
  45.                                 @Override
  46.                                 public void run() {
  47.                                         for(Player p : players) {
  48.                                                 p.setLevel(secsleft);   //玩家经验条等级设置为倒计时器
  49.                                         }
  50.                                         if(secsleft <= 0) {   //到时间
  51.                                                 beginGame();   //private方法
  52.                                                 timer = null;    // 方便判断
  53.                                                 this.cancel();     //取消计时器
  54.                                         }
  55.                                 }
  56.                                 
  57.                         }.runTaskTimer(MCBGMain.instance, 0L, 20L);  //每20ticks(约1秒)计时一次
  58.                 }
  59.                 p.teleport(lobbyLocation); //传送至大厅
  60.                 p.getInventory().clear();
  61.                 return true;
  62.         }
  63.         //其他一些系统默认没有更改的方法就没有写上了
复制代码

到此,已经实现了入场传送&计时器

回看设计图,现在需要的功能就是设置地图四角,随机生成天空平台,以及给予玩家翅膀

先从设置开始,找到继承的edit, 用处理类似指令的方法处理,这里我会使用我自己的风格,不好看/不实用请谅解

地图四角不需要储存/读取y轴,只需X,Z即可,而且只需要2个点即可构成一个方块平面,配置文件大概这样吧:

  1. lobby:
  2. #bukkit自己直接储存location,将会包含X,Y,Z,世界和角度,这里是大厅传送点

  3. map:
  4.     world:  世界名
  5.     1:
  6.       X:
  7.       Z:
  8.     2:
  9.       X:
  10.       Z:
复制代码



这里为了方便操作,我们创建两个class第一个是CornerArea, 也就是通过两个角落生成的长方形面积
第二个是CenterArea,而这个就是以中心点和两个半径生成的长方形面积直接上代码:

  1. class CornerArea {
  2.         int ax,az,bx,bz;
  3.         
  4.         public CornerArea(int x1,int z1,int x2,int z2) {
  5.                 ax = x1;
  6.                 az = z1;
  7.                 bx = x2;
  8.                 bz = z2;
  9.         }
  10.         public void setCorner(Location loc, int point) {
  11.                 if(point == 1) {
  12.                         ax = loc.getBlockX();
  13.                         az = loc.getBlockZ();
  14.                 } else {
  15.                         bx = loc.getBlockX();
  16.                         bz = loc.getBlockZ();
  17.                 }
  18.         }
  19.         public void setCorners(int x1, int z1, int x2, int z2) {
  20.                 ax = x1;
  21.                 az = z1;
  22.                 bx = x2;
  23.                 bz = z2;
  24.         }
  25.         public int getBaseX() {
  26.                 return Math.min(ax, bx);
  27.         }
  28.         public int getBaseZ() {
  29.                 return Math.min(az, bz);
  30.         }
  31.         public int getSizeX() {
  32.                 return Math.abs(ax-bx);
  33.         }
  34.         public int getSizeZ() {
  35.                 return Math.abs(az-bz);
  36.         }
  37.         public int getRandomCenterX(int margins) {
  38.                 return getBaseX()+margins+(int)(Math.random()*(getSizeX()-(2*margins)));
  39.         }
  40.         public int getRandomCenterZ(int margins) {
  41.                 return getBaseZ()+margins+(int)(Math.random()*(getSizeZ()-(2*margins)));
  42.         }
  43.         public CenterArea getCenterArea() {
  44.                 return new CenterArea(getBaseX()+(getSizeX()/2), getBaseZ()+(getSizeZ()/2), getSizeX()/2, getSizeZ()/2);
  45.         }
  46.         
  47.         public void paintEdges(paintType t) {
  48.                 if(t == paintType.RED) {
  49.                         
  50.                 }
  51.                 else if(t==paintType.WHITE) {
  52.                         
  53.                 }
  54.                 else if(t == paintType.REMOVE) {
  55.                         
  56.                 }
  57.         }
  58.         enum paintType{
  59.                 RED,
  60.                 WHITE,
  61.                 REMOVE;
  62.         }
  63. }
复制代码
到这里应该很简单理解,就是以角落的长方形和附属的一些简易算法,之后是CenterArea

  1. class CenterArea {
  2.         int cx,cz,xr,zr;
  3.         
  4.         public CenterArea(int centerx,int centerz, int rangex, int rangez) {
  5.                 cx = centerx;
  6.                 cz = centerz;
  7.                 xr = rangex;
  8.                 zr = rangez;
  9.         }
  10.         public void setCenter(int x, int z) {
  11.                 cx = x;
  12.                 cz = z;
  13.         }
  14.         public void setRange(int rangex,int rangez) {
  15.                 xr = rangex;
  16.                 zr = rangez;
  17.         }
  18.         public CornerArea getCornerArea() {
  19.                 return new CornerArea(cx-xr,cz-zr,cx+xr,cz+zr);
  20.         }
  21. }
复制代码
这里应该更简单,因为CenterArea的目的只是为了方便计算,游戏的大部分操作都是在CornerArea里面进行的,所以这个class只要有能转换成CornerArea的方法应该就可以了
然后编辑地图部分:

  1. @Override
  2. public boolean edit(Player editor, String[] args) {
  3.                 if(args.length <= 0)
  4.                         editor.sendMessage("§c错误:请输入可以识别的指令");   //排除arrayindexoutofbounds
  5.                
  6.                 else if(args[0].equalsIgnoreCase("setlobby")) {
  7.                         lobbyLocation = editor.getLocation();       //免去设置后需要重读文件
  8.                         yml.set("lobby", editor.getLocation());   //bukkit可以直接储存location信息
  9.                         save();     //刚刚写的储存文件的方法,可以直接用yml.save(f)代替
  10.                         editor.sendMessage("§a成功设置 §6大厅");
  11.                 }
  12.                
  13.                 else if(args[0].equalsIgnoreCase("setWorld")) {
  14.                         //设置世界
  15.                         if(args.length > 1) {
  16.                                 yml.set("map.world", args[1]);
  17.                                 world = args[1];
  18.                                 editor.sendMessage("§a成功设置,如果输入的世界不存在将会报错");
  19.                         } else {
  20.                                 yml.set("map.world", editor.getWorld().getName());
  21.                                 world = editor.getWorld().getName();
  22.                                 editor.sendMessage("§a成功设置为当前世界");
  23.                         }
  24.                         save();
  25.                 }
  26.                
  27.                 else if(args[0].equalsIgnoreCase("setmap")) {
  28.                         if(args.length > 1) {
  29.                                 int loc = 1;
  30.                                 
  31.                                 try { loc = Integer.valueOf(args[1]); } catch (NumberFormatException e) {editor.sendMessage("§c你必须输入一个数字!"); return true;}
  32.                                 if(loc <= 0 || loc >= 3) {editor.sendMessage("§c数字必须为1或2"); return true;}
  33.                                 
  34.                                 gameboard.setCorner(editor.getLocation(), loc);
  35.                                 yml.set("map."+loc+".x", editor.getLocation().getBlockX());
  36.                                 yml.set("map."+loc+".z", editor.getLocation().getBlockZ());
  37.                                 save();
  38.                                 editor.sendMessage("§a设置成功");
  39.                         } else {
  40.                                 editor.sendMessage("§c使用方法: §esetmap [1/2]");
  41.                         }
  42.                 }
  43.                
  44.                 else {
  45.                         editor.sendMessage("§c错误: 未识别的命令");
  46.                 }
  47.                 return true;  //信仰:永不return false
  48. }
复制代码
于是乎,又完成了一个功能,下一个,在loadArena里面写上读取世界,大厅,以及几个点的位置(启动时加载)
,直接代码:


  1. @Override
  2. public boolean loadArena() {
  3.                 lobbyLocation = (Location) yml.get("lobby");    //大厅
  4.                
  5.                 world = yml.getString("map.world");      //变量world赋值(刚刚定义的一个string)
  6.                 int x1 = yml.getInt("map.1.x"); int z1 = yml.getInt("map.1.z");
  7.                 int x2 = yml.getInt("map.2.x"); int z2 = yml.getInt("map.2.z");
  8.                 gameboard = new CornerArea(x1,z1,x2,z2);
  9.                 return true;
  10.         }
复制代码

如果不理解请返回目录去看之前教程

本部分最后,在刚刚定义的beginGame方法内写上随机生成天空平台&给予所有玩家翅膀
  1. private void beginGame() {
  2.         state = ArenaState.IN_GAME;       //设定状态为游戏中(至此游戏已经开始)
  3.         int x = x1+(x2-x1)/5+(int)(Math.random()*(x2-x1-(x2-x1)/5));      //x2-x1获取竞技场宽度,随机生成但不太靠边缘,距离边缘1/5距离
  4.         int z = z1+(z2-z1)/5+(int)(Math.random()*(z2-z1-(z2-z1)/5));      //同上,这部分可能有点复杂,仔细想想就好了
  5.         
  6.         //在y=175生成一个5*5的玻璃平台
  7.         World w = MCBGMain.instance.getServer().getWorld(world);
  8.         for(int i = x-2; i <= x+2; i++) {
  9.                 for(int k = z-2; k <= z+2; k++) {
  10.                         w.getBlockAt(i,175,k).setType(Material.GLASS);
  11.                 }
  12.         }
  13.         
  14.         for(Player p:players) {
  15.                 p.teleport(new Location(w,x,176,z));  //传送所有玩家
  16.                 p.getInventory().setChestplate(new ItemStack(Material.ELYTRA, 1));  //给予玩家翅膀并装备到胸甲栏
  17.         }
  18. }
复制代码


至此本部分已经完成,下一部分: 游戏机制的设计&实践, 敬请期待教程10.2
如果有其他建议/意见 请在本帖回复

P.S. 人气不要钱,给了又不会少块肉

作者: Yihc    时间: 2017-12-25 11:23
一脸懵逼的进来,一脸懵逼的出去
收藏起来以后看~

作者: Soul-Tude    时间: 2017-12-25 20:35
插件的思路确实挺清晰,如果能做出来一定会在目前国内的游戏服火的,但是我还是想说一下插件的游戏性方面问题,如果lz真的想往下做,那我就提几个更新思路(可以日后完善):
1.毒圈应设计为圆圈缩小,且应该设计为几何角度随机选点缩圈,否则会引起游戏性问题,如果是矩形的圈那么玩家只要站在死角便可以看到圈内的任何角度(MC的广角设计),如果是圆形的话除非玩家丧心病狂广角开得很大,不然基本上只能看一定的角度,无法卡所谓的死角。 PS:不要在意水印233 这是张PUBG的缩圈规律图。2.声音的判断/血液可用粒子/枪械的设计/mc的辣鸡第三人称/组队(扶队友233)/可以利用矿车的特性做开局的飞机/可用掉落物设置为装备等,然后控制玩家捡取掉落物的时间(否则箱子太多真的= =)/死亡玩家的gamemode 3不应干扰到游戏,视角应控制在存活队友的身上/对外挂的防范/

总而言之,如果想作出MCBG肯定是mod最为合适,不过插件也可以极大程度上的还原,不过工作量还是挺大的,游戏可没那么好做!

作者: ROF    时间: 2018-1-1 20:42
本帖最后由 ROF 于 2018-1-1 20:44 编辑

emmm很好的想法,楼主辛苦了



作者: Soul-Tude    时间: 2018-1-1 21:10
ROF 发表于 2018-1-1 20:42
emmm很好的想法,楼主辛苦了

没导入getname方法的包?
作者: 弱鸡绿毛怪    时间: 2018-1-26 17:57
哦666
大佬什么时候又出新帖子了
作者: 凌云轩雅    时间: 2018-2-2 23:10
这个教程看起来不错,我去目录看下
作者: 董开和天哥    时间: 2018-2-5 11:12
我想知道为什么报错一堆,明明什么包都导入了,代码基本是复制来的(除了修改类名和包名),可还是出现“z1 cannot be resolved to a variable”"x1 cannot be resolved to a variable""lobbyLocation cannot be resolved to a variable""无法解析yml""minPlayers cannot be resolved to a variable"之类的

javalz2.PNG (170.62 KB, 下载次数: 0)

javalz2.PNG

javalz2.PNG (170.62 KB, 下载次数: 1)

javalz2.PNG

javalz3.PNG (139.88 KB, 下载次数: 1)

javalz3.PNG

作者: 弑魂腐竹    时间: 2018-2-7 10:13
真是的很适合新手的帖子废话不多说还是支多多支持也希望作业尽快更新
作者: sOta_cCc    时间: 2018-2-7 14:53
从国外服玩完吃鸡来看这个,我觉得挺有想法。支持
作者: lg0812    时间: 2018-6-18 15:13
楼主厉害。跟着楼主学习。
作者: berry64    时间: 2018-6-30 10:37
董开和天哥 发表于 2018-2-5 11:12
我想知道为什么报错一堆,明明什么包都导入了,代码基本是复制来的(除了修改类名和包名),可还是出现“z1 ...

因为我定义的全局变量没有放到代码里....可以的话尽量不要直接复制黏贴而要自己学习
作者: leich123    时间: 2018-8-24 11:45
谢谢大佬分享了  已经让服务器的技术来这里看看 然后防止做出了一款吃鸡的插件  目前还有一些bug技术已经在修复了  修复后会过来反馈的  总的来说 还是谢谢楼主分享这个思路
作者: ROF    时间: 2018-8-31 17:23
emmm我想知道eclipse怎么弄像LZ那样
代码值得学习一下
新手开发者路过
作者: george2005    时间: 2019-4-5 11:27
        十分感谢!
作者: AlbusDumbledor    时间: 2019-7-4 14:41
神乎其技,不服不行!
作者: 智闪    时间: 2019-8-30 22:55
加油^0^~
作者: anyiying    时间: 2019-8-31 03:38
哪位大佬知道忘记正版账号怎么办
作者: adger.plus    时间: 2019-11-1 00:46
顶一下 顺便问一下api哪里有的看
作者: 辞音    时间: 2020-8-4 01:36
非常完美 nice
作者: TZ2009    时间: 2021-2-8 19:45
要是能把工程文件发一下就更好了,求发一下,谢谢!
作者: TZ2009    时间: 2021-2-8 19:46
前置讲的那一波完全看不懂,太含糊了
作者: whatfilmae    时间: 2021-2-8 19:49
提示: 作者被禁止或删除 内容自动屏蔽
作者: 弓鱼    时间: 2021-2-14 20:15
        MCBBS有你更精彩~
作者: xiaoxiaozhu@    时间: 前天 21:00
我同学早就想玩了