i need some help with WPF
I'm getting started with WPF and wanted to make a simple snake game with the following classes:
GameBoard : Window
{
private Game game;
public GameBoard(){ this.game = new Game(this); }
public Rectangle rectangleFactory(int x, int y)
{
Rectangle rect = new Rectangle();
rect.Width = 10;
rect.Height = 10;
GameArea.Children.Add(rect); /// My Canvas is called GameArea
Canvas.SetLeft(rect, 10 * x);
Canvas.SetTop(rect, 10 * y);
return rect;
}
public void rectangleBin(Rectangle)
{
GameArea.Children.Remove(rect);
}
}
Thing{
private GameBoard Board;
private System.Windows.Shapes.Rectangle rect;
private int[] coordinaters;
public Thing(GameBoard board, int[] coordinates)
{
this.Board = board;
Draw();
}
public Draw(){ rect = Board.rectangleFactory(coordinates);}
public Dispose(){ Board.rectangleBin(rect)}
}
SnakeSegment : Thing
{
SnakeSegment next;
public SnakeSegment(GameBoard board, int[] coordinates) : base(board, coordinates) { }
}
Apple : Thing()
{
public Apple(GameBoard board, int[] coordinates) : base(board, coordinates) { }
public void newApple()
{
Dispose();
getNewCoordinate();
Draw();
}
}
Game
{
private GameBoard Board;
private Apple apple;
Private Snake snake;
private static System.timers.timer timer
public Game(GameBoard board)
{
this.Board = board;
this.apple = new Apple(board, int start_coordinates);
this.snake = new snake(this);
this.timer = new Timer(100);
timer.Elapsed += new ElapsedEventHandler(update_game);
}
public void update_game(object o, ElapsedEventArgs s){ snake.move_forward(); }
}
Snake
{
private SnakeSegmet Head;
Private SnakeSegment Tail
private GameBoard Board;
public Snake(Game game)
{
this.Board = game.Board;
this.Head = new SnakeSegment(Board, start_cooridantse)
this.Tail = Head;
}
private void AddSegment()
{
Head.Next = new SnakeSegment(Board, newCoordinates);
Head = Head.Next;
}
}
There is of course more but i left it out in the interest of readability.
When i start the game it draws the game area and adds an apple and an initial rectangle for the snake, so far so good
But then i update the game and things go south
my next call to SnakeSegment constructor in Head.Next = new SnakeSegment()
ends in rectangleFactory when calling new Rectangle()
the program doesn't crash, but it topples the current stack and nothing more is executed untinl the timer raises an other event
ive tried to troubleshoot and reroute the flow a bit and if i instead call Apple::newApple() the same thing happens when i get to GameArea.Children.Remove(rect);
Im completely new to WPF and this really gives me a headache, if anybody could explain why it happens id be very grateful.
Regards, Tobias
If you want to write your program like that, use WinForms. If you want to use WPF, you'll need to pretty much start again from scratch. WPF is a data-centric language... we manipulate data objects, not UI objects.
Using WPF, you'd need to think about your game objects as a collection of data objects. The board would then be the UI container control that you data bind your collection to. You would then define what each object should look like in a DataTemplate in the XAML. After binding the location properties of the data objects to the location properties of the UI objects you could then move the objects in the UI by changing the location properties of the data objects from code.
Having said all that, I think that you'll find that WPF is not a good Framework for making games. Its main strengths are its data binding capabilities and rich UI possibilities, but it's not very efficient. You'll need a good graphics card to get the most out of it.
Related
I am trying to create sort of a radar. Radar is VisualCollection that consists of 360 DrawingVisual's (which represent radar beams). Radar is placed on Viewbox.
class Radar : FrameworkElement
{
private VisualCollection visuals;
private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here
public Radar()
{
visuals = new VisualCollection(this);
for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
{
DrawingVisual dv = new DrawingVisual();
visuals.Add(dv);
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
}
}
DrawingVisual line = new DrawingVisual();
visuals.Add(line);
// DISCRETES_AMOUNT is about 500
this.Width = DISCRETES_AMOUNT * 2;
this.Height = DISCRETES_AMOUNT * 2;
}
public void Draw(int beamIndex, Brush brush)
{
using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
{
dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
}
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
}
Each DrawingVisual has precalculated geometry for DrawingContext.DrawGeometry(brush, pen, geometry). Pen is null and brush is a LinearGradientBrush with about 500 GradientStops. The brush gets updated every few milliseconds, lets say 16 ms for this example. And that is what gets laggy. Here goes the overall logic.
In MainWindow() constructor I create the radar and start a background thread:
private Radar radar;
public MainWindow()
{
InitializeComponent();
radar = new Radar();
viewbox.Child = radar;
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
In Run() method there is an infinite loop, where random brush is generated, Dispatcher.Invoke() is called and a delay for 16 ms is set:
private int beamIndex = 0;
private Random r = new Random();
private const int turnsPerMinute = 20;
private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
private long deltaDelay = delay;
public void Run()
{
int beginTime = Environment.TickCount;
while (true)
{
GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
{
byte color = (byte)r.Next(255);
gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
}
LinearGradientBrush lgb = new LinearGradientBrush(gsc);
lgb.StartPoint = Beam.GradientStarts[beamIndex];
lgb.EndPoint = Beam.GradientStops[beamIndex];
lgb.Freeze();
viewbox.Dispatcher.Invoke(new Action( () =>
{
radar.Draw(beamIndex, lgb);
}));
beamIndex++;
if (beamIndex >= BEAM_POSITIONS_AMOUNT)
{
beamIndex = 0;
}
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
}
}
Every Invoke() call it performs one simple thing: dc.DrawGeometry(), which redraws the beam under current beamIndex. However, sometimes it seems, like before UI updates, radar.Draw() is called few times and instead of drawing 1 beam per 16 ms, it draws 2-4 beams per 32-64 ms. And it is disturbing. I really want to achieve smooth movement. I need one beam to get drawn per exact period of time. Not this random stuff. This is the list of what I have tried so far (nothing helped):
placing radar in Canvas;
using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting different Thread Priorities;
using different ways of implementing delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
changing LinearGradientBrush to predefined SolidColorBrush;
using BeginInvoke() instead of Invoke() and changing Dispatcher Priorities;
using InvalidateVisuals() and ugly DoEvents();
using BitmapCache, WriteableBitmap and RenderTargetBitmap (using DrawingContext.DrawImage(bitmap);
working with 360 Polygon objects instead of 360 DrawingVisuals. This way I could avoid using Invoke() method. Polygon.FillProperty of each polygon was bound to ObservableCollection, and INotifyPropertyChanged was implemented. So simple line of code {brushCollection[beamIndex] = (new created and frozen brush)} led to polygon FillProperty update and UI was getting redrawn. But still no smooth movement;
probably there were few more little workarounds I could forget about.
What I did not try:
use tools to draw 3D (Viewport) to draw 2D radar;
...
So, this is it. I am begging for help.
EDIT: These lags are not about PC resources - without delay radar can do about 5 full circles per second (moving pretty fast). Most likely it is something about multithread/UI/Dispatcher or something else that I am yet to understand.
EDIT2: Attaching an .exe file so you could see what is actually going on: https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) did not help aswell.
For smooth WPF animations you should make use of the
CompositionTarget.Rendering event.
No need for a thread or messing with the dispatcher. The event will automatically be fired before each new frame, similar to HTML's requestAnimationFrame().
In the event update your WPF scene and you're done!
There is a complete example available on MSDN.
You can check some graphics bottleneck using the WPF Performance Suite:
http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx
Perforator is the tool that will show you performance issues. Maybe you are using a low performance VGA card?
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
The sequence above blocks the thread. Use instead "await Task.Delay(...)" which doesn't block the thread like its counterpart Thread.Sleep(...).
Is it possible to change a variable in another scene in unity. I have a script right now that has the user pick 5 heroes and those 5 heroes get saved to a array, but in order for the game to run how i want it, that array will be in another scene and I'm not sure how to go about saving the five heroes data to an array in another scene. I can do it all in one scene but 2 scenes would be more efficient. Here's my code:
using UnityEngine;
using System.Collections;
public class HeroChooser : MonoBehaviour {
public static GameObject Archer;
GameObject Berserker;
GameObject Rouge;
GameObject Warrior;
GameObject Mage;
GameObject MainCamera;
public int counter = 0;
public bool archerOn = false;
public bool berserkerOn = false;
public bool rougeOn = false;
public bool mageOn = false;
public bool warriorOn = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnGUI(){
if(archerOn == false){
if (GUI.Button (new Rect(50,0,50,50), "Archer")){
Archer = GameObject.Find("Archer");
MainCamera = GameObject.Find("Main Camera");
HeroArraySaver heroArraySaver = MainCamera.GetComponent<HeroArraySaver>();
heroArraySaver.array[counter] = Archer;
archerOn = true;
counter++;
}
}
Its saying that: Static member HeroArraySaver.array cannot be accessed with an instance reference, qualify it with a type name instead im not sure how to go about fixing it.
A simple way would be to create an empty GameObject and attach a script/MonoBehaviour to that which holds your data. To make it persist you would have to call DontDestroyOnLoad() on that GameObject. This will ensure your GameObject will hang around when moving to a different scene.
So something like:
GameObject myPersistentDataObject = new GameObject("myPersistentDataObject");
MyDataClass data_class = myPersistentDataObject.AddComponent<MyDataClass>();
//set your data to whatever you need to maintain
And in your Awake of your MyDataClass you'd do something like
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
Then in your other scene you can simply find your GameObject again and retrieve its data from the attached component.
Assuming you have integer IDs for the heroes, simply store them in a static variable:
public class GlobalData {
public static int[] heroIds;
}
Static variables can be accessed from any scene and will persist as long as your game runs. The same technique works for strings or enums.
I want to make an app that has Silverlight menus but the game part of the app is XNA. I am trying to get the Silverlight/XNA split working using the code from this example game and the method of doing XNA rendering in Silverlight here. Combining these 2 tutorials I have source code that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Controls;
using System.Windows.Navigation;
namespace FYP
{
public partial class GamePage : PhoneApplicationPage
{
GameTimer timer;
SpriteBatch spriteBatch;
Texture2D ballTexture;
IList<Ball> balls = new List<Ball>();
bool touching = false;
public GamePage(ContentManager contentManager)
{
InitializeComponent();
//base.Initialize();
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
//timer.Update += OnUpdate;
//timer.Draw += OnDraw;
// TODO: use this.Content to load your game content here
ballTexture = contentManager.Load<Texture2D>("Ball");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
timer.Start();
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
// Stop the timer
timer.Stop();
}
private void OnUpdate(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//this.Exit();
// TODO: Add your update logic here
//base.Update(gameTime);
HandleTouches();
UpdateBalls();
}
private void OnDraw(GameTime gameTime)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White);
// TODO: Add your drawing code here
foreach (Ball ball in balls)
{
ball.Draw(spriteBatch);
}
//base.Draw(gameTime);
}
private void HandleTouches()
{
TouchCollection touches = TouchPanel.GetState();
if (!touching && touches.Count > 0)
{
touching = true;
Random random = new Random(DateTime.Now.Millisecond);
Color ballColor = new Color(random.Next(255), random.Next(255), random.Next(255));
Vector2 velocity = new Vector2((random.NextDouble() > .5 ? -1 : 1) * random.Next(9), (random.NextDouble() > .5 ? -1 : 1) * random.Next(9)) + Vector2.UnitX + Vector2.UnitY;
//Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, (float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Height / 2);
Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, 200);
float radius = 25f * (float)random.NextDouble() + 5f;
balls.Add(new Ball(this, ballColor, ballTexture, center, velocity, radius));
}
else if (touches.Count == 0)
{
touching = false;
}
}
private void UpdateBalls()
{
foreach (Ball ball in balls)
{
ball.Update();
}
}
}
}
I do not understand how base works, I've had to comment out base.initialize, update and draw however base.OnNavigatedFrom works.
Also, should I be able to get my code to work in theory? I find it very complicated and although reading about XNA/Silverlight to be possible I can't find any source code where people have successfully combined XNA and Silverlight into the same app.
these videos will help you out to understand XNA and SilverLight/XNA combined platform
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11a-XNA-for-Windows-Phone--Part-1
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11b-XNA-for-Windows-Phone--Part-2
Theoretically XNA and SilverLight XNA Combined platform are pretty much the same, just a difference of bits and pieces, You can even ask XNA to render some SilverLight Control, which will make it easier to handle some button event in your game.
Hope this helps
How to get current mouse coordination on the screen?
I know only Mouse.GetPosition() which get mousePosition of element, but I want to get the coordination without using element.
Or in pure WPF use PointToScreen.
Sample helper method:
// Gets the absolute mouse position, relative to screen
Point GetMousePos() => _window.PointToScreen(Mouse.GetPosition(_window));
To follow up on Rachel's answer.
Here's two ways in which you can get Mouse Screen Coordinates in WPF.
1.Using Windows Forms. Add a reference to System.Windows.Forms
public static Point GetMousePositionWindowsForms()
{
var point = Control.MousePosition;
return new Point(point.X, point.Y);
}
2.Using Win32
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
var w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
Do you want coordinates relative to the screen or the application?
If it's within the application just use:
Mouse.GetPosition(Application.Current.MainWindow);
If not, I believe you can add a reference to System.Windows.Forms and use:
System.Windows.Forms.Control.MousePosition;
If you try a lot of these answers out on different resolutions, computers with multiple monitors, etc. you may find that they don't work reliably. This is because you need to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all your monitors. Something like this...(where "this" is a WPF window).
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
public System.Windows.Point GetMousePosition()
{
var point = Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
This works without having to use forms or import any DLLs:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Gets the current mouse position on screen
/// </summary>
private Point GetMousePosition()
{
// Position of the mouse relative to the window
var position = Mouse.GetPosition(Window);
// Add the window position
return new Point(position.X + Window.Left, position.Y + Window.Top);
}
You may use combination of TimerDispatcher (WPF Timer analog) and Windows "Hooks" to catch cursor position from operational system.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT pPoint);
Point is a light struct. It contains only X, Y fields.
public MainWindow()
{
InitializeComponent();
DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += new EventHandler(timer_tick);
dt.Interval = new TimeSpan(0,0,0,0, 50);
dt.Start();
}
private void timer_tick(object sender, EventArgs e)
{
POINT pnt;
GetCursorPos(out pnt);
current_x_box.Text = (pnt.X).ToString();
current_y_box.Text = (pnt.Y).ToString();
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
This solution is also resolving the problem with too often or too infrequent parameter reading so you can adjust it by yourself. But remember about WPF method overload with one arg which is representing ticks not milliseconds.
TimeSpan(50); //ticks
If you're looking for a 1 liner, this does well.
new Point(Mouse.GetPosition(mWindow).X + mWindow.Left, Mouse.GetPosition(mWindow).Y + mWindow.Top)
The + mWindow.Left and + mWindow.Top makes sure the position is in the right place even when the user drags the window around.
Mouse.GetPosition(mWindow) gives you the mouse position relative to the parameter of your choice.
mWindow.PointToScreen() convert the position to a point relative to the screen.
So mWindow.PointToScreen(Mouse.GetPosition(mWindow)) gives you the mouse position relative to the screen, assuming that mWindow is a window(actually, any class derived from System.Windows.Media.Visual will have this function), if you are using this inside a WPF window class, this should work.
I wanna use this code
Point PointA;
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) {
PointA = e.MouseDevice.GetPosition(sender as UIElement);
}
private void Button_Click(object sender, RoutedEventArgs e) {
// use PointA Here
}
I am playing around with some stuff in Silverlight, and I am trying to dynamically draw a curved line between two other objects on a <Canvas/>. I tried doing something like this:
public partial class MainNodeConnection : UserControl
{
private MainNode _sourceNode;
public MainNode SourceNode
{
get { return _sourceNode; }
set { _sourceNode = value; }
}
private ChildNode _targetNode;
public ChildNode TargetNode
{
get { return _targetNode; }
set { _targetNode = value; }
}
private double _sourceX;
private double _sourceY;
private double _targetX;
private double _targetY;
private Path _connection;
public MainNodeConnection()
{
InitializeComponent();
_connection = new Path();
this.Content = _connection;
}
public void UpdateLocations()
{
_sourceX = Canvas.GetLeft(_sourceNode) + (SourceNode.Width/2);
_sourceY = Canvas.GetTop(_sourceNode) + (SourceNode.Height/2);
_targetX = Canvas.GetLeft(_targetNode);
_targetY = Canvas.GetTop(_targetNode);
string pathData = String.Format("M {0},{1} C {2},{3} {4},{5}", _sourceX, _sourceY, _targetX - _sourceX, _targetY - _sourceX, _targetX, _targetY);
PathGeometry geoData = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = new Point(_sourceX, _sourceY);
BezierSegment pseg = new BezierSegment();
pseg.Point1 = new Point(_targetX - _sourceX, _targetY - _sourceY);
pFigure.Segments.Add(pseg);
geoData.Figures.Add(pFigure);
_connection.Stroke = new SolidColorBrush(Colors.Black);
_connection.StrokeThickness = 1;
_connection.Data = geoData;
this.Content = _connection;
}
}
and I built the objects on the <Canvas/> like this:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
MainNodeConnection mnc = new MainNodeConnection();
mnc.Width = 300;
mnc.Height = 300;
Canvas.SetLeft(mnc, Canvas.GetLeft(mainNode1));
Canvas.SetTop(mnc, Canvas.GetTop(mainNode1));
mnc.SourceNode = mainNode1;
mnc.TargetNode = childNode1;
nodeCanvas.Children.Add(mnc);
mnc.UpdateLocations();
}
}
the problem I have is I can't get the line to show up. Can anyone spot what I'm doing wrong, or is there a different/better way to do this?
You're missing Point2 and Point3 in the Bezier curve.
From the help file:
A cubic Bezier curve is defined by
four points: a start point, an end
point (Point3), and two control points
(Point1 and Point2). The BezierSegment
class does not contain a property for
the starting point of the curve; it
only defines the end point. The
beginning point of the curve is the
current point of the PathFigure to
which the BezierSegment is added.
The two control points of a cubic
Bezier curve behave like magnets,
attracting portions of what would
otherwise be a straight line toward
themselves and producing a curve. The
first control point, Point1, affects
the beginning portion of the curve;
the second control point, Point2,
affects the ending portion of the
curve. Note that the curve doesn't
necessarily pass through either of the
control points; each control point
moves its portion of the line toward
itself, but not through itself.