目录

11-2:java绘制方框

目录

单机手游绘制教程11-2:java绘制方框

赶码人

血条没啥好说的,drawLine就完事。游戏本身提供血条了,再画一个显得多余,咱把数字给搞出来就行了。

先把坐标搞明白,咱获取的坐标是一个点,而敌人身上的点多得是。如果获取坐标就是敌人坐标,而不是什么骨骼、武器坐标,那么通常就两种,xyz表示敌人这个立体图形的正中心,要么就是表示敌人脚下平面的正中心,本教程的T游戏是后者这种。

那么如果画一个点上去,这个点就绘制在坦克这个立方体的底面的正中心,前面已经得到了坦克的尺寸size(经过测试,差不多是坦克高度,高度差不多是边长的一半),那么(x,y+size,z)表示坦克立方体的顶面的正中心的点坐标,(x,y,z)和(x,y+size,z)这两个点和矩阵运算得到的结果,符合“近大远小(指的是敌人远近、方框的大小)”,可以用作方框的顶边和底边的屏幕y坐标,再根据敌人的长宽比例,比如FPS的敌人宽高比差不多是0.5,本游戏的宽高比差不多是2,那么结合根据底边中心和顶边中心这两个点计算出的两个屏幕坐标的y坐标的差值,就可以得到方框的宽度,然后再通过这两个点的x坐标就可以得到方框的左边和右边的屏幕x坐标。

经常能看到有人问为啥偏框,情况很多,下图这种比较常见(红色框),框框尺寸没问题,但是总是偏高半个人,说明找到的坐标是敌人中心点坐标,可以读取出这个xyz坐标后,给高度(大多数是y坐标)减去半个人物高度的值就好了。。。总之就是一件事,要用头顶的xyz和脚底的xyz一起和矩阵去运算,才能分别得到头顶在屏幕上的坐标,和脚底在屏幕的坐标….另外,头顶脚底的x和z坐标通常相等,这里可以减少一些运算(本文没体现出来,我不在乎这点运算量…)

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/2b07d94b8c34f45bbb6d37821337f7ffc851c2ea.png@942w_435h_progressive-20220206101339951.png

这是最常见的画方框思路,画一个方框没啥意思,这里干脆搞个3D框吧,注意接下来这个3D框和敌人朝向无关,角度只和相机有关,

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/a79f1f2f2be18b5bba7ae03608da9aa44b764987.png@734w_659h_progressive.png

红色的是下面的平面,绿色的是上面的平面

如图,我们需要8个平面坐标系上的点,说白了就是8个3D坐标点转成的8个屏幕上的x,y点,那么我们已经知道底面的中心点是(x,y,z),对于本系列教程的T游戏,也就是(x,0,z),1~4这四个点就是(x-size, 0, z-size), (x-size, 0, z+size), (x+size, 0, z-size), (x+size, 0, z+size),5~8这四个点3d坐标是(x-size, size, z-size), (x-size, size, z+size), (x+size, size, z-size), (x+size, size, z+size)。

如果想搞和朝向相关的3D框,获取到敌人旋转角,把这8个点都绕着y轴旋转这个角度就行了,实际上和y轴没啥关系(因为是旋转角),可以参考这篇:https://blog.csdn.net/sinat_33425327/article/details/78333946

再顺便提一句吧,我前面视频说y表示高度,这是通常情况,比如本游戏,以及unity引擎的游戏。UE4就比较特殊,(好像是)用z表示高度,我也很无语…

理论说完了,其实等于没说,也没啥可说的,接下来搞代码。

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/02db465212d3c374a43c60fa2625cc1caeaab796-20220206101317532.png

首先搞一个类,用来封装数据,这种不到200行的小项目直接弄个静态内部类就行了,为了省事,全都搞成static,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static class Role{
  public int curHp;
  public int maxHp;
  public float size;
  public float x;
  public float y;
  public float z;

  
  public int[][] screen = new int[8][2];

  
  public int[] hpPos = new int[2];
}

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/a91b6136a0592d86643b99f6e899a4c32f725f34.png@942w_624h_progressive.png

别忘在Main构造方法里面对role数组初始化

矩阵乘坐标会频繁调用,把3d坐标转屏幕坐标进行封装:

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/af0f1206c8c0ba120d8922216625734ca14f6a39.png@942w_306h_progressive.png

这个本系列教程的T游戏能用,其他游戏不能照搬

算了,直接发了吧,毕竟java端代码不像C语言端那么敏感,也不能直接套在其他游戏:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package main;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.WindowConstants;

public class Main extends JFrame{

	private static final long serialVersionUID = 1L;

	private static JComponent view;



	static class Role{
		public int curHp;
		public int maxHp;
		public float size;
		public float x;
		public float y;
		public float z;

		
		public int[][] screen = new int[8][2];

		
		public int[] hpPos = new int[2];
	}

	static Role[] role = new Role[50]; 

	static int count = 0; 

	public Main() {
		for(int i = 0 ; i < 50 ; i ++) {
			role[i] = new Role();
		}

		this.setSize(800, 600);
		this.setTitle("bilibili: 赶码人");
		this.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
		this.setUndecorated(true);
		this.setOpacity(0.5f);

		this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

		view = new JComponent() {
			private static final long serialVersionUID = 1L;

			@Override
			protected void paintComponent(Graphics g) {
				Graphics2D g2d = (Graphics2D)g;
				g2d.setStroke(new BasicStroke(2));
				g2d.setColor(Color.RED);
				g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
				

				g2d.setFont(new Font("宋体", Font.PLAIN, 18));
				g2d.drawString("bilibili: 赶码人", 30, 30);

				for(int i = 0 ; i < count ; i ++) {
					g2d.setColor(Color.RED);
					g2d.drawLine(role[i].screen[0][0], role[i].screen[0][1], role[i].screen[1][0], role[i].screen[1][1]);
					g2d.drawLine(role[i].screen[1][0], role[i].screen[1][1], role[i].screen[2][0], role[i].screen[2][1]);
					g2d.drawLine(role[i].screen[2][0], role[i].screen[2][1], role[i].screen[3][0], role[i].screen[3][1]);
					g2d.drawLine(role[i].screen[3][0], role[i].screen[3][1], role[i].screen[0][0], role[i].screen[0][1]);

					g2d.drawLine(role[i].screen[4][0], role[i].screen[4][1], role[i].screen[5][0], role[i].screen[5][1]);
					g2d.drawLine(role[i].screen[5][0], role[i].screen[5][1], role[i].screen[6][0], role[i].screen[6][1]);
					g2d.drawLine(role[i].screen[6][0], role[i].screen[6][1], role[i].screen[7][0], role[i].screen[7][1]);
					g2d.drawLine(role[i].screen[7][0], role[i].screen[7][1], role[i].screen[4][0], role[i].screen[4][1]);

					g2d.drawLine(role[i].screen[0][0], role[i].screen[0][1], role[i].screen[4][0], role[i].screen[4][1]);
					g2d.drawLine(role[i].screen[1][0], role[i].screen[1][1], role[i].screen[5][0], role[i].screen[5][1]);
					g2d.drawLine(role[i].screen[2][0], role[i].screen[2][1], role[i].screen[6][0], role[i].screen[6][1]);
					g2d.drawLine(role[i].screen[3][0], role[i].screen[3][1], role[i].screen[7][0], role[i].screen[7][1]);

					String hp = role[i].curHp + "/" + role[i].maxHp;
					g2d.setColor(Color.YELLOW);
					g2d.drawString(hp, role[i].hpPos[0] - 20, role[i].hpPos[1]);
				}


			}

		};


		this.add(view);

		this.setVisible(true);


	}


	public static void main(String[] args) {

		new Main();

		byte[] b = new byte[64 + 4 + 50 * (2 + 3 + 1) * 4];

		try(DatagramSocket ds = new DatagramSocket(6666)) {
			DatagramPacket dp = new DatagramPacket(b, b.length);

			float[] matrix = new float[16];

			while(true) {
				ds.receive(dp);

				for(int i = 0 ; i < 16; i++) {
					matrix[i] = Float.intBitsToFloat(byteToInt(b, i * 4));
				}

				count = byteToInt(b, 64);

				for(int i = 0 ; i < count ; i++) {
					int offset = 68 + i * 24;

					role[i].curHp = byteToInt(b, offset);
					role[i].maxHp = byteToInt(b, offset + 4);
					role[i].x = Float.intBitsToFloat(byteToInt(b, offset + 8));
					role[i].y = Float.intBitsToFloat(byteToInt(b, offset + 12));
					role[i].z = Float.intBitsToFloat(byteToInt(b, offset + 16));
					role[i].size = Float.intBitsToFloat(byteToInt(b, offset + 20));



					calcScreenPos(matrix, role[i].x, role[i].y + role[i].size + 2, role[i].z, role[i].hpPos);

					calcScreenPos(matrix,
							role[i].x - role[i].size,
							role[i].y,
							role[i].z + role[i].size,
							role[i].screen[0]);
					calcScreenPos(matrix,
							role[i].x - role[i].size,
							role[i].y,
							role[i].z - role[i].size,
							role[i].screen[1]);
					calcScreenPos(matrix,
							role[i].x + role[i].size,
							role[i].y,
							role[i].z - role[i].size,
							role[i].screen[2]);
					calcScreenPos(matrix,
							role[i].x + role[i].size,
							role[i].y,
							role[i].z + role[i].size,
							role[i].screen[3]);


					calcScreenPos(matrix,
							role[i].x - role[i].size,
							role[i].y + role[i].size,
							role[i].z + role[i].size,
							role[i].screen[4]);
					calcScreenPos(matrix,
							role[i].x - role[i].size,
							role[i].y + role[i].size,
							role[i].z - role[i].size,
							role[i].screen[5]);
					calcScreenPos(matrix,
							role[i].x + role[i].size,
							role[i].y + role[i].size,
							role[i].z - role[i].size,
							role[i].screen[6]);
					calcScreenPos(matrix,
							role[i].x + role[i].size,
							role[i].y + role[i].size,
							role[i].z + role[i].size,
							role[i].screen[7]);
				}



				view.repaint();

			}

		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public static void calcScreenPos(float[] matrix, float x, float y, float z, int[] screenPos) {
		float res0 = matrix[0] * x + matrix[1] * y + matrix[2] * z + matrix[3];
		float res1 = matrix[4] * x + matrix[5] * y + matrix[6] * z + matrix[7];

		float res3 = matrix[12] * x + matrix[13] * y + matrix[14] * z + matrix[15];

		float ndcX = res0 / res3;
		float ndcY = res1 / res3;


		screenPos[0] = (int) ((ndcX + 1) / 2f * view.getWidth());
		screenPos[1] = (int) ( (1- (ndcY + 1) / 2.0) * view.getHeight());
	}

	public static int byteToInt(byte[] b, int index) {
		int i = b[index + 3];
		i = i << 8;
		i = i | (b[index + 2] & 0xff);
		i = i << 8;
		i = i | (b[index + 1] & 0xff);
		i = i << 8;
		i = i | (b[index] & 0xff);
		return i;
	}

}

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/02db465212d3c374a43c60fa2625cc1caeaab796-20220206101317532.png

教程更新完毕,感谢大家支持!!感谢大家不嫌弃我菜(

祝各位观众新年快乐~~