I am making a game in HTML5 using the the svg
tag for the graphics to provide multi-resolution displays. The majority of the game is complete, but in testing I have just come across a major bug which involves the SVG objects being visible, despite being outside of the viewbox on non-native resolutions. I am not sure if this is a flaw in my code, or with the browser itself (Google Chrome 54.0.2840.99 m (64-bit), and 57.0.2950.0 (Official Build) canary (64-bit)). I am having to use Google Chrome because I am using WebSQL in the project (it is not in many major browsers).
The project is a top down scrolling maze game, with shadows that are drawn via raycast filled polygons from shape vertices. The shadows are only drawn if one or more the vertices they are "attached" to are on the screen. The issue is that when I lower my resolution (my default is 1366 x 768) then they do not draw to the top/bottom of the screen as they normally do. It should look like this.
Below is a majorly cut-down version of my code. I would assume that if there were an error, in my code, it would be inside the shadowrays()
function. Normally, the program would be run in fullscreen mode (F11) to ensure that it is the same size as the window. To move the screen, use the w, a, s and d keys
window.setInterval(function() {
if (draw) {
if (keystate[controls[2]]) {
xvelocity -= 2;
}
if (keystate[controls[3]]) {
xvelocity += 2;
}
xvelocity = xvelocity * 0.93;
xscroll += xvelocity;
svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
centerplayer();
if (checkcollision()) {
xscroll -= xvelocity;
xvelocity = xvelocity * -0.8;
}
if (keystate[controls[0]]) {
yvelocity += 2;
}
if (keystate[controls[1]]) {
yvelocity -= 2;
}
yvelocity = yvelocity * 0.93;
yscroll -= yvelocity;
svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
centerplayer();
if (checkcollision()) {
yscroll += yvelocity;
yvelocity = yvelocity * -0.8;
}
shadowrays();
}
}, 16.666666666666667);
var keystate = {};
window.addEventListener("keydown", function(e) {
if (!(((e.keycode || e.which) > 111) && ((e.keyCode || e.which) < 124))) {
keystate[e.keycode || e.which] = true;
}
}, true);
window.addEventListener("keyup", function(e) {
keystate[e.keycode || e.which] = false;
}, true);
function shadowrays() {
var kill = document.getElementsByClassName("shadowray");
while (kill[0]) {
kill[0].parentNode.removeChild(kill[0]);
}
var len = level.length,
shape = {},
points = [],
poly = [],
p1x, p2x, p1y, p2y, p3x, p3y, p4x, p4y, p5x, p5y, p6x, p6y, cx = player.cx.animVal.value,
cy = player.cy.animVal.value,
m, i, n;
for (i = 1; i < len; i += 1) {
shape = level[i];
points = [shape.x, shape.y, shape.x, shape.y + shape.height, shape.x + shape.width, shape.y + shape.height, shape.x + shape.width, shape.y];
for (n = 0; n < 4; n += 1) {
p1x = points[(n * 2) % 8];
p2x = points[(n * 2 + 2) % 8];
p1y = points[(n * 2 + 1) % 8];
p2y = points[(n * 2 + 3) % 8];
if ((((Math.abs(p1x - cx) <= dimensions.width / 2) || (Math.abs(p2x - cx) <= dimensions.width / 2)) && ((Math.abs(p1y - cy) <= dimensions.height / 2) || (Math.abs(p2y - cy) <= dimensions.height / 2))) && shape.id != "enclosure") {
m = (p1y - cy) / (p1x - cx);
p6y = m * (cx + dimensions.width / 2 * (Math.abs(p1x - cx) / (p1x - cx))) - m * cx + cy;
if (Math.abs(p6y - cy) <= dimensions.height / 2) {
p6x = (p6y - cy) / m + cx;
p5x = p6x;
p5y = cy + dimensions.height / 2 * (Math.abs(p6y - cy) / (p6y - cy));
} else {
p6y = cy + dimensions.height / 2 * (Math.abs(p6y - cy) / (p6y - cy));
p6x = (p6y - cy) / m + cx;
p5x = cx + dimensions.width / 2 * (Math.abs(p6x - cx) / (p6x - cx));
p5y = p6y;
}
m = (p2y - cy) / (p2x - cx);
p3y = m * (cx + dimensions.width / 2 * (Math.abs(p2x - cx) / (p2x - cx))) - m * cx + cy;
if (Math.abs(p3y - cy) <= dimensions.height / 2) {
p3x = (p3y - cy) / m + cx;
p4x = p3x;
p4y = cy + dimensions.height / 2 * (Math.abs(p3y - cy) / (p3y - cy));
} else {
p3y = cy + dimensions.height / 2 * (Math.abs(p3y - cy) / (p3y - cy));
p3x = (p3y - cy) / m + cx;
p4x = Number(cx) + Number(dimensions.width) / 2 * (Math.abs(p3x - cx) / (p3x - cx));
p4y = p3y;
}
poly = [];
poly.push(p1x + ',' + p1y, p2x + ',' + p2y, p3x + ',' + p3y, p4x + ',' + p4y, p5x + ',' + p5y, p6x + ',' + p6y);
createpolygon(poly, "", ("s" + i + n), "shadowray");
}
}
}
}
function createpolygon(points, filter, id, classt) {
var polygon = document.createElementNS(svgns, "polygon"),
data_points = "",
max = points.length,
z;
polygon.setAttributeNS(null, "id", "p" + id);
for (z = 0; z < max; z += 1) {
data_points += points[z] + " ";
}
polygon.setAttributeNS(null, "points", data_points);
if (filter !== "") {
polygon.setAttributeNS(null, "filter", filter);
}
polygon.setAttributeNS(null, "class", classt);
svg.appendChild(polygon);
}
function centerplayer() {
player.setAttributeNS(null, "cx", (xscroll + dimensions.width / 2));
player.setAttributeNS(null, "cy", (yscroll + dimensions.height / 2));
var hitbox = document.getElementById("rhb");
hitbox.setAttributeNS(null, "width", player.r.animVal.value * 2);
hitbox.setAttributeNS(null, "height", player.r.animVal.value * 2);
// var hitboxbbox = hitbox.getBBox();
hitbox.setAttributeNS(null, "transform", ("translate(" + (xscroll + dimensions.width / 2 - player.r.animVal.value) + ", " + (yscroll + dimensions.height / 2 - player.r.animVal.value) + ")"));
}
function draw(lvl) {
var objects = lvl.length,
i;
xscroll = Number(lvl[0].xspawn);
yscroll = Number(lvl[0].yspawn);
xvelocity = 0;
yvelocity = 0;
for (i = 1; i < objects; i += 1) {
var object = lvl[i];
if (object.type == "rect") {
createrectangle(object.x, object.y, object.width, object.height, "", object.id, object.class);
} else if (object.type == "circle") {
createcircle(object.x, object.y, object.r, "", object.id, object.class);
} else if (object.type == "polygon") {
createpolygon(object.points, "", object.id, object.class);
}
}
createcircle("7680", "4320", "50", "", "player", "st1");
createrectangle("0", "0", "100", "100", "", "hb", "st2");
createrectangle(lvl[0].xexit - 50, lvl[0].yexit - 50, "100", "100", "", "exit", "exit");
drawn = true;
}
function createcircle(posx, posy, radius, filter, id, classt) {
var circle = document.createElementNS(svgns, "circle");
circle.setAttributeNS(null, "id", "c" + id);
circle.setAttributeNS(null, "cx", posx);
circle.setAttributeNS(null, "cy", posy);
circle.setAttributeNS(null, "r", radius);
if (filter !== "") {
circle.setAttributeNS(null, "filter", filter);
}
circle.setAttributeNS(null, "class", classt);
svg.appendChild(circle);
}
function createrectangle(posx, posy, width, height, filter, id, classt) {
var rectangle = document.createElementNS(svgns, "rect");
rectangle.setAttributeNS(null, "id", "r" + id);
rectangle.setAttributeNS(null, "x", posx);
rectangle.setAttributeNS(null, "y", posy);
rectangle.setAttributeNS(null, "width", width);
rectangle.setAttributeNS(null, "height", height);
if (filter !== "") {
rectangle.setAttributeNS(null, "filter", filter);
}
rectangle.setAttributeNS(null, "class", classt);
svg.appendChild(rectangle);
}
var svg = document.getElementById("canvas"), //the svg element
dimensions = {
width: 1920,
height: 1080
}, //view dimensions
svgns = "http://www.w3.org/2000/svg", //the SVG name space address
player, //player is used later in game
level, xvelocity, yvelocity, xscroll, yscroll, //current level, speeds and positions
controls = ["87", "83", "65", "68", "32"],
drawn = false;
function checkcollision() {
var r = player.r.animVal.value,
frame = document.getElementById("renclosure").getBBox(),
i;
if ((player.cx.animVal.value < r) || (player.cy.animVal.value < r) || (player.cx.animVal.value > frame.width - r) || (player.cy.animVal.value > frame.height - r)) {
return true;
}
var collisions = [],
len, r0 = document.getElementById("rhb").getBoundingClientRect(),
r1 = svg.createSVGRect();
r1.x = r0.left;
r1.y = r0.top;
r1.width = r0.width;
r1.height = r0.height;
var collisions_node = svg.getIntersectionList(r1, null);
len = collisions_node.length;
for (i = 0; i < len; i += 1) {
collisions.push(collisions_node[i]);
}
for (i = len - 1; i >= 0; i -= 1) {
if (collisions[i].id == "renclosure" || collisions[i].id == "cplayer" || collisions[i].id == "rpain" || collisions[i].id == "rhb" || collisions[i].className.animVal == "shadowray") {
collisions.splice(i, 1);
} else if (collisions[i].id == "rexit") { //winner
//DO STUFF
}
}
if (collisions.length > 0) {
return true;
} else {
return false;
}
}
level = [{
"id": "l0",
"name": "pretty", //level name
"description": "oooooo shadows", //desciption
"xspawn": 0, //where the player starts
"yspawn": 0,
"xexit": 2200, //where the exit it
"yexit": 2200,
"bits": 50 //umber of qbits in the level
}, {
id: "enclosure", //shape id
type: "rect", //shape type
x: 0, //top left x
y: 0, //top left y
class: "obstacle_box", //class for style
width: 5760, //width
height: 2160 //height
}, {
id: "1",
type: "rect",
x: 150,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "2",
type: "rect",
x: 350,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "3",
type: "rect",
x: 550,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "4",
type: "rect",
x: 750,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "5",
type: "rect",
x: 950,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "6",
type: "rect",
x: 1150,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "7",
type: "rect",
x: 1350,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "8",
type: "rect",
x: 1550,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "9",
type: "rect",
x: 1750,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "10",
type: "rect",
x: 1950,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "11",
type: "rect",
x: 2150,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "12",
type: "rect",
x: 2350,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "13",
type: "rect",
x: 2550,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "14",
type: "rect",
x: 2750,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "15",
type: "rect",
x: 2950,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "16",
type: "rect",
x: 3150,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "17",
type: "rect",
x: 3350,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "18",
type: "rect",
x: 3550,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "19",
type: "rect",
x: 3750,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}, {
id: "20",
type: "rect",
x: 3950,
y: 200,
class: "obstacle_box",
width: 100,
height: 100
}];
draw(level);
player = document.getElementById("cplayer");
svg.setAttribute("viewBox", (xscroll + " " + yscroll + " " + dimensions.width + " " + dimensions.height));
centerplayer();
shadowrays();
html,
body {
/*default font for all elements*/
font-family: Calibri;
/*ensures page is flush*/
margin: 0px;
width: 100%;
height: 100%;
/*default position of text*/
text-align: center;
/*gets rid of scrollbars*/
overflow: hidden;
/*cursor is always the normal pointer*/
cursor: default;
}
#canvas {
/*makes it flush with the page*/
height: 100%;
width: 100%;
padding: 0;
/*black background*/
background-color: #000;
/*more flushing*/
position: fixed;
left: 0px;
top: 0px;
border: none;
}
#border {
height: 100%;
width: 100%;
padding: 0;
/*black background*/
background: none;
/*more flushing*/
position: fixed;
left: 0px;
top: 0px;
border: 1px solid #000;
}
/*currently the player*/
.st1 {
/*it's black and no outline*/
fill: black;
stroke: none;
}
/*the main black bits of the levels that are collidable*/
.obstacle_box {
/*black fill*/
fill: #000;
/*black outline*/
stroke: #000;
stroke-width: 1;
/*prevents corner curling*/
stroke-miterlimit: 10;
}
/*the hitbox thingy*/
.st2 {
/*no fill but darkish outline*/
fill: none;
stroke: #999;
stroke-width: 1;
stroke-miterlimit: 10;
}
/*the exit*/
.exit {
/*greenish fill*/
fill: #0C3;
/*greener outline*/
stroke: #00FF40;
stroke-width: 5;
/*makes the line ends round*/
stroke-linecap: round;
/*makes them dashed*/
stroke-dasharray: 10, 10;
/*animates in a loop the outline*/
animation: exit 0.5s linear infinite;
}
/*outline effect for the exit*/
@keyframes exit {
0% {
/*outline breaks offset*/
stroke-dashoffset: 0
}
100% {
stroke-dashoffset: 20
}
}
.shadowray {
/*black*/
fill: #000;
/*black*/
stroke: #000;
stroke-width: 1;
stroke-miterlimit: 10;
/*visible*/
opacity: 1;
}
/*the edge of the level*/
#renclosure {
/*the background you actually see*/
fill: #EEE;
}
<svg id="canvas" viewBox="0 0 1920 1080">
<!--for adding SVG filters to-->
<defs id="filters"></defs>
</svg>
<div id="border"></div>
Turns out there was a really simple solution, all I had to do was add a
preserveAspectRatio="none"
into thesvg
tag and this would make it stretch to fill all windows.