I'm making a very basic space invaders game, but having some trouble with the collisionDetection. enemy are the ones I need to hit, they are inside "fiende". "missile" is the div for the missiles.
I haven't tried much, since I'm very new to js, hence I don't have enough knowledge to improve on it.
EDIT: After some testing, I've found out that the collisionDetection breaks when the animations is running.
function collisionDetection() {
for (var enemy = 0; enemy < fiende.length; enemy++) {
for (var missile = 0; missile < missiles.length; missile++) {
if (
missiles[missile].left >= fiende[enemy].left &&
missiles[missile].left <= (fiende[enemy].left + 50) &&
missiles[missile].top <= (fiende[enemy].top + 50) &&
missiles[missile].top >= fiende[enemy].top
) {
fiende.splice(enemy, 1);
missiles.splice(missile, 1);
}
}
}
}
HTML
<div id="background">
<div id="missiles"></div>
<div id="fiende"></div>
</div>
CSS
div.missile1 {
width: 10px;
height: 28px;
background-image: url('missile1.png');
position: absolute;
}
div.enemy {
width: 50px;
height: 50px;
background-image: url('enemy.png');
position: absolute;
}
Some of the animation, it's basically the same after that.
#keyframes bevegelse {
0% {
left: -230px;
top: 0%;
}
5% {
left: 250px;
top: 0%;
}
10% {
left: 250px;
top: 40px;
}
15% {
left: -230px;
top: 40px;
}
When a missile hits the enemy, it destroys some other enemies, which the missile didn't hit. Sometimes it doesn't even destroy the randoms ones.
You have a problem in your loop. You are stepping through the indices into the array, but also modifying the array as you go, so you may get confusing results. This may or may not be the cause of your other issues (clearly this is not all of your code) however fixing this may help.
There are a number of ways to fix this. My usual method is to count backwards through the array so that removing elements does not impact the locations of indices the loop is yet to visit.
function collisionDetection() {
//because this loop is continued once the array is modified, count from the end
for (var enemy = fiende.length-1; enemy >=0 ; enemy--) {
//this loop exits when a clash is found, so count as normal
for (var missile = 0; missile < missiles.length; missile++) {
if (
missiles[missile].left >= fiende[enemy].left &&
missiles[missile].left <= (fiende[enemy].left + 50) &&
missiles[missile].top <= (fiende[enemy].top + 50) &&
missiles[missile].top >= fiende[enemy].top
) {
fiende.splice(enemy, 1);
missiles.splice(missile, 1);
//the enemy has already been hit, exit and dont consider other missiles
break;
}
}
}
}
I've made the assumption here that it is one missile gone per enemy. If you want more than one missile to be destroyed, then remove the break and also count backwards in the inner loop.
Thank you for supplying a reference to your git code (see the comments below).
The above is an error in the code, however not one which is typically causing any problem. The issue seems to be that when the enemies are animated the bullets don't hit them. The problem here is that you are detecting collisions with your js code's view of the enemy location, but you are enhancing the animation with css animation. If you wish to detect the collisions then you need to animate entirely in JS.
Try removing the css animation to verify you can hit the enemies. Then if that works you need to do all the movement in js.
Related
I decided to make a Pac-Man game and after I did it and everything was working somewhat fine on local document I pushed my website on Github pages and decrease in fps was enormous. It turned out page was making recalculation for hundreds elements which caused 20ms+ delay.
Here's a small part of the code that still has performance difference between local and github-pages hosted website.
const gameBoard = document.getElementById("game-board");
const root = document.documentElement.style;
let elements;
let characterNode;
let position = 658;
makeLevel();
function makeLevel() {
for (let i = 0; i < 868; i++) {
const element = document.createElement("DIV");
element.style.backgroundPosition = `0 0`;
let character = document.createElement("DIV");
character.className = "yellow";
element.append(character);
gameBoard.append(element);
}
elements = Array.from(gameBoard.children);
characterNode = elements[658].children[0];
changePosition();
}
function changePosition() {
root.setProperty(`--yellow-sprite-y`, `-32px`);
characterNode.style.transform = `translateX(-20px)`;
setTimeout(() => {
characterNode.style.transform = "";
characterNode.classList.remove(`yellow-visible`);
position = position - 1;
characterNode = elements[position].children[0];
characterNode.classList.add(`yellow-visible`);
changePosition()
}, 200)
}
:root {
--yellow-sprite-y: -32px;
}
#game-board {
width: 560px;
height: 620px;
display: grid;
grid-template-columns: repeat(28, 20px);
background-color: #000000;
}
#game-board > * {
position: relative;
width: 20px;
height: 20px;
}
.yellow {
position: absolute;
top: -4px;
left: -5.5px;
width: 30px;
height: 28px;
z-index: 10;
}
.yellow-visible {
background-image: url("https://i.imgur.com/SphNpH6.png");
background-position: -32px var(--yellow-sprite-y);
transition: transform 200ms linear;
}
<div id="game-board">
</div>
The exact problem in this code is line 29 which on local document performs like this:
while after hosting it on Github performs this way:
Why is it working this way and what can I do to lessen the performance decrease on hosted page?
Amazingly everything works well and bug doesn't exist on CodePen, yet on Github it still persists.
After getting some feedback that my site works well for other users I shared it on CodePen and it also worked fine, day later somebody said there could be an extension that could do something like that and indeed Adblocker Ultimate caused the slow performance.
I know that CSS transitions do not work with auto keyword, so if I want to apply transition: left 2s, right 2s; have to vary both right and left from (let's say) 0 to 100.
I have a script which might change an object's position either towards left or right, but I cannot set right: 100 if left is set to 0 (it has to be auto).
So I came out with this solution.
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0; //same position as auto but can be transitioned
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw"; //actual transition
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto"; //now I can move to either right or left again
//I don't need an animation on reset
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
This didn't work (the assign to 0 was completely ignored).
So I came out with this solution instead (same as above but using setTimeout(..., 0);
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0;
setTimeout(function() {
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw"; //this is executed async
}, 0);
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto";
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
This improved the result, but sometimes (it seems to be completely random, but more frequently on small screens), the transition still does not occur.
I supposed that this was because of the fact that sometimes the asynchronous function is executed too soon, so I tried to increase the delay to 10ms, but the problem still occurs sometimes (less frequently).
Now, I could increase the value furthermore, but:
I can never be sure that the error will not occur anyway sooner or later, maybe on a faster device (unless I set the timeout to a very large number)
I cannot set the timeout to a very large number, since I want it to be imperceptible
So, is there a minimum number that can assure a successful output?
If not, how can accomplish the same result?
As #Akxe suggested, the solution was reflowing the page. In other words, browsers flush multiple changes of the DOM all together. This speeds up the page by a bit but leads to some problems like the one I described (further information here)
As suggested here, the solution was a function like this one:
function reflow() { //this will be called between the two elements
void(document.documentElement.offsetWidth);
}
function moveObject(direction) {
direction ? myobject.style.right = 0 : myobject.style.left = 0;
reflow(); //the line above is now flushed
direction ? myobject.style.right = "50vw" : myobject.style.left = "50vw";
}
function resetObject() {
myObject.style.left = myObject.style.right = "auto";
}
#myobject {
position: absolute;
left: auto;
right: auto;
/*width, height...*/
}
<div id="myObject"></div>
The function actually worked for me even without using void, but I preferred keeping it since in the referenced question someone pointed out that it didn't work for him, and also for more clarity.
This could also be useful: it is an (unofficial) list of everything that causes page reflow.
As seen below, I tried to create a bounce effect at the end of another animation, in Popmotion.
I was not sure how to go about it, so I tried to reverse the velocity once it hit a certain threshold.
The results are sporadic and does not always work.
Any ideas on how to best create a bounce effect with Popmotion?
Clarification 1
The ball bounces most of the times, but how long it bounces varies greatly. Sometimes it stops abruptly after just one bounce. I am not sure why that is, because I do not fully understand how the solution actually works. Why does it slow down, if we simply reverse the velocity. Looking at the code, my guess would have been that the ball would oscillate indefinitely, but it does not.
Clarification 2
In Firefox 65.0.1, the animation seems consistent. In Chrome 72.x, it acts irrationally. I.e. the animation and bounce length changes each time.
const {
tween,
styler,
value,
easing,
physics,
transform
} = popmotion;
const {
clamp,
pipe,
conditional
} = transform;
const ball = document.querySelector('#ball');
const ballStyler = styler(ball);
const ballY = value(0, ballStyler.set('y'));
const BOTTOM = 50;
const pipedPhysics = physics({
acceleration: 2000,
// friction: 0.5,
// restSpeed: 0,
// springStrength: 300,
// to: 50
}).pipe(clamp(0, BOTTOM));
const anim = pipedPhysics.start(ballY);
ballY.subscribe(v => {
if (v >= BOTTOM) {
anim.setVelocity(-ballY.getVelocity());
};
// console.log('v, vel: ', v, ballY.getVelocity());
});
#ball {
background: #ff2420;
border-radius: 50%;
position: absolute;
left: 50%;
width: 50px;
height: 50px;
margin: 0px;
transform-origin: 50%;
}
<script src="https://unpkg.com/popmotion/dist/popmotion.global.min.js"></script>
<div id="ball"></div>
I'm trying to create a border that slowly wraps around the screen. As part of this process, I'm playing around with only increasing the height of the border right now by using the setInterval method. However, I can't get the border height to increase slowly. Below is what I'm doing:
var i = 1;
setBorder = setInterval(borderAnimation(), 200);
function borderAnimation() {
var border = document.getElementById("border-animation");
border.style.height = i + "vh";
i = i + 1;
document.write(i);
if(i = 100){
clearInterval(setBorder);
}
}
document.write(2);
If I change the variable i inside the function to say 5, then the height changes to that number so I know the function is being called at least once.
Similarly, document.write(i) prints only once. So if i is 1, then in the screen I see only 1; it also does not print 2 at any time even though I have document.write(2). Why is this?
After this, I intend to make it so that another div is called that goes around the top (this one is left), then another on the right then another around bottom, thus completing a border that goes around the screen. If someone has a better idea or way of achieving this, please let me know as well.
There are a couple mistakes:
1: pass the function reference (don't call the function) to setInterval
2: if(i = 100) should be if(i == 100)
var i = 1;
setBorder = setInterval(borderAnimation, 200);
function borderAnimation() {
var border = document.getElementById("border-animation");
border.style.height = i + "vh";
i = i + 1;
console.log(i);
if(i == 20){
clearInterval(setBorder);
}
}
#border-animation{
position: absolute;
width: 200px;
border: 1px solid #333;
}
<div id="border-animation"><div>
I'm trying to create a marquee (yes, I've done LOTS of searching on that topic first) using animated text-indent. I prefer this solution over others I've tried, like using translation 100%, which causes text to leak out beyond the boundaries of my marquee.
I've been trying to follow this example here: https://www.jonathan-petitcolas.com/2013/05/06/simulate-marquee-tag-in-css-and-javascript.html
...which I've updated a bit, doing it in TypeScript, using API updates (appendRule instead of insertRule) and dropping concerns about old browser support.
The problem is that the animation restarts using the old keyframe rules -- the step described by the comment "re-assign the animation (to make it run)" doesn't work.
I've looked at what's going on in a debugger, and the rules are definitely being changed -- old rules deleted, new rules added. But it's as if the old rules are cached somewhere, and they aren't being cleared out.
Here's my current CSS:
#marquee {
position: fixed;
left: 0;
right: 170px;
bottom: 0;
background-color: midnightblue;
font-size: 14px;
padding: 2px 1em;
overflow: hidden;
white-space: nowrap;
animation: none;
}
#marquee:hover {
animation-play-state: paused;
}
#keyframes marquee-0 {
0% {
text-indent: 450px;
}
100% {
text-indent: -500px;
}
}
And the relevant section of my TypeScript:
function updateMarqueeAnimation() {
const marqueeRule = getKeyframesRule('marquee-0');
if (!marqueeRule)
return;
marquee.css('animation', 'unset');
const element = marquee[0];
const textWidth = getTextWidth(marquee.text(), element);
const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
const offsetWidth = element.offsetWidth;
if (textWidth + padding <= offsetWidth)
return;
marqueeRule.deleteRule('0%');
marqueeRule.deleteRule('100%');
marqueeRule.appendRule('0% { text-indent: ' + offsetWidth + 'px; }');
marqueeRule.appendRule('100% { text-indent: -' + textWidth + 'px; }');
setTimeout(() => marquee.css('animation', 'marquee-0 15s linear infinite'));
}
I've tried a number of tricks so far to get around this problem, including things like cloning the marquee element and replacing it with its own clone, and none of that has helped -- the animation continues to run as if the original stylesheet values are in effect, so the scrolling of the marquee doesn't adapt to different widths of text.
The next thing I'll probably try is dynamically creating new keyframes objects instead of editing the rules inside of an existing keyframes object, but that's a messy solution I'd rather avoid if anyone has a better solution.
I found a way to get my marquee working, and it did involved dynamically adding and removing keyframes rules from a stylesheet, but that wasn't as painful or ugly as I thought it might be.
let animationStyleSheet: CSSStyleSheet;
let keyframesIndex = 0;
let lastMarqueeText = '';
function updateMarqueeAnimation(event?: Event) {
const newText = marquee.text();
if (event === null && lastMarqueeText === newText)
return;
lastMarqueeText = newText;
marquee.css('animation', 'none');
const element = marquee[0];
const textWidth = getTextWidth(newText, element);
const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
const offsetWidth = element.offsetWidth;
if (textWidth + padding <= offsetWidth)
return;
if (!animationStyleSheet) {
$('head').append('<style id="marquee-animations" type="text/css"></style>');
animationStyleSheet = ($('#marquee-animations').get(0) as HTMLStyleElement).sheet as CSSStyleSheet;
}
if (animationStyleSheet.cssRules.length > 0)
animationStyleSheet.deleteRule(0);
const keyframesName = 'marquee-' + keyframesIndex++;
const keyframesRule = `#keyframes ${keyframesName} { 0% { text-indent: ${offsetWidth}px } 100% { text-indent: -${textWidth}px; } }`;
const seconds = (textWidth + offsetWidth) / 100;
animationStyleSheet.insertRule(keyframesRule, 0);
marquee.css('animation', `${keyframesName} ${seconds}s linear infinite`);
}
There's other stuff going on here not needed for a general solution. One thing is that this method is called for two reasons: The window is being resized, or an update to the marquee text has been made. I always want to update when the window is resized, but otherwise I don't want to update the animation if the text hasn't changed, otherwise it could unnecessarily reset when someone is trying to read it.
The other thing is that I don't want text to scroll at all if it happens to fit nicely without scrolling.