Tile based javascript game - laggy scroll - javascript

I am creating a tile based game in javascript. I am absolutely beginner.
The plan is to learn javascript while I trying to make this game. I am having serious lagging issues when I am trying to scroll the "game"
For a live preview you can see here whats bad:
http://iwansfactory.com/tycoon/index.html
My javascript generates them and they HTML part looks like this:
<div class="tiletype100" id="x0y0" style="left: 2151px; top: -540px;"></div>
The css:
.tiletype2 {
z-index: 0;
position: absolute;
width: 600px;
height: 800px;
background-image: url("http://iwansfactory.com/tycoon/road2.png");
background-repeat: no-repeat;
}
The javascript scroll function is this:
var right = document.documentElement.scrollLeft;
var bottom = document.documentElement.scrollTop;
var rightscrollvalue = 0;
var bottomscrollvalue = 0;
function rightenter() {
rightscrollvalue = 40;
scrollright();
}
function leftenter() {
rightscrollvalue = -40;
scrollright();
}
function rightout() {
rightscrollvalue = 0;
}
function scrollright() {
if (rightscrollvalue != 0) {
right = right + rightscrollvalue;
console.log(right);
window.scrollTo(right, bottom);
setTimeout(function() {
scrollright();
}, 50);
}
}
function bottomenter() {
bottomscrollvalue = 40;
scrollbottom();
}
function topenter() {
bottomscrollvalue = -40;
scrollbottom();
}
function bottomout() {
bottomscrollvalue = 0;
}
function scrollbottom() {
if (bottomscrollvalue != 0) {
bottom = bottom + bottomscrollvalue;
console.log(bottom);
window.scrollTo(right, bottom);
setTimeout(function() {
scrollbottom();
}, 50);
}
}

Your design uses large overlapping tiles that are mostly transparent. This requires a lot of CPU power for rendering, thus making the game laggy.
I propose you make your tiles smaller, just as large as they have to be (so there is a non-transparent pixel on every edge of the image) and use offsets for larger tiles so they get rendered at the right position.

Related

How to fade smoothly between gradients in JavaScript

This code populates a div with a predefined set of gradients and fades through them in a cycle using jQuery's .animate() method:
/// Background Gradient Cycler
var gradients = [
['#9eb5d7', '#242424'],
['#efe2ae', '#a8acc9'],
['#6f7554', '#eee1ad']
]
var gradientsRev = gradients.reverse()
var gradientCover = document.getElementById('gradientCover');
for (var g = 0; g < gradientsRev.length; g++) {
var gradEl = document.createElement('div')
gradEl.className = 'gradient'
gradEl.style.background = `linear-gradient(${gradientsRev[g][0]}, ${gradientsRev[g][1]})`;
gradientCover.appendChild(gradEl)
}
var gradientEls = document.querySelectorAll('#gradientCover .gradient')
function gradientCycler() {
function gradeFade(i, opDest) {
var fadeDur = 20000
$(gradientEls[i]).animate({
'opacity': opDest
}, {
duration: fadeDur,
complete: function() {
if (parseInt(i) > 1) {
if (parseInt(opDest) === 0) gradeFade(i - 1, 0)
else gradFadeStart()
} else {
gradeFade(gradientEls.length - 1, 1)
}
}
})
}
var gradFadeStart = function() {
$('.gradient').css('opacity', 1);
gradeFade(gradientEls.length - 1, 0)
}
gradFadeStart()
}
gradientCycler()
body {
margin: 0;
overflow: hidden;
}
#gradientCover,
.gradient {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="page">
<div id="gradientCover"></div>
</div>
The problem is that the transition at certain parts is visibly choppy, with banding artifacts --
What can be done to reduce this artifacting so that the transition between the gradients appears smoother and less choppy?
I think is related to color depth, As a former CG artist I have seen these "artefacts" in software like Maya and Photoshop, to solve the problem it was necessary to increase the number of bits per channel (in Photoshop going from 8 Bits/Channel to 16).
Normally, this issue of bands appears when the two colors of the gradient are close (in term of RGB values) because there are few values of colors available between these two colors
If the gradient is rendered OR displayed (due to the monitor limitation) at a low number of bits per channel these banding effect can appear.
You can check your monitor color depth here.
You can also apply CSS according to this value:
if (screen.colorDepth <= 8)
//simple blue background color for 8 bit screens
document.body.style.background = "#0000FF"
else
//fancy blue background color for modern screens
document.body.style.background = "#87CEFA"
Including my codepen tests here as an answer just in case it helps anyone. I managed to get some improved performance by pre-rendering PNGs with the canvas' .toDataURL() method and animating through them in jQuery (also achieved about the same performance with this method but using Three.js):
https://codepen.io/tv/zxBbgK
Here's a working example of the JQuery method:
/// Background Gradient Cycler
var gradients = [
['#9eb5d7', '#242424'],
['#efe2ae', '#a8acc9'],
['#6f7554', '#eee1ad']
]
var gradientsRev = gradients.reverse()
var gradientCover = document.getElementById('gradientCover');
for (var g = 0; g < gradientsRev.length; g++) {
var gradEl = document.createElement('div')
gradEl.className = 'gradient'
var gradCanv = document.createElement('canvas')
gradCanv.className = 'gradient'
var ctx = gradCanv.getContext("2d");
var grd = ctx.createLinearGradient(0, 0, 0, gradCanv.height);
grd.addColorStop(0, gradients[g][0]);
grd.addColorStop(1, gradients[g][1]);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, gradCanv.width, gradCanv.height);
var gradIm = gradCanv.toDataURL("img/png")
gradEl.style.backgroundImage = `url(${gradIm})`
gradientCover.appendChild(gradEl)
}
var gradientEls = document.querySelectorAll('#gradientCover .gradient')
function gradientCycler() {
function gradeFade(i, opDest) {
var fadeDur = 20000
$(gradientEls[i]).animate({
'opacity': opDest
}, {
duration: fadeDur,
complete: function() {
if (parseInt(i) > 1) {
if (parseInt(opDest) === 0) gradeFade(i - 1, 0)
else gradFadeStart()
} else {
gradeFade(gradientEls.length - 1, 1)
}
}
})
}
var gradFadeStart = function() {
$('.gradient').css('opacity', 1);
gradeFade(gradientEls.length - 1, 0)
}
gradFadeStart()
}
gradientCycler()
body {
margin: 0;
overflow: hidden;
}
#gradientCover,
.gradient {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-size: 100%;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="page">
<div id="gradientCover"></div>
</div>

Sprite PNG Javascript animation not working

I'm trying to create a Sprite animation using the following image:
To do so I am using it as a background and am trying to manipulate the background's position when animating. Somehow I can't get it working though - it shows the last frame from the very beginning.
Image: https://i.imgur.com/06vjVVj.png - 30800x1398 and 27 frames
Here's a codepen: https://codepen.io/magiix/pen/MWewdYo
#skull {
position: absolute;
border: 1px solid red;
width: 1140px;
height: 1398px;
background: url("https://i.imgur.com/06vjVVj.png") 1140px 0;
}
const animateSkull = () => {
const interval = 50;
let pos = 30800 / 27;
tID = setInterval(() => {
document.getElementById("skull").style.backgroundPosition = `-${pos}px 0`;
if (pos < 30800) {
pos = pos + 1140;
}
}, interval);
};
If you check (with a console for example), you'll see that your animateSkull function is never called, because your addEventListener does not work. Change it to the following so it will be called (but your animateSkull function has another bug (or maybe your css I didn't checked) so it's not fully working after that but you should be able to fix that easily):
document.addEventListener('DOMContentLoaded', () => {
animateSkull();
});
This should do the work, but the frames in your sprite don't have the same width. So the animation looks buggy. (that is one huge image just for the animation)
const animateSkull = () => {
const interval = 1000;
let pos = -1140;
tID = setInterval(() => {
if (pos > -30800) {
pos -= 1140;
}
document.getElementById("skull").style.backgroundPosition = `${pos}px 0`;
}, interval);
};
animateSkull();
#skull {
position: absolute;
border: 1px solid red;
width: 1140px;
height: 1398px;
background-image: url("https://i.imgur.com/06vjVVj.png");
background-position: -1140px 0;
background-size: cover;
}
</style>
<p id="skull"></p>

How do I see if one element is touching another in JavaScript without clicking anything

OK so I've tried one thing from a different question and it worked, but not the way I wanted it to. it didn't work the way I wanted it to! You literally had to click when two objects were touching so it would alert you, if somebody can figure out a way to detect if two elements are touching without having to click that would be a life saver! So I hope you people who read this request please respond if you know how. this is the code below. so one object is moving and i want it to make it stop when the object hits the player (i am making a game) the movement is by px.... i want it to keep testing if one object hits the player, and if it does i want it to stop everything.
var boxes = document.querySelectorAll('.box');
boxes.forEach(function (el) {
if (el.addEventListener) {
el.addEventListener('click', clickHandler);
} else {
el.attachEvent('onclick', clickHandler);
}
})
var detectOverlap = (function () {
function getPositions(elem) {
var pos = elem.getBoundingClientRect();
return [[pos.left, pos.right], [pos.top, pos.bottom]];
}
function comparePositions(p1, p2) {
var r1, r2;
if (p1[0] < p2[0]) {
r1 = p1;
r2 = p2;
} else {
r1 = p2;
r2 = p1;
}
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function (a, b) {
var pos1 = getPositions(a),
pos2 = getPositions(b);
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1]);
};
})();
function clickHandler(e) {
var elem = e.target,
elems = document.querySelectorAll('.box'),
elemList = Array.prototype.slice.call(elems),
within = elemList.indexOf(elem),
touching = [];
if (within !== -1) {
elemList.splice(within, 1);
}
for (var i = 0; i < elemList.length; i++) {
if (detectOverlap(elem, elemList[i])) {
touching.push(elemList[i].id);
}
}
if (touching.length) {
console.log(elem.id + ' touches ' + touching.join(' and ') + '.');
alert(elem.id + ' touches ' + touching.join(' and ') + '.');
} else {
console.log(elem.id + ' touches nothing.');
alert(elem.id + ' touches nothing.');
}
}
this is my video game right now (please do not copy)
<!DOCTYPE html>
/
<html>
<form id="player" class="box">
</form>
<button type="button" class="up" onclick="moveup()">^</button>
<button type="button" class="down" onclick="movedown()">v
</button>
<style src="style.css">
#player {
width: 300px;
height: 100px;
background-color: blue;
display: inline-block;
position: relative;
bottom: -250px;
left: 200px;
}
.up {
position: relative;
bottom: -400px;
}
.down {
position: relative;
bottom: -420px;
}
body {
background-color: black;
}
#car {
width: 300px;
height: 100px;
background-color: red;
display: inline-block;
position: relative;
bottom: -250px;
left: 600px;
}
</style>
<form id="car" class="box"></form>
<script>
imgObj = document.getElementById('player');
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
function moveup() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + 70 + 'px';
}
function movedown() {
imgObj.style.position= 'relative';
imgObj.style.bottom = '-250px';
imgObj.style.bottom = parseInt(imgObj.style.bottom) + -120 + 'px';
}
myMove();
function myMove() {
var elem = document.getElementById("car");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 1000) {
clearInterval(id);
myMove();
} else {
pos++;
elem.style.left = pos + "px";
elem.style.left = pos + "px";
}
}
}
/* please do not copy; this is it so far i want the red box when it hits the player(blue box) to stop everything that is happening */
/* made by Jsscripter; car game */
</script>
</html>
Intersection observer. API was largely developed because of news feeds and infinite scrolling. Goal was to solve when something comes into view, load content. Also is a great fit for a game.
The Intersection Observer API lets code register a callback function
that is executed whenever an element they wish to monitor enters or
exits another element (or the viewport), or when the amount by which
the two intersect changes by a requested amount. This way, sites no
longer need to do anything on the main thread to watch for this kind
of element intersection, and the browser is free to optimize the
management of intersections as it sees fit.
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
All major browsers except safari support the API. For backwards compatibility and Safari support can use the polyfill from W3C found here. Check out this example from MDN:
var callback = function(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
var options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#listItem');
observer.observe(target);
See this in action here: https://codepen.io/anon/pen/OqpeMV

Responsive, cover-fit image tagging

I am working on a project where I have a slideshow with images as follows:
img {
width:100vw;
height:100vh;
object-fit:cover;
}
This makes the images fullscreen and behave like background-size:cover, so they fill out the whole viewport on any screen size without distortion.
I would like to tag certain points with text tooltips on these images. For this purpose I have found Tim Severien's Taggd, which works great on responsive images, but in my case the object-fit:cover; property makes the tagged positions inaccurate.
I have tried everything from CSS hacks to improving Tim's code, but I am out of ideas. If you have any solution or workaround in mind please share.
Thank you!
well i actually wanted to do the same thing.
here is what i've done.
maybe it will help someone in the future.
it would be great if this feature could be integrated in taggd.
function buildTags()
{
// be aware that image.clientWidth and image.clientHeight are available when image is loaded
var croppedWidth = false;
var expectedWidth = 0;
var croppedWidthHalf = 0;
var imageWidth = 0;
var croppedHeight = false;
var expectedHeight = 0;
var croppedHeightHalf = 0;
var imageHeight = 0;
var naturalRatio = image.naturalWidth/image.naturalHeight;
var coverRatio = image.clientWidth/image.clientHeight;
if(Math.abs(naturalRatio - coverRatio) < 0.01)
{
// the image is not cropped, nothing to do
}
else
{
if(naturalRatio > coverRatio)
{
// width is cropped
croppedWidth = true;
expectedWidth = image.clientHeight * naturalRatio;
croppedWidthHalf = (expectedWidth - image.clientWidth)/2;
imageWidth = image.clientWidth;
}
else
{
// height is cropped
croppedHeight = true;
expectedHeight = image.clientWidth / naturalRatio;
croppedHeightHalf = (expectedHeight - image.clientHeight)/2;
imageHeight = image.clientHeight;
}
}
function calcy(y)
{
if(croppedHeight)
{
var positiony = y * expectedHeight;
if(positiony > croppedHeightHalf)
return (positiony - croppedHeightHalf)/imageHeight;
else // tag is outside the picture because cropped
return 0; // TODO : handle that case nicely
}
else
return y;
}
function calcx(x)
{
if(croppedWidth)
{
var positionx = x * expectedWidth;
if(positionx > croppedWidthHalf)
return (positionx - croppedWidthHalf)/imageWidth;
else // tag is outside the picture because cropped
return 0; // TODO : handle that case nicely
}
else
return x;
}
var tags = [
Taggd.Tag.createFromObject({
position: { x: calcx(0.74), y: calcy(0.56) },
text: 'some tag',
}),
Taggd.Tag.createFromObject({
position: { x: calcx(0.9), y: calcy(0.29) },
text: 'some other tag',
}),
....
];
var taggd = new Taggd(image, options, tags);
}
$(window).bind("load", function() {buildTags();});
Is not possible. Think if the user has a tablet with 1024x768 resolution, when the user change view from horizontal to vertical the image can fill the space but you will loose part of the image, loose img quality, etc.
The best way for cross devices is to use big pictures and add in css
img {
height: auto;
width: 100%;
display: block;
}
And fill image background with a color;

Draggable Columns With Pure JavaScript

I'm trying to build a draggable column based layout in JavaScript and having a bit of hard time with it.
The layout comprises of 3 columns (divs), with two dragable divs splitting each. The idea is that they are positioned absolutely and as you drag the draggers, the columns' respective widths, and left values are updated.
The three columns should always span the full width of the browser (the right most column is 100% width), but the other two should remain static by default when the browser is resized (which is why i'm using px, not %).
My code isn't working as of yet, I'm relatively new to JavaScript (which is why I don't want to use jQuery).
Having said that, there must be a more efficient (and cleaner) way of achieving this with less code that works (without reaching for the $ key).
If anyone with some awesome JS skills can help me out on this I'd be super-appreciative.
Here's the fiddle I'm working on http://jsfiddle.net/ZFwz5/3/
And here's the code:
HTML
<!-- colums -->
<div class="col colA"></div>
<div class="col colB"></div>
<div class="col colC"></div>
<!-- draggers -->
<div class="drag dragA" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left:100px;"><div></div></div>
<div class="drag dragB" style="position: absolute; width: 0px; height: 100%; cursor: col-resize; left: 300px;"><div></div></div>
CSS:
body {
overflow:hidden;
}
.col {
position: absolute;
height:100%;
left: 0;
top: 0;
overflow: hidden;
}
.colA {background:red;width:100px;}
.colB {background:green; width:200px; left:100px;}
.colC {background:blue; width:100%; left:300px;}
.drag > div {
background: 0 0;
position: absolute;
width: 10px;
height: 100%;
cursor: col-resize;
left: -5px;
}
and my terrible JavaScript:
//variabe columns
var colA = document.querySelector('.colA');
var colB = document.querySelector('.colB');
var colC = document.querySelector('.colC');
//variable draggers
var draggers = document.querySelectorAll('.drag');
var dragA = document.querySelector(".dragA");
var dragB = document.querySelector(".dragB");
var dragging = false;
function drag() {
var dragLoop;
var t = this;
var max;
var min;
if (dragging = true) {
if (this == dragA) {
min = 0;
max = dragB.style.left;
} else {
min = dragA.style.left;
max = window.innerWidth;
}
dragLoop = setInterval(function () {
var mouseX = event.clientX;
var mouseY = event.clientY;
if (mouseX >= max) {
mouseX = max;
}
if (mouseY <= min) {
mouseY = min;
}
t.style.left = mouseX;
updateLayout();
}, 200);
}
}
function updateLayout() {
var posA = dragA.style.left;
var posB = dragB.style.left;
colB.style.paddingRight = 0;
colA.style.width = posA;
colB.style.left = posA;
colB.style.width = posB - posA;
colC.style.left = posB;
colC.style.width = window.innerWidth - posB;
}
for (var i = 0; i < draggers.length; i++) {
draggers[i].addEventListener('mousedown', function () {
dragging = true;
});
draggers[i].addEventListener('mouseup', function () {
clearInterval(dragLoop);
dragging = false;
});
draggers[i].addEventListener('mouseMove', function () {
updateLayout();
drag();
});
}
I see a couple of things wrong here. First of all, the mousemove event only fires on an element when the mouse is over that element. You might have better luck registering a mousemove listener on the parent of your div.drag elements, then calculating the mouse's position inside that parent whenever a mouse event happens, then using that position to resize your columns and your draggers.
Second, I'm not quite sure what you're trying to do by registering a function with setInterval. You're doing pretty well with registering event listeners; why not continue to use them to change the state of your DOM? Why switch to a polling-based mechanism? (and the function you pass to setInterval won't work anyway - it refers to a variable named event, which in that context is undefined.)
This is just a little example... I hope it can help you :)
window.onload = function() {
var myDiv = document.getElementById('myDiv');
function show_coords(){
var monitor = document.getElementById('monitor');
var x = event.clientX - myDiv.clientWidth / 2;
var y = event.clientY - myDiv.clientWidth / 2;
monitor.innerText = "X: " + x + "\n" + "Y: " + y;
myDiv.style.left = x + "px";
myDiv.style.top = y + "px";
}
document.onmousemove = function(){
if(myDiv.innerText == "YES"){show_coords();}
}
myDiv.onmousedown = function(){
myDiv.innerText = "YES";
}
myDiv.onmouseup = function(){
myDiv.innerText = "NO";
}
}

Categories