right now I'm making a game and a character in it to move the player. I'm just a beginner about programming.
There are 8 buttons, and each button goes to a direction. For example, this my program for
private void btnUp_Click(object sender, EventArgs e)
{
//move up
y = y - 1;
MovePlayer();
UpdateLabelLocation();
}
public void MovePlayer()
{
picPlayer.Location = new Point(x, y);
}
public void UpdateLabelLocation()
{
lblLocation.Text = "Location: (" + x + ", " + y + ")";
}
I want to make it move when I press up, down, left or right keys. Also, if possible I want to make it so that when I press right and up at the same time, it triggers this:
private void btnRightUp_Click(object sender, EventArgs e)
{
//move player
y = y - 1;
x = x + 1;
MovePlayer();
UpdateLabelLocation();
}
I appreciate the help.
What you're essentially doing is creating your own simple game engine. As commentors have noted, you're better off using an existing game engine such as Unity. It's far easier and more liberating than going about it with WinForms.
That said, if you really want to continue with this, I strongly suggest you move the code in the Click handlers to a method. This reduces code duplication. i.e.
private void DownButton_Click(...)
{
MovePlayer(0, 1);
}
private void UpButton_Click(...)
{
MovePlayer(0, -1);
}
public void MovePlayer(float xStep, float yStep)
{
x += xStep;
y += yStep;
MovePlayer();
UpdateLabelLocation();
}
To move down and left you'd call MovePlayer(-1, 1);
To move up and right you'd call MovePlayer(1, -1);
Next you'll need to respond to KeyPress events. i.e.
public void Form_KeyPress(object sender, KeyPressEventArgs args)
{
switch (args.KeyChar) {
case 'a': // Left
args.Handled = true;
MovePlayer(-1, 0);
break;
case 'd': // Right
args.Handled = true;
MovePlayer(1, 0);
break;
case 'w': // Up
args.Handled = true;
MovePlayer(0, -1);
break;
case 's': // Down
args.Handled = true;
MovePlayer(0, 1);
break;
}
}
Note that if you have another control that accepts keyboard input(such as a TextBox), it will intercept the key press. To get around this, use KeyPreview to force the window to preview the input first. args.Handled = true prevents the event from routing to child controls after your code.
Unfortunately WinForms doesn't record multiple keys pressed at the same time, so using KeyPress alone isn't enough to handle corner movement. You can work around this by hooking onto KeyDown and KeyUp, but it's more trouble than it's worth.
Here's a more robust solution. Bear in mind the following isn't thread safe, so if you plan on introducing other threads you'll need to use appropriate locking.
HashSet<KeyCode> state = new HashSet<KeyCode>();
float speed = 120; // 120 pixels/second.
private void Form_KeyDown(object sender, KeyEventArgs args)
{
var key = args.KeyCode;
state.Add(key);
// Fire pressed when a key was up.
if (!state.Contains(key)) {
state.Add(key);
OnKeyPressed(key);
}
}
private void Form_KeyUp(object sender, KeyEventArgs args)
{
var key = args.KeyCode;
state.Remove(key);
// Fire release when a key was down.
if (state.Contains(key)) {
state.Remove(key);
OnKeyReleased(key);
}
}
// Runs when key was up, but pressed just now.
private void OnKeyPressed(KeyCode key)
{
// Trigger key-based actions.
}
// Runs when key was down, but released just now.
private void OnReleased(KeyCode key)
{
// Trigger key-based actions, but on release instead of press.
}
private bool IsDown(KeyCode key)
{
return state.Contains(key);
}
// Trigger this periodically, at least 20 times a second(ideally 60).
// An option to get you started is to use a windows timer, but
// eventually you'll want to use high precision timing instead.
private void Update()
{
var deltaTime = // Calculate the seconds that have passed since the last update.
// Describing it is out of the scope of this answer, but see the links below.
// Determine horizontal direction. Holding both
// A & D down cancels movement on the x-axis.
var directionX = 0;
if (IsDown(KeyCode.A)) {
directionX--;
}
if (IsDown(KeyCode.D)) {
directionX++;
}
// Determine vertical direction. Holding both
// W & S down cancels movement on the y-axis.
var directionY = 0;
if (IsDown(KeyCode.W)) {
directionY--;
}
if (IsDown(KeyCode.S)) {
directionY++;
}
// directionX & directionY should be normalized, but
// I leave that as an exercise for the reader.
var movement = speed * deltaTime;
var offsetX = directionX * movement;
var offsetY = directionY * movement;
MovePlayer(offsetX, offsetY);
}
As you can see, there's quite a bit involved. If you want more fine-grained timing, look into this article. Eventually you'll want to transition to a game loop, but that's yet another topic out of the scope of this answer.
Related
I am new to DirectX and Direct3D/2D etc and just currently running an experiment on whether to pursue making a cad viewer for a machine we have.
I am using the control from here Direct2dOnWPF to enable me to display Direct2D onto WPF window using SharpDX.
At the moment I have the control working and its loads a file and displays a drawing.
I have now created a camera and I have implemented zooming (to a degree) but my issue is with panning. The issue is that when panning I expect the drawing to move with the mouse but it doesn't. Small movements it kind of does but bigger movements cause the drawing to move beyond the mouse movement. Almost like the further I move the mouse in a single movement, the faster it moves.
Ok some code, the Direct2DControl is based on an Image control so I have access to mouse events etc. Here is the some of code on the control with mouse events and a timer. I tried a timer to detect when the mouse stopped as I found the panning would not stop when the mouse did.
// Timer to detect mouse stop
private Timer tmr;
public Direct2dControl()
{
//
// .... Init stuff
//
// Mouse panning
// get mouse position
MouseOrigin = CurrentMousePosition = new Point(0, 0);
tmr = new Timer { Interval = 50 };
tmr.Elapsed += Tmr_Elapsed;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (!DragIsOn)
{
DragIsOn = true;
}
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (DragIsOn)
{
DragIsOn = false;
DragStarted = false;
MouseOrigin = CurrentMousePosition = e.GetPosition(this);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!DragIsOn) return;
MouseMoved = true;
if (!DragStarted)
{
DragStarted = true;
MouseOrigin = CurrentMousePosition = e.GetPosition(this);
tmr.Start();
}
else
{
CurrentMousePosition = e.GetPosition(this);
var x = (float)(MouseOrigin.X - CurrentMousePosition.X);
var y = (float) (MouseOrigin.Y - CurrentMousePosition.Y);
cam.MoveCamera(cam.ScreenToWorld(new Vector2(x, y)));
tmr.Stop();
tmr.Start();
}
}
private void Tmr_Elapsed(object sender, ElapsedEventArgs e)
{
MouseOrigin = CurrentMousePosition;
tmr.Stop();
MouseMoved = false;
}
and the panning in camera class by moving the position.
public void MoveCamera(Vector2 cameraMovement)
{
Vector2 newPosition = Position + cameraMovement;
Position = newPosition;
}
public Matrix3x2 GetTransform3x2()
{
return TransformMatrix3x2;
}
private Matrix3x2 TransformMatrix3x2
{
get
{
return
Matrix3x2.Translation(new Vector2(-Position.X, -Position.Y)) *
Matrix3x2.Rotation(Rotation) *
Matrix3x2.Scaling(Zoom) *
Matrix3x2.Translation(new Vector2(Bounds.Width * 0.5f, Bounds.Height * 0.5f));
}
}
and finally at the start of the begin rendering I update the RenderTarget Transform
target.Transform = cam.GetTransform3x2();
I believe you're calculating the coordinates wrong. First, you need to set the MouseOrigin variable in OnLeftMouseButtonDown and don't modify it in any other method:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (!DragIsOn)
{
DragIsOn = true;
MouseOrigin = e.GetPosition(this);
// I don't know the type of your cam variable so the following is pseudo code
MouseOrigin.x -= cam.CurrentPosition.x;
MouseOrigin.y -= cam.CurrentPosition.y;
}
}
And modify OnMouseMove like this:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!DragIsOn) return;
MouseMoved = true;
var x = (float)(e.GetPosition(this).x - MouseOrigin.X);
var y = (float) (e.GetPosition(this).y - MouseOrigin.Y);
cam.MoveCamera(cam.ScreenToWorld(new Vector2(x, y)));
tmr.Stop();
tmr.Start();
}
The DragStarted and CurrentMousePosition variables are not needed.
Let me know if it works.
I have a co-routine that is triggered when the bool of a toggle button changes, when the bool is changed again that co-routine should be stopped and another one should start. This is my code:
public class thermoPowerControlPanel : MonoBehaviour {
private ThermoElectric thermo;
public bool toggleBool1;
public int temperature;
private int tempUp = 10;
private int tempDown = 1;
public thermoPowerControlPanel (){
temperature = 100;
}
public void turbine1State (bool toggleBool1) {
if (toggleBool1 == false) {
Debug.Log (toggleBool1);
Invoke("ReduceTemperatureEverySecond", 1f);
}
if (toggleBool1 == true) {
Debug.Log (toggleBool1);
Invoke("IncreaseTemperatureEverySecond", 1f);
}
}
private void ReduceTemperatureEverySecond()
{
if (toggleBool1 == true)
{
Debug.Log("I was told to stop reducing the temperature.");
return;
}
temperature = temperature - tempDown;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
private void IncreaseTemperatureEverySecond()
{
if (toggleBool1 == false)
{
Debug.Log("I was told to stop increasing the temperature.");
return;
}
temperature = temperature + tempUp;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
}
When the function turbine1State(bool t1) receives the first bool (false), the routine decreaseTemperatureEverySecond() starts but it stops immediately after, sending the Debug.Log message, it should keep reducing the temperature until the bool (activated by the toggle button) turned true.
.
Can you help?
It is this easy!
public Toggle tog; // DONT FORGET TO SET IN EDITOR
in Start ...
InvokeRepeating( "Temp", 1f, 1f );
... and then ...
private void Temp()
{
if (tog.isOn)
temperature = temperature + 1;
else
temperature = temperature - 1;
// also, ensure it is never outside of 0-100
temperature = Mathf.Clamp(temperature, 0,100);
}
If you ever need to "totally stop" that action (both up and down), just do this
CancelInvoke("Temp");
So easy!
NOTE purely FYI, the other pattern I explained is this:
bool some flag;
Invoke("Temp", 1f);
private void Temp()
{
if (some flag is tripped) stop doing this
.. do something ..
Invoke( ..myself again after a second .. )
}
In real life, it is usually better to "keep Invoking yourself" rather than use InvokeRepeating.
In this simple example, just use InvokeRepeating, and then CancelInvoke.
You can stop coroutine only by it name.
Just try some like StartCoroutine("increaseTemperature"); and then StopCoroutine("increaseTemperature");.
http://docs.unity3d.com/Manual/Coroutines.html
There are a number of ways to call StartCoroutine. You can "stop" a coroutines IF you start it with the "string" method, like this StartCoroutine("FunctionNameAsStringHere"); If you start it like this StartCoroutine(FunctionNameAsStringHere()); you cannot stop it by name.
(You can also access the actual enumerator to stop coroutines, but that is far beyond the scope of a beginner using coroutines.)
This my code for clapping hand gesture ,i use the result to set a robotic arm end effector to a 3d location for every clap)
i want the event handler to trigger just once for every clap gesture,
and reset for another clap gesture. but when i clap and my hands are close together, my event handler keeps firing!! please how do i correct this issue. could i use a reset method or something
[hand clap code]
float previousDistance = 0.0f;
private void MatchClappingGesture(Skeleton skeleton)
{
if (skeleton == null)
{
return;
}
if (skeleton.Joints[JointType.WristRight].TrackingState == JointTrackingState.Tracked && skeleton.Joints[JointType.WristLeft].TrackingState == JointTrackingState.Tracked)
{
float currentDistance = GetJointDistance(skeleton.Joints[JointType.WristRight], skeleton.Joints[JointType.WristLeft]);
{
if (currentDistance < 0.1f && previousDistance > 0.1f )
{
if (this.GestureRecognized != null)
{
this.GestureRecognized(this, new GestureEventArgs(RecognitionResult.Success));
previousDate = DateTime.Now;
}
}
previousDistance = currentDistance;
}
}
}
this is where i call the event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
kinect = KinectSensor.KinectSensors[0];
kinect.Start();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Could not find Kinect Camera: " + ex.Message);
}
kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
kinect.ColorStream.Enable(ColorImageFormat.RgbResolution1280x960Fps12);
kinect.SkeletonStream.Enable(new TransformSmoothParameters()
{
Correction = 0.5f,
JitterRadius = 0.05f,
MaxDeviationRadius = 0.05f,
Prediction = 0.5f,
Smoothing = 0.5f
});
kinect.AllFramesReady += Kinect_SkeletonAllFramesReady;
recognitionEngine = new GestureRecognitionEngine();
armEngine = new ArmControllerEngine();
recognitionEngine.GestureRecognized += new EventHandler<GestureEventArgs>(recognitionEngine_GestureRecognized);
}
event handler fires here
void recognitionEngine_GestureRecognized(object sender,
GestureEventArgs e)
{
//armEngine.setArm(raMove,port,servoId);
MessageBox.Show("HI");
}
the message box prints multiple time instead of just once!!please help
You must bear in mind that Kinect's measurements have a degree of inaccuracy. Also, in certain cases (like when the hands are together!!) that inaccuracy increases greatly, and the detected joint positions start jumping all over the place. Try it with the SDK's provided skeleton viewer and you'll see. This is often mitigated by using smoothing algorithms, but in your case that might not be the most adequate solution. Try increasing the distance you are using to detect the gesture, give it some 20cm or so (btw, what does your GetJointDistance returns? Meters? Then give it 0.2)
void wait(int a)
{
for (int i = 0; i < a; i++) ;
}
int count = 0;
void recognitionEngine_GestureRecognized(object sender, GestureEventArgs e)
{
wait(3);
clapShow();
}
//clap method
void clapShow()
{
count += 1;
if (count == 1)
{
MessageBox.Show("Gesture Capture Within >> " + time.Millisecond.ToString() + " milli Seconds");
}
count = 0 ;
}
}
I called the message box as a method every 3millsecs wait(3) so that slows down the the event handler.
Then set the count to "1" before a gesture then back to "0"after every clap gesture and it works pretty well!!
Note!.... The wait(X) method i created a simple loop that counts down to the value X. It can be any value based on the level of sensitivity level one desires. Thanks jose!!
i hope this helps someone..
I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]
foreach (var node in stack)
{
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
Thread.Sleep(500);
}
The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?
For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0
Edit:
Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.
The code for using a Thread was just shorter and I wanted to give him a solution quickly.
Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.
This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.
Use the timer_Tick function to update the position.
You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.
public partial class Form1 : Form
{
private Point pos = new Point(1,1);
private float[] vel = new float[2];
private Size bounds = new Size(20,20);
private Timer ticky = new Timer(); //System.Windows.Forms.Timer
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ticky.Interval = 20;
ticky.Tick += ticky_Tick;
vel[0] = 4; vel[1] = 0;
ticky.Start();
}
void ticky_Tick(object sender, EventArgs e)
{
updatePosition();
//This tells our form to repaint itself (and call the OnPaint method)
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
}
private void updatePosition()
{
pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
vel[1] += .5f; //Apply some gravity
if (pos.X + bounds.Width > this.ClientSize.Width)
{
vel[0] *= -1;
pos.X = this.ClientSize.Width - bounds.Width;
}
else if (pos.X < 0)
{
vel[0] *= -1;
pos.X = 0;
}
if (pos.Y + bounds.Height > this.ClientSize.Height)
{
vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
pos.Y = this.ClientSize.Height - bounds.Height;
}
else if (pos.Y < 0)
{
vel[1] *= -1;
pos.Y = 0;
}
}
}
Results:
You can use timers to do all sorts of delayed form drawing:
Original Solution:
//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;
new Thread(() => {
foreach (var node in stack)
{
//The invoke only needs to be used when updating GUI Elements
this.Invoke((MethodInvoker)delegate() {
//Everything inside of this Invoke runs on the GUI Thread
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
});
Thread.Sleep(500);
}
}).Start();
Solution in 2022:
await Task.Delay(500);
Things really are better these days.
I have a class project that uses Windows Forms to create a GUI that controls a second form. The second form is a DrawingForm with a bitmap. Using a backgroundworker, I am drawing random, continuous Bezier curves all over the bitmap. It is a simple program, so it is able to draw them quickly, hundreds per second. I would like to add a slider bar that would allow me to control how fast the lines paint. In other words, I dont want to set up each curve to be drawn on a timer, which would cause it to appear to stop and start hundreds of times a second. I have exhausted myself searching google, any tips on how to do this would be awesome. Thanks!
Edit: Here is a code snippet. This code is in my class for my drawing form. Its constructor is called from my main GUI/user control class.
// this is the code executed by the background thread
// it can run continously without hanging the user interface thread
// except that it draws to a bitmap (with the bMapDC) instead of to the form
private void backgroundWorkerDrawing_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100000000; i++)
{
if (scribbleOn == true)
{
curveColor = changeColor(curveColor);
Pen pen = new Pen(curveColor, penThickness);
if (i == 0) // initial curve should start in center, the rest of the points will be random
{
lastX = GUI.rand.Next(0, bMapWidth); //used to store the x coordinate where the curve ends
lastY = GUI.rand.Next(0, bMapHeight); //used to store the y coordinate where the curve ends
bMapDC.DrawBezier(pen, initialX, initialY, GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight),
GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight), lastX, lastY);
}
if (i > 0) // used for all curves after the first one.
{
int tempX = GUI.rand.Next(0, bMapWidth); //used to store the x coordinate where the curve ends
int tempY = GUI.rand.Next(0, bMapHeight); //used to store the y coordinate where the curve ends
bMapDC.DrawBezier(pen, lastX, lastY, GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight),
GUI.rand.Next(0, bMapWidth), GUI.rand.Next(0, bMapHeight), tempX, tempY);
lastX = tempX; // sets the last x coordinate of the last curve for next loop
lastY = tempY; // sets the last y coordinate of the last curve for next loop
}
pen.Dispose(); // free up resources from the pen object
}
else i = 0;
}
}
// timer event handler causes the form to be repreatedly invalidated
// This causes the paint event handler to keep going off,
// which causes the bMap that is continously being drawn to
// by the background thread to be continously redisplayed in the form.
// We will see other ways to do this that may be better.
private void timerInvalidate_Tick(object sender, EventArgs e)
{
Invalidate();
}
private void DrawingForm_Shown(object sender, EventArgs e)
{
lock (bMap)
{
bMapHeight = bMap.Height; // set the vars that keep track of the size of the bMap
bMapWidth = bMap.Width;
initialX = bMapWidth / 2; // start the curve at the center of the bMap
initialY = bMapHeight / 2;
bMapDC = Graphics.FromImage(bMap); // setup the DC (device context) to allow drawing to the bMap)
backgroundWorkerDrawing.RunWorkerAsync(); // start the background thread
timerInvalidate.Enabled = true; // start the timer that will cause periodic Invalidates
}
}
You can make thread and use sleep
private Thread SimulaciaArts;
public Animation(){
public SpleepValue { get; set;}
SimulaciaArts = new Thread(new ThreadStart(simuluj));
}
public void simuluj(){
//anything
Thread.Sleep(SleepValue);
}
and in gui you must use delegate
delegate void Invoker();
private void setSpeed()
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Invoker(setSpeed));
return;
}
Simulation.SleepValue=Speed;
}
Hope it is good.