
[{"content":"","date":"2 June 2025","externalUrl":null,"permalink":"/feishiko-blog-page/","section":"Feishiko's Blog Site","summary":"","title":"Feishiko's Blog Site","type":"page"},{"content":"","date":"2 June 2025","externalUrl":null,"permalink":"/feishiko-blog-page/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"最近我在做一个新的游戏，出于某种原因这是一个手机游戏，所以我需要把它打包到安卓。\n一开始没什么头绪就去看YouTube视频，它很好的帮助了我解决了大部分的问题：https://www.youtube.com/watch?v=vsRnFMMhr58\n根据视频里做的，我下载了Android Studio，然后通过Android下载了JDK和NDK，之后为JDK和NDK配置环境\n之后使用love-android帮助我打包到安卓\n首先要根据love-android上说的把仓库clone下来，\ngit clone --recurse-submodules https://github.com/love2d/love-android 视频里是改里面一点的文件的配置，但是love-android之后似乎有更新，所以只需要在./gradle.properties修改app.application_id和app.orientation就会影响到./app/src/main/AndroidManifest.xml\n然后我想可能需要在AndroidManifest.xml里修改label，这会影响到包的显示名字。\n我最后生成的版本没和视频走，使用Android Studio，上面菜单找到Build，下拉选单先Generate APP Bundles or APKs了一个出来，之后选择Generate Signed APP Bundles or APK...。\n有关签名首先需要一个密钥，但是这在Android Studio中很容易创建一个，选择APK，Key Store Path那直接Create New如果你没有一个key，密码随便填一个就好，下面的信息也随便照着填一填。出来之后Choose Existing，然后找到之间创建的Key。下面会要求你填密码，照着之前写的填就好。\n然后会让你选择一个Variants，如果你的游戏需要麦克风需要选择有record的，然后选择embed开头的，打包到安卓的本质是把nogame替换成了你的game，embed会直接进入游戏，如果是normal的话会让你选择一个.love文件在打开程序之后，这并不是我们想要的。然后就是选择Release的字样。等待打包完成后就能看到你的apk了。\n这边可能会有一个问题，就是你发现游戏的包体很大，点击Build下拉选单的Analyze APK或者直接用zip打开apk去看它的构成内容，你可能会发现lib文件占了大头。这边直接删掉非当前架构的lib就能直接减少20多MB的空间占用。但是这样你需要用其他方法给当前的包重新签名，因为包被修改过了。\n还有一种方法是，./app/build.gradle里，删掉abiFilters行的另外两个非当前架构，我build的是arm64-v8a，也推荐你和我一样。选择架构应该能在Android Studio的某个地方看到。\n如果你需要替换icon，res文件夹里面根据不同尺寸的icon去进行替换即可。\n","date":"2 June 2025","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91love2d%E6%B8%B8%E6%88%8F%E6%89%93%E5%8C%85%E5%88%B0android/","section":"Posts","summary":"最近我在做一个新的游戏，出于某种原因这是一个手机游戏，所以我需要把它打包到安卓。","title":"游戏开发：Love2d游戏打包到Android","type":"posts"},{"content":"最近写Lua的时候偶然发现可以通过文档注释然后为拟定成Class的Table进行补全。\n比如说GameObject:Find(string)这个函数，要填一个string，正常情况下是不会返回table的，如果使用了lua-ls的annotation就可以使得language server补全所要返回的东西。\n---Make a GameObject ---@generic T ---@param class `T` ---@return T function M:New(class) ---@class GameObject local obj = { x = 0, y = 0, zIndex = 0, width = 0, height = 0, class = class, keep = false, id = M.currentID }; 使用以上代码在创建Class的时候annotation里使用泛型，参数标记泛型并且使用`(backticks)，具体可以参考这篇：\nhttps://luals.github.io/wiki/annotations/#generic\n在我自己写的代码中会使得class添加到一个table里：\ntable.insert(M.instanceList, obj); 创建实例后，在Find函数调用便能返回补全\n---Find an instance by name ---@generic T ---@param name `T` # the type is captured using `T` ---@return T function M:Find(name) for key, value in pairs(M.instanceList) do if (value.class == name) then return value end end return nil end 之后只需在创建class前加上---@class的注释标记就行了\nfunction M:New() ---@class Circle: GameObject local o = utility.gameObject:New(\u0026#34;Circle\u0026#34;) 像这样，以下代码便能用过lua-ls补全\nlocal player = utility.gameObject:Find(\u0026#34;Player\u0026#34;); ","date":"29 March 2025","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91lua%E7%9A%84class%E8%A1%A5%E5%85%A8/","section":"Posts","summary":"最近写Lua的时候偶然发现可以通过文档注释然后为拟定成Class的Table进行补全。","title":"游戏开发：Lua的Class补全","type":"posts"},{"content":"因为突然想尝试一下以HTML的Canvas为基础做一下游戏开发！所以就开始做了并且写下这篇文章记录一下\n一开始为了能在Canvas上画出图形，直接在HTML里面写了JavaScript，后面为了模块化开发转成单文件。不过最开始还是写JavaScript的，因为要在画布上画东西需要Get到Canvas，所以需要document.getElementByID，但是由于JavaScript是弱类型，document.getElementByID不但不会隐式返回一个类型，而且没法标注类型，导致接下来写getContext(\u0026quot;2d\u0026quot;)的时候IDE/代码编辑器不会提示。因为以上原因所以毅然换了TypeScript。\nTypeScript遇到的第一个问题是直接写let canvas:HTMLCanvasElement = document.getElementByID会报错，因为getElementByID依赖不会返回一个HTMLCanvasElement类型，所以需要手动as HTMLCanvasElement\n然后就是tsc编译完js整个ts文件会红，这个写个tsc init生成一个tsconfig文件就能解决了。\n之后的大部分时间在研究分文件写之后为什么会一直在js文件里会出现exports和require的代码，这是js所不支持的写法。解决方法就是把tsconfig中的module改成es6，target也改成es6。有可能会因为tsc的缓存没有清除所以一时不会发生改动。可以清除以下tsc的缓存文件或者使用tsc --module es6\n注意import的时候路径后面要加一个.js，就算这是ts文件，如\nimport { Render } from \u0026#34;./render.js\u0026#34;; 不然会在编译到js后找不到文件： 已拦截加载自“http://127.0.0.1:5500/public/ui”的模块，它使用了不允许的 MIME 类型（“text/html”）。\n上面说使用了不允许的MIME类型，实际上是因为找不到文件而返回了404ERROR，而404ERROR是HTML不是JavaScript文件所以导致返回了使用了不允许的MINE类型。\n\u0026lt;!-- index.html --\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script type=\u0026#34;module\u0026#34;\u0026gt; import * as main from \u0026#39;./public/main.js\u0026#39;; \u0026lt;/script\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;canvas width=\u0026#34;800\u0026#34; height=\u0026#34;600\u0026#34; id=\u0026#34;canvas\u0026#34;\u0026gt;\u0026lt;/canvas\u0026gt; \u0026lt;/body\u0026gt; 以上是HTML部分的部分代码，用这种方式去加载main.js文件，因为加入了export和import的代码已经模块化了，再在里面写function并不在全局作用域内。\n// main.ts import {UI} from \u0026#34;./ui.js\u0026#34;; import { Render } from \u0026#34;./render.js\u0026#34;; export class Game { private x: number; private render: Render; private ui:UI; constructor() { this.x = 10; this.render = new Render(); this.ui = new UI(); setInterval(() =\u0026gt; this.Update(), 1000 / 60); } Draw():void { if (this.render.ctx) { this.render.ctx.clearRect(0, 0, 800, 600); this.render.ctx.font = \u0026#34;20px serif\u0026#34;; this.render.ctx.fillText(\u0026#34;random text\u0026#34;, 10 + this.x, 10) this.ui.Draw(); } } private Update():void { this.x += 1; this.Draw(); } } new Game() 很显而易见的为了模块化我们给每个文件起了一个class，与此同时在顶上和构造函数里面进行初始化。\n构造函数中的最后一行\nsetInterval(() =\u0026gt; this.Update(), 1000 / 60); 是为了设定主循环，60每帧，中间用箭头函数是因为箭头函数不会创建自己的this，而是从外部作用域捕获this的值。因此，可以使用箭头函数来解决这个问题。\n// render.ts export class Render { public canvas: HTMLCanvasElement; public ctx: CanvasRenderingContext2D; constructor() { this.canvas = document.getElementById(\u0026#34;canvas\u0026#34;) as HTMLCanvasElement this.ctx = this.canvas.getContext(\u0026#34;2d\u0026#34;) as CanvasRenderingContext2D; } } render.ts中单独设置了画布的渲染，要用到draw的部分调用render.ts。\n我想这样一个基础的游戏框架就搭建好了。\n","date":"13 November 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E4%BD%BF%E7%94%A8%E7%BA%AFtypescript%E7%9A%84%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/","section":"Posts","summary":"因为突然想尝试一下以HTML的Canvas为基础做一下游戏开发！所以就开始做了并且写下这篇文章记录一下","title":"使用纯TypeScript的游戏开发","type":"posts"},{"content":"其实我已经用vscode写了好几个love2d游戏了，我自己的库也在不断完善。\n但每次要写love2d的时候就会有很多代码以外的事情打扰我，比如说国内经典的网络问题，还有关于配置lsp上面。\n所以我想尽可能出一期博客把之前踩过的坑记录一下。\n首先为了在vscode里面写love2d，我们需要love2d库的lsp，这个你或许可以通过直接导入love2d的包的代码来做到直接在lua的基础上引用，能不能行得通我不知道至少这是一种合理的假设。\n那么我自己是用sumneko的lua vscode插件。然后使用lua addon manager添加了一个love2d的lsp支持。开启很简单，ctrl + shift + p后输入\u0026gt; lua addon manager就能打开addon manager的GUI界面，找到love2d一栏，enable之后他就会自动从github远程仓库帮你拉取love2d相关的代码保证能够提示。另外会自动生成一个setting.json（你项目文件夹的）写上一些配置文件。\n那么在国内应该会很频繁的会遇到连不上github的情况，关于vscode配置代理我也一直是一头雾水，你或许可以通过设置里面搜索proxy相关把你所用代理的ip + 端口写上去。但我之前这样做似乎没啥效果。如果不行可以通过修改settings.json（appdata里面的）配置文件，自己写上\u0026quot;http.proxy\u0026quot; : \u0026quot;http://127.0.0.1:7890\u0026quot;之类东西。其实是一个配置方法。\n如果修改settings.json没有用，可以通过更改启动方式。之前看到过这篇文章，在这里引用：https://suiahae.me/Set-proxy-for-Visual-Studio-Code-on-Windows/#:~:text=%E4%B8%BAVisual%20St\n如果log信息里面还是timeout那只能自己在想办法了，可以先排查一下是不是没代理上。\n如果一切正常那大概是正常了，如果不太正常，比如没法取消enable啥的，那么把setting.json（项目文件夹的）里面的lua.workspace.checkThirdParty true了之后就大部分正常了。切记不要删除love2d addon文件夹里面的info.json，这会导致sumneko lua插件无法从github上拉取内容。\n另外，如果你使用scoop下载了lua，这会导致scoop的lua环境会覆盖掉其他lua程序的环境，比如说你下的用love2d做的游戏，特别是用luasteam这个库的。之前因为想玩Balatro但是因为它老是找到scoop的lua让我很为难，因为scoop的环境下没有luasteam。包括如果你以后要上steam，使用luasteam的时候也会因为这个问题无法运行你的程序。所以不需要配置lua在系统里面也能写lua程序。\nlinux也是同理，如果你用包管理器安装了lua，这也会导致无法正常运行使用luasteam的游戏。\n另外，推荐在sumneko的配置里面把lua环境改成luajit，因为love2d使用luajit（不知道原理是什么但是更快一些），新版本的lua支持位运算，正因如此可能会和一些库冲突（比如ldtk的导入库使用了位运算），也有能使用位运算的jit库。如果使用了5.4的语法，这会导致love的web端导出，love.js会出现问题。\n","date":"18 October 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%BD%BF%E7%94%A8vscode%E5%86%99love2d/","section":"Posts","summary":"其实我已经用vscode写了好几个love2d游戏了，我自己的库也在不断完善。","title":"游戏开发：使用vscode写love2d","type":"posts"},{"content":"今天兴冲冲的逃了课回寝室准备打电动，发现电脑开不了机。看了一眼caps lock按了上面的灯没有反应，可能不是屏幕问题。\n拿去学校某个修电脑的地方，老板和我说是静电问题，没有完全关机合盖就会出现这样的问题，长按电源键5s后Dell的logo又亮了起来。\n我非常高兴的直接把电脑装进了包里带回寝室，发现键盘发出耀眼的橙色的光芒，而屏幕确黑到映射出了我的丑陋容貌。\n重复老板讲述的步骤之后发现没有什么用，我抱着一定要在那边开机不合盖带回来的想法抱着电脑不带包过去了。\n老板正在低头修理其他人的电脑，抬头看到我来了，再看到我手上的电脑，向他说明状况后，“你是不是又忘了关机了”。\n“啊，对对对”。\n我向老板借了螺丝刀，开始按照网络上的土方子排除问题。（网络土方子：https://www.bilibili.com/read/cv13753319/）\n把后盖拆开后（四个螺丝可以拧下其他不行，好像是一样的但是为了避免出问题我还是单独标记了），把电池和主板连接的带子拔掉，长按开机键数次，再接上带子，查看是否能正常亮，重复几次之后还是不行。\n老板建议我把内存条拔下来再插进去看看，照做之后发现居然成了，亮了之后Windows11居然在偷偷更新，而且画面渲染还卡了几分钟。\n重新把后盖合上以后，本想就这样开机后走回寝室发现电脑又卡在logo显示的界面。重启之后端着电脑走回了寝室。（我的手承重不了会断掉）\n","date":"9 September 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E7%94%B5%E8%84%91%E7%BB%B4%E4%BF%AEdellg15%E4%B8%A4%E9%BB%84%E4%B8%80%E7%99%BD%E7%94%B5%E8%84%91%E9%BB%91%E5%B1%8F%E9%9D%99%E7%94%B5%E9%97%AE%E9%A2%98/","section":"Posts","summary":"今天兴冲冲的逃了课回寝室准备打电动，发现电脑开不了机。看了一眼caps lock按了上面的灯没有反应，可能不是屏幕问题。","title":"电脑维修：Dell G15 两黄一白电脑黑屏静电问题","type":"posts"},{"content":"最近在给scoop上的软件换到cn源上，无意之中发现曾经装过komorebi（windows上的wm）\nwm(Window Manager)-名词-窗口管理器\n然后一时兴起，想重新整个wm玩玩\nkomorebi的使用很简单，只要在shell里面输入komorebic start --whkd就能使用wm了，在这之前请按照github上说的安装。\n除了Window Manager本身我们还需要一个topbar，来查看各种状态信息和当前所在的工作区(workspace)\n笔者这里使用的是yasb\nyasb坑比较多，而且开发者十个月没有更新了，pr里面有很多恶性bug的修复还没有被合并\n这边放出个人踩到的一些坑：\nPython要使用3.10版本的 PyQt6和PyQt6-Qt6需要一个版本，如果版本不一致先用pip卸载PyQt6-Qt6然后安装与PyQt6统一版本的PyQt6-Qt6 配置文件config.yaml，在traffic下面的type和options前面加四个空格 如果一切正常的话那应该是正常了，接下来在C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup里添加启动脚本就可以了，根据各自不同的shell编写脚本\n文件配置上没什么好说的，根据个人喜好配置就行\n另外使用这个ahk脚本隐藏桌面任务栏：https://www.bilibili.com/read/cv27693381/\n","date":"1 September 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E5%9C%A8windows%E4%B8%8A%E4%BD%BF%E7%94%A8wm/","section":"Posts","summary":"最近在给scoop上的软件换到cn源上，无意之中发现曾经装过komorebi（windows上的wm）","title":"在Windows上使用wm","type":"posts"},{"content":"最近Github需要开启2FA双端验证，但由于在中国没法发送SMS短信验证，同时Github上推荐了三个身份验证软件。\nhttps://support.microsoft.com/zh-cn/account-billing/%E5%9C%A8%E4%B8%AD%E5%9B%BD%E4%B8%8B%E8%BD%BDmicrosoft-authenticator-ebbef05c-a429-4236-8570-1bb1900fec35\n阅读了微软这篇文章后知道可以从联想，OPPO或者三星的应用商店下载Microsoft Authenticator\n虽然我的手机是IQOO但好像也能下\n下载完Microsoft Authenticator后登录一下微软帐号\n扫一下确认2FA的二维码，输入一次性密码就可以了\n需要注意保存一下还原代码\n","date":"30 August 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E5%9C%A8%E4%B8%AD%E5%9B%BD%E5%BC%80%E5%90%AFgithub2fa%E9%AA%8C%E8%AF%81/","section":"Posts","summary":"最近Github需要开启2FA双端验证，但由于在中国没法发送SMS短信验证，同时Github上推荐了三个身份验证软件。","title":"在中国开启Github 2FA验证","type":"posts"},{"content":"这周参加了GMTK GAMEJAM 2024，是个平台跳跃，游戏内出现了如果卡顿就会卡墙里或者浮空的BUG，经过我仔细研究发现这和我在和时间有关的变量后面都乘上了一个deltatime。\n但是不这么做是不行的，因为每个人的机子响应时间不同而且屏幕刷新率也不同，为了保证相同时间内执行的效果一样（比如玩家的移动速度），所以需要乘上一个deltatime补上这一部分时差。然后deltatime由于是上一帧逻辑与当前逻辑的实际现实时间差，所以如果中间有卡顿这个deltatime就会变大。一旦窗口freeze了一段时间，玩家就顺其自然的掉出去了。\n又多亏了我另外两个好哥们（TML和域外），我现在掌握了使用tick时钟进行游戏逻辑帧的处理。\n具体原理是让一秒内处理相同的时间逻辑。因为deltatime经过一秒后整数部位会来到1，所以通过限制一帧内deltatime \u0026gt; 1/一秒内执行的逻辑次数，使deltatime等于1/一秒内执行的逻辑次数，就能让tick进行相对恒定的逻辑帧处理。\n以下代码设定一秒内执行60次tick，即60次逻辑执行，并且恒定。\nlocal totalTime, elapsedTime = 0, 0 -- \u0026lt; function love.update(dt) totalTime = totalTime + dt -- \u0026lt; DrawEvent:Update(); while totalTime - elapsedTime \u0026gt; 1 / 60 do -- \u0026lt; dt = 1 / 60 -- \u0026lt; if self.scenes[self.currentScene].Update ~= nil then self.scenes[self.currentScene]:Update(dt) end; gameObject:ObjectInitUpdate(dt); love.keyboard.updateKeys(); elapsedTime = elapsedTime + 1 / 60 -- \u0026lt; end -- \u0026lt; end ","date":"21 August 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%B8%B8%E6%88%8F%E9%80%BB%E8%BE%91%E5%A4%84%E7%90%86%E6%97%B6%E9%92%9F/","section":"Posts","summary":"这周参加了GMTK GAMEJAM 2024，是个平台跳跃，游戏内出现了如果卡顿就会卡墙里或者浮空的BUG，经过我仔细研究发现这和我在和时间有关的变量后面都乘上了一个deltatime。","title":"游戏开发：游戏逻辑处理时钟","type":"posts"},{"content":"Love2d要导出网页文件需要使用love.js，但是love.js自带了一个看上去很丑陋的外框使其在上传的itch的时候非常不契合\n多亏Love2d Discord的哥们\nSheepolution: The default html that love.js provides is really bad for uploading to itch. You can remove the title and such to make it properly fit the screen.\n为了移除title和footer，需要手动编辑love.js生成的html文件\n删除或注释开头的\u0026lt;h1\u0026gt;标签和最底下的\u0026lt;footer\u0026gt;标签即可\n","date":"21 August 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%BC%98%E5%8C%96love.js%E7%9A%84%E7%95%8C%E9%9D%A2%E9%80%82%E9%85%8Ditch.io/","section":"Posts","summary":"Love2d要导出网页文件需要使用love.js，但是love.js自带了一个看上去很丑陋的外框使其在上传的itch的时候非常不契合","title":"游戏开发：优化Love.js的界面适配itch.io","type":"posts"},{"content":"前段时间电脑坏了，拿去修的这段时间就用了几天自己的300块钱的Linux平板。\n这是一台Surface3，配置很差，而且出于未知原因老是死机，初步推测是硬件不兼容。但是也不得不拿来用用了。\n这几天首先是在itch上玩各种小游戏比如Hempuli的Mountlike推箱子系列，Thinky Puzzle GameJam#3的游戏。后来把我的数位板(Wacom)接上发现也能拿来画画(Krita)\n不过我也想试试解决我写代码的需求，于是试了试Neovim。\n刚刚Vscode不知道为啥不让我回车和删除内容，硬控我几分钟\n由于配置很低，就也用了低配置的游戏框架，Love2d\n那么Love2d只需要main.lua文件就能运行了，主要问题是怎么给Neovim配置Lua和Love2d的LSP\n草又被硬控了\n算了临时换了Joplin继续写，可惜用不了我的alt+hjkl伪vim-key了（因为vim对中文支持不好）\n所以既然需要LSP，首先需要考虑到Love2d的，我用的是这个：https://github.com/S1M0N38/love2d.nvim\n你可以直接按照上面说的用nvim-lspconfig去配置你的lua lsp，当然这也是nvim默认的方案，不过我用了其他的方法。\n首先插件管理器我用的是VimPlug，通过在init.vim中输入链接可以直接自动下载插件。https://github.com/junegunn/vim-plug\n当然init.vim和init.lua是冲突的，选择一种喜欢的插件管理方式就好了。\n然后用coc.nvim管理lsp：https://github.com/neoclide/coc.nvim\nlua推荐使用sumneko/lua-language-server：https://github.com/LuaLS/lua-language-server\n其他语言的lsp可以在coc.nvim的wiki中找到并配置\n根据readme上面的简要步骤安装好之后应该就可以自动载入Love2d的插件了，根据自己的需要进行选择\n","date":"10 July 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%BD%BF%E7%94%A8neovim%E5%86%99love2d/","section":"Posts","summary":"前段时间电脑坏了，拿去修的这段时间就用了几天自己的300块钱的Linux平板。","title":"游戏开发：使用Neovim写Love2d","type":"posts"},{"content":"上回说到，我用Hugo + Github Page搭建了一个个人博客小站，昨天有朋友想让我加个评论区，于是我就加了一下。\n因为我使用的是Blowfish主题，所以我先看了一眼Blowfish的文档，https://blowfish.page/docs/partials/#comments\n上面说甚么\nTo add comments to your articles, Blowfish includes support for a comments partial that is included at the base of each article page. Simply provide a layouts/partials/comments.html which contains the code required to display your chosen comments.\n之类\n可我在Blowfish的Github仓库找了些许会没能找到甚么comments.html，应该是要自己加一个。\n通过Hugo提供的disqus方法，可以先把disqus.html下载下来，然后重新命名为comments.html放到项目根目录的layouts/partials/\n之后需要注册一个disqus帐号，注册完以后选择右边那个选项，即install disqus for my site之类的。\n处理好之后把disqus的shortname抄下来，放到hugo.toml里面，Hugo文档有教程不多赘述了。\n需要注意的地方是这个shortname不是用户名，Configure Disqus for Your Site里面能找到\n听哥们说用utterances的话可以拿github仓库的issue当评论区，充分利用本地资源。\ndisqus的这个方法则是拿disqus作为数据库。\n","date":"27 June 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/web%E4%B8%BAhugo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E8%AF%84%E8%AE%BA%E5%8C%BA/","section":"Posts","summary":"上回说到，我用Hugo + Github Page搭建了一个个人博客小站，昨天有朋友想让我加个评论区，于是我就加了一下。","title":"Web：为Hugo博客添加评论区","type":"posts"},{"content":"\r前言 #\r很久一段时间没有写博客了，或者说今年只写了几篇。和一个哥们聊了一会发觉要把所做的事情给记录下来，能发网上当赛博遗产也不错。其中比较受这篇文章影响如何搭建知识库\n又由于觉得不应该依赖于国内主流网站如知乎，所以决定要自己搭建一个Github Page。\n哥们推荐了Hugo，就去看了一下。\n搭建过程 #\r首先要安装Hugo，我用的是scoop包管理器安装的，官网也有其他包管理器以及其他的安装方法。https://gohugo.io/getting-started/quick-start/\n因为要挑选一个theme，我便从支持的列表里面找了一个好看的，最后相中了Blowfish\n所幸的是Blowfish非常的方便有个CLI工具可以快速搭建不用修改文件。https://blowfish.page/docs/installation/\n按照Hugo官方的quick-start教程走完并且用git submodule的方法安装了Blowfish主题后，试着跑了一下，虽然什么都没有但是有Blowfish的theme了。\n之后把素材之类的东西从Blowfish theme的目录下复制过来才能用，比如config和assets。\n为了方便本地调试复制过来后根目录下的baseURL先不用填，会自动定位本地根目录下的资源文件，如果填了编译之后将无法获取本地资源而是还没搭建好的远程资源。\n由于这是一个github page的子项目，url大概类似feishiko.github.io/feishiko-blog-page。\n用hugo指令打包后，发布的版本位于项目根目录的public文件夹，进入public文件夹并git init，完成git仓库初始化，然后上传到远程仓库，我用的是github，并且用了github desktop，非常方便非常简单非常快捷的就上传好了。\n这里做一个提醒，你可以直接把项目文件上传到github并且用hugo的action在线编译，由于theme是submodule并不会上传到你的github仓库而是作为一个指针远程参与编译。https://gohugo.io/hosting-and-deployment/hosting-on-github/\n上面的链接步骤到第四步是一样的\n因为我是打包的静态网页，所以action用了static一开始。嗯，就算用hugo的action好像也不用像官网那样自己写一个，现在的github似乎是有hugo的预设的。\nworkflow写好或生成好后，等待部署就能看到自己的网页了。\n现在还有一个问题，就是要把实际的网页链接复制到baseURL上，不然会出现资源定位错误的情况。\n目前我不知道要怎么把图像资源定位到博客项目的仓库，可能和我图像地址写的../assets/img/xxx.png有关，不过暂时懒得去考虑了。\n所以有点麻烦的我现在图像资源在feishiko.github.io所属仓库也放了几张，通过F12查看项目文件应该能定位到博客项目的img资源。\nBlowfish主题自带的搜索没法用，我想应该和github page没有提供相关支持有关，之后换了这个action想看看能不能支持搜索依然不行。https://github.com/marketplace/actions/upload-github-pages-artifact\n关于创建文章而言没什么好说的，博客统一的资源定位在content/posts，使用Blowfish的CLI工具或Hugo创建一个文章，同时拟定文件名以及标题（标题可以在里面继续改），会在文件开头生成一些metadata，通过修改日期等数据可以控制网站达到预期的效果。draft设置成true的时候在正式打包并不可见，预览的时候可以通过hugo server -D将draft可见。正式打包可将draft改成false以正式发布。\n如果看不到自己创建的博客，需要让博客显示出来，创建menus或显示最近博客都行。\n踩坑 #\r其实上面或多或少都讲到了可能的坑，毕竟我一路做下来确实没那么顺利。\n顺便一说飞鱼子用的shell是NuShell，在设置环境变量上有点小问题，后面发现可以直接pwsh -command使用powershell的指令。\n如上面说的我确实遇到了资源定位的问题，一开始用的也是static的workflow，但之后重新部署了反而就正常了这一点还不是很清楚。\n每次push后要等待几分钟，效果不会那么快显现出来。\njpeg格式的图片可能有问题，如果不行就用ffmpeg把格式改成png的即可。\n最后效果：https://feishiko.github.io/feishiko-blog-page/ 并且把知乎之前写的文章也搬了上来，有几个早期文章用的github的图床但仓库被我删了，不知道会不会补回来。\n","date":"23 June 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/web%E4%BD%BF%E7%94%A8hugo%E5%92%8Cgithub-page%E5%88%9B%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/","section":"Posts","summary":"前言 #\r很久一段时间没有写博客了，或者说今年只写了几篇。和一个哥们聊了一会发觉要把所做的事情给记录下来，能发网上当赛博遗产也不错。其中比较受这篇文章影响如何搭建知识库","title":"Web：使用Hugo和Github Page创建个人博客","type":"posts"},{"content":"孔乙己是站着喝酒而穿长衫的唯一的人。他身材很高大；青白脸色，皱纹间时常夹些伤痕；一部乱蓬蓬的花白的胡子。穿的虽然是长衫，可是又脏又破，似乎十多年没有补，也没有洗。他对人说话，总是满口之乎者也，教人半懂不懂的。因为他姓孔，别人便从描红纸上的“上大人孔乙己”这半懂不懂的话里，替他取下一个绰号，叫作孔乙己。孔乙己一到店，所有喝酒的人便都看着他笑，有的叫道，“孔乙己，你脸上又添上新伤疤了！”他不回答，对柜里说，“温两碗酒，要一碟茴香豆。”便排出九文大钱。他们又故意的高声嚷道，“你一定又偷了人家的东西了！”孔乙己睁大眼睛说，“你怎么这样凭空污人清白……”“什么清白？我前天亲眼见你偷了何家的代码，吊着打。”孔乙己便涨红了脸，额上的青筋条条绽出，争辩道，“窃码不能算偷……窃码！……程序员的事，能算偷么？”接连便是难懂的话，什么“自由软件”，什么“开源”之类，引得众人都哄笑起来：店内外充满了快活的空气。\n听人家背地里谈论，孔乙己原来也写过代码，但终于拿不到Offer，又不会营生；于是愈过愈穷，弄到将要讨饭了。幸而码风优良，便替人家做做外包，换一碗饭吃。可惜他又有一样坏脾气，便是好喝懒做。坐不到几天，便连人和Git远程仓库，一齐失踪。如是几次，叫他外包的人也没有了。孔乙己没有法，便免不了偶然做些偷窃的事。但他在我们店里，品行却比别人都好，就是从不拖欠；虽然间或没有现钱，暂时记在粉板上，但不出一月，定然还清，从粉板上拭去了孔乙己的名字。\n孔乙己喝过半碗酒，涨红的脸色渐渐复了原，旁人便又问道，“孔乙己，你当真会写代码么？”孔乙己看着问他的人，显出不屑置辩的神气。他们便接着说道，“你怎的连半个Offer也捞不到呢？”孔乙己立刻显出颓唐不安模样，脸上笼上了一层灰色，嘴里说些话；这回可是全是函子范畴之类，一些不懂了。在这时候，众人也都哄笑起来：店内外充满了快活的空气。\n在这些时候，我可以附和着笑，掌柜是决不责备的。而且掌柜见了孔乙己，也每每这样问他，引人发笑。孔乙己自己知道不能和他们谈天，便只好向孩子说话。有一回对我说道，“你写过代码么？”我略略点一点头。他说，“写过，……我便考你一考。三目运算符，怎样写的？”我想，讨饭一样的人，也配考我么？便回过脸去，不再理会。孔乙己等了许久，很恳切的说道，“不能写罢？……我教给你，记着！这些代码应该记着。将来做HR的时候，考人要用。”我暗想我和HR的等级还很远呢，而且我们HR也从不将三目上账；又好笑，又不耐烦，懒懒的答他道，“谁要你教，?:么？”孔乙己显出极高兴的样子，将两个指头的长指甲敲着柜台，点头说，“对呀对呀！……三目有四样写法，你知道么？”\ncondition ? true : false 多种支持三目的语言中常见的形式如C或C#，非常方便的节省了一定的时间复杂度和空间复杂度，非常优雅的解决了问题\n// C# Console.WriteLine(3 \u0026gt; 5 ? \u0026#34;Bigger\u0026#34; : \u0026#34;Smaller\u0026#34;) condition and true or false 不支持标准三目的语言中能使用的形式，又叫逻辑短路，在运算优先级上有一定小问题但大多数情况下能正常使用\n-- Lua print(3 \u0026gt; 5 and \u0026#34;Bigger\u0026#34; or \u0026#34;Smaller\u0026#34;) true if condition else false Python特有的条件状语从句写法，This will be true if achieve this condition, else will be this kind of thing\u0026hellip;，比起正常写三目而言会有点不符合直觉，但是非常符合自然语言的直觉，Python的丑态\n# Python print(\u0026#34;Bigger\u0026#34; if 3 \u0026gt; 5 else \u0026#34;Smaller\u0026#34;) 没有:也是一种非丑态的语句\nif condition {true} else {false} 不支持任何三目写法的情况下使用直接的条件判断也是一种三目，比如Rust，剥离了最核心的内容，剔除了其他的糟粕\n// Rust if 3 \u0026gt; 5 {println!(\u0026#34;Bigger\u0026#34;)} else {println!(\u0026#34;Smaller\u0026#34;)} 我愈不耐烦了，努着嘴走远。孔乙己刚用指甲蘸了酒，想在柜上写字，见我毫不热心，便又叹一口气，显出极惋惜的样子。\n","date":"22 June 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E7%BC%96%E7%A8%8B%E4%B8%89%E7%9B%AE%E6%9C%89%E5%9B%9B%E6%A0%B7%E5%86%99%E6%B3%95%E4%BD%A0%E7%9F%A5%E9%81%93%E4%B9%88/","section":"Posts","summary":"孔乙己是站着喝酒而穿长衫的唯一的人。他身材很高大；青白脸色，皱纹间时常夹些伤痕；一部乱蓬蓬的花白的胡子。穿的虽然是长衫，可是又脏又破，似乎十多年没有补，也没有洗。他对人说话，总是满口之乎者也，教人半懂不懂的。因为他姓孔，别人便从描红纸上的“上大人孔乙己”这半懂不懂的话里，替他取下一个绰号，叫作孔乙己。孔乙己一到店，所有喝酒的人便都看着他笑，有的叫道，“孔乙己，你脸上又添上新伤疤了！”他不回答，对柜里说，“温两碗酒，要一碟茴香豆。”便排出九文大钱。他们又故意的高声嚷道，“你一定又偷了人家的东西了！”孔乙己睁大眼睛说，“你怎么这样凭空污人清白……”“什么清白？我前天亲眼见你偷了何家的代码，吊着打。”孔乙己便涨红了脸，额上的青筋条条绽出，争辩道，“窃码不能算偷……窃码！……程序员的事，能算偷么？”接连便是难懂的话，什么“自由软件”，什么“开源”之类，引得众人都哄笑起来：店内外充满了快活的空气。","title":"编程：三目有四样写法，你知道么？","type":"posts"},{"content":"看到今年CUSGA（中国大学生游戏开发大赛）有一个解谜游戏画面很不错，谜题设计也很优秀，之后发现是一个人做的。\n比起其他人的游戏，他的游戏更有种统一的美术风格。而且，对于小众类型逻辑解谜而言，并不好找到同好一起来制作。\n游戏需要分裂自身，去构建一个刚好能构成两个物体达成结果检测。开始的关卡有一定的引导属性，之后开始出现一些比较线性代数的概念如旋转与缩放，再者出现从一侧进去从反方向出来的机制之后，格子的奇偶性也成了Puzzle Solving的关键。\n由于是个Grid-Based加上Turn-Based，对于空间的逻辑能力会非常敏感，需要恰好构建一个刚好的结构去适应结果。游戏整体难度并不轻度，因为卡关了所以没全打完。\n同为学生的独立游戏开发者鸵鸟居士对他的评价：\n这种级别的自洽和一体性，不是solo，不是左脑跟右脑配合，是做不出来的。机制谜题关卡，配色场景构图，浑然一体。如果不是一个人做的，一定会有撕裂感。\n如果你对这种Grid-Based Turn-Based的逻辑解谜感兴趣可以尝试一下，Parallel的游戏链接：https://xexlax.itch.io/parallel\n这种统一的风格除了xexlax的Parallel，还有不少游戏在这个范畴内。\n这是独立游戏开发者流贾君的新游戏，超级滑刃战士，目前游戏还没开始发售，不过已经有了Steam界面。\n流贾君是一位成熟的独立游戏开发者，而且也是一位个人开发者，它的美术比起很多个人开发者要好，而且它的游戏画面也做到了统一性。\n超级滑刃战士Steam链接：https://store.steampowered.com/app/2847740/Super_Drift_Blade/\n当然不止超级滑刃战士，他过去的很多游戏都保持了一贯的风格：\n《窗户之外》\n《平衡100》\n对于美术好的游戏看过了，不妨现在再看一些美术“差”的游戏\n《A=B》\n《Understand顿悟》\n《蜡笔物理学》\n这些游戏也都是一个人开发的，比起美术更重视玩法的逻辑解谜领域开发者，Artless Games。\n它的游戏或许没有精致的美术，但是它的游戏能引来无数解谜游戏玩家的称赞，因为它是切实去做好逻辑解谜这个类型的。\n或许这无法被常人所理解，但是这个世界上永远有一些小众的文化群体，小众的社群，而Artless的用户群体就是它们。不一定要做热，不一定要趋于大众，但一定要在小范围内做好。这是游戏风格的统一性。\n拿《A=B》举例，虽然界面比起Zachtronics的《SHENZHEN I/O》甚至《TIS-100》简陋，但是你可以在上面的窗口去测试用例，代码编辑器有基本的撤销功能，一个问题有三个档案供选择和调试，调节字体大小，步进和快速运行。这些在玩法上做到了尽可能的人性化。\nA=B Steam链接：https://store.steampowered.com/app/1720850/AB/\n国内的开发者聊完了，接下来聊点国外的开发者。\n《SpelunkyRL》\n《Monster Trainer RL》\n《CvRL2: The Lady of Berkeley》\n《Emerald Woods》\n这是Roguebasin的站长，忠于传统Roguelike游戏开发者，Slashie，他的游戏代码部分都是自己写的，也是自己策划的，但音乐还有美术会寻求外部力量。\n他的游戏绝大部分都是Roguelike，对于现在的很多只玩Hades，Slay the Spire，Binding of Isaac等Roguelite还无法区分like和lite的年轻人来说，Roguelike是个很难接受的类型。\n作为一个有20余年的游戏开发者和Roguelike玩家，Slashie的游戏阅历丰富至极。因此他的游戏也深受Roguelike玩家的喜爱。\nSlashie的个人网站链接：https://slashie.net/\nPlatformer也是个很有趣的类型\n《Love》\n《Nymph\u0026rsquo;s Tower》\nBrlka是一个专注于平台跳跃游戏类型的开发者，虽然笔者喜欢精确平台，但他的游戏也非常对胃口。\nPlatformer也算是个比较小众的类型，特别是精确平台，玩家需要一遍又一遍的尝试，一般难度上会对玩家素质有一定要求。这种负面反馈大于正面反馈的游戏体验放到2024年自然是不会吃香的，当然Thinky Puzzle即逻辑解谜和传统Roguelike也是一样的。\nLove就是这样的一个精确平台游戏，没有华丽的画面，只有朴素的一次又一次的尝试。当你成功攻克目前的关卡到达下一个存档点的这种喜悦，是难以描述的。这也让我想起很多I wanna fangame，特别是Gimmick跳刺和综合。\nBrlka的itch链接：https://brlka.itch.io/\n在文章的最后，我认为不仅仅是作为一个独立游戏开发者，更是应该作为一个玩家。游戏开发者来源于玩家，应该更多的去融入自己喜欢的社区，去找到自己游戏的受众。\n写到最后，发现自己不但写了美术风格上的统一，还写了游戏内容的统一。\n作为个人独立游戏开发者，我们有自己的情感，我们有自己想去表达的内容，我们能够分辨出一个完整的物体。这是团队开发难以做到的，特别是小众类型。\n玩的游戏不一样，难免会有分歧。就算同是平台跳跃，玩蔚蓝的和玩马里奥的也会有很多分歧。\n如果你有一个能和你一起共享喜怒哀乐的团队，大家能够组合成一个整体完成各自的分工，请珍惜他们，这是难能可贵的。\n","date":"11 May 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E7%8B%AC%E7%AB%8B%E6%B8%B8%E6%88%8F%E4%B8%8E%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91%E8%80%85/","section":"Posts","summary":"看到今年CUSGA（中国大学生游戏开发大赛）有一个解谜游戏画面很不错，谜题设计也很优秀，之后发现是一个人做的。","title":"游戏开发：独立游戏与个人开发者","type":"posts"},{"content":"最近参加了Gloabl Game Jam，本来是负责画素材做音乐音效关卡设计和文案的，结果第一天就全部做完了。第二天在程序的提议下帮他写了一些代码，不过还好写了，不然游戏就做不完了。\n这次游戏用的是Cocos Cretor，在这之前没碰过TypeScript和Cocos Creator，花了一点时间就上手了。首先我是有游戏开发经验的，然后Cocos Creator比较完备，有很多开发应该有的工具，以及使用起来比较简单，所以才能那么快的就开始着手项目。\n相比之下，我用了好几天的Love2d还只是一直在造轮子，虽然只是偶然想到，但我觉得有拿出来对比的意义。\n就在刚刚，水群的时候看到群友说了一句\n少造轮子\n顿时有感写下此文\n作为游戏开发者，做游戏其实才是首要目的，而不是一直在写工具类。\n我现在把以后的路铺好了，这样就走的舒服了\n如果有这样的想法，还是最好不要有。\n你轮子还没写完，别人游戏已经做完了，这就是差别。\n所以我想这也是所谓游戏引擎(Game Engine)和游戏框架(Framework)的区别，所谓游戏引擎有个IDE，里面有一套可视化的工具，场景编辑器，资源管理器，或者去快速创建一个游戏引擎里面给的类型，又或者是去更方便的调整某些东西（UI、Shader、粒子效果）。\n所谓游戏框架没有IDE，需要调用lib所给的能用的api，图形绘制方面的，音频方面的，实现成熟游戏引擎已经包装好的功能。\n所谓造轮子，除了在Framework里面，还有在Game Engine里面的，比如Godot的节点，很多其实没啥用，所以需要自己写一些东西，又或者是GameMaker Studio，没有所谓的UI组件，需要自己绘制。\n那么自然，有工具给你为什么不用？为什么要造轮子，可靠的理由很简单，那就是原来的不好用，或者没法用，需要自己整点用的舒服的。\n所以很自然的，在2024年，至少需要在你不能使用某个东西的时候你才有造轮子的需求，才是合理的。\n换一种思路，就是说\n我是不得不造轮子的\n毕竟工具的产生，就是为了方便人的。我现在在给Love2d写东西的理由也很简单，因为我在用的工具满足不了我，所以我会选择Love2d，但Love2d没有这些功能，所以我需要自己造轮子，来确保能用的上。\nGameMaker Studio的IDE很蠢很卡还用不了外部代码编辑器，Godot Mono的Windows空包能到130MB还导不了Web而且有很多多余的功能，Cocos Creator的打包有很大问题（Windows和Web缺失的资源不一样，Web有概率黑屏，Web有概率出现资源加载不完全的情况），Unity不想用。\n现有的比较流行的游戏引擎已经满足不了我了，所以我会寻求一些Framework去客制化我要的东西。\n但是这样损失的是什么？是我自己。\n毕竟没有完美无缺的东西，GameMaker Studio的API设计不错而且产出快，Godot写的舒服，Cocos Creator写的时候也比较舒服。\n所以我在想，有时候学会将就一下也是提升产出的一种方式，而且不只是游戏引擎。\nLove2d我不写那些东西，一样能做游戏，一定要去实现一套现代游戏引擎有的东西？不，根本不需要，完全可以直接上，而且Lua面向过程能写的很舒服，没必要闭包oop写个半天。\n想起自己曾经用Tic-80做过一个sokoban-like，也是在没写任何轮子的情况下一个星期做完了。\n不过写都写了，是时候也要验证一下能不能用了。\n","date":"30 January 2024","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E7%9A%84%E6%84%8F%E4%B9%89/","section":"Posts","summary":"最近参加了Gloabl Game Jam，本来是负责画素材做音乐音效关卡设计和文案的，结果第一天就全部做完了。第二天在程序的提议下帮他写了一些代码，不过还好写了，不然游戏就做不完了。","title":"游戏开发：开发工具的意义","type":"posts"},{"content":"最近为了给游戏添加一些背景，速通了一下Shader，虽然不知道为什么顺便把sdf和raymarching和dnl一起速通了。顺便研究了一下Shader golf（压缩代码数量），在研究Shader golf的时候，我发现可以利用位运算去读取存储在二进制里面的数据，由此来画出1-bit的像素图，并且Tiling。\n二进制由于是由1和0组成的，可以用1表示有东西，0表示没东西在GLSL里面可以很简单的用三目运算来判断并且赋值。\n为了读取每一个数字，在特定的位置需要位右移(\u0026raquo;)，同时%所画图像的宽高，就能实现Tiling。\nvoid mainImage( out vec4 O, in vec2 I) { int art[18] = int[](18,63,30,63,30,12, 18,63,30,30,63,18, 12,12,63,63,45,12); I /= 20.; I.y += 9e4; I.y += (int(I.x)/6)%2==1?iTime*2.:-iTime*2.; O.rgb = (int(art[int(I.y)%18]\u0026gt;\u0026gt;int(I.x)%6)\u0026amp;1)\u0026gt;0?vec3(0.+float((int(I.y)/6)%3)*.2,.2,.3):vec3(.92); } Shadertoy地址：https://www.shadertoy.com/view/Dtyczd\n这段代码不是很golf，而且事后发现有需要改动的地方，接下来会进行一一讲解。\nint art[18] = int[](18,63,30,63,30,12, 18,63,30,30,63,18, 12,12,63,63,45,12); 首先要创建一个数组，里面存储三个图像的数据，图像是6*6。\n以第一个图像举例：\n18的二进制是010010，63的二进制是111111，30的二进制是011110，12的二进制是001100。所以合起来第一个图像会是\n010010\r111111\r011110\r111111\r011110\r001100 剩下三个以此类推，现在写好了数组之后，就是要想办法画出图像了。\nO.rgb = (int(art[int(I.y)%18]\u0026gt;\u0026gt;int(I.x)%6)\u0026amp;1)\u0026gt;0?vec3(0.+float((int(I.y)/6)%3)*.2,.2,.3):vec3(.92); 这行代码看着吓人，但是实际上很简单。\n首先O是输出，我替换了fragColor，在这里我们用了一个三目运算符，来做条件判断，首先看一下?前面的代码，int(art[int(I.y)%18])这里为了能够获取到所有的图像数据，并且我想竖向排布，所以使用了I.y，I替换了fragCoord，意思是当前的像素坐标。换句话就是说，每18个像素重复一次。\n既然从列读取了图像，接下来该从行中获取了。\n为了获取到往右一格的图像数据，这里使用了\u0026raquo;，并且根据每六个像素重复一次，也就是int(I.x)%6，最后对这个结果进行bitwise AND判断，如果是1和1，那么自然会得到1，其值大于0，为true；如果为0和0，那么会得到0，其值不大于0，为false。\n:左边的是vec3(0.+float((int(I.y)/6)%3)*.2,.2,.3)，如果为true，并且每个周期内的一个图像的颜色就要发生一些小小的改变。右边就自然不用解释了，如果是0就画出白色。\n为了让我们的图像看上去更大一点，可以用\nI /= 20.; 为了让奇数列偶数列的图像程不同运动方向，我们可以简单这样写：\nI.y += (int(I.x)/6)%2==1?iTime*2.:-iTime*2.; 因为一些绘制上的问题，我们要把坐标提前一点：\nI.y += 9e4; 当然，这些代码很不美观，以上三行代码可以用一行代码代替：\nI.y += mod(iTime*(int(I /= 20.)/6%2==0?-2.:2.),18.); 除18取余，18的意义在于上面的数组的大小，为了在适当的地方重复，保证无缝衔接。\n这样就不需要I.y += 9e4了。\n","date":"24 November 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/shader%E4%BD%BF%E7%94%A8%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AD%98%E5%82%A81-bit%E4%BD%8D%E5%9B%BE%E6%95%B0%E6%8D%AE/","section":"Posts","summary":"最近为了给游戏添加一些背景，速通了一下Shader，虽然不知道为什么顺便把sdf和raymarching和dnl一起速通了。顺便研究了一下Shader golf（压缩代码数量），在研究Shader golf的时候，我发现可以利用位运算去读取存储在二进制里面的数据，由此来画出1-bit的像素图，并且Tiling。","title":"Shader：使用二进制存储1-bit位图数据","type":"posts"},{"content":"在游戏开发中，会出现一种对格子之间逻辑要求严密的游戏；或者是由于使用的工具封装度较低，为了方便而不得不使用的一种方法。\n当游戏的空间排布是网格状的时候，我们就可以使用ASCII来表示格子上东西的含义，并且只需要一个承装这些字符串的文件即可，然后再通过一些方法把它们读取到游戏中。之后，用至少一个二维数组把这些东西装起来，然后再去处理这些东西直接的逻辑关系。\n逻辑严密 #\r最简单的例子当然还是推箱子或者是俄罗斯方块这种游戏了，推箱子而言，可以把每个关卡的地图用ASCII表示并且存在独立的文件里面，比如说有一个这样的文件，叫level_1.txt。里面可能是这个样子的。\n######\r#..@..#\r#...^.#\r#.`...#\r#..###\r## 随便写的，没有难度。\n然后\u0026rsquo;@\u0026lsquo;是玩家的意思，\u0026rsquo;#\u0026lsquo;是墙壁的意思，\u0026rsquo;.\u0026lsquo;是地板的意思，\u0026rsquo;^\u0026lsquo;是坑的意思，\u0026rsquo;`\u0026lsquo;是箱子的意思。\n为了让这些东西成为意思，所以我们需要在导入的时候做一些工作。\n去读取每一格的东西，如果是某个要素就做什么。\n比如这一格的字符串是\u0026rsquo;#\u0026rsquo;，那么我们就知道这是一个墙，就可以new一个墙的对象出来，保存到用于保存整个地图的数组里面。\n你也可以保存某个字符串或者id，但是我觉得这样缺少明确的意义。\n当然这里会给出示例代码(C#)：\nif (array[iter] == \u0026#39;#\u0026#39;) { Wall wall = new Wall(); gridObjects[width, height, 0] = wall; } 之后，我们可以调用gridObjects，然后去判断每一格上下左右之间的关系，写出逻辑判断。\n工具封装度低 #\r如果你使用的工具没有Tilemap工具，或者是不想用Tiled这是一个免费开源的瓷砖工具，适配很多引擎和框架。\n那么你仍然可以考虑使用ASCII Map，并且在之后用于写非网格的逻辑。\n比如说，现在打算做一个简单的RPG游戏，我们可以这样绘制一个ASCII Map：\n###################\r#......E........1.#\r#1.........1......#\r#..@.....1........#\r#........1......E.#\r#....1........1...#\r#.......1.........#\r################### 比如说，这个\u0026rsquo;@\u0026lsquo;是玩家的意思，\u0026lsquo;1\u0026rsquo;是树的意思，\u0026rsquo;.\u0026lsquo;是地板的意思，\u0026rsquo;#\u0026lsquo;是墙的意思，\u0026lsquo;E\u0026rsquo;是敌人的意思。\n把这个文件读取之后，再分配到对应的地方，创建对应的实例，赋予其对应坐标的属性。\n我自己的部分写完了，但是有一个东西我想可以拿出来看一下。\n有一个叫Recursed的平台跳跃解密游戏，这个游戏的地图就是用ASCII Map做的，虽然这是个商业游戏，但是游戏的源代码在游戏文件夹里面就能看得到，所以我想应该可以拿来引用一下：\nlocal castle = { o = \u0026#34;brick\u0026#34;, n = \u0026#34;brick_u\u0026#34;, u = \u0026#34;brick_d\u0026#34;, [\u0026#34;[\u0026#34;] = \u0026#34;brick_l\u0026#34;, [\u0026#34;]\u0026#34;] = \u0026#34;brick_r\u0026#34;, x = \u0026#34;wall\u0026#34;, [\u0026#34;=\u0026#34;] = \u0026#34;trim\u0026#34;, [\u0026#34;#\u0026#34;] = \u0026#34;bars\u0026#34;, [\u0026#34;:\u0026#34;] = \u0026#34;chain_u\u0026#34;, [\u0026#34;\u0026#39;\u0026#34;] = \u0026#34;chain_d\u0026#34;, [\u0026#34;*\u0026#34;] = \u0026#34;pillar_u\u0026#34;, [\u0026#34;|\u0026#34;] = \u0026#34;pillar_d\u0026#34;} function start() ApplyTiles(castle, 0, 0, [[ xxxxu............nxx xxxxn............u[] xxxxu..............* xxxxn..............| [][]u..............| *..................| |...............==== |...............n[][ ==========......uxxx xxxxxxxxxn......nxxx xxxxxxxxxun.....uxxx xxxxxxxxxxu.....nxxx xxxxxxxxxxn=====uxxx xxxxxxxxxxu[][][]xxx xxxxxxxxxxnxxxxxnxxx ]]) Spawn(\u0026#34;crystal\u0026#34;, 2.5, 6.5) Spawn(\u0026#34;lock\u0026#34;, 4.2, 6.5) Spawn(\u0026#34;player\u0026#34;, 7.8, 7) Spawn(\u0026#34;chest\u0026#34;, 13.2, 11.5, \u0026#34;hole\u0026#34;) Spawn(\u0026#34;key\u0026#34;, 17.5, 5.5) end 顺便一说，这是个很有意思很优秀的解密游戏，整个游戏围绕递归这一思路作为Gameplay，和Patrick\u0026rsquo;s Parabox的机制很像。如果对解谜游戏感兴趣的朋友可以试试。\n","date":"6 November 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%BD%BF%E7%94%A8ascii-map/","section":"Posts","summary":"在游戏开发中，会出现一种对格子之间逻辑要求严密的游戏；或者是由于使用的工具封装度较低，为了方便而不得不使用的一种方法。","title":"游戏开发：使用ASCII Map","type":"posts"},{"content":"有段时间没有玩蔚蓝了，想玩蔚蓝发现我的Olympus出了一些问题。\nError\rsharp.lua:441: Failed running (Win32RegSet)(main)#1 Win32RegSet: sharp thread died\rTraceback\r[C]: in function \u0026#39;error\u0026#39;\rsharp.lua:441: in function \u0026#39;setKey\u0026#39;\rmodinstaller.lua:21: in function \u0026#39;register\u0026#39;\rmain.lua:601: in function \u0026#39;load\u0026#39;\rmain.lua:97: in function \u0026lt;main.lua:96\u0026gt;\r[C]: in function \u0026#39;xpcall\u0026#39;\r[C]: in function \u0026#39;xpcall\u0026#39; 当然很显而易见Olympus是Love2D做的，这里是Lua的异常处理。\n一开始以为是我dotnet的问题，把dotnet 6.0 7.0重新卸载再安装了一遍发现还是不行，顺带把mono也重新安装了。\n此外，如果安装Everest，会安装失败，install-log.txt部分内容：\n...\rSystem.TypeInitializationException: MonoMod.InlineRT.MonoModRulesManager ͳ ʼֵ 趨 쳣 ---\u0026gt; System.IO.FileNotFoundException: δ ܼ ļ 򼯡 System.Collections, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ĳһ ϵͳ Ҳ ָ ļ MonoMod.InlineRT.MonoModRulesManager..cctor()\r--- ڲ 쳣 ջ ٵĽ β ---\rMonoMod.InlineRT.MonoModRulesManager.Register(MonoModder self)\rMonoMod.MonoModder..ctor()\rMonoMod.Program.Main(String[] args)\rFailed installing 4248 (core)\rSystem.Exception: Return code != 0, but 1\rMonoMod.Installer.Everest.EverestInfo.MiniInstallerProxy.Boot(MiniInstallerBridge bridge)\rMonoMod.Installer.Everest.EverestInfo.MiniInstallerProxy.Boot(MiniInstallerBridge bridge)\rMonoMod.Installer.Everest.EverestInfo.Install(Action`1 progress)\rMonoMod.Installer.GameModder._Install()\rMonoMod.Installer.GameModder.Install() 我下载的是4248(core)，这里分两段，第一段是上面的FileNotFoundException，因为这个我一直怀疑是我的环境问题。\n第二个是下面那个Return code != 0, but 1，大概是文件冲突了。\n解决方案 #\r完全卸载蔚蓝游戏文件夹（如果你有mod记得备份一下） 重新下载蔚蓝 重新安装Everest 直接下载4247 (dev) 如果第四步安装失败并且install-log.text里面出现了FileNotFoundException，试试其他版本的，并且留意install-log.txt是否有这段报错。如果没有，重复第一步，第四步换成没有抛出异常的版本。 如果上述过程没有问题，恭喜你，现在可以直接从Steam启动蔚蓝mod了，不用依赖Olympus。\n","date":"6 October 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/celeste-olympus%E5%90%AF%E5%8A%A8%E5%B0%B1%E6%8A%A5%E9%94%99+%E6%97%A0%E6%B3%95%E5%AE%89%E8%A3%85everest/","section":"Posts","summary":"有段时间没有玩蔚蓝了，想玩蔚蓝发现我的Olympus出了一些问题。","title":"Celeste Olympus启动就报错+无法安装Everest","type":"posts"},{"content":"最近看到了很多Unity的游戏工程开始往Godot迁移，具体原因是因为Unity的收费政策的调整导致很多开发者不愿意继续使用Unity，逐渐开始跳转到其他的游戏引擎/框架上，我所见的就C#而言，跳的大多是Godot Mono，不过我正好近期也在用Godot Mono做一个解密游戏。\n当然C#而言也有其他的选择，2D的有XNA的复刻版Monogame和FNA，3D有Flax、Orge或是Stride。\n不过在迁移游戏的时候，有一点比较重要的是，有的代码因为过于依赖引擎/框架的API，需要做出大量改动，或者是不知道要怎么改才能迁移过去。这一点在早期开发游戏的时候就要把纯粹的逻辑部分和调用引擎/框架的API分开来，在这里我要介绍一个无论是否要迁移游戏，或者是正常开发，都很重要的一个程序思想。正如标题所说，前后端分离。\n这样做有利于把逻辑部分和图形分开来，一方面有利于分工合作，另一方面整个项目不会太过凌乱，而且对于比较程式化的游戏而言，这种实现又是不得不需要的。\n说一下最近看到的两个例子：\n杀戮尖塔 #\r我个人比较喜欢这个游戏，之前在和哥们联机的时候有留意一下Console的输出，竟然发现了游戏的地图生成是前后端分离的。一开始我看那个地图生成以为是一个点生成完后往上移动多少个像素再生成一个点，然而并不是这个样子。\n14:05:48.800 INFO dungeons.AbstractDungeon\u0026gt; INIT CARD POOL\r14:05:48.801 INFO helpers.CardLibrary\u0026gt; [INFO] Adding blue cards into card pool.\r14:05:48.802 INFO dungeons.AbstractDungeon\u0026gt; COLORLESS CARDS: 35\r14:05:48.802 INFO dungeons.AbstractDungeon\u0026gt; CURSE CARDS: 10\r14:05:48.804 INFO dungeons.AbstractDungeon\u0026gt; Cardpool load time: 4ms\r14:05:48.805 INFO unlock.UnlockTracker\u0026gt; Already seen: AscendersBane\r14:05:48.805 INFO unlock.UnlockTracker\u0026gt; Already seen: Strike_B\r14:05:48.805 INFO unlock.UnlockTracker\u0026gt; Already seen: Strike_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Strike_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Strike_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Defend_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Defend_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Defend_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Defend_B\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Zap\r14:05:48.806 INFO unlock.UnlockTracker\u0026gt; Already seen: Dualcast\r14:05:48.806 INFO basemod.BaseMod\u0026gt; postCreateStartingDeck for: DEFECT\r14:05:48.806 INFO basemod.BaseMod\u0026gt; postCreateStartingDeck adding [ AscendersBane Strike_B Strike_B Strike_B Strike_B Defend_B Defend_B Defend_B Defend_B Zap Dualcast ]\r14:05:48.815 INFO dungeons.AbstractDungeon\u0026gt; Content generation time: 52ms\r14:05:48.815 INFO basemod.BaseMod\u0026gt; publishStartGame\r14:05:48.815 INFO basemod.BaseMod\u0026gt; mapDensityMultiplier: 1.0\r14:05:48.815 INFO basemod.BaseMod\u0026gt; publishStartAct\r14:05:48.816 INFO basemod.BaseMod\u0026gt; publishPostDungeonInitialize\r14:05:49.149 INFO scenes.AbstractScene\u0026gt; Fading in ambiance: AMBIANCE_BOTTOM\r14:05:49.326 INFO dungeons.AbstractDungeon\u0026gt; Note For Yourself is disabled beyond Ascension 15+\r14:05:49.327 INFO EventUtil\u0026gt; Adding conditional SpecialOneTimeEvents.\r14:05:49.327 INFO EventUtil\u0026gt; Checking for SpecialOneTimeEvent replacements...\r14:05:49.333 INFO dungeons.AbstractDungeon\u0026gt; Generating Room Types! There are 57 rooms:\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; SHOP (5%): 3\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; REST (12%): 7\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; TRSRE (0%): 0\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; ELITE (8%): 7\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; EVNT (22%): 13\r14:05:49.334 INFO dungeons.AbstractDungeon\u0026gt; MSTR (53%): 27\r14:05:49.340 INFO map.RoomTypeAssigner\u0026gt; #### Unassigned Rooms:\r14:05:49.342 INFO map.RoomTypeAssigner\u0026gt; class com.megacrit.cardcrawl.rooms.RestRoom\r14:05:49.342 INFO map.RoomTypeAssigner\u0026gt; class com.megacrit.cardcrawl.rooms.RestRoom\r14:05:49.342 INFO map.RoomTypeAssigner\u0026gt; INFO: Node=(2,13):[(2,14)] was null. Changed to a MonsterRoom.\r14:05:49.342 INFO map.RoomTypeAssigner\u0026gt; INFO: Node=(6,13):[(5,14)] was null. Changed to a MonsterRoom.\r14:05:49.342 INFO dungeons.AbstractDungeon\u0026gt; Generated the following dungeon map:\r14:05:49.342 INFO dungeons.AbstractDungeon\u0026gt; / / | \\ 14 R R R R / | \\ \\ 13 M M M M | \\ / \\ \\ / 12 M ? $ R \\ \\ \\ | \\ 11 E M ? M M / / \\ / / / 10 M ? R ? E | | \\| | 9 M ? E ? \\ \\ / | \\ \\ 8 T T T T T T \\|/ / / | 7 R E M M | \\|/ / 6 M ? R | \\| \\ \\ 5 E R E E \\| \\ | 4 ? $ ? | \\ / \\| 3 M M ? / | | \\ / 2 M ? M $ \\ \\ \\| \\ 1 M ? M ? / / / | 0 M M M M 14:05:49.343 INFO dungeons.AbstractDungeon\u0026gt; Game Seed: 6166791066988275480\r14:05:49.343 INFO dungeons.AbstractDungeon\u0026gt; Map generation time: 16ms\r14:05:49.343 INFO dungeons.AbstractDungeon\u0026gt; [INFO] Elite nodes identified: 7\r14:05:49.343 INFO dungeons.AbstractDungeon\u0026gt; [INFO] Emerald Key placed in: [5,10] 这些大概是我在控制台看到的样子，我截取了部分有价值的信息，这里的输出包括了卡牌的生成以及地图的生成，它能输出到Console里面，说明这个地图生成有一定的数据结构，是一个非离散的网状空间，从输出而言看着像某一种树，不过这种地图生成算法我没有研究过。\n推测在生成完实际的地图之后，再返回到图形渲染去生成玩家能够点到的图标，之后再返回进入战斗场景的数据。\n其实在Traditional Roguelike中，因为游戏以Grid-Based著称，所以地图生成这一块基本需要用到一个二维数组。\n接下来要介绍另一个游戏，它是个Traditional Roguelike。\nCaves of Qud #\rCaves of Qud很不幸是Unity做的，他们团队在Unity刚出事的时候就说了要用三个月的时间把游戏从Unity迁移到Godot上，几天前刷Twitter的时候，发现他们已经把Core迁移过去了。视频和截图都是纯ASCII，并且是在终端输出。这其实说明了一点，Caves of Qud也采用了前后端分离的设计思路。\n虽然Caves of Qud的特效做的很绚丽，贴图也做的很好，但是这背后的程序化工作是少不了的。\n这边贴一下Brian Bucklew在Twitter发的迁移工作视频，有条件可以去看看，没想到包括GUI界面都做了ASCII：https://x.com/unormal/status/1703643790047592912?s=20\n还有个不一定能看到的图片：\nBucklew展示的截图和视频完全是游戏里面所表示的那样，只不过是一个没有贴图和美化的版本，这些纯ASCII依然具有很强大的魅力。\n而且，做这些巨量代码的迁移工作只花了他差不多一天的时间，仅仅只是复制粘贴而已，这些后端部分的代码不敢说全部，但至少绝大部分不依赖Unity，因为它们不依赖图形渲染，它们只是一堆随时能被调用的数据。\n我自己做的很多游戏也遵循了前后端分离的思路，地图生成，敌人的行为，地图的数据。但是目前没有做到完全分离，有时候处理逻辑上的东西也有用到引擎自带的东西。\n但是不得不承认这是非常需要学会的，而且作为一个Programmer，这也是一门必修课。\n","date":"20 September 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF/","section":"Posts","summary":"最近看到了很多Unity的游戏工程开始往Godot迁移，具体原因是因为Unity的收费政策的调整导致很多开发者不愿意继续使用Unity，逐渐开始跳转到其他的游戏引擎/框架上，我所见的就C#而言，跳的大多是Godot Mono，不过我正好近期也在用Godot Mono做一个解密游戏。","title":"游戏开发：前后端分离的设计思路","type":"posts"},{"content":"纵观历史，游戏的存在时间很短，中国的游戏历史更短，独立游戏也不过十年前开始散播萌芽。\n在这期间，游戏比起其他新型娱乐产品，如电影、小说而言，就像一个孩子，但是发展到今天，也已经有了很多的沉淀。\n这篇文章中，我不推荐任何游戏设计有关的书籍或视频，文章之外我也不太会去推荐这些东西，如果你是一名独立游戏开发者。\n这些沉淀仅仅是作为对整个游戏产业而言的，作为那些商业游戏而言的，作为独立游戏，我们需要一套另外的准则。或者说，使用它们需要一定的条件。\n这些书或者视频必定会提到各种实践方案，以及背后的来自其他学科的原理和知识，如果有必要，确实需要使用它们，但是使用它们的前提在于，你是一个游戏开发者，而不是一个游戏爱好者。一定要多做游戏。\n它们是一套已经成型的商业体系，它们会通过市面上已有的游戏类型做出大量的分析。这当然好，通过现有的实例进行分析，从中学习它们，为什么成功了，商业上。游戏开发者自然希望自己的游戏能在商业上有所成就，但是对于独立游戏而言，并非如此。\n经济来源很重要，但是至少在中国，做独立游戏吃力不讨好，有更多来钱更快的路子。其实说白了，如果你是一个独立游戏开发者，你一定明白我的意思，自我实现。\n**做游戏是为自己做的，这是自我实现的过程，而不是沦为商品物化的过程。**在当今资本主义占主导的社会下，经济来源自然重要，但是放长远眼光看，如果把自己的作品放的更重要，而忽略了自我实现的过程，终将会导致一个结果。因为自己的作品而感到愧疚。\n如果你是一名艺术创作者，是否有过这样的经历，以前创作的东西有多好，但是现在的我已经创作不出这种东西了，从而陷入一个自我怀疑的怪圈。很多人喜欢自己的作品，其实只是因为喜欢自己的作品而已，它们看到的不是作为创作者的你，而是这个已经被物化了的商品。\n笔者参加了2023年的CUSGA中国大学生游戏创作大赛，并且进入了决赛，在决赛当天，很多人来到了我的展位面前，夸赞我的游戏做的不错，也有一些发行商加了我的微信好友。到颁奖的时候，虽然游戏没有得奖，但是最后得到了IndiePlay的入围资格。当时被夸耀冲昏了头脑，其实现在想想，它们看中的并不是作为人的我，而是作为物的我和我的游戏，看到了其中的商业价值。\n其中，也有不少人给出了它们认为的修改建议，我当时也全部都记下来了，兴许以后会看看这个游戏有没有需要修改的地方，但是在来之前，我并没有这样的想法。\n回去之后，我一直对这个游戏很纠结，并且暑假的剩下时间，也一直在想着这件事情，这个我原本不想营业的游戏，到底要不要拿去变现，去更新这个游戏？我已经很久没有做过游戏了，我只想做新的游戏。\n那种赞许，对于那时的我只是痛苦，而且CIGA的工作人员也很快联系了我，希望我在几周后上传新的游戏包。在这种反复挣扎中，最后我做出了一个或许常人很难理解的决定，我决定放弃作为这个游戏的一切。放弃这个已经被不少人看中的游戏的一切。\n作为自己思想的导师，我失败了，但是，作为文字的载体，这篇文章，希望能让更多人看到，请为自己做游戏。\n回到书的话题，这些商业游戏的准则适合于独立游戏吗？请根据需要自行择取。如果它们对于构建你心目中的游戏有帮助，请拿去用。这也是唯一的条件，知识仅仅是够用就行了。\n作为独立游戏开发者，我们需要在游戏中展现自己的个性，这也是早批那些经典独立游戏所秉持的信念。这意味着不迎合玩家吗？不。\n迎合应该迎合的玩家，不是所有游戏都应该迎合所有玩家，解密游戏有解密游戏的受众，STG有STG的受众，Roguelike有Roguelike的受众。当然，甚至可以不在特定的游戏类型上面去寻找受众，也有不少喜欢玩一些新奇独立游戏的人群。\n需要秉持独立游戏的个性，还有一点很重要，不要一味的借鉴当下热门的游戏。相信不少人已经在不少游戏中看到其他热门游戏的影子了，这意味着失去了独立性，失去了开发者的个性。\n一开始说了，不会推荐去看游戏开发有关的书籍，但是我会推荐你去多看看这个世界的其他东西。多出去走走，总在室内待着不太好；多听听音乐，提升自己的审美；多浏览图片，提升自己的情操；多看书，增长自己的见识；多玩游戏，希望你能玩的开心。\n","date":"20 September 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E8%AE%BE%E8%AE%A1%E7%8B%AC%E7%AB%8B%E6%B8%B8%E6%88%8F%E7%9A%84%E4%B8%AA%E6%80%A7/","section":"Posts","summary":"纵观历史，游戏的存在时间很短，中国的游戏历史更短，独立游戏也不过十年前开始散播萌芽。","title":"游戏设计：独立游戏的个性","type":"posts"},{"content":"由于暂时不是那么想写最近在做的游戏的代码，又动了歪心思想怎么在室友大晚上聊天让我难以睡着的时候能在tic-80做点游戏，又由于我欠缺低封装框架的代码经验，于是花了一点时间研究了一下怎么实现最小场景树。\n因为只是最小场景树，所以不涉及面向对象或者ECS的编写，这些或许会在以后考虑到。所以目前这个最小场景树仅仅只是做到了场景切换而没有实例等的概念。\n因为是tic-80，所以pico-8也是通用的，而且如果你写的是lua，那么我的代码理论上可以在你想的任何地方使用。\n原理 #\r首先我们有一个游戏，游戏之所以能每一帧都能对玩家的操作进行反应是因为有一个主循环。\n在tic-80里面，主循环表示为\nfunction TIC() then end 然后游戏可以往下细分为无数的场景，比如开始界面，主游戏界面，和游戏结束界面。\n那么要在这些场景中间进行切换，就需要有一些分支，一个分支里面发生的事情不影响另一边发生的事情。\nlua的数据结构为table，只有table，它很自由，也很符合我们的预期。\n我们可以把场景分为某几个函数，用函数来控制游戏的发生。在这里我分成了Init和Loop。Init表示进入场景的时候，Loop表示循环进行。\n每个场景里面都有这两个函数，我们要做的就是在必要的时候运行不同的Init和Loop。\n首先，我们需要一个场景的table，所以我们可以\nScene = {} 为了初始化第一个场景，以及控制所有场景，我们还需要两个变量。\nCurrentScene = \u0026#34;Init\u0026#34; FirstScene = \u0026#34;First\u0026#34; -- Change it to any scene you want 然后需要一个创建场景的方法：\nfunction Scene:New(_scene) Scene[_scene] = {} self.__index = self return setmetatable(Scene[_scene], self) end 很基本的lua面向对象，之所以要这样写是因为我们还需要一个Scene:Change来改变当前的场景。\nfunction Scene:Change(...) local _scene, _keep = ... if _keep == nil then Scene[_scene]:Init() end CurrentScene = _scene end 这里给了一个可控的参数，我们不希望在调用这个函数的时候写多余的代码，所以只需要在需要的时候保持目标场景。\n为了初始化第一个场景，我们需要一个前置场景。\nlocal initScene = Scene:New(\u0026#34;Init\u0026#34;) function initScene:Loop() initScene:Change(FirstScene) end 接着，可以写游戏中的场景了\n-- Scene First -- Must contain an Init and a Loop Function firstScene = Scene:New(\u0026#34;First\u0026#34;) function firstScene:Init() -- Only emit once x = 1 end function firstScene:Loop() -- Emit every frame x = x + 1 cls(2) print(x) if btn(3) then firstScene:Change(\u0026#34;Second\u0026#34;) -- Change to any Scene you want end end -- Scene Second secondScene = Scene:New(\u0026#34;Second\u0026#34;) function secondScene:Init() end function secondScene:Loop() cls(3) print(\u0026#34;Hello World!\u0026#34;) if btn(2) then -- The sceond parameter controls -- whether you want to keep the target -- scene since the last changes. -- Default is not to keep secondScene:Change(\u0026#34;First\u0026#34;, true) end end 最后，只需要在主循环里加上这么一句，我们的最小场景树就完成了：\nScene[CurrentScene]:Loop() 全部代码：\n-- title: MinimalSceneTree -- author: Feishiko, feishiko@foxmail.com -- desc: A minimal scene tree -- site: feishiko.itch.io -- license: MIT License -- version: 0.1 -- script: lua CurrentScene = \u0026#34;Init\u0026#34; FirstScene = \u0026#34;First\u0026#34; -- Change it to any scene you want Scene = {} function Scene:New(_scene) Scene[_scene] = {} self.__index = self return setmetatable(Scene[_scene], self) end function Scene:Change(...) local _scene, _keep = ... if _keep == nil then Scene[_scene]:Init() end CurrentScene = _scene end local initScene = Scene:New(\u0026#34;Init\u0026#34;) function initScene:Loop() initScene:Change(FirstScene) end -- Scene First -- Must contain an Init and a Loop Function firstScene = Scene:New(\u0026#34;First\u0026#34;) function firstScene:Init() -- Only emit once x = 1 end function firstScene:Loop() -- Emit every frame x = x + 1 cls(2) print(x) if btn(3) then firstScene:Change(\u0026#34;Second\u0026#34;) -- Change to any Scene you want end end -- Scene Second secondScene = Scene:New(\u0026#34;Second\u0026#34;) function secondScene:Init() end function secondScene:Loop() cls(3) print(\u0026#34;Hello World!\u0026#34;) if btn(2) then -- The sceond parameter controls -- whether you want to keep the target -- scene since the last changes. -- Default is not to keep secondScene:Change(\u0026#34;First\u0026#34;, true) end end function TIC() Scene[CurrentScene]:Loop() end 如果省去中间两个场景，全部代码大致如下：\nCurrentScene = \u0026#34;Init\u0026#34; FirstScene = nil -- Change it to any scene you want Scene = {} function Scene:New(_scene) Scene[_scene] = {} self.__index = self return setmetatable(Scene[_scene], self) end function Scene:Change(...) local _scene, _keep = ... if _keep == nil then Scene[_scene]:Init() end CurrentScene = _scene end local initScene = Scene:New(\u0026#34;Init\u0026#34;) function initScene:Loop() initScene:Change(FirstScene) end function TIC() Scene[CurrentScene]:Loop() end 最后，这段代码也上传到了我的github上，如果有兴趣可以前往查阅：https://github.com/Feishiko/Minimal-Scene-Tree-in-Tic-80\n","date":"20 September 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%9C%80%E5%B0%8F%E5%9C%BA%E6%99%AF%E6%A0%91%E7%9A%84%E5%AE%9E%E7%8E%B0/","section":"Posts","summary":"由于暂时不是那么想写最近在做的游戏的代码，又动了歪心思想怎么在室友大晚上聊天让我难以睡着的时候能在tic-80做点游戏，又由于我欠缺低封装框架的代码经验，于是花了一点时间研究了一下怎么实现最小场景树。","title":"游戏开发：最小场景树的实现","type":"posts"},{"content":"\r前提须知 #\r本文并非新手向教程，更多我想是个人心得，基本的Arch安装步骤也不会在本文中列出。\n个人需求 #\r因为想在平板上画画，偶尔写一些手写笔记，或者是看一些PDF以及一些资料，所以想整一个平板电脑。无奈于ipad价格相对学生党来说比较昂贵，所以选择了某宝上399的Surface Pro 3，加上一支非官方的触控笔139。在Windows系统上稍微测试了一下笔的功能，但是这支笔并没有想象中好用，压感甚至有点问题，画画的时候不会那么舒服，不过算是能用的程度。\n开始安装 #\r因为Surface只有一个USB接口，所以我再买了一个一接多插入键盘和U盘。\n等一接多回来我就开始动工了，今天正好是七夕，我和Tux过。\n首先需要先关闭Surface的Secure Boot，\n","date":"26 August 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/surface%E5%B9%B3%E6%9D%BF%E7%94%B5%E8%84%91%E5%AE%89%E8%A3%85archlinux/","section":"Posts","summary":"前提须知 #\r本文并非新手向教程，更多我想是个人心得，基本的Arch安装步骤也不会在本文中列出。","title":"Surface平板电脑安装Archlinux","type":"posts"},{"content":"\r事情概况 #\r在出事之前，我全部滚动了，但是因为Aseprite需要libfmt.so.9的依赖，所以没法直接更新，所以我sudo pacman -Syu --ignore fmt。\n更新完之后还没有什么问题，直到我修改了Sway的配置，刷新之后，我的Waybar不见了。\n解决过程 #\r命令行运行Waybar，出现以下报错waybar: error while loading shared libraries: libfmt.so.10: cannot open shared object file: No such file or directory\n于是使用sudo pacman -Sd fmt，忽略依赖问题更新了fmt在这之前因为依赖问题不让我更新\n更新之后Waybar还不能使用，同样这个时候Aseprite也无法使用了，网上搜了两个星期前的一个相关资料说要降级spdlog，但是我的spdlog似乎没有更新，paru -S spdlog更新之后Waybar启动了。\n","date":"20 August 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/2023.8.20%E5%85%B3%E4%BA%8Ewaybar%E5%87%BA%E4%BA%86%E7%82%B9%E9%97%AE%E9%A2%98/","section":"Posts","summary":"事情概况 #\r在出事之前，我全部滚动了，但是因为Aseprite需要libfmt.","title":"2023.8.20关于Waybar出了点问题","type":"posts"},{"content":"笔者用的是Arch Linux, 目前使用Sway窗口管理器，想通过Fcitx5输入中文，浏览器之前用的是Chromium，但是光标移动到Chromium上后按默认快捷键Ctrl + Space没有反应。光标移动到Kitty(一个Console)上之后按快捷键有反应，并且能键入中文，但目前有个问题是不会显示下面的提示，只能凭印象打出。\n一开始没排除是Chromium的问题，以为是Fcitx依赖没有装完，后来仔细去查了相关资料：\nSway是基于wayland的窗口管理器，Firefox完全支持wayland。所以之后就sudo pacman -S firefox把firefox下载了下来，并且试着在里面打字，证实了我的想法，firefox可以用fcitx5输入中文，而Chromium不行。\n如果使用以下参数可以在Chromium里面输入东西，但是表现效果和上面的kitty一样。\n--gtk-version=4\r","date":"15 July 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/fcitx5%E6%97%A0%E6%B3%95%E5%9C%A8chromium%E4%B8%8A%E4%BD%BF%E7%94%A8/","section":"Posts","summary":"笔者用的是Arch Linux, 目前使用Sway窗口管理器，想通过Fcitx5输入中文，浏览器之前用的是Chromium，但是光标移动到Chromium上后按默认快捷键Ctrl + Space没有反应。光标移动到Kitty(一个Console)上之后按快捷键有反应，并且能键入中文，但目前有个问题是不会显示下面的提示，只能凭印象打出。","title":"Fcitx5无法在Chromium上使用","type":"posts"},{"content":"只能通过波形文件倒带，如果你要倒带的部分是midi，那么首先需要以下步骤：\n右键要倒带部分的track，然后选择Render/freeze tracks，之后选择Render tracks to mono stem tracks(and mute originals)，之后就会渲染出该轨道的波形文件，并且将原来的midi音轨禁音。\n之后对要倒带的item右键一下，选择Item settings，接着勾选Reverse active take，然后你的音频就倒带了。我这里已经勾选上了\n虽然教程很短，但是我为此在上面花了不少时间去找实现方法，一开始词还搜错了。\n","date":"4 April 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E5%9C%A8reaper%E4%B8%AD%E5%AE%9E%E7%8E%B0%E5%80%92%E5%B8%A6/","section":"Posts","summary":"只能通过波形文件倒带，如果你要倒带的部分是midi，那么首先需要以下步骤：","title":"在reaper中实现倒带","type":"posts"},{"content":"这几天在玩Caves of Qud，这是一个传统Roguelike游戏，游戏的背景是末日后，人们用水作为一般等价物进行交易，同时用水进行水仪式，可以促进不同派系之间的好感度。游戏中的书本内容是随机生成的，听群友说是隐马尔科夫链，之后出于好奇自己搜了一些资料，并且尝试用马尔科夫链生成一些内容，以下内容是我基于自己写的模型，并且用Soul Music作为语料库生成的内容：\nYou are certain if you can be an immortal and took the Death of his own, and took the Discworld, or Mort for the Discworld, on the Discworld, someone she\u0026rsquo;d known to become sixteen but she knew how to his own, and took the dimensions. But if it were, well, nothing against horses, or later had Eg and took the Death started to become real sorry. Or what, for the Death started to his home yet. Er. Got it!\u0026rsquo; he believed in the Discworld, or later and took the Discworld, on business of the Discworld, someone married then rolled up a story about sex and this much to become accustomed and took the dimensions. And another tooth.'\nAnd then, eventually, and this shows that he later hired and this silliness.'\nAnd another one, and this much can hardly existed at age of his own, once been then rolled his home between the Death sat under the Discworld, someone she\u0026rsquo;d already circling the Death which was probably safe from the Discworld, or Mort was an apprentice but it were, well, nothing here,\u0026rsquo; said that the dimensions. But first thing regardless then got up a story or Mort lost in the Death of his own, and still sitting there are interviewing or Mort was probably true. But the Discworld, or later take a story about memory. And then, in the Discworld, then said, \u0026rsquo; I did Miss Butts shuffled the Death of his home between the dimensions. And another direction. His brow or later take a story but it is a story about sex and took her feel better. It doesn\u0026rsquo;t take an apprentice then glanced at the Discworld, then said, rolling fields, and took the dimensions. But first thing regardless or later another river that you ran smoothly or later another direction.\n看着像是一点都不正经的胡说八道，不过如果你有需要生成一些胡说八道随机文章的地方，这篇文章或许能帮助到你。\n原理 #\r马尔科夫链，今天发生的事情只和昨天有关系，明天发生的事情只和今天有关系。比如现在只有一个词I，然后I能和很多可能性的词连接，比如and/am等等，假如我选择and，那么现在的内容就是I and，接着是and，能和and连接的词比如有you/him/her，这时可能连接的词和I就没有任何关系了，然后假如我们选择you，现在的内容就是I and you，之后我们再去找可以和you连接的词。\n接着是语料库的收集，要生成东西就需要一些原有的素材，比如有这样一段话“I am Feishiko, I like play games.”，我们可以按空格拆分，让这句话拆成I/am/Feishiko,/like/play/games.\n然后输入给我们的模型，以I为开头，可能会生成：I like play games.\n代码 #\r还是以lua为例，因为模型我是用lua写的（\n我们首先要创建一个函数，我们需要一个文本源，用来生成语料库，输出多少个单词，以及我们的第一个关键词是什么。\nfunction ModelBuild(_source, _num, _keyword) end 下面我们要拆分出文本源的关键词，并且把它传给listKey这个table，也就是把所有单词拆分成一个一维数组的一个个元素。\nfunction ModelBuild(_source, _num, _keyword) local listKey = {} -- 用来存所有（存在重复）关键词 local firstPlace = 1 -- 截取文字前面的位置 local secondPlace = 1 -- 截取文字后面的位置 local len = string.len(_source) -- 文本源的长度 while true do while true do if secondPlace \u0026gt;= len then -- 如果读到头了，那就强制中断循环 table.insert(listKey, string.sub(_source, firstPlace, secondPlace)) break end if string.sub(_source, secondPlace, secondPlace) == \u0026#34; \u0026#34; then -- 按空格拆分关键词 table.insert(listKey, string.sub(_source, firstPlace, secondPlace - 1)) secondPlace = secondPlace + 1 firstPlace = secondPlace break end secondPlace = secondPlace + 1 end if secondPlace \u0026gt;= len then -- 如果读到头了，那就强制中断循环 break end end end 下面这段代码用来把关键词插入到一个新的table里面，相当于其他语言的字典，key是一个字符串，value是一个数组，数组里的各个元素是可能与key连接的词 (还是在这个函数里面写代码)\nlocal model = {} for i, v in ipairs(listKey) do -- i就是index, v就是value if model[v] == nil then model[v] = {} end if listKey[i + 1] ~= nil then table.insert(model[v], listKey[i + 1]) end end 下面这段代码用来基于刚才训练的语料库生成文章\nlocal text = _keyword -- text是文章的内容 local keyword = _keyword -- keyword是当前的关键词 for i = 1, _num, 1 do -- 基于要多少词生成文章 if model[keyword] == nil then -- 如果所给的关键词没有找到后面能连接的词，那么就不生成了 break end local nextWord = model[keyword][math.random(#keyword)] -- 通过model table找到下一个能连接的词，#keyword的意思是keyword table的长度（或者说是这个数组的长度） if nextWord == nil or nextWord == \u0026#34;.\u0026#34; then -- 如果不存在能连接的词或者连接的词是一个.，那么就找一个连词作为下一个词 local pron = {\u0026#34;and\u0026#34;, \u0026#34;but\u0026#34;, \u0026#34;or\u0026#34;, \u0026#34;then\u0026#34;} if string.sub(text, string.len(text) - 2, string.len(text) - 2) == \u0026#34;\\\u0026#39;\u0026#34; or string.sub(text, string.len(text) - 2, string.len(text) - 2) == \u0026#34;.\u0026#34; or string.sub(text, string.len(text), string.len(text)) == \u0026#34;.\u0026#34; then -- 该大写的地方要大写 pron = {\u0026#34;And\u0026#34;, \u0026#34;But\u0026#34;, \u0026#34;Or\u0026#34;, \u0026#34;Then\u0026#34;} end nextWord = pron[math.random(4)] end text = text .. \u0026#34; \u0026#34; -- ..的意思在lua里面是连接字符串的意思 text = text .. nextWord keyword = nextWord end return text 那么函数部分就写好了，接下来需要把我们的文章传进来\nfileText = \u0026#34;\u0026#34; file = io.open(\u0026#34;Soul Music.txt\u0026#34;) fileText = fileText .. file:read(\u0026#34;*a\u0026#34;) file:close() 调用一下函数\nmath.randomseed(os.time()) -- 种子用系统时间，保证每一次都是随机的 function init() local text = ModelBuild(fileText, 300, \u0026#34;You\u0026#34;) --- 选用fileText，就是Soul Music.txt作为语料库，生成300词，第一个词是You math.randomseed(os.time()) print(text) end while true do -- 错误处理，如果报错就继续调用这个函数，因为长度有限制，大概是一个int整型的长度，有的时候math.random(#keyword)会报错 if pcall(init) then break end end 重新整理一下，就是以下代码：\nfileText = \u0026#34;\u0026#34; file = io.open(\u0026#34;Soul Music.txt\u0026#34;) fileText = fileText .. file:read(\u0026#34;*a\u0026#34;) file:close() function ModelBuild(_source, _num, _keyword) local listKey = {} local firstPlace = 1 local secondPlace = 1 local len = string.len(_source) while true do while true do if secondPlace \u0026gt;= len then table.insert(listKey, string.sub(_source, firstPlace, secondPlace)) break end if string.sub(_source, secondPlace, secondPlace) == \u0026#34; \u0026#34; then table.insert(listKey, string.sub(_source, firstPlace, secondPlace - 1)) secondPlace = secondPlace + 1 firstPlace = secondPlace break end secondPlace = secondPlace + 1 end if secondPlace \u0026gt;= len then break end end local model = {} for i, v in ipairs(listKey) do if model[v] == nil then model[v] = {} end if listKey[i + 1] ~= nil then table.insert(model[v], listKey[i + 1]) end end local text = _keyword local keyword = _keyword for i = 1, _num, 1 do if model[keyword] == nil then break end local nextWord = model[keyword][math.random(#keyword)] if nextWord == nil or nextWord == \u0026#34;.\u0026#34; then local pron = {\u0026#34;and\u0026#34;, \u0026#34;but\u0026#34;, \u0026#34;or\u0026#34;, \u0026#34;then\u0026#34;} if string.sub(text, string.len(text) - 2, string.len(text) - 2) == \u0026#34;\\\u0026#39;\u0026#34; or string.sub(text, string.len(text) - 2, string.len(text) - 2) == \u0026#34;.\u0026#34; or string.sub(text, string.len(text), string.len(text)) == \u0026#34;.\u0026#34; then pron = {\u0026#34;And\u0026#34;, \u0026#34;But\u0026#34;, \u0026#34;Or\u0026#34;, \u0026#34;Then\u0026#34;} end nextWord = pron[math.random(4)] end text = text .. \u0026#34; \u0026#34; text = text .. nextWord keyword = nextWord end return text end math.randomseed(os.time()) function init() local text = ModelBuild(fileText, 300, \u0026#34;You\u0026#34;) math.randomseed(os.time()) print(text) end while true do if pcall(init) then break end end ","date":"29 March 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E5%9F%BA%E4%BA%8E%E9%A9%AC%E5%B0%94%E7%A7%91%E5%A4%AB%E9%93%BE%E7%9A%84%E9%9A%8F%E6%9C%BA%E6%96%87%E7%AB%A0%E7%94%9F%E6%88%90/","section":"Posts","summary":"这几天在玩Caves of Qud，这是一个传统Roguelike游戏，游戏的背景是末日后，人们用水作为一般等价物进行交易，同时用水进行水仪式，可以促进不同派系之间的好感度。游戏中的书本内容是随机生成的，听群友说是隐马尔科夫链，之后出于好奇自己搜了一些资料，并且尝试用马尔科夫链生成一些内容，以下内容是我基于自己写的模型，并且用Soul Music作为语料库生成的内容：","title":"基于马尔科夫链的随机文章生成","type":"posts"},{"content":"很多游戏，像Tetris（俄罗斯方块）、Sokoban（推箱子）的逻辑实现都是依靠对数据的操纵，把离散的数据用数据结构储存起来，就能对其做很多事情。\n比如在Tetris中，我们可以把每一块离散的空间都放到一个二维数组里面，然后给数组里面的每一个元素一个ASCII，比如#表示方块，.表示空位置。方块旋转的时候可以乘以一个2d旋转矩阵，然后让方块转动到对应的位置；当有一行全部被#挤满的时候，那一行将被删除，并且在上面的所有方块往下移动一格。然后我们可以把这个二维数组用绘图API画出来。\n再比如Sokoban，我们可以用@表示玩家，#表示墙壁，O表示箱子，^表示坑。然后基于二维数组进行各种逻辑上的处理，比如要让玩家移动，要保证玩家前面不能有墙壁，举一个简单的例子，这里用lua\nlocal map = [] for width = 1; i = maxWidth do -- lua的table自增从1开始！ map[width] = [] end -- 省略一些初始化过程~ -- ... -- 如果玩家的左侧没有墙壁，那么玩家就向左移动一格 if map[player.x - 1][player.y] ~= \u0026#34;#\u0026#34; then player.x = player.x - 1 end 只要你掌握了怎么操纵数组，那么对于很多功能的实现就会变得非常简单！像关卡生成或者寻路算法等等，都需要对数据进行各种操纵。\n甚至关卡编辑器我们也可以使用各种格子。\n在SHENZHEN I/O中，玩家如果要制作自己的关卡，就要自己写lua，其中关卡编辑器就是一个asciiMap！玩家把地图用ascii的方式打成字符串，然后后台就会把这个table拿去解析成一个二维数组。\n我自己用一些比较原始的游戏引擎做游戏的时候，也喜欢写asciiMap，然后解析成二维数组，进而写一些逻辑。\n再比如寻路算法，如果你的游戏地图是用二维数组表示的，那么就能通过操纵各种数据来实现寻路。这里我们用一个叫做BFS的算法。\n使用BFS需要一个Quene，我们简略画一个地图。其中下面的@是玩家，E是敌人，#是墙壁，.是空地\n#########\r#E..#..@#\r#..#.#..#\r#.......#\r######### 首先我们要创建另一个二维数组，#标记为-9999，空地标记为0，@标记为0，E自身是9999。在@的位置对四周非-9999的进行标记，此轮标记为1，然后把标记为1的位置记录到Quene，Quene的原则是先进先出。\n#########\r#E..#.1@#\r#..#.#.1#\r#.......#\r######### 接下来就可以对1的这个位置做刚才一样的事情，但是我们是从Quene里面提取的位置，直到Quene没有东西为止。之后的asciiMap应该大致如此：(x表示10，因为这里放不下了)\n#########\r#E9x#21@#\r#98#6#21#\r#8765432#\r######### 然后E（敌人）就可以找周围更小一点的数，如果更小就走这一格，最后就能走到玩家的位置。\n再比如Solitaire(单人纸牌)，像蜘蛛纸牌/空当接龙之类的，也很需要对数组进行操纵\nSolitaire有很多行，很多列，然后每一个元素里面存一个数字，数字表示对应的牌，举个简单的例子：\n1 8 15\r2 9 16\r3 10 17\r4 11 18\r5 12 19\r6 13 20\r7 14 21 随便打了一点，当然正常Solitaire是随机的，乱序的，不像我这么整齐。\n我们都知道，扑克牌一共四种花色，每种花色13张牌，我们就可以1-13一个花色；14-26一个花色以此类推。\n再然后我们可能要堆叠牌，比如我们要把红桃7放到红桃8上面，这里红桃是1-13，那么我们要判断当前牌的id+1是否等于要放到的牌上面（是否等于8）\n总结 #\r所以对于很多游戏来说，数组是一个很强大的工具！如果你能很好的用在你的游戏上，这会很有帮助！\n","date":"9 March 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%95%B0%E7%BB%84%E5%9C%A8%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/","section":"Posts","summary":"很多游戏，像Tetris（俄罗斯方块）、Sokoban（推箱子）的逻辑实现都是依靠对数据的操纵，把离散的数据用数据结构储存起来，就能对其做很多事情。","title":"数组在游戏开发中的应用","type":"posts"},{"content":"\r朗伯余弦定律 #\r用来计算某个角度所得到的亮度的多少，因为亮度不可能小于0（范围0-1），所以最小值为0\nlight = max(cos(theta), 0.0);\nShadertoy #\r首先我们需要一张凹凸贴图（Bump Mapping），用来表示光照物体的各种细节/深度\n代码部分：\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv0 = fragCoord/iResolution.yy; //光照的角度 vec2 lightDir = vec2(cos(iTime), sin(iTime)); //光源高度 float pitch = .5; //朝光的方向偏移原来的uv vec2 uv1 = uv0 + lightDir/iResolution.yy; //获取偏移后的深度 float height0 = texture(iChannel0, uv0).r; float height1 = texture(iChannel0, uv1).r; //深度 float depth = .1; //normal（法向量），根据两个不同的uv偏移的值算出两点相差的角度 float normal = atan(1./iResolution.y, (height1 - height0)*depth); //光照强度 float lam = max(cos(normal - pitch), 0.); // Time varying pixel color // Output to screen fragColor = vec4(vec3(lam), 1.0); } 最终效果：\n","date":"18 February 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%9C%97%E4%BC%AF%E4%BD%99%E5%BC%A6%E5%AE%9A%E5%BE%8B%E5%9C%A8%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/","section":"Posts","summary":"朗伯余弦定律 #\r用来计算某个角度所得到的亮度的多少，因为亮度不可能小于0（范围0-1），所以最小值为0","title":"朗伯余弦定律在游戏开发中的应用","type":"posts"},{"content":"\r牛顿迭代法 #\r可用来收敛到一个点，通过不停的迭代，求方程的近似根。\n这是一个简单的曲线，如果我们要用牛顿法求f(x) = 0，第一步：那么首先需要先随便在横坐标上确认一个点。\n然后第二步：做垂直，与曲线有一个交点，然后第三步：做这个交点的切线，这个切线再与x轴有一个交点，重复第一步，这样就能无限趋近于f(x) = 0\n于是我们可以得到这样一个公式，$x0$是第一步随便确认的点，$x1$是新的于x轴的交点\n牛顿迭代公式: $x1 = x0 - \\frac{f(x0)}{f\u0026rsquo;(x0)}$\nShader部分 #\rconst int iter = 100;//迭代次数 //复数乘法运算 vec2 cmul(vec2 c1, vec2 c2) { return vec2(c1.x*c2.x - c1.y*c2.y, c1.y*c2.x + c1.x*c2.y); } //复数除法运算 vec2 cdiv(vec2 c1, vec2 c2) { float d = dot(c2, c2); //考虑到分母为0的情况 if(d == 0.) return c1; else return vec2((c1.x*c2.x + c1.y*c2.y)/d, (c1.y*c2.x - c1.x*c2.y)/d); } //迭代 int itt(vec2 x0) { for(int i = 0; i \u0026lt;= iter; i++) { //x1 = x0 - (x0^3 - 1)/3*x0, 也就是x1 = x0 - f(x)/f\u0026#39;(x) vec2 x1 = x0 - cdiv(cmul(cmul(x0, x0), x0) - 1., 3.*x0); //当x1与x0无限接近的时候，返回当前的迭代次数 if(distance(x1, x0) \u0026lt; .0000001) { return i; } x0 = x1; } //不满足的话，返回最大的迭代次数 return iter; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { //让uv坐标中心设置在中心 vec2 uv = fragCoord/iResolution.xy - .5; //根据不同迭代次数显示灰度 vec3 col = vec3(float(itt(uv))/float(iter)); // Output to screen fragColor = vec4(col,1.0); } 注：我第一次写的时候在main里面写for，然后如果无限接近就break，但是这种方法貌似有一些问题，我最后得到的图像是二值化的。\n","date":"17 February 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E7%94%A8shadertoy%E5%AE%9E%E7%8E%B0%E7%89%9B%E9%A1%BF%E8%BF%AD%E4%BB%A3%E6%B3%95%E5%88%86%E5%BD%A2%E5%9B%BE%E5%83%8F/","section":"Posts","summary":"牛顿迭代法 #\r可用来收敛到一个点，通过不停的迭代，求方程的近似根。","title":"用ShaderToy实现牛顿迭代法分形图像","type":"posts"},{"content":"在Shader中要生成随机数，就要生成Noise图，通常来说我们都知道可以用sin+fract函数。这种方法也叫Sinhash。\n$f(x) = fract(sin(x)*a)$\n当a越来越大的时候，你会发现其函数图像会变得非常随机的样子，我们取其中一段就实现了随机。fract函数的意思是取小数部位。\n这也是The Book of shaders介绍的方法，这边是对应章节的链接 https://thebookofshaders.com/10/\n我们随后可以用这个方法生成一些噪声图。\n简单封装一下大概是这个样子。\n我们可以用dot也就是dotproduct也就是点积，这种计算方法会更好一点。输入一个二维向量，然后输出一个范围在0-1的float浮点数。之后再乘以一个更大的数，我们就实现了基本的随机。\n随机数会有影响，你可以自己随便选一个数字，但是可能会在具体功能上有一定的偏差，如果不需要解决问题那就用自己随便想的好了。\n让我们再来看看另一种随机数生成方法，Fracthash。\n这种方法没有用到sin，但是同样需要fract。\n这是封装后的代码。\n这个也不是我自己想出来的，之前写了一个shader评论区下面有人给我指点的意见。\n直接调用这个函数，我简单的将两个生成方法放在一起做了对比\n左边是sinhash，右边是fracthash，这咋看没啥区别，但是在后面加了一个iTime让其动起来后会发现sinhash有一丝割裂感，而fracthash会更加自然。\n具体可以看看我在shadertoy写的这个shader：https://www.shadertoy.com/view/mssXz7\n本期内容大概就是这些，希望对你有帮助。\n","date":"27 January 2023","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E7%94%9F%E6%88%90sinhash%E5%92%8Cfracthash-noise/","section":"Posts","summary":"在Shader中要生成随机数，就要生成Noise图，通常来说我们都知道可以用sin+fract函数。这种方法也叫Sinhash。","title":"生成Sinhash和Fracthash Noise","type":"posts"},{"content":"开始就是结束，结束便是开始。\n33年周期，我们做的事情总是会重复发生，不同的人，不同的时间，却是同一件事情。\n所谓既视感，或许是我们以前也曾经经历过这样的事情，一遍又一遍，不断重复，从生到死，从死到生，一遍又一遍的重复。\n我们所做的事情在未来已经被决定了，做法可能会不同，但是结果一定是相同的。\n凯尔特三角形告诉我们，不要用狭隘的二元对立去看待事物，要用三元对立，不止存在阴暗，阴阳，好坏，还有第三者才算完备。\nDark给我的感觉，就是如此，充满哲学，但是又富含科学，时光机的概念是非常具体的，而不是很抽象的展现出来，也是富有一定科学依据的。\n人情世故，科学哲理，悬疑烧脑，剧情复杂，这是我的主观陈述。\n特别是里面各种人物的关系，人与人之间的关系非常紧密，看似毫无关系的事物实质上有很大的关系，而且人性在这部剧里面表现的很充分，可以看得出真情实感。\n演员演技也很惊人，配乐也很精彩，剧情也很癫狂，简直堪称完美。\n一开始看的时候，伴随着各种疑惑与期待，越看越想看，不知不觉一个周末就把两部给干完了，里面的关系十分复杂，但是也不是一开始就全部塞给观众，而是渐进式推进。\n在最后，我们明白了一切，明白了这个规则是如何运转的，明白了起始，明白了终末，明白了一层套一层的阴谋，一层又一层的循环。\n这是一场对时间所有权的较量，亚当，夏娃，还有克劳迪亚·缇德曼。\n克劳迪亚是全剧最聪明的角色，无论从一开始“第一个女性老板”，以及到后面各种展开，包括从克劳迪亚杀死另一个世界自己的那段剧情，都是出乎意外但是又在情理之中的。\n最后克劳迪亚也打赢了这场争夺战，发现了真相，并且将一切都恢复如初。\n虽然是最惨的一家子，但是最后的结局也很圆满，所有该回来的都回来了，也是没有存有遗憾。\n遗憾的话，也不是说没有，那就是看了这么多，有很多想说的话，但是我没法用言语表露出来，虽然感受很多，但是正如freezemind那样影响我，dark也会越来越深的影响我吧，想想这两个东西的作者都是德国人，不愧是德国。\n","date":"21 December 2022","externalUrl":null,"permalink":"/feishiko-blog-page/proses/dark%E8%A7%82%E5%90%8E%E6%84%9F/","section":"Proses","summary":"开始就是结束，结束便是开始。","title":"Dark观后感","type":"proses"},{"content":"","date":"21 December 2022","externalUrl":null,"permalink":"/feishiko-blog-page/proses/","section":"Proses","summary":"","title":"Proses","type":"proses"},{"content":"最近在学网页前端，想试着实现低保真图下的那种伪文本，然后我试着去实现了一下。\n首先在HTML里面写下以下文本\n\u0026lt;!--HTML--\u0026gt; \u0026lt;div class = \u0026#34;colright\u0026#34;\u0026gt; \u0026lt;span class = \u0026#34;firstlow\u0026#34;\u0026gt; \u0026lt;/span\u0026gt;\u0026lt;br\u0026gt; \u0026lt;span class = \u0026#34;low\u0026#34;\u0026gt; \u0026lt;/span\u0026gt;\u0026lt;br\u0026gt; \u0026lt;span class = \u0026#34;low\u0026#34;\u0026gt; \u0026lt;/span\u0026gt;\u0026lt;br\u0026gt; \u0026lt;span class = \u0026#34;low\u0026#34;\u0026gt; \u0026lt;/span\u0026gt;\u0026lt;br\u0026gt; \u0026lt;span class = \u0026#34;low\u0026#34;\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; 我一般会在一个新块定义一个class，这样方便后续操作，然后接着因为我们要在对应行进行一定的操作，所以每一个span都有一个class，并且每一行都要手动换行。\n每个span里面的内容都是一个空格，主要的效果还是在CSS里面实现。\n/*CSS*/ .low { width: 10em; border-width: .5em; border-style: solid; border-radius: 2.9em; zoom: 200%; display: inline-block; } .firstlow { margin-left: 2em; width: 8em; border-width: .5em; border-style: solid; border-radius: 2.9em; zoom: 200%; display: inline-block; } CSS部分主要分两个部分，一个是low类里面进行操作，一个是firstlow类里面进行操作。控制第一行和其他行不一样。\nwidth属性控制低保真图文本的长度\nborder-width属性控制低保真图文本的宽度\nborder-style属性控制低保真图文本样式，因为本质是border\nborder-radius属性控制边缘光滑程度\nzoom属性放大\ndisplay:inline-block保持文本以inline-block的形式display\nmagin-left属性控制第一行位置的偏移\n最后结果大概是这个样子\n希望本文章能帮助到你\n","date":"16 December 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E4%BD%BF%E7%94%A8css%E5%AE%9E%E7%8E%B0%E4%BD%8E%E4%BF%9D%E7%9C%9F%E5%9B%BE%E6%96%87%E6%9C%AC/","section":"Posts","summary":"最近在学网页前端，想试着实现低保真图下的那种伪文本，然后我试着去实现了一下。","title":"使用CSS实现低保真图文本","type":"posts"},{"content":"\r你大概需要4min来阅读这篇文章 #\r最近有一直开始写博客，用markdown写，然后传各个平台，关于Bilibili，我们知道它是不支持markdown的，所以我们正常情况下是传不上去的。\n我最近自己研究出了一个方法，可以把markdown文件转成HTML，然后直接编辑网页前端代码。\n这边直接给出步骤。\n把markdown转成HTML #\r这个百度搜一搜应该就能出来，我自己用的是这个网站：https://www.bejson.com/convert/html2markdown/\n打开Bilibili的上传专栏界面 #\r如图所示\n接着按F12，打开开发者工具，打不开就手动旁边打开。\n然后在正文里面输入点东西，方便定位，比如HelloWorld。\n接着在开发者工具里面，ctrl + F快捷键快速搜索，输入你刚刚输进去的HelloWorld，找到对应的代码，然后右键，选择以HTML格式修改，把你markdown转HTML的那段粘贴进去，然后文章内容呈现出来了！\n注意事项 #\r首先Bilibili是不能传外链图片的，所以图片没法传进来，必须要Bilibili这边传图片上去。\n其次，链接貌似是传不上去的，会被警告然后退回，同时，()[]这种传链接的方法在Bilibili是用不了的。\n大概就是这么多，希望能对你有帮助。\n个人itch.io链接点这里\n个人Twitter链接点这里\n2022/10/30\n","date":"30 October 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E5%A6%82%E4%BD%95%E5%9C%A8bilibili%E4%B8%8A%E4%BC%A0markdown/","section":"Posts","summary":"你大概需要4min来阅读这篇文章 #\r最近有一直开始写博客，用markdown写，然后传各个平台，关于Bilibili，我们知道它是不支持markdown的，所以我们正常情况下是传不上去的。","title":"如何在bilibili上传markdown","type":"posts"},{"content":"\r你大概需要3min阅读这篇文章 #\r最近一直在写博客，考察过非常多的网站，想稍微分享一下自己的体会。\nGithub #\r首先是支持markdown的，流量应该不会有多少。\n免费图床，但是国内用不了，同时你也可以搞Github Pages在上面弄个静态网页，但是我没这么干，就直接写上去了。\n然后关于Github比较神奇的是国内的图床会有一点点问题，比如gif没法播放。我个人的解决方法是国内一套图床，github一套图床。\nCSDN #\r国内搜索权重最高的网站，臭名昭著，但是我还是要发。\n虽然对于游戏设计来说应该没人在CSDN上看，支持markdown。\n一般传CSDN主要还是其他程序方面的东西。\n知乎 #\r对独立游戏，游戏设计这些来说应该都是会有人看的，当然也包括程序方面的东西。支持markdown。\n简书 #\r一天只能传两篇，支持markdown，不支持外链图床，要图片得手动加，不过简书给我的印象是写文的会多一些。\n博客园 #\r发博客要实名，所以我没发，听朋友说氛围不错。\nBilibili #\r不支持markdown，如果你实在想发可以看看我写的上篇文章，有详细教程关于如何把自己写的markdown文件通过某种方式传到Bilibili上，会多一些步骤，但是整体不会很麻烦。\n国内图床我有个一直在用的，但是切记不能传违法乱纪的东西，聚合图床\n希望对你有所帮助！\n个人itch.io链接点这里\n个人Twitter链接点这里\n2022/10/30\n","date":"30 October 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E7%94%A8markdown%E5%9C%A8%E5%90%84%E4%B8%AA%E7%BD%91%E7%AB%99%E5%86%99%E5%8D%9A%E5%AE%A2/","section":"Posts","summary":"你大概需要3min阅读这篇文章 #\r最近一直在写博客，考察过非常多的网站，想稍微分享一下自己的体会。","title":"用Markdown在各个网站写博客","type":"posts"},{"content":"\r你大概需要3min来阅读这篇文章 #\r今天打了FAITH的第二章，因为我自己之前在itch玩过第一章(第一章免费)，所以我很确定这游戏是Gamemaker做的，我想再确认一下这游戏是不是换引擎了，点开游戏根目录，嗯~，确实没换。\n但是我发现了一个神奇的文件夹，里面充满了各种各样好玩的东西。\n我看了一下，都挺好玩的，然后看到reference vids这个文件夹的时候，我沉默了。\n那么里面到底装了什么东西呢，有点一言难尽，我确实猜到了dev可能会这么做，但是实际去看的时候有点生草。\n我先解释一下，reference vids的大致意思是参考视频。\n为了防止剧透，这里就放大家应该都看过的，第三章宣传片里面约翰神父手持圣经祈祷的画面。\n没错！这是dev本人自己录下来的，然后再描边，然后就是你在游戏里看到的约翰神父！\n让我们再来看看游戏中的这段CG是怎么样的：\n这种技术叫做转描。\n事先录好一段视频，然后紧接着在电脑上逐帧根据表演人的动作画出来，这种动画也和关键帧动画不同，叫做逐帧动画。\n关键帧动画的绘制方法大概是先画出各个关键的动作，比如我要画一个小球跳动的动画，第一帧是球在高处，第二帧是因为有一个非常快的速度而在半空中变形，第三帧落到地面然后变扁。那么我们接下来要做的事情就是在这三帧之间补全动画，让动画变得流畅起来，而不是闪现。\n逐帧动画逐帧动画的每秒帧数比关键帧动画要多很多，其特点是非常的流畅，但是制作成本也会极大增高。转描是制作逐帧动画的一种方式，通过现实的演员的动作写到电脑中然后用手画出的形式实现低廉的高帧率动画。但是这种形式貌似业界普遍不是很认可。\n但是适当运用转描也能做出一些有奇特风格的美术效果。而且实现起来比起直接上手去画要低廉很多，也不会有太多技术上的要求。可以适当运用在开发游戏中。\n个人itch.io链接点这里\n个人Twitter链接点这里\n2022/10/28\n","date":"28 October 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/faith%E7%9A%84%E8%BF%87%E5%9C%BAcg%E6%98%AF%E6%80%8E%E4%B9%88%E5%88%B6%E4%BD%9C%E7%9A%84/","section":"Posts","summary":"你大概需要3min来阅读这篇文章 #\r今天打了FAITH的第二章，因为我自己之前在itch玩过第一章(第一章免费)，所以我很确定这游戏是Gamemaker做的，我想再确认一下这游戏是不是换引擎了，点开游戏根目录，嗯~，确实没换。","title":"FAITH的过场CG是怎么制作的","type":"posts"},{"content":"\r你大概需要3min来阅读这篇文章 #\r这里是Feishiko，今天来分享一下关于如何在GMS2中实现时间缓动的效果。\n这个效果本身实现起来不难，所以这边主要是来点一下思路。\n图像绘制 #\r个人比较懒，所以就直接画出来了\noPlayer DrawEvent\ndraw_circle_color(x, y, 16, c_red, c_red, false);//绘制一个实心红球 oBullet DrawEvent\ndraw_circle_color(x, y, 5, c_blue, c_blue, false);//绘制一个实心蓝球 准备工作 #\r接下来点一下要怎么实现，首先子弹和玩家都会有一个速度，当时间减慢的时候，玩家的速度和子弹的速度都会变慢，所以我们要用他们的速度除以一个\u0026gt;1的数，或者乘以一个小数。\n首先玩家和子弹都需要一个速度，我这边都记为spd变量，然后让他们除以一个全局变量，如果你不想用全局变量也可设置一个object保持，假设是oGameCont，那就oGameCont.那个变量，这个是控制全局物体速度的核心变量。\n具体实现起来像这个样子。\nvar realSpeed = spd/global.gearTimes; 于是我们得到了计算之后真正的速度\n核心代码 #\r接下来如何让角色移动，子弹移动，都采用这个计算出来所得到的速度。\n这是玩家移动的大致代码\nif(keyboard_check(vk_up) \u0026amp;\u0026amp; keyboard_check(vk_right)){\rx += lengthdir_x(realSpeed, 45);\ry += lengthdir_y(realSpeed, 45);\t}else if(keyboard_check(vk_up) \u0026amp;\u0026amp; keyboard_check(vk_left)){\rx += lengthdir_x(realSpeed, 135);\ry += lengthdir_y(realSpeed, 135);\t}else if(keyboard_check(vk_left) \u0026amp;\u0026amp; keyboard_check(vk_down)){\rx += lengthdir_x(realSpeed, 180 + 45);\ry += lengthdir_y(realSpeed, 180 + 45);\t}else if(keyboard_check(vk_right) \u0026amp;\u0026amp; keyboard_check(vk_down)){\rx += lengthdir_x(realSpeed, 270 + 45);\ry += lengthdir_y(realSpeed, 270 + 45);\t}else if(keyboard_check(vk_up)){\rx += lengthdir_x(realSpeed, 90);\ry += lengthdir_y(realSpeed, 90);\t}else if(keyboard_check(vk_down)){\rx += lengthdir_x(realSpeed, 270);\ry += lengthdir_y(realSpeed, 270);\t}else if(keyboard_check(vk_left)){\rx += lengthdir_x(realSpeed, 180);\ry += lengthdir_y(realSpeed, 180);\t}else if(keyboard_check(vk_right)){\rx += lengthdir_x(realSpeed, 0);\ry += lengthdir_y(realSpeed, 0);\t} 完美移动\n虽然会繁琐一点，但是这样移动不会出现斜向更快的现象。\n子弹的移动代码\nx += lengthdir_x(realSpeed, direction);\ry += lengthdir_y(realSpeed, direction); 之所以运用这种方法而不用GMS自带的变量是因为这样会更好更方便操纵，也会更好的管理。当然视具体情况而定，有时候也可以小用一下GMS的内置变量，比如我照样用这里的direction。\n时缓和正常状态，我这里用了一个比较丝滑的方法过渡。\nif(keyboard_check(vk_shift)){\rglobal.gearTimes = lerp(global.gearTimes, 5, .05);\t}else{\rglobal.gearTimes = lerp(global.gearTimes, 1., .05);\t} 线性插值\n最后效果 #\r大概就是这么多，希望你喜欢本期内容！\n个人itch.io链接点这里\n个人Twitter链接点这里\n2022/10/15\n","date":"28 October 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/gms2%E4%B8%AD%E5%AE%9E%E7%8E%B0%E6%97%B6%E9%97%B4%E5%8F%98%E6%85%A2/","section":"Posts","summary":"你大概需要3min来阅读这篇文章 #\r这里是Feishiko，今天来分享一下关于如何在GMS2中实现时间缓动的效果。","title":"GMS2中实现时间变慢","type":"posts"},{"content":"\r你需要10min来阅读这篇文章 #\r突然想谈谈个人对游戏开发的时候绘制素材这一方面的见解。我不是专业搞美术的，目标也是能用就行，能看就行，今天就来谈谈如何在只有少量基础的情况下绘制出能用的像素画素材。\n预备知识 #\r形状 结构 比例 透视 线条 颜色 形状 #\r无论是像素画还是插画，任何一种形式对于形状的把控都有很高的要求，有时候画出来的东西怪怪的，可能就是形状没有抓好的问题。特别是像素画，有时候差一个像素带来的视觉效果就有很大的不同。\n圆形、矩形、三角形，是比较基础的三种形状，它们有着不同的属性。\n圆形：柔和\n矩形：稳定\n三角形：危险\n不同的形状所表现的功能是不一样的，不同的形状带给人的感受也是不一样的，然后我们就可以用一些基础形状来搭建我们复杂的各种物体了。\n因为像素画的尺寸很小，所以用色块来画比起插画会更方便一些，当然人各有所好，关于我自己是比较常用色块的。\n比如说我们要画一个药水瓶，让我们先仔细观察一下药水瓶是什么样的，然后通过观察，去刨析出其形状是什么样的，画在纸上透视是什么样的，结构是什么样的，比例是怎么样的，最后是什么样的颜色看上去会更符合需求。\n药水瓶有瓶底，瓶身，有个管口，有个软木塞，我们可以把各自的部位抽象成对应的形状，方便我们后续的工作。\n当然后续我们可以修正一下形状，调整一下位置，让其看起来更像一个药水瓶。然后填充对应的颜色，补充对应的细节。\n结构 #\r形状做到的是看上去是什么样，看上去是这个样，看上去是什么感受。结构则是所要画的物体在我们大脑中的建模应该是什么样，从各个角度观察应该用什么形状去把它画出来。\n还是刚刚的药水瓶，这次我们要做的工作是把要画的物体在自己的头脑中立体化，底下是一个球形，上面是一个圆柱形，软木塞是一个倒圆台。\n比例 #\r为了确保我们的结构足够的准确，看着自然，我们要回到现实中或者给定的参考图中的各种高度比，长度比，按照这个比例进行头脑中的建模。很多时候看着不像的问题也可能是比例没有把控好。\n透视 #\r透视有分为一点透视，二点透视，还有三点透视，最简单的一个原则是近大远小，实际情况下，我们在绘画的时候，特别是画物体，很难说只是单独的一点透视或者二点透视，需要多种相互结合，不过透视能够帮助我们去寻找物体之间的远近关系，以及要怎么从不同角度去看，去画这个物体。\n这方面的知识点我在这里不展开详细的说明。\n线条 #\r这里专门把线条抓出来讲，这里的线条不仅仅是你看到的有些像素画外面的outline，也包括没有outline的一些图块出来的像素画，因为像素画的限制性，每一个像素都非常重要，这里简单谈几个让像素画线条表现的更自然的几个案例。\n一般来说，我们叫这种情况Jagged，这样的线条，看上去并不会很自然，实际上看上去会很不舒适，为了解决这种问题，我们可以选择一开始的时候就选择aseprite的完美像素（Pixel-Perfect），或者手动修正。\n具体来说是每次落差的像素格子要徐徐递增徐徐递减，不能一下子落差太大。\n解决方法就是让段与段之间的差不要那么大，尽可能平缓一些。\n顺便提醒一下，有时候尺寸过小弯曲的线条画不下可以通过改变一些饱和度明度之类的值造成视觉上的一种误差以为有弯曲实际上是两个像素点，但是看自己的像素画风格，少颜色风格的那没有必要\n颜色 #\rHSB色盘是个很好的颜色模式，大家应该也都知道RGB色盘，但是RGB色盘对于调色来说并不合适，一般我们用HSB调色。\n个人推荐创建一个自己用的调色板，对于让自己游戏的颜色统一有着非常关键的作用，如果不会调色也没关系，网上都有现成的调色盘，拿过来用就行，而且颜色都很好看，这个不会有版权问题。\n另外，Aseprite里面有非常多的预设调色盘，也非常推荐拿来用。\n这是个人自己做的调色盘。\n关于颜色我还有一些要说的，大家可以试试颜色比较少的那种呈现形式，一个游戏里面只有几种颜色，这边有几种风格的游戏感兴趣的可以看看，Downwell, Apple Slash, Red Death\n正文 #\r艺术风格 便捷工具 FX Shader 艺术风格 #\r在这里，我会推荐几种实现起来比较简单的像素画风格。\n雅达利 #\r顾名思义，雅达利的风格，这种风格的像素画实现起来简单，但需要搭配一些简单的shader来实现一些屏幕效果。大概是扫描线。\n这里有个推荐的游戏，FAITH，有点放私货的意味了。\n这是个恐怖游戏，画面雅达利风格，不过不是全部的雅达利，很多地方还有许多动画CG，实现起来应该不难，但是非常的丝滑。\n故事方面也挺牛逼的，嘛，不能剧透，可以看看steam宣传片。一共三章，steam上这个版本是第三章夹杂前面两章一起打包发售的，最近刚刚发售。\n极简像素 #\r这种风格比前一种要常见太多了，制作也很简单，而且表现效果也很不错。\n这边照样上游戏，TowerClimb，这个游戏是我最喜欢的roguelite（全是私货），讲的是A man climb with hope，玩这个游戏的朋友特别喜欢拿Spelunky类比，因为这个游戏和Spelunky的相似度还是挺高的，不过前提说一下，玩法还是完全不一致的，Spelunky2和TowerClimb我都很喜欢，我都是这两个游戏的粉丝。这个游戏精细的地方体现在像素动画，shader，特别是音乐。\n这个游戏是别人推荐给我的，还没买，但是也是这种风格。Environmental Station Alpha，是一个类银河恶魔城。\nISO #\r这种风格应该不少见，第一次看可能以为是普通的二点透视，但是实际上两边并没有消失点！\nISO是Isometric的简称，在ISO的世界里遵循平行透视的原则，因此也不会存在消失点，这方面的游戏我玩的少，上一个老游戏，Game Dev Story。\nASCII #\r由像素组成的也是像素画（错乱）\n这种风格也应该会挺少见的，主要是由字符组成，所以做ASCII游戏在美术上会非常省力，只需要用字体就能画出一个个贴图（\n不过ASCII游戏由于可观性不是那么高嘛，玩的人就相对会少一些，所以除非你是做Roguelike，或者就指定了要做ASCII游戏，那我不建议你用这个风格。\n照例上图，Nethack\n便携工具 #\r选择框 #\r如果有的时候位置不太对，可以直接框起来然后移动一下位置，或者有的时候需要某一部位整体往某个方向移动几个像素，这个时候选择框就派上用场了。\nJumble Tool #\rAseprite里面的一个工具，大概是个画笔，类似于模糊工具，但是其作用是发生在像素画上的，它会将笔刷内的像素位置错排，造成一种很神奇的效果。\n镜像工具 #\r用来画一些对称的图形比较方便\n重复tiles #\r可以轻松画出无缝贴图\n当然，此外还有很多小工具我们可以拿去用的，可以自己多多探索一下。\nFX #\r这个算投机取巧吧（\n在aseprite里面有FX功能，里面的Convolution Matrix点进去之后可以给自己的素材加点花，不过这个适当加。\n还有就是有时候我们希望给自己的素材加点outline，那就可以直接FX那一块添加Outline。比如黑色的outline，有些时候为了显得更可爱一些会用白色的outline，当然怎么用看自己。还有人会根据向内的像素颜色手动选择对应的outline。\nShader #\r这个应该就不用我多说了，懂得都懂。\nGLSL ES，许多游戏引擎都有提供shader的实现方法，但是比较常见的版本还是GLSL ES。\n像Unity，Godot的Shader是进一步封装过的，但是其更原始的GLSL ES还是需要了解。\nShader的世界非常好玩，主要分片元着色器和顶点着色器，我们像素画主要用片元着色器，有时候增加一些合理的效果能为画面加分，但是请也不要滥用。\n如果你没用学过Shader语言，这里我推荐看看Book of Shaders，作为入门还是非常不错的。\n希望你喜欢本期内容！\n推荐从Github这边看本人的博客以获得最佳体验\n个人itch.io链接点这里\n个人Twitter链接点这里\n2022/10/27\n","date":"27 October 2022","externalUrl":null,"permalink":"/feishiko-blog-page/posts/%E6%B8%B8%E6%88%8F%E7%94%A8%E7%AE%80%E5%8D%95%E5%83%8F%E7%B4%A0%E7%94%BB%E7%B4%A0%E6%9D%90%E7%9A%84%E7%BB%98%E5%88%B6/","section":"Posts","summary":"你需要10min来阅读这篇文章 #\r突然想谈谈个人对游戏开发的时候绘制素材这一方面的见解。我不是专业搞美术的，目标也是能用就行，能看就行，今天就来谈谈如何在只有少量基础的情况下绘制出能用的像素画素材。","title":"游戏用简单像素画素材的绘制","type":"posts"},{"content":"","date":"1 January 0001","externalUrl":null,"permalink":"/feishiko-blog-page/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","date":"1 January 0001","externalUrl":null,"permalink":"/feishiko-blog-page/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"1 January 0001","externalUrl":null,"permalink":"/feishiko-blog-page/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"1 January 0001","externalUrl":null,"permalink":"/feishiko-blog-page/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"}]