一时兴起,想写个模拟地铁驾驶的游戏,但是很多东西都不会,资源哪里有?例如列车前进时周围景物的动态效果怎么做出来,速度控制杆上各个幅度代表了多大的加速度。我很佩服也很羡慕《申城脉动》的作者,能写出那样的一个游戏和获得那么多的资源。地铁族上面资料不少的,但不会找出来,废话不多说。
里面要用到的速度仪表盘,上网看见别人画了不少很炫的,但提供下载没有。对GDI+不太了解的我只能自己写。幸亏看到一篇博文,是别人学生时代的实验报告的(呵呵!这就是差别)。里面的代码我没看,我只看了一幅图片就够了。之前只困惑于仪表盘上面的刻度是怎么画出来的,那篇文章里面有一幅图就是一幅三角函数的图。之前一直没想到这里要用回初中时学的三角函数。
下面将从控件的属性,还有绘制的过程做一个介绍。从而介绍完这个仪表盘的制作。
数据类型 |
属性名称 |
描述 |
int |
LongGraduateLength |
长刻度长度 |
int |
ShortGraduateLength |
短刻度长度 |
float |
Short2Long |
短刻度转长刻度进率 |
float |
ShortgraduateSpan |
短刻度间隔,实际数值的间隔 |
float |
Range |
量程 |
int |
Angle |
整个刻度的角度,角度制的度数 |
int |
Width |
控件宽度,与高度相等 |
int |
Height |
控件高度,与宽度相等 |
float |
Value |
当前值 |
绘制控件都是在OnPaint事件里执行GDI+的代码。
protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); DrawGraduation(e.Graphics); DrawArc(e.Graphics); }
第一个方法是画表盘的刻度,以及上面的示数;第二个方法是画表盘的指针,包括当前的值。
先介绍第一个方法
1 private void DrawGraduation(Graphics g) 2 { 3 int startDec = 270 - (360 - Angle) / 2; 4 int endDec = 270 + (360 - Angle) / 2 - 360;//计算出起始角度和终止角度 5 float dealta = Angle * ShortgraduateSpan / Range; //计算出每个刻度间的角度间隔 6 StringFormat sf=new StringFormat(){ Alignment= StringAlignment.Center,LineAlignment= StringAlignment.Center}; 7 8 for (float i = startDec; i >= endDec; i -= dealta)//通过循环画出表盘上的刻度 9 { 10 if ((i - startDec)*Range/Angle % Short2Long == 0) //判断此位置画的是大刻度还是小刻度 11 { 12 //画大刻度 13 g.DrawLine(Pens.White, 14 Radius + (float)Math.Cos(Degree2Radian(i)) * Radius, 15 Radius - (float)Math.Sin(Degree2Radian(i)) * Radius, 16 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength), 17 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength) 18 ); 19 //写刻度上的文字 20 g.DrawString(((startDec - i) * Range / Angle).ToString(), this.Font, Brushes.White, 21 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength-15), 22 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength-15), 23 sf 24 );//sf是文字的格式,这里设置成居中 25 } 26 else 27 //画小刻度 28 g.DrawLine(Pens.White, 29 Radius + (float)Math.Cos(Degree2Radian(i)) * Radius, 30 Radius - (float)Math.Sin(Degree2Radian(i)) * Radius, 31 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - ShortGraduateLength), 32 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - ShortGraduateLength) 33 ); 34 } 35 }
画刻度线还考了一回初中的三角函数。
只要有刻度线所在的角的角度,刻度线的起点和终点的坐标都可以确定下来。起点就是半径上圆心的另一端。通过sin*R可以得到起点的相对圆心的纵坐标;cos*R可以得到起点相对圆心的横坐标。终点的同样道理,通过sin*(R-L)可以得到终点的相对圆心的纵坐标;cos*(R-L)可以得到终点相对圆心的横坐标。
.NET Framework提供的三角函数有点坑,方法的描述上是说角度值,但是实际上要传是弧度值。所以我这里还要做一个角度制和弧度制的转换。
private double Degree2Radian(double degree) { return degree * Math.PI / 180; }
下面到另一个方法
1 private void DrawArc(Graphics g) 2 { 3 //示数和外部的圆圈 4 for(float i=0;i<3;i+=0.1f) 5 g.DrawEllipse(Pens.White, Radius - 25+i, Radius - 25+i, 50-2*i, 50-2*i); 6 g.DrawString(this.Value.ToString(), new Font(this.Font.FontFamily, 20,FontStyle.Bold), Brushes.White, Radius, Radius, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }); 7 8 //旋转画布,形成一定的倾角 9 Matrix matrix = new Matrix(); 10 matrix.RotateAt(Value * Angle / Range - (270 - (360 - Angle) / 2), new PointF(Radius, Radius), MatrixOrder.Append); 11 g.Transform = matrix; 12 13 //绘制指针上面的主体矩形 14 Rectangle rec = new Rectangle(Radius + 25, Radius - 3,Radius-55-ShortGraduateLength, 6); 15 g.DrawRectangle(Pens.White, rec); 16 g.FillRectangle(Brushes.White, rec); 17 18 //绘制指针上的三角形 19 g.DrawPolygon(Pens.White,CreateTriangle(Radius*2 -30-ShortGraduateLength,Radius ,6,10,false)); 20 g.FillPolygon(Brushes.White, CreateTriangle(Radius*2 -30-ShortGraduateLength, Radius , 6, 10, false)); 21 22 //绘制指针末端的小矩形 23 rec = new Rectangle(Radius * 2 - 25 - ShortGraduateLength,Radius-1,20,2); 24 g.DrawRectangle(Pens.White, rec); 25 g.FillRectangle(Brushes.White, rec); 26 }
这里指针实际上有四个图形组成,指针起始的一个圆形,主体的那个粗的矩形,达到渐细效果的三角形和末端的一个小矩形
.NET Framework又没有提供画三角形的方法,所以画三角形它只能通过画多边形的方法来绘制。
我这里定义了一个只符合这里使用的方法,用于绘制一个等腰三角形,传入底边的中点坐标,底边和高的长度,是水平还是垂直,来构造一个三角形,最后返回三个顶点的坐标
1 private Point[] CreateTriangle(int x, int y,int bottom, int height, bool isHorizontal) 2 { 3 Point p1, p2, ph; 4 if (isHorizontal) 5 { 6 p1 = new Point(x - bottom/2, y); 7 p2 = new Point(x + bottom/2, y); 8 ph = new Point(x, y + height); 9 } 10 else 11 { 12 p1 = new Point(x, y - bottom/2); 13 p2 = new Point(x, y + bottom/2); 14 ph = new Point(x + height, y); 15 } 16 return new Point[] {p1,p2,ph }; 17 }
附一幅完成的效果图,最后把整个控件的源码粘上来。
觉得这个控件一点儿也不通用,只能满足极个别的需求。大家尽管提一下意见,让我改进改进。
1 public class Dashboard:Control 2 { 3 public Dashboard() 4 { 5 this.Width = 300; 6 this.Height = 300; 7 this.BackColor = Color.Black; 8 DoubleBuffered = true; 9 this.Font = new Font(this.Font.FontFamily, 15f); 10 } 11 12 private int longGraduateLength=20,shortGraduateLength=10,angle=300; 13 private float short2Long=2,shortgraduateSpan=5,range=100; 14 private Color lineColor=Color.White; 15 16 public int LongGraduateLength 17 { 18 get { return longGraduateLength; } 19 set 20 { 21 longGraduateLength = value < 1 ? 1 : value; 22 } 23 } 24 25 public int ShortGraduateLength 26 { 27 get { return shortGraduateLength; } 28 set 29 { 30 shortGraduateLength = value < 1 ? 1 : value; 31 } 32 } 33 34 public float Short2Long 35 { 36 get { return short2Long; } 37 set 38 { 39 short2Long = value < 1 ? 1 : value; 40 } 41 } 42 43 public float ShortgraduateSpan 44 { 45 get { return shortgraduateSpan; } 46 set 47 { 48 shortgraduateSpan = value < 1 ? 1 : value; 49 } 50 } 51 52 public float Range 53 { 54 get { return range; } 55 set 56 { 57 range = value < 1 ? 1 : value; 58 } 59 } 60 61 public int Angle 62 { 63 get { return angle; } 64 set 65 { 66 angle = value < 1 ? 1 : value; 67 } 68 } 69 70 public Color LineColor 71 { 72 get { return lineColor; } 73 set { lineColor = value; } 74 } 75 76 public new int Width 77 { 78 get { return base.Width; } 79 set 80 { 81 if (value != base.Height) base.Height = value; 82 base.Width = value; 83 } 84 } 85 86 public new int Height 87 { 88 get { return base.Height; } 89 set 90 { 91 if (value != base.Width) base.Width = value; 92 base.Height = value; 93 } 94 } 95 96 private float currValue; 97 public float Value 98 { 99 get { return currValue; } 100 set 101 { 102 float preValue = currValue; 103 if (value < 0) currValue = 0; 104 else if (value > Range) currValue = Range; 105 else currValue = value; 106 if (preValue != currValue) 107 { 108 if(ValueChanged!=null) ValueChanged(this, new EventArgs()); 109 this.Refresh(); 110 } 111 112 } 113 } 114 115 protected override void OnPaint(PaintEventArgs e) 116 { 117 base.OnPaint(e); 118 //刻度 示数 指针 示数 119 DrawGraduation(e.Graphics); 120 DrawArc(e.Graphics); 121 } 122 123 private int Radius 124 { 125 get { return Width / 2; } 126 } 127 128 private Point core; 129 private Point Core 130 { 131 get 132 { 133 if (core.X != this.Radius || core.Y != this.Radius) 134 core = new Point(this.Radius, this.Radius); 135 return core; 136 } 137 } 138 139 private void DrawGraduation(Graphics g) 140 { 141 //换算角度范围 142 //半径*sin=外点 143 //(半径-刻度长)*sin=内点 144 //内点标数 145 int startDec = 270 - (360 - Angle) / 2; 146 int endDec = 270 + (360 - Angle) / 2-360; 147 float dealta=Angle*ShortgraduateSpan/Range; 148 StringFormat sf=new StringFormat(){ Alignment= StringAlignment.Center,LineAlignment= StringAlignment.Center}; 149 150 for (float i = startDec; i >= endDec; i -= dealta) 151 { 152 if ((i - startDec)*Range/Angle % Short2Long == 0) 153 { 154 g.DrawLine(Pens.White, 155 Radius + (float)Math.Cos(Degree2Radian(i)) * Radius, 156 Radius - (float)Math.Sin(Degree2Radian(i)) * Radius, 157 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength), 158 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength) 159 ); 160 g.DrawString(((startDec - i) * Range / Angle).ToString(), this.Font, Brushes.White, 161 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength-15), 162 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength-15), 163 sf 164 ); 165 } 166 else 167 g.DrawLine(Pens.White, 168 Radius + (float)Math.Cos(Degree2Radian(i)) * Radius, 169 Radius - (float)Math.Sin(Degree2Radian(i)) * Radius, 170 Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - ShortGraduateLength), 171 Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - ShortGraduateLength) 172 ); 173 } 174 } 175 176 private void DrawArc(Graphics g) 177 { 178 for(float i=0;i<3;i+=0.1f) 179 g.DrawEllipse(Pens.White, Radius - 25+i, Radius - 25+i, 50-2*i, 50-2*i); 180 g.DrawString(this.Value.ToString(), new Font(this.Font.FontFamily, 20,FontStyle.Bold), Brushes.White, Radius, Radius, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }); 181 182 183 Matrix matrix = new Matrix(); 184 matrix.RotateAt(Value * Angle / Range - (270 - (360 - Angle) / 2), new PointF(Radius, Radius), MatrixOrder.Append); 185 g.Transform = matrix; 186 187 Rectangle rec = new Rectangle(Radius + 25, Radius - 3,Radius-55-ShortGraduateLength, 6); 188 g.DrawRectangle(Pens.White, rec); 189 g.FillRectangle(Brushes.White, rec); 190 191 g.DrawPolygon(Pens.White,CreateTriangle(Radius*2 -30-ShortGraduateLength,Radius ,6,10,false)); 192 g.FillPolygon(Brushes.White, CreateTriangle(Radius*2 -30-ShortGraduateLength, Radius , 6, 10, false)); 193 194 rec = new Rectangle(Radius * 2 - 25 - ShortGraduateLength,Radius-1,20,2); 195 g.DrawRectangle(Pens.White, rec); 196 g.FillRectangle(Brushes.White, rec); 197 } 198 199 private Point[] CreateTriangle(int x, int y,int bottom, int height, bool isHorizontal) 200 { 201 Point p1, p2, ph; 202 if (isHorizontal) 203 { 204 p1 = new Point(x - bottom/2, y); 205 p2 = new Point(x + bottom/2, y); 206 ph = new Point(x, y + height); 207 } 208 else 209 { 210 p1 = new Point(x, y - bottom/2); 211 p2 = new Point(x, y + bottom/2); 212 ph = new Point(x + height, y); 213 } 214 return new Point[] {p1,p2,ph }; 215 } 216 217 private double Degree2Radian(double degree) 218 { 219 return degree * Math.PI / 180; 220 } 221 222 protected override void OnResize(EventArgs e) 223 { 224 base.OnResize(e); 225 this.Refresh(); 226 } 227 228 public delegate void OnValueChanged(object sender, EventArgs e); 229 230 public event OnValueChanged ValueChanged; 231 }