3D映画を見るたびに、私たちは簡単に興奮します。2D 画面や紙に 3D 効果を反映させることは、常に人々の研究の方向性でしたが、今日はそれを完成させるためにボールを例に挙げてみましょう。
銀河
プロジェクトを実装する前に、立体的なボールの描き方を理解する必要があります
for(int i = 0;i < 1000;i++){
g.setColor(new Color(i / 4,i / 4,i / 4));
g.fillOval(200 + i / 4 ,300 + i / 4,500 - i / 2,500 - i / 2);
}
原則は次のとおりです。徐々に縮小し(描画の開始点は左上隅であるため、円が小さくなると開始点を右下に移動する必要があることに注意してください)、徐々に明るくなる立体を描き続けます。 (色が白から黒に変わります) 円は立体感を出すことができ、
最初にこれを自分で試すことができます。記事には写真は掲載されていません。
1. 必要なタブレットを完成させる
今回はJPanelを継承しており、前回とほぼ同じですが、違いはツールのインターフェースです。
public void addTool(JPanel toolPanel){
toolPanel.setLayout(new FlowLayout());
Dimension btnDim = new Dimension(300,80);
Dimension toolDim = new Dimension(180,50);
String[] btnnames = {"开始","停止","继续"};
String[] jlsnames = {" 起点x坐标: "," 起点y坐标:"," 轨迹宽度:", " 轨迹长度:"," 球尺寸:"};
for(int i = 0;i < 5;i++){
JLabel jl = new JLabel(jlsnames[i]);
jl.setFont(new java.awt.Font("Dialog", 1, 30));//设置标签字体
JTextField jt = new JTextField();
jt.setFont(new java.awt.Font("Dialog", 1, 30));//设置输入框字体
jl.setPreferredSize(toolDim);
jt.setPreferredSize(toolDim);
toolPanel.add(jl);
toolPanel.add(jt);
ac.jts[i] = jt;//得到输入框的地址
}
for (int i = 0; i < 3; i++) {
JButton btn = new JButton(btnnames[i]);
btn.setPreferredSize(btnDim);
btn.addActionListener(ac);
toolPanel.add(btn);
}
}//在Jpanel上加上工具
インターフェイス入力を読み取る必要があるため、JTextFiled (テキスト入力ボックス) を使用できます。代わりに、JLabel (ラベル) を使用します。これは、ユーザーに何かを入力するように促すために使用されます。
取得する必要がある情報には、惑星運動の軌道の開始点、軌道のサイズ、および惑星のサイズが含まれます。
もちろん、システムのデフォルト フォントは小さすぎるため、setFont メソッドを使用して自分で調整し、満足のいく効果を得ることもできます。
2. リスナー クラスを完成させる
1. 必要な属性を確立します。
JTextField[] jts = new JTextField[5];
Graphics g;
Boolean flag = true;
Vector<Ball> balls ;
int[] data = new int[5];
JTextField 配列は、入力ボックスの内容を取得するためのもので、リスナーをボタンにバインドするときに、インターフェイス上のテキスト入力ボックスのアドレスがリスナーに渡され、読み取れるようになります。
ac.jts[i] = jt;//入力ボックスのアドレスを取得
ただし注意が必要なのは、JTextFiled は文字列型のテキスト入力ボックスであり、必要な数として使用できないため、変換するメソッドを作成して作成することです。
int[] data = new int[5];// 入力番号を保存
public int change(String input) {
int allNum = 0;
int num = 0;
for (int i = 0; i < input.length(); i++) {
char a = input.charAt(i);
int t = (int) a;
if (t <= 57 && t >= 48) {
num = t - 48;
allNum*= 10;
allNum += num;
}
}
return allNum;
}//把文本框中的字符串转换为 int;
フラグは、惑星が動いているかどうかを判断するために使用されます。
Graphics g に関しては決まり文句であり、ボール配列 (インターフェイスで作成され、リスナーに渡される) は、後で描画するために惑星情報を格納します。
2. ボタンの反応を書く
public void actionPerformed(ActionEvent e) {
String btnname = e.getActionCommand();
String name = btnname;
if (name.equals("开始")) {
for(int i = 0;i < 5;i++){
data[i] = change(jts[i].getText());
}//得到文本框中的数字
Ball ball = new Ball(data[0],data[1],data[2],data[3],data[4]);
ball.orbit(); //先存好轨迹
balls.add(ball);
}
if(name.equals("继续")){
flag = true;
}
if(name.equals("停止")){
flag = false;
}
}
ボタンは次のように反応します。
開始: インターフェイスのテキスト入力ボックスの情報を使用して、ボール オブジェクトを作成し、ボール配列に格納します。
続行: フラグは true
停止: フラグは false
3. Ball クラスを完成させます (インターフェイス クラスはこのクラスの配列を作成し、デフォルトのボールを埋めます)。
1. 必要な属性を設定し、作成方法を使用してパーツを取得します。
private int xPoint, yPoint , width, height,size;
double t; //t用来决定drawBall方法中,i缩减倍数。
Vector<Integer> x=new Vector<>();
Vector<Integer> y=new Vector<>();//存放轨迹坐标
Vector<Double> moveSize=new Vector<>();//存放倍数
Random rnd = new Random();
int luckyDog = rnd.nextInt(3);//决定那个颜色为小球颜色
public Ball(int xPoint, int yPoint, int width, int height, int size) {
this.xPoint = xPoint;
this.yPoint = yPoint;
this.width = width;
this.height = height;
this.size = size;
this.t = 1000 / size;
}
軌道の開始点、サイズ、および惑星サイズに加えて、位置の変化に応じてボールをスケーリングする必要があるという事実にも注意を払う必要があります。描画処理中に 0 になるため、t の目的は負の数が表示されるのを防ぐことです。
配列アノテーションについては、その関数が書かれており、乱数はボールを描画する際の色を決定するために使用されます。
2. 移動サイズの計算方法
public double countSize(int x,int y) {//参数为轨迹坐标
double maxDistance;
double a = width > height ? 0.5 * width : 0.5 * height;
double b = width < height ? 0.5 * height : 0.5 * width;
double c = Math.pow(a * a - b * b,2);//得到椭圆的 a,b,c数据
if(b < c){
double m = b * b / c * c;
maxDistance = - c * c * m * m + 2 * b * b * m + a * a + b * b;
}else{
maxDistance = 4 * b * b;
}//通过圆锥曲线知识,求出离下端点最远的点
double distance = Math.pow(Math.abs(x - xPoint - 0.5 * width),2) +
Math.pow(Math.abs(y - yPoint - height),2);//求出点到观察点距离
double time = 1 - Math.pow(distance/maxDistance,0.5) * 0.6;
double moveSize = this.size * time;
return moveSize;//为了使效果更好,采取下端点为观察点,这样更能体现出近大远小。
//之后一定要好好学习数学,怎么都用得上的!!!55555,可恶极了,还是逃不脱数学的魔掌!!!
}//计算移动的球大小
次の端点を観測点として、最も遠い点から 0.4 倍のサイズでスケーリングし、その他はこの比率に従ってスケーリングします。
距離の解については、高校の円錐曲線の知識を使って求めることができます。
3. 軌道法
public void orbit() {//为了不成为“shit山”,可以多次使用,改用参数来完成!
int num = 0;
BufferedImage orbitbuff = new BufferedImage(2000, 2000, BufferedImage.TYPE_INT_RGB);
Graphics bg = orbitbuff.getGraphics();
bg.setColor(Color.white);
bg.drawOval(xPoint, yPoint, width, height);//用缓存画笔画上一个椭圆,提供轨迹
//值得注意,缓存图片默认为黑色,故我们画上白色椭圆
for (int i = xPoint; i <= xPoint + width; i++) {
for (int j = yPoint; j <= yPoint + height; j++) {
int color = orbitbuff.getRGB(i, j);
if (color == -1/*为白色*/) {
x.add(i);
y.add(j);//得到坐标点
moveSize.add(countSize(x.get(num),y.get(num++)));//得到缩放尺寸
break;/*跳出循环目的是,由于椭圆是对称图形,会出现一个横坐标,
两个纵坐标的情况,如果以此绘制球,会出现上下翻跳的情况。*/
}
}
}//仅仅得到椭圆上半部的坐标点
for (int i = xPoint + width; i >= xPoint; i--) {
for (int j = yPoint + height; j >= yPoint; j--) {
int color = orbitbuff.getRGB(i, j);
if (color == -1/*为白色*/) {
x.add(i);
y.add(j);
moveSize.add(countSize(x.get(num),y.get(num++)));//得到缩放尺寸
break;/*跳出循环目的是,由于椭圆是对称图形,会出现一个纵坐标,
两个横坐标的情况,如果以此绘制球,会出现左右翻跳的情况。*/
}
}
}//得到椭圆下半部分的坐标点
//读出缓存图像上椭圆的坐标点,存为我们的数组里,可作为轨迹点
}//轨迹方法(内含缩放)
ボールが動くと上下にジャンプする理由は注目に値します。オービットバフが画像をキャッシュするのは、軌道点を取得するためであり、描画するためではないことを理解しています。
4. ボールを描く方法
public void ballpaint(Graphics bg , int x ,int y, double size ){
for (int i = 0; i < 1000; i++) {
switch (luckyDog){
case 0 :
Color ballColor0 = new Color(i / 6,0 , 0);
bg.setColor(ballColor0);
break;
case 1 :
Color ballColor1 = new Color(0,i / 6 , 0);
bg.setColor(ballColor1);
break;
case 2 :
Color ballColor2= new Color(0,0 , i / 6);
bg.setColor(ballColor2);
break;
}//决定是哪个颜色的小球
bg.fillOval((int) (x + i / t / 2), (int) (y + i / t / 2),
(int) (size - i / t), (int) (size - i / t));
}/*改变大小的操作,是通过缩放倍数而完成的!!!!
这是改进得来,效果更好*/
}//画一个球方法
小玉の描き方はベタ玉の描き方とほぼ同じ
異なる:スイッチを使用してボールの色を決定します。ラッキードッグの値はオブジェクトの作成時に決定され、変更されません。
4. 描画スレッドを完了します (インターフェイス クラスが存在するときは既にオンになっています)。
惑星の動きの効果は、小さなボールを連続的に描画し、次に背景画像を描画してそれを除去し、動的な効果を実現するためです。マルチスレッドを使用しないと、プログラムがスタックし、他のアプリケーションが実現できなくなります。
1. 必要な属性を設定し、分析方法で部品を取得します。
Listener ac ;//不用什么 new Listener(),避免死循环创建问题。
Vector<Ball> balls;//不用什么 new Vector<Ball>(),避免数组地址不一,数据不同问题!
BufferedImage buff = new BufferedImage(2000,1840,BufferedImage.TYPE_INT_RGB);
Graphics bg = buff.getGraphics();//用于绘制的缓存图片
Graphics g;
Vector<Integer> num = new Vector<Integer>();//用于之后绘画方法点的迭代,而分别设置界限;
public DrawThread(Listener ac, Vector<Ball> balls,Graphics g) throws IOException {
this.ac = ac;
this.balls = balls;
this.g = g;
}
2. 背景画像を取得する
File file = new File("C:\\Users\\27259\\Desktop\\milkyRiver.jpg");
BufferedImage padbuff = ImageIO.read(file);//得到底板背景图片
3.runメソッドに内容を書く
public void run(){
while(true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}//防止移动过快,效果不好
//也可以减少资源过多占用,使得负载过重
while(!ac.flag){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}//用死循环卡住线程时,一定要加上一些内容,不然计算过快不会再去取 ac.flag,使得线程无法继续
for (int i = 0;i < balls.size(); i++) {
if(num.size() <= balls.size()) {/*之所num数组多建一个单位,是为了
防止 num.get(i) == balls.get(i).x.size() 处的 num.get(i)越界,
因为 for (int i = 0; i < balls.size(); i++) 处的 balls.size() 可能变大了!!!*/
//小小加一,手速就跟不上了!
num.add(0);
}
}//设置迭代界限
bg.drawImage(padbuff,0,0,null);//画上背景图片
for (int i = 0; i < balls.size(); i++) {
int t = num.get(i);
balls.get(i).ballpaint(bg,balls.get(i).x.get(t),
balls.get(i).y.get(t),balls.get(i).moveSize.get(t));
num.set(i,t+1);
if(num.get(i) == balls.get(i).x.size() - 1){
num.set(i,0);
}
}//完成迭代并绘制。
g.drawImage(buff,0,0,null);//绘制缓存图片
}
}
その際、 listen クラスで記述したフラグを while(!ac.flag)で使用してスレッドをブロックしていましたが、もちろん、内容の一部を埋めることに注意する必要があります。
理解するのが最も難しいのは num 配列. これは、スプラッシュ スクリーンの問題を回避する ために 、キャッシュされた画像に描画する必要があるすべてのボールを同時に描画する必要があるためです。 、すべてキャッシュされた画像にあります。
ただし、すべてのボールに同じ数のトラック ポイントがあるわけではないことを理解する必要があるため、配列を使用して各ボールのトラック ポイントをトラバースする必要があります。
1. 上記の理由により、balls 配列に 1 を加えたサイズの num 配列を作成します。
2. ボール配列をトラバースし、各ボール オブジェクトに ballpaint メソッドを使用させ、パラメータの添字は num に対応する値を使用します。
3. num に対応する値に 1 を加算します。
4. 対応する小さいボールの座標点の数から 1 を引いた数に等しいかどうかを判断し、等しい場合は num = 0 にリセットします。
これで私たちは自分たちの銀河を完成させました。