Close

Hit and miss

A project log for Retro style Star Trek action adventure game

Using my collection of tools and function to build a action/adventure game.

timescaleTimescale 04/26/2020 at 15:520 Comments

I've been thinking about the game mechanics a bit and I'm thinking of making the action bits a bit more straight forward. Just shoot some stuff with various weapons and have the dialogue between those sessions.

But for it to work, you have to accurately be able to shoot something. So I need some methods to determine if something is hit by a phaser or something else. Some collision detection and such.

All the previous demo's with the phaser fire were faked. There was no function to see if the beam actually hit something, but the demo today shows the phaser to be highly accurate. It detects hits on a per pixel level, which is what I wanted. Unfortunately the current method is NOT the one I wanted.

My initial idea was to simply take the screen position, reverse transform and scale that pixel one time per object and then check if that pixel would exist an if it did, if it had the alpha channel on or not. If not, it would be a hit.

However, after tinkering and thinking about it for a while, I could not get it to work. So I brute forced it and put it in the drawing routine. This does make for a bit of a performance hit and it will limit the number of on screen actors. What I do now is to set a target attribute which, when it is true, will make the drawing routine check if the pixel that is being drawn matches a pixel of a weapon system. If it does, it sets a hit flag for that object and as the object function is executed in the same loop, it will know each frame if the ship is being hit or not.

It is not elegant, but it works. I also build it so that when you fire the phaser, you can't control the cross-hair. This is part of the mechanics I'm playing with. There are a number of constraints I want to have to make the game part more challenging and interesting. The phaser-lock feature is the first. It makes it so you have to plan your attack because while you can't directly steer the cross-hair while firing, when you move the ship, the cross-hair is influenced by that. So by planning your angle of attack, you can then move you ship accordingly while firing and land more hits.

Another constraint I'm thinking about is to limit the phaser itself. This could be done in a variety of ways. There could be a cool-down time, limiting the speed at which you can fire or a energy bank that, when depleted renders the weapon useless for some time. A combination could also be possible, but effectively I want the player to have to think about tactics on how to approach the enemy.

I hope that I'll be able to implement my original more elegant solution for hit-detection, but for now this seems to work fine.


Update : I really did not like my solution. It was wasteful and and it did not belong in the render engine code. I did not want to check every point against another point especially not at that point in time. So I doubled down on the other solution and it seems I have it working.

It is far more elegant and useful. The function takes the cross hair coordinates, reverse transforms it with the translation, rotation and scale data and computes if that point lies within the graphic asset and if it does, it checks if that point has alpha or not. If it does not, you just hit the ship!

I have got it to the point where it requires no matrix transformations at all and, per coordinate is very light weight.

Here is a fun screenshot.

This is a test image to see what the hit function was actually seeing. This test function is tremendously slow, but it does show that the reverse transform makes for a very accurate mapping to the source graphic.

The exercise in thinking about this problem has been interesting. My frustrated quick ugly solution did the same job and per pixel was quite light weight. This solution per pixel is really compute heavy, but I need only to compute one, not thousands.

In effect this means that for collision detection between any point to any object, the time needed to calculate would be roughly the time that is needed to scale and rotate any pixel of an active object.


Here is the function as it stands now.

function chkHit(objID,px,py){
        // check a screen coordinate against an object. Used for collision detection and weapons.

        var hit = false;

        const deg = IGObjects[objID]["rotate"],
              scale = IGObjects[objID]["scale"],
              tileID = assetPointer[IGObjects[objID]["mediaObject"]],
              tileWidth = Math.floor(sceneGraphics[tileID].width * scale),
              tileHeight = Math.floor(sceneGraphics[tileID].height * scale),
              centerX = Math.round(tileWidth / 2),
              centerY = Math.round(tileHeight / 2);

        // needed for full 360 rotation with shear transforms.    
        if (deg > 270){ 
            deg -= 360; 
        }     
        if (deg < 90){
            var rad = deg2rad(deg);
        } else {
            var rad = -deg2rad(180 - deg);
        }

        const aShear = -Math.tan(rad/2).toFixed(2),
              bShear =  Math.sin(rad).toFixed(2);

        // shear transform just the center
        var xDisplace = centerX + (aShear * centerY),
            yDisplace = centerY + Math.floor(bShear * xDisplace);
            xDisplace += Math.floor(aShear * yDisplace);
        
        const xDiff = centerX - xDisplace,// get the xoffset difference
              yDiff = centerY - yDisplace;// get the yoffset difference

        var dx = px - IGObjects[objID]["x"];
        var dy = py - IGObjects[objID]["y"];

        if (deg >= 90 && deg <= 270){
            // mirror the shear rotation on both axis for full rotation & set pivot point.
            dx = (dx * -1) + xDiff;
            dy = (dy * -1) + yDiff; 
        } else {
            // set pivot point for the other half
            dx -= xDiff - tileWidth;
            dy -= yDiff - tileHeight - 2; 
        }

        dx -= aShear * dy * 2; // reverse transform
        dy -= bShear * dx; // the coordinate.

        dx = Math.floor(dx / scale); // account for scaling
        dy = Math.floor(dy / scale);

        if (dx > 0 && dy > 0 && dx < sceneGraphics[tileID].width && dy < sceneGraphics[tileID].height){
            // coordinates fall within the graphics boundaries
            var curPixel = (dy * sceneGraphics[tileID].width + dx) * 4;
            if (sceneGraphics[tileID].data[curPixel + 3] != 0){
                // Pixel has no alpha. This is a hit.
                hit = true;
            }
        }
        return hit;
    }
 

Perhaps the most interesting thing here is the reverse shear rotation method with is very simple indeed. The rest of it is really just the reverse of any odd choice I made earlier for the sprite rendering routine.


But anyway, now I can finally sleep!

Discussions