Kinect 连线游戏
游戏的用户界面
查找最近的游戏者
每一次事件执行时,我们查找第一个合适的游戏者。程序不会锁定某一个游戏者。如果有两个游戏者,那么靠Kinect最近的那个会是活动的游戏者。这就是GetPrimarySkeleton的功能。如果没有活动的游戏者,手势图标就隐藏。否则,我们使用活动游戏者离Kinect最近的那只手作为控制。
private static Joint GetPrimaryHand(Skeleton skeleton)
{
Joint primaryHand = new Joint();
if (skeleton != null)
{
primaryHand = skeleton.Joints[JointType.HandLeft];
Joint righHand = skeleton.Joints[JointType.HandRight];
if (righHand.TrackingState != JointTrackingState.NotTracked)
{
if (primaryHand.TrackingState == JointTrackingState.NotTracked)
{
primaryHand = righHand;
}
else
{
if (primaryHand.Position.Z > righHand.Position.Z)
{
primaryHand = righHand;
}
}
}
}
return primaryHand;
}
private void TrackHand(Joint hand)
{
if (hand.TrackingState == JointTrackingState.NotTracked)
{
HandCursorElement.Visibility = Visibility.Collapsed;
}
else
{
HandCursorElement.Visibility = Visibility.Visible;
DepthImagePoint point = this.kinectDevice.MapSkeletonPointToDepth(hand.Position, this.kinectDevice.DepthStream.Format);
point.X = (int)((point.X * LayoutRoot.ActualWidth / kinectDevice.DepthStream.FrameWidth) - (HandCursorElement.ActualWidth / 2.0));
point.Y = (int)((point.Y * LayoutRoot.ActualHeight / kinectDevice.DepthStream.FrameHeight) - (HandCursorElement.ActualHeight / 2.0)); Canvas.SetLeft(HandCursorElement, point.X);
Canvas.SetTop(HandCursorElement, point.Y); if (hand.JointType == JointType.HandRight)
{
HandCursorScale.ScaleX = ;
}
else
{
HandCursorScale.ScaleX = -;
}
}
}
游戏逻辑实现
有效点击范围应该要比实际的UI元素大。这一点在Kinect或者其他触控设备上都是应该遵循的设计原则。如果用户移动到了这个点击区域,就可以认为用户点击到了这个目标点。
各种坐标空间及变换
骨骼数据是镜面对称的。在大多数情况下,应用是可行的,因为人对应于显示屏就应该是镜面对称。在上面的连线小游戏中,人对于与屏幕也应该是镜面对称,这样恰好模拟人的手势。但是在一些游戏中,角色代表实际的游戏者,可能角色是背对着游戏者的,这就是所谓的第三人称视角。在有些游戏中,这种镜像了的骨骼数据可能不好在UI上进行表现。一些应用或者游戏希望能够直面角色,不希望有这种镜像的效果。当游戏者挥动左手时也希望角色能够挥动左手。
protected const string KinectDevicePropertyName = "KinectDevice";
public static readonly DependencyProperty KinectDeviceProperty = DependencyProperty.Register(KinectDevicePropertyName, typeof(KinectSensor), typeof(SkeletonViewer), new PropertyMetadata(null, KinectDeviceChanged)); private static void KinectDeviceChanged(DependencyObject owner, DependencyPropertyChangedEventArgs e)
{
SkeletonViewer viewer = (SkeletonViewer)owner; if (e.OldValue != null)
{
((KinectSensor)e.OldValue).SkeletonFrameReady -= viewer.KinectDevice_SkeletonFrameReady;
viewer._FrameSkeletons = null;
} if (e.NewValue != null)
{
viewer.KinectDevice = (KinectSensor)e.NewValue;
viewer.KinectDevice.SkeletonFrameReady += viewer.KinectDevice_SkeletonFrameReady;
viewer._FrameSkeletons = new Skeleton[viewer.KinectDevice.SkeletonStream.FrameSkeletonArrayLength];
}
} public KinectSensor KinectDevice
{
get { return (KinectSensor)GetValue(KinectDeviceProperty); }
set { SetValue(KinectDeviceProperty, value); }
}
将这个自定义控件加到应用中很简单。由于是自定义控件,自需要在应用程序的XAML文件中声明自定义控件,然后在程序中给SkeletonViewer的KinectDevice赋值
namespace SkeletonGame
{
public partial class SkeletonViewer : UserControl
{
private readonly Brush[] _SkeletonBrushes = new Brush[] { Brushes.Black, Brushes.Crimson, Brushes.Indigo, Brushes.DodgerBlue, Brushes.Purple, Brushes.Pink };
private Skeleton[] _FrameSkeletons;
protected const string KinectDevicePropertyName = "KinectDevice";
public static readonly DependencyProperty KinectDeviceProperty = DependencyProperty.Register(KinectDevicePropertyName, typeof(KinectSensor), typeof(SkeletonViewer), new PropertyMetadata(null, KinectDeviceChanged));
// 依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象称为“依赖对象”。
// WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来。依赖对象的概念被DependencyObject类所实现,依赖属性的概念则由DependencyProperty类所实现 public KinectSensor KinectDevice
{
get { return (KinectSensor)GetValue(KinectDeviceProperty);}
set { SetValue(KinectDeviceProperty, value); }
} public SkeletonViewer()
{
InitializeComponent();
} private static void KinectDeviceChanged(DependencyObject owner, DependencyPropertyChangedEventArgs e)
{
SkeletonViewer viewer = (SkeletonViewer)owner; if (e.OldValue !=null) // 释放资源
{
((KinectSensor)e.OldValue).SkeletonFrameReady -= viewer.KinectDevice_SkeletonFrameReady;
viewer._FrameSkeletons = null;
} if (e.NewValue != null)
{
viewer.KinectDevice = (KinectSensor)e.NewValue; // Set
viewer.KinectDevice.SkeletonFrameReady += viewer.KinectDevice_SkeletonFrameReady;
viewer._FrameSkeletons = new Skeleton[viewer.KinectDevice.SkeletonStream.FrameSkeletonArrayLength];
}
} private void KinectDevice_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
SkeletonsPanel.Children.Clear();
JointInfoPanel.Children.Clear(); using (SkeletonFrame frame = e.OpenSkeletonFrame())
{
if (frame!=null)
{
if (this.IsEnabled)
{
frame.CopySkeletonDataTo(this._FrameSkeletons); for (int i = ; i < this._FrameSkeletons.Length;i++ )
{
DrawSkeleton(this._FrameSkeletons[i], this._SkeletonBrushes[i]); TrackJoint(this._FrameSkeletons[i].Joints[JointType.HandLeft], this._SkeletonBrushes[i]);
TrackJoint(this._FrameSkeletons[i].Joints[JointType.HandRight], this._SkeletonBrushes[i]);
}
}
}
}
} private void TrackJoint(Joint joint, Brush brush)
{
if (joint.TrackingState != JointTrackingState.NotTracked)
{
Canvas container = new Canvas();
Point jointPoint = GetJointPoint(joint); double z = joint.Position.Z; Ellipse element = new Ellipse();
element.Height = ;
element.Width = ;
element.Fill = brush;
Canvas.SetLeft(element, - (element.Width / ));
Canvas.SetTop(element, - (element.Height / ));
container.Children.Add(element); // 检测到手之后,添加椭圆 TextBlock positionText = new TextBlock();
positionText.Text = string.Format("<{0:0.00}, {1:0.00}, {2:0.00}>", jointPoint.X, jointPoint.Y, z);
positionText.Foreground = brush;
positionText.FontSize = ;
positionText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Canvas.SetLeft(positionText, );
Canvas.SetTop(positionText, );
container.Children.Add(positionText); // 用TextBlock来显示手坐标位置 Canvas.SetLeft(container, jointPoint.X);
Canvas.SetTop(container, jointPoint.Y); // 将Canvas移动到相应位置 JointInfoPanel.Children.Add(container);
}
} private void DrawSkeleton(Skeleton skeleton, Brush brush)
{
if (skeleton != null && skeleton.TrackingState == SkeletonTrackingState.Tracked)
{
//Draw head and torso
Polyline figure = CreateFigure(skeleton, brush, new[] { JointType.Head, JointType.ShoulderCenter, JointType.ShoulderLeft, JointType.Spine,
JointType.ShoulderRight, JointType.ShoulderCenter, JointType.HipCenter});
SkeletonsPanel.Children.Add(figure); figure = CreateFigure(skeleton, brush, new[] { JointType.HipLeft, JointType.HipRight });
SkeletonsPanel.Children.Add(figure); //Draw left leg
figure = CreateFigure(skeleton, brush, new[] { JointType.HipCenter, JointType.HipLeft, JointType.KneeLeft, JointType.AnkleLeft, JointType.FootLeft });
SkeletonsPanel.Children.Add(figure); //Draw right leg
figure = CreateFigure(skeleton, brush, new[] { JointType.HipCenter, JointType.HipRight, JointType.KneeRight, JointType.AnkleRight, JointType.FootRight });
SkeletonsPanel.Children.Add(figure); //Draw left arm
figure = CreateFigure(skeleton, brush, new[] { JointType.ShoulderLeft, JointType.ElbowLeft, JointType.WristLeft, JointType.HandLeft });
SkeletonsPanel.Children.Add(figure); //Draw right arm
figure = CreateFigure(skeleton, brush, new[] { JointType.ShoulderRight, JointType.ElbowRight, JointType.WristRight, JointType.HandRight });
SkeletonsPanel.Children.Add(figure);
}
} private Polyline CreateFigure(Skeleton skeleton, Brush brush, JointType[] joints)
{
Polyline figure = new Polyline(); figure.StrokeThickness = ;
figure.Stroke = brush; for (int i = ; i < joints.Length; i++)
{
figure.Points.Add(GetJointPoint(skeleton.Joints[joints[i]]));
} return figure;
} private Point GetJointPoint(Joint joint) // 骨骼点的坐标转化
{
DepthImagePoint point = this.KinectDevice.MapSkeletonPointToDepth(joint.Position, this.KinectDevice.DepthStream.Format);
point.X *= (int)this.LayoutRoot.ActualWidth / KinectDevice.DepthStream.FrameWidth;
point.Y *= (int)this.LayoutRoot.ActualHeight / KinectDevice.DepthStream.FrameHeight; return new Point(point.X, point.Y);
}
}
}
namespace SkeletonGame
{
public partial class MainWindow : Window
{
private KinectSensor kinectDevice;
private int puzzleDotIndex;
private Skeleton[] frameSkeletons;
private DotPuzzle puzzle; public KinectSensor KinectDevice
{
get { return this.kinectDevice; }
set
{
if (this.kinectDevice != value)
{
//Uninitialize
if (this.kinectDevice != null)
{
this.kinectDevice.Stop();
this.kinectDevice.SkeletonFrameReady -= KinectDevice_SkeletonFrameReady;
this.kinectDevice.SkeletonStream.Disable();
SkeletonViewerElement.KinectDevice = null;
this.frameSkeletons = null;
} this.kinectDevice = value; //Initialize
if (this.kinectDevice != null)
{
if (this.kinectDevice.Status == KinectStatus.Connected)
{
this.kinectDevice.SkeletonStream.Enable();
this.frameSkeletons = new Skeleton[this.kinectDevice.SkeletonStream.FrameSkeletonArrayLength];
SkeletonViewerElement.KinectDevice = this.KinectDevice;
this.kinectDevice.Start();
this.KinectDevice.SkeletonFrameReady += KinectDevice_SkeletonFrameReady;
}
}
}
}
} public MainWindow()
{
InitializeComponent();
puzzle = new DotPuzzle();
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, ));
this.puzzle.Dots.Add(new Point(, )); this.puzzleDotIndex = -; this.Loaded += (s, e) =>
{
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
this.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected); DrawPuzzle(this.puzzle);
};
} private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Initializing:
case KinectStatus.Connected:
case KinectStatus.NotPowered:
case KinectStatus.NotReady:
case KinectStatus.DeviceNotGenuine:
this.KinectDevice = e.Sensor;
break;
case KinectStatus.Disconnected:
//TODO: Give the user feedback to plug-in a Kinect device.
this.KinectDevice = null;
break;
default:
//TODO: Show an error state
break;
}
} private void KinectDevice_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
using (SkeletonFrame frame = e.OpenSkeletonFrame())
{
if (frame != null)
{
frame.CopySkeletonDataTo(this.frameSkeletons);
Skeleton skeleton = GetPrimarySkeleton(this.frameSkeletons); // this.frameSkeletons 会包含多个骨架 Skeleton[] dataSet2 = new Skeleton[this.frameSkeletons.Length];
frame.CopySkeletonDataTo(dataSet2); if (skeleton == null)
{
HandCursorElement.Visibility = Visibility.Collapsed;
}
else
{
Joint primaryHand = GetPrimaryHand(skeleton);
TrackHand(primaryHand);
TrackPuzzle(primaryHand.Position);
}
}
}
} private static Skeleton GetPrimarySkeleton(Skeleton[] skeletons)
{
Skeleton skeleton = null; if (skeletons != null)
{
//查找最近的游戏者
for (int i = ; i < skeletons.Length; i++)
{
if (skeletons[i].TrackingState == SkeletonTrackingState.Tracked)
{
if (skeleton == null)
{
skeleton = skeletons[i];
}
else
{
if (skeleton.Position.Z > skeletons[i].Position.Z)
{
skeleton = skeletons[i];
}
}
}
}
} return skeleton;
} private static Joint GetPrimaryHand(Skeleton skeleton)
{
Joint primaryHand = new Joint(); if (skeleton != null)
{
primaryHand = skeleton.Joints[JointType.HandLeft];
Joint righHand = skeleton.Joints[JointType.HandRight]; if (righHand.TrackingState != JointTrackingState.NotTracked)
{
if (primaryHand.TrackingState == JointTrackingState.NotTracked)
{
primaryHand = righHand;
}
else
{
if (primaryHand.Position.Z > righHand.Position.Z)
{
primaryHand = righHand;
}
}
}
} return primaryHand;
} private void TrackHand(Joint hand)
{
if (hand.TrackingState == JointTrackingState.NotTracked)
{
HandCursorElement.Visibility = Visibility.Collapsed;
}
else
{
HandCursorElement.Visibility = Visibility.Visible; DepthImagePoint point = this.kinectDevice.MapSkeletonPointToDepth(hand.Position, this.kinectDevice.DepthStream.Format);
point.X = (int)((point.X * LayoutRoot.ActualWidth / kinectDevice.DepthStream.FrameWidth) - (HandCursorElement.ActualWidth / 2.0));
point.Y = (int)((point.Y * LayoutRoot.ActualHeight / kinectDevice.DepthStream.FrameHeight) - (HandCursorElement.ActualHeight / 2.0)); Canvas.SetLeft(HandCursorElement, point.X);
Canvas.SetTop(HandCursorElement, point.Y); if (hand.JointType == JointType.HandRight)
{
HandCursorScale.ScaleX = ;
}
else
{
HandCursorScale.ScaleX = -; // 如果是右手,图像水平反转
}
}
} private void DrawPuzzle(DotPuzzle puzzle)
{
PuzzleBoardElement.Children.Clear(); if (puzzle != null)
{
for (int i = ; i < puzzle.Dots.Count; i++)
{
Grid dotContainer = new Grid();
dotContainer.Width = ;
dotContainer.Height = ;
dotContainer.Children.Add(new Ellipse { Fill = Brushes.Gray }); TextBlock dotLabel = new TextBlock();
dotLabel.Text = (i + ).ToString();
dotLabel.Foreground = Brushes.White;
dotLabel.FontSize = ;
dotLabel.HorizontalAlignment = HorizontalAlignment.Center;
dotLabel.VerticalAlignment = VerticalAlignment.Center;
dotContainer.Children.Add(dotLabel); //在UI界面上绘制点
Canvas.SetTop(dotContainer, puzzle.Dots[i].Y - (dotContainer.Height / ));
Canvas.SetLeft(dotContainer, puzzle.Dots[i].X - (dotContainer.Width / ));
PuzzleBoardElement.Children.Add(dotContainer);
}
}
} private void TrackPuzzle(SkeletonPoint position)
{
if (this.puzzleDotIndex == this.puzzle.Dots.Count)
{
//游戏结束
}
else
{
Point dot;
if (this.puzzleDotIndex + < this.puzzle.Dots.Count)
{
dot = this.puzzle.Dots[this.puzzleDotIndex + ];
}
else
{
dot = this.puzzle.Dots[];
} DepthImagePoint point = this.kinectDevice.MapSkeletonPointToDepth(position, kinectDevice.DepthStream.Format);
point.X = (int)(point.X * LayoutRoot.ActualWidth / kinectDevice.DepthStream.FrameWidth);
point.Y = (int)(point.Y * LayoutRoot.ActualHeight / kinectDevice.DepthStream.FrameHeight);
Point handPoint = new Point(point.X, point.Y);
Point dotDiff = new Point(dot.X - handPoint.X, dot.Y - handPoint.Y);
double length = Math.Sqrt(dotDiff.X * dotDiff.X + dotDiff.Y * dotDiff.Y); int lastPoint = this.CrayonElement.Points.Count - ;
//手势离点足够近
if (length < )
{
if (lastPoint > )
{
//移去最后一个点
this.CrayonElement.Points.RemoveAt(lastPoint);
} //设置直线的终点
this.CrayonElement.Points.Add(new Point(dot.X, dot.Y)); //设置新的直线的起点
this.CrayonElement.Points.Add(new Point(dot.X, dot.Y)); //转到下一个点
this.puzzleDotIndex++;
if (this.puzzleDotIndex == this.puzzle.Dots.Count)
{
//通知游戏者游戏结束
}
}
else
{
if (lastPoint > )
{
//移除最后一个点,更新界面
Point lineEndpoint = this.CrayonElement.Points[lastPoint];
this.CrayonElement.Points.RemoveAt(lastPoint);
//将手势所在的点作为线的临时终点
lineEndpoint.X = handPoint.X;
lineEndpoint.Y = handPoint.Y;
this.CrayonElement.Points.Add(lineEndpoint);
}
}
}
} }
}