// TODO in case we need this somewhere else it might be a good idea to move this to the EntityFactory private void CreateAsteroid(FarPosition position, IUniformRandom random, ContentManager content) { // Randomly scale and rotate it. var scale = (float)random.NextDouble(0.5f, 1f); var angle = (float)random.NextDouble() * MathHelper.TwoPi; // Determine shape for physics system. var textureName = "Textures/Asteroids/rock_" + random.NextInt32(1, 14); var texture = content.Load <Texture2D>(textureName); var hull = new List <Vector2>(TextureConverter.DetectVertices(texture, 8f, textureName: textureName)[0]); for (var k = 0; k < hull.Count; ++k) { hull[k] -= new Vector2(texture.Width / 2f, texture.Height / 2f); hull[k] = XnaUnitConversion.ToSimulationUnits(hull[k]) * scale; } var polygons = EarClipDecomposer.ConvexPartition(hull); // Create physical representation. var entity = Manager.AddEntity(); var body = Manager.AddBody(entity, position, angle, Body.BodyType.Dynamic); foreach (var polygon in polygons) { Manager.AttachPolygon(body, polygon, density: 1000f, restitution: 0.2f); } // Slow down to allow reaching sleep state again. body.LinearDamping = 0.05f * Space.Util.Settings.TicksPerSecond; body.AngularDamping = 0.025f * Space.Util.Settings.TicksPerSecond; // Bounds of the asteroid for rendering culling. We use the diagonal for a loose fit that // contains every possible rotated state of the texture. var width = UnitConversion.ToSimulationUnits(texture.Width); var height = UnitConversion.ToSimulationUnits(texture.Height); var diagonal = (float)Math.Sqrt(width * width + height * height); var bounds = new FarRectangle(-diagonal / 2, -diagonal / 2, diagonal, diagonal); // Rendering stuff. Manager.AddComponent <Indexable>(entity).Initialize(bounds, CameraSystem.IndexId); Manager.AddComponent <Indexable>(entity).Initialize(bounds, InterpolationSystem.IndexId); Manager.AddComponent <SimpleTextureDrawable>(entity).Initialize(textureName, scale); // Auto removal. Manager.AddComponent <CellDeath>(entity).Initialize(true); Manager.AddComponent <Indexable>(entity).Initialize(CellSystem.CellDeathAutoRemoveIndexId); // Make it destructible. var health = Manager.AddComponent <Health>(entity); health.Value = health.MaxValue = 200 * scale; health.Regeneration = 0f; // As they don't move on their own, start asteroids as sleeping to save performance. body.IsAwake = false; }
/// <summary> /// Shuffles the specified list using the Fisher-Yates shuffle. This only shuffles a segment in a list, as opposed /// to shuffling the complete list. /// </summary> /// <typeparam name="T">The type of element stored in the list.</typeparam> /// <param name="list">The list to shuffle.</param> /// <param name="offset">The offset at which to start shuffling.</param> /// <param name="length">The length of the interval to shuffle.</param> /// <param name="random">The random number generator to use for shuffling. If none is specified, a new one will be created.</param> public static void Shuffle <T>(this IList <T> list, int offset, int length, IUniformRandom random = null) { random = random ?? new MersenneTwister(); for (var i = offset + length - 1; i > offset; --i) { var j = random.NextInt32(i + 1); var t = list[j]; list[j] = list[i]; list[i] = t; } }
/// <summary>Updates the behavior and returns the behavior type to switch to.</summary> public void Update() { // Don't update more often than we have to. For example, patrol // behaviors should require way fewer updates than attack // behaviors. if (_ticksToWait > 0) { --_ticksToWait; return; } // Do the behavior specific update. if (!UpdateInternal()) { // Skip if we don't have to do the rest (e.g. popped self). return; } // No change, wait a bit with the next update. _ticksToWait = _pollRate / 2 + Random.NextInt32(_pollRate); // Figure out where we want to go. var targetPosition = GetTargetPosition(); // And accordingly, which way to accelerate to get there. var direction = (Vector2)(targetPosition - ((ITransform)AI.Manager.GetComponent(AI.Entity, TransformTypeId)).Position); // Normalize if it's not zero. var norm = direction.LengthSquared(); if (norm > 0) { norm = (float)Math.Sqrt(norm); direction.X /= norm; direction.Y /= norm; } // Multiply with the desired acceleration. var speed = MathHelper.Clamp(GetThrusterPower(), 0, 1); direction.X *= speed; direction.Y *= speed; // Figure out where we want to go vegetatively (flocking). direction += GetVegetativeDirection() * AI.Configuration.VegetativeWeight; // Set our new acceleration direction and target rotation. var shipControl = ((ShipControl)AI.Manager.GetComponent(AI.Entity, ShipControl.TypeId)); shipControl.SetAcceleration(direction); shipControl.SetTargetRotation(GetTargetRotation(direction)); }
/// <summary>Samples the local attribute count.</summary> /// <param name="random">The randomizer to use.</param> /// <returns></returns> private int SampleLocalAttributeCount(IUniformRandom random) { return((_additionalLocalAttributeCount.Low == _additionalLocalAttributeCount.High || random == null) ? _additionalLocalAttributeCount.Low : random.NextInt32(_additionalLocalAttributeCount.Low, _additionalLocalAttributeCount.High)); }
private void PopulateSubCell(CellStateChanged message, IUniformRandom random) { // We randomly spawn some asteroid fields in sub-cells. These are laid out as follows: // - we decide how many fields we want to spawn. // - we build a grid inside the cell that has enough entries for our asteroid fields, // i.e. we take the next higher squared number (e.g. if we have 5 fields, we generate // a 3x3 grid). // - For each field we randomly pick one such grid cell to determine it's basic position, // and shift it to a random position inside that grid cell. // - To position the actual asteroids, we want it to be somewhat circular, but it should // not look too regular. We lay out our asteroids in a spiral, on which we place our // asteroids in a fixed interval. Finally we add some random offset to make the spiral // non-obvious. // Get content manager to fetch textures from which to generate physics models. var content = ((ContentSystem)Manager.GetSystem(ContentSystem.TypeId)).Content; // Number of asteroid fields in this cell? var fieldCount = random.NextInt32(8, 12); // Determine number of rows and cols for base positions. var cols = (int)Math.Ceiling(Math.Sqrt(fieldCount)); // Build sampling list. var cells = new List <Tuple <int, int> >(cols * cols); for (var x = 0; x < cols; ++x) { for (var y = 0; y < cols; ++y) { cells.Add(Tuple.Create(x, y)); } } // Size of a cell in our sub grid. var gridSize = CellSystem.SubCellSize / (float)cols; // Cell top left corner position. var cellPosition = new FarPosition(message.X, message.Y) * CellSystem.SubCellSize; // Generate asteroid fields. for (var i = 0; i < fieldCount; ++i) { // Get base position. var positionIndex = random.NextInt32(cells.Count); var fieldIndex = cells[positionIndex]; cells.RemoveAt(positionIndex); var asteroidCount = random.NextInt32(30, 60); var center = cellPosition + new FarPosition( fieldIndex.Item1 * gridSize + (float)random.NextDouble(0, gridSize), fieldIndex.Item2 * gridSize + (float)random.NextDouble(0, gridSize)); // We grow our asteroid fields as spirals, with a little jitter. const float jitter = 2.5f; const float radiusStep = 0.4f; //< how fast we move outwards. const float angleStep = 2.25f; //< the asteroid interval on the spiral. var theta = angleStep / radiusStep; // Create first one at the center. CreateAsteroid(center, random, content); // Generate rest of the spiral. for (var j = 1; j < asteroidCount; ++j) { // Compute position in our spiral. var radius = radiusStep * theta; var position = center; position.X += (float)Math.Cos(theta) * radius; position.Y += (float)Math.Sin(theta) * radius; position.X += (float)random.NextDouble(-jitter / 2, jitter / 2); position.Y += (float)random.NextDouble(-jitter / 2, jitter / 2); theta += angleStep / radius; CreateAsteroid(position, random, content); } } //// Sprinkle some asteroids in the background, for depth and movement perception. //for (var i = 0; i < 1000; ++i) //{ // var center = cellPosition + new FarPosition( // (float) random.NextDouble(0, CellSystem.SubCellSize), // (float) random.NextDouble(0, CellSystem.SubCellSize)); // // Randomly scale and rotate it. // var layer = (float) random.NextDouble(0.5f, 0.75f); // var scale = (float) random.NextDouble(0.6f, 0.9f); // var angle = (float) random.NextDouble() * MathHelper.TwoPi; // // Determine shape for physics system. // var textureName = "Textures/Asteroids/rock_" + random.NextInt32(1, 14); // var texture = content.Load<Texture2D>(textureName); // // Create component that will be rendered. // var entity = Manager.AddEntity(); // Manager.AddComponent<Transform>(entity).Initialize(center, angle); // // Expand bounds based on layer for camera and interpolation system, so that they // // can see the asteroids in time (lower parallax = farther in background = moving // // slower = longer in screen = wider actual viewport necessary to check). // var width = UnitConversion.ToSimulationUnits(texture.Width); // var height = UnitConversion.ToSimulationUnits(texture.Height); // var diagonal = 8f * (float) Math.Sqrt(width * width + height * height) / layer / scale; // var bounds = new FarRectangle(-diagonal / 2f, -diagonal / 2f, diagonal, diagonal); // // Rendering stuff. // Manager.AddComponent<Parallax>(entity).Initialize(layer); // Manager.AddComponent<Indexable>(entity).Initialize(bounds, CameraSystem.IndexId); // Manager.AddComponent<Indexable>(entity).Initialize(bounds, InterpolationSystem.IndexId); // Manager.AddComponent<SimpleTextureDrawable>(entity).Initialize(textureName, new Color(100, 100, 100, 255), scale); // // Auto removal. // Manager.AddComponent<CellDeath>(entity).Initialize(true); // Manager.AddComponent<Indexable>(entity).Initialize(CellSystem.CellDeathAutoRemoveIndexId); //} }