Log In  


Cart #16969 | 2015-11-24 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
71

Cart #16909 | 2015-11-23 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
71

Cart #16877 | 2015-11-22 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
71

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.

71


2

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 can't see how to win... The wind never changes, and you don't keep your momentum...?

Aha! I finally got the Z/X buttons to do something! ... And I was going in the wrong direction! Okay.


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. ;)


1

congrats on shipping this. REALLY great work.


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.


This is awesome! I never thought about how sailing works and it was a blast figuring it out on this tiny simulation.


The landscapes are adorable.


So close to breaking 100 seconds. Bah!


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!


Nice work! This is very pretty and really challenging, even with some sailing experience. Glad you don't have to make decisions that quickly in a real boat!


Thanks. :)


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 :-)


Yeah, something between sounds cool. I am very interested in your project then! Do you have twitter or other way to follow your development?


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


I see only picorion though, I am interested in your sailing simulator which I assumed you will start working on. But I will keep tab on your profile then. :p


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.


I love this little game! I stumbled on it years back, and I just remembered it existed because I've been thinking about learning to sail. This game might have even inspired it ;)

My high score was 96.2667!



[Please log in to post a comment]