Love2d Web with Solana SDK connect Phantom wallet - Part 2 END
From previous part, we created game flow UI that work well without actual connect Phantom Wallet, now we'll finish up all, i hope anyone can picture what's entirely purpose i want to show. From widely Game Engine till niche Game Framework just a bit wall to breaking out if you already read Unity2d WebGL + Solana SDK, once you finish this thread, you can do same things for all any kind of Web3/HTML5/WebGL that exported from any Engine/Framework.Bridge Love2d communicate Love.js (Love2d side)
From previous part if you already extract "love-with-js-main.zip" you should have "love-with-js-main" folder and inside look like below image.
we'll use "js-server.lua" for Love2d "main.lua", let's implement main.lua on Visual Studio Code.
1. import js-server.lua at the top of main.lua.
local js = require("js-server") -- add this line
local gameScene = require("game")
local currentScene = "main"
2. in function update must be call js.update inside.
function love.update(dt)
js.update() -- add this line
if currentScene == "game" then
gameScene.update(dt)
end
end
3. in event mouse press, we'll send debug log from Love2d to love.js and you'll see it on browser console.
function love.mousepressed(x, y, button)
if currentScene == "main" then
if button == 1 and
x >= btConnect.x and x < btConnect.y + btConnect.Image:getWidth() and
y >= btConnect.y and y < btConnect.y + btConnect.Image:getHeight() then
js.run("console_log", "green button pressed!") -- add this line
currentScene = "game"
gameScene.load()
end
elseif currentScene == "game" then
local result = gameScene.mousepressed(x, y, button)
if result == "main" then
js.run("console_log", "red button pressed!") -- add this line
currentScene = "main"
end
end
end
Move to game.lua we need js-server.lua too.
1. import js-server.lua at the top of game.lua
local js = require("js-server") -- add this line
local game = {}
local btDisconnect = {}
2. inside update function, don't forget to save file.
function game.update(dt)
js.update() -- add this line
end
If you try to run main.lua you'll see an error because of main.lua not found js-server.lua
Let's copy js-server.lua from "love-with-js-main" folder and paste beside main.lua, now your main.lua, should be fine without any error.
Bridge Love2d communicate Love.js (Love.js side)
You may need to new window from Visual Studio Code and choose File > Open Folder ..., browse to C:\[your path]\love.js-master\src\compat and open, OR stay on same window as main.ua and browse to C:\[your path]\love.js-master\src\compat and open index.html.
1. import lua-server.js to index.html.
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=1">
<title>{{title}}</title>
<!-- Load custom style sheet -->
<link rel="stylesheet" type="text/css" href="theme/love.css">
<script src="lua-server.js"></script> <!-- add this line -->
</head>
2. implement function runCommand after end of window addEventlistener keydown, once we click on green or red button from Love2d on index.html runCommand function should be call and print console.log on browser console.
window.addEventListener("keydown", function(e) {
// space and arrow keys
if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
e.preventDefault();
}
}, false);
// add runCommand function() { }
function runCommand(cmd, args) {
switch (cmd) {
case "console_log":
console.log(args[0])
break
case "console_warn":
console.warn(args[0])
break
default:
break
}
};
3. copy lua-server.js from "love-with-js-main" and paste to "C:\[your path]\love.js-master\output_folder" an "output_folder" come from previous part.
Build test bridge Love2d to Love.js
Drag all file and right-click > Compress to ... > ZIP File, then rename to main.love like previous part.
Open cmd "command prompt" from windows search, type "cd C:\[your path]\love.js-master\", hit Enter. Now you're in love.js-master folder, type
node index.js main.love output_folder --title "Love2d Bridge Love.js" --compatibility
and hit Enter
One last thing you need to copy "love.js" from "love-with-js-main" and paste to C:\[your path]\love.js-master\output_folder, it will ask to replace love.js just replaced.
from cmd move to C:\[your path]\love.js-master\output_folder by type "cd output_folder" and hit Enter, then running localhost by type "http-server" and hit Enter. Your localhost should be run fine similar below image.
open urt http://127.0.0.1:8080 on your browser. open your developer console (i'm using Edge, click ... > More tools > Developer tools)
click green button. You'll see message on console that we send from Love2d and come in runCommand function on index.html, it's work! you can leave both http-server running and this tab too. it can be save your time to re-run them back.
**another pain point of this, any time when we compile main.love to be web game by this cmd.
node index.js main.love output_folder --title "Love2d Bridge Love.js" --compatibility
in output_folder will be replaced with original love.js then we need to copy love.js from "love-with-js-main" every time. So just replace love.js from template before it compiled.
copy love.js from "love-with-js-main" and paste to "C:\[your path]\love.js-master\src\compat" and replaced
Connect Wallet & Disconnect Wallet
Connect Wallet & Disconnect Wallet
[love2d side]
1. copy "dkjson.lua" from extracted folder "dkjson-master" and put into "love2d-hello-world".
2. go to main.lua, we need to import dkjson.lua
local js = require("js-server")
local gameScene = require("game")
local currentScene = "main"
local json = require("dkjson") -- add this line
3. we need to add listener in onload function both connect and disconnect event, also convert json string to json object
function love.load()
js.set("connect_event", function(raw)
local res = json.decode(raw)
if res ~= nil then
if res.success then
js.run("console_log", "success! " .. res.data)
currentScene = "game"
gameScene.load()
else
js.run("console_warn", res.data)
end
end
end)
js.set("disconnect_event", function(raw)
local res = json.decode(raw)
if res ~= nil then
if res.success then
js.run("console_log", "disconnected!")
currentScene = "main"
else
js.run("console_warn", res.data)
end
end
end)
4. on mouse press function, we need to add function call connect and disconnect for each button, just comment out scene change on love2d, leave js.run connect to love.js side.
function love.mousepressed(x, y, button)
if currentScene == "main" then
if button == 1 and
x >= btConnect.x and x < btConnect.y + btConnect.Image:getWidth() and
y >= btConnect.y and y < btConnect.y + btConnect.Image:getHeight() then
js.run("console_log", "green button pressed!")
--currentScene = "game"
--gameScene.load()
js.run("connect_wallet") -- add this line
end
elseif currentScene == "game" then
local result = gameScene.mousepressed(x, y, button)
if result == "main" then
js.run("console_log", "red button pressed!")
--currentScene = "main"
js.run("disconnect_wallet") -- add this line
end
end
end
[love.js side]
1. on index.html at function runCommand add case connect_wallet and case disconnect_wallet like below image.
function runCommand(cmd, args) {
switch (cmd) {
case "console_log":
console.log(args[0])
break
case "console_warn":
console.warn(args[0])
break
case "connect_wallet":
MyGameBridge.connectWallet();
break
case "disconnect_wallet":
MyGameBridge.disconnectWallet();
break
default:
break
}
};
2. next after end "runCommand" and before "var Module = {...}" create "window.MyGameBridge = {...}" like below, provide connect and disconnect to phantom app, and package result json to string with jStr function. return event must be match event in main.lua both connect_event and disconnect_event.
window.MyGameBridge = {
jStr: function(status, payload) {
return JSON.stringify({success: status, data: payload});
},
connectWallet: function() {
console.log("Connecting to Phantom...");
try {
if (window.solana && window.solana.isPhantom) {
window.solana.connect()
.then(resp => {
lua("connect_event", window.MyGameBridge.jStr(true, resp.publicKey.toString()))
})
.catch(err => {
lua("connect_event", window.MyGameBridge.jStr(false, err.message))
});
} else {
lua("connect_event", window.MyGameBridge.jStr(false, "err wallet not found >> "))
}
} catch (err) {
lua("connect_event", window.MyGameBridge.jStr(false, err.message))
}
},
disconnectWallet: async function () {
if (window.solana && window.solana.isConnected) {
try {
await window.solana.disconnect();
lua("disconnect_event", window.MyGameBridge.jStr(true, "success disconnected."))
} catch (err) {
lua("disconnect_event", window.MyGameBridge.jStr(false, err.message))
}
} else {
lua("disconnect_event", window.MyGameBridge.jStr(false, "unknown error."))
}
},
};
after done saved index.html, let zip all file to "main.love" again copy paste to "love.js-master" and run cmd node index.js game.love output_folder --title "My Bridge Game" --compatibility again, you don't to run http-server if previous step you leave it running, just go to browser game tab previous step and click hard refresh or empty cache and hard refresh.
Test Connect and Disconnect
Click green button when Phantom App appear, just fill password and click connect, once successful you should see your public key on console and screen change from main to game. similar below image.
Once success connect, try click red button, your screen should be back to view green button on main scene, and you should see "disconnected!" on console.
1. on game.lua at top of file import dkjson, user_wallet to keep player connect wallet address and last one is receiver wallet address (your wallet address to receive SOL from player/user)
local js = require("js-server")
local json = require("dkjson") -- add this line
local game = {}
local btDisconnect = {}
local btBuyItem = {}
local total_potion = 0
local user_wallet = "" -- add this line
local receiver_wallet = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -- add wallet merchant/receiver address
2. next line after implement function store user wallet from main.lua
function game.wallet(public_address)
user_wallet = public_address
end
3. still on game.lua under on load function implement listener function "buy_item_event", inside function handle success and err result, if success so user/player transaction is completed then you give an item to user/player.
function game.load()
-- add below listener
js.set("buy_item_event", function(raw)
local res = json.decode(raw)
if res ~= nil then
if res.success then
total_potion = total_potion + 1
js.run("console_log", "Purchased! ".. res.data .." Total Potion = " .. total_potion)
else
js.run("console_warn", res.data)
end
end
end)
4. game.lua, on mouse press function add trigger yellow button calling buy item, it required 3 parameters ordering as user wallet, receiver wallet and item price (SOL).
function game.mousepressed(x, y, button)
if button == 1 and
x >= btDisconnect.x and x < btDisconnect.y + btDisconnect.Image:getWidth() and
y >= btDisconnect.y and y < btDisconnect.y + btDisconnect.Image:getHeight() then
return "main"
elseif button == 1 and
x >= btBuyItem.x and x < btBuyItem.y + btBuyItem.Image:getWidth() and
y >= btBuyItem.y and y < btBuyItem.y + btBuyItem.Image:getHeight() then
js.run("console_log", "buy addr: " .. user_wallet .. " recv: " .. receiver_wallet .. " SOL: " .. 0.002)
js.run("buy_item", user_wallet, receiver_wallet, 0.002)
end
end
5. switch to main.lua on load function inside connect event if success you need to user wallet by add gameScene.wallet and pass parameter res.data (user/player wallet address), don't forget to save both main.lua and game.lua file.
function love.load()
js.set("connect_event", function(raw)
local res = json.decode(raw)
if res ~= nil then
if res.success then
js.run("console_log", "success! " .. res.data)
gameScene.wallet(res.data) -- add thisline
currentScene = "game"
gameScene.load()
else
js.run("console_warn", res.data)
end
end
end)
Implement Buy Item (Love.js side)
1. open index.html (C:\[your path]\love.js-master\src\compat) import solana web3 sdk in between head tag. at line after "lua-server.js".
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=1">
<title>{{title}}</title>
<!-- Load custom style sheet -->
<link rel="stylesheet" type="text/css" href="theme/love.css">
<script src="lua-server.js"></script>
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script><!-- add this line -->
</head>
2. scroll down to runCommand function add case buy item event. Then pass an args[0] args[1] and args[2] to solanaSignAndSend
function runCommand(cmd, args) {
switch (cmd) {
case "console_log":
console.log(args[0])
break
case "console_warn":
console.warn(args[0])
break
case "connect_wallet":
MyGameBridge.connectWallet();
break
case "disconnect_wallet":
MyGameBridge.disconnectWallet();
break
case "buy_item":
// args[0] = user_wallet, args[1] = receiver_wallet, args[2] = item price
MyGameBridge.solanaSignAndSend(args[0], args[1], args[2]);
break
default:
break
}
};
3. scroll down to window.MyGameBridge add function solanaSignAndSend after disconnectWallet. Saved index.html.
solanaSignAndSend: async function (publicAddress, recipientAddress, solValue) {
// when release change to "mainnet-beta", "devnet" use for testing only
try {
const clusterApi = solanaWeb3.clusterApiUrl("devnet");
const connection = new solanaWeb3.Connection(clusterApi);
const fromPublicKey = new solanaWeb3.PublicKey(publicAddress);
const toPublicKey = new solanaWeb3.PublicKey(recipientAddress);
const transaction = new solanaWeb3.Transaction().add(
solanaWeb3.SystemProgram.transfer({
fromPubkey: fromPublicKey,
toPubkey: toPublicKey,
lamports: BigInt(Math.floor(solanaWeb3.LAMPORTS_PER_SOL * solValue)),
})
);
transaction.feePayer = fromPublicKey;
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
const res = await window.solana.signAndSendTransaction(transaction);
if (!res || !res.signature ) {
lua("buy_item_event", window.MyGameBridge.jStr(false, "No signature."))
return;
}
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
if (!blockhash || !lastValidBlockHeight ) {
lua("buy_item_event", window.MyGameBridge.jStr(false, "Invalid blockhash."))
return;
}
const confirm = await connection.confirmTransaction({
signature: res.signature,
blockhash: blockhash,
lastValidBlockHeight: lastValidBlockHeight
});
var strJson = JSON.stringify(confirm);
lua("buy_item_event", window.MyGameBridge.jStr(true, strJson))
} catch (error) {
console.error("err >> ", error.message);
lua("buy_item_event", window.MyGameBridge.jStr(false, error.message))
}
},
Test Solana SDK integration
Now your game should be ready sell an item. zip your love2 game to "main.love" and copy paste to folder "love.js-master", then open cmd "cd your_path/love.js-master" hit enter. Run cmd.
node index.js main.love output_folder --title "My Bridge Game" --compatibility
then refresh your current game tab, your game should be run without any error, click green button to connect, main scene should be change to game scene.
At game scene, now Potion is "0", once click yellow button for purchase potion, it's trigger Phantom popover for confirm payment (use airdrop from first Part to be enough for test), click "Confirm"
Wait a little longer when transaction completed, you should receive confirm transaction on Love2d game.lua just handle as you need. (Don't forget to check your balance in your wallet).
Now your Love2d as web game can sell any item in your game and receive purchased as SOL currency. Done, hope you enjoy making game and make money on web platform with Love2d. :)Buy Me a Coffee.