Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Burn: 8-Bit Game Development With Ruby

remore
September 20, 2014

Burn: 8-Bit Game Development With Ruby

There are countless ways out there that emulate .nes game, but very few that create .nes rom file without hassle. This is the reason why Kei have developed Burn, is_a handy toolkit to make a .nes application from Ruby DSLs. Burn v0.1.3 release is announced on reddit this April, and it spread to every corner of the world.

In this presentation we will explore what Burn is, how it is structured, and what homebrewed .nes application looks like in an NES Emulator. We'll also learn a few practical techniques for making the most from Ruby bindings, Ripper and Ruby DSL.

An introduction at RubyKaigi2014 official website:
http://rubykaigi.org/2014/presentation/S-KeiSawada

Github repository:
https://github.com/remore/burn

remore

September 20, 2014
Tweet

More Decks by remore

Other Decks in Technology

Transcript

  1. self.introduction do greetings "Hi" id "remore" name "Kei Sawada" icon

    from "Tokyo" job "Software Engineer" favorite do method "instance_eval" instrument "Double Bass" end end
  2. #VSOJOHOFT'VFM 'VFM CVSOHFN QSFQSPDFTTPS NBJOD NVTJDT TPVOET NBJOOFT NVTJDEP 

    FOE EFDMBSFEP GPP FOE TDFOFEP MBCFMbIFMMP XPSME` FOE SVOUJNFMJC DPNQJMFS MJOLFS ϫʔΫϑϩʔ͸͜Μͳײ͡Ͱ͢ɻ̍ʣ࣮ߦɺ'VFMΛೖྗ ̎ʣ$ͷιʔε౳Λੜ੒ɹ̏ˍ̐ʣDDͰίϯύΠϧɾϦϯΫ
  3. UNQCVSONBJOD # installation sudo gem install burn sudo burn init

    # :nes mode echo "scene {label 'hello world'}" > main.rb burn vi tmp/burn/main.c 5IFCFTUXBZUPTFFNBJODJTUPUSZ CVSOJOH
  4. UNQCVSOBTTFUNVTJDT sarabande: .word @chn0,@chn1,@chn2,@chn3,@chn4,music_instruments .byte $03 @chn0: @chn0_0: .byte $c2,$43,$1a,$89,$3f,$89...$13,$89,$3f,$89,$3f,

    $13,$89,$3f,$95,$3f @chn0_loop: @chn0_1: .byte $9f .byte $fe .word @chn0_loop @chn1: @chn1_0: .byte $c2,$42,$29,$89,$3f,$89...,$89,$3f,$2e,$89,$3f, $89,$3f,$2e,$89,$3f,$95,$3f @chn1_loop: @chn1_1: .byte $9f .byte $fe .word @chn1_loop
  5. UNQCVSOBTTFUTPVOET sounds: .word @attack @attack: .byte $00,$af .byte $01,$c8 .byte

    $19 .byte $00,$af .byte $01,$c8 .byte $19 .byte $01,$64 .byte $1a .byte $00,$af .byte $01,$c8 .byte $19 .byte $01,$64 .byte $1a .byte $01,$32 .byte $1a .byte $ff
  6. 8SJUJOH3VCZ%4-BOE$PEF declare do frame 0 end scene do label "hello,

    world!", 4, 5 color :text, :white main_loop <<-EOH frame+=1 goto “fin” if frame==100 EOH end scene "fin”“do label "Good-bye!" end #JOEJOH  JOTUBODF@FWBM ݸਓతʹɺ3JQQFSͱ#JOEJOH ͱ಺෦%4- ͸ 3VCZͷ(PPE1BSUTͩͱࢥͬͯ·͢ɻ͜Ε͸Ͳͷ෦෼ʹ׆༻ͯ͠Δ͔ͷਤɻ 8IBUUIF)FMMJTUIJT
  7. %FUBJMFE1SPDFTTPGNBJO@MPPQ frame+=1 goto "fin" if frame==100 [:program, [[:opassign, [:var_field, [:@ident,

    "frame", [1, 0]]], [:@op, "+=", [1, 5]], [:@int, "1", [1, 7]]], [:if_mod, [:binary, [:var_ref, [:@ident, "frame", [2, 14]]], :==, [:@int "100", [2, 21]]], [:command, [:@ident, "goto", [2, 0]], [:args_add_block, [[:string_literal, [:string_content, [:@tstring_content, "fin", [ 6]]]]], false]]]]] 3JQQFSTFYQ SFNPSFTJNQMF@USBOTQJMFS 3JQQFS͕ѻ͍΍͍͢4ࣜΛఏڙͯ͘͠Ε͍ͯΔ͓͔͛Ͱɺ ߦͪΐͬͱͷίʔυͰ$ݴޠ෩ͷจࣈྻ΁ͷม׵͕؆୯ʹ࣮૷Ͱ͖·͢ ɾɾɾ3VCZ`T4ZOUBY frame+=1; if(frame==100){ goto("fin"); } ɾɾɾ$4ZOUBY
  8. &YBNQMF #!/usr/bin/env ruby require 'ripper' require './simple_transpiler' fuel = <<-EOH

    frame+=1 goto "fin" if frame==100 EOH SimpleTranspiler.ruby2c(Ripper.sexp(fuel)) # frame+=1; # if(fraome==100){ goto(“fin”); } ʢ΋ͪΖΜɺѻ͑Δߏจ͸ຊ౰ʹجຊతͳ΋ͷͷΈͰɺ੍ݶ͋Γ·͘ΓͰ͕͢ʣ
  9. (FOFSBUJOH3VCZ$PEF  @___star = Sprite.new(" 11 11 1111 1111 1111111111111111

    11111111111111 111111111111 1111111111 11111111 11111111 11111111 1111 1111 11 11 1 1 ",0,0)  #main  #main-main_loop:2  @user_input.init_for_next_loop  @___star.x=20  @___star.y-=3  @screen.activated_sprite_objects << @___star if  @screen.activated_sprite_objects.index(@___star).nil?  @pc = @opcodes.index("#main-main_loop:2")  #END *OEFYBOE$POUFOUT 4USJOH PG@opcodes"SSBZ
  10. #VSOJOHUFMOFU'VFM 5FMOFU4FSWFS TFSWFSUFMOFUSC DPOpHBQQ UBSHFUUFMOFU FOE EFDMBSFEP GPP FOE TDFOFEP

    MBCFMbIFMMP` FOE class TelnetVm attr_reader :screen def initialize(fuel) @pc=0 @opcodes=JitCompiler .new(fuel).compile end def next_frame self.instance_eval @opcodes[@pc] @pc+=1 end def interrupt # trigger event # by setting interrupt flag end end 'VFM FWFOUNBDIJOF JOUFSSVQU OFYU@GSBNF !TDSFFO GVFM LFZFWFOU 5FSNJOBMT TDSFFO PVUQVU UFMOFUαʔόͷ؆୯ͳઃܭਤͰ͢ɻFWFOUNBDIJOFͷதͰͭͷ QFSJPEJD@UJNFSΛಈ͔͍ͯ͠·͢ɻͭ͸ը໘ग़ྗɺ΋͏̍ͭ͸ೖྗड෇༻
  11.  @___star = Sprite.new(" 11 11 1111 1111 1111111111111111 11111111111111

    111111111111 1111111111 11111111 11111111 11111111 1111 1111 11 11 1 1 ",0,0)  #main  #main-main_loop:2  @user_input.init_for_next_loop  @___star.x=20  @___star.y-=3  @screen.activated_sprite_objects << @___star if  @screen.activated_sprite_objects.index(@___star).nil?  @pc = @opcodes.index("#main-main_loop:2")  #END 0OFWFSZTJOHMFQFSJPE UJNFGSBNF PG&WFOU.BDIJOF JOTUBODF@FWBMXJMMCFDBMMFE BT!QDJTJODSFNFOUFE TBNQMFEVNQEBUBPG!PQDPEFT
  12. 1FSGPSNBOU&OPVHI *TO`U*U w -(5. w 6QUJNFEBZT DPOO-"NJOdNBY BWH d 

    d  d  d  ্ʑʁ
  13. “Why do they call it rush hour when nothing moves?”

    Robin Williams lԿ΋ಈ͍ͯͳ͍ͷʹɺͳΜͰϥογϡΞϫʔͳΜͯݺͿΜ͍ͩʁz ϩϏϯɾ΢ΟϦΞϜζʢΞϝϦΧͷίϝσΟΞϯɾആ༏ʣ
  14. ·ͣແཧ͚ͩͲΦʔϓϯιʔεͳ30.ւ଑൛30.ͳ ੈքʹ͢Δ͜ͱ͕5IF3JHIU8BZͩͱࢥͬͯ·͢ɻ $BO8F8PSLBSPVOE 5IF4JUVBUJPO w 4PPO "MNPTUIPQFMFTT w )PXFWFS JOMPOHUFSN

    #VSOJTBJNJOHUP NBLFUIJTXPSMEGVMMPGPQFOTPVSDF w FH'VFM)VC w )PQFGVMMZ FWFOUVBMMZ w 0QFOTPVSDFE30.1JSBUFE30.
  15. "DLOPXMFEHFNFOU w -PHPT GSPN5IF/PVO1SPKFDU w IUUQUIFOPVOQSPKFDUDPNUFSNpSF w IUUQUIFOPVOQSPKFDUDPNUFSNSFGSFTI w 1IPUPT

    w IUUQTXXXqJDLSDPNQIPUPTIJTHMBTTXPSLT  w IUUQTXXXqJDLSDPNQIPUPTSBJOEPH 
  16. -JNJUBUJPOT w 5IFSFBSF50/40'MJNJUBUJPOT w SPNNPEF GPMMPXJOHUIJOHTBSF/05TVQQPSUFE w FHQMBZFS TDSPMMJOHTDSFFO %1$.

    NBLJOHGVMM VTFPGBUUSJCVUFUBCMFFUD w UFMOFUNPEF  w TJOHMFJOQVUNPEFJTOPUTVQQPSUFE FUD w )POFTUMZ XJUI#VSOWEFWFMPQFSPOMZDBO CVJMEQPPSBQQMJDBUJPO ੍ݶࣄ߲͹͔ΓͳͷͰɺͰ͖Δ͜ͱ͚ͩΛॻ͍ͨํ͕ૣ͍ঢ়ଶͰ͕͢ɾɾɾ ը໘εΫϩʔϧ͕Ͱ͖ͳ͍ɺεϓϥΠτ͕Ξχϝʔγϣϯ͠ͳ͍ɺͳͲ
  17. 'VFM%4-ˌEFDMBSF TDFOF declare do i 0 #equivalent to "int i=0;"

    in C end scene "game" do color :text, :red # use :red color for text label "hello", 3, 5 # display string "hello" at x=3, y=5 play "battle" # play "battle" music fade_in # trigger fade-in effect wait 30 # wait for certain period goto "intro" # move to another scene end EFDMBSFσΟϨΫςΟϒͰ͸ɺϓϩάϥϜͰར༻͢Δม਺Λએݴ͠·͢ɻ TDFOFσΟϨΫςΟϒ͕ɺ࣮ࡍͷϓϩάϥϜͷಈ࡞Λهड़͢ΔՕॴͰ͢ɻ
  18. config :app do target :telnet # :rom by default width

    80 # 67 by default height 24 # 13 by default fps :high # :moderate, :low user_input :enable # :disable by default end config :server do ip_addr '0.0.0.0' # 127.0.0.1 by default port 60000 # 60000 by default max_clients 20 # 10 by default end 'VFM%4-ˌDPOpH ͜ͷதͰ͸UBSHFU͕Ұ൪ॏཁͰɺ͜Ε͕#VSOͷಈ࡞ϞʔυΛنఆ͠·͢ɻ TFSWFSʹ͍ͭͯ͸ɺOFTͷ৔߹͸8&#SJDL UFMOFUͷ৔߹͸ &WFOU.BDIJOFͷಈ࡞Λنఆ͠·͢ɻ
  19. 'VFM%4-ˌNVTJD music "battle" do tempo :allegro # set tempo channel

    "string" do # select instrument d2 :eighth # :eighth is the length rest :eighth d2 :eighth rest end channel "piano" do a3 a3 end end ݸਓతʹ͸#VSOͰҰ൪໘ന͍ͱࢥͬͯΔ%4-Ͱ͢ɻ OFTϞʔυͷΈ͔͠ରԠ͍ͯ͠·ͤΜ͕ɺ%4-Λॻ͘ͱ OFTΤϛϡϨʔλʹ౥ࡌ͞ΕͯΔ'.ԻݯΛૢ࡞ͯ͠ԻָΛ࠶ੜͰ͖·͢ɻ
  20. 'VFM%4-ˌTPVOE sound "attack" do effect do duty_cycle :higher velocity 15

    # 7 by default envelope_decay :disable # disabled by default length 10 # range: 1-254 pitch 200 end effect do length 11 pitch 100 end end '.ԻݯΛར༻ͨ͠ޮՌԻͷੜ੒΋ՄೳͰ͢ɻ '.Իݯʹͦ͜·Ͱৄ͘͠ͳ͍ͷͰɺ֤ϝιουΛͲ͏͍͡Δͱ ͲΜͳԻʹͳΔͷ͔ɺ·ͩ๻ʹ΋૝૾͖ͭ·ͤΜɻ