要求:
实现投篮程序。用键盘控制投篮动作,篮球按照一定的概率,以抛物线的路线运动到篮筐中,落地时能够有反弹,最终静止。
主体框架:
主体就是三个画面,篮筐,人,球,人有移动的效果和shoot投球(创建新的球的对象添加到Vector数组中,以便于监听操作)的效果,球有抛物线滚动的效果,落地有回弹消失的效果。
重点和难点:
1、设置抛物线,这里就是运动到一个简单的物理公式,横坐标速度不变,纵坐标速度加上gt,这里我们设置一个count计时,横坐标和纵坐标分别每次偏移一个单位速度,就会得到抛物线的效果。这里我加了一点小细节,为了提高命中率我将随机数的范围调整在0.5到0.8之间,提升用户体验感。
public class ball extends JLabel implements ActionListener{
int speed = 24;
double alpha = 0.98;
Timer timer;
int count = 0;
double v ;
double x;
@Override
public void actionPerformed(ActionEvent e) {
count ++;
v = speed + alpha * count;
int dy = (int) (this.getY() - speed + alpha * count );
this.setLocation(this.getX() -speed ,dy);
if(this.getY()== 800 - 82) timer.stop();
}
public ball(int x, int y){
this.setBounds(x, y, 84, 82 );
this.setIcon(new ImageIcon(this.getClass().getResource("ball.jpg")));
this.x = Math.random();
if(this.x < 0.5) this.x += 0.5;
if(this.x > 0.8) this.x-=0.1;
speed = (int)(this.x*speed )+ 5;
timer = new Timer(10, this);
timer.start();
}
}
2、设置回弹效果,这里我花了好长时间调整都没有得到效果,这里简单说一下:
(1)误区:不要直接在计时器里面测试回弹效果,因为每次计时器启动都会创建一个新的对象,然后回弹的球就会重叠,导致bug现象(ps:这个真的让我怀疑人生了好久)
(2)最终方案:这里我是采用了反向speed来进行回弹的效果,然后每次的上升或者下降的幅度都相应减少来达到最终静止的效果,每进行一次计时,幅度减少一点,方向取反,这里注意一个物理上的小细节,反弹时的速度等于落地时速度。
public class dropedball extends JLabel {
Timer timer1,timer2;
int speed;
double alpha = 1;
int count = 0;
int flag;
class L1 implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
count ++;
if(speed < 0) {
speed += alpha;
}
else if(speed > 0)
speed -= alpha;
setLocation(getX(),getY()-speed);
speed = -speed;
}
}
public dropedball(int speed, int x, int y){
this.setIcon(new ImageIcon(this.getClass().getResource("ball.jpg")));
this.setBounds(x, y, 84, 82);
this.timer1 = new Timer(100, new L1());
this.timer1.start();
this.speed = speed;//这里是为了将下落时侯的速度放大
}
}
3、设置回弹静止后消失的效果,这里涉及到了回弹类回弹的过程和主类的计时器的监听来判断静止,在这里我也卡了很长时间(有点汗颜,基础不牢):
- 误区:就是消失条件的设置,不要单纯的以为静止就是消失的条件,注意你的参数你的回弹过程中的幅度的变化能否变为0,不能的话就会一直在alpha两侧浮动,就会一直不能实现消失的效果。
- 误区:在主类监听的过程中不要单纯的想当然,当球落地就设置得分并且开始回弹,回弹静止就开始消失,单纯这样的话永远也不可能实现消失的效果,主要是每次监听都会重新启动计时器,就相当于你的dropedball一直在被创建,一直在frame里面添加,每次监听到新的落地的球的时候,没错,回弹的球时开始回弹到静止的过程了,可是你在一个计时的范围内如果没有达到静止的话,之后就再也不可能找到这个回弹的球了,因为已经被新的回弹的球代替了,程序永远也不可能走到之前回弹的球的静止的那一步,球也就永远不会消失。
- 解决方案,另外设置一个Vector用来放回弹的球,计时器的碰撞检测中添加一个回弹球静止的循环判断,到达静止就将其移除。
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.ujn.cn.xx;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
/**
*
* @author 99159
*/
public class basketball {
JFrame frame;
JLabel basket,scoreboard, result;
boy boy1;
Timer timer1;
int count = 0;
Vector<ball> balls = new Vector<ball>();
Vector<dropedball> dbs = new Vector<dropedball>();
KeyListener K;
class L1 implements ActionListener{
public boolean checkPointInRectangular(int x1, int y1, int x2, int y2, int width, int height){
//描述一个点是否在指定矩形的内部, 是返回true, 否则返回false
if(x1 >= x2 && x1 <= x2 + width && y1 >= y2 && y1 <= y2 + height)
return true;
else
return false;
}
public boolean check(int y){
if(y >= 800 - 82) return true;
else return false;
}
@Override
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < balls.size(); i++)
{
if(checkPointInRectangular(balls.get(i).getX() + 42, balls.get(i).getY() + 82, basket.getX() + 250 , basket.getY() + 180, 100, 50))
{
count ++;
frame.repaint();
frame.remove(scoreboard);
scoreboard.setText("得分" + count + "分!");
scoreboard.setBounds(500, 0, 100, 100);
frame.add(scoreboard);
}
if(check(balls.get(i).getY())){
dropedball db = new dropedball((int)balls.get(i).v,balls.get(i).getX(),800-82);
frame.remove(balls.get(i));
balls.remove(i);
frame.add(db);
dbs.add(db);
}
frame.repaint();
}
for(int i = 0; i < dbs.size(); i++)
{
if(dbs.get(i).speed == 0) {
frame.remove(dbs.get(i));
dbs.remove(i);
frame.repaint();
}
}
if(count >= 100) {
frame.removeKeyListener(K);
scoreboard.setText("得分" + count + "分!");
result.setBounds(800, 0, 300, 100);
result.setText("游戏结束,恭喜您获胜!");
frame.add(result);
frame.repaint();
timer1.stop();
}
}
}
public basketball(){
frame = new JFrame("投篮游戏");
frame.setSize(1800,800);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
scoreboard = new JLabel();
boy1 = new boy();
result = new JLabel();
scoreboard.setBounds(500, 0, 100, 100);
scoreboard.setText("得分" + count + "分!");
basket = new JLabel();
basket.setBounds(0,0,400,800);
basket.setIcon(new ImageIcon(this.getClass().getResource("basket.jpg")));
frame.add(basket);
frame.add(scoreboard);
frame.add(boy1);
frame.setVisible(true);
timer1 = new Timer(10, new L1());
timer1.start();
KeyListener K = new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_W:
boy1.moveUp();
break;
case KeyEvent.VK_S:
boy1.moveDown();
break;
case KeyEvent.VK_A:
boy1.moveLeft();
break;
case KeyEvent.VK_D:
boy1.moveRight();
break;
case KeyEvent.VK_SPACE:
ball b = boy1.shoot();
balls.add(b);
frame.add(b);
frame.repaint();
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
}
};
frame.addKeyListener(K);
}
public static void main(String[] args) {
basketball f = new basketball();
}
}
4、为了游戏完成性最好再设置一个游戏结束的条件,比如分数达到多少就关闭计时器,输出游戏结束
if(count >= 100) {
frame.removeKeyListener(K);
scoreboard.setText("得分" + count + "分!");
result.setBounds(800, 0, 300, 100);
result.setText("游戏结束,恭喜您获胜!");
frame.add(result);
frame.repaint();
timer1.stop();
}
总结:
这次算是用java来写一个相对比较完整的小游戏,也是我做的第一个稍微像样一点的游戏,物理碰撞检测和一些触发后的效果着实令我很着迷(要不然不会写了一下午~~毕竟第一次嘛),这个投篮游戏让我想起了之前第一次接触微信小程序时玩的投篮小游戏,和弟弟一起玩的,有点触动吧也算是,让我想起臭博了,这就打个电话给他哈哈哈,也算是给往昔一个可有可无的答案,成长嘛,本身就是一个不断提升认知的过程,总是在不经意间给之前的自己很多很多答案,这样回忆往昔的时候有时会多一点宽慰,有时可能会少了一点遗憾,有时会揭露一些美好的幻想,不过总归是对那恍若琉璃般的岁月,不胜感激。