I have been working on a 3D endless runner game for some time when I faced this issue. During sideways movement, after switching each lane, my character just jumps back (-z axis) a couple of decimals which is very noticeable for someone who's playing the game; feeling like he's teleporting backward a few decimals. The issue came up after adding this simple code transform.position = locationAfterChangingLane; which stopped the overflowing of sideways movement. It did stop overflowing but made this annoying bug.
(code line situated in the isChangingLane if statement in update method.)
Here's my script (full, also I have commented where the above code line for easy reference.)
//Variables for Lane switching
private bool isChangingLane = false;
private Vector3 locationAfterChanginLane = Vector3.zero;
private Vector3 sideWayMovementDistance = Vector3.right * 2f; // This might be the case that triggers abnormal movements
private float sideWaySpeed = 6f;
public enum Lane
{
Left,
Right,
Center
}
public enum MoveDirection
{
Left,
Right,
None
}
Lane currentLane = Lane.Center;
void Update()
{
currentBaseState = anim.GetCurrentAnimatorStateInfo(0);
if (controller.isGrounded)
{
verticalVelocity = -0.5f;
if (currentBaseState.fullPathHash == locoState)
{
if (Input.GetButtonDown("Jump"))
{
verticalVelocity = 18f;
anim.SetBool("Jump", true);
}
else if (Input.GetKeyDown(KeyCode.S))
{
anim.SetBool("Slide", true);
}
}
MoveLeftRight(); // This is the method to move right and left.
if (isChangingLane)
{
if (Math.Abs(transform.position.x - locationAfterChanginLane.x) < 0.1f)
{
isChangingLane = false;
moveVector.x = 0;
transform.position = locationAfterChangingLane; // This is the code which throws this issue.
}
}
}
}
private void MoveLeftRight()
{
MoveDirection requestedMoveDirection = MoveDirection.None;
if (Input.GetKeyDown(KeyCode.A) && !isChangingLane)
{
requestedMoveDirection = MoveDirection.Left;
isChangingLane = true;
}
else if (Input.GetKeyDown(KeyCode.D) && !isChangingLane)
{
requestedMoveDirection = MoveDirection.Right;
isChangingLane = true;
}
switch (requestedMoveDirection)
{
case MoveDirection.Right:
if (currentLane == Lane.Right)
{
Debug.Log("Right Lane");
break; //Do nothing when in right lane.
}
else if (currentLane == Lane.Center)
{
locationAfterChanginLane = transform.position + sideWayMovementDistance;
moveVector.x = +sideWaySpeed;
currentLane = Lane.Right;
Debug.Log("Center --> Right");
}
else if (currentLane == Lane.Left)
{
locationAfterChanginLane = transform.position + sideWayMovementDistance;
moveVector.x = +sideWaySpeed;
currentLane = Lane.Center;
Debug.Log("Left --> Center");
}
break;
case MoveDirection.Left:
if (currentLane == Lane.Left)
{
Debug.Log("Left Lane");
break; //Do nothing when in left lane.
}
else if (currentLane == Lane.Center)
{
locationAfterChanginLane = transform.position - sideWayMovementDistance;
moveVector.x = -sideWaySpeed;
currentLane = Lane.Left;
Debug.Log("Center --> Left");
}
else if (currentLane == Lane.Right)
{
locationAfterChanginLane = transform.position - sideWayMovementDistance;
moveVector.x = -sideWaySpeed;
currentLane = Lane.Center;
Debug.Log("Right --> Center");
}
break;
}
}
Help would be greatly appreciated. How do I solve this? Also can you kindly explain why this happens? Many thanks!
It looks like you have a non-instantaneous movement animation as your character changes lane. That's good, but it also looks like you save the destination point at the moment that someone presses left or right. This position is stored in memory, and then about 0.5 sec later when they arrive at that lane fully, it reuses that position - which has stayed behind them.
A quick fix would be, once the lane movement animation has ended, just change locationAfterChangingLane's Z component to match the player's current Z component. Or, instead of saving and setting that vector, apply a Math.Max/Math.Min operation to the player's location so that they don't go past a certain maximum.
Another side note: I've seen some people run into math issues when playing their infinite runner for more than a few minutes because all their numbers are entering high ranges. It's possible that you'd want to consider simply having sections of the world simply "slide past" a still protagonist and be recycled behind him, so that even after 20 minutes of playing he's still at (0, 0, 0)
Related
For developing a side-scrolling platform 2D game I want to implement a moving camera class, the reason of using the class instead of moving the whole map is that I'll have to use too many objects at once witch will cause a lag. I cannot let that happen.
There's a nice algorithm for handling the camera, when player is moving further than the width of the screen then camera moves on players direction until he is once again in the middle of the screen, I've been working several days for making this algorithm work however there's been no success.
// Main
public class Camera
{
protected float _zoom;
protected Matrix _transform;
protected Matrix _inverseTransform;
//The zoom scalar (1.0f = 100% zoom level)
public float Zoom
{
get { return _zoom; }
set { _zoom = value; }
}
// Camera View Matrix Property
public Matrix Transform
{
get { return _transform; }
set { _transform = value; }
}
// Inverse of the view matrix,
// can be used to get
// objects screen coordinates
// from its object coordinates
public Matrix InverseTransform
{
get { return _inverseTransform; }
}
public Vector2 Pos;
// Constructor
public Camera()
{
_zoom = 2.4f;
Pos = new Vector2(0, 0);
}
// Update
public void Update(GameTime gameTime)
{
//Clamp zoom value
_zoom = MathHelper.Clamp(_zoom, 0.0f, 10.0f);
//Create view matrix
_transform = Matrix.CreateScale(new Vector3(_zoom, _zoom, 1)) *
Matrix.CreateTranslation(Pos.X, Pos.Y, 0);
//Update inverse matrix
_inverseTransform = Matrix.Invert(_transform);
}
}
This is the camera class I made for handling the screen, it's main purpose is to resize the screen, more precisely to zoom in and out whenever I want to change my screen, (Title screen, Playing screen, Game over, and like that.)
Moving the camera is quite simple with keys, like this.
if (keyState.IsKeyDown(Keys.D))
Cam.Pos.X -= 20;
if (keyState.IsKeyDown(Keys.A))
Cam.Pos.X += 20;
if (keyState.IsKeyDown(Keys.S))
Cam.Pos.Y -= 20;
if (keyState.IsKeyDown(Keys.W))
Cam.Pos.Y += 20;
And ofc. the drawing method witch apply the camera.
spriteBatch.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend, null, null, null, null, Cam.Transform);
Here comes the part when I stop, so what I want to do is make something like 2 2D rooms. By Room I mean the place where I usually place objects. like this "Vector2(74, 63)" So I want to create a place where I could draw items that would stick to the screen and wouldn't move, and make the screen bounds that would make my algorithm to work, witch will be always on screen and as an addition it will check if one of the borders of the screen "room" reaches the certain coordinates of the map "room".
I think that the reason for that would be obvious because I don't want player to move camera outside the map when he reaches the wall, otherwise the player would already see a part of the next map where he will be transformed.
The reason of drawing both maps next to each other is again to reduce the loading time so player wouldn't have to wait for playing the next map.
Alright, so I've run into more troubles than I expected so I'll add extra information and will start with the player class:
// Main
public class Player
{
public Texture2D AureliusTexture;
public Vector2 position;
public Vector2 velocity;
public Vector2 PosForTheCam; // Variable that holds value for moving the camera
protected Vector2 dimensions;
protected CollisionPath attachedPath;
const float GRAVITY = 18.0f;
const float WALK_VELOCITY = 120f;
const float JUMP_VELOCITY = -425.0f;
// Constructor
public Player()
{
dimensions = new Vector2(23, 46);
position = new Vector2(50, 770);
}
public void Update(float deltaSeconds, List<CollisionPath> collisionPaths)
{
#region Input handling
KeyboardState keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Left))
{
velocity.X = -WALK_VELOCITY;
}
else if (keyState.IsKeyDown(Keys.Right))
{
velocity.X = WALK_VELOCITY;
}
else
{
velocity.X = 0;
}
if (attachedPath != null && keyState.IsKeyDown(Keys.Space))
{
velocity.Y = JUMP_VELOCITY;
attachedPath = null;
}
velocity.Y += GRAVITY;
#endregion
#region Region of handling the camera based on Player
PosForTheCam.X = velocity.X;
#endregion
#region Collision checking
if (velocity.Y >= 0)
{
if (attachedPath != null)
{
position.X += velocity.X * deltaSeconds;
position.Y = attachedPath.InterpolateY(position.X) - dimensions.Y / 2;
velocity.Y = 0;
if (position.X < attachedPath.MinimumX || position.X > attachedPath.MaximumX)
{
attachedPath = null;
}
}
else
{
Vector2 footPosition = position + new Vector2(0, dimensions.Y / 2);
Vector2 expectedFootPosition = footPosition + velocity * deltaSeconds;
CollisionPath landablePath = null;
float landablePosition = float.MaxValue;
foreach (CollisionPath path in collisionPaths)
{
if (expectedFootPosition.X >= path.MinimumX && expectedFootPosition.X <= path.MaximumX)
{
float pathOldY = path.InterpolateY(footPosition.X);
float pathNewY = path.InterpolateY(expectedFootPosition.X);
if (footPosition.Y <= pathOldY && expectedFootPosition.Y >= pathNewY && pathNewY < landablePosition)
{
landablePath = path;
landablePosition = pathNewY;
}
}
}
if (landablePath != null)
{
velocity.Y = 0;
footPosition.Y = landablePosition;
attachedPath = landablePath;
position.X += velocity.X * deltaSeconds;
position.Y = footPosition.Y - dimensions.Y / 2;
}
else
{
position = position + velocity * deltaSeconds;
}
}
}
else
{
position += velocity * deltaSeconds;
attachedPath = null;
}
#endregion
}
}
So I state it clear that I asked my friend to do most of it because I wanted to handle the gravity and the slopes so we made it work similar like in Unity. And he happened to know how to do that.
And so I'll add the Update method that handles the camera from the Main Class.
MM.Update(gameTime); // Map Managher update function for map handling
Cam.Update(gameTime); // Camera update
Cam.Zoom = 2.4f; // Sets the zoom level for the title screen
// Takes the start position for camera in map and then turns off the update
// so the camera position can be changed. Else it would just keep an infinite
// loop and we couldn't change the camera.
if (StartInNewRoom)
{
Cam.Pos = MM.CameraPosition; // Applys the camera position value from the map manager class
StartInNewRoom = false;
}
I am unsure how to handle the camera, like I used your method and the result often ended up that camera moves by itself or it doesn't move at all.
If you don't want objects to move with the camera like a HUD you need a second spriteBatch.Begin() without your camera matrix which you draw after your actual scene.
To make the camera not move out of the map you could use some kind of collision detection. Just calculate the right border of your camera. It depends where the origin of your camera is.
Is your camera matrix working like this? Because the position should be negative or it will move in the wrong direction.
This is how mine looks like.
return Matrix.CreateTranslation(new Vector3(-camera.position.X, -camera.position.Y, 0)) *
Matrix.CreateRotationZ(Rotation) * Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(Viewport.Width * 0.5f, Viewport.Height * 0.5f, 0));
Viewport.Width/Height * 0.5 centers you camera.
You can also apply this behind your Pos.X/Y
To Camera follows player
public void Update(Player player)
{
//Clamp zoom value
_zoom = MathHelper.Clamp(_zoom, 0.0f, 10.0f);
//Create view matrix
_transform = Matrix.CreateScale(new Vector3(_zoom, _zoom, 1)) *
Matrix.CreateTranslation(player.Pos.X, player.Pos.Y, 0);
//Update inverse matrix
_inverseTransform = Matrix.Invert(_transform);
}
Pulling my hair out with this one, hope its not something silly. Below is a snippet of code from a program. When leftwalker.x == 150 he should gotoAndPlay the standstill animation but it only plays the first frame, the previous animation runs fine. Any ideas?
var data =
{
images: ["images/ste_basic_wand.png"],
frames: {width:64, height:64},
animations:
{
// start, end, next, speed
walkright: [143,151,"walkright",1.18],
walkleft: [118,125,"walkleft",1.18],
stand:[39,45,"stand",0.08],
standstill:[26,27, "standstill", 1.2]
}
};
var spriteSheet = new createjs.SpriteSheet(data);
leftwalker = new createjs.Sprite(spriteSheet);
leftwalker.name = "lefty";
leftwalker.framerate = 30;
leftwalker.x = 100;
leftwalker.y = 100;
leftwalker.currentFrame = 0;
leftwalker.scaleY = leftwalker.scaleX = 2;
leftwalker.gotoAndPlay("walkright");
stage.addChild(leftwalker);
createjs.Ticker.setFPS(10);
createjs.Ticker.addEventListener("tick", tick);
}
function tick(event) {
if(container.x < 150)
{
container.x += 5;
}
if(leftwalker.x < 150)
{
leftwalker.x += 2;
}
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
}
// if (circle.x > stage.canvas.width) { circle.x = 0; }
stage.update(event); // important!!
}
The reason this happens is because you are calling gotoAndPlay("standstill") during the tick. Once you reach 150, your sprite stops moving, so it is perpetually at x=150. This means each tick will tell it to gotoAndPlay the same frame, resulting it in being "stuck".
Figured out a way around it, still not sure why but easaljs didn't like the code
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
}
When I change it so the char isn't stuck on point 150 (move him to 151) the animation begins. I also slowed the animation on the standing still down to make it seem more real but this isn't related to the fix I didn't post this code.
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
if(leftwalker.x < 180)
{
leftwalker.x += 1;
}
}
as basis for my GPS functionality I've taken HelloMap3D Example of Nutiteq (Thx Jaak) and I adapted to show my current GPS position light different of this example, so, no growing yelow circles but a fix blue translucent circle with a center point as my current Position and works fine except the update. It should erase the past position if location is changed, so that
this update happens as in the example in the method onLocationChanged
This is the code in my Main Activity
protected void initGps(final MyLocationCircle locationCircle) {
final Projection proj = mapView.getLayers().getBaseLayer().getProjection();
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
locationCircle.setLocation(proj, location);
locationCircle.setVisible(true);
}
// Another Methods...
}
}
I have adapted MyLocationCircle Class like this
public void update() {
//Draw center with a drawable
Bitmap bitmapPosition = BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_home);
PointStyle pointStyle = PointStyle.builder().setBitmap(bitmapPosition).setColor(Color.BLUE).build();
// Create/update Point
if ( point == null ) {
point = new Point(circlePos, null, pointStyle, null);
layer.add(point);
} else { // We just have to change the Position to actual Position
point.setMapPos(circlePos);
}
point.setVisible(visible);
// Build closed circle
circleVerts.clear();
for (float tsj = 0; tsj <= 360; tsj += 360 / NR_OF_CIRCLE_VERTS) {
MapPos mapPos = new MapPos(circleScale * Math.cos(tsj * Const.DEG_TO_RAD) + circlePos.x, circleScale * Math.sin(tsj * Const.DEG_TO_RAD) + circlePos.y);
circleVerts.add(mapPos);
}
// Create/update line
if (circle == null) {
LineStyle lineStyle = LineStyle.builder().setWidth(0.05f).setColor(Color.BLUE).build();
PolygonStyle polygonStyle = PolygonStyle.builder().setColor(Color.BLUE & 0x80FFFFFF).setLineStyle(lineStyle).build();//0xE0FFFF
circle = new Polygon(circleVerts, null, polygonStyle, circle_data);
layer.add(circle);
} else {
circle.setVertexList(circleVerts);
}
circle.setVisible(visible);
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public void setLocation(Projection proj, Location location) {
circlePos = proj.fromWgs84(location.getLongitude(), location.getLatitude());
projectionScale = (float) proj.getBounds().getWidth();
circleRadius = location.getAccuracy();
// Here is the most important modification
update();
}
So, each time our Position changes is called onLocationChanged(Location location) Method and there will be called locationCircle.setLocation(location) and last there, it will be called update called.
The questions are, What am I making wrong? and How can I solve it?
Thank you in advance.
You create and add new circle with every update. You should reuse single one, just update vertexes with setVertexList(). In particular this line should be outside onLocationChanged cycle, somewhere in initGPS perhaps:
circle = new Polygon(circleVerts, null, polygonStyle, circle_data);
I want to implement on my project this sketch about dragging a box.
Instead of one box, I have several circles each drawn with different coordinates in the form
ellipse(lemma23.x, lemma23.y, diameterLemma23, diameterLemma23);
ellipse(law3.x, law3.y, diameterLaw3, diameterLaw3);
ellipse(law2.x, law2.y, diameterLaw2, diameterLaw2);
How do I test if the cursor is on one of the circles?
Here's a screen shot of my project:
I want to test when cursor is on (or near) a circle so that I can change its position by dragging.
The entire sketch is in pastebin.
I started with the example in your question. There are a few main differences for drawing multiple shapes:
You have to check whether the cursor is within each shape.
You have to draw each shape.
You may want to worry about overlapping, but I did not.
In the following code, I build upon the example directly although I removed the few lines which change the color of the box when clicked and I reorganized the code into the MovingEllipse class so that multiple ellipses can be drawn easily. (This code draws two ellipses.)
Note that the code in draw() checks the position of the mouse for each ellipse, however, I suppose this could be improved upon (i.e. perhaps by creating an array of ellipse positions and looping over the array). Also, for this code to work properly, mousePressed and mouseReleased methods need to be copied like the mouseDragged method. (I was trying to make my example brief.)
Anyway, this is one way to draw multiple ellipses and detect which one should be moved. Hope it helps!
int esize = 75;
MovingEllipse e1 = new MovingEllipse(0.0, 0.0, esize, 0.0, 0.0);
MovingEllipse e2 = new MovingEllipse(0.0, 0.0, esize, 0.0, 0.0);
void setup()
{
size(640, 360);
e1.eX = width/2.0; // Center of ellipse 1.
e1.eY = height/2.0;
e2.eX = width/4.0; // Center of ellipse 2.
e2.eY = height/4.0;
}
void draw()
{
background(0);
// Test if the cursor is over the ellipse.
if (mouseX > e1.eX-esize && mouseX < e1.eX+esize &&
mouseY > e1.eY-esize && mouseY < e1.eY+esize) {
e1.overBox = true;
e2.overBox = false;
} else if (mouseX > e2.eX-esize && mouseX < e2.eX+esize &&
mouseY > e2.eY-esize && mouseY < e2.eY+esize) {
e2.overBox = true;
e1.overBox = false;
} else {
e1.overBox = false;
e2.overBox = false;
}
// Draw the ellipse(s).
e1.update(e1.eX, e1.eY, e1.overBox);
e2.update(e2.eX, e2.eY, e2.overBox);
}
void mouseDragged() {
e1.mouseDragged();
e2.mouseDragged();
}
// Don't forget to repeat this for mousePressed and mouseReleased!
// ...
class MovingEllipse {
float eX, eY; // Position of ellipse.
int eSize; // Radius. For a circle use eSize for both x and y radii.
float xOffset, yOffset; // Where user clicked minus center of ellipse.
boolean locked, overBox; // Flags used for determining if the ellipse should move.
MovingEllipse (float ex, float ey, int esize, float xoff, float yoff) {
eX = ex;
eY = ey;
eSize = esize;
xOffset = xoff;
yOffset = yoff;
}
void update(float ex, float ey, boolean over) {
eX = ex;
eY = ey;
overBox = over;
// Draw the ellipse. By default, (eX, eY) represents the center of the ellipse.
ellipse(eX, eY, eSize, eSize);
}
void mousePressed() {
if(overBox) {
locked = true;
} else {
locked = false;
}
xOffset = mouseX-eX;
yOffset = mouseY-eY;
}
void mouseDragged() {
if(locked) {
eX = mouseX-xOffset;
eY = mouseY-yOffset;
}
}
void mouseReleased(){
locked = false;
}
}
Just check if the distance between the cursor and the centre of the circle is within the hit radius. The hit radius could be made larger than the radius of the circle to catch near hits.
Is it possible that the axis scale outside the graph could be scale using the mouse event "mouse_down and hold" and move up or down in y-axis the same with the x-axis move left or right? ex. when I trigger MouseDownEvent and hold the x-axis scale 0.6 or at the space along with that scale and move it to the right, scale should scroll depend in the chartfraction? could you post an example? Thanks in advance!
Separately panning and zooming Y axises can be achieved using the mouse events of ZedGraph: MouseDownEvent, MouseMoveEvent, MouseUpEvent and MouseWheel events (credits go to a colleague of mine).
It works with multiple GraphPanes and multiple Y axises.
The MouseMoveEvent is used to shift the Min and the Max of an Y axis when the mouse is moved while its button is pressed. If not, it is used to get the reference of the Y axis object the mouse is hovering on.
The MouseDownEvent is used to initiate an axis pan operation.
The MouseWheel is used to perform a zoom on an Y axis.
And the MouseUpEvent is used to clean things when zooming and panning operations are finished.
Here is the code :
// The axis that is currently hovered by the mouse
YAxis hoveredYAxis;
// The graphpane that contains the axis
GraphPane foundPane;
// The scale of the axis before it is panned
double movedYAxisMin;
double movedYAxisMax;
// The Y on the axis when the panning operation is starting
float movedYAxisStartY;
void z_MouseWheel(object sender, MouseEventArgs e)
{
if (hoveredYAxis != null)
{
var direction = e.Delta < 1 ? -.05f : .05f;
var increment = direction * (hoveredYAxis.Scale.Max - hoveredYAxis.Scale.Min);
var newMin = hoveredYAxis.Scale.Min + increment;
var newMax = hoveredYAxis.Scale.Max - increment;
hoveredYAxis.Scale.Min = newMin;
hoveredYAxis.Scale.Max = newMax;
foundPane.AxisChange();
z.Invalidate();
}
}
bool z_MouseUpEvent(ZedGraphControl sender, MouseEventArgs e)
{
hoveredYAxis = null;
return false;
}
bool z_MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e)
{
var pt = e.Location;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (hoveredYAxis != null)
{
var yOffset = hoveredYAxis.Scale.ReverseTransform(pt.Y) - hoveredYAxis.Scale.ReverseTransform(movedYAxisStartY);
hoveredYAxis.Scale.Min = movedYAxisMin - yOffset;
hoveredYAxis.Scale.Max = movedYAxisMax - yOffset;
sender.Invalidate();
return true;
}
}
else
{
var foundObject = findZedGraphObject(null);
hoveredYAxis = foundObject as YAxis;
if (hoveredYAxis != null)
{
z.Cursor = Cursors.SizeNS;
return true;
}
else
{
if (z.IsShowPointValues)
{
z.Cursor = Cursors.Cross;
return false;
}
else
{
z.Cursor = Cursors.Default;
return true;
}
}
}
return false;
}
bool z_MouseDownEvent(ZedGraphControl sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (hoveredYAxis != null)
{
movedYAxisStartY = e.Location.Y;
movedYAxisMin = hoveredYAxis.Scale.Min;
movedYAxisMax = hoveredYAxis.Scale.Max;
return true;
}
}
return false;
}
This is a helper that factorizes a bit the object find operations of ZedGraph.
object findZedGraphObject(GraphPane pane = null)
{
var pt = zgc.PointToClient(Control.MousePosition);
if (pane == null)
{
foundPane = zgc.MasterPane.FindPane(pt);
if (foundPane != null)
{
object foundObject;
int forget;
using (var g = zgc.CreateGraphics())
if (foundPane.FindNearestObject(pt, g, out foundObject, out forget))
return foundObject;
}
}
return null;
}
If I understand your question correctly, here's my response:
zedgraph has got an in-built function called "Pan", you could change the scale of x & y axis.
Place the cursor within the 'chart area'
Hold the 'ctrl' button & move the mouse towards x & y directions to change the scale.
you could get back to original state by 'Un-Pan' (Context Menu)
Cheers..:)
Do You want to create a ScrollBar?
zedGraphControl1.IsShowHScrollbar = true;
//Set borders for the scale
zedGraphControl1.GraphPane.XAxis.Scale.Max = Xmax;
zedGraphControl1.GraphPane.XAxis.Scale.Min = Xmin;