From c5e6885ee831dc054cb2133e38feaaaba01b8d9d Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Mon, 28 Apr 2014 00:36:56 +0200 Subject: [PATCH] mouse click, physics integrator reading input --- ld48-29.go | 142 +++++++++++++++++++++++++++++++++++++++++------- log.mdown | 30 ++++++++++ spritesheet.png | Bin 8829 -> 8842 bytes 3 files changed, 152 insertions(+), 20 deletions(-) diff --git a/ld48-29.go b/ld48-29.go index a2c6b59..936ec42 100644 --- a/ld48-29.go +++ b/ld48-29.go @@ -23,6 +23,7 @@ const ( INPUT_DOWN = 1 INPUT_LEFT = 2 INPUT_RIGHT = 3 + INPUT_JUMP = 4 ) // iterate faster @@ -50,25 +51,34 @@ func onError(err glfw.ErrorCode, desc string) { } func onKey(input chan int, window *glfw.Window, k glfw.Key, s int, action glfw.Action, mods glfw.ModifierKey) { - if action != glfw.Press{ + if action != glfw.Press && action != glfw.Release { return } + release := 0 + if action == glfw.Release { + release = 8 + } + switch glfw.Key(k) { case glfw.KeyR: - if mods & glfw.ModSuper != 0 { + if (mods & glfw.ModSuper != 0) && action == glfw.Press { reexec() } case glfw.KeyUp: - input <- INPUT_UP + input <- INPUT_UP + release case glfw.KeyDown: - input <- INPUT_DOWN + input <- INPUT_DOWN + release case glfw.KeyLeft: - input <- INPUT_LEFT + input <- INPUT_LEFT + release case glfw.KeyRight: - input <- INPUT_RIGHT + input <- INPUT_RIGHT + release + case glfw.KeySpace: + input <- INPUT_JUMP + release case glfw.KeyEscape: - window.SetShouldClose(true) + if action == glfw.Press { + window.SetShouldClose(true) + } default: return } @@ -219,29 +229,108 @@ func drawWaterTile(x int, y int, t float64) { func main() { done := make(chan int) input := make(chan int, 64) + tick := make(chan int) - go renderer(done, input) - go func() { - for { - in := <- input - log.Printf("input %d", in) - } - }() - + go renderer(done, input, tick) + go inputMapper(input) + go stepper(tick) <-done } +// world + +var inputState [5]bool + +func inputMapper(input chan int) { + inputState = [5]bool{false,false,false,false,false} + for { + in := <- input + pressed := in < 8 + if !pressed { + in = in - 8 + } + inputState[in] = pressed + } +} + +func stepper(tick chan int) { + pcx := 0.0 + pcy := 0.0 + ppx := pcx + ppy := pcy + pm := 10.0 + + ct := float64(time.Now().UnixNano()) / math.Pow(10, 9) + pt := ct + dt := 0.0 + + for { + <-tick + + pt, ct = ct, float64(time.Now().UnixNano()) / math.Pow(10, 9) + dt = ct - pt + + pvx, pvy := (pcx - ppx) / dt, (pcy - ppy) / dt + ppx, ppy = pcx, pcy + + fx := 0.0 + fy := 0.0 + + // gravity + fy += -10.0 * pm + + // movement + if inputState[INPUT_LEFT] { + fx += -20 + } + if inputState[INPUT_RIGHT] { + fx += 20 + } + if inputState[INPUT_JUMP] { + fy += 20 * pm + } + + // friction + fx += -57 * pvx + fy += -57 * pvy + + // integrator + pax := fx / pm + pay := fy / pm + pvx = pax * dt + pvx + pvy = pay * dt + pvy + dpcx := pvx * dt + dpcy := pvy * dt + if math.Abs(dpcx) < 0.001 { + dpcx = 0 + } + if math.Abs(dpcy) < 0.001 { + dpcy = 0 + } + pcx += dpcx + pcy += dpcy + + if pcy < 0 { + pcy = 0 + } + + log.Printf("%.03f %.03f %.03f %.03f", pvx, pvy, pcx, pcy) + } + +} + // renderer var mouseX float64 var mouseY float64 var mouseVisible bool +var mousePressed bool var ww int var wh int -func renderer(done chan int, input chan int) { +func renderer(done chan int, input chan int, tick chan int) { runtime.LockOSThread() glfw.SetErrorCallback(onError) @@ -253,8 +342,8 @@ func renderer(done chan int, input chan int) { glfw.WindowHint(glfw.Resizable, 0) - ww = 640*2 - wh = 480*2 + ww = 640 + wh = 480 window, err := glfw.CreateWindow(ww, wh, "LD48-29", nil, nil) if err != nil { @@ -274,8 +363,15 @@ func renderer(done chan int, input chan int) { mouseVisible = mouseX < vx2 && mouseX >= 0 && mouseY < vy2 && mouseY >= 0 } + onMouseButtonClosure := func (window *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + if button == 0 { + mousePressed = action == 1 + } + } + window.SetKeyCallback(onKeyClosure) window.SetCursorPositionCallback(onMouseClosure) + window.SetMouseButtonCallback(onMouseButtonClosure) window.MakeContextCurrent() @@ -286,6 +382,7 @@ func renderer(done chan int, input chan int) { render(textures, lists) window.SwapBuffers() glfw.PollEvents() + tick <- 1 } done <- 1 @@ -318,6 +415,7 @@ func setup() (textures map[string]gl.Texture, lists map[string]uint) { lists["test"] = makeSprite(0, 0, 2, 2) lists["cursor"] = makeSprite(3, 2, 1, 1) + lists["cursorclick"] = makeSprite(3, 3, 1, 1) lists["cloud1"] = makeSprite(0, 2, 3, 2) lists["cloud2"] = makeSprite(0, 4, 2, 2) lists["cloud3"] = makeSprite(2, 4, 2, 2) @@ -412,13 +510,17 @@ func render(textures map[string]gl.Texture, lists map[string]uint) { if j == 2 { wt = t } - drawWaterTile(i, j, wt) + drawWaterTile(i, j, 4*wt) } } // mouse pointer if mouseVisible { - drawSprite(textures["sprites"], mouseX, mouseY, 0, 1.0, lists["cursor"]) + cursor := lists["cursor"] + if mousePressed { + cursor = lists["cursorclick"] + } + drawSprite(textures["sprites"], mouseX, mouseY, 0, 1.0, cursor) } } diff --git a/log.mdown b/log.mdown index c0768db..152faa6 100644 --- a/log.mdown +++ b/log.mdown @@ -151,3 +151,33 @@ All time is CEST - 19:25 Window size independence. More clouds, better tiles. Second platform. + +- 21:55 + + Train departure. + +- 22:35 + + Input state management in additional goroutine done. + +- 23:00 + + Stuck in world update iteration, missing how to have its goroutine sleep. + +- 23:09 + + Worked around by having the physics goroutine tied to video ticks via a + channel. + +- 23:39 + + Broken physics integrator does stupid things. To the moon in seconds, physics + is fun! + +- 23:57 + + Problem solved. Was accumulating twice. Now to find nice constants for movement. + +- 00:07 + + Train arrives. Integrator stubbornly refuses to move player left more than once. diff --git a/spritesheet.png b/spritesheet.png index b9d162ec529703848e7855a8e9fe2ab159f57dfa..0b2b6827fa2d2f7b22200073452d66e453aa3269 100644 GIT binary patch delta 5248 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 delta 5235 zcmeBj{p+&9imzVJ)5S5Qg7NL#jqBcQ6Kwk~Azs;}aLkn<+$~GF;o*|A!aH^q%r^6o zJ)r-fWF^mj2dT$)E)6$ala_eSz2y7gfc^u9k0;N)J15uW?KQ>i%9}O&So>51Wr}`G z{M>y>^y%{R`+oiSd4Qiu;D%~r_@!6=`+wYibnO4f^8Lr;>U!q4vefT+{CsX}T{0UZ zhYbTG%L7KMiE0iBnv9znBJ$*?Sh_`P1Ru|0eExEW&bzMUB;g>A>sfk=w&(uM}sB7P9zj z{9u}>-b(Lv|KgGs81t|HHT}aOwtA+uENbZtYgxaZdM^3rk{V~t&tSzDSN%@4Hn2B( zGG1t0e_MC%6|2~=wzcyN6#vaxWGC$^(YX7PJ;8>f;-ME$0=N!Ye zbrU`*Ff{&FOb}zJoPP7O``-`ge{|Io#2TK4CUY`!R0vyeANYK5o63#6{KpBO|NU4| zKmXfos{`lGhwR<>z3lFP|DWF(qVBzwn_ZS{|2NO#&#UdaJ@a#qpZ8>lT5w$@ip}`j z<+*J8jTn#h=l}V@v%p5rv-N-;hXTXy7im(QjYgngTlPP7`m?1$|3BUCuUv9^=R^6~ z`y&}n9c~KYa8O`kWKnR^f2ho2CFsCl)wnc*quxcKfx+d4YE6rSIunP3`UVm1Mh+H% z29CWg{Q@ss7#Lr$OiNM}Xk=g#SUCNIAk$oq8LSC8iQek-*^dU**Cw822wBejou(w=J+85%uU z{Nh=+>D0eSu-*Q?D(}bP`$GF|m^)Oi|KMtz%BjGh&Ailt!$m1UjN$Z`WZr-)^3}Gx zN_j5)-}cX(yJ0Ky#&!mlKO71Sb#GQmGQDFFVEC?gfRW>epaa978^OjbcQ_Ro-o-XB z3w%&+VED0dc^bzKVF!jgyBXLO9=J0wen|E|D^Q@GAjVLCt@(Hyf1YgPm-;s#lS>&A z`9bDba5vo+q@X=hz2(BQzr#Np5qcgUTiM5%$HgvooOK!*bZW5)&W9~_N-ECLOF z37TR|f*cAAf>oUIiWfQ=m@aVY9CcDaic=Sy6!yB*LRA5mvsf1i27R+elI2L++V@JDM>9ob*+ti4`* zSpMI%A5SG6RCXQ<=UC&zz!)K^anw;k2%Ny=vl|xuyYc$s+T@0R+rRFQWLP8HVatLX zuni15${G0_4)ilHJvcsZmf{0H2F8N-Ofn4(@{BA8`fW{}56op?dT@``qMm`#o{6JD zzB;R|!J2V1!v;B0^ z+syr~4QoNU25-!4!5cI6xTA(HF;kBeH4U}4yK`N)osGIye7kPTeYXqe&flsJ`+axY z|7X<=8=W<+h4A|91C0>3<)#AJIS0pmfr7pHo6A6UTv54w`&S6FFwE z9x(bF!7TFicwB#;Ec2KBYyY2RN_gw=MiC{nSq|*wt}kFW?2hq0WAqoG>- z0fC$~gSFx7gN@glqumw$%I^l5EbDN?8Dz{kri5tTy~nC*q#6IT&u=}@cl`WWPiNzqRe9ko~#kjo_nhPelVbXb%G92k_o z2-!I;5N2dqARN)viZ^G~`>~v1GT63Z>E!dxNh|m7+mObv>N4Y>~Z4$BK&HwrWy zU}HJQwBhmB3+l(V+&8=OKz!Q$v)>tVbPq}~33RwTm}-$7dEsDH-uwpf!8utFZa(AqlUjDxUGWj*bAvq?256T5%4bPi%UAN!8=XT)P--kDTyJkZnGp^Vx}Hnp+`*2B*dT3i=LRfdD_=I+vMMF7u@oB^SAj|>$bnG z-!!p1|L@Ik`P)|Zc1%$ll9|?*-T9V%S(iI1;{3ba^JM>-{j*`N-*Jk;THuB}BTGVk ztzWldfocOoOrXx~jd$#HRu^Wt{@r-}F@JUOzlmG_fB(RvP=8|kZ}$10+a<;Ny%wl2 zWFFYU=AAWpbC$01_UN}ie*S!O@ABl`+N-yAHYsdiYn;R&Q)k0=#@e{qwOOZhRq%n{ z&a(@uY+pQS7dtP;l*lQSO+< zvNH+nP@S-kLnXZSuXP2>)GIUp-`!O+TPghapZ8|r^F^*5J|4%PFDrWScfA3#$MUnq ztO_UR-?!Uxj^k2|d-$iMHLED95ZJ<=I|?!0+CF8txmzGrMxZ<=}L{FbIk_TYxkT4-vt`}1nMZ*>2B z6N~=BFGA(h6FeV3PqRNiU(Wm4nw~A|W^WQx;rWxhaXqI4_m`-jMl&|f=d1cLm9wIM z{z2o0J4a0xs8(EhY_nb_ow5GZrYBn;EqNpETIIQi%X8(v={av48JL1u+*lW+&0(Kc z&(ZuR_L8Z?eAORo_lJ~hs@f`-EqcQ_VLJ;O(*)KVyi>mHVOzFj=}U9BV=ePirDLsU zMlbt+C10K4|I_X9PWGY0F_(fqi$Z|< z1g-;`;bJ#TPft6Lb23R*}Ev?|OKn%+Ch%T%Dj z!S8_j2tqbB~DRW=2n_-QI$$U?qJnyhI?TZvPNHeWvGT_*E@Sxz; z2$g5yKQ8qsoDW_!|JK$g`coaHSQHFYE__j%z%u_|=gyk<_J1cg==}AJ+v?36ap~*y zdDm+#m;c^%_0LbUUsZ4F_t+NqXWHLqo?)GK>Cal@hSj|+DZeHkIDh%lXYcQySJ(7g zP5ad*%Ocl$Ae7-#!{@vNsSj4WSNu|$Wl|;|WV&aLgw@ zFUUX6uUNYN=!H3w4%zo?=F7;}o5p9aGhO=pzHmn6_L`hqy??*#e)(bb{cSn*wsl#K zSA7{~E&4P=Q*^;SrVC7apKS*-AyUg)}Pr|}7n#+fW@nCc6*H>w`g*VjJzepCJSedpd7Z*opBW%A8u zS{HWPjaBuazTbE1ue)-*mpX z!CE4BM?pEun)3|r);Yeq;L75sPDb29agOqlrVHFq{o#BATipWPW|?Vi;mP+`Nw(eP#En&;d9y!kfm z*7^U}5;YIL+dp;w(dt|I3;s`8-@g6pk($@_fB)Y8=~kZdu{(Zmx%<5bTrAH7E&ggZ zq&jw%+J4Tj|M|I6F23mh7yJE|HUBs7*Vz<%`^4n=Q|-Ui|Nb}caO@o>4}z(e=plsw_@+FQrm04`fqQs z^L)nMn89*}DPiV6E*;*idfp_@$peUdX+mIiSh!pUR7`%Nti5PrhyWE&tvgNgHe1vs`+u z&r6>jf90$jdwz54fxW^8=NYbLB}g^M*M7OU;Kr9Zb55M+Hh-#30omK< zeUh8ou$y5?1AAks&BBA~FJD^vhTr}YvupkH)Y-R}JU{kL{q*nS@yE|tvN!HlD==p= z5L=M^+^gjCpGtw}$CnzZ=;|+@)?dQmqFNwc&oqH8CHulsGbVfcd9NmXiD&fs{y!^T z`}?eOx($nxq$hwZV$^pmPz{W z74A8T0{sUn*$gVw^Bk3XzDZr!wYGA?yy>-nC;zF^^Ibhv`Q^(m_VpYy*!E0Y{&ab2 z+~uCP=5@UZy67ER-aH~bD-|m-Cw51@8A15 zpJhSng{KR?L|7e8`oEn2e}R@4v)AdXYuT2u&CmNE73Cqhi#cw+M_OngW^yd?|OpEXTw*AWL$`$p$W;SkmwXyfyeyw^2p+A`yo*&OX zI3?lA18-(yU+>FH@BN9mWKzE?{*?Ojr^~t;Wk8PIw>kE%-R@Xg!DdVib)%c3X zL3%;>KV4UcSLc{_nBIu`Vt@G{>9H11lIZKIHQ*5jnE7LHB8J{P9oI&#jEz zm3wyjtquH*9xQIG3bkwn{Jmbx`&2ECe_DR|-+?&aVqL7Gb(56pK6WjL~Rp5uC7 z{d(4UY%`vn+^#BpcJa34$|rN4dA~iDV=_N)edSNN2Bk#l2?tg%RBUr+{vws(o^G=C z+4OnW*FN!nn_Xrk?2*r$^(}MPo9$C#S5GeCxWml&nI-Jr(sv(EH+%Pe-V(HT)_T?T zx0WAVpJ`gH8~^!h*8XYsTbKT`dz|03dtU#!`?YKn;<7_UUtKBd^o>e8xlioF%GiDS zPe0f;%;7pdkz)?4!!_mk|MV-lW6f`QfABQe%95zre`^}k?rV#_FeYgFdq+Mx&ELNM v{OgahKj-dKclf(BFUQ-s@BWED|M|_DeN(q3FWbVvz`)??>gTe~DWM4fDwn%O