-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
566 additions
and
1 deletion.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<title>TetaNES Web</title> | ||
<style> | ||
@font-face { | ||
font-family: "Emulogic"; | ||
src: local("Emulogic"), url("emulogic.ttf") format("truetype"); | ||
} | ||
|
||
body { | ||
--color: #aaa; | ||
--heading: #92340b; | ||
--background: #0c141f; | ||
background-color: var(--background); | ||
max-width: 80%; | ||
margin: auto; | ||
margin-bottom: 100px; | ||
color: var(--color); | ||
font-family: "Courier New", Courier, monospace; | ||
} | ||
|
||
h1 { | ||
margin: 40px 0; | ||
} | ||
|
||
h1, | ||
h2 { | ||
color: var(--heading); | ||
font-family: Emulogic; | ||
text-align: center; | ||
} | ||
|
||
p { | ||
font-weight: 600; | ||
max-width: 70ch; | ||
margin: 15px 0; | ||
} | ||
|
||
table { | ||
--color: #333; | ||
border-collapse: separate; | ||
border-color: var(--color); | ||
border-spacing: 0; | ||
border: 0.5px solid var(--color); | ||
text-align: left; | ||
width: 100%; | ||
} | ||
|
||
th { | ||
color: var(--heading); | ||
} | ||
|
||
th, | ||
td { | ||
padding: 5px; | ||
border: 0.5px solid var(--color); | ||
} | ||
|
||
td { | ||
font-weight: bold; | ||
} | ||
|
||
a { | ||
color: #ffe64d; | ||
text-decoration: none; | ||
} | ||
|
||
a:hover { | ||
color: #df740c; | ||
text-decoration: underline; | ||
} | ||
|
||
canvas { | ||
background: rgba(0.13, 0.13, 0.13, 1); | ||
margin: 0 40px; | ||
width: fit-content; | ||
height: fit-content; | ||
} | ||
|
||
.load-rom { | ||
width: 0.1px; | ||
height: 0.1px; | ||
opacity: 0; | ||
overflow: hidden; | ||
position: absolute; | ||
z-index: -1; | ||
} | ||
|
||
button, | ||
.load-rom-label { | ||
background-color: #92340b; /* Rust color */ | ||
border: none; | ||
color: white; | ||
cursor: pointer; | ||
display: inline-block; | ||
text-decoration: none; | ||
font-family: inherit; | ||
font-size: 1em; | ||
font-family: Emulogic; | ||
} | ||
|
||
#scale1, | ||
#scale2, | ||
#scale2 { | ||
width: 3.5em; | ||
} | ||
button { | ||
margin: 10px 0; | ||
padding: 5px 8px; | ||
} | ||
button:hover, | ||
label:hover { | ||
background-color: #0b6992; | ||
} | ||
|
||
.load-rom-label { | ||
margin-top: 20px; | ||
margin-bottom: 10px; | ||
width: 10em; | ||
font-size: 1em; | ||
padding: 10px 16px; | ||
text-align: center; | ||
} | ||
|
||
button:focus, | ||
button:hover, | ||
.load-rom-label:focu, | ||
.load-rom-label:hover { | ||
color: #000; | ||
background-color: #48bef1; | ||
} | ||
|
||
.load-rom-label:focus { | ||
outline: 1px dotted #fff; | ||
outline: -webkit-focus-ring-color auto 5px; | ||
} | ||
|
||
.load-rom-label * { | ||
pointer-events: none; | ||
} | ||
|
||
#error { | ||
color: red; | ||
font-weight: bold; | ||
text-align: center; | ||
margin-bottom: 20px; | ||
} | ||
|
||
#wrapper { | ||
display: flex; | ||
justify-content: center; | ||
flex-wrap: wrap; | ||
margin-bottom: 20px; | ||
} | ||
|
||
#content { | ||
max-width: 70ch; | ||
margin: auto; | ||
} | ||
|
||
#controls { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
|
||
#homebrew-menu { | ||
top: 40px; | ||
left: 50%; | ||
transform: translateX(-50%); | ||
background-color: var(--background); | ||
position: absolute; | ||
width: 50%; | ||
height: 500px; | ||
border: 2px solid var(--heading); | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
#homebrew-list { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
|
||
#homebrew-close { | ||
position: absolute; | ||
right: 10px; | ||
} | ||
|
||
.hidden { | ||
display: none !important; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<noscript | ||
>This page contains WebAssembly and Javascript content, please enable | ||
Javascript in your browser.</noscript | ||
> | ||
|
||
<h1>TetaNES</h1> | ||
|
||
<div id="error"></div> | ||
<div id="wrapper"> | ||
<canvas id="view" width="512" height="480"></canvas> | ||
<canvas id="backView" class="hidden"></canvas> | ||
|
||
<div id="controls"> | ||
<input | ||
type="file" | ||
id="load-rom" | ||
name="load-rom" | ||
class="load-rom" | ||
accept=".nes" | ||
/> | ||
<label id="load-rom-label" for="load-rom" class="load-rom-label" | ||
>Load ROM</label | ||
> | ||
|
||
<button id="load-homebrew">Homebrew ROMs</button> | ||
|
||
<div id="scale"> | ||
<button id="scale1">1X</button> | ||
<button id="scale2">2X</button> | ||
<button id="scale3">3X</button> | ||
</div> | ||
|
||
<button id="toggle-audio">Unmute</button> | ||
<button id="toggle-pause">Pause</button> | ||
|
||
<div id="fps"></div> | ||
</div> | ||
</div> | ||
|
||
<div id="content"> | ||
<p> | ||
<em>TetaNES</em> is an emulator for the Nintendo Entertainment System | ||
(NES) written in <a href="https://www.rust-lang.org/">Rust</a> and | ||
<a href="https://webassembly.org/">WebAssembly</a>. The desktop version | ||
is much more performant and fully featured, written using | ||
<a href="https://www.libsdl.org/">SDL2</a>. | ||
</p> | ||
|
||
<p> | ||
You can choose from a list of homebrew games, or load your own ROM which | ||
uses the <a href="https://www.nesdev.org/wiki/INES">iNES</a> or | ||
<a href="https://www.nesdev.org/wiki/NES_2.0">NES 2.0</a> format. | ||
</p> | ||
|
||
<p> | ||
You can check out the code on | ||
<a href="https://github.com/lukexor/tetanes">github</a>. | ||
</p> | ||
|
||
<h2>Controls</h2> | ||
<table> | ||
<tr> | ||
<th>Action</th> | ||
<th>Key</th> | ||
</tr> | ||
<tr> | ||
<td>A Button</td> | ||
<td>Z</td> | ||
</tr> | ||
<tr> | ||
<td>B Button</td> | ||
<td>X</td> | ||
</tr> | ||
<tr> | ||
<td>A Button (Turbo)</td> | ||
<td>A</td> | ||
</tr> | ||
<tr> | ||
<td>B Button (Turbo)</td> | ||
<td>S</td> | ||
</tr> | ||
<tr> | ||
<td>Start Button</td> | ||
<td>Return</td> | ||
</tr> | ||
<tr> | ||
<td>Select Button</td> | ||
<td>Right Shift</td> | ||
</tr> | ||
<tr> | ||
<td>D-Pad</td> | ||
<td>Arrow Keys</td> | ||
</tr> | ||
</table> | ||
</div> | ||
|
||
<div id="homebrew-menu" class="hidden"> | ||
<h2>Homebrew ROMS</h2> | ||
<div id="homebrew-list"></div> | ||
<button id="homebrew-close">X</button> | ||
</div> | ||
|
||
<script src="dist/bundle.js"></script> | ||
</body> | ||
</html> |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
Development notes | ||
|
||
After completing Lawn Master, I had plan to try use C compiler to make a simple | ||
NES game. From my previous experience with programming in C for micros (Genesis | ||
and ZX Spectrum), and from things that thefox did, I knew it could be very | ||
worthy in terms of development speed. The plan was to check it, and, in case of | ||
success, prove that C is an actual option to develop NES games, not just a | ||
theoretical possibility. | ||
|
||
I wanted to make a project very fast, so I decided to not do an original game | ||
this time, because design takes most of the time, and just make a port. I've | ||
seen two new ZX Spectrum games by Denis Grachev, Join and Alter ego, at WoS when | ||
they were released, and liked the combination of simplicity, playability, and | ||
sort of retro appeal in them. I made a low-level library in 6502 assembly to use | ||
in the project first. When main features of the library were implemented, I have | ||
sent a mail to Denis, asking if he would allow to port Alter Ego. It took some | ||
time, from June 8 to 17, to get the answer. Denis gave his permission, but I | ||
already was busy with other project, and only has been able to start on the port | ||
June 25. | ||
|
||
Development process took about 10 days, the game was fully completed, but | ||
without music, at July 5. This includes finishing the library, writing all the | ||
game code from scratch, reverse-engineering levels format, and beating up both | ||
the original game and port few times to test everything. The most difficult part | ||
that was not expected by me initially was redesign of all the levels from | ||
scratch. Initially I thought I can just convert them, edit a bit, and draw new | ||
graphics, but in order to be able to use more colors and make better graphics I | ||
had to completely redo all the levels to the NES attribute grid, only keeping | ||
overall design of the original levels. In other words, none of the original data | ||
get into the port, and there were some changes to make it more playable as well, | ||
so it is actually more like a remake than a port. Levels and graphics redesign | ||
took most of the time. I also made 5 graphics sets instead of 3 sets from the | ||
original. | ||
|
||
Code part was relatively easy both in assembly (low-level libary) and C (game), | ||
except for few WTF bugs that took some time to figure out. There are about 1000 | ||
lines of assembly code for library, 1000 lines of FamiTone code (has been | ||
adapted easiliy), and ~1500 lines of C code. Even total number of lines, 3500, | ||
is significally less than amount of assembly code in my previous NES games that | ||
were written in assembly, had ~5000 lines each (including FamiTone too), and | ||
were much simpler gameplay and game logic wise. | ||
|
||
As the game was a bit short on RAM, I've put FamiTone vars along with palette | ||
buffer into the stack page. Despite being written in C, the game uses ~20 bytes | ||
of the stack at most. | ||
|
||
Other part of speeding up the development process was 'outsourcing' of the | ||
music. I knew it is a risky decision, because any other person involved into a | ||
project actually increases overall time, not decreases it, but I just tired from | ||
making everything by myself all the time. It did increased time very | ||
considerably - although I've negotiated about the music with kulor even before | ||
starting any actual work on the game, by different reasons including personal | ||
busyness and some misunderstanding, he only started 15th, ten days after the | ||
game development itself was completed. This amount of music revealed a lot of | ||
bugs and problems in FamiTone, not all of which were fixed, and data of one of | ||
tracks was fixed by hand due to lack of time. Music was finished 22th, just in | ||
time for DiHalt demoparty. Initially I planned to just release game, but because | ||
the party date was now close, and there was a multiplatform game compo, I | ||
decided to release the game there to get more publicity. | ||
|
||
My conclucion regarding C usage on NES is that it is worthy indeed. It speeds up | ||
and simplifies development process a lot because it greatly reduces amount of | ||
code to be written and debugged, and the code is much more readable. However, to | ||
use C you just have to know the system and 6502 very well, because debugging is | ||
much more difficult - in case of the problems when C code does not work as | ||
expected, you need to figure out what to do by examining of the generated | ||
assembly code. So it is not easy way to program for NES, it actually requires | ||
more knowledge than programming in assembly. Execution speed is, of course, | ||
lower, but this wasn't an issue for this project, the size of the generated code | ||
was more important actually - it is much larger than it could be if programmed | ||
in assembly by hand. | ||
|
||
Please note that the game is released as freeware, not Public Domain. There are | ||
three authors involved. I personally grant you rights to do whatever you want | ||
with things I created (code, sound effects, graphics), but rights to other | ||
components (game concept, characters, title, music) are reserved to authors of | ||
these components. I.e., if you want to port it somewhere else, you need to ask | ||
Denis Grachev (and Kulor, if you need music) for permission. | ||
|
||
|
||
Software used | ||
|
||
CC65 - C compiler and assembler Notepad++ - for all the code and text works | ||
FamiTracker - to make all the music and sound effects UnrealSpeccy - playing and | ||
reversing the original version Borland Turbo Explorer - to make a level editor, | ||
but it was only used to view levels FCEUX, VirtuaNES (profiler mod) - to test | ||
everything, some others for compatibility tests NES Screen Tool - to design all | ||
the graphics, screens, and levels Inkscape, Blender, GIMP, CutePDF Writer - to | ||
make manual and label |
Oops, something went wrong.