返回顶部

[转载] 真正的从零基础学Bukkit开发-Java的流程控制和Bukkit的API

[复制链接]
像素搬运菌Lv.7 显示全部楼层 发表于 2024-2-21 02:41:42 |阅读模式 打印 上一主题 下一主题 来自 中国广西南宁
联机教程
教程类型: 插件
教程来源: 转载
原贴地址: 贺兰星辰
教程目标: bukkit

马上登录/注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 像素搬运菌 于 2024-2-21 02:45 编辑

氵了一早上hhhhhh,不想氵了,还是乖乖来更新吧
(所以如果有金粒和人气的话请一定务必要往我身上砸啊)
上期作业讲解:
写一个工厂方法

这很容易,你只需要写一个静态方法,返回一个对象就好,如:
public static Object getOnjectInstance(){
  return new Object();
}
Java的运算符  听起来挺高大上,但运算符的意思其实就是加减乘除号之类的....算数运算符:
  hey,你一定知道,数学是一切的基础,即使在编程中也是这样!因此,有以下的运算符可以使用:
int a = 2; //还记得吗,在Java中,一个等于号的意思就是赋值,在这里意为把2赋值给a
int b = 3;
int c;
c = a + b; //加号的意思就是两数相加,就像算数一样,因此c的值为5
c = a - b; //减号的意思就是两数相减,因此c的值为-1
c = a * b; //“星号”(在小键盘上有)的意思就是两数相乘,因此c的值为6
c = a / b; //“左斜杠”的意思是两数整除,注意,是整除,只会返回整数,因此,在这里,2除以3返回的值为0
c = a % b; //“百分号”的意思是取余数,因此c的值为2
int d = a * (b + c) //显而易见,数学中的括号优先计算也是可以使用的
hey,如果我们搞一些“数学悖论”,比如,让除数为0,会发生什么?
int a = 0;
int b = 2;
int c = b/a;
控制台报错:Exception in thread "main" java.lang.ArithmeticException: / by zero
可以看到,Java通过抛出(throw,即,报错)异常来提醒开发者,如果有人尝试这么做,或许我们应该使用“try"语句处理这个异常

特殊的自增/自减运算符
hey,看看这个代码
int a = 1;
a = a +1;

乍一看貌似不太对,有人自己加自己?但事实上,这是正常的语法,其中,赋值后a的值为2。
还记得赋值是什么吗,把右边的值给左边,如果右边是个算式呢?那就先算,再赋值。因此, a = a + 1实际上是先计算了a+1,然后把这个值(即,2)赋值给a。
但是或许,有简单的方法让这个值加一
int a = 1;
int a1 = a++;
int a2 = ++a;
int a3 = a--;
int a4 = --a;
这里的++和--即为自增运算符,他们可以让变量增加或是减少1
但是等一下,++可以在变量前面,也可以在变量后面,有什么不同吗?
当我们运行这段代码并输出所有变量时,我们能够看到:

hey,貌似跟我们想的不一样,为什么结果是这些?
事实上,a++和a--这种形式,被称作后缀自增自减法,先计算表达式,再自增自减。因此a1中,先把a1赋值给a,然后使a自增为2;
++a和--a这种形式,被称作前缀自增自减法,先自增自减,再计算表达式。因此在a2中,先使已经在上一步自增为2的a自增为3,然后把a赋值给a2,故a2为3
其实还挺容易理解的,不是吗?
Tips:在上述代码中,变量被赋值了多次,最后的值取最终赋值结果,除非定义final关键词,否则任何变量都可以无限赋值(使用final后,则只能赋值一次)
小心,加号的作用不仅仅是两数相加!在String中,两个字符串可以使用加号来合并起来,如
String a = "hello";
String b = " world";
String c = a + b;(因此c为hello world)
关系运算符:
  关系运算符即运算两个数字(也可能是对象,但对象(比如String)一般会用equals方法)是否具有特定关系,运算均返回布尔值(即boolean,真或假)
int a = 2;
int b = 3;
boolean c;
c = a==b; // ==运算符意为判断==左右两边(即a和b)是否相等,很显然,a不等于b,所以c在这里值为false;
c = a!=b //!=运算符恰恰相反,意为判断左右两边是否不等,所以这里c值为true;
Tips:千万注意!别把=当作==,这俩很容易搞混!
为什么对象使用equals方法比较相同,而不是用==?
  ==做的工作其实是对比两个东西的内存地址是否相同,在基本数据类型中,当值相同时,他们的内存地址也是相同的原因很复杂,在这里不多赘述),因此才可以用==来比较;而String之前说过是特殊的对象,我们创建一个字符串时,实际上是new(初始化)了一个String对象,两个String对象的内存地址不可能相同,因此不能用==比较。大部分这样的对象都拥有equals方法,通过这个方法可以真正对比两个对象的内容而不是内存地址是否相同
String a = "a";
String b = "a";
boolean c = a==b; //c应该为false
c = a.equals(b); //c为true;
//但是实际运行中,第一个对c的赋值也返回了true,这是因为Java针对String对象搞了一个专门的==号,使他可以对比String的内容了(在其他语言中这个叫做“运算符重载”,不幸的事,Java不支持开发者进行运算符重载.

逻辑运算符:  在Java中有三种逻辑运算符,他们分别是逻辑与,逻辑或和逻辑非运算符(很显然,他们只适用于比较布尔值(boolean))
boolean a = true;
boolean b = false;
boolean c;
c = a&&b //&&为逻辑与运算符,当左右都为true是,返回true,否则返回false。此处为false
c = a||b //||为逻辑或运算符,当左右有一个为true时,就可返回true,都不为true则返回false。此处c为true
c = !(a&&b) //!为逻辑非运算符,如果后面的值为true则返回false;后面的值为false则返回true。此处a&&b为false,故c为true
//当然,类似于a&&b&&c这样也是可以的,即abc都为true时才返回true
括号的又一个特殊用法
  从上面的代码中,我们可以看到,括号起了一个包含的作用,它使逻辑非运算符指向a&&b这个式子而不是紧跟在他后面的a(即,运行!a&&b会发生的事情)

除此之外,还有位运算符,用于数字的二进制计算,此处略

Java的流程控制:
  什么是流程控制?流程控制可以干什么?
很显然,生活中充满了流程控制:如果我不氵贴了,我就去写开发教程。这个“如果”就是一个流程控制
流程控制可以分为“循环”和“条件”
条件:
int a = 20;
//if语句:如果括号内为true,则运行大括号内语句。对应人类语言:如果...就
if(a==20){
  a=10; //因为a确实等于20,因此此处会被执行,a值为10;
}
//if...else语句:如果括号内为true,则执行大括号内语句,否则执行else后的大括号语句。对应人类语言:如果...就...否则...就...
if(a==20){
a=10; //因为上面a被赋值为10了,所以这里的代码不会被执行,因为a不等于20
}else{
  a=15; //因为a==20为false,故此处会被执行,a值为15
}
//if...else if...else语句:对应人类语言:如果,否则如果,否则如果,否则
if(a==1){
  //此处代码块不会被执行,因为a为15,不等于1
}else if(a==2){
  //此处代码块不会被执行,因为a为15,不等于2
}else if(a==15){
  //此处代码块会被执行,因为a确实等于15
}else{
  //此处代码块不会被执行,因为a不是一个即不等于1,又不等于2,又不等于15的其他数
}
//注意,最后面的else不是必须的,如果你不需要否则的话;else if可以无限套娃,写一堆else if.
//新活:套娃
int num1 = 1;
int num2 = 2;
if(num1==1){
if(num2==2){
  //即当num1为1且num2为2时执行此处代码
  }
}
//然而以上代码完全可以简化为if(num1==1&&num2==2){}
//事实上你会发现如果要判断一个数是否为指定值,那么用else if会很麻烦,要一直套娃,因此我们可以使用switch语句
//switch...case语句
int number = 200;
switch(number) //这个括号里的值曾经不能是String,但现在可以了
{
  case 10:
    //当number为10时执行此处代码块,此例中不会执行
    break; //必须在每一个case末尾标注break(在接下来的循环流程中,你也可以使用break,意为跳出循环)
  case 20:
    //do something
    break;
  case 200:
    //此处代码会被执行
    break;
  default:
    //default的作用同if...else if..else中的else一样,同时也不是必要的
循环:
int a = 10;
//while
while(a<10){
  a++; //while循环,当括号内boolean表达式被满足时便开始循环,直到不满足表达式
}
while(true){
  //while true会使一个循环一直持续下去,不会停止(除非在循环中break)
}
//do...while
do{
a++;//于上面不同的是,do...while会先执行一次循环体内的代码,然后再按照while的表达式判断循环(也就是说,do while中代码至少会被执行一次)
}while(a<10);
int b = 1;
//for
for(int i=1;i<10;i++){//千万注意,这里是两个分号,不是逗号
  /* for循环的流程是这样的:
  首先,int i=1是初始化,初始化一个值用来计数(当然,在循环体(即,for下面大括号里的那些代码)中也可被调用),此处初始化了一个值为1,名为i的int值
  然后,i<10是判断表达式,当此处判断满足(即,为true)时,执行循环体,否则跳出循环
  每一次执行完循环体后,调用i++对计数的值(别的也行)进行更新
  直至不满足表达式,跳出循环
  因此,上面的for意为,一个数为i,如果i<10循环一次,每次循环完令i加1
*/
}
for(;;){
  //同while(true)
}
//foreach(增强式for循环)略,用到List,Map,数组的时候再讲
continue和break关键字
  有时候,我们可能需要提前跳出循环,这时,我们可以使用break和continue关键词(所以他俩只能用在循环和switch语句里头用)
  break代表跳出循环:遇到break时,直接跳出整个循环体,忽略循环体内,break下方的代码
int a =1;
//以下while代码效果同上方的那个for循环代码
while(true){
  if(a>=10);{
    break;
}
  a++;
}

  continue则不一样,他代表跳过当前循环:遇到continue时,跳过本次循环,跳过循环体内的其他代码,然后进入下一次循环
为什么我们要在case下面用break?
  如果你break,那么执行完这个case后,会继续检索下面的case(包括default),这不是我们需要的(在本例中没啥用,但如果是判断a是否大于指定数呢?)

BukkitAPI:
  我们已经提了很久API这个玩意,但是API到底是啥?
  API意为“应用程序接口”,是应用程序留给开发者对其应用程序进行拓展的便捷工具。对于我们来说BukkitAPI就是Bukkit给一个封闭箱子留的一个开口,籍此开口,我们可以进入到服务端的内部,对服务器进行高级的修改。
  在Java中,为了了解API,我们可以查询“Javadoc”,Javadoc是由Java为程序自动生成的注释文档,写明了应用开发者的API参数用途等
BukkitAPI(英文原版): https://hub.spigotmc.org/javadocs/bukkit/index.html?overview-summary.html
BukkitAPI(第三方汉化版): https://bukkit.windit.net/javadoc/
  为了确保满足大部分新手理解BukkitAPI,在下面的实战中,我们会选择第三方汉化版的BukkitAPI

BukkitAPI中的监听器:
  监听器即Listener,他可以对一些特定的事件(即,Event)进行监听,对事件进行处理(如,玩家加入服务器事件,PlayerJoinEvent)
//一个玩家加入事件监听器类的结构
package cn.lingyuncraft.hellominecraftplugin;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class Listener implements org.bukkit.event.Listener {
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent e){
            }
}
  在Bukkit中,只有同时满足以下三步,才能成功的注册监听器:
    1.监听器方法所在类必须实现(implements)org.bukkit.event.Listener接口
    2.监听器方法必须具有@org.bukkit.event.EventHandler注解
    3.必须使用Bukkit.getPluginManager().registerEvents(Listener listener,Plugin plugin);注册监听器类
  Tips:对于implements,你可以近似理解为他是让一个类实现了一个抽象的,没有意义的,只有要求的“类”的所有要求,因此它也就变成了这个类的一种

实战:当玩家shaokeyibb第三次加入游戏的时候给他op(什么鬼)
BukkitAPI版本:1.15.2(1.13+API改动很大,请以自己版本的API为准)
1.首先,我们要进行数据存储,这样才有可能为“第三次”计数,因为只需要为固定数量的玩家记一个整数,因此我们选择使用静态的int值来存储玩家进服次数。
public static int count = 0; //初始化int值
2.然后,我们需要编写监听器,前往Javadoc,在概览界面下,我们可以看到org.bukkit.event包存储了各种各样的事件监听器

其中,我们可以看到org.bukkit.event.player存储的是与玩家相关的事件
打开,我们可以发现“PlayerJoinEvent"代表着玩家进入服务器事件,这正是我们所需要的

打开,我们可以从“方法概要”看到这个事件和其所继承(extend)于其父监听器对象中的方法

还记得吗,我们想要做的应当是:判定进服的玩家的ID是否为shaokeyibb,是的话就+1,当为3时给他OP.
所以我们可以发现PlayerJoinEvent继承了来自PlayerEvent(代表所有和玩家有关系的事件)的getPlayer方法
可以看到,这个方**返回一个Player对象
但是我们不能把对象和一个玩家ID(即,String字符串)直接对比,我们得想办法获取这个Player对象的玩家ID?
点击Player,我们可以查询到Player类包含的所有方法,如没有static,他们都是非静态的
经过一番查阅,我们找到了getDisplayName()方法


但根据描述,返回的玩家ID可能会包含有颜色代码什么的,这很显然不安全,所以你应该使用继承于HumanEntity的getName()方法
然后,我们得知道怎么给玩家Op
于是我们找到了继承于ServerOperator类的setOp(boolean value)方法


3.东西都找齐了,那我们就可以开始写代码了!
Listener类
package cn.lingyuncraft.hellominecraftplugin;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class Listener implements org.bukkit.event.Listener {
    private static int count = 0;
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent e){
        Player player = e.getPlayer();
        if(player.getName().equals("shaokeyibb")){
            count++;
        }
        if (count==3){
            player.setOp(true);
        }
    }
}

插件主类
package cn.lingyuncraft.hellominecraftplugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class Hellominecraftplugin extends JavaPlugin {
    @Override
    public void onEnable() {
        // Plugin startup logic
        Bukkit.getPluginManager().registerEvents(new Listener(),this);
    }
    @Override
    public void onDisable() {
        // Plugin shutdown logic
    }
}
运行maven install编译插件,安装到服务器内,即可看到效果
Q&A
1.count变量我能写到别的地方吗?
  可以,本例中,count变量因为只在Listener类中使用因此是private的,你可以在插件主类上声明,但这样就需要设置为protected或者public;注意不要把变量声明到onPlayerJoin方法中,这会使每次触发监听器时都将count设置为0.

2.Bukkit.getPluginManager().registerEvents(new Listener(),this);是怎么回事?
  Bukkit.getPluginManager().registerEvents(Listener listener,Plugin plugin);用来注册监听器对象,使该类中的监听器真正有效被调用(没错,被调用,我们写方法,触发事件时服务器调用我们的方法).传入的两个参数分别是监听器对象和插件对象.
  那为什么本例中传入了new Listener()和this,而不是new Listener()和new 插件主类名()?
  我们需要传入一个Listener对象,但很显然这个对象未被实例化过,所以这里我们应该new一个出来传入在Bukkit中,但是插件主类对象只能实例化一个,而这一个已经在插件开启的时候由服务器实例化过了,我们不能实例化两次主类对象,因此我们在主类,使用this关键字表示主类对象。
  我可以在别的类获取主类对象实例吗?
  当然可以,你需要在主类声明一个静态的主类对象类型的变量(没有赋值,所以值为null,还不能用),然后在插件开启(即onEnable方法)时将this赋予给这个变量,然后写一个静态的Getter来获取这个实例。此后,你便可以使用这个Getter当作在主类的那个this用了。

作业:
1.尝试给上面的插件加一个当玩家得到op时发送消息的功能

2.尝试获取主类实例

这个帖子内容量很大,写了我一整个下午,所以大家一定要评分支持啊!(最好是人气


下一章可能会讲:数据存储(数组,List,Map),导入其他插件依赖(pom.xml),forEach


帖子地址: 

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

像素世界 成立于2022年8月,是中国开发者开创的一个综合像素沙盒游戏交流社区,拥有稳定的开发维护及运营技术,提供长期交流需求。本社区开放了我的世界模组、地图、插件等资源交流版块,集中了大量资源为玩家和开发者提供了优质的游戏环境。
  • 官方B站

  • 微信公众号

  • 商务合作