加载中...

Cosmoscope

Cosma —
8/31/2024

To open a record, select a node in the graph or a title in the index.

意识形态批判

id : acc3d6d1
types : undefined
Begin : 8/31/2024

意识形态是什么?

意识形态是一种无意识信仰或恋物癖

  1. 不是因为上帝存在,所以你才跪下,而是因为你跪下,所以上帝存在,当你跪下时,信仰就会不请自来。
  2. 意识形态不需要你相信,只需要你按规行事,其隐含的逻辑是 信仰的物质仪式比内心认同更难反思和摧毁
  3. 意识形态中处于我们的“行为”,而不只是“观念”这边他是外在于我们意识的社会客观结构
  4. 要改变无意识信仰或恋物癖(意识形态),在心理上改变是无效的,必须改变客观状态,改变替我们相信的他者

现代社会恋物癖

即商品恋物癖,人与人的关系由封建社会的主奴关系转移到物与物的关系之中(社会关系:生产关系、等级关系不再直接以统治和奴役的人际关系显现,占用更多商品即为主)
恋物癖是一种误认的结构效果,是一个符号网络的效果被误解为一个对象的直接属性
(例如国王与臣民是互相认同后国王和臣民的概念才成立的;商品A与商品B的价值是在等价与非等价的概念后才具有的。其都非自身的天然属性)

认识或者揭示恋物癖的本质无法对其造成影响

商品恋物癖的特别症状--劳动力商品(无产阶级),症状是无意识体系的断裂点,揭示了其平稳运作的矛盾。揭示了资本主义的崩溃点,劳动力商品通过出卖自己,颠覆了普遍自由的普适性,暴露了其等价交换的虚假性与排他性。
意识形态批判的目的是揭示这一症状,还必须在符号结构中对其进行符号阐释,实现符号重构,使劳动力商品(症状)转化成无产阶级(圣状),圣状即在符号秩序中丧失居所被迫颠覆符号秩序建构出新型的符号秩序。

幻象

幻象是意识形态的核心部分,决定了人们看待世界的方式
在幻象中欲望是被建构,幻象总是他者的异化,欲望总是他者的欲望建构的幻象,掩盖了大他者的不一致性(阶级矛盾)
穿越幻象是颠覆意识形态统治的途径,意识到幻象只是幻象并与其保持距离。
当今最大的幻象是资本主义生产力发展的宏大叙事.

逾越快感与受虐快感

剩余快感可表达为两种形式
一、逾越快感,即违反法则,偷偷享乐
二、受虐快感,即服从法则,在法则禁锢中享受快感
在当今强大的资本主义秩序中
"左翼"们(后现代主义、法兰克福学派、女性主义、后殖民主义等左派)
**自知无力抵抗,**无法构想出整体式的社会变革
只能以多元、包容、共存等无伤大雅的概念进行假意批判
却实则补充甚至发展了资本主义体制
因此,他们被称为傻瓜,在可允许的范围内反叛,享受逾越快感
右翼则"化痛苦为力量",将苦难当做命运的考验
下位者们努力改变自己,以适应不公的现实制度
上位者们劝人向"善",告知努力就会有回报
并且认为稳定胜于一切,哪怕采取不正当的镇压,也要誓死维护现有秩序
因此,他们被称为坏蛋,遵循法则、以苦为乐,享受受虐快感
总之,剩余快感就是"奴仆们"在侍奉"主子"的过程中领取的奖励

大他者

大他者:社会规则,文化秩序,神信仰,意识形态,权利常识,习惯

齐泽克呼吁,不要"为了大他者牺牲",而要"为了空无(未来)牺牲(革命)
一个人如何才能根本的与符号秩序决裂?
你必须放弃使你进入符号秩序的某种内在诱惑(对象 a )
因为它是你苟活于符号秩序中的幻象和欲望
只有当你宁可牺牲最宝贵的东西也绝不委曲求全,这才是决裂的真正姿态
齐泽克倡导“向自己最宝贵的东西开枪"的疯狂创举
例如,电影《普通嫌疑犯》中,当主人公发现妻子和女儿被暴徒以枪挟持,他采取了将妻女射死的激进姿态。这个行为使他能够残忍地追踪到这一帮派的所有成员,并将他们全部杀死。主人公彻底否决了作为匪徒的大他者以及它给予的求饶希望,从而开启了复仇之路。
再如,革命并不是为了某个社会理想_做出的牺牲,它的因果逻辑是回溯生成的,在革命时,革命者总是前途渺茫,他们脱离了原有的社会符号秩序,却还没有新的身份和秩序,这实际上已将革命等同于"为空无牺牲"了,为空无牺牲,是一种绝不妥协的绝对否定性,它意味着让我们去做"成为我们所不是"的激进行为,撕毁与大他者的约定,放弃符号委任的身份,置身于实在界的恐怖空无中。

天使 Seraphim

id : 430fbf44
types : undefined
Begin : 8/31/2024

​ 风在粗粝旷阔的砾石原野上鼓动着,宛如心脏的鼓点阵阵跳动,形单影只的旅人缠绕着灰白色的面巾看不清样貌,突兀的恍似孤石。即便是平地,他的一步仿佛用尽了所有的气力,只能在这心跳的间隙间缓缓地挪动。

​ 天使降临了,旅人勉强地抬起头,看向前方徐徐展现的白光,白光中浮现了一只硕大的眼球,瞳孔是黑色的没有一丝亮色,粗壮的墨黑睫毛从眼球四周规律般的排布,并不弯曲螺旋状伸向外围,睫毛的尖端被一圈圈泛着银色金属光泽的椭圆圆盘围绕着、旋转着,以眼球与旅人的视线形成轴转动着。

​ 神迹显现了,旅人从家乡疯癫乞丐听到的预言是真的,突如其来的雨摧毁了家乡,起初的细雨没人在意,虽然雨滴中夹杂着细碎的灰白绒毛,雨一直在下,一个星期过去了,虽然有些许奇怪,但村民们早已习惯了,撑着粗布制成的伞在雨中行走,渐渐地,人们发现水虽浸润土地并遁入不见,房顶道路田垄目光皆是,绒毛千织万絮覆盖着万物。

Hexo 星空背景和流星特效

id : sj428g
types : undefined
keywords :
Begin : 9/1/2024

博客魔改教程总结(一) | Fomalhaut🥝

注: 该效果仅在dark mode下生效。

  1. [BlogRoot]/source/js目录下新建universe.js,输入以下代码:
 function dark() {window.requestAnimationFrame\=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame;var n,e,i,h,t=.05,s=document.getElementById("universe"),o=!0,a="180,184,240",r="226,225,142",d="226,225,224",c=\[\];function f(){n=window.innerWidth,e=window.innerHeight,i=.216\*n,s.setAttribute("width",n),s.setAttribute("height",e)}function u(){h.clearRect(0,0,n,e);for(var t=c.length,i=0;i<t;i++){var s=c\[i\];s.move(),s.fadeIn(),s.fadeOut(),s.draw()}}function y(){this.reset\=function(){this.giant\=m(3),this.comet\=!this.giant&&!o&&m(10),this.x\=l(0,n-10),this.y\=l(0,e),this.r\=l(1.1,2.6),this.dx\=l(t,6\*t)+(this.comet+1\-1)\*t\*l(50,120)+2\*t,this.dy\=-l(t,6\*t)-(this.comet+1\-1)\*t\*l(50,120),this.fadingOut\=null,this.fadingIn\=!0,this.opacity\=0,this.opacityTresh\=l(.2,1\-.4\*(this.comet+1\-1)),this.do\=l(5e-4,.002)+.001\*(this.comet+1\-1)},this.fadeIn\=function(){this.fadingIn&&(this.fadingIn\=!(this.opacity\>this.opacityTresh),this.opacity+=this.do)},this.fadeOut\=function(){this.fadingOut&&(this.fadingOut\=!(this.opacity<0),this.opacity\-=this.do/2,(this.x\>n||this.y<0)&&(this.fadingOut\=!1,this.reset()))},this.draw\=function(){if(h.beginPath(),this.giant)h.fillStyle\="rgba("+a+","+this.opacity+")",h.arc(this.x,this.y,2,0,2\*Math.PI,!1);else if(this.comet){h.fillStyle\="rgba("+d+","+this.opacity+")",h.arc(this.x,this.y,1.5,0,2\*Math.PI,!1);for(var t=0;t<30;t++)h.fillStyle\="rgba("+d+","+(this.opacity\-this.opacity/20\*t)+")",h.rect(this.x\-this.dx/4\*t,this.y\-this.dy/4\*t-2,2,2),h.fill()}else h.fillStyle\="rgba("+r+","+this.opacity+")",h.rect(this.x,this.y,this.r,this.r);h.closePath(),h.fill()},this.move\=function(){this.x+=this.dx,this.y+=this.dy,!1\===this.fadingOut&&this.reset(),(this.x\>n-n/4||this.y<0)&&(this.fadingOut\=!0)},setTimeout(function(){o=!1},50)}function m(t){return Math.floor(1e3\*Math.random())+1<10\*t}function l(t,i){return Math.random()\*(i-t)+t}f(),window.addEventListener("resize",f,!1),function(){h=s.getContext("2d");for(var t=0;t<i;t++)c\[t\]=new y,c\[t\].reset();u()}(),function t(){document.getElementsByTagName('html')\[0\].getAttribute('data-theme')=='dark'&&u(),window.requestAnimationFrame(t)}()};  
    dark()  
  1. [BlogRoot]/source/css目录下新建universe.css,输入以下代码:
/* 背景宇宙星光  */  
#universe{  
  display: block;  
  position: fixed;  
  margin: 0;  
  padding: 0;  
  border: 0;  
  outline: 0;  
  left: 0;  
  top: 0;  
  width: 100%;  
  height: 100%;  
  pointer-events: none;  
  /* 这个是调置顶的优先级的,-1在文章页下面,背景上面,个人推荐这种 */  
  z-index: -1;  
}  
  1. 在主题配置文件_config.butterfly.ymlinject配置项下填入:
inject:
  head:  
   - <link rel="stylesheet" href="/css/universe.css">  
  bottom:  
   - <canvas id="universe"></canvas>  
   - <script defer src="/js/universe.js"></script>  
  1. 重新编译即可看到效果。

Hexo 音乐播放器 - aplayer

id : sj443t
types : undefined
keywords :
Begin : 9/1/2024

Butterfly添加全局吸底Aplayer教程
Home - APlayer

初步设置

  1. 在博客根目录[BlogRoot]下打开终端,运行以下指令:
    npm install hexo-tag-aplayer --save  
  1. 在网站配置文件_config.yml中修改aplayer 配置项为:
    # 音乐插件  
    aplayer:   
      meting: true  
      asset_inject: false  
  1. 在主题配置文件中修改aplayerInject配置项为:
    # Inject the css and script (aplayer/meting)  
    aplayerInject:  
      enable: true  
      per_page: true  

普通界面播放器

在博客的音乐页面(\source\music\index.md文件)添加如下:

{% meting "7422861869" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:400px" "preload:none" "theme:#ad7a86"%}

或者使用 HTML 格式

<div class="aplayer" data-id="000PeZCQ1i4XVs" data-server="tencent" data-type="artist" data-preload="auto" data-theme="#3F51B5"></div>

全局吸底模式

在主题配置文件中设置:

inject:
  head:
  bottom:
    - <div class="aplayer no-destroy" data-id="7422861869" data-server="netease" data-type="playlist" data-fixed="true" data-autoplay="true" data-lrcType="-1"> </div>

几种不同的模式

普通列表模式

{% meting "7422861869" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:100px" "preload:none" "autoplay = false" "theme:#ad7a86"%}

单曲播放

<div class="aplayer no-destroy" data-id="1441758494" data-server="netease" data-type="song"  data-autoplay="true" data-lrcType="-1"> </div>

迷你模式

<div class="aplayer no-destroy" data-id="1441758494" data-server="netease" data-type="song" data-mini="true" data-autoplay="true" data-lrcType="-1"> </div>

其他

参数

server可选:netease(网易云音乐),tencent(QQ音乐),kugou(酷狗音乐),xiami(虾米音乐),baidu(百度音乐)。建议网易云
type可选:song(歌曲),playlist(歌单),album(专辑),search(搜索关键字),artist(歌手)。添加单曲选的歌曲,歌单选择playlist,可以自行尝试。
id获取示例: 打开网易云音乐,选择喜欢的歌单,在网页版打开,获取歌单list,填入即可。使用的时候将上边的ID号换为自己喜欢的歌单即可。==注意歌单中不能包括VIP音乐,否则无法解析==。建议单独建立一个歌单,以后有喜欢的音乐添加进去,网页也会自动同步添加。
lrcType设置为 -1默认显示歌词,放在fixed模式下比较合适。

换页不中断音乐

在主题配置文件中设置:

pjax:
 enable: ture
 exclude:

Hexo 文章加密

id : sj4cd2
types : undefined
keywords :
Begin : 9/1/2024

hexo-blog-encrypt

  1. 在根目录执行以下命令
    npm install --save hexo-blog-encrypt  
  1. Front matter配置方法
    ---  
    title: Hello World  
    tags:  
     - 作为日记加密  
    date: 2016-03-30 21:12:21  
    password: mikemessi  
    abstract: 有东西被加密了, 请输入密码查看.  
    message: 您好, 这里需要密码.  
    theme: xray  
    wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.  
    wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.  
    ---  
  1. 配置文件中针对tags的加密
# Security  
encrypt: # hexo-blog-encrypt  
  abstract: 有东西被加密了, 请输入密码查看.  
  message: 您好, 这里需要密码.  
  tags:  
   - {name: tagName, password: 密码A}  
   - {name: tagName, password: 密码B}  
  theme: xray  
  wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.  
  wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.  
  1. 你可以在线挑选你喜欢的主题,并应用到你的博客中:
  2. 重启项目进入对应的文章页面即可看到加密效果

Hexo 自定义Page - 网格页面

id : sj4dta
types : undefined
keywords :
Begin : 9/1/2024

效果展示:
Grid展示

  1. 创建pug页面文件
    创建themes/butterfly/layout/includes/page/grid.pug
#grid
  if site.data.grid
    each i in site.data.grid
      .grid-item
        h2.grid-item-title= i.class_name
        .grid-item-description= i.description
        .grid-item-content
          each item, index in i.grid_list
            .grid-item-content-item
              .grid-item-content-item-cover
                img.grid-item-content-item-image(src=url_for(item.image) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt=item.name)
              .grid-item-content-item-info
                .grid-item-content-item-name= item.name
                .grid-item-content-item-specification= item.specification
                .grid-item-content-item-description= item.description
                .grid-item-content-item-toolbar
                  if item.link.includes('https://') || item.link.includes('http://')
                    a.grid-item-content-item-link(href= item.link, target='_blank') 详情
                  else
                    a.grid-item-content-item-link(href= item.link, target='_blank') 查看文章

  1. 将文件植入butterfly
    在themes/butterfly/layout/page.pug中添加grid.pug
when 'grid'
        include includes/page/grid.pug
  1. 创建数据文件
    创建source/_data/grid.yml
- class_name: 追番
  description: ❤
  grid_list:
    - name: 深夜重拳
      specification: 真夜中ぱんチ
      description: 
      image: https://lain.bgm.tv/pic/cover/l/0c/cf/477207_0lA1U.jpg
      link: https://www.bgm.tv/subject/477207
    - name: 亚托莉 -我挚爱的时光-
      specification: ATRI -My Dear Moments- 
      description: 「我想完成主人留下的最后一道命令。在那之前,我来做夏生的脚!」在逐渐沉入海中的安宁小镇,少年与机器人少女难以忘怀的夏天开始了——
      image: https://lain.bgm.tv/pic/cover/l/66/6d/397604_TgJ63.jpg
      link: https://bgm.tv/subject/397604


- class_name: 阅读
  description: ❤
  grid_list:
    - name: 最后机会所在的城市 - 阿德里安·柴可夫斯基
      specification: City of Last Chances
      description: 伊尔玛一直都很黑暗,但从未像现在这样黑暗。这座城市在Palleseen占领的重拳之下,在犯罪黑社会的窒息之下,在工厂主的靴子之下,在可怜的穷人的重压之下,在古老诅咒的负担之下。点燃这场大火的火花是什么?尽管这座城市有难民、流浪者、杀人犯、疯子、狂热分子和小偷,但催化剂一如既往地将是锚木——那片黑暗的树林,那片原始的遗迹,那座满月时通往陌生遥远海岸的门户。
      image: https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1655508932i/60147395.jpg
      link: https://www.goodreads.com/book/show/60147395-city-of-last-chances
    - name: 灵魂囚笼 - 阿德里安·柴可夫斯基
      specification: Cage of Souls
      description: 太阳膨胀了,生病了,也许快死了。在其危险的光线下,沙德拉帕,最后一个城市,拥有不到10万人的灵魂。Shadrapar建在无数文明的废墟上,在早已死去的祖先的废墟上幸存下来,是一个博物馆、一个避难所、一个监狱,在一个对人类来说越来越陌生的世界上。
      image: https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1547654406i/40803025.jpg
      link: https://www.goodreads.com/book/show/40803025-cage-of-souls
  1. 引入css代码
    将以下css注入到主题配置文件中
.grid-item-content {  
  display: flex;  
  flex-direction: row;  
  flex-wrap: wrap;  
  margin: 0 -8px;  
}  
  
.grid-item-content-item {  
  width: calc(25% - 12px);  
  border-radius: 12px;  
  border: var(--style-border-always);  
  overflow: hidden;  
  margin: 8px 6px;  
  background: var(--heo-card-bg);  
  box-shadow: var(--heo-shadow-border);  
  min-height: 400px;  
  position: relative;  
}  
  
@media screen and (max-width: 1200px) {  
  .grid-item-content-item {  
    width: calc(50% - 12px);  
  }  
}  
  
@media screen and (max-width: 768px) {  
  .grid-item-content-item {  
    width: 100%;  
  }  
}  
  
.grid-item-content-item-info {  
  padding: 8px 16px 16px 16px;  
  margin-top: 12px;  
}  
  
.grid-item-content-item-name {  
  font-size: 18px;  
  font-weight: bold;  
  line-height: 1;  
  margin-bottom: 8px;  
  white-space: nowrap;  
  overflow: hidden;  
  text-overflow: ellipsis;  
  width: fit-content;  
}  
  
.grid-item-content-item-specification {  
  font-size: 12px;  
  color: var(--heo-secondtext);  
  line-height: 1;  
  margin-bottom: 12px;  
  white-space: nowrap;  
  overflow: hidden;  
  text-overflow: ellipsis;  
}  
  
.grid-item-content-item-description {  
  line-height: 20px;  
  color: var(--heo-secondtext);  
  height: 60px;  
  display: -webkit-box;  
  overflow: hidden;  
  -webkit-line-clamp: 3;  
  -webkit-box-orient: vertical;  
  font-size: 14px;  
}  
  
a.grid-item-content-item-link {  
  font-size: 12px;  
  background: var(--heo-gray-op);  
  padding: 4px 8px;  
  border-radius: 8px;  
  cursor: pointer;  
}  
  
a.grid-item-content-item-link:hover {  
  background: var(--heo-main);  
  color: var(--heo-white);  
}  
  
h2.equipment-item-title {  
  line-height: 1;  
}  
  
.equipment-item-description {  
  line-height: 1;  
  margin: 4px 0 8px 0;  
  color: var(--heo-secondtext);  
}  
  
.grid-item-content-item-cover {  
  width: 100%;  
  height: 200px;  
  background: var(--heo-secondbg);  
  display: flex;  
  justify-content: center;  
}  
  
img.grid-item-content-item-image {  
  object-fit: cover;  
  height: 100%;  
}  
  
div#equipment {  
  margin-top: 26px;  
}  
  
.grid-item-content-item-toolbar {  
  display: flex;  
  justify-content: space-between;  
  position: absolute;  
  bottom: 12px;  
  left: 0;  
  width: 100%;  
  padding: 0 16px;  
}  
  
a.bber-reply {  
  cursor: pointer;  
}
  1. 创建 Grid 页面
    创建/source/grid/index.md
---  
title: 近期所看
date: 2024-09-01 11:54:30
aside: false
type: grid 
---

群聚算法

id : sj4k77
types : undefined
keywords :
Begin : 9/1/2024

群聚算法的核心

  1. 群聚
  2. 对齐
  3. 分割

注:以GDScript为演示代码


extends CharacterBody2D
class_name Enemy

@onready var sprite = $Sprite2D

@onready var player:Node2D = get_tree().get_first_node_in_group("player")
@onready var speed = 200.0

# 转向微分
var steering 
# 当前方向
var current_direction = Vector2.ZERO
# 目标方向
var desired_direction = Vector2.UP
# 目标速度
var desired_velocity = Vector2.ZERO
# 玩家方向
var player_direction = Vector2.ZERO
# 群聚点方向
var flock_direction = Vector2.ZERO
# 群聚间隔阻碍均方向
var aave = Vector2.ZERO
# 群聚均方向
var dave = Vector2.ZERO
# 群聚均位置
var pave = Vector2.ZERO
# 视野距离
var sight_distance = 200
# 间隔距离
var interval_distance = 30
# 视野范围因子,为视野范围的一半
var sight_range_factor = deg_to_rad(45)
# 避障方向
var avoid_obstacle_direction = Vector2.ZERO
# 避障距离
var avoid_obstacle_diatance := 50.0
# 避障因子
var avoid_obstacle_factor:float = 1.0
# 是否存在障碍
var has_obstacle := false
# 障碍位置
var obstacle_position := Vector2.ZERO

func _physics_process(delta):
	player = get_tree().get_first_node_in_group("player")
	player_direction = global_position.direction_to(player.global_position)
	
	caculate_flock()
	caculate_avoid_obstacle()
	
	if has_obstacle:
		avoid_obstacle_direction =  (obstacle_position - global_position).normalized().orthogonal() 
		avoid_obstacle_factor = 200/(global_position.distance_to(obstacle_position))
		desired_direction = avoid_obstacle_direction
	else:
		desired_direction = (player_direction * 0.4 + flock_direction * 0.3 + dave * 0.3 + aave * 0.8).normalized()
	
	desired_velocity = desired_direction * speed
	steering = (desired_velocity - velocity) * delta * 3 * avoid_obstacle_factor
	velocity += steering
	current_direction = velocity/speed
	
	sprite.flip_h = current_direction.x > 0
	move_and_slide()
	
	queue_redraw()

# 群聚算法 (群聚、对齐、分隔)
func caculate_flock():
	var enemies:Array = get_tree().get_nodes_in_group("enemy")
	aave = Vector2.ZERO
	dave = Vector2.ZERO
	pave = Vector2.ZERO
	if enemies.size() <= 1:return
	for enemy in enemies:
		var e:Enemy = enemy
		var enemy_direction = global_position.direction_to(e.global_position)
		if e.global_position.distance_to(global_position) <= sight_distance and current_direction.angle_to(enemy_direction) <= sight_range_factor:
			dave += e.current_direction
			pave += e.global_position
			
			if e.global_position.distance_to(global_position) <= interval_distance:
				aave += (-enemy_direction)
	
	dave = dave.normalized()
	pave = pave/enemies.size()
	aave = aave.normalized()
	
	flock_direction = global_position.direction_to(pave)

# 避障计算
func caculate_avoid_obstacle():
	avoid_obstacle_direction = Vector2.ZERO

	var space_state = get_world_2d().direct_space_state
	var direction = current_direction
	var query = PhysicsRayQueryParameters2D.create(global_position,global_position + (direction* sight_distance))
	query.collide_with_areas = true
	var result = space_state.intersect_ray(query)

	if not result.is_empty() and result["collider"].is_in_group("tilemap"):
		has_obstacle = true
		obstacle_position = result["position"]
	
	if has_obstacle and global_position.distance_to(obstacle_position) > avoid_obstacle_diatance:
		has_obstacle = false
		obstacle_position = Vector2.ZERO
		avoid_obstacle_factor = 1.0
	
	
#func _draw():
#	draw_line(Vector2.ZERO,current_direction * sight_distance,Color8(255, 0, 0) ,6)


LINQ 初步使用

id : sj4kdd
types : undefined
keywords :
Begin : 9/1/2024

查询表达式 query
链式表达式 chained

延迟执行与消耗
一个表达式只有当“消耗”时才会真正开始执行
消耗条件:
1.Dump()、ToDictionary()、ToList()、ToArray()
2.Count()、Min() Max() Sum()
3.遍历foreach
4.Take() First() Last()

LINQ并不是只针对可枚举类型的(IEnumerable)类型的扩展方法,可以用于:

  • IEnumerable
  • IOrderedEnumerable
  • IQueryable
  • ParallelQuery
  • ...

选择列表中大于等于4的偶数

var res = 
	from n in list
	where n % 2 = 0 && n >= 4
	orderby n 
	select n;

# 链式表达式不需要以select结尾
var res = list.Where(n=>n%2=0 && n>=4).OrderBy(n=n);

求两个数组交集

var res = arr1.Intersect(arr2);

var res = 
	from n in arr1
	where arr2.Contains(n)
	select n;

分组


var res = arr.GrounpBy(x=>x).Select(g => new {g.Key, Count = g.Count()});// 返回一个匿名类的列表

var res = arr.GrounpBy(x=>x).ToDictionary(g => g.Key, g => g.Count()});// 返回一个数组


var res = 
	from x in arr
	group x by x into g
	select new {g.key, Count = g.Count()};

一些常用函数:


#多线程
var arr = Enumerable
	.Range(1,10)
	.AsParallel() //转成多线程
	.AsOrdered() //排序
	.Select(x => {
		Thread.Sleep(500); 
		return x * x;
	})
	.AsSequential() //转回单线程

#展平
var res = mat.SelectMany(n => n).ToArry();

#笛卡尔积
var prods = 
	from i in Enumerable.Range(0,5)
	from j in Enumerable.Range(0,5)
	from k in Enumerable.Range(0,5)
	select $"{i},{j},{k}";
	
 统计字母频率
var words = new string[]{"tom", "jerry","spike","tyke"
						,“butch”,“quacker”}
var query = 
	from w in words
	from c in w
	group c by c into g
	select new {g.Key , Count = g.Count()} into a
	orderby a.Count descending
	select a;

var query = words
	.SelectMany(c => c)
	.GroupBy(c => c)
	.Select(g => new {g.Key,Count = g.Count()})
	.OrderByDescending(g => g.Count);

# 批量下载文件
var tasks = urls
	.Select(url => DownloadAsync(url,url.Split('/').Last()));

var tasks = 
	from url in urls
	let filename = url.Split('/').Last()
	orderby filename
	select DownloadAsync(url,filename);

await Task.WhenAll(tasks);

async Task DownloadAsync(string url,string filename)
{
	await Task.Delay(1000);
	$"{filename} download.".Dump();
}


# 寻找派生类
var types = Assembly
	.GetAssembly(typeof(Exception))
	.GetTypes()
	.Cache();

types
	.Where(t=>t.IsAssignableTo(typeof(Exception)))
	.Select(t=>t.name)
	.OrderBy(t=>t.Length)

新手常见错误

绕远路

  • 不知道First() Last() Average()方法
  • 不知道 Count() First() Min() Sum()等方法可以传参
  • 不知道 Max() MaxBy() 区别
  • 不知道各种 Default

滥用开销大

  • 滥用 ToList ToDictionary等等
  • 滥用 Count()
  • 滥用OrderBy() [可以用Arry.Sort替代]
  • 不知道 First() 和 Single() 区别
    • First 拿到第一个
    • Single 拿到唯一的一个(会进行遍历)

Hexo X Obsidian 组合

id : sj6xhv
types : undefined
keywords :
Begin : 9/2/2024

文章模板 - 快速创建Post

配置插件

安装第三方插件 Templater
创建 Templates 目录,修改配置指定 Template 的目录。修改配置项 Template folder location 为 Templates
然后再此目录下创建 Front-matter.md 文件,此文件用作 hexo 的 frontmatter 模板。

---  
title: <% tp.file.title %>  
categories:  
  - <% tp.file.folder(relative=true) %>  
tags:  
  - ''  
abbrlink: <% tp.user.get_url() %>  
date: <% tp.date.now(format="YYYY-MM-DD HH:mm:ss") %>  
cover: ''  
---

其中:

模板 作用
tp.file.title 获取到的就是文件名
tp.file.folder(relative=true) 是获取文件所在的相对路径,就是所在目录名字
tp.user.get_url() 是自定义方法,脚本后面展示,用于自动生成博客的 url
tp.date.now(format=”YYYY-MM-DD HH:mm:ss”) 以指定格式格式化时间

详细的变量使用请查看 Templater 官方文档https://silentvoid13.github.io/Templater/

自定义脚本

创建目录 Scripts, 然后在设置里配置 Script files folder location 为 Scripts

获取 GUID 脚本 Scripts/get_url.js 这里是取出当前时间戳,然后去掉毫秒,再按照 0-9 + a-z 一共 36 个字符来表示,得到一个 6 位不重复的字符串,用作文章的链接。

function generateTimestampUrl() {  
  var timestamp = Math.round(new Date() / 1000);  
  var url = timestamp.toString(36)  
  return url;  
}  
module.exports = generateTimestampUrl;

然后每次创建新 markdown 文件的时候,只需要点击 templater 按钮, 然后就会自动生成我们需要的 frontmatter.

隐藏 Templates 和 Scripts 目录

安装 Hidden Folder 插件,可以隐藏这两个目录。

安装Obsidian文字处理插件

Pangu

给中英文之间迅速添加空格
GitHub - Natumsol/obsidian-pangu: 为 Obsidian 笔记加上「盘古之白」,排版强迫症者的福音。 | A small plugin aims to add space between Chinese Characters and English Alphabet, and it is a boon for typographically compulsive people.

Linter

可对笔记进行格式和样式设置
GitHub - platers/obsidian-linter: An Obsidian plugin that formats and styles your notes with a focus on configurability and extensibility.

参考:
构建 Obsidian 的 Hexo 写作工作流 | 半方池水半方田 (uuanqin.top)
Hexo 博客适配 Obsidian 新语法 | 半方池水半方田 (uuanqin.top)

NPBehave Introduction

id : sj7462
types : undefined
keywords :
Begin : 9/3/2024

群聚算法

本文侧重点在 NPBehave 的代码逻辑而非使用。

前言

基础介绍

NPBahave 是 GitHub 上开源的一个行为树,其代码简洁有力,与 Unity 耦合较低,适合拿来做双端行为树。

meniku/NPBehave: Event Driven Behavior Trees for Unity 3D (github.com)

NPBehave 基于功能强大且灵活的基于代码的方法,从 behavior 库定义行为树,并混合了虚幻引擎的一些很棒的行为树概念。与传统的行为树不同,事件驱动的行为树不需要每帧从根节点遍历。它们保持当前状态,只有在实际需要时才继续遍历。这使得它们的性能更高,使用起来也更简单。

使用案例

案例一:Hello World

代码会一直输出"Hello World"


private Root behaviorTree;
behaviorTree = new Root(
	new Action(() => Debug.Log("Hello World!"))
);
behaviorTree.Start();

案例二:阻止结点执行

使用 WaitUntilStopped 可以控制结点执行完向父级告知是否停止

// 序列Composite
behaviorTree = new Root(
	new Sequence(
		new Action(() => Debug.Log("Hello World!")),
		new WaitUntilStopped()
	)
);
behaviorTree.Start();

运行逻辑

重要模块功能注释


时钟 Clock

时钟用于连续或者间断执行函数。

注:Observer 即一个委托 Action

内部:
Timer
属性
timerPool:Timer 列表
updateObservers: 每帧执行的 Observer 列表
timers:单帧执行的 Observers 字典(由 Timer 决定), Observer-Timer 的字典
addTimers:缓存的待添加 Timer,字典,Observer-Timer
removeTimers:缓存的待移除 Timer,HashSet
addObservers:缓存的待添加 Observer,HashSet
removeObserver:缓存的待移除 Observer,HashSet
isInUpdate:是否在执行 Observer,用于控制 Timer 与 Observer 缓存的增删
elapsedTime:该 Clock 执行函数(调用 Update)经过的时间
方法
AddTimer:将一个 Observer 添加到 timers/addTimers、从 removeTimers 中移除(由 isInUpdate 决定),并赋予 Timer 传参
RemoveTimer:将一个 Observer 从 timers/addTimers 中移除、添加到 removeTimers 中(由 isInUpdate 决定)
HasTimer:返回是否有指定的 Observer 存在于 timers/addTimers/ !removeTimers
AddUpdateObserver:将一个 Observer 添加到 updateObservers/addObservers、从 removeObservers 中移除(由 isInUpdate 决定)
RemoveObservers:将一个 Observer 从 Observers/addObservers 中移除、添加到 removeObservers 中(由 isInUpdate 决定)
HasUpdateObservers:返回是否有指定的 Observer 存在于 updateObservers/addObservers/!removeObservers
Update:核心函数 1. 执行 updateObservers 中的委托(不存在于 removeObservers) 2. 执行 timers 中的委托(不存在于 removeObservers) 3. 使 timer/observer 的 add/remove 缓存生效,后清空缓存

  • getTimerPool:返回分配的 Timer,如果 timePool 有未 used 的 Timer 则返回此 Timer,无则创建新 Timer 加入 timerPool 并返回

黑版 Blackboard

黑板是一个树状图存储数据的数据结构。

内部
Type
Notification :通知
属性
clock
isNotiflying
parentBlackboard: 父黑板
children: 子黑板哈希集
data: string-object 数据字典
observers :string-ActionList 委托列表字典
addObservers :string-Action 待添加的委托字典
removeObservers :string-Action 待移除的委托字典
notifcations:通知列表
notificationsDispatch:待分发通知列表
方法
Enable:将自身添加入父黑板的子黑板(父黑板需存在)
Disable:将自身从父黑板的子黑板移除(父黑板需存在),调用 clock 的 RemoveTimer 将自身的 NotifiyObservers 函数移除。
Set:赋值传入键值对至 data。 添加 Add/Change Notification 至 notifications, 调用 clock 的 AddTimers 传入 并调用自身的 NotifiyObservers 函数,并调用父黑板的 Set 方法。
Unset:从 data 中移除传入键,添加 Remove Notification 至 notifications。, 调用 clock 的 AddTimers 传入 并调用自身的 NotifiyObservers 函数。
Get:返回传入键在自身 data 映射的值,如果自身 data 无该键则调用父黑板的 Get 方法
Isset:返回传入键是否存在于自身 data 或父黑板 data 中
AddObserver:调用 getObserverList 获取传入键的委托列表,将一个 Observer 添加到 observers/addObservers、从 removeObservers 中移除(由 isNotiflying 决定)
RemoveObserver:调用 getObserverList 获取传入键的委托列表,将一个 Observer 从 observers/addObservers 中移除、添加到 removeObservers 中(由 isNotiflying 决定)
NotiflyObservers: 1. 清空 notificationsDispatch;将 notifications 添加至自身与之黑板的 notificationsDispatch;调用子黑板 clock 的 AddTimer 方法传入子黑板的 NotifiyObservers 方法;清空 notifications 2. 将 notificationsDispatch 的每个 notification 传入相同键的 Observer 作为参数进行调用 3. 使 add/remove Observer 缓存生效,后清空缓存
getObserverList:返回从传入的委托列表字典中获取传入键对应的值,如无则创建再返回


结点 Node

内部
State:枚举,表示 Node 状态(INACTIVE/ACTIVE/STOP_REQUESTED)
属性
CurrentState:State,表示该 Node 状态,默认为 INACTIVE
RootNode :Root,根结点
ParentNode:Container,父结点
Label:string
Name:string
Blackboard
Clock
方法
IsStopRequested:返回自身 CurrentState 是否为 STOP_REQUESTED
IsActive:返回自身 CurrentState 是否为 ACTIVE
SetRoot
SetParent
Start:设置 CurrntState 为 ACTIVE,调用 DoStart 方法
Stop:设置 CurrntState 为 STOP_REQUESTED,调用 DoStop 方法
ParentCompositeStopped:调用 DoParentCompositeStopped 方法
DoStart:空方法
DoStop:空方法
DoParentCompositeStopped:空方法
Stopped:设置 CurrntState 为 INACTIVE,调用 ParentNode 的 ChildStopped(this,true)*
ToString:返回 Name+{Label}的拼接字符串
GetPath:返回父路径+“/"+Name


容器 Container : Node

属性
Collapse:bool,容器是否损坏
方法
ChildStopped:传参调用 DoChildStopped 方法
DoChildStopped


装饰器 Decorator : Container

属性
Decoratee:Node,被装饰结点
方法
SetRoot
ParentCompositeStopped:调用基类、被装饰结点该方法


根结点 Root : Decorator

属性
mainNode:Node
Blackboard
Clock
方法
DoStart: Enable Blackboard,Start mainNode
DoStop:当 mainNode 是激活时调用其 Stop(),反之从 Clock 中移除其 Start 方法委托
DoChildStop(NodeA,boolA):如果自身处于告知停止状态则 Disable Blackboard 并 Stopped(boolA),反之将 mainNode.Start 方法传入 Clock.AddTimer


运行逻辑解释

案例一:输出“Hello World”解释

private Root behaviorTree;
behaviorTree = new Root(
	new Action(() => Debug.Log("Hello World!"))
);
//-> mainNode = Action
behaviorTree.Start();
/*
-> Root.DoStart->Action.DoStart
{
	Debug.Log("Hello World!")
	Action(Node).Stopped(success=true)
}
->Action.ParentNode(Root).ChildStopped(Node=Action,success=true)
->Root.DoChildStopped(Node=Action,success=true) if !IsStopRequested ->
{
	this.clock.AddTimer(0, 0, this.mainNode.Start) //再次调用Action.Start
}
如上图流程所示,执行会进入一个调用Action.Start的循环,结果是一直输出"Hello World"
*/

总结:当 Action 结点执行完毕后会向父级结点传递一个子结点执行成功并终止的结果,Root 收到该结果会再次执行子结点。

案例二:Sequence、WaitUntilStopped 解释

// 序列Composite
behaviorTree = new Root(
	new Sequence(
		new Action(() => Debug.Log("Hello World!")),
		new WaitUntilStopped()
	)
);
/* {
	mainNode = Sequence
	Sequence.Chidren = [Action,WaitUntilStopped]
}*/
behaviorTree.Start();
/*
->Sequence.DoStart->Sequence.ProcessChildren
->Action.Start
{
	Debug.Log("Hello World!")
	Action(Node).Stopped(success=true)
}
->Action.ParentNode(Sequence).ChildStoped->Sequence.DoChildStop(success=true)
->ProcessChildren if result=true
->WaitUntilStopped(Node).Start->Node.DoStart{}
//此时因为Node的Start方法为空,所以暂停执行
*/

总结:使用 Sequence 结点可以按传入结点列表的顺序执行结点,当运行 WaitUntilStopped 结点时因为其未方法为空所以暂停执行了。

结点类型汇总

Root

  • Root(Node mainNode):无休止地运行 mainNode,不论任何情况
  • Root(Blackboard Blackboard, Node mainNode):使用给定的黑板,而不是实例化一个;无休止地运行给定的 mainNode,不论任何情况
  • Root(Blackboard blackboard, Clock clock, Node mainNode):使用给定的黑板而不是实例化一个;使用给定的时钟,而不是使用 UnityContext 中的全局时钟;无休止地运行给定的 mainNode,不论任何情况

组合结点

Selector

  • Selector(params Node[] children):按顺序运行子元素,直到其中一个子元素成功(如果其中一个子元素成功,则成功)。

Sequence

  • Sequence(params Node[] children):按顺序运行子节点,直到其中一个失败(如果所有子节点都没有失败,则成功)。

Parallel

  • Parallel(Policy successPolicy, Policy failurePolicy, params Node[] children): 并行运行子节点。
  • 当 failurePolocity 为 Polociy.ONE。当其中一个孩子失败时,并行就会停止,返回失败。
  • 当 successPolicy 为 Policy.ONE。当其中一个孩子失败时,并行将停止,返回成功。
  • 如果并行没有因为 Policy.ONE 而停止。它会一直执行,直到所有的子节点都完成,然后如果所有的子节点都成功或者失败,它就会返回成功。

RandomSelector

  • RandomSelector(params Node[] children):按随机顺序运行子进程,直到其中一个子进程成功(如果其中一个子进程成功,则成功)。注意,对于打断规则,最初的顺序定义了优先级。

RandomSequence

  • RandomSequence(params Node[] children):以随机顺序运行子节点,直到其中一个失败(如果没有子节点失败,则成功)。注意,对于打断规则,最初的顺序定义了优先级。

任务结点

Action

  • Action(System.Action action):(总是立即成功完成)
  • Action(System.Func singleFrameFunc): 可以成功或失败的操作(返回 false to fail)
  • Action(Func<bool, Result> multiframeFunc):可以在多个帧上执行的操作(
    Result.BLOCKED——你的行动还没有准备好
    Result.PROGRESS——当你忙着这个行为的时候,
    Result.SUCCESS 或 Result.FAILED——成功或失败)。
  • Action(Func<Request, Result> multiframeFunc2): 与上面类似,但是 Request 会给你一个状态信息:
    Request.START 表示它是您的操作或返回结果的第一个标记或者是 Result.BLOCKED 最后一个标记。
    Request.UPDATE 表示您最后一次返回 Request.PROGRESS;
    Request.CANCEL 意味着您需要取消操作并返回结果。成功或者 Result.FAILED。

Wait

  • Wait(float seconds): 等待给定的秒,随机误差为 0.05 *秒
  • Wait(float seconds, float randomVariance): 用给定的随机变量等待给定的秒数
  • Wait(string blackboardKey, float randomVariance = 0f):
  • Wait(System.Func function, float randomVariance = 0f): 等待在给定的 blackboardKey 中设置为 float 的秒数

WaitUntilStopped

  • WaitUntilStopped(bool sucessWhenStopped = false):等待被其他节点停止。它通常用于 Selector 的末尾,等待任何 before 头的同级 BlackboardCondition、BlackboardQuery 或 Condition 变为活动状态。

装饰器结点

BlackboardCondition

  • BlackboardCondition(string key, Operator operator, object value, Stops stopsOnChange, Node decoratee): 只有当黑板的键匹配 op / value 条件时,才执行 decoratee 节点。如果 stopsOnChange 不是 NONE,则节点将根据 stopsOnChange stop 规则观察黑板上的变化并停止运行节点的执行。
  • BlackboardCondition(string key, Operator operator, Stops stopsOnChange, Node decoratee): 只有当黑板的键与 op 条件匹配时才执行 decoratee 节点(例如,对于一个只检查 IS_SET 的操作数操作符)。如果 stopsOnChange 不是 NONE,则节点将根据 stopsOnChange stop 规则观察黑板上的变化并停止运行节点的执行。

BlackboardQuery

  • BlackboardQuery(string[] keys, Stops stopsOnChange, System.Func query, Node decoratee):BlackboardCondition 只允许检查一个键,而这个将观察多个黑板键,并在其中一个值发生变化时立即计算给定的查询函数,从而允许您在黑板上执行任意查询。它将根据 stopsOnChange stop 规则停止运行节点。

Condition

  • Condition(Func condition, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点
  • Condition(Func condition, Stops stopsOnChange, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点。根据 stopsOnChange stop 规则重新评估每个帧的条件并停止运行节点。
  • Condition(Func condition, Stops stopsOnChange, float checkInterval, float randomVariance, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点。在给定的校验间隔和随机方差处重新评估条件,并根据 stopsOnChange stop 规则停止运行节点。

Cooldown

  • Cooldown(float cooldownTime, Node decoratee):立即运行 decoratee,但前提是最后一次执行至少没有超过 cooldownTime
  • Cooldown(float cooldownTime, float randomVariation, Node decoratee): 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime
  • Cooldown(float cooldownTime, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee): 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime,当 resetOnFailure 为真时,如果修饰节点失败,则重置冷却时间
  • Cooldown(float cooldownTime, float randomVariation, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee) 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime,当 startAfterDecoratee 为 true 时,将在 decoratee 完成后而不是启动时启动冷却计时器。当 resetOnFailure 为真时,如果修饰节点失败,则重置冷却时间。

Failer

  • Failer(Node decoratee): 总是失败,不管装饰者的结果如何。

Inverter

  • Inverter(Node decoratee): 如果 decoratee 成功,则逆变器失败;如果 decoratee 失败,则逆变器成功。

Observer

  • Observer(Action onStart, Action onStop, Node decoratee): 一旦 decoratee 启动,运行给定的 onStart lambda;一旦 decoratee 结束,运行 onStop(bool result) lambda。它有点像一种特殊的服务,因为它不会直接干扰 decoratee 的执行。

Random

  • Random(float probability, Node decoratee): 以给定的概率,0 到 1 运行 decoratee。

Repeater

  • Repeater(Node decoratee): 无限重复给定的装饰,除非失败
  • Repeater(int loopCount, Node decoratee): 执行给定的 decoratee 循环次数(0 表示 decoratee 永远不会运行)。如果 decoratee 停止,循环将中止,并且中继器失败。如果 decoratee 的所有执行都成功,那么中继器将会成功。

Service

  • Service(Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后每次运行服务。
  • Service(float interval, Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后按给定的间隔运行服务。
  • Service(float interval, float randomVariation, Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后在给定的时间间隔内以随机变量的方式运行服务。

Succeeder

  • Succeeder(Node decoratee): 永远要成功,不管装饰器是否成功

TimeMax

  • TimeMax(float limit, bool waitForChildButFailOnLimitReached, Node decoratee): 运行给定的 decoratee。如果 decoratee 没有在限制时间内完成,则执行将失败。如果 waitforchildbutfailonlimitarrived 为 true,它将等待 decoratee 完成,但仍然失败。
  • TimeMax(float limit, float randomVariation, bool waitForChildButFailOnLimitReached, Node decoratee):运行给定的 decoratee。如果 decoratee 没有在限制和随机变化范围内完成,则执行将失败。如果 waitforchildbutfailonlimitarrived 为 true,它将等待 decoratee 完成,但仍然失败。

TimeMin

  • TimeMin(float limit, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到限制时间之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果被装饰者在达到限制时间之前失败,装饰者将立即停止。
  • TimeMin(float limit, bool waitOnFailure, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到限制时间之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果 waitOnFailure 为真,那么当 decoratee 失败时,decoratee 也将等待。
  • TimeMin(float limit, float randomVariation, bool waitOnFailure, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到随机变化时间限制之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果 waitOnFailure 为真,那么当 decoratee 失败时,decoratee 也将等待。

WaitForCondition

  • WaitForCondition(Func condition, Node decoratee): 延迟 decoratee 节点的执行,直到条件为真,检查每一帧
  • WaitForCondition(Func condition, float checkInterval, float randomVariance, Node decoratee): 延迟 decoratee 节点的执行,直到条件为真,使用给定的 checkInterval 和 randomVariance 进行检查

扩展内容

Hexo 自定义Page - 音乐页面

id : sjccoh
types : undefined
keywords :
Begin : 9/5/2024

主题配置文件中配置CDN.option.meting.js

// 配置CDN或者是本地引用
meting_js: https://npm.elemecdn.com/[email protected]/assets/js/Meting2.min.js

创建 source/music/index.md

``` md

title: 音乐馆
date: 2024-09-04 22:40:30
type: music
aplayer: true
top_img: false
comments: false
aside: false

### 创建 `themes/butterfly/layout/includes/page/music.pug`

// id:歌单 server:音乐服务提供者
#anMusic-page
meting-js(id="1111111" server="netease" type="playlist" mutex="true" preload="auto" theme="var(--furo-main)" order="list")

### 修改 `themes/butterfly/layout/page.pug`
``` diff
  #page  
-   if top_img === false  
+   if top_img === false && page.type != 'music'  
      h1.page-title= page.title  
    case page.type  
      when 'link'  
        include includes/page/flink.pug  
      ...  
+     when 'music'  
+       include includes/page/music.pug  
      default  
        include includes/page/default-page.pug

修改themes/butterfly/layout/includes/layout.pug

  body  
    if theme.background  
      #web_bg  
+     #an_music_bg

创建引用 js css

var furo = {
    //设置音乐背景为固定
    setDefaultMusigBg:function(){
        const anMusicBg = document.getElementById("an_music_bg");
        anMusicBg.style.backgroundImage = "url(/img/background.jpg)";
        console.info(anMusicBg);
    },

    // 设置背景为音乐背景
    changeMusicBg: function (isChangeBg = true) {
        if (window.location.pathname != "/music/") {
            return;
        }
        const anMusicBg = document.getElementById("an_music_bg");

        if (isChangeBg) {
            // player listswitch 会进入此处
            const musiccover = document.querySelector("#anMusic-page .aplayer-pic");
            anMusicBg.style.backgroundImage = musiccover.style.backgroundImage;
        } else {
            // 第一次进入,绑定事件,改背景
            let timer = setInterval(() => {
                const musiccover = document.querySelector("#anMusic-page .aplayer-pic");
                // 确保player加载完成
                console.info(anMusicBg);
                if (musiccover) {
                    clearInterval(timer);
                    anMusicBg.style.backgroundImage = musiccover.style.backgroundImage;
                    // 绑定事件
                    furo.addEventListenerChangeMusicBg();

                    // 暂停nav的音乐
                    if (
                        document.querySelector("#nav-music meting-js").aplayer &&
                        !document.querySelector("#nav-music meting-js").aplayer.audio.paused
                    ) {
                        furo.musicToggle();
                    }
                }
            }, 100);
        }
    },
    addEventListenerChangeMusicBg: function () {
        const anMusicPage = document.getElementById("anMusic-page");
        const aplayerIconMenu = anMusicPage.querySelector(
            ".aplayer-info .aplayer-time .aplayer-icon-menu"
        );

        anMusicPage
            .querySelector("meting-js")
            .aplayer.on("loadeddata", function () {
                furo.changeMusicBg();
                console.info("player loadeddata");
            });

        aplayerIconMenu.addEventListener("click", function () {
            document.getElementById("menu-mask").style.display = "block";
            document.getElementById("menu-mask").style.animation =
                "0.5s ease 0s 1 normal none running to_show";
        });

        document.getElementById("menu-mask").addEventListener("click", function () {
            if (window.location.pathname != "/music/") return;
            anMusicPage
                .querySelector(".aplayer-list")
                .classList.remove("aplayer-list-hide");
        });
    },
};

// 调用
// furo.changeMusicBg(false);
furo.setDefaultMusigBg();

:root {
    --music-list-title-color: #fff
}

#page:has(#anMusic-page) {
    border: 0;
    box-shadow: none !important;
    padding: 0 !important;
    background: transparent !important;
}

#an_music_bg {
    display: none;
    /* filter: blur(100px); */
    opacity: 0.4;
    position: fixed;
    z-index: -999;
    background-attachment: local;
    background-position: center center;
    background-size: cover;
    background-repeat: no-repeat;
    width: 100%;
    height: 100%;
    /* top: -50%;
    left: -50%; */
    transform: rotate(0deg);
}

body:has(#anMusic-page) #an_music_bg {
    display: block;
}

body:has(#anMusic-page) {
    background: rgb(13, 13, 13);
}

#anMusic-page meting-js .aplayer {
    display: flex;
    flex-direction: row-reverse;
    background: rgba(0, 0, 0, 0);
    border: none;
    box-shadow: none;
}

body:has(#anMusic-page) #web_bg {
    display: none;
}

body:has(#anMusic-page) #footer,
body:has(#anMusic-page) #nav-music {
    display: none;
}

#anMusic-page .aplayer-body {
    width: 40%;
    height: 75vh;
}

#anMusic-page ol>li:hover {
    background: #ffffff33;
    border-radius: 6px;
}

#anMusic-page .aplayer-pic {
    float: none;
    width: 180px;
    height: 180px;
    border-radius: 12px;
    margin: auto;
    left: 0;
    right: 0;
}

#anMusic-page .aplayer-info {
    margin: 0 20px 0 20px;
    border-bottom: none;
}

#anMusic-page .aplayer-info .aplayer-music {
    text-align: center;
    height: auto;
    margin: 15px;
}

#anMusic-page .aplayer-info .aplayer-music .aplayer-author,
#anMusic-page .aplayer-info .aplayer-music .aplayer-title {
    font-size: 2rem;
    font-weight: 700;
    color: #ffffff;
}

#anMusic-page .aplayer-info .aplayer-lrc {
    height: 800%;
    margin-top: 80px;
    mask-image: linear-gradient(to bottom,
            #000,
            #000,
            #000,
            #000,
            #000,
            #000,
            #000,
            #000,
            #000,
            #000,
            #0000,
            #0000);
}

#anMusic-page .aplayer-info .aplayer-lrc p {
    font-size: 14px;
    color: #ffffff;
}

#anMusic-page .aplayer .aplayer-lrc:after,
#anMusic-page .aplayer .aplayer-lrc:before {
    display: none;
}

/* 控制器 */
#anMusic-page .aplayer-info .aplayer-controller {
    position: fixed;
    max-width: 1500px;
    margin: auto;
    left: 0;
    right: 0;
    bottom: 50px;
}

#anMusic-page .aplayer-info .aplayer-controller .aplayer-bar-wrap {
    margin: 0 160px 0 150px;
}

#anMusic-page .aplayer-info .aplayer-controller .aplayer-played {
    background: var(--music-list-title-color) !important;
}

#anMusic-page .aplayer-info .aplayer-controller .aplayer-thumb {
    -webkit-transform: none;
    transform: none;
    background: #fff !important;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back,
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward,
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
    display: inline;
    position: fixed;
}

#anMusic-page .aplayer-info .aplayer-time {
    position: absolute;
    width: 100%;
    bottom: 21px;
    height: 0;
    display: flex;
    justify-content: flex-end;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner {
    margin-right: 18px;
    margin-top: -8px;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back {
    position: absolute;
    left: 0;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
    position: absolute;
    left: 40px;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward {
    position: absolute;
    left: 80px;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon {
    width: 2rem;
    height: 2rem;
    margin-left: 15px;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-menu {
    display: none;
}

#anMusic-page .aplayer-info .aplayer-time .aplayer-icon path {
    fill: var(--music-list-title-color);
    opacity: 0.8;
}

#anMusic-page .aplayer-list {
    width: 60%;
    max-height: none !important;
    height: 100%;
}

#anMusic-page ol {
    max-height: 75vh !important;
    padding-right: 25px;
}

#anMusic-page ol>li {
    border-top: 1px solid transparent;
    font-size: 14px;
}

#anMusic-page ol>li.aplayer-list-light {
    background: rgb(255 255 255 / 20%);
    border-radius: 6px;
}

#anMusic-page ol>li span {
    color: var(--music-list-title-color);
}

#anMusic-page ol>li.aplayer-list-light .aplayer-list-cur {
    display: none;
}

#anMusic-page ol>li span.aplayer-list-author {
    opacity: 0.6;
}

/* 导航栏 */
.page:has(#anMusic-page) #nav {
    backdrop-filter: none !important;
    background: 0 0 !important;
    border-bottom: none !important;
}

.page:has(#anMusic-page) #page-header.not-top-img #nav a,
.page:has(#anMusic-page) #page-header #nav .back-home-button {
    color: var(--music-list-title-color);
}

body:has(#anMusic-page) .s-sticker div {
    color: var(--music-list-title-color) !important;
}

body:has(#anMusic-page) .aplayer .aplayer-info .aplayer-controller .aplayer-time .aplayer-icon.aplayer-icon-loop {
    margin-right: 15px;
}

[data-theme="dark"] .page:has(#anMusic-page) #page-header:before {
    background-color: transparent;
}

/* **** 移动端样式 ***** */
@media screen and (max-width: 768px) {
    body:has(#anMusic-page) #rightside {
        display: none;
    }

    body:has(#anMusic-page) #content-inner,
    body:has(#anMusic-page) #page {
        z-index: auto;
    }

    /* 歌曲列表 */
    #anMusic-page .aplayer-list {
        position: fixed;
        z-index: 1002;
        width: 100%;
        bottom: -76%;
        left: 0;
        background: var(--sidebar-bg);
        height: auto;
        border-radius: 15px 15px 0px 0px;
        padding: 15px 0px;
    }

    #anMusic-page .aplayer-list.aplayer-list-hide {
        bottom: 0% !important;
    }

    #anMusic-page ol {
        max-height: 60vh !important;
        padding-right: 0px;
    }

    #anMusic-page ol>li {
        display: flex;
        margin: 0 10px;
    }

    #anMusic-page ol>li span {
        color: var(--font-color);
    }

    #anMusic-page ol>li span.aplayer-list-title {
        width: 30%;
    }

    #anMusic-page ol>li.aplayer-list-light {
        background: #33a673;
        padding: 5px 20px;
        border-radius: 10px;
    }

    #anMusic-page ol>li.aplayer-list-light span {
        color: #fff;
    }

    #anMusic-page ol>li span.aplayer-list-title {
        max-width: 55%;
        width: auto;
        display: -webkit-box;
        -webkit-line-clamp: 1;
        overflow: hidden;
        -webkit-box-orient: vertical;
    }

    #anMusic-page ol>li span.aplayer-list-author {
        position: absolute;
        right: 10px;
        width: auto;
        max-width: 35%;
        display: -webkit-box;
        -webkit-line-clamp: 1;
        overflow: hidden;
        -webkit-box-orient: vertical;
    }

    #anMusic-page ol>li.aplayer-list-light span.aplayer-list-author {
        right: 15px;
    }

    /* 歌词信息 */
    #anMusic-page .aplayer-body {
        width: 100%;

        position: fixed;
        margin: auto;
        left: 0;
        right: 0;
        top: 100px;
    }

    #anMusic-page .aplayer-info .aplayer-lrc {
        margin-top: 40px;
        height: auto;
        max-height: 200%;
        min-height: 100%;
        mask-image: linear-gradient(to bottom,
                #000,
                #000,
                #000,
                #000,
                #0000,
                #0000);
    }

    #anMusic-page .aplayer-info .aplayer-lrc p.aplayer-lrc-current {
        color: #33a673;
    }

    /* 控制按键和进度条 */
    #anMusic-page .aplayer-info .aplayer-controller {
        width: 100%;
        bottom: 100px;
    }

    #anMusic-page .aplayer-info .aplayer-controller .aplayer-bar-wrap {
        margin: 0 30px;
    }

    #anMusic-page .aplayer-info .aplayer-time {
        bottom: -40px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner {
        position: absolute;
        width: 100%;
        margin-right: 0;
        margin-top: -33px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner .aplayer-dtime {
        position: absolute;
        right: 30px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner .aplayer-ptime {
        position: absolute;
        left: 35px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back {
        margin: auto;
        right: 110px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
        margin: auto;
        right: 0;
        left: 0;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward {
        margin: auto;
        left: 110px;
        right: 0;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-order {
        position: absolute;
        left: 22px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-loop {
        position: absolute;
        right: 25px;
    }

    #anMusic-page .aplayer-info .aplayer-time .aplayer-icon-menu {
        display: inline;
        position: absolute;
        right: 25px;
        top: -90px;
    }

    #anMusic-page .aplayer-volume-bar-wrap {
        bottom: 0px;
        right: 7px;
    }

    #anMusic-page .aplayer .aplayer-info .aplayer-controller .aplayer-volume-wrap {
        left: -66px;
    }
}

Hexo X Obsidian 实现关系图谱页面

id : sjfygk
types : undefined
keywords :
Begin : 9/7/2024

前期准备

安装 hexo-filter-titlebased-link 插件:解决文章双向链接渲染

uuanqin/hexo-filter-titlebased-link: Transfer wiki links (based on the title) in Markdown files to permalink. 将基于标题的双向链接转换为 Hexo 设定的永久链接。 (github.com)

npm install hexo-filter-titlebased-link --save

准备 obsidian2cosma 脚本:转换 Obsidian 文章为 Cosma 可识别的文章

下载此脚本重命名为obsidian2cosma.py,存放在根目录my_scripts文件夹。
uuanqin/obsidian2cosma: Convert Obsidian vaults to collection of notes readable by Cosma and Zettlr (github.com)
因为本站文章的 abbrlink 非 int 类型而是 string 类型,该脚本不支持因而进行了些修改:

// 155 行处
-  return int("0x" + hex_abbr, 16)
+  return hex_abbr

安装 Cosma:将文章生成关系图谱网站

Github:graphlab-fr/cosma: Cosma is a document graph visualization tool. It modelizes interlinked Markdown files and renders them as an interactive network in a web interface. (github.com)
Officials Webstie:Cosma | Installing Cosma (arthurperret.fr)

npm install @graphlab-fr/cosma -g

修改 Cosma 源码:为部署进行适配

修改默认cosma配置文件

修改源码中的core\models\config.js

  1. 解决需要手动填写cosma c命令生成config.ymlfiles_origin的问题
  2. 调整cosma配置文件修改样式
  static base = Object.freeze({
    select_origin: 'directory',
    files_origin: './', //源文件路径
    nodes_origin: '',
    links_origin: '',
    nodes_online: '',
    links_online: '',
    images_origin: '',
    export_target: '',
    history: true,
    focus_max: 2,
    record_types: { undefined: { fill: '#E6E7E7', stroke: '#858585' } }, //结点颜色
    link_types: { undefined: { stroke: 'simple', color: '#e1e1e1' } },
    references_as_nodes: false,
    references_type_label: 'references',
    record_filters: [],
    graph_background_color: '#ffffff',
    graph_highlight_color: '#ff6a6a',
    graph_highlight_on_hover: true,
    graph_text_size: 10,
    graph_arrows: true,
    node_size_method: 'degree',
    node_size: 10,
    node_size_max: 20,
    node_size_min: 2,
    attraction_force: 200,
    attraction_distance_max: 250,
    attraction_vertical: 0,
    attraction_horizontal: 0,
    views: {},
    record_metas: [],
    generate_id: 'always',
    link_context: 'tooltip',
    hide_id_from_record_header: false,
    title: '',
    author: '',
    description: '',
    keywords: [],
    link_symbol: '',
    csl: '',
    bibliography: '',
    csl_locale: '',
    css_custom: '',
    devtools: false,
    lang: 'en',
  });

替换点击关系图谱中结点为跳转为文章页面

修改源码中的`core\static\bundle.js

- ("data-node",(function(t){return t.id})).append("a").attr("href",(function(t){return"#"+t.id})
+ ("data-node",(function(t){return t.id})).append("a").attr("href",(function(t){return"/posts/"+t.id+".html"})

(废弃)修剪页面只显示画布

该方法已废弃,新实现通过hexo的custom_css完成
修改源码中的 core\static\styles\styles.css

+ aside,main,.close-button,#graph-controls {
+   display: none;
+ }

操作部署

生成关系图谱页面

  1. 在根目录创建\source\DO_NOT_RENDER\cosmoscope\文件夹存在
  2. \source\DO_NOT_RENDER路径添加到_config.ymlskip_render
    参考:Configuration | Hexo
    skip_render: "source/DO_NOT_RENDER/**"
    
  3. 在根目录package.jsonscripts项里添加以下:
  "scripts": {
    "ob2co": "python my_scripts/obsidian2cosma.py -i source/_posts -o my_scripts/cosma_dir/data --ignore -f --method abbrlink --attrreplacement categories,types date,begin --verbose",
    "cosma-data": "cd my_scripts/cosma_dir/data && cosma c && cosma m",
    "cosma-copy": "copy \".\\my_scripts\\cosma_dir\\data\\cosmoscope.html\" \".\\themes\\butterfly\\layout\\includes\\page\\Html\\cosmoscope.html\" ",
    "cosma-rm-output": "echo \"删除转换后的输出文件 - Cosma\" && rmdir /s /q .\\my_scripts\\cosma_dir\\data\\",
    "gen-cosma": "npm run ob2co && npm run cosma-data && npm run cosma-copy && npm run cosma-rm-output"
  },
  • 脚本中cosma-copy将生成的html文件移动到了主题内文件夹,可以自定义移动路径
  • 使用npm run gen-cosma生成关系图谱页面,每次更新改关系图谱页面时需手动执行该命令

配置到网页页面

Hexo 自定义Page - 音乐页面

参考 自定义cosmoscope页面

配置cosmoscope.pug:

#cosmoscope-page
    include Html/cosmoscope.html

配置source/cosmoscope/index.md

使用custom_css对页面样式进行修改,主要是对除canvas进行裁剪:

/* Cosmoscope 文章星图页面 */ 
body:has(#cosmoscope-page)  { 
    display: block !important;
}

#content-inner:has(#cosmoscope-page) {
    display: block!important;
    max-width:none;
    padding: 0 !important;
    border: 0;
}

#content-inner:has(#cosmoscope-page) > #page {
    background: none;
}

.graph-wrapper {
    background: rgba(0,0,0,0) !important;
}

body:has(#cosmoscope-page) aside,#footer,main,.close-button,#graph-controls {
    display: none;
}

(可选)配置到网站导航

在主题配置文件中添加以下配置:

menu:
  文图: /cosmoscope || fas fa-arrow-right-arrow-left

(可选)配置到网站侧边栏

参考:自定義側邊欄 | Butterfly
创建`source/_data/widget.yml

- class_name: seamless-card
	id_name:
	name:
	icon:
	order:
	html: |
		<div style="height: 250px">
		<iframe src=\".\\themes\\butterfly\\layout\\includes\\page\\Html\\cosmoscope.html\"frameborder="no" style="width: 100%; height: 100%"></iframe>
		</div>

记录拓展

存在问题

  1. 当文章的文件名与其meta.title不一致时,无法生成正确的连接。
  2. cosmoscope.html中的部分文段在执行命令时会报错(YAMLException: end of the stream or a document separator is expected (24:26)),无影响待解决。

后续改进

暂无

参考文章

Butterfly 侧边栏实现 Obsidian 关系图谱 | 半方池水半方田 (uuanqin.top)
Hexo 博客适配 Obsidian 新语法 | 半方池水半方田 (uuanqin.top)

LimboAI Introduction

id : sjhio9
types : undefined
keywords :
Begin : 9/8/2024

Help

Click here to access Cosma's documentation

Shortcuts

Space Re-run the force-layout algorithm
S Move the cursor to Search
Alt + click (on a record type) Deselect other types
R Reset zoom
Alt + R Reset the display
C Zoom in on the selected node
F Switch to Focus mode
Escape Close the active record
控制中心