每当我们玩着各式各样游戏时,例如LOL,原神,空洞骑士,你会不会突然脑海里蹦出一个想法,如果自己也可以做一个这样的游戏的就好了。虽然这对我们非常遥远,但是也许在踏上这条游戏制作的路时,可能我们能成为这样的一份子。
所以,让我们开始制作我们的第一个游戏吧,我叫做碰撞小球。
多线程游戏 2022-07-26 16-35-38
建立Ballgame类:
1.建立可视化部分,老生常谈了
this.setTitle("多线程游戏");
this.setSize(1000,1000);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
FlowLayout flow=new FlowLayout();
this.setLayout(flow);
JButton btn=new JButton("停止");
JButton btn1=new JButton("继续");
Dimension dim=new Dimension(100,50);
btn.setPreferredSize(dim);
btn.addActionListener(ll);
this.add(btn);
btn1.setPreferredSize(dim);
btn1.addActionListener(ll);
this.add(btn1);
this.setVisible(true);//建立好所需的界面,给按钮绑好监听器
2.内部加入鼠标监听器:
addMouseListener(new MouseAdapter() { //内部监听器加法,节省空间
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
Random rnd=new Random();
double speedx=rnd.nextInt(8)-1.6;
double speedy=rnd.nextInt(6)-2.1;
Color cl=new Color(rnd.nextInt(Integer.MAX_VALUE));
int size=rnd.nextInt(40)+10;
Ball ball=new Ball(e.getX(),e.getY(),speedx,speedy,size,cl);
bl[index++]=ball;
}//每点击鼠标一次,即给储存小球信息的数组,Ball[],赋予最开始的信息。
});
3.开辟一个线程,让其负责小球的移动,使得界面其他操作不会被卡死(重中之重,要认真学习)
Graphics g=getGraphics();
Threadball run=new Threadball(this,g);//值得注意的是,我们这次传的是一个对象,方便线程取值
Thread t=new Thread(run);
t.start();//开辟一个新线程,同时将所需信息传过去
4.为了使游戏有层级感,用paint画黑矩形:
public void paint(Graphics g){
g.setColor(Color.black);
g.fillRect(0,100,1000,50);
}//绘制黑矩形,使区域划分开,让按钮可以不被遮盖。
也许你会对传对象感到奇怪,这正是多线程中重要的一点在之后会进行讲解。
建立按钮监听器Ac类:用来控制线程进行
public void actionPerformed(ActionEvent e){
btnname=e.getActionCommand();
if(btnname.equals("停止")){
flag=false;
}
if(btnname.equals("继续")){
flag=true;
}
}//写好按钮反应,控制线程进行。
建立Ball类:
利用构析方法,得到所需信息。
绘制小球:
public void drawball(Graphics g){
g.setColor(cl);
g.fillOval((int)(x),(int)(y),size,size);
} //绘制小球
小球移动方法:
public void move(){
if(x<0+0.5*size){
x=0+0.5*size;
speedx=-speedx;
}
if(x>1000-0.5*size){
x=1000-0.5*size;
speedx=-speedx;
}
if(y<150+0.5*size){
y=150+0.5*size;
speedy=-speedy;
}
if(y>1000-0.5*size){
y=1000-0.5*size;
speedy=-speedy;
}//小球碰到边界后,及时位置回复,同时改变速度方向
x+=speedx;
y+=speedy;//通过使小球的x,y值发生变化,当下次绘制时达到移动的效果。
}//小球移动
建立ThreadBall类,实现Runable的接口:
利用构析方法,得到所需信息,值得注意的是得到了Ballgame的对象。
线程所具操作:
public void run() {//线程所具操作
while (true) {
g.setColor(Color.black);
g.fillRect(0,100,1000,50);//多次绘制黑矩形,防止被小球遮盖
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}//用延时来控制小球速度,否则计算机计算过快,会使得小球移速过快而效果不好
for (int i = 0; i < gui.index; i++) {/*之所以一开始要将对象传来,是因为在线程中,无法完成值传递
故想要获取信息,只能动态得到,用对象来完成*/
/*如果只是将index传入的话,哪怕进行点击后,index++,而线程中的index始终为0*/
for (int j = i + 1; j < gui.index; j++) {
double distance = Math.abs(bl[i].x - bl[j].x) * Math.abs(bl[i].x - bl[j].x) +
Math.abs(bl[i].y - bl[j].y) * Math.abs(bl[i].y - bl[j].y);
double rad = ((bl[i].size + bl[j].size) * (bl[i].size + bl[j].size)) / 4;
if (distance < rad) {
if (bl[i].speedx * bl[j].speedx > 0) {
bl[i].speedx = -bl[i].speedx;
}
if (bl[i].speedy * bl[j].speedy > 0) {
bl[i].speedy = -bl[i].speedy;
}//检测碰撞与否,利用高中数学原理
bl[i].speedx = -bl[i].speedx + rnd.nextInt(1) - 0.7;
bl[i].speedy = -bl[i].speedy + rnd.nextInt(1) - 0.4;
bl[j].speedx = -bl[j].speedx + rnd.nextInt(1) - 0.5;
bl[j].speedy = -bl[j].speedy + rnd.nextInt(1) - 0.3;//如果碰撞了,不仅要反向,还要有变化,防止卡死
}
}
Ball ball = gui.bl[i];//值得注意的是,数组是地址传递,所以才可以使信息迭代
ball.move();
ball.drawball(g);
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}//让小球留在屏幕上的时间
while (!gui.ll.flag/*取到监听器的flag来控制线程的进行*/) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}//利用while循环卡死
g.setColor(Color.white);
g.fillRect(0,150,1000,850);//通过白屏的手段消除之前图像,方便小球独立出来
}
}
值得回味的是,我们得到小球数目,即index的时候,我们并非利用什么构析方法,抑或是值传递而得到,而是通过Ballgame类的gui对象,来动态取得,如此才可以得到。
除此之外,我们应当牢记数组是采取地址传递的,故虽然每次循环时,我们都会建立一个临时Ball类的ball对象来进行move方法,使得坐标值发发生变化,而下个临时对象依然能得到改变后的信息,就在于数组的特殊性。