Thanks to the A-Frame library, it's super easy to add 3D experiences to your browser using just HTML tags!
If you're already familiar with HTML, check out our guide on creating custom maps with A-Frame. Or, if you'd like to learn HTML, explore our short guide on how to code with HTML.
Before we explain how to code with HTML it's important to explain that this page is not going to go into high detail on the many tags, attributes, and CSS styles. This is why there are so many other reference pages and other resources out there. If you don't know how to do something, just look it up. Some good places to look are, W3 Schools or MDN Webdocs. If it's not a matter of forgetting the name of something, look or ask a question on a forum like Stack Overflow. Some of my personal favorites are AI tools Chat GPT or Gemini
HTML is the foundational markup language used by 96.7% of all websites, with 94.4% specifically utilizing HTML5. It's easy to learn and use. HTML is written using tags, where each set of tags represents an element on the page. Some elements are visible, while others alter the page’s structure or behavior. Attributes specify little details that change what elements do.
Elements have an opening tag: <tagname>
and most have a closing tag: </tagname>
. Opening tags have a <
, the element name, then a >
. Closing tags are the same except they have a /
after the <
.
The stuff inside the two tags is the elements's content. For example: <p>This is a paragraph of text.</p>
Result:
There are many different tags like the h1 tag that creates a big heading:
<h1>This is a heading</h1>
Result:
Or the button tag that creates a button:
<button>This is a button</button>
Result:
Those (and many more tags) are the building blocks of a website and they work on their own. If you were to open an index.html file with
<button>This is a button</button><h1>This is a heading</h1>
, it would look like this:
However, websites should have a structure. First you need the thing at the top like this: <!DOCTYPE html>
(It doesn't have a closing tag.)
Then right after it there is the html tag that looks like this <html lang="en"> </html>
.
Inside the html tag is where we put the head tag and body tag. The head tag is the head of the page, it will hold some
of our scripts, the title, favicon, CSS, meta tags, and all the stuff we don't see directly.
The other tag inside the html tag is the body tag. This holds all the stuff we get to see like button and h1 tags.
You can put some scripts that directly modify the page in there too. Here's an example of a page:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example Page</title>
</head>
<body>
<h1>This is an example</h1>
<p>Cool right?</p>
<button>You can click me, but without JavaScript, nothing happens.</button>
</body>
</html>
Result:
You could do this, but we can't do that much. This is why we have attributes.
Attributes help us specify the details on elements.
Attributes can set the id, class, style, alignment, source, width, height, and many more details of a element.
Attributes are added in the opening tag of an element after the name of it. You put the name of the tag, an = sign, and inside quotes the value of the attribute.
For example, we can set the id of the heading tag to coolHeading like this: <h1 id="coolHeading">A Cool Heading/h1>
I know you're probably wondering though, why would you want to set the id attribute of an element? It's not like it changes how it looks.
Elements have id and class attributes to identify them from other tags. This way you can use them in javascript and CSS.
These can change how the element looks.
There are some attributes that change an element on their own though, like the src attribute on an img tag.
It sets the source of the image. The image tag is self-closing, so it doesn't need a closing tag. The link is the source to the image: <img src="https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/tagIcon.png?v=1734742942629">
Result:
One thing to remember though is not all attributes work on all tags. For example, the src attribute will also work on a script tag to set the source of a script, but not on an h1 tag.
There are also other attributes like the align attribute that aligns an element on a page: <h1 align="center">Center Heading</h1>
Result:
Or the href attribute that works on a tags. It sets the link that the a tag takes you to: <a href="https://mmspicyio.xyz">mm spicy io</a>
Result:
Or the style attribute that adds CSS quickly to an element: <h1 style="color: yellow">Mm Spicy io</h1>
Result
There are many other ways to add CSS but we won't go into them since we're just teaching HTML right now.
There are many other HTML elements and attributes. Once again, for more lessons and detail on this check out resources like W3 Schools or MDN Webdocs. This was just an introductory course to HTML, I know that you really came to make 3D games.
Mm Spicy io is made with the A-Frame library. It makes 3D development on the web super easy for people who aren't familiar with JavaScript and the Three.js library.
To add the A-Frame library to our page we include the CDN script in the head tag. <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
A CDN helps us add code to our page that's hosted externally.
Then we add an a-scene tag. <a-scene></a-scene>
Inside this tag is where we add the shapes in our 3D scene.
A-Frame has a lot of primitives. We'll cover the most commonly used and basic ones.
To find a full list of A-Frame primitives, components, and more, check the A-Frame documentation.
There are a bunch of 3D shapes like the box, sphere, cylinder, plane, and torus. You can also add 3D models (gltf/glb)
with the a-gltf-model element.
If we add our a-scene with those elements inside to our full html template, it'll look like this: <html>
<head>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-box></a-box>
<a-sphere></a-sphere>
</a-scene>
</body>
</html>
And the result will look like this: (back up a bit using the back arrow key to see something)
This is super cool, you can even move around to see the objects! But everthing has no color, and they're on top of each other.
Just like with regular HTML, you can add attributes to change things about the objects. In A-Frame, these are called components.
Here are some commonly used components: The position component changes the 3D position. Y being up and down (+ is up and - is down), Z being forward and backward (+ is backward and - is forward), and X being left and right (+ is left and - is right).
In the component though, whenever you are specifying x, y, and z. The order is X Y Z—alphabetical, and leave a space between each number.
You can use the material component for advanced materials or just the color component for basic coloring.
The scale component changes how big an object is on the x, y, and z axes. The rotation component changes the rotation. Here is another example using these components: <html>
<head>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-box position="0 -1 0" scale="20 1 20" color="brown"></a-box>
<a-sphere position="5 2 3" color="red"></a-sphere>
<a-torus position="-5 2 -3" rotation="45 0 0" color="purple" scale="3 3 3"></a-torus>
</a-scene>
</body>
</html>
And it'll look like this:
You can also add gltf/glb models with the a-gltf-model primitive.
Just set the src attribute to the path to the model, and just like that, it's in the game.
The src attribute can also be used as an image path to add image textures on shapes.
<html>
Now we have a car in our scene:
<head>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-box position="0 -1 0" scale="20 1 20" src="https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/groundSmaller.png?v=1732580199929"></a-box>
<a-sphere position="5 2 3" color="red"></a-sphere>
<a-torus position="-5 2 -3" rotation="45 0 0" color="purple" scale="3 3 3"></a-torus>
<a-gltf-model position="2 0 -5" rotation="0 70 0" src="https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/car.glb?v=1740160365174"></a-gltf-model>
</a-scene>
</body>
</html>
This is cool but it's not like an Mm Spicy io map. To do this, we need to add physics.
First, we need our physics system CDN: <script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js"></script>
Then we'll turn on debug mode in our scene: <a-scene physics="debug: true">
This will add red lines around our physics bodies to show their collisions. Just set it to false to remove them. Now all we have to do is give our objects the static-body
or dynamic-body
component
and the object will be a physics body. A static-body will stay still even if there is nothing below it.
You can use it for floors and walls. A dynamic-body will change and move like a regular object. In Mm Spicy io maps, the only dynamic body is the player,
but for the sake of this example, we'll just set the floor as a static-body.
<html>
Now the objects fall from the sky when we load the page:
<head>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js"></script>
</head>
<body>
<a-scene physics="debug: true">
<a-box position="0 -1 0" scale="20 1 20" color="brown" static-body></a-box>
<a-sphere position="5 20 3" color="red" dynamic-body="shape: sphere"></a-sphere>
<a-box position="-5 5 -3" rotation="42 0 0" color="purple" scale="3 3 3" dynamic-body="shape: box"></a-box>
</a-scene>
</body>
</html>
Setting a 3D model as a physics body is a bit harder to do.
If you just give an a-gltf-model the static or dynamic body component, you'll get an error and the physics won't work.
First, we have to set the shape to none: static-body="shape: none"
(this can all be done with a static or dynamic body)
Then, for every shape we add, we insert a new component. We name these components shape__ and then whatever we want to name the shape.
This could be shape__main or shape__handle or shape__one. Whatever you want as long as it starts with shape__.
We then specify the attributes of the shape: shape__main="shape: box; offset: 0 1 0; halfExtents: 2 2 2"
When specifying the shape, include the value you are specifying, then a colon :
then what you want to set it to, and finish it with a semicolon ;
Here is a table describing these components and what they do:
Property | Applies To | Default | Description |
---|---|---|---|
shape |
— | box |
Shape of the physics body: box , sphere , or cylinder . |
offset |
— | 0 0 0 |
Position of shape relative to the body. |
orientation |
— | 0 0 0 1 |
Rotation of shape relative to the body (quaternion). |
radius |
sphere |
1 |
Sphere radius. |
halfExtents |
box |
1 1 1 |
Half-extents of the box. Use 0.5 0.5 0.5 for a 1×1×1 box. |
radiusTop |
cylinder |
1 |
Top radius of the cylinder. |
radiusBottom |
cylinder |
1 |
Bottom radius of the cylinder. |
height |
cylinder |
1 |
Height of the cylinder. |
numSegments |
cylinder |
8 |
Number of cylinder subdivisions. |
We don't have to use all of these components because most of them depend on the shape we are using. For example, numSegments will only be used on a cylinder.
Now, if we add the car back in with a collision. Our code will look like this:
<html>
The hitboxes don’t need to perfectly match the shape of the object, as shown in the result below.
<head>
<script src='https://aframe.io/releases/1.7.0/aframe.min.js'></script>
<script src='https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.2/dist/aframe-physics-system.min.js'></script>
</head>
<body>
<a-scene physics='debug: true'>
<a-box position='0 -1 0' scale='20 1 20' color='brown' static-body></a-box>
<a-sphere position='5 20 3' color='red' dynamic-body='shape: sphere'></a-sphere>
<a-box position='-5 5 -3' rotation='42 0 0' color='purple' scale='3 3 3' dynamic-body='shape: box'></a-box>
<a-gltf-model position='0 5 -5' rotation='10 70 0' src='https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/car.glb?v=1740160365174' dynamic-body='shape: none' shape__main='shape: box; offset: 0 1.3 1.5; halfExtents: 1.5 1.3 2.5' shape__hood='shape: box; offset: 0 1 -2.3; halfExtents: 1.5 1 1.3'></a-gltf-model>
</a-scene>
</body>
</html>
In an actual MmSpicy io map, all the objects except the player are static bodies. So when making custom maps, make sure to do that.
This is what the player entity looks like: <a-sphere id='player' position='0 5 0' visible='false' player-controls>
The dynamic-body component is added in later with a spawn script.
<a-camera position='0 1.6 0' look-controls='pointerLockEnabled: true' wasd-controls='enabled: false' id='cam'></a-camera>
</a-sphere>
To avoid delays or popping in when your map loads, preload all assets (like images and 3D models) inside the a-assets tag at the top of the scene. Image textures are loaded inside the img tag.
<a-assets><img src="link or path to your image" id="name for the image"></a-assets>
3D models are loaded with the a-asset-item tag. <a-assets><a-asset-item src="link or path to your model" id="name for the model"></a-asset-item></a-assets>
To use a loaded asset, set the src attribute of your entity to the asset’s ID with a # in front.<html>
As you can see, everything still works fine:
<head>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<a-asset-item src="https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/car.glb?v=1740160365174" id="car"></a-asset-item>
<img id="ground" crossorigin="anonymous" src="https://cdn.glitch.global/756a4aaf-b43f-4a95-998c-1c3ac912e721/ground.001.png?v=1725831593424">
</a-assets>
<a-box position="0 -1 0" scale="100 1 100" static-body src="#ground" material="shader: flat"></a-box>
<a-gltf-model position="0 0 -5" src="#car"></a-gltf-model>
<a-sky color="lightblue"></a-sky>
</a-scene>
</body>
</html>
Our assets are stored in this github repository: https://github.com/cHacHaem/mmspicyassets To use them in your map, copy this cdn: https://cdn.jsdelivr.net/gh/cHacHaem/mmspicyassets@main/
Then put the name of the file after the @main/. For example, to use the car model, you would put https://cdn.jsdelivr.net/gh/cHacHaem/mmspicyassets@main/car.glb
Now, you can create custom maps in the editor. In the starter template, you'll see a simple aframe scene with a movment and spawn script attached. Every time you make an edit, it's shown on the right preview panel and saved to your computer with local storage.
Remember, if you ever need to know something, check the A-Frame documentation or ask an AI chatbot like Chat GPT or Gemini. Just keep in mind: AI sometimes makes mistakes. It’s best to understand the code and prompts you're working with. That way, you can build better, more reliable maps! Now you’re ready to build awesome 3D maps for MmSpicy io. Good luck—and happy coding!