public class WrappedAccelerometer
{
static Accelerometer accelerometer;
static AccelerometerReadingEventArgs lastSeenAcceleration;
static WrappedAccelerometer()
{
accelerometer = new Accelerometer();
accelerometer.Start();
accelerometer.ReadingChanged += new System.EventHandler<AccelerometerReadingEventArgs>(accelerometer_ReadingChanged);
lastSeenAcceleration = null;
}
static void accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
lastSeenAcceleration = e;
}
public static AccelerometerReadingEventArgs GetState()
{
return lastSeenAcceleration;
}
}
public static class VirtualThumbsticks
{
static VirtualThumbsticks()
{
}
/// <summary>
/// Gets the value for steering.
/// </summary>
public static double Steering
{
get
{
var accelerometerState = WrappedAccelerometer.GetState();
// if there is no accelerometer state, then return a value of 0.0
if (null == accelerometerState)
return 0.0;
// because we are in landscape...
// we have to use accerometer state
return accelerometerState.Y;
}
}
/// <summary>
/// Gets the value for acceleration.
/// </summary>
public static double Thrusters
{
get
{
var accelerometerState = WrappedAccelerometer.GetState();
// if there is no accelerometer state, then return a value of 0.0
if (null == accelerometerState)
return 0.0;
// calculate the scaled vector from the touch position to the center,
// scaled by the maximum thumbstick distance
// this conversions sets normalised from the Z value:
// accelerometerState.Z will be -1.0 when the phone is flat down
// accelerometerState.Z will be 0.0 when the phone is upright
// we convert that to thrust ratio using:
var thrust = -2.0 * accelerometerState.Z - 1.0;
if (thrust > 1.0)
thrust = 1.0;
if (thrust < -1.0)
thrust = -1.0;
// this actually works quite nicely thanks to trigonomics :)
//System.Diagnostics.Debug.WriteLine(string.Format("Z is {0:0.00}, N is {1:0.00}", accelerometerState.Z, thrust));
return thrust;
}
}
/// <summary>
/// Updates the virtual thumbsticks based on current touch state. This must be called every frame.
/// </summary>
public static void Update()
{
// do nothing for now
}
}
// Determine rotation amount from input
rotationAmount.X = (float)VirtualThumbsticks.Steering;
// Scale rotation amount to radians per second
rotationAmount = rotationAmount * RotationRate * elapsed;
// ...
// Determine thrust amount from input
float thrustAmount = (float)VirtualThumbsticks.Thrusters;
// Calculate force from thrust amount
Vector3 force = Direction * thrustAmount * ThrustForce;
// Create rotation matrix from rotation amount
Matrix rotationMatrix =
Matrix.CreateFromAxisAngle(Right, rotationAmount.Y) *
Matrix.CreateRotationY(rotationAmount.X) *
Matrix.CreateRotationX(rotationAmount.Z);
TouchCollection touches = TouchPanel.GetState();
touchActive = touches.Count;
public static double PitchAcceleration
{
get
{
if (touchActive)
return 1.0;
return -1.0;
}
}
// modify the Altitude
Position.Y += AltitudeStep * (float)VirtualThumbsticks.PitchAcceleration;
4. To provide some variation in height, set some constants in Ship.cs:
private const float MinimumAltitude = 100.0f;
private const float MaximumAltitude = 6000.0f;
private const float AltitudeStep = 50.0f;
5. To make the environment a bit more interesting, change the altitude values of our prizes and enemy ships in the main game file. e.g.
// Two arrays that define the location of obstacles and collection items.
// these could be loaded per level giving different level designs
Vector3[] coords = new Vector3[] { new Vector3(45000, 50, 57000),
new Vector3(-45000, 2000, 57000),
new Vector3(57000, 1500, -45000),
new Vector3(-57000, 4000, -45000),
new Vector3(8000f, 3000, 0f),
new Vector3(-8000f, 2000, 0f),
new Vector3(21500, 1000, 32500),
new Vector3(-21500, 3000, 21500),
new Vector3(21500, 2000, -21500),
new Vector3(-21500, 400, -21500) };
Vector3[] treecoords = new Vector3[] { new Vector3(-18000f, 1200f, 0f),
new Vector3(0f, 2400f, 18000f),
new Vector3(0f, 3600f, -18000f),
new Vector3(57000f, 1200f, 0f),
new Vector3(-57000f, 2400f, 0f),
new Vector3(0f, 3600f, 57000f),
new Vector3(0f, 1200f, -57000f),
new Vector3(32500f, 2400f, 32500f),
new Vector3(-32500f, 3600f, 32500f),
new Vector3(32500f, 1200f, -32500f),
new Vector3(-32500f, 1200f, -32500f) };:
So the game's working... but it's a bit static... so next up was to hack into the main game loop and to give the enemies some motion...
To do this, I took each existing Enemy tree (currently just a sphere) and replaced them with an object:
public class EnemyShip
{
private static float MaxSpeed = 80.0f;
private static float AccelerationConstant = 10.0f;
public BoundingSphere Sphere;
public BoundingSphere OriginalSphere;
public Vector3 CurrentSpeed;
public EnemyShip(BoundingSphere sphere)
{
OriginalSphere = sphere;
Reset();
}
public void Reset()
{
Sphere = new BoundingSphere(OriginalSphere.Center, OriginalSphere.Radius);
CurrentSpeed = new Vector3();
}
public void AccelerateTowards(Vector3 Position)
{
var accerlationDirection = Position - Sphere.Center;
accerlationDirection.Normalize();
CurrentSpeed += AccelerationConstant * accerlationDirection;
var currentLength = CurrentSpeed.Length();
if (currentLength > MaxSpeed)
{
CurrentSpeed = CurrentSpeed * MaxSpeed / currentLength;
}
}
public void UpdatePosition()
{
Sphere.Center += CurrentSpeed;
}
}
then each time around the loop, I call AccelerateTowards() and UpdatePosition on each of these enemies.
Result...
- the enemies definitely come chase
- the enemies also quite often collide with each other - I should add some sort of protection to stop them overlapping...
- when you crash into an enemy it's also quite hard to escape again afterwards!
To cope with the overlapping... I added System.Linq, changed the update to
public void UpdatePosition(System.Collections.Generic.IEnumerable<BoundingSphere> DoNotMoveWithin)
{
var enlargedSphere = new BoundingSphere(Sphere.Center + CurrentSpeed, Sphere.Radius * 3);
if (DoNotMoveWithin.Any(x => x.Intersects(enlargedSphere)))
return;
var candidateNewSphere = new BoundingSphere(Sphere.Center + CurrentSpeed, Sphere.Radius);
Sphere = candidateNewSphere;
}
and called this using:
enemyShip.UpdatePosition(enemyShips.Where(x => x != enemyShip).Select(x => x.Sphere));
This kind of works.... but it also leads to some problems - e.g. once the enemy ships have moved together they don't move apart again.
I guess I could add some code that means each enemy has to stay within X of its starting location.... another day... another hack...
public void UpdatePosition(System.Collections.Generic.IEnumerable<BoundingSphere> DoNotMoveWithin)
{
var newCenter = Sphere.Center + CurrentSpeed;
if ((newCenter - OriginalSphere.Center).Length() > MaxDistanceFromOriginal)
return;
var enlargedSphere = new BoundingSphere(newCenter, Sphere.Radius * 3);
if (DoNotMoveWithin.Any(x => x.Intersects(enlargedSphere)))
return;
var candidateNewSphere = new BoundingSphere(newCenter, Sphere.Radius);
Sphere = candidateNewSphere;
}
Out of time
So that's it... out of time.
What have I learnt from my hack?
- XNA's pretty easy to pick up - it's C# and .Net - you can use Linq :)
- The contents projects look easy - but there's some gotchas in there when importing models - I'm sure if I play with these some more, then they will just work.
- The 3D and Vector code in XNA is *lovely* - it's great to just be able to write code that adds and subtracts Positions and Vectors - and the Matrix code for Rotation is likewise very clean to read - love it.
- Making a 3D model that works nicely on the screen takes work and thought!
- Making a "fly-through" model playable is difficult - even with my "helicopter game" shortcut, then the spaceship was still sometimes hard to control. From a playability perspective, 2D still has lots of advantages which might be why games like Annoyed Avians are selling so well.
- Using the accelerometer in XNA is easy - and it does provide a very immersive experience for a gamer
- Using "virtual buttons" on the screen is also pretty easy in XNA - but user feedback for these buttons is definitely needed...
- The processors on WP7 phones (CPU and GPU) are stunningly quick - the amount of maths going on and the speed its done at is simply awesome.
- I don't think I day gos by when I don't love using Linq - it's magic too.
- I've still got plenty more to learn.... and plenty more hacking to do...
If anyone wants my code then I'll post it somewhere for you all to enjoy :)
No comments:
Post a Comment