diff --git a/Shared/Camera.cs b/Shared/Camera.cs
index 30d9f9e..f81a943 100644
--- a/Shared/Camera.cs
+++ b/Shared/Camera.cs
@@ -5,13 +5,18 @@ using System;
// https://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php
namespace SemiColinGames {
class Camera {
- private Rectangle bbox = new Rectangle(0, 0, 1920 / 4, 1080 / 4);
+ // Screen size in pixels is 1920x1080 divided by 4.
+ private Rectangle bbox = new Rectangle(0, 0, 480, 270);
public int Width { get => bbox.Width; }
public int Height { get => bbox.Height; }
public int Left { get => bbox.Left; }
public int Top { get => bbox.Top; }
+ public Matrix Projection {
+ get => Matrix.CreateOrthographicOffCenter(Left, Left + Width, Height, 0, -1, 1);
+ }
+
public void Update(Point player, int worldWidth) {
int diff = player.X - bbox.Center.X;
if (Math.Abs(diff) > 16) {
diff --git a/Shared/Player.cs b/Shared/Player.cs
index df22e3c..5636a79 100644
--- a/Shared/Player.cs
+++ b/Shared/Player.cs
@@ -11,7 +11,7 @@ namespace SemiColinGames {
Right = 1
};
- enum Pose { Walking, Standing, Crouching, Stretching, SwordSwing, Jumping };
+ public enum Pose { Walking, Standing, Crouching, Stretching, SwordSwing, Jumping };
private const int moveSpeed = 180;
private const int jumpSpeed = -600;
@@ -46,6 +46,15 @@ namespace SemiColinGames {
this.texture = texture;
}
+ // TODO: just make Facing an int.
+ public int GetFacing {
+ get { return (int) facing; }
+ }
+
+ public Pose GetPose {
+ get { return pose; }
+ }
+
public Point Position { get { return position; } }
public void Update(float modelTime, History input, AABB[] collisionTargets) {
@@ -147,102 +156,15 @@ namespace SemiColinGames {
} else {
pose = Pose.Standing;
}
-
- DrawSightLines(collisionTargets);
- }
-
- Vector2 Rotate(Vector2 point, float angle) {
- float cos = FMath.Cos(angle);
- float sin = FMath.Sin(angle);
- return new Vector2(
- point.X * cos - point.Y * sin,
- point.Y * cos + point.X * sin);
}
- bool PointInCone(
- float visionRangeSq, float fovCos, Vector2 eyePos, Vector2 direction, Vector2 test) {
- Vector2 delta = Vector2.Subtract(test, eyePos);
- if (delta.LengthSquared() > visionRangeSq) {
- return false;
+ public Vector2 EyePosition {
+ get {
+ bool walking = pose == Pose.Walking || pose == Pose.Jumping;
+ Vector2 eyeOffset = walking ? eyeOffsetWalking : eyeOffsetStanding;
+ return Vector2.Add(
+ Position.ToVector2(), new Vector2(eyeOffset.X * (int) facing, eyeOffset.Y));
}
- float dot = Vector2.Dot(Vector2.Normalize(direction), Vector2.Normalize(delta));
- return dot > fovCos;
- }
-
- void DrawSightLines(AABB[] collisionTargets) {
- float fov = FMath.DegToRad(45);
- float fovCos = FMath.Cos(fov);
- Color color = Color.LightYellow;
-
- Vector2 eyeOffset = pose == Pose.Walking ? eyeOffsetWalking : eyeOffsetStanding;
- Vector2 eyePos = Vector2.Add(
- Position.ToVector2(), new Vector2(eyeOffset.X * (int) facing, eyeOffset.Y));
-
- float visionRange = 150;
- float visionRangeSq = visionRange * visionRange;
- Vector2 ray = new Vector2(visionRange * (int) facing, 0);
- if (pose == Pose.Stretching) {
- ray = Rotate(ray, (int) facing * FMath.DegToRad(-30));
- }
- if (pose == Pose.Crouching) {
- ray = Rotate(ray, (int) facing * FMath.DegToRad(30));
- }
- Vector2 coneBottom = Rotate(ray, fov);
- Vector2 coneTop = Rotate(ray, -fov);
-
- List points = new List();
- List boxes = new List();
- points.Add(Vector2.Add(eyePos, coneBottom));
- points.Add(Vector2.Add(eyePos, coneTop));
- foreach (AABB box in collisionTargets) {
- int hitCount = points.Count;
- if (PointInCone(visionRangeSq, fovCos, eyePos, ray, box.TopLeft)) {
- points.Add(box.TopLeft);
- }
- if (PointInCone(visionRangeSq, fovCos, eyePos, ray, box.TopRight)) {
- points.Add(box.TopRight);
- }
- if (PointInCone(visionRangeSq, fovCos, eyePos, ray, box.BottomLeft)) {
- points.Add(box.BottomLeft);
- }
- if (PointInCone(visionRangeSq, fovCos, eyePos, ray, box.BottomRight)) {
- points.Add(box.BottomRight);
- }
- if (points.Count > hitCount) {
- boxes.Add(box);
- Debug.AddRect(box, color);
- }
- }
-
- HashSet boxesSeen = new HashSet();
- foreach (Vector2 point in points) {
- float minTime = 1;
- AABB? closestBox = null;
- Vector2 delta = Vector2.Subtract(point, eyePos);
- foreach (AABB box in boxes) {
- Hit? maybeHit = box.IntersectSegment(eyePos, delta);
- if (maybeHit != null) {
- float time = FMath.Clamp(maybeHit.Value.Time, 0, 1);
- Vector2 target = Vector2.Add(eyePos, Vector2.Multiply(delta, time));
- Debug.AddLine(eyePos, target, color);
- if (time < minTime) {
- minTime = time;
- closestBox = box;
- }
- }
- }
- if (closestBox != null) {
- boxesSeen.Add(closestBox.Value);
- }
- }
-
- foreach (AABB box in boxesSeen) {
- Debug.AddRect(box, Color.Orange);
- }
-
- Debug.AddLine(eyePos, Vector2.Add(eyePos, ray), Color.Red);
- Debug.AddLine(eyePos, Vector2.Add(eyePos, coneTop), Color.Red);
- Debug.AddLine(eyePos, Vector2.Add(eyePos, coneBottom), Color.Red);
}
// Returns the desired (dx, dy) for the player to move this frame.
diff --git a/Shared/SneakGame.cs b/Shared/SneakGame.cs
index 8bb5d6b..09e3210 100644
--- a/Shared/SneakGame.cs
+++ b/Shared/SneakGame.cs
@@ -11,7 +11,10 @@ namespace SemiColinGames {
const double TARGET_FRAME_TIME = 1.0 / TARGET_FPS;
readonly GraphicsDeviceManager graphics;
- RenderTarget2D renderTarget;
+ RenderTarget2D sceneTarget;
+ RenderTarget2D lightingTarget;
+
+ BasicEffect lightingEffect;
SpriteBatch spriteBatch;
SpriteFont font;
@@ -54,10 +57,23 @@ namespace SemiColinGames {
Debug.Initialize(GraphicsDevice);
- renderTarget = new RenderTarget2D(
+ sceneTarget = new RenderTarget2D(
+ GraphicsDevice, camera.Width, camera.Height, false /* mipmap */,
+ GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
+ lightingTarget = new RenderTarget2D(
GraphicsDevice, camera.Width, camera.Height, false /* mipmap */,
GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
+ lightingEffect = new BasicEffect(GraphicsDevice);
+ lightingEffect.World = Matrix.CreateTranslation(0, 0, 0);
+ lightingEffect.View = Matrix.CreateLookAt(Vector3.Backward, Vector3.Zero, Vector3.Up);
+ lightingEffect.VertexColorEnabled = true;
+
+ RasterizerState rasterizerState = new RasterizerState() {
+ CullMode = CullMode.None
+ };
+ GraphicsDevice.RasterizerState = rasterizerState;
+
base.Initialize();
}
@@ -67,7 +83,7 @@ namespace SemiColinGames {
font = Content.Load("font");
player = new Player(Content.Load("Ninja_Female"));
- world = new World(Content.Load("grassland"), Levels.DEMO);
+ world = new World(Content.Load("grassland"), Levels.ONE_ONE);
grasslandBg1 = Content.Load("grassland_bg1");
grasslandBg2 = Content.Load("grassland_bg2");
}
@@ -131,8 +147,8 @@ namespace SemiColinGames {
Debug.SetFpsText(fpsText);
- // Draw scene to RenderTarget.
- GraphicsDevice.SetRenderTarget(renderTarget);
+ // Draw scene to sceneTarget.
+ GraphicsDevice.SetRenderTarget(sceneTarget);
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
@@ -148,7 +164,8 @@ namespace SemiColinGames {
// Set up transformation matrix for drawing world objects.
Matrix transform = Matrix.CreateTranslation(-camera.Left, -camera.Top, 0);
- spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null, null, transform);
+ spriteBatch.Begin(
+ SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null, null, transform);
// Draw player.
player.Draw(spriteBatch);
@@ -162,7 +179,13 @@ namespace SemiColinGames {
// Aaaaand we're done.
spriteBatch.End();
- // Draw RenderTarget to screen.
+ // Draw lighting to lightingTarget.
+ GraphicsDevice.SetRenderTarget(lightingTarget);
+ GraphicsDevice.Clear(new Color(0, 0, 0, 0f));
+ lightingEffect.Projection = camera.Projection;
+ DrawFov();
+
+ // Draw sceneTarget to screen.
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);
if (framesToSuppress == 0) {
@@ -171,7 +194,8 @@ namespace SemiColinGames {
RasterizerState.CullNone);
Rectangle drawRect = new Rectangle(
0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
- spriteBatch.Draw(renderTarget, drawRect, Color.White);
+ spriteBatch.Draw(sceneTarget, drawRect, Color.White);
+ spriteBatch.Draw(lightingTarget, drawRect, Color.White);
// Draw debug toasts.
Debug.DrawToasts(spriteBatch, font);
@@ -182,5 +206,77 @@ namespace SemiColinGames {
base.Draw(gameTime);
drawTimer.Stop();
}
+
+ private void DrawFov() {
+ // TODO: DrawIndexedPrimitives
+ Color color = Color.FromNonPremultiplied(new Vector4(0, 0, 1, 0.6f));
+ Vector2 eyePos = player.EyePosition;
+ int numConePoints = 60;
+ // TODO: don't new[] every frame.
+ VertexPositionColor[] conePoints = new VertexPositionColor[numConePoints];
+ float visionRange = 150;
+ float visionRangeSq = visionRange * visionRange;
+ float fov = FMath.DegToRad(120);
+ float fovStep = fov / (numConePoints - 1);
+
+ Vector2 ray = new Vector2(visionRange * player.GetFacing, 0);
+ if (player.GetPose == Player.Pose.Stretching) {
+ ray = ray.Rotate(player.GetFacing * FMath.DegToRad(-30));
+ }
+ if (player.GetPose == Player.Pose.Crouching) {
+ ray = ray.Rotate(player.GetFacing * FMath.DegToRad(30));
+ }
+
+ for (int i = 0; i < conePoints.Length; i++) {
+ float angle = -fov / 2 + fovStep * i;
+ Vector2 rotated = ray.Rotate(angle);
+ Vector2 closestHit = Vector2.Add(eyePos, rotated);
+ float hitTime = 1f;
+
+ Vector2 halfTileSize = new Vector2(World.TileSize / 2.0f, World.TileSize / 2.0f);
+ for (int j = 0; j < world.CollisionTargets.Length; j++) {
+ AABB box = world.CollisionTargets[j];
+ if (Math.Abs(box.Position.X - player.Position.X) > visionRange + halfTileSize.X) {
+ continue;
+ }
+ Vector2 delta = Vector2.Add(halfTileSize, Vector2.Subtract(box.Position, eyePos));
+ if (delta.LengthSquared() > visionRangeSq) {
+ continue;
+ }
+ Hit? maybeHit = box.IntersectSegment(eyePos, rotated);
+ if (maybeHit != null) {
+ Hit hit = maybeHit.Value;
+ if (hit.Time < hitTime) {
+ hitTime = hit.Time;
+ closestHit = hit.Position;
+ }
+ }
+ }
+
+ float tint = 0.6f - hitTime / 2;
+ Color tinted = Color.FromNonPremultiplied(new Vector4(0, 0, 1, tint));
+ conePoints[i] = new VertexPositionColor(new Vector3(closestHit, 0), tinted);
+ }
+
+ // TODO: don't new[] every frame.
+ VertexPositionColor[] vertices = new VertexPositionColor[numConePoints * 3];
+ VertexPositionColor eyeVertex = new VertexPositionColor(new Vector3(eyePos, 0), color);
+ for (int i = 0; i < numConePoints - 1; i++) {
+ vertices[i * 3] = eyeVertex;
+ vertices[i * 3 + 1] = conePoints[i];
+ vertices[i * 3 + 2] = conePoints[i + 1];
+ }
+
+ VertexBuffer vertexBuffer = new VertexBuffer(
+ GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.WriteOnly);
+ vertexBuffer.SetData(vertices);
+
+ GraphicsDevice.SetVertexBuffer(vertexBuffer);
+
+ foreach (EffectPass pass in lightingEffect.CurrentTechnique.Passes) {
+ pass.Apply();
+ GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, vertices.Length / 3);
+ }
+ }
}
}