public void TestStateClone() { MatchState state = new MatchState(new PhysicsEngine(new GameWorld(null))); IEntity e = new IEntity(IDGenerator.GenerateID(), new Vec2(100f, -99f), 1f, 1f, 1f); state.AddEntity(e); Assert.True(state.ContainsEntity(e.ID), "entity1 added to match"); IEntity e2 = new IEntity(IDGenerator.GenerateID(), new Vec2(42f, 24f), 1f, 1f, 1f); state.AddEntity(e2); Assert.True(state.ContainsEntity(e2.ID), "entity2 added to match"); MatchState clone = state.Clone() as MatchState; Assert.True(clone.ContainsEntity(e.ID), "clone contains entity1"); Assert.True(clone.ContainsEntity(e2.ID), "clone contains entity2"); IEntity clonedE = clone.GetEntity(e.ID); TestClonedEntityValues(e, clonedE, true, "after clone (e1)"); ChangeClonedEntity(clonedE, new Vec2(11f, 111f), new Vec2(22f, 222f), 1000f, 2000f, 333f); TestClonedEntityValues(e, clonedE, false, "after clone modif (e1)"); IEntity clonedE2 = clone.GetEntity(e2.ID); TestClonedEntityValues(e2, clonedE2, true, "after clone (e2)"); ChangeClonedEntity(clonedE2, new Vec2(87f, 78f), new Vec2(52f, 25f), 76f, 88f, 2121f); TestClonedEntityValues(e2, clonedE2, false, "after clone modif (e2)"); }
public bool HasCollisionBelow(IEntity entity) { Debug.Assert(entity != null); Rect r = entity.CreateCollisionRectangle(); float initialMid = r.Top + r.Height / 2f; r.X -= JUMP_X_TOLERANCE / 2f; r.Width += JUMP_X_TOLERANCE; r.Y += JUMP_Y_TOLERANCE; // move it down a bit var tiles = World.Map.GetTouchedTiles(r); foreach (var tile in tiles) { if (tile.Key.Intersects(r) && // we have a collision (tile.Value == CollisionType.Block || tile.Value == CollisionType.Platform) && // it is a blocking collision tile.Key.Top >= initialMid) { // the block is below us (below our feet) int x = World.Map.GetTileXIndex(tile.Key); int y = World.Map.GetTileYIndex(tile.Key); // If the tile above the current tile is passable, we may jump ! if (World.Map.IsValidXIndex(x) && World.Map.IsValidYIndex(y) && World.Map.GetCollision(x, y - 1) == CollisionType.Passable) return true; } } return false; }
/// <summary> /// Resolves the collisions between an entity and the world it is in. /// </summary> public void UndoCollisions(IEntity entity) { Debug.Assert(entity != null); var collisions = World.GetTouchedObjects(entity.CreateCollisionRectangle()); HandleCollisionGroup(entity, collisions); }
public override void Clone(IEntity e) { ICharacter c = (ICharacter)e; base.Clone(c); JumpForce = c.JumpForce; HorizontalAcceleration = c.HorizontalAcceleration; Animation = c.Animation; FacingLeft = c.FacingLeft; }
/// <summary> /// Clone the specified entity to copy its values. /// </summary> public virtual void Clone(IEntity e) { CollisionWidth = e.CollisionWidth; CollisionHeight = e.CollisionHeight; ID = e.ID; MoveSpeed = e.MoveSpeed; Position = e.Position.Clone() as Vec2; Velocity = e.Velocity.Clone() as Vec2; }
void ChangeClonedEntity(IEntity e, Vec2 pos, Vec2 vel, float colW, float colH, float moveSpeed) { e.Position.X = pos.X; e.Position.Y = pos.Y; e.Velocity.X = vel.X; e.Velocity.Y = vel.Y; e.CollisionWidth = colW; e.CollisionHeight = colH; e.MoveSpeed = moveSpeed; }
public bool HasCollisions(IEntity entity) { Debug.Assert(entity != null); var rect = entity.CreateCollisionRectangle(); var collisions = World.GetTouchedObjects(rect); foreach (var collision in collisions) { if (collision.Value != CollisionType.Passable && rect.Intersects(collision.Key)) { return true; } } return false; }
/// <summary> /// Runs a physics update on the given entity with a given delta seconds. /// Internally, this function calls the physics update with a fixed timestep. /// Therefore, if the delta seconds is not a /// </summary> public void Update(double deltaSeconds, IEntity entity) { Debug.Assert(deltaSeconds > 0.0); Debug.Assert(entity != null && entity.Position != null && entity.Velocity != null); while (deltaSeconds >= FIXED_TIMESTEP.TotalSeconds) { ApplyUpdate(entity); deltaSeconds -= FIXED_TIMESTEP.TotalSeconds; } if (deltaSeconds > 0.0) { // we have leftover time to simulate float progress = (float)(deltaSeconds / FIXED_TIMESTEP.TotalSeconds); InterpolateUpdate(entity, progress); } }
void TestClonedEntityValues(IEntity original, IEntity clone, bool equal, string message) { string eql_msg = equal ? " same " : " different "; string intro = "cloned value" + eql_msg; Action<object, object, string> assert; if (equal) { assert = Assert.AreEqual; } else { assert = Assert.AreNotEqual; } Assert.AreEqual(original.ID, clone.ID, intro + "ID " + message); // must stay the same assert(original.Position.X, clone.Position.X, intro + "position " + message); assert(original.Velocity.Y, clone.Velocity.Y, intro + "velocity " + message); assert(original.CollisionHeight, clone.CollisionHeight, intro + "collision height " + message); assert(original.CollisionWidth, clone.CollisionWidth, intro + "collision width " + message); assert(original.MoveSpeed, clone.MoveSpeed, intro + "move speed " + message); }
void ApplyMovement(IEntity e, Action onPhysicsPass) { // Multiple physics passes to reduce the chance of "going through" obstacles when we're too fast. Vec2 passMovement = (e.Velocity * DELTA_S) / PHYSICS_PASSES; for (int pass = 0; pass < PHYSICS_PASSES; ++pass) { e.Position += passMovement; if (onPhysicsPass != null) onPhysicsPass(); } }
/// <summary> /// Fixes a collision between an entity and a single object. /// </summary> static void UndoCollision(IEntity entity, Rect entityRect, Rect collided) { Debug.Assert(entity != null); Debug.Assert(entityRect != null); Debug.Assert(collided != null); Vec2 intersection = Utilities.GetIntersectionDepth(entityRect, collided); if (intersection != Vec2.Zero) { float abs_dept_x = Math.Abs(intersection.X); float abs_dept_y = Math.Abs(intersection.Y); // Resolve the collision on the axis where it will be less noticeable (the smallest collision amount) if (abs_dept_y < abs_dept_x) // collision on the y axis { entity.Position.Y += intersection.Y; //TODO: only undo collision for platforms if we hit the ground // Only stop our movement if we're going straigth into the obstacle. if (Math.Sign(intersection.Y) == -Math.Sign(entity.Velocity.Y)) entity.Velocity.Y = 0f; // stop our Y movement } else // collision on the x axis { entity.Position.X += intersection.X; entity.Velocity.X = 0f; // stop our X movement } } }
/// <summary> /// Handles the collision of an object with multiple entities. /// </summary> void HandleCollisionGroup( IEntity entity, List<KeyValuePair<Rect, CollisionType>> collisions) { Debug.Assert(entity != null); Debug.Assert(collisions != null); foreach (KeyValuePair<Rect, CollisionType> collision in collisions) { // We recreate an entity rectangle on every loop // because multiple collisions might affect the entity in the same frame. Rect entityRect = entity.CreateCollisionRectangle(); Rect rect = collision.Key; CollisionType type = collision.Value; switch (type) { case CollisionType.Block: UndoCollision(entity, entityRect, rect); break; case CollisionType.Passable: break; // do nothing then default: throw new NotImplementedException("Collision type not implemented."); } } }
static Vec2 ValidateActionPosition(IEntity player, PlayerAction action) { Vec2 position = action.Position; // If the position provided by the client seems legit, we take it. Otherwise, we ignore it // and log it (might be a hacker). if (Vec2.DistanceSquared(player.Position, position) >= MAX_TOLERATED_OFF_DISTANCE * MAX_TOLERATED_OFF_DISTANCE) { position = player.Position; } return position; }
void RestrictSpeed(IEntity entity) { // Restrict movement speed if (entity.Velocity.GetLengthSquared() > IEntity.MAX_SPEED * IEntity.MAX_SPEED) entity.Velocity = Vec2.Normalize(entity.Velocity) * IEntity.MAX_SPEED; }
public virtual object Clone() { IEntity clone = new IEntity(ID, Position, MoveSpeed, CollisionWidth, CollisionHeight); clone.Clone(this); return clone; }
/// <summary> /// Makes the specified entity move in a certain direction for the current frame. /// </summary> public void Move(IEntity entity, HorizontalDirection direction) { Debug.Assert(entity != null && entity.Velocity != null); Debug.Assert(Utilities.MakeList(HorizontalDirection.Left, HorizontalDirection.None, HorizontalDirection.Right) .Contains(direction)); if (direction != HorizontalDirection.None) { // we want to move Debug.Assert(Utilities.MakeList(HorizontalDirection.Left, HorizontalDirection.Right).Contains(direction)); // Find in what direction we should apply the force float moveForce = (int)direction * entity.MoveSpeed; // Apply the movement entity.Velocity.X += moveForce; } }
/// <summary> /// Resets the movement of the entity. /// </summary> public void ResetMovement(IEntity entity) { entity.Velocity = Vec2.Zero; }
void UpdateEntity(IEntity entity) { Debug.Assert(entity != null && entity.Position != null && entity.Velocity != null); ApplyMovement(entity, null); }
public bool IsOnGround(IEntity entity) { return Collisions.HasCollisionBelow(entity); }
/// <summary> /// Interpolates between the current entity state and its future state (with one /// more physics update). /// </summary> void InterpolateUpdate(IEntity entity, float progress) { Debug.Assert(entity != null && entity.Position != null && entity.Velocity != null); Debug.Assert(progress + double.Epsilon >= 0.0 && progress <= 1.0 + double.Epsilon); // Keep some values that will be interpolated Vec2 initialPos = entity.Position; Vec2 initialVel = entity.Velocity; // Simulate the next step ApplyUpdate(entity); // interpolate some elements entity.Position = Vec2.Lerp(initialPos, entity.Position, progress); entity.Velocity = Vec2.Lerp(initialVel, entity.Velocity, progress); }
public bool CollidesWithWorld(IEntity e) { return Collisions.HasCollisions(e); }
public override void Clone(IEntity champ) { ServerChampion c = (ServerChampion)champ; base.Clone(c); }
void ApplyUpdate(IEntity e) { if (e is ICharacter) UpdateCharacter((ICharacter)e); else UpdateEntity(e); }