In this course, Daniel Bark will teach you how to code a fully functional Minecraft clone using React, JavaScript, and 3JS. Daniel is a senior software engineer from Sweden. You will create a game that supports five Minecraft block types. It will include moving, jumping, switching and placing blocks, saving, and more. You will learn about many React concepts such as use state, use effect, use ref, and custom hooks for state management. and you’ll be able to apply the principles to create other 3D games in JavaScript. All right, so let’s get on with the tutorial. And to begin with, let’s have an overview of the steps we’re going to take in this tutorial to build a Minecraft clone. So, first we’ll look through the code that we’re starting out with and the packages that we’re going to use. Then we’ll add our first 3D element, which is a skybox that will wrap the entire world. We need some images that will be used as textures on the ground and on the cubes. Uh next up, we’ll use a plane as the ground and our player is going to interact with that ground. Um so the player is going to get a firsterson view. We’ll mount a camera to a sphere and that will feel like a firsterson game. Then we need to have some gravity. We need to make sure that the player cannot fall through the ground and when you jump you should fall back to the ground and so on. We need some movement to go back and forward and to the sides. And then we need some state management because we need to keep track of all the cubes in the world. So you need to be able to add and remove cubes in a global state. So we need to keep track of that. And then we also need to keep track of what type of cube the player want to add to the world. So we also need some type of UI that indicates what cube texture is active. It would also be nice if we can save our world so we don’t have to start from scratch every time we come to the app. So we’ll implement a button to store the world in the local storage and also to u reset the world if we want to start over. We’ll have a reset button for that. And in the end we should build ourselves something fun like a house or we’ll see what we come up with. All right. So let’s look through the code that we’re starting with. So we have a package JSON. So we have react 3 fiber the the main package and then we’re going to use some nice functions from canon and dry we have react we have three we have a way to generate ids and we have the state manager sustand now let’s jump to the components folder is empty right now but we’ll we’ll populate it inside images we have small images like this we have dirt glass grass logs and wood. And I already created a JavaScript file that just imports these images and then exports them in an object like this. Let’s go to the index. So here, not much is going on. Quite standard. We have an index.css file with not much in it. I just added some helper classes and we’ll probably add some more later on. And inside the app here we have just a div that says outside canvas and then we have canvas from reactory fiber and we have nothing inside the canvas right now. So we see a text here that is outside the canvas and the rest of the screen is covered by the canvas. All right. So we are done with looking through the boiler plate. So onwards towards the sky. So the sky we will import from let’s see if we get some help here. Sky from React 3 dry and let’s see if something happened. Yes, we got a sky. So for now we can remove this div that says outside canvas. What I wanted to show with that is that you can add normal HTML as long as it’s outside of the canvas. Because if I move this into the canvas, we get some errors because React 3 fiber doesn’t know what to do with the div. It has all kinds of other primitives. It can render like meshes and geometries and so on, but it cannot handle divs. But we can place them outside and then we can mix the 3D stuff and the normal HTML stuff. So I’ll remove outside canvas for now. So we get a full view of the 3D content here. For on the sky we can set sun position and that takes uh an array of XY Z. So I’ll do 100 and 20 or something. It doesn’t matter too much. But now we see that the sky got a bit brighter and I cannot really look at the any other part of the sky because the camera is now static. Um, so we can adjust the sun position later if we’re not happy with it. But at least now we have a sky in the day and not an evening sky like it was before. So I think we’ll leave the sky like this for now and jump onwards to the next step. All right. So let’s prepare our images and turn them into 3JS textures. So first I’m going to change actually this images file so that we can destructure them when we import them directly. So I will clean it up like this. Let’s create a new file
and call it textures.
So then we can import from images. So what we need to do here is create textures. So let’s start with the dirt texture. So we need to import from 3JS
the texture loader.
So we say new texture loader and we give it the dirt image. So we just repeat this. There we go. And we create a separate texture also for the ground. Uh even though we’re going to use the same image.
So we’ll call that ground texture and uses the grass image. So now we want to export these.
There we go. So now we have a file that exports the textures. So I think we can cross off textures and images. So our next step in the list is to work with the ground. So let’s before we create the ground, let’s add a few things in here. Let’s add an ambient light. So that is a light that will uh light up every 3D surface. Uh, and that one takes a prop intensity. So, let’s do one 0.5. And before we include the ground, uh, we need to add a physics container because we’re going to use a hook from Canon to create our plane that will be the ground. And that needs to be within a physics boundary. So, we’ll do physics from cannon. And inside here is where we can create the ground. So let’s create that component now.
Ground.
Okay. So here to create our ground plane, we’re going to use a hook from Canon called use plane.
So this plane will give us an array where the reference comes first and that is a reference that we will place on the mesh that we let Canon control. So use plane. So it takes a callback function that returns an object. So here we can set some properties of this plane like rotation and position and so on. So we’ll do rotation and here we have an array of three and position also an array of three.
So now we’re going to return some elements here. So we will take a mesh. We first we will reference this mesh. So we’ll set ref is ref. And then we have the geometry. So we’ll have u a plane buffer geometry and we’ll attach
this as a geometry. And here we can set the size of the plane with the orgs prop. So since it since it is a plane, it has two dimensions. So we’ll set it 100 by 100. And we also need to add a material here. So we’ll do a mesh standard material.
And this should attach as a material. And here is where we want to add a texture. But first, we can just set a color. Let’s do hot pink. All right. So, if we look at the browser now, we have something hot pink that is covering the entire uh screen. So, we don’t want to have color hot pink here. We want to attach the ground texture. So let’s import the ground texture. So we just say map is ground texture. Okay. So we got an error uh because we forgot one step in the texture loader. We need to call it and then call the load function.
Okay, so we instantiate a texture loader and on that we call the load function for each image. That’s important to remember.
So now we can continue debugging this.
Okay, so now we have a new texture here. So we as we can see it doesn’t really look like uh the the grass image which has these small pixels on it. So what I suspect is happening now is that the image is being stretched out. So we want this texture to be repeating. We want this small image to repeat over and over. So we can do ground texture dot wrap s and we’ll do repeat wrapping and import that from three. And the same for T. And then we’ll do repeat dot set. And we’ll set this to the same as our size 100 100.
There we go. So now we have it repeating. But as we can see the it looks very blurry and that has to do with how the texture is being stretched and how essentially how the rendering engine figures out how to display these colors. So with this setting it’s like smearing them. And we can change that if we do ground texture dot mag filter
nearest filter instead. So let’s see what happens now. That looks much better. Now it looks pixelated as it should. So our next step would be to flatten or lay down the ground because right now the ground is standing up. So we can play with this rotation here. So I will rotate the xaxis and we want to rotate it 90° but in radians. So 2 pi would be 360° which means that one pi is 180° and half of a pi is 90°. So I will do math dot pi / 2 to rotate it 90°.
And it might be that we’re seeing it from behind now. So I will flip it n 180°.
So it folded over maybe so that we can no longer see it with the camera. So let’s do it a little bit less. Okay, there we go. So now we can see that it lays down but not completely 90°. But for now, we can keep it like this for a while. We can see that it’s almost correct. Uh because once we can move the camera around, it’s going to be easier for us to to flip it back then because then we will add gravity and we will fall down onto the ground. But let’s keep it like this just that we at least can see that we have the ground there. All right. So, we have the ground. Let’s start to work on the player.
So our player needs to have a camera. So we can destructure camera from a hook
called use three. So that hook is from Reactory Fiber. So we also need some type of geometry to represent the player the geometry that can collide with other geometries and so on. So for that we can use a a sphere. So this will be similar to what we did to the plane for the ground. So we’ll do use sphere
and we call we call use sphere and it takes a function that returns back an object.
So here we can set mass. We’ll set that to one. We’ll set type to be dynamic and we’ll return a mesh
and attach the ref.
And then let’s add our player.
All right.
So we we should have a player now. So the next thing, so we won’t see that anything change yet. So we need somewhere to store the position of our player.
So let’s create a variable called position and we’ll do a reference
and it has XY Z coordinates. So we’ll do 0 0 0 to begin with. Now we will call the use frame hook. So this hook will run on every frame. So what we want to do now is attach the camera to this position that we’re going to connect to our player.
So what we can do is that we can take the camera which is from use three and we take the position and we copy
and we pass in a new vector three
and this vector we give position current
and we give it the X. And then we do the same for Y and Z. position dot current dot one which is Y and position.curren
two.
So now we should have a camera that follows this reference to a position here. Okay, we can set the console log here just to see that our frame is running. Okay, so we see that we no longer see the ground and we have frame console login here. So we can see that it runs on every frame. So let’s remove this. So we did something to the camera at least that is good. So if we uncomment this line, we should see the ground again. And we do. So that’s good. So we were able to move the camera. We copy it to this position every frame. So our next step is to have this position follow the position of this sphere because this sphere will be affected by physics and gravity. So we can do a use effect
and this use effect will need to rerun every time the sphere changes position. So we can take out element number two here from usphere which is the API an API towards the sphere. So we’ll say that rerun this effect every time the sphere API position changes. And what do we want to do when the position changes? We want API.positionscribe position dots subscribe and subscribe takes a call back where we get call it P and we’ll say position dot current is equal to P.
So P is a triplet. So it will look like this. So now we have a sphere that is connected to this reference or let’s say this reference follows the sphere and we can get the camera to follow this reference. So our goal is to get this camera to follow the sphere.
Okay. So now I added back the camera so we can see that something happens. We get ejected. I don’t know if we may be colliding with the ground and then to handle that collision we just get ejected. If I change the set value here. So if I do 10 and I reload, I land on the ground. But because remember what what technical depth we had from the ground, we didn’t flatten it to 90° properly. So now would be a good time to fix that. So like I said to to rotate 90° that is half of a pie. So now we flatten the ground out.
So we fly up and then we land on the ground. We have taken the camera. We glue the camera to our position reference and then we subscribe to API position changes of the sphere. So we have a riff that tracks the sphere and then we glue the camera to our reference. Hopefully that makes sense. So we need to do similar things to the velocity. So let’s copy this position code here and we’ll call it call this val and here we track API velocity
API velocity.
So here we get V and we’ll set val.curren to V. So now we have a reference of the velocity that subscribes to the velocity of the sphere. Okay. So let’s fix this catapult effect thing. So what I suspect happens is that when the sphere is inside of the ground plane they that state cannot exist. So we just get flung out so that there’s no collision anymore. So we need to lift up the sphere a bit. So let’s try five.
So now we start from above the plane and then we fall down to it. So let’s see how close we can get.
So if we put one, we seem to be standing on the ground as we start. So that’s great. Let’s see if we can actually trigger some movement on the sphere. So we have the API. So we can do API dot velocity dot set. So here we can give it the X Y and Z velocities. So we can do 0 1 0. Then we should be constantly lifted upwards. So we’ll do something similar to this when we implement jumping because then we want to set velocity upwards but just for a short while so it so you fall back down so it’s a normal jump. All right. So I think before we can move before we can move forward, pun intended, uh with the player, uh we should make a hook for the keyboard inputs so that we can register different key presses uh that we’re going to use to control this sphere that is the player. So let’s add this to our to-do list. Keyboard input.
So the player is not yet done. We’ll move to the keyboard inputs first.
So the keyboard input is actually a hook. So let’s do a hooks folder and we’ll call it use keyboard. Export con use use keyboard. So we’ll need to have some state here to keep track of what keys are pressed. So we’ll do we’ll call it movement
set movement
use state. So let’s set up this initial state. All right. So we need to map out all the keys and call them things that we understand. Uh so one action is move forward, move backward. We also have move left and move right. We have jump and then we have texture one, two, three, four, five. I think that should be all the now that we have textures, we should maybe call it actions. So now we need to have a use effect and inside here we do document dot add event listener listen for key down.
Then
we need to create some functions to handle this key up. And since we’re adding event listeners, we also need to return a function that removes them. Otherwise, we will add endless event listeners. Handle key down. We wrap this in the use call back. So inside here we get the key down event. So let’s create some helper function to determine what key maps to what. So we’ll do function action by key. So here we get a key key map key action map. So here we say key here we need to type it as they as they are called in the key event. So key W like this. We’ll call it move forward and we have space for jump. And then we have digit one. And now we can say what texture it corresponds to. We’ll say it’s dirt and grass.
And the last one is log. So now we mapped out what keys belongs to what actions and then we just return key action map with key. Of course we need to check uh so that only we only react to the keys that we are listening for.
action by key e docode.
So now we can do set movement and of course we need to preserve the previous state.
So we we we spread the previous state and then we set
We can do action equals action by key.
Now we can just say
if action
action is true and this is now called set action actions. Okay. So it will be very similar for a handle key up
just that we set this to false. So here we need to do handle key down and handle key up and then we just return the actions.
Let’s try this hook from the player component. I’ll do like this. So we’ll just see those that are true
key and value and we return those that have true. All right. So now we should be able to see in the console log when we press these different keys that we are looking for. Okay. So we have no actions when we start. That’s good. So now I press W. Okay. So I’m not getting anything. So let’s debug. Let’s see if we are here.
Yes, we get the keyboard event action by key. Action by key is a function, not a map. So, this should fix that. And same thing here.
So I’m pressing W S A D jump spacebar and the digits one 2 3 4 5. Okay, so we have everything working. So now we can react to these actions and control the movement of the sphere by looking at what key presses we’re doing. So we can cross off the keyboard inputs. All right. So, let’s control the player using the keyboard inputs. And I think an easy one to start with is the jumping because this requires some vector math. So, it will be a little bit tricky, but I think we’ll manage it. So first we can look at
if actions do jump.
That means that we are jumping. Uh we can reuse this code
here. We can for the x we’ll just keep the value and for zed we’ll just keep the value
and here this is a constant that we’ll use for the jumping. So let’s store it jump
force. Let’s try 10
jump force.
Okay, let’s try it out. Spacebar. All right, so we achieved the jump. It felt a little bit too high. And also we can jump indefinitely, which is wrong. Uh, so first let’s fix the jump height. Let’s try three.
Yeah, that’s better. We’ll try four for now. Uh so we need to do another if check here if we can jump again. And one easy way of uh eliminating all these extra jumps is that we make sure that our y velocity is zero. Make sure that we’re not moving up and down because whenever we are moving up and down it means that we are falling through the sky or already jumped and we’re on our way up. So we need to make sure that the velocity in the y direction is really close to zero. So let’s do since we we care about positive and negative we can do absolute value and we have it on velocity dot
current.
It’s val right.
Current one should be smaller than 0.
Let’s do 0.05 maybe. Okay. So, let’s try. Good. We can still jump. I’m pressing it continuously, but we we’re only allowed to jump once we’ve landed. Oh, there’s one bug that we should fix and that is that we can we can jump if we hit right in the moment when we’re actually slowing down from going up to going down again. So, it’s um the the velocity there becomes almost zero at a certain frame. So, if I can time that frame, you can see that I can jump two times in a row. But we’ll leave it like that for now. So we can start to think about how we move in a direction. And the tricky part about it is that later on we will be able to turn around. So W will always take us in the camera direction. So we need one vector to represent what direction we are facing, another vector for the forwards and backwards speed and the other one for the side speed. So let’s create those vectors. one direction vector
vector 3
and uh we’ll call it front vector
also a vector 3 and a side vector. So the front vector it’s this Z axis here that will determine uh us going forwards and backwards. So both the move forward and move backwards should affect this force here. So one way of doing it is we check actions domove backwards and we set it to one if we’re moving backwards or zero. And then we subtract the same thing but we’re moving forward.
So if we press them at the same time they cancel out. We could actually destructure here. Move forward, backwards, move forwards, move right, move left, jump. Then we can clean this up.
Like this. And like this. All right. So, we have this one one or zero. And this one one or zero. And if they are both one they cancel out which is a good thing because if you go forward and then you start to hold backwards also then uh you should stop right that’s what we see in games. So then we’ve taken care of the front vector. So let’s do something similar for the side vector. So we’ll copy these. So now we’re not moving in the Z direction anymore. I think this should be the X direction.
And here it’s move left and here it’s move right. So we get the same behavior there. If you’re holding both at the same time, you should go nowhere.
There we go. So now we’re going to do some calculations and we’ll we will originate from the direction vector. So we’ll do direction dot subtubract vectors uh and those vectors are the front vector and the side vector. And then we’ll chain on some more operations. We normalize and then we multiply scalar. And here we need to determine how fast we want to go. So let’s also make a variable for that or a constant. We’ll try four for speed as well. So we’ll multiply the speed and then finally we apply uler
and this is where we get the correct facing direction from the camera. So we have a reference to the camera and its rotation. So this is where we make sure that the front and side vectors are correct in relation to the camera. So now we can take this direction vector and apply it to the sphere. So API dot velocity
dot set and then we do the direction.x X direction doy and direction dot zed. But that’s not completely true because the yaxis is not controlled by these front and side vectors. This is just for moving backwards and forwards and to the sides. So the y should actually stay the same. So we’ll do well. one. So that should be enough. In the best of worlds, we would be able to move at this point.
See if we need to refresh.
All right. I think we’re missing the new keyword here for the vectors. There we go. All right. So, we have a typo here. Okay. So, let’s try. I’m pressing forward. All right. So, forward is working. Backward is working. Now, we’ll try backwards and forwards at the same time. And it stops. That’s great. Let’s try left and right at the same time. And I let go of left. All right. So, that seems to be working. So, we cannot yet test that it will be still correct when we turn the camera. That we will do once we once we build out the the pointer lock controls uh where we can control the camera with the mouse. All right. So, I think we are done with the player for now. Let’s move forwards with the firsterson view. So we’ll create a component FPV
and this one is actually quite simple because we get a lot of help from uh react fiber here. Okay. So we need of course to connect this to the camera. So we need use three
where we can get the camera.
Okay. So we return
pointer lock controls
args is camera destruction GL here passing that as GL dom element FPV from FPV
and just render it.
All right. So, uh when I click here now, it should lock the camera to how I move the mouse. So, now I can look around. So, now we can see that if I’m facing left, I can move straight ahead. And if I turn a bit, I can still move straight ahead. So, our player movement seemed to work fine. and the pointer lock controls seem to work as well. There’s one thing we should do with the firsterson view is that we should add a small cursor to uh the middle of the screen. So outside of the canvas, let’s add a div class name cursor and let’s put just a plus there. and let’s position it absolute. And we’re going to center this on the screen. So let’s make a class called centered. So this is positioned absolute. So we do top 50%, left 50%. We need to add a transform translate
minus 50% and minus 50%. So that is just because if we if we center it 5050% without doing this transform translate then it will not actually be in the middle because I think I can show you this background hot pink. If we make this bigger we see that it is in fact centered. But if we don’t have this translate, this is what will happen. The top left corner of the square will be in the center. So what we’re adjusting for with 50% is 50% of the width and height of the square. So then it will move to the middle. But this one doesn’t need to be big at all. So we’ll remove these two. That looks good. And then remove the background.
So move the the color styling to cursor.
There we go. Now we have a marker to indicate the center of where we are watching. Great. I think we are done with uh the player for now. So we will cross out firsterson view. We’re also done with gravity and we are done with the movement. Our next item on the agenda is to add a state manager. We can add a hook called use store that keeps track of the game state. So let’s create our hook. Use store. So we’ll need create from sustand use store.
We can wrap this in create and we get a setter method here as an input parameter. So we will return an object. So we call create. We pass it a function that returns an object. So in this object we can declare our state. So, we need to keep track of a texture. And we can default the texture to dirt. We need to keep track of the cubes that the player is going to be able to place. So, we can just default that to an array. And now, we’re going to declare our methods that you can use to interact with the state. So, we’ll of course have add cube, remove cube. You need to be able to set the texture and for later we want to be able to save the world. Reset world. These are all the operations that we need to support in our store. So when we add a cube, all we need to know is where to place it because the texture we already know since we are in the store. So when when someone calls add cube we need to call set and in set we pass it a callback function and we get back the previous state. So here we can return an object. So here we just want to affect the cubes which is an array. So we spread in the previous cubes
and then at the end we add our new cube. And every cube needs to have a key and then we can use nano ID for that.
We have a position that we get from XY Z that’s being passed in. And then we have the texture which we can get from pre.
So that should be all it takes for us to add a cube. But before we do that, we need a way to render cubes. So I suggest that we leave the store like this for now with just a method of adding cubes and then we create the component to render a cube and then we can start to render them out. We can actually initialize the state with one cube just for debugging. Let’s do 111. So now we always have one cube initially that we can quickly see if when we start to rendering the cubes if they come out. So I s I suggest that we leave the store like this before we finish the rest of the store functions and then start to working on rendering our first cube. So let’s add a new component. Before we create the cube, we should have a a component that is taking care of rendering all the cubes essentially consuming the store and mapping out the cubes. So we’ll make a component called cubes. So we need definitely to have used the use store
from hooks.
So our use store hook we can we get the state back and then we can return an array where we can place different things from the state in whatever order we want. So we want state cubes.
So then the first element here will be the cubes. So if we do console log cubes, we can return nothing here. Let’s add cubes. Cubes.
So since the player needs to be able to stand on the cubes, the cubes needs to be within the physics.
So add it like that. Okay. So we consoled out the cubes and this is our debugging cube that we added a dirt cube on position 111. So now we can start working on mapping out those cubes. So we return.
Here we’re going to use the cube component. So let’s create that one first.
Since we’re doing a mapping, we need to use the key which is something that we have on the cube already. We have key, we have XY Z texture. So here we can use the key. This is actually position.
So we receive position and texture. So to create the cube, we’re going to use the the canon use box hook.
So we’ll set type is static and we have the position from the incoming prop.
So here we return a mesh and to the mesh we connect the ref. Here we need a box buffer geometry and we also need a mesh standard material
attach as geometry
color is hot pink. Do we have a cube anywhere? Okay. So I see we called it position in the store and here we’re destructuring position.
So we need to call it push here.
Oh, okay. So, the reason I couldn’t find the cube was because I was standing on it. I feel very stupid now.
That’s good. So, let’s play around with these values. See if we can get get it up in the air.
All right. So, there we have our first cube. All
right. So, we have the cube standing on the ground. So now let’s fix the texture of the cube.
So let’s pass in the texture.
In the store we just call it dirt and in textures we call it dirt texture. So let’s do texture plus texture. So we import all the textures as textures and then we map it by the texture in the store plus texture and then we get our active texture. Then we can do map active texture
and remove the color. There we go. We get the dirt texture on the cube. Let’s move it a bit closer to us.
So, as we can see, it doesn’t look so good. So, we need to do the same treatment we did to the grass to every cube texture. So, we’ll test it with dirt. dirt texture
mag filter
nearest filter. Let’s see if that helped. Yeah, now it looks good and pixelated and not smooshed.
Let’s do it to ground texture in here as well since we are already here. And then we don’t have to do it in here. Let’s move these out as well. There we go. So now we fixed it for all the textures inside of the texture file instead to keep our components more clean. I was thinking to maybe keep this in ground because it correlates to the size of it. So, we’ll keep it here. So, now we should be able to add a second cube with another texture.
And we’ll pick another position texture
wood.
All right. So now we can render cubes with different textures. And this one looks nice as well since it’s been fixed. All right. So now we have a way of rendering cubes once they are added to the store. All right. So we have the basics of the state management and we have the cubes basics as well.
Something that is missing in this list is adding cubes. So one way of adding a cube is to click the ground. So let’s open the ground. So we need to get the add cube method from use store.
add cube
and we return an array with state add cube. All right. So we had have the add cube method. Uh but we need to figure out the x y and z coordinates to uh to pass to this function. So let’s add an on click to this mesh.
Let’s do E do. Stop propagation so that the click cannot be passed through the ground. So on the event we have something called E.point.
So we want to get X Y and Z. So we can do object dot values from E dot point. So let’s stop here to check out what happens when we click the ground. So we have E.
Let’s look at point.
So we have a vector three. So we just want to get the values of this vector. We don’t want to pass the whole object. So if we do object valvalues, we just get these three values and we structure them out here. So if we hover the x, y, and zed, we see that we do in fact get the values here. As you saw, there are a lot of decimals here. We want the cubes to end up on fixed location according to this grid that we can see here in full numbers. So we need to round this. So we need to do a mapping of these values and return math seal of the value and then we should be able to call add cube with x y and zed. Right? So let’s try this out. Let’s reload. And we click the ground and we do get a cube.
So as we can see the seal function made it round up. So let’s try with floor instead. So, as we can see, I think we should try moving the ground down by half a unit and see if that will make the cubes lay flat on the ground. So, let’s try that. So, now that we fixed the ground, we can go back to seal here.
Math seal. And when I click the ground, we actually get cubes added. Pretty cool. So now let’s lower these default cubes or we can actually remove these default cubes cubes now that we have a way of adding cubes for real. Okay, so we handle the case where you click the ground and you want to add a cube. But what about when you click the surface of a cube to add a cube next to it? Okay. So let’s handle the clicking of cubes. So we can copy this state call here because we want to do the same inside of cube.
But we also want to have remove cube later on. So let’s add that straight away.
So my plan is that if you hold in a modifier key and click then you can remove cubes as well.
So let’s add an on click.
We stop propagation. So here we need to handle a few things. A cube has six sides and the side that you actually click needs to get another cube next to it. So we’ll create a variable clicked face.
And a face of a cube actually has two triangular shapes that covers the face. So it’s not a full square, it’s two triangles that together cover the square face. So instead of six faces, it has 12 faces. But that is pretty easy to fix. So we can just take take e dophase index and divide this by two and then we will be in the range between zero and five instead of zero and 11 and we of course need to math do floor it because there’s no 0.5 face index. So now clicked face should be between zero and five. So let’s console log face clicked face. So let’s add a cube here. Phase four, face one, face five, face zero, top is phase two, and the bottom should be phase three then. So now we just need to write if statement to cover all these clicked faces. So if it’s zero add cube. So we have a ref to the box that has a position. So we can do xyz from ref current position. So we can pass in xy z. And then we need to on one of them maybe subtract one to the x. And for another face, we need to do x + one. But it’s a little bit of guesswork to figure out what phase is what direction, if you understand what I mean. 1 2 3 4 5 6.
So something like that. This will most likely be wrong. All right. So we add a cube. We click one face. So here we see that it was the opposite.
All right. So now we have logging as well. So if I click this face, we see that it was four and the cube was added on the other side. So clicked face four just needs to flip. And then let’s flip this one. If I click this face, that was face one and that was also flipped. So this should probably minus one and maybe plus one here. Let’s try the top.
So phase two, nothing happened. So I’m suspecting that there’s a cube underneath. So this should probably be + one and this should be minus one. So let’s try again to click the top. Yeah, now that was correct.
So I think we might have it. Nice. Now we can click a cube to add another cube next to it. So now now it starts to become fun. So we can return if we are done with the operation.
So now we can handle the removal of cubes. If E. Altkey key remove cube X Y Z and return. And of course we haven’t implemented the remove cube function yet.
So what we can do just to try it quickly is to just console log here
xyz
xyz
remove. All right. So I’m holding down the alt key and I’m clicking it and we get a call to remove cube with xyz. So that’s great. So now we can implement the remove cube function.
So we can borrow some code from add cube. We will still call set but instead of adding to this array we can take pre dot cubes dot filter cube or we keep all the cubes where x y and z are not equal to the xy z being passed in. So we destructure
x, y and z from the cube. Then we return x not equal to x,
y not equal to y, and z not equal to set. So let’s see if we can filter out the cube that we’re clicking on. Let’s add a few.
So here we need to say cube cube. Pulse. All right, let’s try again. So add a few cubes and then alt key and they were all removed. I think the issue is that this should be or because if any coordinate is not correct, it’s not the the right box to filter out. So let’s try again.
There we go. One cube being removed at a time. Great. Now we can remove cubes. Great progress. All right. So we are done with adding cubes and removing cubes. So let’s continue with the cube type selector. So we should create some UI that indicates what selections there are and what is currently active. So let’s create a component texture selector.
And this one will not be a three component. This will just be HTML. And this one should just be visible when we’re actually changing the texture. So we need some state here.
So determine if it’s visible right now or not. We’ll default it to false. We need to get the texture from u from the store. active texture
state and we get an array back where we take state dot. We just call it texture
texture.
So, let’s call create a use effect that runs whenever the active texture changes.
Active texture. So, if the active texture changes, we want to have this texture selector visible for like 2 seconds and then we want it to disappear. So we need a visibility timeout that is set timeout
for 2 seconds. So after 2 seconds we want to set visible false and immediately we want to set visible true and then we need to clear the timeout.
Clear timeout visibility
like this.
So we only want to show something is if visible is true.
So let’s do class name absolute and centered. So now we we also need to handle so that the texture is actually changed when we press the digits actions from use keyboard.
So in the store we have a set texture method. So let’s finish that. We take a texture called set.
We don’t care about the previous. We just want to set texture based on the incoming texture. So let’s look at use keyboard. We have texture one to five. This is what we want to dig out here.
So let’s do a use effect again that will run for texture one texture two
if texture one. So now we need the set texture from the store.
Set texture dirt. Okay, we’ll call this dirt,
grass,
glass, wood, and log. So, the actions will now contain props that are the actual names of the textures.
So we’ll call it pressed texture.
So if any of these textures are true then that texture will be the pressed texture and then we can do if pressed texture set texture to pressed texture. Let’s see. Pressed pressed texture. So, let’s see if it works.
If I press one. All right, let’s debug.
So, if I press one, there to be true. So, that’s great. So, it works correctly in use keyboard and in the texture selector. Let’s console log dirt. Let’s see if we can get dirt to be one. So, one thing what we’ve must do is add the texture selector to our app before we start to looking for console logs in it. So, let’s add it here. Texture.
All right. So, we get dirt false. We saw it there. And as I press one, we got dirt true and texture selector appears for two seconds and then gets removed. So it looks very promising active texture. All right. So here we have our textures as booleans. So let’s create a mapping that will set the prop which is the name of the texture that will map from the name to the booleans. So then we can do object entries
find
key value. So we want to find a texture where the value is true. If we have such a pressed texture, we’ll take the value from that. All right. So we get five booleans here and we put them in a mapping. So we’ll get dirt falls, grass falls, maybe glass true and if glass is true we will find the true texture store that in press texture. So if we found a press texture, we get the key of that by taking the first uh element in that tupil. So let’s see if it actually worked. Grass, pressed dirt, pressed wood, and pressed log. So now I pressed glass. So this is the glass cube. Pressing dirt and I get a dirt cube. So cool. We have a way of selecting different textures just using the keyboard. I don’t know what happened with this cube. But let’s continue building the UI to show the actual selections.
So we already have a list of images with these. So let’s import those images.
Okay. So let’s create a mapping here images.
So we keep the name that they are called in the store to the actual image. That’s
so we need to loop these in a map here. Images and we’ll do object dot entries images. So here we get key and source image. So we can return an image image tag source is source key is K
and now we need to check which one is the active one and that we can do by adding a class name. So here we can do K equals active texture. If that is true, if that is true, we add active. Otherwise, nothing. And let’s have an alt prop the uh the name of the texture. All right. So let’s see if anything happened. So when I change the texture now, here we have an error. This should be an array. So now let’s see what Oh, we we got it. So when I change texture, the texture selector appears. So we should just make it bigger.
Let’s add a class
texture select.
So we can do transform scale 10. It should be select.
There we go. That’s bigger.
Let’s do maybe five. And then let’s handle the active also. So if we have an imageactive there, let’s do a border. of two pixel solid red. So let’s see if we can see which one is active. Yes, we can.
Okay, so we between the four and the five we have the wrong digits. So that is probably because I typed them in the wrong order here.
All right, now they work correctly. All right, let’s see all our cubes. We have wood or log, we have wood, we have glass, we have grass, and we have dirt. Really nice. Let’s remove this console log. All right, so our cube type selector is done. The next thing on the agenda would be to save the world in local storage. So that is something that we can do inside of our store. So let’s create some helper functions to talk to the local storage. So what we want to store in the local storage is an array. So we need to do JSON parse and JSON stringify to talk to the local storage. So let’s call create a helper function get local storage.
So we we take in a key and we return JSON.par
window local storage get item of key. So all we’re doing is JSON parsing what we get from local storage. And let’s do a similar function with set local storage. We take in a key and of course the value that we want to set. And then we do JSON stringify
set item and then we set we pass the key. Actually this JSON string if I should be just wrapping the value
value right now we have our helper functions. So when we first create the state we want to do get local storage and we can just make up a key here. We’ll call it cubes. So we’ll either get something or it will be empty. So we’ll fall back to the empty array. So now we can implement these two functions. Let’s start with just resetting the world. That would just be set
and then we just set the cubes to be empty array like that. That should reset the world. And then we have say world. So we call set just to get the current state. And inside here we just call set local storage. The key was cubes. And we store prev. cubes. So we don’t need a load world function because that happens when we just reload the page. But we we also need to create some buttons on the UI for this. Let’s create a component called menu.
So we want to access save world and reset world from use store.
So state
reset world. All right.
So, let’s create some UI
class name of menu and absolute.
We need save and we need reset
on click.
Save world.
Reset world. All right, let’s import menu.
So, let’s see where we have our menu. We have it, but it’s down there. So, we need to style it so it becomes in the top.
So, we’ll do top 10 pixels, left 10 pixels. There we have them.
All right. So, let’s try them out. We’ll place a few cubes of different types.
All right. We have a nice line here. Let’s save it. So now it should be in local storage. We can verify that application local storage. We have cubes and there we have the cubes. So now when I reload the page, do we have any cubes? Oh, here they are. Really cool. So now we can persist it. Of course, we can also reset, but they are still in local storage. So if I just reload the cubes will be there. All right, we are done with saving the world in local storage. But we have one more thing that would be nice to have hover state on cubes. I think it would be nice if when the cursor hovers a cube that they become a bit darker so that you indicate what cube you’re actually hovering on. All right, let’s keep track if a uh cube is hovered.
Set is hovered. Use state from react false. Now we can add some pointer events here on pointer move
and we take the event stop propagation set is hovered true.
And then we just do the same for
on pointer out.
And now we need to adjust the appearance based on the hover. So let’s add color here is hovered.
Let’s make it gray. If they’re hovered, we make them gray. Otherwise, we keep them white.
But one thing that would be nice is if the glass cube would actually be a bit transparent. So we add transparent true to all the cubes. And then we do opacity active texture equals glass. Then we do 0.5
otherwise we do one and this should be just texture.
All right. So there we have transparent glass. And maybe it’s a bit too transparent. Let’s do 0.6.
Really nice. Very cool.
Right. So, we’re getting close to being able to build that house. And of course, transparent glass is a must for building houses
so you can watch outside.
All right, I will quickly finish this house just to check it off the list.
should probably have a door.
All right, we’re inside the house.
I think we can check it off the list. All right, we can say actually right now that we have fixed everything on our to-do list and even some more things that we had to add as we went on. For those of you that got this far, kudos to you. It’s quite a long project. If you want to submit pull requests to this uh project, feel free. I will look at them and probably pull in many of them. So, thank you so much everyone that watched. I hope you learned a thing or two about uh building 3D things on the web and I hope you got inspired to try out to build something on your own. So please keep watching free code camp, continue to learn and also you can check out uh my channel if you should want to. Bye now. Thanks a lot for watching.