Not quite finished entry for http://midwestgamejam.org

left/right to steer, 'z' to draw in the sail, 'x' to let it out. Finish all the waypoints without running aground!

**General Sailing tips:**

Sailing with the wind is easy enough, just let the sail out all the way. You can get the most power this way, but you can't sail faster than the wind.

To sail perpendicular to the wind pull the sail in to about a 45 degree angle. This will give you a lot of power for acceleration. Then you can pull the sail in even more to trade power for speed. It's sort of like gears on a car, and you can sail much faster than the speed of the wind this way.

Lastly, you can't sail directly against the wind, but you can still sail well up to 45 degrees of the wind if you pull the sail in close. Then just zigzag back and forth across the wind. This is called tacking.

Changes:

1.1 - Made tacking more forgiving and added red limit lines to make the sail mechanic more intuitive.

That's quite fun, and goes to show that I have very little understanding of sailing as I spend a minute trying to go right into the wind.

I guess I should include some sailing tips along with the instructions! It's supposed to be mildly realistic, so it's probably not very intuitive if you haven't done it before. (Sorry :( )

Sailing with the wind is easy enough, just let the sail out all the way. You can get the most power this way, but you can't sail faster than the wind.

To sail perpendicular to the wind pull the sail in to about a 45 degree angle. This will give you a lot of power for acceleration. Then you can pull the sail in even more to trade power for speed. It's sort of like gears on a car, and you can sail much faster than the speed of the wind this way.

Lastly, you can't sail directly against the wind, but you can still sail well going NW or SW if you pull the sail in close. Then just zigzag back and forth. This is called tacking.

Coming into this already knowing how to sail a small boat, this game was not too difficult but it's great fun! <3 Hell it could even be a teaching tool. Of course it leaves out some crucial details, but how realistic is a Pico-8 sailing sim gonna be?

I was just thinking it would be cool to simulate the rudder, so that you see it turn either way. You could even make the boat steer faster or slower based on it's speed, but this would make the game harder. (not that I would mind xD)

EDIT: 161 seconds

love this game

Yeah. "mildly realistic" :D You do turn faster based on speed, but I don't simulate the center of drag or anything so you'd get stuck if there wasn't a minimum turning speed.

Glad you liked it! Maybe I should add rain somehow to it so I can enter into the Pico8 Jam too. heh.

My high score is 124 seconds, but I also played it far too many times. ;)

That was surprisingly fun! Probably my favorite game with "simulator" in the (working) title.

I'm thinking of showing this to my father. Let's see if real world experience helps him.

I really like the little flapping of the sail when it's not catching wind, very nice touch.

I've been dreaming about this game for year. Thanks so much for making it. You really nailed making the controls feel good. If there was more to do in the world (Trading ports? Pirate ships?) I would definitely spend many many hour playing this. Really any story or procedural content could easily turn this from an arcade style game in to a much longer experience. Not that there's anything wrong with the arcade style.

Thank!

I was thinking of making a sailing simulator too... You beat me to it! :-)

The graphics are very nice but I think the relationship between speed and scale could be improved... The boat picks up speed so quickly (including backwards) it's easy to run aground without having any time to react. Perhaps you could slow things down a bit?

Or maybe I just need to understand sailing dynamics better! ;-)

Anyway, nice work!

@fuchikoma71 - I think this is what was meant by 'arcade' game feel... but regardless I also think it would be better to scale how boat pickup speed or make much bigger map (but not on pico sadly).

@dagondev - a much bigger map would be feasible in pico with procedural methods, but yes, a "real-time" sailing simulator would probably not be fun to play on the platform.

Still, I think a compromise between arcade and simulation is often possible (this is what I was aiming for with pic-Orion :-)

I'm just a "bedroom programmer" who doesn't even have a twitter account... My cartridge is on the repository though. The interface is a bit "cold" and functional by pico-8 standards and the gameplay is a admittedly rather basic. Much more so than "Fair Winds". But it does what I wanted it to do :-)

Look it up, give it a go and let me know what you think...

Thanks... At the moment, I'm not working on the sailing simulator but on something completely different (vintage cyberpunk-inspired puzzler). But I'll keep it in mind for a future project ;-)

@dagondev I would play more sailing simulators if such things existed. This was just a 24 hour game jam game, so it doesn't really push Pico8 to it's limits or anything. ;)

Yah, the map is the biggest it can be for a static tilemap. I did consider procedural ones, but never really got around to it. I have plenty of code room for it. I think maybe 3/4 of the tokens remaining.

Sounds like a good idea for a expanding base game. Btw. Would have something against using your code? (ofc with proper credits) I would like experiment a little with this. :)

EDIT: any chance for explanation what variables mean/what is happening in crucial part of the code? It would speed up tinkering around this. Thanks!

Sure. Go for it. I think all of the carts posted to the forum have an implicit creative commons license anyway.

Anyway, it was a game jam game so I never bothered to comment the code, but here is a version with comments:

--trinagle drawing swiped from: https://www.lexaloffle.com/bbs/?tid=2734 function lerp(a,b,alpha) return a*(1.0-alpha)+b*alpha end function clip(v) return max(-1,min(128,v)) end function dtri(x1,y1,x2,y2,x3,y3,c) if(y2<y1) then if(y3<y2) then local tmp = y1 y1 = y3 y3 = tmp tmp = x1 x1 = x3 x3 = tmp else local tmp = y1 y1 = y2 y2 = tmp tmp = x1 x1 = x2 x2 = tmp end else if(y3<y1) then local tmp = y1 y1 = y3 y3 = tmp tmp = x1 x1 = x3 x3 = tmp end end y1 += 0.001 -- offset to avoid divide per 0 local miny = min(y2,y3) local maxy = max(y2,y3) local fx = x2 if(y2<y3) then fx = x3 end local d12 = (y2-y1) if(d12 != 0) d12 = 1.0/d12 local d13 = (y3-y1) if(d13 != 0) d13 = 1.0/d13 local cl_y1 = clip(y1) local cl_miny = clip(miny) local cl_maxy = clip(maxy) for y=cl_y1,cl_miny do local sx = lerp(x1,x3, (y-y1) * d13 ) local ex = lerp(x1,x2, (y-y1) * d12 ) rectfill(sx,y,ex,y,c) end local sx = lerp(x1,x3, (miny-y1) * d13 ) local ex = lerp(x1,x2, (miny-y1) * d12 ) local df = (maxy-miny) if(df != 0) df = 1.0/df for y=cl_miny,cl_maxy do local sx2 = lerp(sx,fx, (y-miny) * df ) local ex2 = lerp(ex,fx, (y-miny) * df ) rectfill(sx2,y,ex2,y,c) end end -- Create a 2x3 "translate, rotate, scale" transform matrix. function trs(tx, ty, angle, sx, sy) rx, ry = cos(angle), -sin(angle) return {rx, ry, -ry, rx, tx, ty} end -- Multiply two transform matrices. function transform_mult(t1, t2) return { t1[1]*t2[1] + t1[3]*t2[2], t1[2]*t2[1] + t1[4]*t2[2], t1[1]*t2[3] + t1[3]*t2[4], t1[2]*t2[3] + t1[4]*t2[4], t1[1]*t2[5] + t1[3]*t2[6] + t1[5], t1[2]*t2[5] + t1[4]*t2[6] + t1[6], } end -- Invert a transform matrix. function transform_inv(t) local inv_det = 1/(t[1]*t[4] - t[3]*t[2]) return { t[4]*inv_det, -t[2]*inv_det, -t[3]*inv_det, t[1]*inv_det, (t[3]*t[6] - t[5]*t[4])*inv_det, (t[5]*t[2] - t[1]*t[6])*inv_det, } end -- Transform a vector. function transform_v(m, x, y) return m[1]*x + m[3]*y, m[2]*x + m[4]*y end -- Transform a point. function transform_p(m, x, y) return m[1]*x + m[3]*y + m[5], m[2]*x + m[4]*y + m[6] end -- Number of frames since the game started. ticks = 0 -- Direction the wind blows. wind_dir = {x = 2, y = 0} -- Arrays for wind/wave effect sprites. winds = {} waves = {} wakes = {} -- x-offset of the mast on the boat. -- Used for drawing the sail. mastx = 6 -- Current waypoint. wayx, wayy = 0, 0 -- Waypoint trigger radius. wayr = 20 -- Waypoints. waypoints = { {x = 117, y = 91}, {x = 31, y = 142}, {x = 354, y = 75}, {x = 250, y = 140}, {x = 47, y = 236}, {x = 33, y = 150}, {x = 140, y = 15}, {x = 136, y = 294}, {x = 35, y = 106}, {x = 245, y = 17} } -- Drop the current waypoint and return the coordinates of the next. function pop_waypoint() local wayp = waypoints[1] del(waypoints, wayp) if wayp then return wayp.x, wayp.y else finish = ticks end end function _init() -- Add wind and wave sprites. for i = 1, 16 do add(winds, {x = rnd(128), y = rnd(128)}) add(waves, {x = rnd(128), y = rnd(128)}) end -- Setup the boat object. boat = { -- Current transform matrix. m = {1, 0, 0, 1, 0, 0}, -- Mast's transform matrix. mast_m = trs(mastx, 0, 0, 1, 1), -- Current rotation. rotation = 0.25, -- Current angle of the sail boom. boom = 0.25, -- Current angle limit of the sail boom. -- (How much the rope will let it out) limit = 0.25, vel = {x = 0, y = 0}, } -- Get the first waypoint. wayx, wayy = pop_waypoint() speed = 0 end function _update() -- debug_str = "" -- Get the input vales for the turning and sail. local tiller = (btn(0, 0) and -1 or 0) + (btn(1, 0) and 1 or 0) local sail = (btn(4, 0) and -1 or 0) + (btn(5, 0) and 1 or 0) -- Rotate the boat. boat.rotation += tiller*0.007 -- Adjust the sail's max angle. boat.limit = max(0.01, min(boat.limit + 0.006*sail, 0.25)) local m = boat.m local mast = transform_mult(m, boat.mast_m) local vx, vy = boat.vel.x, boat.vel.y -- Relative velocity of the boat to the wind. local vrx = wind_dir.x - vx local vry = wind_dir.y - vy -- The direction of the force applied to the sail by the wind. local fn = mast[3]*vrx + mast[4]*vry -- The value of the force applied to the sail by the wind. local fx, fy = mast[3]*fn, mast[4]*fn -- Slow down the boat slightly, and push it along using the force. vx = 0.99*vx + 0.1*fx vy = 0.99*vy + 0.1*fy -- Rapidly slow the boat down in the lateral direction. local keel_drag = 0.2*(m[3]*vx + m[4]*vy) vx -= keel_drag*m[3] vy -= keel_drag*m[4] -- Update the boat's properties boat.vel.x = vx boat.vel.y = vy speed = sqrt(vx*vx + vy*vy) local x = boat.m[5] + vx local y = boat.m[6] + vy boat.m = trs(x, y, boat.rotation, 1, 1) -- Torque applied to the boom by the wind. local btorque = wind_dir.x*mast[3] + wind_dir.y*mast[4] -- Rotate the boom based on the torque applied to it. boat.boom = max(-boat.limit, min(boat.boom - 0.1*btorque, boat.limit)) boat.mast_m = trs(mastx, 0, boat.boom, 1, 1) -- Add sprites for the boat's wake if ticks%4 == 0 then local wx, wy = transform_p(boat.m, -10, 0) local wdx, wdy = transform_v(boat.m, 0, 1) add(wakes, {x = wx, y = wy, dx = wdx, dy = wdy}) if #wakes > 20 then del(wakes, wakes[1]) end end -- Check if the boat is inside the waypoint. if wayx then local dx, dy = (x - wayx)/wayr, (y - wayy)/wayr if dx*dx + dy*dy < 1 then wayx, wayy = pop_waypoint() end end ticks += 1 end -- Drawing code. Mostly self-explanatory? black = 0 dgrey = 5 lgrey = 6 white = 7 red = 8 brown = 4 blue = 12 peach = 15 function draw_wind(tx, ty) local count = #winds local gust = 0.3 for i = 1, #winds do local phase = 0.01*ticks + i/count local dx = wind_dir.x + gust*sin(phase) local dy = wind_dir.y + gust*sin(0.6*phase + 0.5) local wind = winds[i] wind.x = (wind.x + dx)%128 wind.y = (wind.y + dy)%128 pset((wind.x + tx)%128, (wind.y + ty)%128, lgrey) end end function draw_waves(tx, ty) local count = #waves local duration = count*3 for i = 1, count do local phase = (ticks/2 + i)/count%1 local wave = waves[i] spr(flr(6*phase), (wave.x + tx)%128, (wave.y + ty)%128) if phase == 0 then wave.x, wave.y = rnd(128), rnd(128) end end end function draw_boat(view) local m = transform_mult(view, boat.m) local x1, y1 = transform_p(m, -10, -4) local x2, y2 = transform_p(m, 8, -4) local x3, y3 = transform_p(m, 8, 4) local x4, y4 = transform_p(m, -10, 4) local x5, y5 = transform_p(m, 12, 0) dtri(x1, y1, x2, y2, x3, y3, brown) dtri(x1, y1, x3, y3, x4, y4, brown) dtri(x2, y2, x3, y3, x5, y5, brown) local mast = transform_mult(m, boat.mast_m) local sdir = wind_dir.x*mast[3] + wind_dir.y*mast[4] local mx, my = transform_p(mast, 0, 0) local sx1, sy1 = transform_p(mast, -15, 0) local sx2, sy2 = transform_p(mast, -13, 5*max(-1, min(3*sdir, 1))) dtri(mx, my, sx1, sy1, sx2, sy2, peach) local mx2, my2 = transform_p(mast, -16, 0) line(mx, my, mx2, my2, dgrey) end function draw_wakes(tx, ty) local count = #wakes for i = 1, count do local wake = wakes[i] local x, y = wake.x + tx, wake.y + ty local t = 3 + 0.5*(count - i) local dx, dy = wake.dx*t, wake.dy*t pset(x, y, white) pset(x + dx, y + dy, white) pset(x - dx, y - dy, white) end end function draw_waypoint(tx, ty) if not wayx then return end local x, y = wayx + tx, wayy + ty local sx, sy = x - 64, y - 64 local div = max(abs(sx), abs(sy)) if div > 64 then circ(64/div*sx + 64, 64/div*sy + 64, 2, red) end circ(x, y, wayr + 5*sin(ticks/60), red) end function _draw() cls() rectfill(0, 0, 128, 128, blue) local m = boat.m local tx, ty = 64 - m[5], 64 - m[6] local view = {1, 0, 0, 1, tx, ty} draw_wakes(tx, ty) draw_waves(tx, ty) draw_waypoint(tx, ty) draw_boat(view) draw_wind(tx, ty) color(0) print("speed: "..flr(30*speed).." knots") if(finish) then print("Finished: "..(finish/30).." s") else print("time: "..flr(ticks/30).." s") end if debug_str then color(0) print(debug_str) end end |

This is lovely! I would love to try a version with more things to do.

[Please log in to post a comment]