From 00c0f6605b67cbfb5b2a3cff9c5ca4f2234cb0cc Mon Sep 17 00:00:00 2001 From: Alexander Griffith Date: Fri, 25 Mar 2022 19:40:58 -0400 Subject: [PATCH] implemented duplicate --- .duplicate/clone/update-layout.sh | 3 + .duplicate/new-game.sh | 87 ++++++++++ .../overwrite/lib/gamestate.lua | 113 +++++++++++++ .../seperate-source/overwrite/lib/js.lua | 151 ++++++++++++++++++ .duplicate/seperate-source/overwrite/main.lua | 58 +++++++ .duplicate/seperate-source/overwrite/makefile | 95 +++++++++++ .../overwrite/src/mode-intro.fnl | 27 ++++ .../seperate-source/overwrite/src/wrap.fnl | 9 ++ .duplicate/seperate-source/update-layout.sh | 7 + readme.org | 66 ++++++-- 10 files changed, 604 insertions(+), 12 deletions(-) create mode 100755 .duplicate/clone/update-layout.sh create mode 100755 .duplicate/new-game.sh create mode 100644 .duplicate/seperate-source/overwrite/lib/gamestate.lua create mode 100644 .duplicate/seperate-source/overwrite/lib/js.lua create mode 100644 .duplicate/seperate-source/overwrite/main.lua create mode 100644 .duplicate/seperate-source/overwrite/makefile create mode 100644 .duplicate/seperate-source/overwrite/src/mode-intro.fnl create mode 100644 .duplicate/seperate-source/overwrite/src/wrap.fnl create mode 100755 .duplicate/seperate-source/update-layout.sh diff --git a/.duplicate/clone/update-layout.sh b/.duplicate/clone/update-layout.sh new file mode 100755 index 0000000..73d4c31 --- /dev/null +++ b/.duplicate/clone/update-layout.sh @@ -0,0 +1,3 @@ +target=$1 +location=$(dirname "$0") +cp -rf $location/../../* $target diff --git a/.duplicate/new-game.sh b/.duplicate/new-game.sh new file mode 100755 index 0000000..2829d9f --- /dev/null +++ b/.duplicate/new-game.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +help="new-game.sh: \n + A tool for duplicating min-love2d-fennel. \n\ +\n\ +usage: \n\ + ./new-game.sh [project-name] [opts]\n\ +\n +opts:\n + -o= (--output-directory=) Target parent directory. Defaults to PWD\n + -l= (--layut=) Set the layout. Defaults to minimal layout.\n + -f (--force) overwrite the target directory if it exists\n + -h (--help) Display this text\n +\n +eg: \n\ +./new-game.sh sample-game -o=../ -f +" + +name=$1 +output_dir=$(pwd) +gethelp=false +location=$(dirname "$0") +force=false +layout="clone" + +for i in "$@" +do +case $i in + -o=*|--output-directory=*) + output_dir="${i#*=}" + ;; + -l=*|--layout=*) + layout="${i#*=}" + ;; + -f|--force) + force=true + ;; + -h|--help) + gethelp=true + ;; + *) + # unknown option + ;; +esac +done + + +if [ ! -d $output_dir ]; then + echo "output directory not found!" + echo $output_dir + exit 1 +fi + +if [ ! -d $location/$layout ]; then + echo "\"$layout\" layout not found!" + echo "Valid built in layouts are:" + echo "clone" + echo "seperate-source" + exit 1 +fi + +target_dir=$output_dir/$name + +if [ -d $target_dir ]; then + if [ $force = true ]; then + echo "Overwriting $target_dir." + rm -rf $target_dir + else + echo "target directory already exists! Use -f or --force to overwrite this directory." + echo $target_dir + exit 1 + fi +fi + +copy-dir (){ + mkdir $target_dir + ./$location/$layout/update-layout.sh $target_dir + rm -rf $location/.duplicate + rm -rf $location/.git +} + +if [ $gethelp = true ]; then + echo -e $help +else + copy-dir +fi + diff --git a/.duplicate/seperate-source/overwrite/lib/gamestate.lua b/.duplicate/seperate-source/overwrite/lib/gamestate.lua new file mode 100644 index 0000000..36e0563 --- /dev/null +++ b/.duplicate/seperate-source/overwrite/lib/gamestate.lua @@ -0,0 +1,113 @@ +--[[ +Copyright (c) 2010-2013 Matthias Richter + +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. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +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 function __NULL__() end + + -- default gamestate produces error on every callback +local state_init = setmetatable({leave = __NULL__}, + {__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end}) +local stack = {state_init} +local initialized_states = setmetatable({}, {__mode = "k"}) +local state_is_dirty = true + +local GS = {} +function GS.new(t) return t or {} end -- constructor - deprecated! + +local function change_state(stack_offset, to, ...) + local pre = stack[#stack] + + -- initialize only on first call + ;(initialized_states[to] or to.init or __NULL__)(to) + initialized_states[to] = __NULL__ + + stack[#stack+stack_offset] = to + state_is_dirty = true + return (to.enter or __NULL__)(to, pre, ...) +end + +function GS.switch(to, ...) + assert(to, "Missing argument: Gamestate to switch to") + assert(to ~= GS, "Can't call switch with colon operator") + ;(stack[#stack].leave or __NULL__)(stack[#stack]) + return change_state(0, to, ...) +end + +function GS.push(to, ...) + assert(to, "Missing argument: Gamestate to switch to") + assert(to ~= GS, "Can't call push with colon operator") + return change_state(1, to, ...) +end + +function GS.pop(...) + assert(#stack > 1, "No more states to pop!") + local pre, to = stack[#stack], stack[#stack-1] + stack[#stack] = nil + ;(pre.leave or __NULL__)(pre) + state_is_dirty = true + return (to.resume or __NULL__)(to, pre, ...) +end + +function GS.current() + return stack[#stack] +end + +-- XXX: don't overwrite love.errorhandler by default: +-- this callback is different than the other callbacks +-- (see http://love2d.org/wiki/love.errorhandler) +-- overwriting thi callback can result in random crashes (issue #95) +local all_callbacks = { 'draw', 'update' } + +-- fetch event callbacks from love.handlers +for k in pairs(love.handlers) do + all_callbacks[#all_callbacks+1] = k +end + +function GS.registerEvents(callbacks) + local registry = {} + callbacks = callbacks or all_callbacks + for _, f in ipairs(callbacks) do + registry[f] = love[f] or __NULL__ + love[f] = function(...) + registry[f](...) + return GS[f](...) + end + end +end + +-- forward any undefined functions +setmetatable(GS, {__index = function(_, func) + -- call function only if at least one 'update' was called beforehand + -- (see issue #46) + if not state_is_dirty or func == 'update' then + state_is_dirty = false + return function(...) + return (stack[#stack][func] or __NULL__)(stack[#stack], ...) + end + end + return __NULL__ +end}) + +return GS diff --git a/.duplicate/seperate-source/overwrite/lib/js.lua b/.duplicate/seperate-source/overwrite/lib/js.lua new file mode 100644 index 0000000..be52835 --- /dev/null +++ b/.duplicate/seperate-source/overwrite/lib/js.lua @@ -0,0 +1,151 @@ +__requestQueue = {} +_requestCount = 0 +_Request = +{ + command = "", + currentTime = 0, + timeOut = 2, + id = '0' +} +local os = love.system.getOS() +local __defaultErrorFunction = nil +local isDebugActive = false + +JS = {} + +function JS.callJS(funcToCall) + if(os == "Web") then + print("callJavascriptFunction " .. funcToCall) + end +end + +--You can pass a set of commands here and, it is a syntactic sugar for executing many commands inside callJS, as it only calls a function +--If you pass arguments to the func beyond the string, it will perform automatically string.format +--Return statement is possible inside this structure +--This will return a string containing a function to be called by JS.callJS +local _unpack +if(_VERSION == "Lua 5.1" or _VERSION == "LuaJIT") then + _unpack = unpack +else + _unpack = table.unpack +end +function JS.stringFunc(str, ...) + str = "(function(){"..str.."})()" + if(#arg > 0) then + str = str:format(_unpack(arg)) + end + str = str:gsub("[\n\t]", "") + return str +end + +--The call will store in the webDB the return value from the function passed +--it timeouts +local function retrieveJS(funcToCall, id) + --Used for retrieveData function + JS.callJS("FS.writeFile('"..love.filesystem.getSaveDirectory().."/__temp"..id.."', "..funcToCall..");") +end + +--Call JS.newRequest instead +function _Request:new(isPromise, command, onDataLoaded, onError, timeout, id) + local obj = {} + setmetatable(obj, self) + obj.command = command + obj.onError = onError or __defaultErrorFunction + if not isPromise then + retrieveJS(command, id) + else + JS.callJS(command) + end + obj.onDataLoaded = onDataLoaded + obj.timeOut = (timeout == nil) and obj.timeOut or timeout + obj.id = id + + + function obj:getData() + --Try to read from webdb + return love.filesystem.read("__temp"..self.id) + end + + function obj:purgeData() + --Data must be purged for not allowing old data to be retrieved + love.filesystem.remove("__temp"..self.id) + end + + function obj:update(dt) + self.timeOut = self.timeOut - dt + local retData = self:getData() + + if((retData ~= nil and retData ~= "nil") or self.timeOut <= 0) then + if(retData ~= nil and retData:match("ERROR") == nil) then + if isDebugActive then + print("Data has been retrieved "..retData) + end + self.onDataLoaded(retData) + else + self.onError(self.id, retData) + end + self:purgeData() + return false + else + return true + end + end + return obj +end + +--Place this function on love.update and set it to return if it returns false (This API is synchronous) +function JS.retrieveData(dt) + local isRetrieving = #__requestQueue ~= 0 + local deadRequests = {} + for i = 1, #__requestQueue do + local isUpdating =__requestQueue[i]:update(dt) + if not isUpdating then + table.insert(deadRequests, i) + end + end + for i = 1, #deadRequests do + if(isDebugActive) then + print("Request died: "..deadRequests[i]) + end + table.remove(__requestQueue, deadRequests[i]) + end + return isRetrieving +end + +--May only be used for functions that don't return a promise +function JS.newRequest(funcToCall, onDataLoaded, onError, timeout, optionalId) + if(os ~= "Web") then + return + end + table.insert(__requestQueue, _Request:new(false, funcToCall, onDataLoaded, onError, timeout or 5, optionalId or _requestCount)) +end + +--This function can be handled manually (in JS code) + --How to: add the function call when your events resolve: FS.writeFile("Put love.filesystem.getSaveDirectory here", "Pass a string here (NUMBER DONT WORK")) +--Or it can be handled by Lua, it auto sets your data if you write the following command: + -- _$_(yourStringOrFunctionHere) +function JS.newPromiseRequest(funcToCall, onDataLoaded, onError, timeout, optionalId) + if(os ~= "Web") then + return + end + optionalId = optionalId or _requestCount + funcToCall = funcToCall:gsub("_$_%(", "FS.writeFile('"..love.filesystem.getSaveDirectory().."/__temp"..optionalId.."', ") + table.insert(__requestQueue, _Request:new(true, funcToCall, onDataLoaded, onError, timeout or 5, optionalId)) +end + + +--It receives the ID from ther request +--Don't try printing the request.command, as it will execute the javascript command +function JS.setDefaultErrorFunction(func) + __defaultErrorFunction = func +end + +JS.setDefaultErrorFunction(function(id, error) + if( isDebugActive ) then + local msg = "Data could not be loaded for id:'"..id.."'" + if(error)then + msg = msg.."\nError: "..error + end + print(msg) + end +end) diff --git a/.duplicate/seperate-source/overwrite/main.lua b/.duplicate/seperate-source/overwrite/main.lua new file mode 100644 index 0000000..aadb813 --- /dev/null +++ b/.duplicate/seperate-source/overwrite/main.lua @@ -0,0 +1,58 @@ +-- bootstrap the compiler +fennel = require("lib.fennel") +table.insert(package.loaders, fennel.make_searcher({correlate=true})) + +fennel.path = love.filesystem.getSource() .. "/?.fnl;" .. + love.filesystem.getSource() .. "/src/?.fnl;" .. + love.filesystem.getSource() .. "/src/?/init.fnl;" .. + fennel.path + +debug_mode = true +pp = function(x) print(fennel.view(x)) end +db = function(x) + if (debug_mode == true) then + local debug_info = debug.getinfo(1) + -- print debug.getinfo + local currentline = debug_info.currentline + -- local file = debug_info.source:match("^.+/(.+)$") + local file = debug_info["short_src"] or "" + local name = debug_info["namewhat"] or "" + pp({"db", x}) + end +end + + +js = (require "lib.js") +lume = require("lib.lume") + +local make_love_searcher = function(env) + return function(module_name) + local path = module_name:gsub("%.", "/") .. ".fnl" + if love.filesystem.getInfo(path) then + return function(...) + local code = love.filesystem.read(path) + return fennel.eval(code, {env=env}, ...) + end, path + end + end +end + +table.insert(package.loaders, make_love_searcher(_G)) +table.insert(fennel["macro-searchers"], make_love_searcher("_COMPILER")) + +require("lib.js") + +-- taken from bump +function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2) + return x1 < x2+w2 and x2 < x1+w1 and + y1 < y2+h2 and y2 < y1+h1 +end + +function pointWithin(px,py,x,y,w,h) + return px < x + w and px > x and + py < y + h and py > y +end + +math.randomseed( os.time() ) + +require("wrap") diff --git a/.duplicate/seperate-source/overwrite/makefile b/.duplicate/seperate-source/overwrite/makefile new file mode 100644 index 0000000..230bdba --- /dev/null +++ b/.duplicate/seperate-source/overwrite/makefile @@ -0,0 +1,95 @@ +VERSION=0.1.0 +LOVE_VERSION=11.4 +NAME=change-me +ITCH_ACCOUNT=username +URL=https://username.itch.io/change-me +AUTHOR="Change Me" +DESCRIPTION="Change Me" + +HEIGHT=640 +WIDTH=880 +CANVAS_COLOUR="26,16,16" +TEXT_COLOUR="250,248,240" + + +LIBS_LUA := $(wildcard lib/*) +LIBS_FNL := $(wildcard lib/*.fnl) +LUA := $(wildcard *.lua) +SRC := $(wildcard src/*.fnl) +OUT := $(patsubst src/%.fnl,%.lua,$(SRC)) +OUT_LIBS := $(patsubst %.fnl,%.lua,$(LIBS_FNL)) + +run: $(OUT) $(OUT_ENTS); love . + +count: ; cloc src/*.fnl --force-lang=clojure + +clean: ; rm -rf releases/* $(OUT) $(OUT_LIBS) + +cleansrc: ; rm -rf $(OUT) $(OUT_LIBS) + +%.lua: src/%.fnl; lua lib/fennel --compile --correlate $< > $@ + +lib/%.lua: lib/%.fnl; lua lib/fennel --compile --correlate $< > $@ + +LOVEFILE=releases/$(NAME)-$(VERSION).love + +$(LOVEFILE): $(LUA) $(OUT) $(OUT_LIBS) $(LIBS_LUA) assets #text + mkdir -p releases/ + find $^ -type f | LC_ALL=C sort | env TZ=UTC zip -r -q -9 -X $@ -@ + +love: $(LOVEFILE) + +# platform-specific distributables + +REL=$(PWD)/buildtools/love-release.sh # https://p.hagelb.org/love-release.sh +FLAGS=-a "$(AUTHOR)" --description $(DESCRIPTION) \ + --love $(LOVE_VERSION) --url $(URL) --version $(VERSION) --lovefile $(LOVEFILE) + +releases/$(NAME)-$(VERSION)-x86_64.AppImage: $(LOVEFILE) + cd buildtools/appimage && ./build.sh $(LOVE_VERSION) $(PWD)/$(LOVEFILE) + mv buildtools/appimage/game-x86_64.AppImage $@ + +releases/$(NAME)-$(VERSION)-macos.zip: $(LOVEFILE) + $(REL) $(FLAGS) -M + mv releases/$(NAME)-macos.zip $@ + +releases/$(NAME)-$(VERSION)-win.zip: $(LOVEFILE) + $(REL) $(FLAGS) -W32 + mv releases/$(NAME)-win32.zip $@ + + +releases/$(NAME)-$(VERSION)-web.zip: $(LOVEFILE) + buildtools/love-js/love-js.sh releases/$(NAME)-$(VERSION).love $(NAME) -v=$(VERSION) -a=$(AUTHOR) -o=releases -x=$(WIDTH) -y=$(HEIGHT) -c=$(CANVAS_COLOUR) -t=$(TEXT_COLOUR) + +releases/$(NAME)-$(VERSION)-source.zip: + find assets buildtools lib ./conf.lua ./makefile ./license.txt ./main.lua ./readme.org src buildtools -type f | LC_ALL=C sort | env TZ=UTC zip -r -q -9 -X $@ -@ + +runweb: $(LOVEFILE) + buildtools/love-js/love-js.sh releases/$(NAME)-$(VERSION).love $(NAME) -v=$(VERSION) -a=$(AUTHOR) -o=releases -x=$(WIDTH) -y=$(HEIGHT) -c=$(CANVAS_COLOUR) -t=$(TEXT_COLOUR) -r + +linux: releases/$(NAME)-$(VERSION)-x86_64.AppImage +mac: releases/$(NAME)-$(VERSION)-macos.zip +windows: releases/$(NAME)-$(VERSION)-win.zip +web: releases/$(NAME)-$(VERSION)-web.zip +source: releases/$(NAME)-$(VERSION)-source.zip + +# If you release on itch.io, you should install butler: +# https://itch.io/docs/butler/installing.html + +uploadlinux: releases/$(NAME)-$(VERSION)-x86_64.AppImage + butler push $^ $(ITCH_ACCOUNT)/$(NAME):linux --userversion $(VERSION) +uploadmac: releases/$(NAME)-$(VERSION)-macos.zip + butler push $^ $(ITCH_ACCOUNT)/$(NAME):mac --userversion $(VERSION) +uploadwindows: releases/$(NAME)-$(VERSION)-win.zip + butler push $^ $(ITCH_ACCOUNT)/$(NAME):windows --userversion $(VERSION) +uploadlove: releases/$(NAME)-$(VERSION).love + butler push $^ $(ITCH_ACCOUNT)/$(NAME):love --userversion $(VERSION) +uploadweb: releases/$(NAME)-$(VERSION)-web.zip + butler push $^ $(ITCH_ACCOUNT)/$(NAME):web --userversion $(VERSION) +uploadsource: releases/$(NAME)-$(VERSION)-source.zip + butler push $^ $(ITCH_ACCOUNT)/$(NAME):source --userversion $(VERSION) + + +upload: uploadlinux uploadmac uploadwindows uploadweb uploadlove cleansrc + +release: linux web mac windows upload cleansrc diff --git a/.duplicate/seperate-source/overwrite/src/mode-intro.fnl b/.duplicate/seperate-source/overwrite/src/mode-intro.fnl new file mode 100644 index 0000000..415ad17 --- /dev/null +++ b/.duplicate/seperate-source/overwrite/src/mode-intro.fnl @@ -0,0 +1,27 @@ +(import-macros {: incf} :sample-macros) + +(var counter 0) +(var time 0) + +(love.graphics.setNewFont 30) + +(local (major minor revision) (love.getVersion)) + +{:draw (fn draw [self message] + (local (w h _flags) (love.window.getMode)) + (love.graphics.printf + (: "Love Version: %s.%s.%s" + :format major minor revision) 0 10 w :center) + (love.graphics.printf + (: "This window should close in %0.1f seconds" + :format (math.max 0 (- 3 time))) + 0 (- (/ h 2) 15) w :center)) + :update (fn update [self dt set-mode] + (if (< counter 65535) + (set counter (+ counter 1)) + (set counter 0)) + (incf time dt) + (when (> time 3) + (love.event.quit))) + :keypressed (fn keypressed [self key set-mode] + (love.event.quit))} diff --git a/.duplicate/seperate-source/overwrite/src/wrap.fnl b/.duplicate/seperate-source/overwrite/src/wrap.fnl new file mode 100644 index 0000000..a837033 --- /dev/null +++ b/.duplicate/seperate-source/overwrite/src/wrap.fnl @@ -0,0 +1,9 @@ +(local gamestate (require :lib.gamestate)) +(local stdio (require :lib.stdio)) + +(fn love.load [args] + (love.graphics.setDefaultFilter "nearest" "nearest" 0) + (if (= :web (. args 1)) (global web true) (global web false)) + (gamestate.registerEvents) + (gamestate.switch (require :mode-intro)) + (when (not web) (stdio.start))) diff --git a/.duplicate/seperate-source/update-layout.sh b/.duplicate/seperate-source/update-layout.sh new file mode 100755 index 0000000..260a207 --- /dev/null +++ b/.duplicate/seperate-source/update-layout.sh @@ -0,0 +1,7 @@ +target=$1 +location=$(dirname "$0") +cp -rf $location/../../* $target +cp -rf $location/overwrite/* $target +mv $target/sample-macros.fnl $target/src/ +rm -rf $target/*fnl +mv -f $target/src/sample-macros.fnl $target diff --git a/readme.org b/readme.org index 02d074e..16a6039 100644 --- a/readme.org +++ b/readme.org @@ -1,30 +1,52 @@ * Minimal Fennel Love2D Setup -In a lead up to the annual [[https://itch.io/jam/autumn-lisp-game-jam-2018][Autumn Lisp Game Jam]] I thought I'd look into Phil Hegelberg's approach to last Aprils Jam, using [[https://love2d.org/][love2d]] in concert with [[https://fennel-lang.org/][fennel]]. Phil outlines his approach on his [[https://technomancy.us/187][blog]]. - -The first step is to see how fennel works as a standalone execution environment, then slowly integrate some of the love2d API. +In the lead up to the semi-annual [[https://itch.io/jam/autumn-lisp-game-jam-2018][Autumn Lisp Game Jam]] I thought I'd look into Phil Hegelberg's approach to last Aprils Jam, using [[https://love2d.org/][love2d]] in concert with [[https://fennel-lang.org/][fennel]]. Phil outlines his approach on his [[https://technomancy.us/187][blog]]. This repo contains the minimal viable setup to get started with Phil Hegelberg's game design process, plus some additional libraries. +* Getting Started +The following commands will clone this project and duplicate its structure into a new folder =$PROJECT_NAME= + #+BEGIN_SRC bash -PROJECT=project-name -git clone https://gitlab.com/alexjgriffith/min-love2d-fennel.git $PROJECT -cd $PROJECT -rm -rf .git +git clone https://gitlab.com/alexjgriffith/min-love2d-fennel.git +./min-love2d-fennel/.duplicate/new-game.sh $PROJECT_NAME #+END_SRC -* Phil's Modal Callbacks (PMC) +Check out the makefile and conf.lua files in =$PROJECT_NAME=, updating them with information relevant to your game. -Phil Hegelberg's [[https://gitlab.com/technomancy/exo-encounter-667/][exo-encounter-667]] is structured using a modal callback system. Each game state has a mode and each mode has a series of specific callbacks. +You can enter =love .= in the =$PROJECT_NAME= directory to run your game, or =make run=. -If you design your game as a series of states in a very simple state machine, for example *start-screen*, *play* and *end*, with unidirectional progression, you can easily separate the logic for each state into state/mode specific callbacks. As an example, in order to have state dependant rendering that differs between start-screen,play and end you could provide a *draw* callback for each of those states. Similarly if we need state dependent logic and keyboard input we could provide *update* and *keyboard* callbacks. As you iterate you can add and remove callbacks and states/modes as needed with very little friction. +The following lines with =Update= should be changed in the =makefile= and =love.conf= to reflect your game. + +#+BEGIN_SRC makefile +VERSION=0.1.0 +LOVE_VERSION=11.4 +NAME=change-me # Update +ITCH_ACCOUNT=change-me-too # Update +URL=https://gitlab.com/alexjgriffith/min-love2d-fennel # Update +AUTHOR="Your Name" # Update +DESCRIPTION="Minimal setup for trying out Phil Hagelberg's fennel/love game design process." # Update +#+END_SRC + +#+BEGIN_SRC lua +love.conf = function(t) + t.gammacorrect = true + t.title, t.identity = "change-me", "Minimal" -- Update + t.modules.joystick = false + t.modules.physics = false + t.window.width = 720 + t.window.height = 450 + t.window.vsync = false + t.version = "11.4" +end +#+END_SRC * Emacs Setup Once you install the latest version of [[https://gitlab.com/technomancy/fennel-mode][fennel-mode]], you can run =C-u M-x fennel-repl= followed by =love .= to launch a repl. -* Project Structure +* Default Project Structure The =make= process as-is will only compile the contents of the root folder and the =lib/= folder+subfolders, so make sure to put your game files in either of those. @@ -32,6 +54,26 @@ Specifically, every =.fnl= file needed at runtime needs to be situated in the ro In order to use macros, they have to be put in =.fnl= files inside =lib/=. +* Seperate your Code into a /src directory + + If you want a more opinionated layout, you can use pass in a =--layout= parameter when creating your project. + +#+BEGIN_SRC bash +./min-love2d-fennel/.duplicate/new-game.sh $PROJECT_NAME --layout=seperate-source +#+END_SRC + +This build uses =gamestate= rather than Phil's approach to scene seperation and puts all your =.fnl= files into a =/src= directory. It also provides a seperate makefile that handles this layout. + +Note, any macros will have to be placed in the root of the project or in the =lib= folder (this can be modified in =main.lua=) + +Presently the only layouts are =clone= and =seperate-source=. If you want to make your own check out the =.duplicate= directory to see how they work. + * Release Process -Use =make linux=, =make windows=, or =make mac= to create executables for each platform, or =make release= to do all three. Make sure to read the makefile for more commands (and remember to edit your game data in it!) +Use =make linux=, =make windows=, =make mac=, or =make web= to create targets for each platform, or =make release= to make all four. Check out the makefile for more commands, and remember to edit your game data in it! + +* Phil's Modal Callbacks (PMC) + +Phil Hegelberg's [[https://gitlab.com/technomancy/exo-encounter-667/][exo-encounter-667]] is structured using a modal callback system. Each game state has a mode and each mode has a series of specific callbacks. + +If you design your game as a series of states in a very simple state machine, for example *start-screen*, *play* and *end*, with unidirectional progression, you can easily separate the logic for each state into state/mode specific callbacks. As an example, in order to have state dependant rendering that differs between start-screen,play and end you could provide a *draw* callback for each of those states. Similarly if we need state dependent logic and keyboard input we could provide *update* and *keyboard* callbacks. As you iterate you can add and remove callbacks and states/modes as needed with very little friction.