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. #VSO#JU(BNF
    %FWFMPQNFOU
    8JUI3VCZ
    !SFNPSF 4FQUI
    3VCZ,BJHJBU'VOBCPSJ +BQBO

    View full-size slide

  2. 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

    View full-size slide

  3. 8IBUPOFBSUIJT#VSO
    ʢ஌Βͳ͍ํ͕΄ͱΜͲͩͱࢥ͍·͕͢ʣ#VSOͱ͸ҰମͳΜͰ͠ΐ͏ʁ

    View full-size slide

  4. #VSOJTBSVCZHFNXIJDIFOBCMF
    ZPVUPDSFBUFZPVSPXO
    OFT30.*NBHF'JMF
    5FMOFU4FSWFS"QQMJDBUJPO
    GSPN3VCZ%4- BTPGW

    #VSO͸SVCZHFNͰ͢ɻ#VSOΛ࢖͏ͱʢWͰ͸ʣɺOFT30.Πϝʔδ΍
    5FMOFUαʔόΞϓϦέʔγϣϯΛ3VCZͷ%4-Ͱ؆୯ʹهड़͢Δ͜ͱ͕Ͱ͖·͢

    View full-size slide

  5. #VSOXPSLTpOFXJUI
    8JOEPXT .BDBOE-JOVY
    $3VCZ /PUZFUUFTUFE
    XJUI+3VCZBOE3VCJOJVT

    7FSTJPO)JTUPSZ4VNNBSZ
    WSFMFBTFEOFTNPEF
    WBEEFEUFMOFUNPEF
    WMBUFTUWFSTJPO

    View full-size slide

  6. %FNP
    IUUQTHJUIVCDPNSFNPSFCVSO

    View full-size slide

  7. telnet 128.199.158.17 60000
    0S-JWF%FNP
    *O0UIFS8PSET 4USFTT5FTU

    View full-size slide

  8. l"NB[JOHXPSLz
    l7FSZWFSZDPPM
    TUV⒎z

    View full-size slide

  9. l5IFDPPMFTUTIJUJT
    BMXBZTUIFNPTU
    VTFMFTTz
    ௒༁ɿ࠷ߴʹΫʔϧͳ΋ͷ͸͍ͭ΋
    ࠷ߴʹ໾ʹཱͨͳ͍΋ͷͰ͢Ͷ

    View full-size slide

  10. *LOPX NBO
    ஌ͬͯͨ

    View full-size slide

  11. OFT$PNQJMBUJPO1SPDFTT
    5FMOFU4FSWFS"SDIJUFDUVSF
    8IZ 8IZBOE8IZ
    "QQFOEJY-JNJUBUJPOTBTPGW
    "QQFOEJY'VFM%4-&YBNQMFT

    View full-size slide


  12. #VSOͷOFTੜ੒ೳྗ͸DDʹ׬શʹґଘ͍ͯ͠·͢ɻ
    ΑͬͯɺDDͰͰ͖Δ͜ͱ͸#VSOͰ΋Ͱ͖ΔΑ͏ʹকདྷతʹ͸ͳΔ΂͖Ͱ͢
    ʢࠓ͸΄Μͷগ͠ͷදݱ͔࣮͠૷Ͱ͖͍ͯͳ͍ͱࢥ͍·͢ʣ

    View full-size slide

  13. #VSOJOHOFT'VFM
    'VFM
    CVSOHFN
    QSFQSPDFTTPS
    NBJOD
    NVTJDT
    TPVOET
    NBJOOFT
    NVTJDEP

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

    View full-size slide

  14. 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

    View full-size slide

  15. 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

    View full-size slide

  16. 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

    View full-size slide

  17. 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

    View full-size slide

  18. %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

    View full-size slide

  19. &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”); }
    ʢ΋ͪΖΜɺѻ͑Δߏจ͸ຊ౰ʹجຊతͳ΋ͷͷΈͰɺ੍ݶ͋Γ·͘ΓͰ͕͢ʣ

    View full-size slide

  20. OFT$PNQJMBUJPO1SPDFTT
    5FMOFU4FSWFS"SDIJUFDUVSF
    8IZ 8IZBOE8IZ
    "QQFOEJY-JNJUBUJPOTBTPGW
    "QQFOEJY'VFM%4-&YBNQMFT

    View full-size slide

  21. (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

    View full-size slide

  22. #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Λಈ͔͍ͯ͠·͢ɻͭ͸ը໘ग़ྗɺ΋͏̍ͭ͸ೖྗड෇༻

    View full-size slide

  23. )PX%PFT5IJT
    -JOF0G$PEF8PSL
    self.instance_eval @opcodes[@pc]
    !PQDPEFTͷத਎͸Կʁɹ࣮ͬͯ͜͜ࡍԿΛJOTUBODF@FWBMͯ͠Δͷʁ

    View full-size slide


  24. @___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

    View full-size slide

  25. 1FSGPSNBODF
    w 5FMOFUTFSWFSJTCVJMUXJUI&WFOU.BDIJOF#VUIPX
    NVDIDBQBCMFJTJU
    w %JERVJDLQFSGPSNBODFUFTUVOEFSGPMMPXJOH
    UFTUJOHFOWJSPONFOU
    w 4FUUJOHVQBDIFBQFTU7.PO%JHJUBM0DFBO
    .# DPSF
    XJUI$FOU04SFMFBTF 'JOBM

    w .3*WFSTJPOXBTQ
    ύϑΥʔϚϯεςετ΋΍ͬͯΈ·ͨ͠

    View full-size slide

  26. 1FSGPSNBOU&OPVHI *TO`U*U
    w -(5.
    w 6QUJNFEBZT
    DPOO-"NJOdNBY BWH

    d

    d

    d

    d

    ্ʑʁ

    View full-size slide

  27. OFT$PNQJMBUJPO1SPDFTT
    5FMOFU4FSWFS"SDIJUFDUVSF
    8IZ 8IZBOE8IZ
    "QQFOEJY-JNJUBUJPOTBTPGW
    "QQFOEJY'VFM%4-&YBNQMFT

    View full-size slide

  28. l"QQMJDBUJPO
    %FWFMPQNFOUz

    View full-size slide

  29. 5FMOFU͸೥ɺOFT͸೥Ҏ্ར༻͞Εଓ͚͖ͯͨɻ͔͠΋Ͳͷ04Ͱ΋
    ಈ࡞͢ΔɻOFT30.΍UFMOFU͸ɺͳͥ͜Μͳʹ׆༻͞Ε͍ͯΔͷ͔ʁ
    w -POH-JWF
    w 5FMOFUJOJUJBMMZEFWFMPQFEJOBDDPSEJOHUP
    XJLJQFEJB*UIBTCFFOVTFECSPBEMZFWFOJO
    8IZ
    w OFT30.JOWFOUFEJOTBOETUJMMCFJOHPOFPG
    UIFGBWPSJUFUPZBNPOHHFFLTVOUJMOPX8IZ
    w 8PSLT6OJWFSTBMMZ
    w #PUIUFMOFUDMJFOUBOEOFT30.FNVMBUPSBSF
    JNQMFNFOUFEJOBMMNBKPS048IZ
    3FFWBMVBUJPOPG-PX5FDI

    View full-size slide

  30. “Why do they call it rush hour
    when nothing moves?”
    Robin Williams
    lԿ΋ಈ͍ͯͳ͍ͷʹɺͳΜͰϥογϡΞϫʔͳΜͯݺͿΜ͍ͩʁz
    ϩϏϯɾ΢ΟϦΞϜζʢΞϝϦΧͷίϝσΟΞϯɾആ༏ʣ

    View full-size slide

  31. ΤϛϡϨʔλͷ਺ͱಉ͘͡Β͍ͨ͘͞Μͷ
    ஶ࡞ݖ͕ΫϦΞͳOFT30.ϑΝΠϧ͕ྲྀ௨͍ͯ͠Ε͹خ͍͠ͷͰ͕͢
    *EFBM )BQQZ

    w OFT 㱣
    &NVMBUPS 㱣

    w 4VQQMZBOEEFNBOEBSFJOHPPECBMBODF

    View full-size slide

  32. ݱ࣮͸ͦΜͳ͜ͱ͸ͳ͍ͷͰͨ͠ɻ
    άάΔͱ͙͢ʹݟ͔ͭΔɺஶ࡞ݖͱग़ॴ͕ෆ໌ͳ30.ͷ਺ʑɻ
    #VU3FBMJUZ 4BE

    w OFT '&8
    &NVMBUPS 㱣

    w *OTUFBE 5IFSFJT."/:QJSBUFEOFT30.T

    View full-size slide

  33. ·ͣແཧ͚ͩͲΦʔϓϯιʔεͳ30.ւ଑൛30.ͳ
    ੈքʹ͢Δ͜ͱ͕5IF3JHIU8BZͩͱࢥͬͯ·͢ɻ
    $BO8F8PSLBSPVOE
    5IF4JUVBUJPO
    w 4PPO "MNPTUIPQFMFTT
    w )PXFWFS JOMPOHUFSN #VSOJTBJNJOHUP
    NBLFUIJTXPSMEGVMMPGPQFOTPVSDF
    w FH'VFM)VC
    w )PQFGVMMZ FWFOUVBMMZ
    w 0QFOTPVSDFE30.1JSBUFE30.

    View full-size slide

  34. $PODMVTJPO
    OFT$PNQJMBUJPO1SPDFTT
    *4'6/ TJODF6TJOH3VCZJT'VO

    5FMOFU4FSWFS"SDIJUFDUVSF
    $IJMEJTI#VU803,4
    8IZ 8IZBOE8IZ
    #FDBVTF-PX5FDI*T4UJMM
    (PPE"OE0QFO4PVSDJOH*U
    .BLFT6T)BQQJFS

    View full-size slide

  35. "DLOPXMFEHFNFOU
    w -PHPT GSPN5IF/PVO1SPKFDU

    w IUUQUIFOPVOQSPKFDUDPNUFSNpSF
    w IUUQUIFOPVOQSPKFDUDPNUFSNSFGSFTI
    w 1IPUPT
    w IUUQTXXXqJDLSDPNQIPUPTIJTHMBTTXPSLT

    w IUUQTXXXqJDLSDPNQIPUPTSBJOEPH

    View full-size slide

  36. OFT$PNQJMBUJPO1SPDFTT
    5FMOFU4FSWFS"SDIJUFDUVSF
    8IZ 8IZBOE8IZ
    "QQFOEJY-JNJUBUJPOTBTPGW
    "QQFOEJY'VFM%4-&YBNQMFT

    View full-size slide

  37. -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
    ੍ݶࣄ߲͹͔ΓͳͷͰɺͰ͖Δ͜ͱ͚ͩΛॻ͍ͨํ͕ૣ͍ঢ়ଶͰ͕͢ɾɾɾ
    ը໘εΫϩʔϧ͕Ͱ͖ͳ͍ɺεϓϥΠτ͕Ξχϝʔγϣϯ͠ͳ͍ɺͳͲ

    View full-size slide

  38. JOTUBOU#65QPPSTPGBS

    View full-size slide

  39. OFT$PNQJMBUJPO1SPDFTT
    5FMOFU4FSWFS"SDIJUFDUVSF
    8IZ 8IZBOE8IZ
    "QQFOEJY-JNJUBUJPOTBTPGW
    "QQFOEJY'VFM%4-&YBNQMFT

    View full-size slide

  40. '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σΟϨΫςΟϒ͕ɺ࣮ࡍͷϓϩάϥϜͷಈ࡞Λهड़͢ΔՕॴͰ͢ɻ

    View full-size slide

  41. 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ͷ৔߹͸8SJDL UFMOFUͷ৔߹͸
    &WFOU.BDIJOFͷಈ࡞Λنఆ͠·͢ɻ

    View full-size slide

  42. '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ΤϛϡϨʔλʹ౥ࡌ͞ΕͯΔ'.ԻݯΛૢ࡞ͯ͠ԻָΛ࠶ੜͰ͖·͢ɻ

    View full-size slide

  43. '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
    '.ԻݯΛར༻ͨ͠ޮՌԻͷੜ੒΋ՄೳͰ͢ɻ
    '.Իݯʹͦ͜·Ͱৄ͘͠ͳ͍ͷͰɺ֤ϝιουΛͲ͏͍͡Δͱ
    ͲΜͳԻʹͳΔͷ͔ɺ·ͩ๻ʹ΋૝૾͖ͭ·ͤΜɻ

    View full-size slide

  44. )BQQZ)PNFCSFXJOH

    View full-size slide