I just got the book Head First C# 3rd Edition. In the first part of the book it gets you started creating a simple game where you make bouncing circles that you must avoid while clicking and dragging a "human" to safety. The coding involves C# as well as XAML for the graphics side. Everything works except for one thing. Instead of clicking and dragging the "human" if I click on it it will "follow" the cursor around and the game behaves as if the cursor itself is the human since that is where game over happens when I hit one of the bouncing circles. I cannot figure out what I am doing wrong. I have looked over the code line by line next to the book.....The only thing I can figure out is that this block of code.
private void playArea_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (humanCaptured)
{
Point pointerPosition = e.GetCurrentPoint(null).Position;
Point relativePosition = grid.TransformToVisual(playArea).TransformPoint(pointerPosition);
if ((Math.Abs(relativePosition.X - Canvas.GetLeft(human)) > human.ActualWidth * 20)
|| (Math.Abs(relativePosition.Y - Canvas.GetTop(human)) > human.ActualHeight * 20))
{
humanCaptured = false;
human.IsHitTestVisible = true;
}
else
{
Canvas.SetLeft(human, relativePosition.X - human.ActualWidth / 2);
Canvas.SetTop(human, relativePosition.Y - human.ActualHeight / 2);
}
}
}
The book says to put the human actual width and height to 3. But if I do that and try to click on the person in the actual game it won't even register the cursor at all. but if I put that at 20 or above and I click on the human as I said earlier it starts following my cursor around and the rest of the game behaves normally.
Here is the code that is supposed to make the cursor be able to "click" and "drag" the human.
private void human_PointerPressed(object sender, PointerRoutedEventArgs e)
{
if (enemyTimer.IsEnabled)
{
humanCaptured = true;
human.IsHitTestVisible = false;
}
}
And here is the related XAML code this is supposed to be working with to make it click and drag but instead is making it follow my cursor around the area:
<StackPanel x:Name="human" Orientation="Vertical" Canvas.Left="17" Canvas.Top="15"
PointerPressed="human_PointerPressed" >
<Ellipse Fill="White" Height="10" Stroke="White" Width="10"/>
<Rectangle Fill="White" Height="25" Stroke="White" Width="10" />
</StackPanel>
Everything else seems to be working right. I have checked the code in the book over and over and gone to their site. Nothing I did is different as far as I can tell. If anyone can help it will be greatly appreciated.
After reading the Book "Head First C#, 3rd Edition"
I would say the problem isn't your code...
It's more about how he detects if a Human get captured, because he does not check if "human" collide with a "enemy" he just did some Pointerevent's which will get triggert if a Pointer (your Mouse) will enter an "enemy" and not if your "human" collide with him...
Related
The Goal
I want to build an audio visualizer based on 1/3 octaves like this one in an WinUI3 app:
I already have the sampling part, I only need to deal with the rendering and visual part.
What Have I tried?
While looking at the WinUI3 Gallery App, I've found that you can modify rectangles in a nice way using either a translation or a scale modifier(and it's also animated):
<!-- Automatically animate changes to Scale -->
<Rectangle x:Name="rectangle" Width="50" Height="50" Scale="1,1,1" >
<Rectangle.ScaleTransition>
<Vector3Transition />
</Rectangle.ScaleTransition>
<Rectangle.TranslationTransition>
<Vector3Transition />
</Rectangle.TranslationTransition>
</Rectangle>
using System.Numerics;
private void button_Click(object sender, RoutedEventArgs e)
{
rectangle.Scale = new Vector3(1, 1, 1);
rectangle.Translation = new Vector3(1, 1, 1);
}
Firstly I tried to have 11 very long rectangles and move them upwards using the value. The problem with this approach is that the rectangles cannot be bigger than the container. You can have a default scale that actually overrides this rule but it lead to blurry corners.
So I've decided to try to scale the Y value of the rectangles by the respective value for each octave.
What I have
Right now I have 11 rectangles in a StackPanel named "rectangles", each rectangle looking like this
<Rectangle
Width="50"
Height="40"
Margin="7,10,7,0"
Fill="{ThemeResource SystemAccentColor}"
RadiusX="7"
RadiusY="3"
Scale="1,0,1">
<Rectangle.ScaleTransition>
<Vector3Transition />
</Rectangle.ScaleTransition>
<Rectangle.TranslationTransition>
<Vector3Transition />
</Rectangle.TranslationTransition>
</Rectangle>
And a timer that runs this code every 10ms:
private void FetchOctaves(object state)
{
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
if (!Audio.IsPlaying()) return;
var octaves = Audio.GetFrequencyData().ToList();
for (var i = 0; i < octaves.Count; i++)
{
// Map() normalizes a value from a source min and max to a target min and max
var yScale = Map(octaves[i], -60f, 20f, 1f, 12f);
rectangles.Children[i].Scale = new Vector3(1, yScale, 1);
}
}
);
}
And the result looks like this:
The Problem(s)
As you can figure, my solution is quite hardcoded and it leads to some problems:
1. I cannot find a maximum scale value in order to appeal to all window sizes.
Bigger scale values make my rectangles go out of the container's bounds.
One solution to this would be to have a property MaxScale that adapts whenever the size of the container changes(I already modify the rectangle's width whenever this happens). But this doesn't solve the next issue:
2. When met with big Y-scale values my rectangle's rounded corners start to look blurry.
This probably happens because of how the app handles scale(by zooming in).
What I want
I want to know if there's a way to resize the actual height of the rectangles at runtime.
This would solve both of my problems and would allow me to make it responsive to different window sizes.
If that is not possible, I would appreciate if you would point me towards another solution to achieve this goal using WinUI3.
Thanks in advance!
I have a WPF Touch app (MS Surface, VS 2017, .NET 4.7) that shows a number of simple shapes (lines, rects, circles) above an image, all on on a canvas. I'm having a trouble with selecting my ManipulationContainer for touch.
Roughly what I have is this:
<ScrollViewer x:Name="MyViewer"
ManipulationStarting="Viewer_ManipulationStarting">
<Canvas x:Name="MyCanvas">
<Image x:Name="MyImage" Source={Binding MyImageSource}"/>
<ItemsControl x:Name="MyShapes" ItemsSource="{Binding MyShapes}"/>
</Canvas>
</ScrollViewer>
I want let my user do two different things with touch.
With one finger they can draw new shapes (relative to the canvas)
With two fingers they can zoom the entire canvas (relative to the scrollviewer)
So if the user is drawing a new shape, then ManipulationContainer must be "MyCanvas". But if the user is zooming the whole scene, then the ManipulationContainer must be "MyViewer" (because in that case I'm changing the LayoutTransform of the whole canvas).
So I need (it would seem) to select from one of two different manipulation containers depending on which of these operations is happening. But I can't seem to figure out how. Below is what I have (which doesn't work) where I choose the container. in the ManipulationStarting handler.
private void Scene_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
// If zooming, container is the parent viewer. Otherwise it's
// drawing a shape relative to the canvas.
if (e.Manipulators.Count() >= 2)
e.ManipulationContainer = MyViewer; // Zooming whole canvas
else
e.ManipulationContainer = MyCanvas; // Drawing on canvas
e.Handled = true;
}
As you likely guessed, when I first get this, I always get just one manipulator; The user is extremely unlikely to have his very first touch be with two fingers exactly simultaneously. So my code always thinks I'm drawing a shape.
It's only later on, in the ManipulationDelta that I start getting more than one manipulator. But it's too late then to choose a container. It's already been chosen. And if I check the number of manipulators then, my coordinates, delta, and origin are all relative to the very thing I'm trying to move. It makes the zooming jump all over the place.
I don't know what to do about this. I have to let my user zoom at any time so I do not have the option of forcing them to choose a "zoom tool".
I'm sure I'm missing something simple but I don't know what it is and I'd like to avoid blind alleys. Any suggestions?
I have created a flashlight app for Windows Phone. As i already acces the camera to enable the flash, i thought it would be a nice feature to also allow the user to view the previewframes, and be able to zoom in to get a better look at things.
I have a canvas with a composite transform (as i also have to rotate the camera to match potrait mode). I use a slider (started with pinch, but i want one hand control) to increase and decrease the scale of the canvas, effectively zooming in and out. So far so good and on windows Phone 8 (HTC 8x) this works fine, i can go crazy on the slider and nothing bad happens.
On Windows Phone 8.1 (Lumia 930) however, at seemingly randoms moments when moving the slider, the app just closes down and in the debug output the following line is shown:
The program '[3164] TaskHost.exe' has exited with code -1073741819 (0xc0000005) 'Access violation'.
I have no idea how to find the cause of this as the moment of the crash seems to be different, its just Always related to the scaling of the canvas. I am hoping someone can point me in the right direction so i can solve this.
The canvas in XAML:
<Canvas x:Name="viewfinderCanvas">
<Canvas.Background>
<VideoBrush x:Name="viewfinderBrush"/>
</Canvas.Background>
<Canvas.RenderTransform>
<CompositeTransform x:Name="rt" />
</Canvas.RenderTransform>
</Canvas>
And my code for setting the scale:
private void Zoom_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (canZoom && !isFocusing)
{
zoomValue = Math.Round(Zoom.Value, 1);
rt.ScaleX = zoomValue * staticValue;
rt.ScaleY = zoomValue * staticValue;
ZoomValue.Text = zoomValue.ToString() + " x";
}
}
Where canzoom is set to true on the loaded event of the mainpage, and isFocusing is set to true upon the autofocus.
I have a weird issue I have no ideea how to fix.
I'm creating a color picker from scratch. I'm doing so by creating a set of user controls and putting them together in a "master control".
When a user drags over the hue picker, for example, I handle the mouse down, move and up (inside the hue picker user control).
A simple bool dictates what happens, as far as the actual moving goes.
//Mouse down
_isDrag = true;
//Mouse Move
if(!_isDrag) return;
//Moving the position indicator shape thingy
//Calculating the hue
//Mouse Up
_isDrag = false;
But, if the mouse up occurs outside of the bounds of the hue picker, the mouse up event doesn't fire.
Thus, when the user returns to the area of the hue picker, the shape indicator thingy runs rampart.
I am certain an answer lies somewhere but I'm afraid my searching skills are not up to the task.
I've no idea what to look for.
Thank you for your time.
Solution:
private bool _isDrag;
//Request Mouse capture for the Container
private void MsDown(object sender, MouseButtonEventArgs e)
{
_isDrag = true;
Mouse.Capture(MainContainer);
}
//Release Mouse capture
private void MsUp(object sender, MouseButtonEventArgs e)
{
_isDrag = false;
Mouse.Capture(null);
}
//Move the handle vertically along the main container, with respect to it's width - so it's centered.
private void MsMove(object sender, MouseEventArgs e)
{
if (!_isDrag) return;
Canvas.SetTop(Handle, e.GetPosition(ContentRow).Y - Handle.ActualHeight / 2);
}
Thank you for your answer!
Edit 2:
Following up on my issue. While Capture basically did the trick, I noticed that, if fast dragging outside the bounds of the user control , sometimes the handle would get stuck close to the edge. If I would move the mouse slowly, this wouldn't happen. Weird. Also, I could never reach 0 and .ActualHeight
So I'll post my fix here, just in case another dude encounters this issue.
I split my grid like this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="7"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="7"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="7"></RowDefinition>
</Grid.RowDefinitions>
With 7 being half the size of my handle (a circle).
The content area (the actual area you can interact with visually) is in the middle cell (in a separate grid with false hit test visibility).
Spanning the entire main grid is an invisible rectangle used for hit testing.
And to move the handle
private void MoveHandle()
{
_pos.X = _pos.X - Handle.ActualWidth/2;
_pos.Y = _pos.Y - Handle.ActualHeight / 2;
//this is just to be sure. I'm paranoid. Being a color picker, these actually matter a lot.
_pos.X = Math.Max(Math.Min(_pos.X, RectColor.ActualWidth - Handle.ActualWidth/2), -Handle.ActualWidth / 2);
_pos.Y = Math.Max(Math.Min(_pos.Y, RectColor.ActualHeight -Handle.ActualWidth/2), -Handle.ActualHeight/2);
Canvas.SetLeft(Handle, _pos.X);
Canvas.SetTop(Handle, _pos.Y);
}
I have no idea why the previous code I had almost worked. It's basically the same thing as before. But, somehow, this performs a million times better. Good luck!
The search term you are looking for is Mouse Capture. Capture in your MouseDown, and you can get mouse events even after the mouse leaves your control.
I currently programming the classic arcade game Asteroids in C# WPF to get some practice.
I have run into an issue that I cannot seem to solve.
I am running into issues while generating asteroids and adding to the canvas element which contains all of my game objects.
I have a generateAsteroids method that gets called every 20 milliseconds along over methods that update the player ships position and so on. generateAsteroids method performs various calculations (comments in function) to determine how many asteroids to add to the asteroidCollection list. This all works fine.
The issue arises when I try to add the asteroids Polygon object to the games canvas.
I get the following error: "ArugementException was unhandled by user code: Specified Visual is already a child of another Visual or the root of a CompositionTarget"
Now I understand what this means (I think), all of the Asteroid objects are called "asteroid" which obviously is not ideal, Ive researched and found that you cannot dynamically create variable names for objects on the fly.
I have tried giving the Polygon a dynamic name each time one gets added to the canvas.
Can anyone in the know of this issue please help me out?
Ive added all the code that I think is relevant, please let me know if you need to see more.
Thanks
C#:
public void drawAsteroid(Asteroid theAsteroid)
{
// entityShape is a Polygon object
theAsteroid.entityShape.Name = "asteroid" + this.asteroidsAdded.ToString();
theAsteroid.entityShape.Stroke = Brushes.White;
theAsteroid.entityShape.StrokeThickness = 2;
theAsteroid.entityShape.Points = theAsteroid.getEntityDimensions();
gameCanvas.Children.Add(theAsteroid.entityShape);
}
// Called every 20 milliseconds by method that updates the game canvas. Possibly quite inefficient
public void generateAsteroids()
{
// Number of asteroids to add to the collection = the length of the game so far / 3, then subtract the amount of asteroids that have already been added
int asteroidNum = Convert.ToInt32(Math.Ceiling((DateTime.Now - gameStartTime).TotalSeconds / 3));
asteroidNum -= asteroidsAdded;
for (int i = 0; i <= asteroidNum; i ++)
{
asteroidCollection.Add(new Asteroid());
this.asteroidsAdded += 1;
}
foreach (Asteroid asteroid in asteroidCollection)
{
drawAsteroid(asteroid);
}
}
XAML:
<Window x:Name="GameWindow" x:Class="AsteroidsAttempt2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="1000" Height="1000" HorizontalAlignment="Left" VerticalAlignment="Top" Loaded="GameWindow_Loaded">
<Canvas x:Name="GameCanvas" Focusable="True" IsEnabled="True" HorizontalAlignment="Left" Height="1000" VerticalAlignment="Top" Width="1000" KeyDown="GameCanvas_KeyDown" KeyUp="GameCanvas_KeyUp">
<Canvas.Background>
<ImageBrush ImageSource="D:\CPIT\BCPR283\Asteroids\Asteroids\AsteroidsAttempt2\Resources\SpaceBackground.jpg" Stretch="Fill"/>
</Canvas.Background>
</Canvas>
In each call of the drawAsteroid method you're adding all Polygons from the asteroidCollection to the Canvas, regardless if they have already been added or not. But you can't add the same object twice to the Children collection of a WPF Panel. That's why you get the exception (it has nothing to do with the Name).
Change your code like this:
if (!gameCanvas.Children.Contains(theAsteroid.entityShape))
{
gameCanvas.Children.Add(theAsteroid.entityShape);
}
Of course the code still lacks logic to remove Polygons from the Canvas that are no longer contained in the asteroidCollection. You will also have to add that.
And you don't need to set the Name of the Polygon objects at all, unless you want to access them by their name later.