Thursday, February 10, 2011
DDDHack - XNA mods
Some #DDDHack Action
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 :)
Friday, February 04, 2011
Some DateTimeExtensions (Links)
StartOfTodayInUtc
ToStartOfTimeZonesDayInUtc
ToLocalTime and ToUniversalTime
FirstDayOfMonth / LastDayOfMonth
SetTime
AddWeekdays
Tuesday, February 01, 2011
Followup on .Net Collection numbers - release build with code "pre-JITed"
Some numbers on .Net Collections performance
BaselineAdd of size 1000 took 10856 ticksBaselineAdd of size 10000 took 47490 ticksBaselineAdd of size 100000 took 343267 ticks
ListAdd of size 1000 took 5459 ticksListAdd of size 10000 took 43505 ticksListAdd of size 100000 took 738327 ticks
PreSizedListAdd of size 1000 took 4241 ticksPreSizedListAdd of size 10000 took 152861 ticksPreSizedListAdd of size 100000 took 664572 ticks
WronglyPreSizedListAdd of size 100000 took 746042 ticksVeryWronglyPreSizedListAdd of size 100000 took 772499 ticksLinkedListAdd of size 1000 took 4210 ticksLinkedListAdd of size 10000 took 42905 ticksLinkedListAdd of size 100000 took 794595 ticksListRemoveByIndex of size 1000 took 145411 ticksListRemoveByObject of size 1000 took 3590562 ticks
- If you can help it, don't use Remove() - use RemoveAt()!
- For smallish lists, LinkedLists perform rather well
- The penalty for wrongly sizing lists seems small (maybe the code is wrong or maybe my understanding is!).
- It's remarkably hard to write generics that rely on generics (especially when LinkedList<> doesn't derive from the same interfaces as List<>)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace CollectionsTiming
{
class Program
{
private const int TestSize = 100000;
static void Main(string[] args)
{
var p = new Program();
p.DoIt();
}
public void DoIt()
{
InternalListAddTestRun("BaselineAdd", TestSize / 100, () => new NopList<TestObject>());
InternalListAddTestRun("BaselineAdd", TestSize / 10, () => new NopList<TestObject>());
InternalListAddTestRun("BaselineAdd", TestSize, () => new NopList<TestObject>());
Console.WriteLine();
InternalListAddTestRun("ListAdd", TestSize / 100, () => new List<TestObject>());
InternalListAddTestRun("ListAdd", TestSize / 10, () => new List<TestObject>());
InternalListAddTestRun("ListAdd", TestSize, () => new List<TestObject>());
Console.WriteLine();
InternalListAddTestRun("PreSizedListAdd", TestSize / 100, () => new List<TestObject>(TestSize / 100));
InternalListAddTestRun("PreSizedListAdd", TestSize / 10, () => new List<TestObject>(TestSize / 10));
InternalListAddTestRun("PreSizedListAdd", TestSize, () => new List<TestObject>(TestSize));
Console.WriteLine();
InternalListAddTestRun("WronglyPreSizedListAdd", TestSize, () => new List<TestObject>(TestSize / 100));
InternalListAddTestRun("VeryWronglyPreSizedListAdd", TestSize, () => new List<TestObject>(TestSize / 1000));
Console.WriteLine();
InternalListAddTestRun("LinkedListAdd", TestSize / 100, () => new LinkedList<TestObject>());
InternalListAddTestRun("LinkedListAdd", TestSize / 10, () => new LinkedList<TestObject>());
InternalListAddTestRun("LinkedListAdd", TestSize, () => new LinkedList<TestObject>());
Console.WriteLine();
InternalListRemoveTestRun("ListRemoveByIndex", TestSize, TestSize / 100, (list, index, obj) => list.RemoveAt(index));
InternalListRemoveTestRun("ListRemoveByObject", TestSize, TestSize / 100, (list, index, obj) => list.Remove(obj));
Console.WriteLine();
//InternalListRemoveTestRun("PreSizedAdd", TestSize, TestSize / 100, () => new List<TestObject>(TestSize / 100));
Console.WriteLine();
Console.ReadLine();
}
public void InternalListRemoveTestRun(string name, int totalSize, int removeSize, Action<List<TestObject>, int, TestObject> removeAction)
{
InternalTestRun(name, removeSize, () => InternalRemoveTestRun(totalSize, removeSize, removeAction));
}
public long InternalRemoveTestRun(int totalSize, int removeSize, Action<List<TestObject>, int, TestObject> removeMethod)
{
var testList = new List<TestObject>(totalSize);
for (int i = 0; i < totalSize; i++)
{
testList.Add(new TestObject(i));
}
SortedList<int, Tuple<int, TestObject>> toRemove = new SortedList<int, Tuple<int, TestObject>>(removeSize);
var rand = new Random();
while (toRemove.Count < removeSize)
{
int candidate = rand.Next(totalSize);
toRemove[candidate] = new Tuple<int, TestObject>(candidate, testList[candidate]);
}
Stopwatch s = new Stopwatch();
s.Start();
foreach (var tuple in toRemove.Values.Reverse())
{
removeMethod(testList, tuple.Item1, tuple.Item2);
}
s.Stop();
var result = s.ElapsedTicks;
return result;
}
public void InternalListAddTestRun(string name, int maxSize, Func<LinkedList<TestObject>> initialiser)
{
InternalTestRun(name, maxSize, () => LinkedListAddTestRun(maxSize, initialiser));
}
public void InternalListAddTestRun(string name, int maxSize, Func<IList<TestObject>> initialiser)
{
InternalTestRun(name, maxSize, () => ListAddTestRun(maxSize, initialiser));
}
public void InternalTestRun(string name, int maxSize, Func<long> testMethod)
{
var result = testMethod();
Console.WriteLine("{0}\tof size {1}\ttook {2} ticks", name, maxSize, result);
}
public long ListAddTestRun(int maxSize, Func<IList<TestObject>> initialiser)
{
Stopwatch s = new Stopwatch();
s.Start();
var testObject = initialiser();
for (int i = 0; i < maxSize; i++)
{
testObject.Add(new TestObject(i));
}
s.Stop();
var result = s.ElapsedTicks;
return result;
}
public long LinkedListAddTestRun(int maxSize, Func<LinkedList<TestObject>> initialiser)
{
Stopwatch s = new Stopwatch();
s.Start();
var testObject = initialiser();
for (int i = 0; i < maxSize; i++)
{
testObject.AddLast(new TestObject(i));
}
s.Stop();
var result = s.ElapsedTicks;
return result;
}
}
class NopList<T> : IList<T>
{
public void Add(T to)
{
}
public NopList()
{
}
public NopList(int capacity)
{
}
public int IndexOf(T item)
{
throw new NotImplementedException();
}
public void Insert(int index, T item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public T this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(T item)
{
throw new NotImplementedException();
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public int Count
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
throw new NotImplementedException();
}
public IEnumerator<T> GetEnumerator()
{
throw new NotImplementedException();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
class TestObject
{
public string MyKey { get; set; }
public string SomeRandomText { get; set; }
public int Index { get; set; }
public TestObject(int index)
{
MyKey = Guid.NewGuid().ToString();
SomeRandomText = Guid.NewGuid().ToString();
Index = index;
}
}
}