CSS職人 入門
はじめに
はじめまして、jumdtwです。
ノリで始めたCSS職人について書いていこうと思います。
ソースコード
きっかけ
1, メジロマックイーンを崇拝する
2, マックイーンの絵でも描こうとググる
3, youtubeで戦場ヶ原さんをCSSで書いているやばい人を見かける
4, ノリで始めてしまう
といった感じです。ちなみに、マックイーンを描くのに40時間以上かかっているので皆さんはおとなしくペンタブ買ったほうが賢明だと思います。
完成品
完成品はこちらになります。
ウマ娘のゲーム内イラストを参考に作成しました。
CSS 職人基本テクニック
ここからはCSS職人の基本テクニックを紹介していきます。マックイーンも下記の基本テクニックで99%記述しています。
overflow: hidden
overflow: hidden は下記図のように親要素からはみ出した子要素を表示しなくなるといったものです。
これとborderなどを利用して線画を仕上げていきます。
box-shadow
上記 overflowとborderだけでもかけなくはないのですがとがっている部分や大きな楕円部分は書きにくかったり塗りにくかったりします。そこでbox-shadowを利用します。box-shadowを利用することで尖っている表現をきれいに書くことができます。
余談
正直CSSで描くメリットがあまり多くないというは事実。CSSを理解している人からすると努力がなんとなく伝わるとは思うのだが、多くの人はCSSを知らないので見た目上は変わらない。
まとめ
みんなもCSS職人になろう!!!
golangでRPCをやる
2021年現在 恐竜キング 7つのかけらでメガラプトルを入手する方法
そんでもってそんな恐竜キング唯一のゲームがDSから出ているのですが、これが曲者(「恐竜キング 七つのかけら」で検索検索~。以降「本ゲーム」とします)。 ゲーム自体の面白さはいいんですが、DS時代によくあった「nintendo wifiコネクション~」、「会場で通信すると~」がいっぱい詰まっている。 これのせいでまず、図鑑のコンプリートが不可能だったりします。
てなわけで通常の方法では入手不可能になったとさ。
Go言語をwindows10でもデバッグしたい
まぁ、Println関数には文字列やらint型の変数やらが来るわけだからinterface{}で対応しているのね。ちなみに、その後のinterface{}型の変数をデバッガで追っていったら 以下のようなコードがあった。
cron で sudo がしたい
# 分 時 日 月 曜日 <CMD0>
30 3 * * * <CMD1>
0 5 * * * <CMD2>
# 分 時 日 月 曜日 <CMD0>
n h * * * ~/hoge.sh
sudo touch fuga
user ALL=NOPASSWD: touch
root権限に以降
$ sudo su
root権限でcronを使用したことない場合は以下コマンドで設定ファイルを作る
# crontab -u root -e
記述した設定ファイルを読み込ませる
# crontab cron.conf
n h dom mon dow /home/user/test.sh
sudo touch fuga
以下をコメントから除外
cron.* /var/log/cron.log
ファミコンのわかりづらい仕様について
01Sub/Cmp命令について
- Aレジスタからメモリまたは即値を減算する
- キャリーフラッグを反転したものを減算する
これら手順が終了した際に四つのフラッグを変更するものだと思ってました。
- Aレジスタからメモリまたは即値を減算する
- キャリーフラッグを反転したものを減算する
- 計算結果に応じて4つの各フラッグを書き換える
- Aレジスタからメモリまたは即値を減算する
- キャリー/オバーフローフラッグを更新する
- キャリーフラッグを反転したものを減算する
- 計算結果に応じてゼロ/ネガティブフラッグを更新する
02$2007のreadについて
ファミコンはメモリマップトioなので、PPUのメモリを読み取る時は$2007番地を利用するのだが、ここにトラップがひそんでいる。 読み込む際、一回目の読み込みはバッファの影響で読み込まれない。なので、$2006番地(PPUの読み込みする番地を指定するメモリ)を更新した後、 一回読み込みをスルーしなければならない。これを知らないとCPUは正しく動作しているのに、うまく動作しないという地獄が始まる。
03Zero Page IndexX
で、これの問題が、
つまり、計算であふれた値は切られるということだ。ファミコンのアドレスは16bitなので、これに気づかないと意外とはまる。
04Scroll指定について
メモリの$2000番地を使うと描画の基準点を設定できるのだが、PPUメモリの$2000を基準と設定すると画像の青い点の部分が描画の基準となる。
05まとめ
ファミコンエミュレータ作り おわり
FC emulator作り with golang
完成までのマラソン記事。がんばってやり切るぞい。
また、注意点としてこの記事はコードを更新しながら作成していっていくので記事で書いてある内容と実際のコードに差が生まれる可能性はあります。
お問い合わせは私のツイッターまでお願いします。@jumdtw
CPUからVRAMへの書き込み
これまでにCPUとPPUを作成したのでそれを連携させてPPUから画面に描画させます。
今回で目標は達成します!!
CPUとPPUの連携
PPUの画面描画をEbitenというgolangで使えるゲームエンジンで実装しましたが、そのUpdate関数の中でCPUを動作させていきます。
ここでまずお伝えしたいこととしてCPUは直接的にVRAMに書き込みができません。0x2006番地と0x2007番地のメモリマップトioを使用してVRAMに書き込んで行きます。
そのためCPUのstore命令の実装を少し変えます。
// FC_CPU/emuCPU.go type CpuEmu struct { //各割り込みを行うための情報 Irqaddr uint16 Nmiaddr uint16 Resetaddr uint16 InterruptFlag bool // vram へ書き込みを行うための内部情報 // vram addr VramAddr uint16 // vram write flag VramWriteFlag bool // vram write value VramWriteValue uint8 // A X Y S P Regi map[string]uint8 // PC RegPc uint16 // Memory Memory [memCap]uint8 }
// FC_CPU/opcmd.go func (fcEmu *CpuEmu) staAbs() { var absposhigh uint16 = uint16(fcEmu.Memory[fcEmu.RegPc+1]) var absposlow uint16 = uint16(fcEmu.Memory[fcEmu.RegPc]) var pos uint16 = (absposhigh << 8) + absposlow // 0x2006だったらppuへのアクセス if pos == 0x2006 { fcEmu.VramAddr = fcEmu.VramAddr << 8 fcEmu.VramAddr += uint16(fcEmu.Regi["A"]) } if pos == 0x2007 { fcEmu.VramWriteFlag = true fcEmu.VramWriteValue = fcEmu.Regi["A"] }else { fcEmu.VramWriteFlag = false } fcEmu.Memory[pos] = fcEmu.Regi["A"] fcEmu.RegPc = fcEmu.RegPc + 2 }
まず、CPUの構造体に現在の状態を管理できるフラッグや変数を設置します。そしてstore命令でio制御用にマッピングされている領域に書き込みが行われた場合それ専用の処理を記述しています。今回はVRAMアクセス用のものしか実装していません。
これを実装し、画像データの入ったROMの中身を画面に描画するプログラムをCPUエミュレータに実行させていきます。(使っている画像データがデータだけにそのプログラムの配布ができません。申し訳ありません。)
CPUの実行タイミング
以下は前回作成したUpdate関数を改良したものです。
// FcEmulator.go func (g *Game) Update(screen *ebiten.Image) error { // Generate the noise with random RGB values. var numblock int var numtile int var vv int for i :=0; i<30; i++ { for k :=0; k < 32; k++ { // vv は n枚目のタイルの一番左上のピクセルの最初の配列番号 // k は一増えるごとに8pixl×ピクセル倍率分増える // i は一増えるごとに256×ピクセル倍率に8pixl×ピクセル倍率を掛ける // 最後に4を掛けることにより配列数を出す。 vv = (k*8*Pixlsize+i*256*Pixlsize*8*Pixlsize)*4 // patternNum がどのブロックにあるか // tileは32x30の半分なのでブロックは16x15.ナンバリングは0スタート numblock = Numblockreturn(i,k) numtile = k+i*32 DrawTile(g,vv,numblock,numtile) // ppu はcpuの3倍のクロックを持っているのでppuの方が動作的には早いが // さすがにここで実行すると遅すぎるので将来的には修正が必要 cpuexecute(g) } } return nil }
cpuexecute(g)を実行することで一命令実行していきます。ただし、この位置での実行は実際のPPUとCPUの実行速度にかなりの差が出てしまうため将来的には修正が必要担ってきます。今回はこれでも目標に達成できるだけの実行速度には達しているのでこれで妥協します。
以下はcpuexecute(g)のソースコードです。
// FcEmulator.go func cpuexecute(g *Game){ // NMI割り込み // 多分本来のハードウェア動作だと次にこの動作処理が来るまでにCPU側で処理が終わっている。 // でもこのエミュレータだと実質同期処理しているような状態なので処理が終わる前に再び // nmiaddrで割り込みが入ってしまう。そのため割り込み処理が終わるまでフラッグで判断して処理する。 nmiflag := g.Cpuemu.Memory[0x2000] & 0b10000000 if nmiflag == 0b10000000 && g.Cpuemu.InterruptFlag == false{ // ステータスレジスタの変化 g.Cpuemu.Regi["P"] = g.Cpuemu.Regi["P"] & 0b11101011 g.Cpuemu.Regi["P"] = g.Cpuemu.Regi["P"] + 0b00010100 // 割り込みフラッグ 割り込み中はtrue // 割り込み処理に入ったのでtrueにする g.Cpuemu.InterruptFlag = true // スタックに現在のPCを積む var sppos uint16 = 0x1000 + uint16(g.Cpuemu.Regi["S"]) // 0x4050 だったら 50 40 の順でスタックに積む。個々の動作は等で確認が取れていないためこのエミュレータだけの可能性あり。 lowaddr := g.Cpuemu.RegPc & 0x00ff highaddr := g.Cpuemu.RegPc & 0xff00 highaddr = highaddr >> 8 g.Cpuemu.Memory[sppos] = uint8(lowaddr) g.Cpuemu.Memory[sppos] = uint8(highaddr) g.Cpuemu.Regi["S"] = g.Cpuemu.Regi["S"] - 2 // 割り込みアドレスの代入 g.Cpuemu.RegPc = g.Cpuemu.Nmiaddr } g.Cpuemu.Execute() if g.Cpuemu.VramWriteFlag { g.Ppuemu.Memory[g.Cpuemu.VramAddr] = g.Cpuemu.VramWriteValue g.Cpuemu.VramAddr++ g.Cpuemu.VramWriteFlag = false } }
NMI割り込みとはハードウェアから入る強制的な割り込みです。ファミコンでは一定周期でPPUからCPUへこの割り込みが入ります。(CPU側の設定で遮断可能)
割り込みがあったら割り込み用のアドレスに書き換えます。
もし割り込みがなかったらそのまま命令を実行します。
実行結果
こんな感じになりました。前回と違ってちゃんとCPUから色情報も書き込んでいるので色塗りもいい感じになっています。
プログラムの内容自体はROMの中身をただただ描画するだけなのでこれ以上の動きは特にありません。
衝撃の事実
無事目標を達成してファミコンエミュレータの完成です!!!!!!!!!!!!
というのは幻想です
なぜならこのエミュレータは多大な問題を抱えているからです。
・そもそもマッパー0(拡張機能なしの純粋なファミコンのこと。つまりドラクエは無理)しか動かない
・スクロール機能がなくマリオのようなアクション系のゲームが動かない
・そもそもスプライト機能がなくマリオの描画ができない
・コントローラー?知らない子ですね
軽くあげるだけでこれだけありますね。特にスプライトとコントローラーが致命的です。
いやまぁ、実装しようと思えばできるのですが今回の目標は美少女の描画ということにしてしまったので。
やるとしたら番外編ということでまた記事自体は書くかもしれません。
まとめ
今回のエミュレータ作りを通して学べたことは以下です。
・golangの標準パッケージのソースが具体的にどのように動いているのか
・並行処理の重要性とその実装
・ファミコンのすごさ
個人的には並行処理が学べて楽しかったです。オライリーから書籍も出ているのでぜひ。
参考
YY-CHR @wiki
電子書籍「ファミコンゲーム製作入門」(おまけ付き)
ギコ猫でもわかるファミコンプログラミング
Memory-Map of NES
ファミコンのグラフィックスの省メモリ化テクニックとは?
Go言語 - 複数ファイルのコンパイル
Golangで自分自身で定義したパッケージをインポートする方法あれこれ
配列と Slice
Go言語でバイナリファイルを読み込む