一时兴起,想写个模拟地铁驾驶的游戏,但是很多东西都不会,资源哪里有?例如列车前进时周围景物的动态效果怎么做出来,速度控制杆上各个幅度代表了多大的加速度。我很佩服也很羡慕《申城脉动》的作者,能写出那样的一个游戏和获得那么多的资源。地铁族上面资料不少的,但不会找出来,废话不多说。
里面要用到的速度仪表盘,上网看见别人画了不少很炫的,但提供下载没有。对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 }
附一幅完成的效果图,最后把整个控件的源码粘上来。
觉得这个控件一点儿也不通用,只能满足极个别的需求。大家尽管提一下意见,让我改进改进。
class="code_img_closed" src="/Upload/Images/2013091919/0015B68B3C38AA5B.gif" alt="" />logs_code_hide('66196c21-838e-4ba8-a34c-d5cc1473da76',event)" src="/Upload/Images/2013091919/2B1B950FA3DF188F.gif" alt="" />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 }仪表盘完整代码