예제 #1
0
    void DrawBorder(Vector2 position, int width, int height)
    {
        TileIndex borderTiles = new TileIndex(0, 1);
        for (int j = 0; j < height; j++)
        {
            int row;
            if (j == 0) row = 0;
            else if (j == height - 1) row = 2;
            else row = 1;

            for (int i = 0; i < width; i++)
            {
                int column;
                if (i == 0) column = 0;
                else if (i == width - 1) column = 2;
                else column = 1;

                TileIndex tile = borderTiles + new TileIndex(column, row);
                TileEngine.DrawTile(Assets.UITiles, tile, position + new Vector2(i, j) * Assets.CellSize);
            }
        }
    }
예제 #2
0
    public void Update()
    {
        List<Action> diagnostics = new List<Action>();

        AnimationTimer += 1;
        bool advanceFrame = false;
        if (AnimationTimer >= Assets.AnimationPeriod)
        {
            AnimationTimer -= Assets.AnimationPeriod;
            advanceFrame = true;
        }

        //==========================================================================================
        // Conversations:
        //==========================================================================================
        List<Creature> availableConversers = new List<Creature>();
        foreach (Creature creature in Creatures)
        {
            if (creature != Player)
            {
                float distance = (creature.Position - Player.Position).Length();

                if (creature.Conversation.Length > 0 && distance < 96)
                {
                    availableConversers.Add(creature);
                }

                if (creature.CanKill && State == GameState.Playing && distance < 48)
                {
                    State = GameState.Losing;
                    Engine.StopMusic(2.0f);
                }

                if (creature.CanWin && State == GameState.Playing && distance < 48)
                {
                    State = GameState.Winning;
                    Engine.StopMusic(2.0f);
                }
            }
        }

        Conversers.RemoveAll(x => !availableConversers.Contains(x));

        if (!InConversation && availableConversers.Count > 0 && Engine.GetKeyDown(Key.J))
        {
            // Begin a conversation:
            Creature other = availableConversers[0];
            Conversers.Add(other);
            ConversationPage = 0;

            if (other.Thought == Thought.WaitToJoin) other.Thought = Thought.Follow;
        }
        else if (InConversation && Engine.GetKeyDown(Key.J))
        {
            // Continue a conversation:
            ConversationPage += 1;
        }

        //==========================================================================================
        // Player input:
        //==========================================================================================
        Vector2 input = Vector2.Zero;
        if (Engine.GetKeyHeld(Key.A)) input.X -= 1;
        if (Engine.GetKeyHeld(Key.D)) input.X += 1;
        if (Engine.GetKeyHeld(Key.W)) input.Y -= 1;
        if (Engine.GetKeyHeld(Key.S)) input.Y += 1;
        Player.Movement = input;

        if (input.Length() != 0)
        {
            ShowMoveTip = false;
        }

        //==========================================================================================
        // AI:
        //==========================================================================================
        foreach (Creature creature in Creatures)
        {
            if (creature.Thought == Thought.Follow)
            {
                Vector2 vectorToPlayer = Player.Position - creature.Position;
                float distanceToPlayer = vectorToPlayer.Length();
                if (distanceToPlayer > creature.FollowDistanceMin && distanceToPlayer < creature.FollowDistanceMax)
                {
                    creature.Movement = vectorToPlayer.Normalized();
                }
                else
                {
                    creature.Movement = Vector2.Zero;
                }
            }
        }

        //==========================================================================================
        // Physics:
        //==========================================================================================
        foreach (Creature creature in Creatures)
        {
            if (State != GameState.Playing)
            {
                creature.Movement = Vector2.Zero;
                creature.Velocity = Vector2.Zero;
            }

            creature.Velocity += creature.Movement * creature.MaxAcceleration * Engine.TimeDelta;
            creature.Velocity.X = Clamp(creature.Velocity.X, -creature.Speed, creature.Speed);
            creature.Velocity.Y = Clamp(creature.Velocity.Y, -creature.Speed, creature.Speed);

            // Update sprite facing:
            if (creature.Movement.X < 0) creature.Facing = TextureMirror.Horizontal;
            if (creature.Movement.X > 0) creature.Facing = TextureMirror.None;

            // Update position and collide:
            {
                // These bounds describe the "solid" part of the entity; it is independent of position.
                Bounds2 creatureShape = new Bounds2(
                    new Vector2(1 / 8f, 6 / 8f) * Assets.CellSize,
                    new Vector2(6 / 8f, 2 / 8f) * Assets.CellSize);

                Vector2 motion = creature.Velocity * Engine.TimeDelta;
                TileIndex nearest = GetCellAt(creature.Position, Assets.WallTiles);

                // Find everything that could be collided with:
                List<Bounds2> obstacles = new List<Bounds2>();
                for (int row = nearest.Row - 2; row <= nearest.Row + 2; row++)
                {
                    for (int column = nearest.Column - 2; column <= nearest.Column + 2; column++)
                    {
                        if (Obstacles[column, row])
                        {
                            Bounds2 obstacleBounds = new Bounds2(
                                new Vector2(column, row) * Assets.CellSize,
                                Assets.CellSize);

                            // The effective bounds are the sum of the obstacle's and the mover's bounds:
                            Vector2 min = obstacleBounds.Min - creatureShape.Max;
                            Vector2 max = obstacleBounds.Max - creatureShape.Min;
                            Bounds2 totalBounds = new Bounds2(min, max - min);
                            obstacles.Add(totalBounds);

                            if (Game.DebugCollision && creature == Player)
                            {
                                diagnostics.Add(() => Engine.DrawRectEmpty(obstacleBounds.Translated(Origin), Color.Green));
                            }
                        }
                    }
                }

                // Calculate collision along the X and Y axes independently.
                // Each calculation considers only motion along that axis.
                // Performing these steps sequentially ensures that objects can't move diagonally through other objects.

                // X step:
                Vector2 position = creature.Position;
                Vector2 newPosition = position + new Vector2(motion.X, 0);
                foreach (Bounds2 bounds in obstacles)
                {
                    // Leftward:
                    if (motion.X < 0 &&
                        position.Y > bounds.Position.Y &&
                        position.Y < bounds.Position.Y + bounds.Size.Y)
                    {
                        float limit = bounds.Position.X + bounds.Size.X;
                        if (position.X >= limit && newPosition.X < limit) newPosition.X = limit;
                    }

                    // Rightward:
                    if (motion.X > 0 &&
                        position.Y > bounds.Position.Y &&
                        position.Y < bounds.Position.Y + bounds.Size.Y)
                    {
                        float limit = bounds.Position.X;
                        if (position.X <= limit && newPosition.X > limit) newPosition.X = limit;
                    }
                }
                creature.Position.X = newPosition.X;

                // Y step:
                position = creature.Position;
                newPosition = position + new Vector2(0, motion.Y);
                foreach (Bounds2 bounds in obstacles)
                {
                    // Upward:
                    if (motion.Y < 0 &&
                        position.X > bounds.Position.X &&
                        position.X < bounds.Position.X + bounds.Size.X)
                    {
                        float limit = bounds.Position.Y + bounds.Size.Y;
                        if (position.Y >= limit && newPosition.Y < limit) newPosition.Y = limit;
                    }

                    // Downward:
                    if (motion.Y > 0 &&
                        position.X > bounds.Position.X &&
                        position.X < bounds.Position.X + bounds.Size.X)
                    {
                        float limit = bounds.Position.Y;
                        if (position.Y <= limit && newPosition.Y > limit) newPosition.Y = limit;
                    }
                }
                creature.Position.Y = newPosition.Y;

                if (Game.DebugCollision && creature == Player)
                {
                    diagnostics.Add(() =>
                    {
                        Engine.DrawRectEmpty(creatureShape.Translated(Origin + creature.Position), Color.Green);
                    });
                }
            }

            // Slow to a stop when there is no input -- separately on each axis:
            if (creature.Movement.X == 0)
            {
                float speed = Math.Abs(creature.Velocity.X);
                speed = Math.Max(0, speed - creature.Deceleration);
                creature.Velocity.X = Math.Sign(creature.Velocity.X) * speed;
            }

            if (creature.Movement.Y == 0)
            {
                float speed = Math.Abs(creature.Velocity.Y);
                speed = Math.Max(0, speed - creature.Deceleration);
                creature.Velocity.Y = Math.Sign(creature.Velocity.Y) * speed;
            }
        }

        //==========================================================================================
        // Scroll to keep the player onscreen:
        //==========================================================================================
        {
            Vector2 margin = new Vector2(300, 250);
            Origin.X = Clamp(Origin.X,
                -Player.Position.X + margin.X,
                -(Player.Position.X + Assets.PropTiles.DestinationSize.X) + Game.Resolution.X - margin.X);
            Origin.Y = Clamp(Origin.Y,
                -Player.Position.Y + margin.Y,
                -(Player.Position.Y + Assets.PropTiles.DestinationSize.Y) + Game.Resolution.Y - margin.Y);
        }

        //==========================================================================================
        // Draw the static part of the map:
        //==========================================================================================
        for (int row = 0; row < MapHeight; row++)
        {
            for (int column = 0; column < MapWidth; column++)
            {
                TileEngine.DrawTile(Assets.WallTiles, Walls[column, row], Origin + new Vector2(column, row) * Assets.CellSize);
            }
        }

        //==========================================================================================
        // Draw creatures back-to-front:
        //==========================================================================================
        foreach (Creature creature in Creatures.OrderBy(x => x.IsFlat ? 0 : 1).ThenBy(x => x.Position.Y))
        {
            TileEngine.DrawTile(Assets.PropTiles, creature.Appearance[creature.Frame], Origin + creature.Position,
                mirror: creature.Facing);

            if (advanceFrame)
            {
                creature.Frame = (creature.Frame + 1) % creature.Appearance.Length;
            }
        }

        //==========================================================================================
        // Draw UI:
        //==========================================================================================
        if (InConversation)
        {
            string speech = Conversers[0].Conversation[ConversationPage];
            int width = 16;
            int height = 3;
            Vector2 pos = new TileIndex((20 - width) / 2, 12 - height) * Assets.CellSize;
            DrawBorder(pos, width, height);
            pos += 0.5f * Assets.CellSize;
            TileEngine.DrawTileString(Assets.FontTiles, speech, pos);
        }
        else if (availableConversers.Count > 0)
        {
            string text = "\x18 Converse";
            Vector2 pos = new Vector2((20 - (text.Length + 1) / 2) / 2, 11) * Assets.CellSize;
            TileEngine.DrawTileString(Assets.FontTiles, text, pos);
        }
        else if (ShowMoveTip)
        {
            string text = "\x1C \x1D \x1E \x1F Move";
            Vector2 pos = new Vector2((20 - (text.Length + 1) / 2) / 2, 11) * Assets.CellSize;
            TileEngine.DrawTileString(Assets.FontTiles, text, pos);
        }

        //==========================================================================================
        // End of game cinematics:
        //==========================================================================================
        if (State == GameState.Losing)
        {
            if (advanceFrame)
            {
                EndGameFrame += 1;
                if (EndGameFrame == Assets.EndMusicFrame) Engine.PlayMusic(Assets.LosingMusic, fadeTime: 6.0f);
            }
            Color background = Color.Black.WithAlpha(EndGameFrame / 5f);
            Engine.DrawRectSolid(new Bounds2(Vector2.Zero, Game.Resolution), background);

            string text = "You died";
            float textAlpha = Clamp((EndGameFrame - 8) / 5f, 0, 1);
            Color textColor = Assets.LosingTextColor.WithAlpha(textAlpha);
            Vector2 pos = new Vector2((20 - (text.Length + 1) / 2) / 2, 5) * Assets.CellSize;
            TileEngine.DrawTileString(Assets.FontTiles, text, pos, color: textColor);
        }
        else if (State == GameState.Winning)
        {
            if (advanceFrame)
            {
                EndGameFrame += 1;
                if (EndGameFrame == Assets.EndMusicFrame) Engine.PlayMusic(Assets.WinningMusic, fadeTime: 6.0f);
            }
            Color background = Color.White.WithAlpha(EndGameFrame / 5f);
            Engine.DrawRectSolid(new Bounds2(Vector2.Zero, Game.Resolution), background);

            string text = "You escaped!";
            float textAlpha = Clamp((EndGameFrame - 8) / 5f, 0, 1);
            Color textColor = Assets.WinningTextColor.WithAlpha(textAlpha);
            Vector2 pos = new Vector2((20 - (text.Length + 1) / 2) / 2, 5) * Assets.CellSize;
            TileEngine.DrawTileString(Assets.FontTiles, text, pos, color: textColor);
        }

        if (EndGameFrame > 20)
        {
            string text = "\x1A New game";
            Vector2 pos = new Vector2((20 - (text.Length + 1) / 2) / 2, 11) * Assets.CellSize;
            Color color = (State == GameState.Winning) ? Assets.WinningTextColor : Assets.LosingTextColor;
            TileEngine.DrawTileString(Assets.FontTiles, text, pos, color: color);

            if (Engine.GetKeyDown(Key.N))
            {
                Restart = true;
            }
        }

        //==========================================================================================
        // Debug information:
        //==========================================================================================
        if (Game.Debug)
        {
            foreach (Action action in diagnostics)
            {
                action();
            }
        }
    }