Skip to content

Collision and shooting mechanics. #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 10, 2019
Binary file modified assets/sprites-edit.psd
Binary file not shown.
110 changes: 110 additions & 0 deletions lib/map.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,115 @@
import mapinfo from '/assets/map.json';
import spritesheet from './spritesheet';
import * as math from '/lib/math';

const tileNormals = [
{ x: 0, y: -1 },
{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 },
];

// This is fever dream based collision detection.
// Idea: Checking which edge normal is aligned best
// Sources:
// - Reflection: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector
export function getCollider(x, y, lastX, lastY, nextVelocity) {
const cmap = getCollisionMap();
const tileX = Math.floor((x / $globalConfig.width) * cmap.width);
const tileY = Math.floor((y / $globalConfig.height) * cmap.height);
const worldTileSize = $globalConfig.width / cmap.width;

const index = tileX + tileY * cmap.width;
const colliderValue = cmap.data[index];

if (!colliderValue) {
return null;
}

const tileWorldPosX = (tileX / cmap.width) * $globalConfig.width;
const tileWorldPosY = (tileY / cmap.height) * $globalConfig.height;

const center = worldTileSize / 2;

// Offseting vertice to the center of each edge for better dot resolution where looking for most aligned edge normal
const vertices = [
{ x: tileWorldPosX + center, y: tileWorldPosY },
{ x: tileWorldPosX + worldTileSize, y: tileWorldPosY + center },
{
x: tileWorldPosX + worldTileSize - center,
y: tileWorldPosY + worldTileSize,
},
{ x: tileWorldPosX, y: tileWorldPosY + worldTileSize - center },
];

// This is only needed for debugging purpose.
const edges = [
// top edge
[
{ x: tileWorldPosX, y: tileWorldPosY },
{ x: tileWorldPosX + worldTileSize, y: tileWorldPosY },
],
// right edge
[
{ x: tileWorldPosX + worldTileSize, y: tileWorldPosY },
{ x: tileWorldPosX + worldTileSize, y: tileWorldPosY + worldTileSize },
],
// bottom edge
[
{ x: tileWorldPosX + worldTileSize, y: tileWorldPosY + worldTileSize },
{ x: tileWorldPosX, y: tileWorldPosY + worldTileSize },
],
// left edge
[
{ x: tileWorldPosX, y: tileWorldPosY + worldTileSize },
{ x: tileWorldPosX, y: tileWorldPosY },
],
];

var cloesestDot = -Number.MAX_VALUE;
var closestIndex = 0;

const lastPos = { x: lastX, y: lastY };

// Think about sub-stepping!
for (let i = 0; i < 4; i++) {
const dir = math.vecSub(lastPos, vertices[i]);
const dot = math.vecDot(dir, tileNormals[i]);

// Take into account if specific edge is facing another tile. If so, block it as possible selection.
// LAAAAAMEEE! We should validate against object being inside square. But whatever... works 99% of the time ;)
if (dot > cloesestDot) {
cloesestDot = dot;
closestIndex = i;
}
}

const closestNormal = tileNormals[closestIndex];
const closestEdge = edges[closestIndex];

return {
code: colliderValue,
tx: tileX,
ty: tileY,
x: tileWorldPosX + worldTileSize / 2,
y: tileWorldPosY + worldTileSize / 2,
edge: closestEdge,
velocity: math.vecReflect(nextVelocity, closestNormal),
};
}

let collisionMapMemoise;
export function getCollisionMap() {
if (collisionMapMemoise) {
return collisionMapMemoise;
}

collisionMapMemoise = mapinfo.layers.find(layer => {
return layer.name === 'collision';
});

return collisionMapMemoise;
}

export default function(ctx) {
mapinfo.layers.forEach(layer => {
Expand Down
30 changes: 30 additions & 0 deletions lib/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ export const lerp = (start, end, amt) => {

export const vecDot = (v1, v2) => v1.x * v2.x + v1.y * v2.y;

export const vecSub = (v1, v2) => ({
x: v1.x - v2.x,
y: v1.y - v2.y,
});

export const vecAdd = (v1, v2) => ({
x: v1.x + v2.x,
y: v1.y + v2.y,
});

export const vecLength = v => Math.sqrt(vecDot(v, v));

export const vecCross = (v1, v2) => v1.x * v2.y - v1.y * v2.x;
Expand All @@ -19,6 +29,26 @@ export const vecFromAngle = radians => ({
y: Math.sin(radians),
});

export const vecMult = (v1, v2) => ({
x: v1.x * v2.x,
y: v1.y * v2.y,
});

export const vecMultScalar = (v1, scalar) => ({
x: v1.x * scalar,
y: v1.y * scalar,
});

export const vecInvert = v1 => vecMultScalar(v1, -1);

// r=d−2(d⋅n)n
export const vecReflect = (v1, normal) => {
const dot = vecDot(v1, normal);
let result = vecMultScalar(normal, dot);
result = vecMultScalar(result, 2);
return vecSub(v1, result);
};

export const toRadians = angle => angle * (Math.PI / 180);

export const fromRadians = radians => radians * (180 / Math.PI);
10 changes: 9 additions & 1 deletion lib/shape.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ function drawCircle(ctx, x, y, r, fillStyle) {
ctx.fill();
}

export default { drawGrid, drawRect, drawCircle };
function drawLine(ctx, startPos, endPos, strokeStyle = '#FF0000') {
ctx.beginPath();
ctx.moveTo(startPos.x, startPos.y);
ctx.lineTo(endPos.x, endPos.y);
ctx.strokeStyle = strokeStyle;
ctx.stroke();
}

export default { drawGrid, drawRect, drawCircle, drawLine };
11 changes: 10 additions & 1 deletion src/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let config = {
maxShootWidth: 10,

// Shooting behaviour
angleSpeed: 5,
angleSpeed: 3.5,
powerSpeed: 0.025,
defaultPower: 0,
maxLenght: 25,
Expand Down Expand Up @@ -135,6 +135,15 @@ function updateBase(dt) {

function update(dt) {
updateBase(dt);

if (input.isDownOnce('Digit1')) {
if (!config.defaultPower) {
config.defaultPower = 1;
} else {
config.defaultPower = 0;
}
}

projectile.update(dt);
}

Expand Down
22 changes: 0 additions & 22 deletions src/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,6 @@ import menu from './menu';
import level from './level';
import levelBase from './level-base';

window.$globalConfig = {
// Draw bbox around entities and grid for a map
isDebugDraw: false,

// Print each keystroke
isDebugInput: false,

// TODO: Add button mapping for keys
playerInput: {
0: {
moveUpKey: 'ArrowUp',
moveDownKey: 'ArrowDown',
powerKey: 'ControlRight',
},
1: {
moveUpKey: 'KeyW',
moveDownKey: 'KeyS',
powerKey: 'Space',
},
},
};

let canvas;
let ctx;
let vw;
Expand Down
24 changes: 24 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
window.$globalConfig = {
width: 128,
height: 128,
// Draw bbox around entities and grid for a map
isDebugDraw: false,

// Print each keystroke
isDebugInput: true,

// TODO: Add button mapping for keys
playerInput: {
0: {
moveUpKey: 'ArrowUp',
moveDownKey: 'ArrowDown',
powerKey: 'ControlRight',
},
1: {
moveUpKey: 'KeyW',
moveDownKey: 'KeyS',
powerKey: 'Space',
},
},
};

import { init } from './game';

window.addEventListener('load', init, false);
35 changes: 30 additions & 5 deletions src/projectile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import shape from '/lib/shape';
import * as math from '/lib/math';
import { getCollider } from '/lib/map';

function update(dt) {}

Expand Down Expand Up @@ -28,30 +29,54 @@ function destroy(item) {
list.delete(item);
}

// For debug purpose only
let lastCollision = null;

function update(dt) {
for (let item of list) {
const { x, y, velocity, drag, idleVelocityMagnitude } = item;

if (math.vecLength(velocity) < idleVelocityMagnitude) {
// Destroy on idle
destroy(item);
return;
}

const nvelocity = {
let nvelocity = {
x: velocity.x * drag,
y: velocity.y * drag,
};

item.x = x + nvelocity.x * dt;
item.y = y + nvelocity.y * dt;
let nx = x + nvelocity.x * dt;
let ny = y + nvelocity.y * dt;

const collider = getCollider(nx, ny, x, y, nvelocity);
if (collider) {
lastCollision = collider;
const pos = { x, y };
const npos = { x: nx, y: ny };
const dir = math.vecSub(collider, npos);
nvelocity = collider.velocity;

nx = x + nvelocity.x * dt;
ny = y + nvelocity.y * dt;
}

item.x = nx;
item.y = ny;

item.velocity = nvelocity;
}
}

function draw(ctx) {
ctx.fillStyle = '#ffffff';
ctx.fillStyle = '#ff0000';

if (lastCollision) {
shape.drawCircle(ctx, lastCollision.x, lastCollision.y, 1);
shape.drawLine(ctx, lastCollision.edge[0], lastCollision.edge[1]);
}

ctx.fillStyle = '#ffffff';
for (let item of list) {
const { x, y } = item;
shape.drawCircle(ctx, x, y, 2);
Expand Down