最近迷上吃鸡游戏,慢慢对他的跑圈机制产生了兴趣,于是就试着写了个吃鸡游戏跑圈机制出来~~~
1.C# winform程序中比较简单的绘图控件就是 PictureBox
用到的主要辅助类有:Bitmap,Graphics,Brush,Pen。用形象的方式介绍下他们:PictureBox控件相当于一块土地,为画图腾出空间;但有了土地不能直接开始画,因为没有画板对吧,所以Bitmap相当于画布或者画纸,用于覆盖在地面上,方便我们进行画图;Graphics就是画图的手;Pen就是画笔很简单;但是有了画笔,不知道笔的粗细和颜色,那么Brush就是用来定义这两者的。
int width = pBox_home.Width; //获取PictureBox的宽高
int height = pBox_home.Height;
map_bg = new Bitmap(width, height); //设置要涂改的背景
mGraphics = Graphics.FromImage(map_bg); //设置设置画笔
pBox_home.Image = map_bg; //添加背景
mBrush = new SolidBrush(Color.Blue);
mPen_outer = new Pen(mBrush, 2);
mBrush = new SolidBrush(Color.White);
mPen_inner = new Pen(mBrush, 2);
2.在C#语言中,Y轴是朝下的
3.Graphics中的 DrawEllipse(Pen pen, int x, int y, int width, int height) 是我们主要用到的画圆函数
他将椭圆和标准圆整合到一起。说下每个参数的意思:
pen:用来画椭圆(圆)的笔
x:画圆的x坐标
y:画圆的y坐标
width:长轴
height:短轴(不清楚椭圆的长轴,短轴可以自行百度下~)
在这个函数中有2个坑,看清楚了别陷阱去了!
①这里的 x 和 y 指的是将整个圆(椭圆)包含在其中的最小的正方形(矩形)的最左上角那个点。意思就是如果我们想在(100,100)这个点画一个半径为20的圆的话,必须要减去圆的半径,在(90,90)上调用这个函数才能达到效果,如下图:
②然后是 width 和 height,指的是长轴和短轴,换做是圆则长轴和短轴相等,但这个长度量实际上指的是直径,不是半径啊!记清楚了哟!
public void drawCircle(Pen pen, PointF point, float radius) //自定义画圆函数
{
//将想要画的位置减去半径radius, 以及radius*2 代表的就是①和②的意思
Rectangle mRect = new Rectangle((int)(point.X - radius), (int)(point.Y - radius), (int)(radius * 2), (int)(radius * 2));
mGraphics.DrawEllipse(pen, mRect);
}
难点1:跑圈时内部安全区动态创建问题
玩过吃鸡的朋友都知道,在一定时间到后开始跑圈,内外两个圆重合时,随机加载下一个安全区,但这个安全区不能超出外圆原来的界限,必须把整个新的安全区包含在内,那么如何来实现这一点,如下图:
灰色圆圈的半径为 R1-R2,实际上内部安全区的新圆心只能在灰色圆圈上及以内产生,如果在图上绿色随机产生点的画,那么画出来的新安全区就超出外圈的界限了。
///
/// 在圆心为point,半径为radius的圆内,产生一个半径为radius_inner的圆的圆心
///
/// 外圆圆心
/// 外圆半径
/// 内圆半径
/// 内圆圆心
public PointF PointOfRandom(PointF point, float radius_outer, float radius_inner)
{
int x = random.Next((int)(point.X - (radius_outer - radius_inner)), (int)(point.X + (radius_outer - radius_inner)));
int y = random.Next((int)(point.Y - (radius_outer - radius_inner)), (int)(point.Y + (radius_outer - radius_inner)));
while (!isInRegion(x - point.X, y - point.Y, radius_outer - radius_inner))
{
x = random.Next((int)(point.X - (radius_outer - radius_inner)), (int)(point.X + (radius_outer - radius_inner)));
y = random.Next((int)(point.Y - (radius_outer - radius_inner)), (int)(point.Y + (radius_outer - radius_inner)));
}
PointF p = new PointF(x, y);
return p;
}
///
///
///
/// 与大圆圆心的x方向偏移量
/// 与大圆圆心的y方向偏移量
/// 大圆与小圆半径的差
/// 判断点是否在范围内
public bool isInRegion(float x_off, float y_off, float distance)
{
if (x_off * x_off + y_off * y_off <= distance * distance)
{
return true;
}
return false;
}
难点2:毒圈和安全域相内切问题
在缩圈时,外部毒圈不断缩小,有那么个时刻,内圈和外圈相内切即有且仅有一个点重合,这是的状态为内切,如下图:
那么这个重合点必定在外圈和内圈圆心所在的直线上,相信这个不难理解。那么此刻的判定条件就是 相差距离 = 0,即R2+两圆心距离 = R1。
///
/// 判断两个圆是否重合,或者是相内切
///
/// 外圆圆心
/// 外圆半径
/// 内圆圆心
/// 内圆半径
/// 是否相内切
public bool isIntersect(PointF p_outer, float r_outer, PointF p_inner, float r_inner)
{
//判定条件:两圆心的距离 + r_inner = r_outer
float distance = (float)Math.Sqrt((p_outer.X - p_inner.X) * (p_outer.X - p_inner.X) + (p_outer.Y - p_inner.Y) * (p_outer.Y - p_inner.Y));
if (distance + r_inner >= r_outer)
{
return true;
}
return false;
}
难点3:内切后的运行状态?
实际上在内切后,外圈的圆心以直线的方式不断向内圈圆心靠近,这条直线就是外圈圆心和内圈圆心所在的直线,并且外圈半径依旧在不断变小,如下图:
直线的斜率和直线方程在图上,我就不做过多解释,相信大家能看懂。值得一提的是,在人为设定外圈半径每次减少1个单位长度的情况下,即外圈圆心在所在直线上向内圈圆心移动1个单位,所以 x_off^2 + y_off^2 = 1,那么在知道斜率k和直角三角形中斜边为1的情况下就能够求出每次外圈圆心 x 和 y的移动距离。那么这个过程的结束条件就是:外圈半径r1 = 内圈半径r2。
if (mRadius_outer != mRadius_inner) //外圈和内圈圆心重合,半径相同
{
// k = y/x
// y = kx
// x^2+y^2 = 1
// x^2 = 1/(k^2+1)
float k = (mPoint_outer.Y - mPoint_inner.Y) / (mPoint_outer.X - mPoint_inner.X);
float x_off = 1 * (float)Math.Sqrt(1 / (k * k + 1));
// 通过mPoint_outer和mPoint_inner的x坐标来判断此时外圆圆心要移动的是该 + x_off(x轴偏移量)还是 -x_off
mPoint_outer.X += 1 * (mPoint_outer.X < mPoint_inner.X ? 1 : -1) * x_off;
// 知道变化后的外圈圆心的x坐标,和直线方程来求对应的y坐标
mPoint_outer.Y = k * (mPoint_outer.X - mPoint_inner.X) + mPoint_inner.Y;
mDrawHelper.drawCircle(mPen_outer, mPoint_outer, mRadius_outer);
mDrawHelper.drawCircle(mPen_inner, mPoint_inner, mRadius_inner);
}
至此,所有的问题都解决了,快去撸一个你自己的吃鸡跑圈游戏吧~(滑稽脸)
github地址:https://github.com/lawler61/chicken