fenomas / noa

Experimental voxel game engine.
MIT License
616 stars 91 forks source link

Source code for the live test app? #12

Closed Nesh108 closed 7 years ago

Nesh108 commented 7 years ago

Hey Andy!

First of all: great job! I have just found this project and I find it very interesting, especially because I am working on something like this as well. I have been using voxel-engine and three.js but the performance and the low maintenance of the engine (last update around 3/4 years ago) made me consider switching to something else.

I found your live app really interesting and that's what sold it to me but I noticed it is not available in the examples folder. Would it be possible to get it as an example? I am mainly asking because I want to start adding things like mobs with AI and things like that (together with physics for the liquids).

Let me know!

Cheers,

/N.

fenomas commented 7 years ago

Hey, thanks for the kind words! I also started out by trying to use voxel.js, but I had similar issues and wound up rolling up my own engine.

As for the demo - wow, I had completely forgotten about that! And now that I look, I'm not sure where the source is. Instead though, check out this presentation world that I used for a talk. It was a fork of the live demo world, and I think it has all the same features - fluid blocks, mobs, etc.

Note that it will take a few edits to make that world run with the current engine though, as a couple of APIs have changed signature since then. The /examples/ demos are current, though.

If you want to hack on any engine features feel free to ask whatever!

Nesh108 commented 7 years ago

Great to hear from you!

That's pretty nice, I was interested in getting into the mob part, as I'd love to work on having some flexibility on the AI for the different mobs.

I will definitely be in touch soon :)

Btw, is there any reason why you picked Babylon over, say, Three.js? I did some research myself and, although Babylon seems to be more suitable for games and has better performances, Three.js is much more popular, especially with voxel stuff. I am asking because I started with Three.js but since you are using Babylon, I don't mind too much switching :)

fenomas commented 7 years ago

Great! Regarding Babylon, I always found Three's API confusing, so I went looking for an alternative and once I tried Babylon I've had no reason to try anything else. It's powerful, the API is intuitive, and the community is super-responsive. There's an active forum where the devs answer questions, which makes it really easy to learn.

Nesh108 commented 7 years ago

Ok, you convinced me :D

Nesh108 commented 7 years ago

Heya Andy!

I am currently working on switching the engine of my project to yours and I am struggling with some things:

        this.noa.inputs.down.on('fire', function () {
            if (this.noa.targetedBlock) 
                this.noa.setBlock(0, this.noa.targetedBlock.position);
        }.bind(this));

        // on right mouse, place some grass
        this.noa.inputs.down.on('alt-fire', function () {
            if (this.noa.targetedBlock) 
                this.noa.addBlock(1, this.noa.targetedBlock.adjacent);
        }.bind(this));

or using the event:

        this.noa.on('targetBlockChanged', function(tb) {
            console.log(tb);
            if(tb) {
                console.log("Got it!");
                this.highlight_block_position = tb.position;
                this.highlight_adjacent_block_position = tb.position.adjacent;
                console.log("Highlighting");
            }
        }.bind(this));

Is there something I need to do to enable it?

        this.noa.world.on('worldDataNeeded', function (id, data, x, y, z) {
            if(Math.max(Math.abs(x),Math.abs(y),Math.abs(z)) <= MAX_N_BLOCKS){
                for (let i = 0; i < data.shape[0]; ++i) {
                    for (let k = 0; k < data.shape[2]; ++k) {
                        let height = get_height_map(x + i, z + k);
                        for (let j = 0; j < data.shape[1]; ++j) {
                            if (y + j < height) {
                                if (y + j < 0) {
                                    data.set(i, j, k, dirtID);
                                } else if (y + j == 0) {
                                    data.set(i, j, k, waterID);
                                }
                                else {
                                    data.set(i, j, k, grassID);
                                }
                            }
                        }
                    }
                }
            }
            this.noa.world.setChunkData(id, data);
        }.bind(this));

Once I manage to update my project, I am sure I will know your engine much better and I will be able to contribute with some cool new things :D

/N.

fenomas commented 7 years ago

Hey, thanks for further looking at the engine!

Regarding target blocks, both of your first two code samples worked perfectly for me - I just copied them into the test world and they worked as written. So I think that either there's a problem somewhere else in your code, or maybe I have some discrepancy between my test environment and what's in the github repo. I can't find anything though. Does the test world run for you? (the one you get by doing npm test in the noa folder and then browsing to localhost:8080 ?

Nesh108 commented 7 years ago

I tried it and got sh: webpack-dev-server: command not found

EDIT: Forgot to run npm install, duh. Test world works on my side.

fenomas commented 7 years ago

For the question about world extents, basically this engine doesn't know anything about how your world is shaped - it just asks for data within the visible radius and displays whatever you send it. So if you want the world to be empty beyond some point you can return empty data like you're doing, or if you want the world to "wrap around", you can return data that way, etc.

Eventually I should maybe think about what happens when the coordinates overrun JS's max number values, but I haven't thought about it. :)

Nesh108 commented 7 years ago

I think the coordinates should wrap around after you reach JS's max number, in that way you can keep going north and end up south :D

fenomas commented 7 years ago

I tried it and got sh: webpack-dev-server: command not found

Did you do npm install in the noa directory? Webpack and webpack-dev-server should be listed as dev dependencies. Or you could install them globally, or use some other method of packing if you like. webpack-dev-server basically runs webpack to build the project, then runs a small webserver that serves to localhost:8080, with some wrappers that make the page reload when the source files get changed.

Nesh108 commented 7 years ago

Yeah, I posted and realized that. The test world works just fine. :)

fenomas commented 7 years ago

Regarding final questions:

Is there a way to find if the player is out of bounds? (I am not sure if this would make sense if your map is just endless)

Nothing right now, there's no handling for extreme numbers. Mind you, the JS number limits are very large compared to voxel world sizes.

Do you have some 'fly' option for the player? Like to go in creative mode for building.

Nothing is built-in. The quickest hack would be to let the player have infinite air jumps, so you can move around easier:

noa.ents.getMovement(noa.playerEntity).airJumps = 9999

You could also make the player float by turning off his gravity:

noa.ents.getPhysicsBody(noa.playerEntity).gravityMultiplier = 0

But you'd need to then add some ways of moving up and down, and maybe change the physics module's air friction so that you don't keep flying forever, etc.

fenomas commented 7 years ago

By the way, I'm happy to answer questions like this, but maybe better to enter each question as a separate issue, so that if anyone else comes along with the same question they'll find it easily. Then one of these days I'll look at the past issues and know what I need to write documentation for. ;)

Nesh108 commented 7 years ago

Yeah, if you don't have map bounds then it makes no sense for you to have it. My maps are limited in size (128x128), so the player is able to fall off the world.

And for the missing stuff, I will look into it and add them to the engine :D

Now I am still figuring out the targetedBlock issue :/

fenomas commented 7 years ago

Ok, good luck! Note that if the player falls off the world you'll need to detect it somehow, since by default they'll just fall forever (and noa will keep requesting new chunks faster and faster as they fall).

There used to be a "player-changed-chunks" event that the engine emitted when the player crossed a chunk border. I'm not sure why I removed it but I should add that back in, since it's the easiest way to check stuff like this.

Nesh108 commented 7 years ago

I think I know why it happened, I am using npm install and apparently over there the latest version is 0.15.0. But here on github is v0.20.1.

So, the old code didn't have 'targetBlockChanged' :D

fenomas commented 7 years ago

Ahhhh, sorry about that, I had not published to npm in a while. I just pushed v0.20.2 there. Sorry about that!

You can overwrite what you have by doing npm i noa-engine@latest.

Nesh108 commented 7 years ago

No problem! I enjoy these small hiccups, they make me learn your engine even more :D

By the way, do you know if anything changed between 0.15 and the current version regarding the world creation? Now the targetBlockChanged works like a charm but the world is completely empty (but you can still see that there is something). Maybe how the textures are loaded?

fenomas commented 7 years ago

Yes, check the repo README for a summary of changes in recent versions. The important one here is probably a change in 0.16 or so, that changed block registration from being a function that takes a long list of arguments to a function that takes an object full of settings.

Nesh108 commented 7 years ago

The registration works, it just doesn't show neither the textures nor the plain colours. I think I followed the hello-world example almost to the letter.


        let brownish = [0.45, 0.36, 0.22];
        let greenish = [0.1, 0.8, 0.2];
        let blueish = [0.20, 0.85, 0.95, 0.5];
        this.noa.registry.registerMaterial('dirt', brownish, null);
        this.noa.registry.registerMaterial('grass', greenish, null);
        this.noa.registry.registerMaterial('grass_side', greenish, 'grass/grass_side.png');
        this.noa.registry.registerMaterial('water', blueish, null);

        // register block types and their material name
        let dirtID = this.noa.registry.registerBlock(1, 'dirt' );
        let grassID = this.noa.registry.registerBlock(2, ['grass', 'dirt', 'grass_side']);
        let waterID = this.noa.registry.registerBlock(3, 'water', { fluidDensity: 1, viscosity: 0.8 }, false, false, true);

        this.noa.world.on('worldDataNeeded', function (id, data, x, y, z) {
            // populate ndarray with world data (block IDs or 0 for air)
            if(Math.max(Math.abs(x),Math.abs(y),Math.abs(z)) <= MAX_N_BLOCKS){
                for (let i = 0; i < data.shape[0]; ++i) {
                    for (let k = 0; k < data.shape[2]; ++k) {
                        let height = get_height_map(x + i, z + k);
                        for (let j = 0; j < data.shape[1]; ++j) {
                            if (y + j < height) {
                                if (y + j < 0) {
                                    data.set(i, j, k, dirtID);
                                } else if (y + j == 0) {
                                    data.set(i, j, k, waterID);
                                }
                                else {
                                    data.set(i, j, k, grassID);
                                }
                            }
                        }
                    }
                }
            }
            // pass the finished data back to the game engine
            this.noa.world.setChunkData(id, data);
        }.bind(this));

I can break the blocks, I simply can't see them.

fenomas commented 7 years ago

The problem is the registerBlock arguments, which changed sometime after the old live demo was made. You've almost changed it right, but the materials array goes in the options object along with the other options. So the signature should look like:

let someID = noa.registry.registerBlock( 1, {
    material: 'matName', // or [ 'mat1', 'mat2', .... ]
    solid: false,
    fluid: true,
    fluidDensity: 1.0, 
    // ...
})

Since I haven't found a good way of generating JSdocs, the documentation for this stuff basically lives in the source files - e.g. a full list of what options that function recognizes can be found commented right above the function itself:

https://github.com/andyhall/noa/blob/master/lib/registry.js#L91-L109

Hope this helps!

Nesh108 commented 7 years ago

Woah, how did I miss that?! Thanks a lot dude :D