From 7e7da420b2eaf750356632085f3ab6b50a5e8ff2 Mon Sep 17 00:00:00 2001 From: fnicon Date: Wed, 18 Mar 2026 00:37:38 +0900 Subject: [PATCH] add town square --- game/lib/choro/projection.lua | 77 ++++ game/lib/json.lua | 388 ++++++++++++++++++ game/main.lua | 1 + game/src/world/1_intro/init.lua | 4 +- game/src/world/2_town_square/init.lua | 31 ++ .../world/2_town_square/pseudo3d/collide.lua | 12 + .../src/world/2_town_square/pseudo3d/ease.lua | 55 +++ .../2_town_square/pseudo3d/roadsegment.lua | 338 +++++++++++++++ game/world_map.lua | 1 + 9 files changed, 905 insertions(+), 2 deletions(-) create mode 100644 game/lib/choro/projection.lua create mode 100644 game/lib/json.lua create mode 100644 game/src/world/2_town_square/init.lua create mode 100644 game/src/world/2_town_square/pseudo3d/collide.lua create mode 100644 game/src/world/2_town_square/pseudo3d/ease.lua create mode 100644 game/src/world/2_town_square/pseudo3d/roadsegment.lua diff --git a/game/lib/choro/projection.lua b/game/lib/choro/projection.lua new file mode 100644 index 0000000..27cfea6 --- /dev/null +++ b/game/lib/choro/projection.lua @@ -0,0 +1,77 @@ +local vm = require("lib.vornmath") + +-- https://jakesgordon.com/writing/javascript-racer-v1-straight/ +---@class lib.choro.projection +local projection = {} + +--- World {x, y, z} - Camera {x, y, z} (Relative to Camera Pos) +---@param world table {x, y, z} +---@param cam table {x, y, z} +---@return table {x, y, z} +function projection.translateToRelativeCamera(world, cam) + return vm.vec3(world) - vm.vec3(cam) +end + +--- project relative camera {x, y, z} to z render distance on screen {x, y} +---@param camPos table {x, y, z} +---@param distance number distance to render point +---@return table {x, y} +function projection.projectRelativeCamera(camPos, distance) + return vm.vec2({ + camPos[1] * distance / camPos[3], + camPos[2] * distance / camPos[3] + }) +end + +--- scale things from camera projection {x, y} +---@param camProj table camera projection {x, y, z} +---@param screenScale number projection scale / distance +---@param resolution table {screen width, screen height} +---@return table {x, y} +function projection.scalePosToRelativeProjectCamera(camProj, screenScale, resolution) + local w2 = resolution[1]/2 + local h2 = resolution[2]/2 + return vm.vec2({ + vm.round(w2 + ( screenScale * camProj[1] * w2)), + vm.round(h2 - ( screenScale * camProj[2] * h2)), + }) +end + +--- scale things from camera projection {w, h} +---@param size table world size {w, h} +---@param screenScale number projection scale / distance +---@param resolution table {screen width, screen height} +---@return table {w, h} +function projection.scaleSizeToRelativeProjectCamera(size, screenScale, resolution) + local w2 = resolution[1]/2 + local h2 = resolution[2]/2 + return vm.vec2({ + vm.round( screenScale * size[1] * w2), + vm.round( screenScale * size[2] * h2) + }) +end + +--- calculate distance from field of view +---@param fieldOfView number field of view +---@return number distance distance from camera to projection plane +function projection.distanceCamToProjection(fieldOfView) + local d = 1 / math.tan((fieldOfView/2) * math.pi/180); + return d +end + +--- apply world to camera projection +---@param world table world position {x, y, z} +---@param cameraPos table camera position {x, y, z} +---@param cameraDepth number distance from camera to projection plane +---@param resolution table render resolution {width, height} +---@param size table object {width, height} +---@return table,table {x, y, z}, {w, h} +function projection.projectWorldToCam(world, cameraPos, cameraDepth, resolution, size) + local relativetocamera = projection.translateToRelativeCamera(world, cameraPos) + local scale = cameraDepth / relativetocamera[3]; + return + projection.scalePosToRelativeProjectCamera(relativetocamera, scale, resolution), + projection.scaleSizeToRelativeProjectCamera(size, scale, resolution) +end + +return projection diff --git a/game/lib/json.lua b/game/lib/json.lua new file mode 100644 index 0000000..711ef78 --- /dev/null +++ b/game/lib/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/game/main.lua b/game/main.lua index e7cffcc..ecf44c0 100644 --- a/game/main.lua +++ b/game/main.lua @@ -3,6 +3,7 @@ local wm = require("world_map") world = { ["main_menu"] = require("src.world.main_menu")(), ["1_intro"] = require("src.world.1_intro")(), + ["2_town_square"] = require("src.world.2_town_square")(), ["race"] = require("src.world.race")(), ["train"] = require("src.world.train")(), }; diff --git a/game/src/world/1_intro/init.lua b/game/src/world/1_intro/init.lua index b6eed01..3dbd806 100644 --- a/game/src/world/1_intro/init.lua +++ b/game/src/world/1_intro/init.lua @@ -13,7 +13,7 @@ local wm = require("world_map") local wrapper = world:extend() local function button_func() - load_world(wm["race"]) + load_world(wm["2_town_square"]) end function wrapper:new() @@ -59,7 +59,7 @@ function wrapper:update(dt) local c_v = v[c_video.dict.video] if c_v ~= nil then if (not c_v.data.video:isPlaying()) then - load_world(wm["race"]) + load_world(wm["2_town_square"]) end end end diff --git a/game/src/world/2_town_square/init.lua b/game/src/world/2_town_square/init.lua new file mode 100644 index 0000000..8e67a19 --- /dev/null +++ b/game/src/world/2_town_square/init.lua @@ -0,0 +1,31 @@ +local reap = require("lib.reap") + +local BASE = reap.base_path(...) + +local world = require("wrapper.Concord.world") + +local debug_entity = require("src.world.common.template.debug_entity") + +local wm = require("world_map") + +local wrapper = world:extend() + +function wrapper:new() + wrapper.super.new(self, BASE, ".2_town_square") +end + +function wrapper:load(_args) + wrapper.super.load(self, { + "src/world/common/system/" + }, { + { + assemblage = debug_entity.assembleDebug, + data = { + position = {0, 0}, + label = "2_town_square" + } + }, + }) +end + +return wrapper diff --git a/game/src/world/2_town_square/pseudo3d/collide.lua b/game/src/world/2_town_square/pseudo3d/collide.lua new file mode 100644 index 0000000..adf8b3d --- /dev/null +++ b/game/src/world/2_town_square/pseudo3d/collide.lua @@ -0,0 +1,12 @@ +local utils = {} + +function utils.overlap(x1, w1, x2, w2, percent) + local half = (percent or 1)/2; + local min1 = x1 - (w1*half); + local max1 = x1 + (w1*half); + local min2 = x2 - (w2*half); + local max2 = x2 + (w2*half); + return not ((max1 < min2) or (min1 > max2)); +end + +return utils diff --git a/game/src/world/2_town_square/pseudo3d/ease.lua b/game/src/world/2_town_square/pseudo3d/ease.lua new file mode 100644 index 0000000..60c2943 --- /dev/null +++ b/game/src/world/2_town_square/pseudo3d/ease.lua @@ -0,0 +1,55 @@ +local vm = require("lib.vornmath") + +local utils = {} + +function utils.easeIn(a,b,percent) + return a + (b-a)*math.pow(percent,2); +end + +function utils.easeOut(a,b,percent) + return a + (b-a)*(1-math.pow(1-percent,2)); +end + +function utils.easeInOut(a,b,percent) + return a + (b-a)*((-math.cos(percent*math.pi)/2) + 0.5); +end + +function utils.percentRemaining(n, total) + return (n%total)/total; +end + +function utils.interpolate(a, b, percent) + return a + (b-a)*percent +end + +function utils.randomInt(min, max) + return vm.round(utils.interpolate(min, max, math.random())); +end + +function utils.randomChoice(options) + return options[utils.randomInt(1, #options)] +end + +function utils.limit(value, min, max) + return math.max(min, math.min(value, max)) +end + +function utils.accelerate(v, accel, dt) + return v + (accel * dt) +end + +function utils.exponentialFog(distance, density) + return 1 / (math.exp(distance * distance * density)) +end + +function utils.increase(start, increment, max, is_loop) -- with looping + local result = start + increment + if (result > max) and is_loop then + result = 1 + elseif result <= 0 and is_loop then + result = max - 1 + end + return result +end + +return utils diff --git a/game/src/world/2_town_square/pseudo3d/roadsegment.lua b/game/src/world/2_town_square/pseudo3d/roadsegment.lua new file mode 100644 index 0000000..2f8a367 --- /dev/null +++ b/game/src/world/2_town_square/pseudo3d/roadsegment.lua @@ -0,0 +1,338 @@ +local reap = require("lib.reap") + +local BASE = reap.base_path(...) + +local easeFN = require(BASE .. ".ease") +local json = require("lib.json") +local QuadCache = require("wrapper.lappy.new.quad").obj +local ImageCache = require("wrapper.lappy.new.image").obj + +local utils = {} + +local function loadJSON(path) + local data = json.decode(love.filesystem.read(path)) + return data +end + +local ROAD = { + LENGTH= { NONE= 0, SHORT= 25, MEDIUM= 50, LONG= 100 }, -- num segments + HILL= { NONE= 0, LOW= 20, MEDIUM= 40, HIGH= 60 }, + CURVE= { NONE= 0, EASY= 2, MEDIUM= 4, HARD= 6 } +}; + +-- function utils.getRumbleColor(index, rumbleLength) +-- local colorDark = hex2color("#1a1d33") +-- local colorBright = hex2color("#c8d0fa") +-- if (math.floor(index/rumbleLength)%2 == 0) then +-- return colorBright +-- else +-- return colorDark +-- end +-- end + +-- function utils.getFinishStartColor() +-- local colorDark = hex2color("#CCFFCC") +-- local colorBright = hex2color("#FFCCFF") + +-- return colorDark, colorBright +-- end + +local function lastY(segments) + if (#segments == 0) then + return 0 + else + return segments[#segments].p2.world.y + end +end + +local function lastWidth(segments, fallback) + if (#segments == 0) then + return fallback + else + return segments[#segments].p2.world.w + end +end + +function utils.findSegment(zPosition, segments, segmentLength) + local index = (math.floor(zPosition/segmentLength) % #segments) + 1 + if (index >= #segments) then + index = #segments + end + return segments[index]; +end + +--- add quad and image +---@param path string +---@param slice "horizontal" | "vertical" +---@param w1 number +---@param w2 number +function utils.addQuads(path, slice, w1, w2) + local image = ImageCache:load_to(path, path) + + local sw, sh = image:getDimensions() + + local quads = {} + if (slice == "vertical") then + for i = 0, sh - 1 do + local percent = easeFN.percentRemaining(i, sh - 1) + local destW = easeFN.interpolate(w1, w2, percent) + table.insert(quads, + QuadCache:load_to(string.format("%s %s %s %s %s %s %s", + path, 0, i, sw, 1, sw, sh), + path, 0, i, sw, 1, sw, sh) + ) + end + elseif (slice == "horizontal") then + for i = 0, sw - 1 do + local percent = easeFN.percentRemaining(i, sw - 1) + local destH = easeFN.interpolate(w1, w2, percent) + table.insert(quads, + QuadCache:load_to( + string.format("%s %s %s %s %s %s %s", + path, i, 0, 1, sh, sw, sh), + path, i, 0, 1, sh, sw, sh) + ) + end + end + return quads, image +end + +--- add new segment to segments +---@param segments any +---@param segmentLength number z coord length +---@param rumbleLength number for debug purpose only +---@param curve number curve degree +---@param y number height +---@param width number +---@return table segment it's basically part that are drawn +function utils.addSegment(segments, segmentLength, rumbleLength, curve, y, width) + local index = #segments + + local w1 = lastWidth(segments, width) + local w2 = width + + local y1 = lastY(segments) + local y2 = y + + local fQuads, fImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2) + local cQuads, cImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2) + + local lQuads, lImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2) + local rQuads, rImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2) + + return { + --- index for seeking + index= index + 1, + --- part 1 start + p1 = { + world = + { + w = w1, + y = y1, + z = index * segmentLength, + }, + camera = {}, + screen = {} + }, + --- part 2 end + p2 = { + world = { + w = w2, + y = y2, + z = (index+1)* segmentLength, + }, + camera = {}, + screen = {} + }, + --- curve to follow along + curve = curve, + sprites= {}, + ---p1 -> p2 quads + texture = { + floor = { + image = fImage, + quads = fQuads + }, + ceil = { + image = cImage, + quads = cQuads + }, + wallL = { + image = lImage, + quads = lQuads + }, + wallR = { + image = rImage, + quads = rQuads + }, + } + } +end + +function utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, y, roadWidth) + local startY = lastY(segments); + local endY = startY + ((y or 0) * segmentLength); + + local startWidth = lastWidth(segments, (roadWidth or 0)); + local endWidth = (roadWidth or 0) + + local total = enter + hold + leave; + for n = 1, enter do + table.insert( + segments, + utils.addSegment( + segments, + segmentLength, + rumbleLength, + easeFN.easeIn(0, curve, n/enter), + easeFN.easeInOut(startY, endY, n/total), + easeFN.easeInOut(startWidth, endWidth, n/total) + ) + ) + end + for n = 1, hold do + table.insert( + segments, + utils.addSegment( + segments, + segmentLength, + rumbleLength, + curve, + easeFN.easeInOut(startY, endY, (enter + n)/total), + easeFN.easeInOut(startWidth, endWidth, (enter + n)/total) + ) + ) + end + for n = 1, leave do + table.insert( + segments, + utils.addSegment( + segments, + segmentLength, + rumbleLength, + easeFN.easeInOut(curve, 0, n/leave), + easeFN.easeInOut(startY, endY, (enter+hold+n)/total), + easeFN.easeInOut(startWidth, endWidth, (enter + hold + n)/total) + ) + ) + end + return segments +end + +function utils.addStraight(segments, segmentLength, rumbleLength, num) + num = num or ROAD.LENGTH.MEDIUM; + return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 2000); +end + +function utils.addHill(segments, segmentLength, rumbleLength, num, height) + num = num or ROAD.LENGTH.MEDIUM; + height = height or ROAD.HILL.MEDIUM; + return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000); +end + +function utils.addCurve(segments, segmentLength, rumbleLength, num, curve, height) + num = num or ROAD.LENGTH.MEDIUM; + curve = curve or ROAD.CURVE.MEDIUM; + height = height or ROAD.HILL.NONE; + return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, curve, height, 2000); +end + +function utils.addLowRollingHills(segments, segmentLength, rumbleLength, num, height) + num = num or ROAD.LENGTH.SHORT; + height = height or ROAD.HILL.LOW; + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, -height, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000); + return segments +end + +function utils.addSCurves(segments, segmentLength, rumbleLength) + utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000); + utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, 2000); + return segments +end + +function utils.addDownhillToEnd(segments, segmentLength, rumbleLength, num) + num = num or 200; + utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, -ROAD.CURVE.EASY, -lastY(segments)/segmentLength, 2000); + return segments +end + +function utils.resetCars(cars, segments, segmentLength, totalCars, maxSpeed) + local n, car, segment, offset, z, sprite, speed; + for n = 0, totalCars do + offset = math.random() * easeFN.randomChoice({-0.8, 0.8}); + z = math.floor(math.random() * segments.length) * segmentLength; + sprite = easeFN.randomChoice({ + ImageCache:load_to("asset/image/sample/javascript-racer-master/images/sprites/bush1.png", + "asset/image/sample/javascript-racer-master/images/sprites/bush1.png") + }); + + speed = maxSpeed/4 + math.random() * maxSpeed; + car = { offset = offset, z = z, sprite = sprite, speed = speed }; + segment = utils.findSegment(car.z, segments, segmentLength); + table.insert(segment.cars, car); + table.insert(cars, car); + end +end + +function utils.loadRoad(segments, segmentLength, rumbleLength, path) + local segmentData = loadJSON(path) + for _, v in ipairs(segmentData) do + local enter = v.enter + local hold = v.hold + local leave = v.leave + local curve = v.curve + local height = v.height + local width = v.width + utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, height, width); + end +end + +function utils.resetRoad(path, pathSprite, segmentLength, rumbleLength, playerZ) + local segments = {} + utils.loadRoad(segments, segmentLength, rumbleLength, path) + if (pathSprite) then + utils.loadSpriteMapData(segments, pathSprite); + end + local trackLength = #segments * segmentLength; + return segments, trackLength +end + +function utils.loadSpriteMapData(segments, path) + local segmentData = loadJSON(path) + for _, v in ipairs(segmentData) do + local index = v.index + local texture = v.texture + local offset = v.offset + utils.addSprite(segments, index, ImageCache:load_to(texture, texture), offset, texture); + end +end + +local imageData = { + ["car"] = { + path = "asset/image/sample/javascript-racer-master/images/sprites/car01.png" + } +} + +function utils.addSprite(segments, index, sprite, offset, key) + table.insert(segments[index].sprites, { + source= sprite, + offset= offset, + data = {} + }) + local _sIndex = #segments[index].sprites + segments[index].sprites[_sIndex].data = { + path = imageData[key].path, + type = imageData[key].type + } +end + + +return utils diff --git a/game/world_map.lua b/game/world_map.lua index b5c4c01..65f5ccf 100644 --- a/game/world_map.lua +++ b/game/world_map.lua @@ -1,6 +1,7 @@ return { ["main_menu"] = "main_menu", ["1_intro"] = "1_intro", + ["2_town_square"] = "2_town_square", ["race"] = "race", ["train"] = "train" }