3 Commits

Author SHA1 Message Date
4569145df6 add binary 2025-08-09 18:54:48 +09:00
c2efdf9095 fix version 2025-08-09 18:46:29 +09:00
09994d2d94 add binary 2025-08-09 18:37:59 +09:00
75 changed files with 43 additions and 3249 deletions

View File

@@ -24,7 +24,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '25'
node-version: '21'
- name: Install dependencies
run: |
@@ -56,9 +56,9 @@ jobs:
uses: actions/cache@v4
with:
path: ./bin
key: ailog-bin-${{ runner.os }}-v${{ hashFiles('Cargo.toml') }}
key: ailog-bin-${{ runner.os }}
restore-keys: |
ailog-bin-${{ runner.os }}-v
ailog-bin-${{ runner.os }}
- name: Setup ailog binary
run: |

Binary file not shown.

View File

@@ -8,14 +8,10 @@ draft: false
## 最小構成
まずはdiskの設定から。
```sh
# cfdisk /dev/sda
# cgdisk /dev/sda
```
次にdiskのフォーマットなど。それをmountしてarchlinuxを入れます。bootloaderも設定しておきましょう。
```sh
$ mkfs.vfat /dev/sda1
$ mkfs.ext4 /dev/sda2
@@ -24,7 +20,6 @@ $ mount /dev/sda2 /mnt
$ mount --mkdir /dev/sda1 /mnt/boot
$ pacstrap /mnt base base-devel linux linux-firmware linux-headers
$ genfstab -U /mnt >> /mnt/etc/fstab
$ arch-chroot /mnt
$ pacman -S dhcpcd grub os-prober efibootmgr
@@ -32,62 +27,14 @@ $ grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=grub
$ grub-mkconfig -o /boot/grub/grub.cfg
```
これで`exit;reboot`すると起動できます。
## よく使うもの
```sh
$ pacman -S openssh zsh vim git tmux cargo
```
## userの作成
```sh
$ passwd
$ useradd -m -G wheel ${USER}
$ passwd ${USER}
```
```sh
$ HOSTNAME=archlinux
$ echo "$HOSTNAME" > /etc/hostname
```
shellの変更など。
```sh
$ chsh -s /bin/zsh ${USER}
or
$ useradd -m -G wheel -s /bin/zsh ${USER}
```
## sudoの使い方
1. `/etc/sudoers`は編集を間違えると起動できなくなります。安全のため`visudo`が推奨されています。
2. `vim`では`:w!`で保存します。
```sh
$ sudo visudo
or
$ vim /etc/sudoers
```
```sh:/etc/sudoers
%wheel ALL=(ALL:ALL) ALL
```
よく`update`する人は特定のコマンドをpasswordなしで実行できるようにしておいたほうが良いでしょう。
```sh:/etc/sudoers
%wheel ALL=(ALL:ALL) NOPASSWD: /usr/bin/pacman -Syu --noconfirm
```
```sh
$ sudo pacman -Syu --noconfirm
```
これだけ`exit;reboot`すると起動できます。
## networkの設定
```sh
$ pacman -S git vim tmux zsh openssh
```
次にnetworkです。ここでは`systemd-networkd`を使用します。`dhcpcd`を使ったほうが簡単ではあります。もし安定しないようなら`dhcpcd`を使用。
```sh
@@ -139,7 +86,7 @@ $ systemctl restart getty@tty1
## window-manager
`xorg`でdesktop(window-manager)を作ります。`i3`を使うことにしましょう。`xorg`は`wayland`に乗り換えたほうがいいかも。その場合は`sway`がおすすめ。
`xorg`でdesktop(window-manager)を作ります。`i3`を使うことにしましょう。`xorg`は`wayland`に切り替えたほうがいいかも。
```sh
$ pacman -S xorg xorg-xinit i3 xterm
@@ -170,7 +117,7 @@ PasswordAuthentication no
$ systemctl restart sshd
```
基本的にlanから使う場合はdefaultで問題ありませんが、wanから使う場合は変更します。とはいえ、lanでもport, passwordは変えておいたほうがいいでしょう。
基本的にlan使う場合はdefaultで問題ありませんが、wan使う場合は変更します。とはいえ、lanでもport, passwordは変えておいたほうがいいでしょう。
次に接続側でkeyを作ってserverに登録します。
@@ -232,119 +179,3 @@ bindkey '^[[B' history-substring-search-down
```
`powerline`は重いのでコメントしています。
## フリーズの解消
古いpcにlinuxを入れる際は`linux-fm`に注意してください。
頻繁にフリーズするようなら`linux-firmware`を削除するのがおすすめです。
```sh
$ pacman -Q | grep linux-firmware
$ pacman -R linux-firmware ...
# pacman -S broadcom-wl-dkms
```
## pacmanが壊れたときの対処法
```sh
$ pacman -Syu
# これがうまくいかないことがある
```
```sh
# dbがlockされている
$ rm /var/lib/pacman/db.lock
# ファイルが存在すると言われる
$ pacman -Qqn | pacman -S --overwrite "*" -
# pgp-keyをreinstallする
$ pacman -S archlinux-keyring
$ pacman-key --refresh-key
```
## archlinuxの作り方
archlinuxはシンプルなshell scriptと言えるでしょう。なので色々と便利です。ここでは、`img.sh`, `install.sh`を作ります。
### img.sh
ここでは`archlinux.iso`, `archlinux.tar.gz`を生成します。これはarchlinux上で実行してください。
```sh:img.sh
#!/bin/bash
pacman -Syuu --noconfirm git base-devel archiso
git clone https://gitlab.archlinux.org/archlinux/archiso
./archiso/archiso/mkarchiso -v -o ./ ./archiso/configs/releng/
mkdir -p work/x86_64/airootfs/var/lib/machines/arch
pacstrap -c work/x86_64/airootfs/var/lib/machines/arch base
arch-chroot work/x86_64/airootfs/ /bin/sh -c 'pacman-key --init'
arch-chroot work/x86_64/airootfs/ /bin/sh -c 'pacman-key --populate archlinux'
tar -zcvf archlinux.tar.gz -C work/x86_64/airootfs/ .
```
例えば、`pacstrap`で自分の好きなツールを指定すれば、独自のimgを作成でき、`docker`にも使えます。
```sh
$ docker import archlinux.tar.gz archlinux:syui
$ docker run -it archlinux:syui /bin/bash
```
### install.sh
最小構成のinstall scriptです。どこかのurlに置いて、install時にcurlして実行するようにすれば便利です。
```sh
$ curl -sLO arch.example.com/install.sh
$ chmod +x install.sh
$ ./install.sh
```
```sh:install.sh
#!/bin/bash
set -euo pipefail
# 変数定義
DISK="/dev/sda"
HOSTNAME="ai-arch"
USERNAME="ai"
# パーティション作成(自動)
parted $DISK mklabel gpt
parted $DISK mkpart ESP fat32 1MiB 1GiB
parted $DISK set 1 esp on
parted $DISK mkpart primary linux-swap 1GiB 5GiB
parted $DISK mkpart primary ext4 5GiB 100%
# ファイルシステム作成
mkfs.fat -F32 ${DISK}1
mkswap ${DISK}2
mkfs.ext4 ${DISK}3
# マウント
mount ${DISK}3 /mnt
mkdir -p /mnt/boot
mount ${DISK}1 /mnt/boot
swapon ${DISK}2
# インストール
pacstrap -K /mnt base linux linux-firmware base-devel vim networkmanager grub efibootmgr
# 設定
genfstab -U /mnt >> /mnt/etc/fstab
arch-chroot /mnt /bin/bash << EOF
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
hwclock --systohc
echo "ja_JP.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
echo "LANG=ja_JP.UTF-8" > /etc/locale.conf
echo "$HOSTNAME" > /etc/hostname
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=ARCH
grub-mkconfig -o /boot/grub/grub.cfg
systemctl enable NetworkManager
useradd -m -G wheel $USERNAME
EOF
```

View File

@@ -1,151 +0,0 @@
---
title: "game system v0.4.0"
slug: "game"
date: "2025-08-12"
tags: ["ue"]
draft: false
---
今回は、game systemのupdateをまとめます。
分かりづらいので、game systemは全体で同じversionに統一しています。
まず、大きく分けて3つのシステムをupdateしました。
- yui system: キャラクターのバースト(必殺技)を実装
- at system: ログイン処理とデータ構造の作成
- world system: 場所ごとにBGMを再生するシステムの構築
- world system: 惑星に雪や雨を降らせることに成功
<iframe width="100%" height="415" src="https://www.youtube.com/embed/eXrgaVNCTA4?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## 戦闘シーンの作成
1キャラクターにつき、1スキル、1バースト、1ユニークというのは決まっていました。これは`yui system`の領域。
アイの属性はアイ属性なので、テーマカラーは黄色です。属性自体は`ai system`の領域ですが、現在、関連反応のシステムまでは実装していません。
今回はバーストの作成、ダメージ表記、enemy(敵)の撃破までを実装しました。最初から作り変えたので大変でした。
<iframe src="https://blueprintue.com/render/l7_xvfbp" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
## 音楽システムの実装
これは`world system`の領域で、開発中は`PlayerStart`で各位置に瞬間移動して確認しています。これはアイでなければ設定上無理でした。
具体的には、PlayerStartのtagと音楽を同じ名前で登録します。そして、playerに最も近いものを再生します。効率的でシンプルですが、少し欠陥があるシステムかもしれません。これは、enemy-hpの表示と連動させています。現在、鳴らしているbgmの名前がわかれば表示できるというわけですね。enemy-bossもPlayerStartのtagで同じ名前で置いてあります。
<iframe src="https://blueprintue.com/render/x80534fn" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
原作の設定は、ゲーム開発中も適用されます。アイを動かして空を飛んでいますが、あれはアイだからできるのであって、宇宙空間の移動とかもそうです。
原作の設定を紹介しておきます。
### 原作の設定: アイはなぜ空を飛べるのか
アイはものすごい質量を持ちます。空を飛んでいるというより、地球を持ち上げて、空を飛んでいるように見せている、という感じで飛行しています。
いやいや、それじゃあ、地球はアイに落ちるだけで、空どころか地面に落ちるだろう、と言われそうですが、地球というのは宇宙から見るとすごいスピードで回転、移動しています。
そして、宇宙で星と星がぶつかるときは、決して直接ドカーンと衝突するわけではないのです。
お互いに距離を取りながらぐるぐる回って、やがてぶつかる、そんなイメージ。
質量と質量の間があるわけですね。
アイが瞬間的に自身の質量の一部を現すと、間ができ、対象の星の質量を計算しながら、それを持ち上げて動かすような感じで移動しています。
### 原作の設定: アイはなぜ宇宙空間でも平気なの
それはアイの体の周りには極小の大気の膜があるためだとされています。超重力で圧縮された大気の膜があるため、宇宙空間、その他一切の外的影響をあまり受けません。
アイは常に、自分の星の中にいるのと同じ状態、といえばいいのでしょうか。そんな感じです。
## データ構造の変更
次に、`at system`です。ゲームデータを再構築しました。
ゲームデータは主にsystem情報とuser情報に分けられ、jsonで管理されます。
各パラメータですが、ゲームに必要な値を`cp`として圧縮することにしました。このcpをsystem.jsonあるいはゲーム自体で各キャラクターの設定、つまり、`attack: 10%, hp: 20%, skill: 70%`などで分けられます。これが最もわかりやすく、最も効率的な方法だと考えました。
```json:user.json
{
"character": [
{ "id": 0, "cp": 100 }
]
}
```
```json:system.json
{
"character": [
{ "id": 0, "name": "ai", "ability": "ai" }
]
},
{
"ability": [
{ "id": 0, "name": "ai" }
]
}
```
これをログインシステムに連動させました。
このサイトで`at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.verse.user`を検索してもらえればわかります。
```sh
# ゲームシステム
at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.verse
# aiのアカウント
at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.verse.user/6qyecktefllvenje24fcxnie
# syuiのアカウント
at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/ai.syui.verse.user/vzsvtbtbnwn22xjqhcu3vd6y
```
ちなみに、私のアカウントである`syui.syui.ai`ではアイは使用できません。現在使用できるキャラは`dragon`のみ。
現在、アイを使用できるのは、アイのアカウントのみです。この方針は可能な限り維持されるでしょう。
## 惑星に雨や雪を降らせる
これはなかなか苦労していたのですが、実装できました。
まず、有効にすると宇宙空間でも雨が降ってしまいます。止めると惑星内で雨が降りません。
これを解消するには、player locationと0原点のdistanceから条件をつけ、雲の下、雲の上と定義します。調整が必要。
そして、udsのweather、特に`Apply Weather Changes Above Cloud Layer`が重要で、`Apply Clouds`の値を調整します。
<iframe src="https://blueprintue.com/render/dstkcaia" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
## 実体ある太陽のatmosphere問題
まず、私が使っている実体ある太陽にはatmosphereがついています。
これはフレアなどを設定しています。
しかし、これを地球から見た場合、その大気圏を通すと、非常に見栄えが悪い変なカクカクした光が映り込みます。
この解消も非常に苦労しました。例えば、これを`BP_Sun`としましょう。これは起動時にすべての値を設定します。ゲームプレイ中に値の調整をすることは考えられていません。当然と言えるでしょう。
しかし、私のシステムでは、太陽のatmosphereを調整する必要があります。非常に複雑な設定は、リセットでしか解消できないということになりました。そして、udsに入れている小アクタコンポーネントの太陽は、リセットも容易ではありません。
色々な処理を作り、先程作った地表からの現在地の割り出しを条件に、これをリセットする処理をねじ込みました。
<iframe src="https://blueprintue.com/render/nsqu0hnf" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
## 動画で確認
<iframe width="100%" height="415" src="https://www.youtube.com/embed/H1efWYmIugc?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
1. BGMが切り替わる
2. 物理ボックスが反応
3. 敵へのダメージ
4. ボスの撃破
5. 雨が雲の上では止まる
6. ログインでatprotoのアカウントを反映
7. プレイでatprotoの情報を更新

View File

@@ -1,180 +0,0 @@
---
title: "なぜ自作ゲームのsystemを作るのか"
slug: "game"
date: "2025-08-18"
tags: ["ue"]
draft: false
---
現在、自作ゲームを開発しています。
どういうゲームかと一言でいうと現実の反映を目指しています。
現実の反映とは何でしょう。例えばゲームではblueskyのようなsnsのアカウントでログインできます。ゲームの世界は現実に合わせた惑星形式です。キャラクターの属性は現実にある物質です。原子や中性子など。
今回は、なぜ自作ゲームのsystemを作っているのか解説します。
## 一つの青写真
私は`2023-12-04`あたりからunreal engine(ue)を触り始めました。
当時、ゲームでこんなことがやりたいなと思って作った画像があります。
![](https://raw.githubusercontent.com/syui/img/master/other/ue5_ai_20231204_0001.jpg)
https://syui.github.io/blog/post/2023/12/04/ue-vs-unity/
今ではゲーム作りに対する考え方も変わりましたが、上のイメージは頭の中にずっと残っていて、ようやく、イメージ通りの戦闘シーンを作成できました。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/tBsYgqI1uSc?rel=0&showinfo=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- 敵の砲撃は剣で弾き返す事ができます。反射したものが当たると敵もダメージを受けます
- 敵の砲撃が激しすぎるためバースト時の無敵時間を長めに設定しています
### 原作の設定: アイのバースト
アイのバースト、つまり必殺技について解説します。自分で作ったカード、`超新星`というタイトルをモデルにした技ですが、技名自体は`中性子`になります。具体的には周囲の原子から中性子を取り出し、それを一点に集めて放つ技。ようは、中性子星を作り出しそれを飛ばしています。これがアイのバースト、中性子です。このゲームは麻雀要素を入れようと考えていて、それはバーストに適用されます。役が揃うと、超新星に変化するという実装を考えています。
### キャラクターの音声
アイはかなり低い確率でスキルやバースト時に音声が付いています。これをキャラクターの音声システムとしましょう。
キャラクターは性格に応じて音声の発動頻度が異なります。アイは最も音声確率が低いランクに割り当てられています。(コンピュータで作っているため粗が見えても困るので)
## 広い世界と狭い世界
### なぜアイを操作するゲームはつまらないのか
私がゲーム作りを始めた理由はいくつかありますが、もし現実にアイというキャラクターがいたら一体どんな感じになるのだろう。それを体感してみたいと思ったからです。
この思いは面白いゲームを作ることにはまるで寄与しないものでしたが、私はそれを作ることにしました。
ここで、なぜアイを操作するゲームがつまらないのか簡単に説明します。
### 広い世界は面白くない
私は普段、アイを使ってゲームを操作し、世界を飛び回り、作っています。
なぜなら、そのほうが開発に便利なのでそうしています。また都合がいいことに、アイというキャラクターは設定上、そういう事が可能となります。
しかし、先程も述べたように、そのようなゲームは恐ろしくつまらない、ということです。
とすれば、重要なのは小さくても、しっかりしたゲームを作ること。上のようなゲームを作ってはいけないのです。
広い世界、無制限の移動ではなく、狭くてもしっかりした世界を作らなければいけない。面白いゲームとはそういうものです。
### 最初の思いと面白いゲーム
次は、初めての思い、初心を大切にすることを考えていきます。
開発者が作りたいゲームと面白いゲームは大抵の場合、両立しません。
例えば、映像美、見せることと実際に面白いことは違うのです。
誰もが初めて何かをする時、そこには各人の思いがあります。それは小さいものであれ大きいものであれ、そこに意識がある。
それは、時間が経つと忘れてしまうものですが、心の奥深くに残っている。
しかし、大抵の場合、そういった思いと、誰もが面白いと思う人気ゲームを作るという思いは相反します。
つまり、心の奥底に眠っている最初の思いと、面白いゲームは違うものだし、また、ゲームに限らず、これは色んな作品に言えることだと思います。
これを勘違いして「自分の思いは、他人にも面白いはずだ」とそう思い込むのは誤りです。
では、どうすればいいのでしょう。最初の思いを捨て、面白いゲームを分析して世間に受け入れられるものを作るべきなのでしょうか。
これはyesとも言えるし、noとも言えます。
優先順位としては、間違いなく面白いゲームを作るべきです。個人開発者のよくわからないこだわりなどさっさと捨てるべき、そう思います。
ですが、本当にそれでいいのでしょうか。
私はそれはもったいないと思います。
したがって、できる限りそこを両立させる方法を探すべきだと思います。
私はここで`分離`という方法を使います。世界(方針)を切り離すのです。
これが広い世界と狭い世界、個人開発の指針になります。一見矛盾するこの2つの世界の分離と統合を考えます。
最初の思い、本当に作りたかったもの、楽しくも面白くもないけど、自分の世界。そして、小さく作る面白いゲームの世界。
自分で作ったものを無駄にしないようシステムという4つの柱を立てました。
4つの柱は、根源的な価値観によるもの。広いも狭いも、面白いも面白くないも関係ありません。systemは以下のようになります。これらはどのゲームにも当てはめられ、使用できることを目指して設計されます。
- `world system`: 現実に合わせた世界を構築するシステム。最初は地球、太陽、月から生成される。ゲームエンジンの背景に月の絵を動かすという常識を変更する。実体ある月をその空間に置くことで背景を生成。惑星システムを構築する。
- `ai system`: 属性やゲーム性を構築するシステム。
- `yui system`: 唯一性という価値観から構築されるシステム。現実をゲームに、ゲームを現実に反映することで自然と実現される。
- `at system`: プレイヤーのアカウントをatprotoというprotocolの理解により構築する。protocolは容易には無くならないし変更されないもの。`@`というdomainで繋がるユーザーアカウントのシステム。blueskyというsnsに利用されている。
### systemを作ろう
ゲームはsystemの集合体で作るのが一番いい。
開発では、まとめることが重要になります。
systemとは関数やコンポーネント、変数の集まりです。それは結果であって目的ではありません。
目的の一つはわかりやすさ。例えば、敵を倒した時、アイテムをドロップさせる処理を作ります。個別に敵のBPに作るのではなく、systemとして作っておくとよいでしょう。1と押せば1つのアイテムがドロップします。3と押せば3つです。内容もランダムかつランクを付けましょう。Aランク-Cランクのアイテムです。
つまり、そのsystemに3Bと伝えると、3つのアイテムがドロップし、Bランクのアイテムがドロップしやすい、という結果が出力されます。アイテム一覧もすべてそのsystemが管理し、簡単に設定できます。
ゲームを作るというのは、systemを作ること。それは単体の実行ではありません。個人開発の場合は特に設計を重視し、まとめることを重視します。
これが何かというと、一つはルールを作ることです。
1キャラクターにつき1スキル、1バースト。例外は認めない。このようなルールです。そして、そのルールに基づいたsystemを設計し、例えば、キャラクターならキャラクターの統一管理を目指します。統一管理というのは、数字やobjectを入れれば設定は完了です。その通りにすべてのキャラクターが共通動作するようにしておこう、そこを目指そうということです。
これがsystemを作るということです。
私は、敵(enemy)を作っている時間がありませんから、enemyもcharacterにまとめることにしました。
そのため、少し動きの調整が難しかったりもしますが、この方向性で間違いないと思います。
シーンやムービー、ストーリーは広い世界(アイで操作する世界)にまとめ、enemyもplayerが操作できるcharacterにまとめ、単体で作るものを減らし、すでにあるもの、作ったものは他の役割も担えるようにしていきましょう。このような考え方が個人開発では重要になってくると思います。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/L6eZUZNCOH8?rel=0&showinfo=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
### やることを明確に
個人開発者には、できることとできないことがあります。
市販のゲームは、あらゆる専門家が大量に集まり一緒に作っているゲームです。個人開発で全部はできません。
しかし、できないからと言って、手を抜くのも違いますよね。
確かに、私が作りたかったゲームは面白くないかもしれない。けど、一見すると面白そうに見えます。
映像美や見せることと実際に面白いことは違います。しかし、映像美は別に悪いことではありません。それはゲームとしては面白くないけど、見せる力があると思います。シーンやムービーとして利用できるのではないでしょうか。
私はシーンやムービーを作っている時間はありません。しかし、私が今まで作ってきた広い世界はそういったことに使えばいい。狭い世界の背景にも使えます。
いつか行けるかもしれない広い世界。理想と現実。
最初から理想だけあっても、それは面白くない。最初から現実だけあってもそれはただのゲームです。面白いゲームとは、現実と理想のバランス。あるいは、その過程を作ることにあるのではないでしょうか。
最初の夢を持ち続けることも、そして、多くの人が楽しめるものを作る事も両方大切です。初心を捨てず分離して、新たに面白いゲームを目指して作り始めること。そして、最終的に統合できる道筋を思い描けるなら。そんなことを思います。
次は、小さくも完璧で、狭くても全てに由来がある、そんな世界を作っていこうと思っています。
### 狭い世界をどうやって作っていこう
ポイントは、カラフル+ポップだと思います。小さい+完璧もポイントですね。また、動作は非常にゆっくりがいいのではないでしょうか。
つまり、小さいが完璧に動作し、カラフルでポップな世界。それがここでいう狭い世界になります。
私はこの辺のこともあまり知りませんから、epicの[stack o bot](https://www.fab.com/ja/listings/b4dfff49-0e7d-4c4b-a6c5-8a0315831c9c)というテンプレートをもとに学習しながら作っていこうと考えています。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/6tO0S7IOC9w?rel=0&showinfo=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## game system v0.4.3
- ps5 controllerの対応
- game animation sampleとstack o botの統合
- item drop system
- character audio system
- sword reflection
- character dragon skill (enemy)
- bgm systemの修正

View File

@@ -1,101 +0,0 @@
---
title: "自作ゲームを開始から終了までプレイ"
slug: "game"
date: "2025-08-23"
tags: ["ue"]
draft: false
---
自作ゲームを開発しています。
今回は開始から終了までの大体の流れができたのでプレイしてみました。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/FTX1CrzKBy8?rel=0&showinfo=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## ゲームの流れを解説
1. 物語は宇宙から始まる。プレイヤーの村に突然宇宙船がやってきて、プレイヤーが連れ去られる。
2. ここは船内にある檻の中。監視兵が慌ただしい。「おい、あれが出たって」「まさか」などの会話。チラと窓の外に目をやる。すると何かが光ったような気がした。
3. (ここでプレイヤーは初回のみアイを操作可能になる。ゲーム開始時にすぐに操作可能にすることが重要だと思ったので、シーンの作成はやめて、プレイヤーに戦艦を撃破してもらうことに)
4. 艦内は爆発し、星に落ちていく。目が覚めると...そこからステージが始まる。(ここからプレイヤーのキャラクターに切り替わる。今回はアイのアカウントなので、アイになっているが本来は違う)
5. ステージの背景に小さな子がぐーぐ寝ている。(先ほど操作したアイは変身時の金髪輪っかなので、黒髪に戻っているアイを見てもプレイヤーにはわからない)
6. ステージを進み、ドラゴンを倒してゲームは終了。花火っぽいものを打ち上げ、ポーズを決める。その後、ゲーム終了まで自由操作。アイテムのドロップがある。
## 面白いゲームを目指して
インベーダーやマリオなど今の技術では簡単に作れそうなレトロゲームがあります。
それらが面白いのかと言われれば、私は面白いと思います。
とはいえ、今そういったもので遊ぶかというと、それは違うと思います。
しかし、個人開発者はまずその段階に到達する必要があるのではないかと感じます。
では、レトロゲームの面白さについて、改めて分析してみることにしましょう。
レトロゲームなんて、AIを使えば簡単に作れますよ。そんな声が聞こえてきそうですが、それは少し違うと思います。
例えば、ステージ1を作れても、ステージ2,3,4、そして、ラストのステージまで、市販の初代マリオと同じように作っているのでしょうか。
それができていないなら、それは作れていないということです。
そして、ステージだけがマリオじゃないですよね。私がプレイしたことがあるスーパーマリオワールドはボスを倒すと演出がありました。
そこには、物語があり、花火が打ち上がり、紙吹雪が舞い、主人公がポーズを決めるのです。意外とゲーム自体よりそういったものを含めて面白いゲームなのであって、それが重要なのだと思います。
個人開発者の多くは私を含め、そういった面白いゲームを作れているのかというと、できていないと思うのです。
もちろん、色々な人がいますから、できている人もいると思います。しかし、私にはできていない。そこまで作れていないし、レトロゲームの域にすら到達していません。
## 面白いものと売れるものは違う
では、面白いものを作れば、それで売れるのかというと、それもまた違うと思います。
既にあるゲームのパクリ、それはそれで面白いゲームになると思います。しかし、今更レトロゲームを作っても、誰もプレイしないと思います。
面白いゲームと人気が出ることは違うのです。
そこで重要になるのがオリジナリティという要素ではないでしょうか。
したがって、段階があるとしたら、面白いゲームの域に到達する。その後、オリジナリティの域に到達する。あるいは、同時にそれをこなす必要があるのだと思います。
とはいえ、まずは面白いゲームを作ること。せめてその域に到達したいですよね。そして、レトロゲームにも十分に面白い要素は揃っているので、それらを参考にするのが良いと判断しました。
## 人気が出ることと利益が出ることもまた違う
そういえば、収益化もまた別の話だよなと思ったので書きます。
確かに、それは必須条件かもしれませんが、それがあれば必ずというものではないと思います。確かに寄与する部分は少なからずあるとは思うけど。
例えば、snsをみていると、すごいインプレッション、注目を集めたのに、売れなかった漫画がたくさんあります。つまり、人気は出たが、利益は出なかったケースだと思います。
したがって、収益化までの道のりもまた長いのではないか。大変なのかもしれない。そんなふうに思うのです。
これを短絡的な見通しで「面白ければ売れる」などと考えていると、当てが外れるかもしれない。そのへんはあまり期待しないほうがいいかも。
段階的にそれぞれの戦略を考えていくのが良いのではないかな。
`面白い -> 人気が出る -> 利益が出る`...その間にも高い壁がある。
## オリジナリティはどうやって出すのか
例えば、制約からです。
私は設計において、いくつかの決め事を作りました。例えば、以下のルールがあります。
1. 物理法則に反しない
ゲームで物を浮かせるのは簡単だ。しかし、この世界は現実の反映を目指している。したがって、すぐに物を浮かせたり、あるいはテレポートしたり、それをやってはいけない。そういったものには必ず、原理を説明できなければならない。特殊なアイテムが必要となる。このアイテムをアイの家に3つ置いてあるとしよう。この場合、その3つをマップに置くと、その世界にはもうない。使えない。そのようなルールだ。その世界の重要なアイテムはアイが持っていて、作っている。無限には湧いてこない。制限がある。これはatprotoに保存し、公開しておくのもおもしろいかもしれないな。こういったことがその世界の由来につながるのだと思う。
2. マップは一つ
マップは必ず一つの中で完結させること。世界は一つというルール。たくさんのマップを分けてはいけない。不便でも一つのマップの中だけで世界を作ろう。マップを分けてはいけない。宇宙も地上も一つにすること。シーンやムービーを作るときも同じ。違うマップでそれをやってはいけない。
## 今回のゲーム作りで意識したこと
- game system v0.4.4
1. レトロゲームの面白さを必要最小限で実装
2. オリジナリティを融合(このゲームのテーマである宇宙、そして物語)
3. すべての実装を各システムで動かす

View File

@@ -1,107 +0,0 @@
---
title: "plcにhandle changeを反映する"
slug: "plc"
date: "2025-09-05"
tags: ["atproto"]
draft: false
---
いつまで経ってもbsky.teamのplcにhandle changeが反映されないので色々やってみました。
結論から言うと、`PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX`を使用し、`base58`のrotation-keyを作成後に、indigoにある`goat plc`を使用します。
1. `goat key generate --type secp256k1`で生成されたキーを分析
2. そのキーから正しいmulticodecプレフィックスを抽出
3. PDSのhex keyに同じプレフィックスを適用
```sh
$ go install github.com/bluesky-social/indigo/cmd/goat@latest
```
```sh
$ goat account login -u syui.syui.ai -p $PASS --pds-host https://syu.is
$ goat plc history did:plc:vzsvtbtbnwn22xjqhcu3vd6y
did:key:zQ3shZj81oA4A9CmUQgYUv97nFdd7m5qNaRMyG16XZixytTmQ
$ goat plc update did:plc:vzsvtbtbnwn22xjqhcu3vd6y \
--handle syui.syui.ai \
--pds https://syu.is \
--atproto-key did:key:zQ3shZj81oA4A9CmUQgYUv97nFdd7m5qNaRMyG16XZixytTmQ > plc_operation_syui.json
# もしミスった時は前の操作を無効化して再実行
$ goat plc update did:plc:vzsvtbtbnwn22xjqhcu3vd6y \
--handle syui.syui.ai \
--pds https://syu.is \
--atproto-key did:key:zQ3shZj81oA4A9CmUQgYUv97nFdd7m5qNaRMyG16XZixytTmQ \
--prev "bafyreifomvmymylntowv2mbyvg5i7wgv375757l574gevcs7qbysbqizk4" > plc_operation_syui_nullify.json
```
```sh
source base58_env/bin/activate
python3 -c "
import base58
# 生成されたsecp256k1キーを分析
generated_secp256k1 = '${zXXX...}'
decoded = base58.b58decode(generated_secp256k1[1:]) # 'z'を除く
print('Generated secp256k1 key analysis:')
print(' Total length:', len(decoded))
print(' Full hex:', decoded.hex())
# 32バイトの鍵データを除いたプレフィックスを抽出
if len(decoded) > 32:
prefix = decoded[:-32]
key_data = decoded[-32:]
print(' Prefix hex:', prefix.hex())
print(' Prefix length:', len(prefix))
print(' Key data length:', len(key_data))
pds_rotation_hex = '${PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX}'
pds_rotation_bytes = bytes.fromhex(pds_rotation_hex)
prefixed_rotation_key = prefix + pds_rotation_bytes
multibase_rotation_key = 'z' + base58.b58encode(prefixed_rotation_key).decode()
print('\\nConverted PDS rotation key:')
print(' Multibase:', multibase_rotation_key)
else:
print(' No prefix found, key is raw')
"
deactivate
```
```sh
$ PDS_ROTATION_KEY=zXXX...
$ goat plc sign --plc-signing-key "$PDS_ROTATION_KEY" plc_operation_syui.json > plc_signed_syui.json
$ goat plc submit --did did:plc:vzsvtbtbnwn22xjqhcu3vd6y plc_signed_syui.json
success
$ goat plc history did:plc:vzsvtbtbnwn22xjqhcu3vd6y
```
## 手順をおさらい
1. `plc_operation.json`を作成
2. `plc_operation.json``PDS_ROTATION_KEY`を使用し、`plc_signed.json`を作成
3. `plc_signed.json`を使用し、plcを更新
## plcを確認
```sh
did=did:plc:vzsvtbtbnwn22xjqhcu3vd6y
curl -sL "https://plc.directory/$did"|jq .alsoKnownAs
curl -sL "https://plc.syu.is/$did"|jq .alsoKnownAs
[
"at://syui.syui.ai"
]
[
"at://syui.syui.ai"
]
```

View File

@@ -1,31 +0,0 @@
---
title: "ue5でdualsenseを使う"
slug: "ps5-controller"
date: "2025-09-07"
tags: ["ue"]
draft: false
---
ps5-controllerは`dualsense`というらしい。ue5で使うには、以下のpluginを使います。fabかgithubのreleaseからpluginフォルダに入れてbuildするか2つの方法があります。
## dualsense plugin
- [https://github.com/rafaelvaloto/WindowsDualsenseUnreal](https://github.com/rafaelvaloto/WindowsDualsenseUnreal)
- [https://github.com/rafaelvaloto/GamepadCoOp](https://github.com/rafaelvaloto/GamepadCoOp)
![](/img/ue_ps5_controller_v0100.jpg)
`v1.2.10`からmultiplayを意識した`GamepadCoOp`との統合が行われました。
コントローラーのライトをキャラクター切り替え時に変更する処理を入れました。
<iframe src="https://blueprintue.com/render/tx_q1evf" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
## dualsenseの分解
最近、ドリフト問題が発生していたこともあり、何度も分解していました。
よって、このタイプのコントローラーなら簡単に修理できるようになりました。
今後も`dualsense`を使用していく可能性は高いですね。

View File

@@ -1,124 +0,0 @@
---
title: "comfyuiでwan2.2を試す"
slug: "comfyui"
date: "2025-09-10"
tags: ["comfyui"]
draft: false
---
comfyuiにwan2.2が来ていたので試してみました。wanがcomfyuiの公式に採用されているので、導入が簡単になっています。
今回は爆速になったLoRA採用でいきます。なお、無効化ードを外すとクオリティ重視の設定になります。
関係ありませんが、comfyui公式ページのコメントシステムは[giscus/giscus](https://github.com/giscus/giscus)を使用しているようですね。
# comfyui
```sh
$ git clone https://github.com/comfyanonymous/ComfyUI
$ cd ComfyUI
$ winget install python.python.3.13
$ pip uninstall torch torchaudio
$ pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu129
$ pip install -r requirements.txt
$ python main.py
```
もしvenvを使用する場合
```sh
$ python -m venv venv
$ venv\Scripts\activate
$ pip install -r requirements.txt
$ python main.py
```
## wan2.2
基本的にpromptから生成し、bypassを切ると画像を参照できます。
workflowをdownloadしてcomfyuiで開きます。
```sh
# ComfyUI/user/default/workflows/
$ curl -sLO https://raw.githubusercontent.com/Comfy-Org/workflow_templates/refs/heads/main/templates/video_wan2_2_5B_ti2v.json
```
必要なものは公式ページにリンクがあります。
[https://docs.comfy.org/tutorials/video/wan/wan2_2](https://docs.comfy.org/tutorials/video/wan/wan2_2)
```sh
ComfyUI/
├───📂 models/
│ ├───📂 diffusion_models/
│ │ └───wan2.2_ti2v_5B_fp16.safetensors
│ ├───📂 text_encoders/
│ │ └─── umt5_xxl_fp8_e4m3fn_scaled.safetensors
│ └───📂 vae/
│ └── wan2.2_vae.safetensors
```
![](/img/comfyui_wan22_0001.png)
<video src="/img/comfyui_wan22_0001.mp4" width="100%" controls></video>
## wan2-2-fun-control
これはポーズを動画から作成して動画を作ります。
```sh
$ curl -sLO https://raw.githubusercontent.com/Comfy-Org/workflow_templates/refs/heads/main/templates/video_wan2_2_14B_fun_control.json
```
[https://docs.comfy.org/tutorials/video/wan/wan2-2-fun-control](https://docs.comfy.org/tutorials/video/wan/wan2-2-fun-control)
```sh
ComfyUI/
├───📂 models/
│ ├───📂 diffusion_models/
│ │ ├─── wan2.2_fun_control_low_noise_14B_fp8_scaled.safetensors
│ │ └─── wan2.2_fun_control_high_noise_14B_fp8_scaled.safetensors
│ ├───📂 loras/
│ │ ├─── wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors
│ │ └─── wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors
│ ├───📂 text_encoders/
│ │ └─── umt5_xxl_fp8_e4m3fn_scaled.safetensors
│ └───📂 vae/
│ └── wan_2.1_vae.safetensors
```
## wan2-2-fun-inp
これは画像から画像を参考にして動画を生成します。
[https://docs.comfy.org/tutorials/video/wan/wan2-2-fun-inp](https://docs.comfy.org/tutorials/video/wan/wan2-2-fun-inp)
```sh
$ curl -sLO https://raw.githubusercontent.com/Comfy-Org/workflow_templates/refs/heads/main/templates/video_wan2_2_14B_fun_inpaint.json
```
```sh
ComfyUI/
├───📂 models/
│ ├───📂 diffusion_models/
│ │ ├─── wan2.2_fun_inpaint_high_noise_14B_fp8_scaled.safetensors
│ │ └─── wan2.2_fun_inpaint_low_noise_14B_fp8_scaled.safetensors
│ ├───📂 loras/
│ │ ├─── wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors
│ │ └─── wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors
│ ├───📂 text_encoders/
│ │ └─── umt5_xxl_fp8_e4m3fn_scaled.safetensors
│ └───📂 vae/
│ └── wan_2.1_vae.safetensors
```
## ゲームで動かしたほうがいい
今回、ゲームのスクショを使って動画を生成してみました。
しかし、ゲームで動かしたほうがよほど早く確実です。

View File

@@ -1,64 +0,0 @@
---
title: "comfyuiでフィギュア化する"
slug: "comfyui"
date: "2025-09-11"
tags: ["comfyui"]
draft: false
---
`gemini`でnano bananaというフィギュア化が流行っています。今回は、それを`comfyui`で再現してみようと思います。
# comfyui
```sh
$ git clone https://github.com/comfyanonymous/ComfyUI
$ cd ComfyUI
$ winget install python.python.3.13
$ pip uninstall torch torchaudio
$ pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu129
$ pip install -r requirements.txt
$ python main.py
```
もしvenvを使用する場合
```sh
$ python -m venv venv
$ venv\Scripts\activate
$ pip install -r requirements.txt
$ python main.py
```
## flux-1-kontext-dev
[https://docs.comfy.org/tutorials/flux/flux-1-kontext-dev](https://docs.comfy.org/tutorials/flux/flux-1-kontext-dev)
基本的にcomfyuiで作成した画像にはworkflowが保存されています。ですから、画像があればpromptやbypassなども含めて生成情報がわかります。情報が削除されていない限りは再現することが可能です。
今回は、`flux-1-kontext-dev`を使用します。
```sh
$ curl -sLO https://raw.githubusercontent.com/Comfy-Org/example_workflows/main/flux/kontext/dev/flux_1_kontext_dev_basic.png
```
```sh
📂 ComfyUI/
├── 📂 models/
│ ├── 📂 diffusion_models/
│ │ └── flux1-dev-kontext_fp8_scaled.safetensors
│ ├── 📂 vae/
│ │ └── ae.safetensor
│ └── 📂 text_encoders/
│ ├── clip_l.safetensors
│ └── t5xxl_fp16.safetensors or t5xxl_fp8_e4m3fn_scaled.safetensors
```
[msg content="prompt: Convert to collectible figure style: detailed sculpting, premium paint job, professional product photography, studio lighting, pristine condition, commercial quality, toy photography aesthetic"]
以下は`wan2.1`で生成した時の動画。
![](/img/comfyui_wan21_0001.webp)
![](/img/comfyui_flex1_nano_banana_0001.png)
できました。

View File

@@ -1,110 +0,0 @@
---
title: "blenderで作ったモデルを改良した"
slug: "ue-blender-model"
date: "2025-09-18"
tags: ["ue", "blender"]
draft: false
---
blenderで作ったモデルは、ueで動かしてみると、なかなか思ったとおりに動かないことは多いです。原因も多種多様で、とても一言では言い表せない。
今まで気になっていたところは以下の2点でした。
1. 指がちゃんと動かない
2. 衣装のすり抜けが気になる
## 指を修正するにはueからblenderへ
blenderで作ったモデルは指がぎこちない動きで、複数の要因が関係しています。特に大きいのが手動で塗っていたウェイトペイント。
しかし、これを完璧に塗り、かつueで動作確認するのはよくありません。なぜなら、blenderとueで動きが異なるからです。それも全く異なるわけではなく微妙に合わないのです。
ということで、ueでまず指の動きがちゃんとできているモデルをblenderに持ってきて、手の部分を移植するというのが今回採用した方法です。
- o: `ue -> blender`
- x: `blender -> ue`
![](/img/ue_blender_model_ai_v0701.png)
![](/img/ue_blender_model_ai_v0702.png)
動きを見るのは、vrm4uの`RTG_UEFN_${name}`を使用します。
blenderの操作方法です。ポイントだけ書いておきます。
1. modelを2つ読み込む。aとbとする。
2. bのboneとbody(object)でいらない部分を削除する。ここでは手の部分だけ残す。 key[x]
3. a, bで大体の位置を合わせる。 key[g, z]
4. bのboneを選択肢、aのboneを選択して統合する。 key[C-j]
5. サイドバーのアーマチュアのところをみて、手のボーンを腕のボーンにいれる(これはモデルによる)。特に手がオフセット維持で指についていることが重要。 key[C-p]
6. bのbody(object)を選択し、モディファイアからaのbodyにデータ転送する。データ転送では、頂点データ、頂点グループを選択。適用する。
7. bのbodyを選択し、aのbodyを選択して統合する。 key[C-j]
8. bodyを編集して、手と腕をつなげる。
あとは、vrm exportの際に指とかのボーンを自動で入れれば動くと思います。
私の場合は、スカートに必要なボーンを追加したりもしました。これはueでの動作を意識しましょう。
## スカートと足の動きを関連付ける
衣装は、`Spine`以下にあるワンピースなので、厳密にはスカートではありませんが、ここではスカートということにします。
このスカートは、3d-modelでは非常に厄介なもので、足の動きに追従できず体に入り込んでしまうのです。
これを解消するためには様々な方法があり、たとえblenderの機能を使って解消しても、ueでは効果がありません。よって、こちらもueから解消するのがベストです。
今回、ABPに`Look At`を使うことで解消しました。
```md
# ABP
## Look At
- Bone to Modify: スカート前、中央
- Look at Target: Spine (中心)
## Look At
- Bone to Modify: スカート前、左
- Look at Target: LeftLeg (左足)
## Look At
- Bone to Modify: スカート前、右
- Look at Target: RightLeg (右足)
```
`Look at Location`の位置は調整してください。私の場合は`0, 50, 0`です。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/3o98Aivn--0?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
完璧ではないけど、これでもかなり改良されたほう。
## 実践投入
### unique skillのデザインを考える
まず、アイのunique skill(ユニークスキル)のデザインを考えました。
1. カメラワークは正面に移動
2. スロー再生を開始
3. 忍術のようなモーション
4. カメラを通常に戻す
5. 属性の範囲ダメージ
### tatoolsを使って忍術モーションを作る
[tatools](https://www.fab.com/ja/listings/a5d3b60d-b886-4564-bf6d-15d46a8d27fe)を使います。
[https://github.com/threepeatgames/ThreepeatAnimTools](https://github.com/threepeatgames/ThreepeatAnimTools)
使い方は簡単ですが、動画が分かりづらいので、ポイントだけ解説します。pluginの起動、既存のアニメーションの修正、保存です。
1. pluginの起動は、`/Engine/Plugins/ThreepeatAnimTools/Picker/ThreepeatAnimTools_CR_Picker`を起動します。アウトライナーにでもウィンドウを追加しましょう。
2. 修正したいアニメーション(アニメシーケンス)を開いて、`シーケンサで編集 -> コントロールリグにベイク -> CR_UEFNMannyTatoolsRig`します。
3. これでlevel(map)上でレベルシーケンスを開けます。
4. ここからが修正ですが、まず、例えば、腕を選択して向きを変えたとしましょう。これだけでは保存されません。もとに戻ってしまいます。ここで、(1)シーケンサの下にあるアニメーションを削除し、(2)選択している部位のすべてのコンマを削除します。再生してみると編集したとおりになります。
5. 保存は、シーケンサのメニューバーにある保存ボタン(現在のシーケンスとサブシーケンスを保存)を押します。もとのアニメーションを開くと反映されています。
![](https://ue-book.syui.ai/img/0016.png)
### 実戦動画
<iframe width="100%" height="415" src="https://www.youtube.com/embed/tJQ1y-8p1hQ?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

View File

@@ -1,401 +0,0 @@
---
title: "ue + vrm4u + mac/ios"
slug: "ue-mac"
date: "2025-09-22"
tags: ["ue", "mac"]
draft: false
---
# ue mac/ios support
- ue 5.6.1
- mac 26
- windows 11
## ue for mac
現時点でのxcode26には対応していません。ueを起動する際はxcodeを切り替えます。そうではないとbuild optionが機能しません。(クエリ中になる)
appleの方針で製品のversionは年号になりました。
> ex: mac26, ios26
```sh
xcode-ue () {
disk=hdd
case $1 in
(u | ue) d=/Volumes/${disk}/Xcode.app/Contents/Developer ;;
(*) d=/Applications/Xcode.app/Contents/Developer ;;
esac
sudo xcode-select --reset
sudo xcode-select --switch $d
}
```
ここでは、`/Volumes/${disk}/Xcode.app`をxocde16とします。
buildすると`/$Project/Mac/${Project}.xcarchive`ができます。
```sh
# Finderでアーカイブを右クリック → "Show in Finder"
# .xcarchive を右クリック → "Show Package Contents"
# ./Products/Applications/ai.app をダブルクリック
```
```sh
# ターミナルでTeam IDを確認
$ security find-identity -v -p codesigning
```
## ue for linux
linux版もbuildすることにしました。
- steam osはたしかlinuxだったはず
- ゲームとpixelstreamingをlinux serverで動かせれば楽
[https://dev.epicgames.com/documentation/ja-jp/unreal-engine/linux-development-requirements-for-unreal-engine](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/linux-development-requirements-for-unreal-engine)
必要なものをdownloadして、windows上で環境を整えます。`clang 18.1.0`
ue editorを開いてメニューの`tool -> c++`で何かを作ります。すると、`.sln`がproject rootにできます。できなければ、`.uproject`を右クリックで`.sln`を作ります。
epic launcherでue installerのoption:linuxを再び有効にします。
`.sln`を開いてbuildに`linux`を選択し、右バーのMyProject(Airse)を右クリックでbuildします。pluginなどが対応していないときは`.uproject`を開いて`false`に変更します。対応している場合もbuild errになることがあります。
`wsl ubuntu`なども必要になるかもしれませんが、情報にはありません。
## vrm4u for mac
まずc++のprojectをueで作成します。
`libassimp.a`を生成します。
[https://github.com/ruyo/assimp](https://github.com/ruyo/assimp)
これを`/Plugins/VRM4U/ThirdParty/assimp/lib/Mac/libassimp.a`に置いて、projectで開きます。pluginがbuildされますが、`/Plugins/VRM4U/Binaries/Mac/*`が生成され、これを使うことになります。
```sh
/Plugins/VRM4U/
├── Binaries
│   ├── Mac
│   │   ├── UnrealEditor-VRM4U.dylib
│   │   ├── UnrealEditor-VRM4UCapture.dylib
│   │   ├── UnrealEditor-VRM4UCaptureEditor.dylib
│   │   ├── UnrealEditor-VRM4UEditor.dylib
│   │   ├── UnrealEditor-VRM4UImporter.dylib
│   │   ├── UnrealEditor-VRM4ULoader.dylib
│   │   ├── UnrealEditor-VRM4UMisc.dylib
│   │   ├── UnrealEditor-VRM4URender.dylib
│   │   └── UnrealEditor.modules
├── ThirdParty
│   ├── assimp
│   │   ├── lib
│   │   │   ├── Mac/libassimp.a #このファイル
└── VRM4U.uplugin
```
他のprojectで利用する際は`/Binaries`, `/ThirdParty`をcopyすればいいので、取っておいてください。vrm4uが更新されたときは再びprojectでbuildしたほうがいいですね。
## ue for git
mac/iosでもbuildできるようにすること、そういったprojectを作成することを目指します。
最終的にiosでもプレイできるゲームを作りたいなと思っていて、これは単純なカードを集め、キャラを強化するゲームにしようと考えています。
mac/iosは軽量パッケージとして必要最小限の構成で作る予定です。特に重いworld systemを分離、統合が簡単にできるようにする予定です。
そこで、winにはrsyncがありませんので、gitを使用することにしました。
```sh
$ winget install git.git
$ cd ${project}
$ git pull
```
署名も機能させておきたいので、gpgを使います。commit, pushすると`verify`が付くやつです。
```sh
$ winget install gnupg.gnupg
$ which gpg
$ git config --global gpg.program "C:\Program Files (x86)\gnupg\bin\gpg.exe"
```
現在使っているterminalは作成(パスフレーズ)が動作しないようです。したがって、mac, linuxで作成してimportします。
```sh
$ gpg --full-generate-key
XXX
$ id=XXX
$ gpg --export-secret-keys ${id} > ~/gpg-key-win.asc
---
$ gpg --import C:\Users\${USER}\gpg-key-win.asc
$ rm C:\Users\${USER}\gpg-key-win.asc
$ gpg --edit-key ${id}
trust
5
quit
```
作成したkeyはwinでimportした後はmac, linuxから削除したほうがいいかも。コマンドは書きません。
これをgit-serverに登録しておけばいいでしょう。
```sh
$ gpg --armor --export ${id}
```
あと、`~/.gitconfig`も更新しておきます。
```sh
$ git config --global user.signingkey ${id}
$ git config --global commit.gpgsign true
```
```sh
# 再起動
$ gpgconf --kill gpg-agent
$ gpg-connect-agent /bye
```
# Airse
ゲームのタイトルは`Airse`に決まりました。まだ決まっていなかったのかというと、決まっていませんでした。仮名で作ってきましたが、これを機に根本的な部分を見直しました。
- ai + verse
- [A]irse = `unrealengine` naming rules
## rse
RSE = [R]elativistic [S]tellar [E]volution
> ja: 相対論的恒星進化
## name rule
- app, name, project = Airse
- repo, dir = `ai/rse`
- id = `ai.syui.rse`
file, variable, function, etc. follow the following name rules.
1. use `_` to separate characters after abbreviations such as `CBP`.
2. use `_` to separate characters before numbers.
3. use capital letters for all other names, priority: `ue > repo`
ex: `CBP_CharacterAiSkill_1`
## game system
`[ai, yui, at, world]`
- AUTHOR = Syui
- PROJECT = Airse
```sh
# example
/Content/${AUTHOR}/${PROJECT}/
├── World/ # world system
│ ├── Origin/ # origin system (dream system)
│ └── BGM/ # bgm system
├── Yui/ # yui system
│ ├── Character/ # character system
│ ├── Enemy/ # enemy system
│ ├── Evolution/ # e system (evo system)
│ ├── Voice/ # voice system
│ └── Live/ # v system (live system)
├── AI/ # ai system
│ ├── Action/ # action system
│ └── Status/ # status system
└── AT/ # at system
├── Item/ # item system
├── Card/ # card system
└── Save/ # save system
```
## bad ex
1. `ai/airse` = `[ai] x 2`
2. `syui/ai/rse` = `priority < ue`
```md
[fix]
1. ai/airse -> ai/rse
2. syui/ai/rse -> Syui/Airse
```
## version
今までgame systemのversionでやってきました。game systemのversionはアイのモデルの変化で決定されてきました。
しかし、ue versionがわかったほうがいいので、以下の形式に変更します。
- version: `5.7.0.6.11`
```sh
[ue] [system] [fix]
5.7.0.6.11
{
"version": "5.7.0.6.11",
{
"ue": "5.7.0",
"system": "6",
"fix": "11"
}
}
```
# 開発の方向性
考えに変化があったので、お伝えします。大きなものは以下の2つです。
1. ゲームの完成を目指すが、ちゃんとしたシステムを作ることも目指す
2. 完璧に自信があるものでゲームを作る
## ちゃんとしたシステムを作る
ゲームの完成を目指して、色々と考えやってきましたが、ちゃんとしたシステムを作ることを優先したほうがいいと考えるようになりました。
というのも、ちゃんとしたシステムを作っておけば、それを組み合わせるだけでいろんなゲームを作れるからです。
ゲームを構成する要素、その基本というのは決まっていて、システムも決まっています。例えば、キャラクター操作。それさえ本当にレベルの高いシステムを自分で作れるなら、色々なゲームに応用できますよね。
ゲームを完成させられることは素晴らしいことです。
しかし、ゲーム制作をやめてしまうときはどんなときでしょう。ゲームを完成させるのは本当に大変で、そこに到達できる人は少ないのですが、しかし、到達できた人も、そこでやめてしまう人が多いんじゃないでしょうか。
続けられる人はごく僅かで、大きな理由は2つあると思っています。
一つは成功しなかったこと。続けるだけのメリットを感じられなかったことだと思います。
2つ目は、作ってきたものがどうしようもなく再利用できない状態にあることだと思います。例えば、作ってきたものがシステム化されておらず、他のゲームを作ろうとしたときに利用できない形、ごちゃごちゃで自分にも把握できず、使い回せない、あるいは別のゲームに統合できない状態であること。
そうでなければ、他のアイデアをすぐに試してみようとなりやすい、ゲーム開発のハードルは低くなっているはずです。にも関わらず、ちょっとやってすぐ辞めてしまう人がいます。
それはなぜかというと、作ってきたものがゴチャゴチャで使い物にならなかったときじゃないかと思います。もちろん、本人の熱意とか継続性とか意思とかそういったものもあります。でも、心が折れそうな時にもコントロール可能な環境要因があるはずで、そうした環境を構築する能力も重要なのではないかと思うのです。周りにちゃんとしたシステムがたくさんある、そんな状態を作る、ということ。そういうのを目指していきたいと思い、ちゃんとしたシステムを作ることも優先的に考え始めました。
## 自信があるものでゲームを構築する
インディーズゲーム、特に3dは本当に難しい。私のゲーム開発の方針は、少しずつ決まってきて、注意しなければならないこともわかってきました。
それは、無理をしないこと。無茶をしないと言い換えてもいいでしょう。
しかし、この無茶をしないというのは表現が難しく、本人が無茶だと思っていなくても無茶に含まれることは多いと思います。
例えば、個人が3dでゲームを作ることでしょう。
「いやいや、そんな」と思われるかもしれませんが、細かいところを見ると、個人開発で3dをやるのは、結構な無茶だと今では思います。また、ueを使うこともそれに含まれるかもしれません。
ueや3dを使うと、個人でも大きいものが作れた気になってしまう。広いマップ、リアルな描写、動く3dモデル。
しかし、扱いきれない武器ほど怖いものはありません。初心者の個人開発にとって、それを置いていくほうがいい場合もある。だけどそれを手にとって進んでしまうのです。
この場合、個人ができるのは、その武器に圧倒的な制限をつけ、使える場面を限定することだと思います。
私も自分のゲームをプレイしていて、この部分はよくできているなというところが少しあります。しかし、総合的なゲーム性で考えると、全然ダメですね。
でも、それなら本当によくできた自信がある部分だけでゲームを作ればいいんじゃないかな。
そこには工夫が必要になるかもしれないし、コンセプトが重要になると思いますが、私はそのような結論に至ります。
完璧に動作する部分、バグが少なく、自信があるところ、自分のゲームの最大の魅力、そこだけを使ってゲームを構築することを今は考えています。
面白いかどうかは、正直わかりませんが、パッと見で、少しプレイして、「あれ、これすごいんじゃない」と思わせることができたら成功だと思います。最初はそこを目指していこうかなと。
無理をしてできることを増やしても意味がありません。特に個人開発で、3dで、かつueだと、それはとても危険な気がします。
とはいえ、重要なのは、たくさん作ること。
3dで開発するな、ueを使うな、開発者は好きなものを作るな、と言いたいわけではありません。
言いたいのは、たくさん作ってきたものの中には光るもの、よくできたものがいくつか出てきます。そういったものを使ってゲームを構築する。その方向性でも考えてみる、ということ。
おそらく、3dで作る場合、ueを使う場合、個人開発者が好きなものを作る場合に、このような制限は役に立つと思います。
以上が、最近の個人開発の方向性、あるいは考え方の話です。
## ちゃんとしたシステムをどのように作るのか
1. 名前規則に忠実であること。フォルダ、ファイルや変数、関数などのすべて。例外的な名前規則を付けたものを置く場所を決めておくこと。
2. 簡単にシステムを分離、統合できる状態であること。
3. 依存関係を減らし、ファイルは自分のフォルダのみで動く状態にすること。assetはdownloadするが、できる限りそれを使わず、使用するものは自分のフォルダに置いて整理すること。それを使ってシステムを構築すること。
4. その時に使わないものは即座に削除すること。これはノードや変数、関数、すべて。
ようは、売られているassetやpluginの状態を目指すのが一番良くて、downloadすればすぐに使えるような形が理想的。
## pvをゲームにする試み
自信があるものでゲームを構築するといっても、どのようにやればいいのでしょう。
ここからは自分が書いたメモを貼り付けます。
原神のキャラクターpvがある。とても参考になりそうだ。
[https://www.youtube.com/watch?v=0MiIciljaWY](https://www.youtube.com/watch?v=0MiIciljaWY)
pvを作ったほうがいいのかと思ったことがあって、いや、pvをゲームにすればいいという案が浮かんだ。
これはpixelstreamingなどで配信することを考えたゲームといえばいいかな。要は簡単に遊べる一つの軽量パッケージのようなものだ。体験版みたいな感じだろうか。
さて、ゲームを開始すると同時にロゴ、音楽が流れる。pvのような少しのムービーがあり、キャラクターが紹介される。
次に、戦闘シーンだが、これに関しては例えばユーザーがスキル、バーストの2種類の技を発動できるようにしておき、ユーザーの行動によってpvが変わるという仕様である。仕様というか、仕組みである。つまり、pvをゲームに、ゲームをpvに。そんな感じの試み。
軽量パッケージ化をどのように進めるか、あるいは、簡単なゲーム作りはどう進めるかに迷っていたが、この方向性で行こうと思う。
pvを作ると同時に、ゲームを作る。ゲームをpvにする。単純だが効果的なアイディアだと思う。
これなら操作可能範囲も大幅に削れるし、基本的にpvを作って、一部操作可能なゲームにするだけだ。
ゲーム性とダメージ変動。これについては簡易的な個人アカウントみたいなものを使って実装したいという案がある。ゲームには再生ボタンがあり、実行するとpvが流れる仕組みだが、上にログインボタンがあり、そこでログインできる。ログインといってもhandleを入れるだけの簡単なものだ。で、oauthで簡単なゲームアカウント作成のページを別に作っておく。それを実行していると、atprotoからデータを読み取る。
ゲーム作りの方向性として、軽量で、しっかりと動き(バグがなく)、効果的で面白いものを作ろうとしていて、その答えの一つが、pvをゲーム化するという案だった。
これはいくつの前提思考をもとに構築されている。前提思考とは、シンプルなゲームを作ろうという試みで、最初はわかりやすいシューティングゲームのようなものを構想していた。
しかし、この構想には不足がある。一言でゲームの始まりは?終わりは?クリアやゲームオーバーの演出は?その他諸々のゲームにとって重要な要素が伝わらない、そして、その部分をどう構築していくか見えない点にあった。
この弱点を克服した案が「ゲームキャラのpv動画をゲームにしよう」というものだ。
もっとシンプルに言うと「pvをゲームにする」ということ。これならイメージがはっきりと浮かび上がり、かつゲーム化する際の要素も決まってくる。ゲームの始まりと終わり、ゲーム中がどのようなものかをはっきりとイメージすることができる。
では、実際のキャラpvを分析してみよう。ここでは原神のウェンティの動画を分析する。
1. 黒い画面にロゴ(作者)が浮かび上がる。美しいBGMが流れる
2. 緑の木々と青い空
3. カメラがキャラクターの方に移動
4. キャラクターが物語の重要なセリフ
5. 背後に敵の影
6. 敵が攻撃してくる、攻撃はキャラクターの方向に向かう、カメラワーク
7. キャラは優雅にそれを避ける
8. キャラの紹介文や演出が入る
9. バトルシーン
10. スキル、爆発の紹介(セリフあり)
11. 最後のキャラを正面に通常攻撃、弓が放たれ
12. ロゴ(タイトル)が浮かび上がる。美しいBGMの終わりと合わせる
これを自分のゲームに当てはめてみる。
1. 黒い画面にロゴ(syui)が浮かび上がる。BGM、ピッチは0.2
2. アイの家とアイが屋根の上に座っている、モーションあり
3. セリフ、物語は天空に浮かぶ島からはじまる(誰しもが興味を掻き立てられる内容=天空城や古代兵器)、BGMのピッチを徐々に上げていくmax:1.0
4. 地球に近づく黒い影
5. 砲撃が始まる、赤い光が星をめがけて落ちていく
6. アイが高速飛行して、敵の場所に移動
7. キャラの紹介と音声
8. スキルとバーストの技紹介、キーやボタンをかっこよく表示(ななめ、大きめ)。背景は灰色とカラーをあわせる
9. バトルシーン(プレイヤーが操作可能)
10. 1ボタン(1操作)でゲームクリア。時間経過で次のシーンに移行
11. 最後にキャラを正面にアップし通常攻撃
12. ロゴ(ai)が浮かび上がり、BGMの終わりと合わせる
面白さの実装には弱いpv。これをどう克服していくかを考える。
- バトルシーンを少し長くする
- atprotoのデータを参照し、現実アカウントの値をダメージ表記に反映する
- ダメージ総合値を表示したり記録したりする
原神の面白さは元素反応にある。つまり、キャラクターの攻撃の組み合わせ。ダメージ増加量など。原神では、キャラを敵の前に動かせる、スキル回し、爆発という流れで戦闘を楽しむ。これを分解すると、「合わせることと、ダメージ増加量のコントロール」だと思う。これを自分にもできる簡単な仕組みで実現できないかを考えている。

View File

@@ -1,111 +0,0 @@
---
title: "ue 5.7.0pでprojectを作り直す"
slug: "ue-57p"
date: "2025-09-25"
tags: ["ue", "mac", "linux"]
draft: false
---
`unreal engine 5.7.0-preview`が来ました。
[https://forums.unrealengine.com/t/unreal-engine-5-7-preview/2658958](https://forums.unrealengine.com/t/unreal-engine-5-7-preview/2658958)
`vrm4u``5.7`に対応しているので、game animation sample(gasp)をベースに構築してみます。
- gasp: [https://www.fab.com/listings/880e319a-a59e-4ed2-b268-b32dac7fa016](https://www.fab.com/listings/880e319a-a59e-4ed2-b268-b32dac7fa016)
- vrm4u: [https://github.com/ruyo/VRM4U/releases/](https://github.com/ruyo/VRM4U/releases/)
## game animation sample for ue5.7
1. まず空のprojectをue5.7で作ります。私は後に利用する`Valiant Combat`で作りました。[a]とします。
2. game animation sampleのproject(gasp)はまだ対応していないので、ue5.6で作ります。[b]とします。
3. [b]の`${project}/Config`, `${project}.uproject`を参考に[a]に移植します。
```json:${project}.uproject
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "AnimationWarping",
"Enabled": true
},
{
"Name": "RigLogic",
"Enabled": true
},
{
"Name": "LiveLink",
"Enabled": true
},
{
"Name": "LiveLinkControlRig",
"Enabled": true
},
{
"Name": "PoseSearch",
"Enabled": true
},
{
"Name": "AnimationLocomotionLibrary",
"Enabled": true
},
{
"Name": "MotionWarping",
"Enabled": true
},
{
"Name": "HairStrands",
"Enabled": true
},
{
"Name": "Chooser",
"Enabled": true
},
{
"Name": "Mover",
"Enabled": true
},
{
"Name": "NetworkPrediction",
"Enabled": true
}
]
```
特に`../Config/DefaultEngine.ini`が重要です。
これで完了です。普通に動きます。グラフィックがきれいになっているような気がして、軽量感も少し上がってるかも。
![](/img/ue_v570p_gasp_vrm4u_0001.png)
package buildは少し分かりづらいですが、`[]Windows`となっているところを`[o]Windows`にしないといけません。gasp + vrm4uでのpackage buildはwin, macで成功しました。macはxcode26でいけます。
linuxは失敗です。`Microsoft.MakeFile.Targets 44`なので、調べてみると`toolchain: v26_clang-20.x.x`が必要なのでしょう。まだ公開されていないと思います。docsにはlinkがありませんでした。
[https://dev.epicgames.com/documentation/ja-jp/unreal-engine/linux-development-requirements-for-unreal-engine](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/linux-development-requirements-for-unreal-engine)
1. `/Source/`にc++を置いて、`.uproject`を右クリックで`generate visual studio project files`を選択。
2. `${project}.sln`を開きます。
3. Development: Linux
4. 右エクスプローラーから`$project`を選択して、右クリックでbuildを開始。
{{< msg type="warning" content="This version of the Unreal Engine can only be compiled with clang 20.x. clang 18.1.0 may not build it" >}}
```sh
# https://cdn.unrealengine.com/CrossToolchain_Linux/v25_clang-18.1.0-rockylinux8.exe
& "C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat" Airse Linux Development -Project="C:\Users\syui\Documents\Unreal Projects\5.7\Airse\Airse.uproject" -WaitMutex -FromMsBuild
エラー: MSB3073
Airse: "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.MakeFile.Targets" 44
```
## ue5.7に移行する
最近はproject再構築と新しいbuild環境の構築をやっていたので、ついでに`5.7`に移行することにしました。まだ手を付けている部分が少ない時に移行したほうがよいと判断。

View File

@@ -1,24 +0,0 @@
---
title: "world origin systemができた"
slug: "dream-system"
date: "2025-10-08"
tags: ["ue"]
draft: false
---
## 現実と夢の世界
私のゲームでは、現実に合わせて世界を作るworld system(planet system)とゲームに合わせて作るorigin system(dream system)があります。
origin systemの由来は、地球平面説や天動説、つまり、古代の宇宙観です。惑星はお椀の形をしており、そこに地上が乗っているイメージ。
![](/img/ue_world_system_img_0001.png)
このorigin systemは、軽量性、独立性、統合性を考えられたデザインで、非常に扱いやすい形となっています。
![](/img/ue_world_system_img_0002.png)
今回は`ue5.6.1 -> ue5.7.0p`の移植作業が完了し、origin systemの仕様ができました。これで新しい惑星や地上の追加、統合が楽になります。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/xQEGkTrJ45Y?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

View File

@@ -1,94 +0,0 @@
---
title: "comfyuiを自動化する"
slug: "comfyui-auto-controlnet"
date: "2025-10-19"
tags: ["comfyui"]
draft: false
---
今回は、`comfyui`の自動化を紹介します。
## comfyuiの自動化手順
以下の機能を使用します。
1. `Apply InstantID`: 顔を指定します。
2. `Apply ControlNet`: ポーズを指定します。
まずこちらのworkflowを読み込むと早く書けます。workflowは通常、comfyuiで作られた画像に記録されています。
[https://docs.comfy.org/tutorials/controlnet/pose-controlnet-2-pass](https://docs.comfy.org/tutorials/controlnet/pose-controlnet-2-pass)
ここから`Apply InstantID`を追加します。`Apply ControlNet`から`positive`, `negative``InstantID``KSampler`につなぎます。
```md
[ControlNet] -> [InstantID] -> [KSampler]
```
自動化には以下のノードを使います。
1. `Batch Image Loop Open`: loop処理を作れます。
2. `Load Image Batch From Dir`: 画像をディレクトリから読み込みます。
3. `LogicUtil_Uniform Random Choice`: ランダムで区切り文字を選択します。loop中にpromptの中身を変えます。
なお、`comfyui`の外部ノードは以下を使用しています。
- comfyui_instantid
- loop-image
- comfyui-inspire-pack
自動化の手順としては、まず、ポーズをディレクトリに保存しておき、`Load Image Batch From Dir`で読み込みます。`Batch Image Loop Open`につなぎます。それを`Apply ControlNet`につなぎます。
最終的に`KSampler`から`VAE Decode`をつなぎ、そこから`Batch Image Loop Close`でループを閉じます。
もしここで保存したければ、`VAE Decode``Save Image`にも繋いでおきます。
```md
[Load Image Batch From Dir] -> [Batch Image Loop Open] ->
[ControlNet] -> [InstantID] -> [KSampler] ->
[VAE Decode] -> [Batch Image Loop Close]
```
[![](/img/comfyui_instantid_controlnet_0001.png)](/img/comfyui_instantid_controlnet_0001.png)
[![](/img/comfyui_instantid_controlnet_0002.png)](/img/comfyui_instantid_controlnet_0002.png)
[![](/img/comfyui_instantid_controlnet_0003.png)](/img/comfyui_instantid_controlnet_0003.png)
[![](/img/comfyui_instantid_controlnet_0004.png)](/img/comfyui_instantid_controlnet_0004.png)
## comfyuiの便利なード
`filename_prefix``Get Date Time String(JPS)`を使用しています。これでファイル名が重複しづらくなります。
役立つ外部ノードです。
- comfy-image-saver
- JPS-Nodes
- comfyui-custom-scripts
例えば、loop中にpromptをランダムで変える処理を追加しています。これは`LogicUtil_Uniform Random Choice`で実現しており、区切り文字は`,`です。
```md
background: city street,
background: cloud sky,
background: galaxy planet,
```
## ポーズの作成手順
[https://openposes.com/](https://openposes.com/)
例えば、自作ゲーム動画を保存し、`ffmepg`で画像化します。
```sh
$ ffmpeg -i input.mp4 output%04d.png
```
その画像を使って、ポーズを作成することができます。
- `OpenPose Pose`: `comfyui_controlnet_aux`
[![](/img/comfyui_instantid_controlnet_0005.png)](/img/comfyui_instantid_controlnet_0005.png)

View File

@@ -1,115 +0,0 @@
---
title: "gpt-ossを使用する"
slug: "lms-gpt-oss"
date: "2025-10-19"
tags: ["openai", "AI", "windows"]
draft: false
---
今回は、openaiの[gpt-oss](https://huggingface.co/openai/gpt-oss-120b)を使用する方法です。
[https://openai.com/ja-JP/index/introducing-gpt-oss/](https://openai.com/ja-JP/index/introducing-gpt-oss/)
`120b`, `20b`があります。好きな方を使いましょう。ここでは`20b`を使用します。
```sh
$ ollama run gpt-oss:20b
or
$ lms get openai/gpt-oss-20b
```
今回は、lms(LM Studio)で使用します。
```sh
# https://lmstudio.ai/
$ pip install lmstudio
# https://huggingface.co/openai/gpt-oss-20b
$ lms get openai/gpt-oss-20b
```
今後、家庭のpcは、gpu(nvidia, amd)を積んで`lms``gpt-oss`を動かすのが一般的になりそう。
## サービスとして公開する
例えば、apiとして公開することもでき、それを自身のサービス上から利用するなどの使い方があります。なお、`lms`にもこのような機能はあります。
```sh
# https://cookbook.openai.com/articles/gpt-oss/run-transformers
$ transformers serve
$ transformers chat localhost:8000 --model-name-or-path openai/gpt-oss-20b
---
$ curl -X POST http://localhost:8000/v1/responses -H "Content-Type: application/json" -d '{"messages": [{"role": "system", "content": "hello"}], "temperature": 0.9, "max_tokens": 1000, "stream": true, "model": "openai/gpt-oss-20b"}'
```
```sh
$ cloudflared tunnel login
$ cloudflared tunnel create gpt-oss-tunnel
```
```yml:~/.cloudflared/config.yml
tunnel: 1234
credentials-file: ~/.cloudflared/1234.json
ingress:
- hostname: example.com
service: http://localhost:8000
- service: http_status:404
```
```sh
$ cloudflared tunnel run gpt-oss-tunnel
```
ただ、apiのreqにはキーとか設定しておいたほうがいいかも。
## 高速、大規模に使うには
`vllm`を使います。linuxが最適です。gpu(nvidia-cuda)がないときついので、win + wslで動かします。nvidiaの`H100`や`DGX Spark`が必要になると思います。
cudaはcomfyuiで使っている`cu129`に合わせました。
```sh
$ wsl --install archlinux
$ wsl -d archlinux
$ nvidia-smi
```
```sh
$ mkdir ~/.config/openai/gpt-oss
$ cd ~/.config/openai/gpt-oss
$ python -m venv venv
$ source venv/bin/activate
$ pip install --upgrade pip
$ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu129
$ pip install vllm transformers
$ python -m vllm.entrypoints.openai.api_server \
--model openai/gpt-oss-20b \
--port 8000 \
```
```sh
$ curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-oss-20b",
"messages": [{"role": "user", "content": "こんにちは!"}]
}'
```
## お金の使い道
最近、iphoneやmacを買うより、`DGX Spark`を買ったほうが良いのではないかと考えることがあります。
[https://www.nvidia.com/ja-jp/products/workstations/dgx-spark/](https://www.nvidia.com/ja-jp/products/workstations/dgx-spark/)
pc(RTX)やmacは、60万円ほどかかりますし、それは`DGX Spark`の値段と同じです。どうせ同じ値段を使うなら、何を買うのが良いのでしょう。
パソコンのスペックというのは、毎年それほど変わりません。RTXにしても同じです。
とするなら、既に持っているものではなく、持っていないスパコンを購入し、そこにAIをホストしたり、あるいはその性能をpcから利用する事を考えたほうが良いのではないか。最近はそんなことをよく考えます。
今後はpcを買う時代ではなく、スパコンを買う時代に突入するかもしれません。

View File

@@ -1,24 +0,0 @@
---
title: "plamo-2で翻訳する"
slug: "lms-plamo-2"
date: "2025-10-19"
tags: ["lms", "AI", "windows"]
draft: false
---
今回は、`lms`で[pfnet/plamo-2-translate](https://huggingface.co/pfnet/plamo-2-translate)を使用する方法です。
![](/img/lms_plamo2_0001.png)
- [https://huggingface.co/mmnga/plamo-2-translate-gguf](https://huggingface.co/mmnga/plamo-2-translate-gguf)
- [https://huggingface.co/mmnga/plamo-2-translate-gguf/discussions/1/files](https://huggingface.co/mmnga/plamo-2-translate-gguf/discussions/1/files)
`lms``mmnga/plamo-2-translate-gguf`をdownloadして読み込みます。
次に、`discussions/1`にある`en2ja.preset.json`, `ja2en.preset.json`のファイルを保存するなり、作成して、それをプリセットからインポートします。
```sh
$ curl -sL "https://huggingface.co/mmnga/plamo-2-translate-gguf/raw/refs%2Fpr%2F1/plamo%202%20translate%20en2ja.preset.json" > en2ja.preset.json
$ curl -sL "https://huggingface.co/mmnga/plamo-2-translate-gguf/raw/refs%2Fpr%2F1/plamo%202%20translate%20ja2en.preset.json" > ja2en.preset.json
```

View File

@@ -1,39 +0,0 @@
---
title: "macbook air(mid 2011)のarchlinuxでフリーズ対応"
slug: "arch-macbook"
date: "2025-10-20"
tags: ["archlinux", "macbook"]
draft: false
---
今回はmacbook air(mid 2011)のarchlinux運用の話をします。
```sh
$ uname -r
6.12.53-1-lts
```
運用のコツとしては、`linux-lts`を使うこと。`linux-firmware`を入れないこと。`broadcom-wl-dkms`を入れること。
```sh
$ pacman -S linux-lts
$ pacman -S pacman -S broadcom-wl-dkms linux-lts-headers
$ grub-mkconfig -o /boot/grub/grub.cfg
---
$ pacman -Qq | grep "^linux-firmware" | sudo pacman -R -
$ mkinitcpio -P
```
```sh:/etc/pacman.conf
IgnorePkg = linux linux-headers broadcom-wl-dkms
```
linux-ltsをupdateしたときは、必ず`broadcom-wl-dkms`をreinstallをしないと動きません。
## usbからの実行
```sh
$ mount /dev/sda2 /mnt
$ mount /dev/sda1 /mnt/boot
$ arch-chroot /mnt
```

View File

@@ -1,39 +0,0 @@
---
title: "archlinuxでvnc"
slug: "arch-vnc"
date: "2025-10-20"
tags: ["archlinux", "vnc", "mac"]
draft: false
---
自分のブログに書いてあると思ったんだけど、見当たらなかったので。
`xorg`, `i3`を使用しています。
```sh
$ sudo pacman -S tigervnc
```
```sh
$ vncpasswd
$ x0vncserver -rfbauth ~/.config/tigervnc/passwd
```
macから接続するには
```sh
$ open vnc://192.168.1.2:5900
```
## 操作感を同じにする
1. 自動起動
2. `Win`から`Alt`に変更。その上で`setxkbmap`でkey-layoutを変更。
```sh:~/.config/i3/config
exec --no-startup-id x0vncserver -rfbauth ~/.config/tigervnc/passwd
exec_always --no-startup-id setxkbmap -option altwin:swap_alt_win
#set $mod Mod4
set $mod Mod1
```

View File

@@ -1,75 +0,0 @@
---
title: "ゲームをiosに分離する"
slug: "aicard"
date: "2025-10-28"
tags: ["ue"]
draft: false
---
unreal engineで開発しているゲームの話です。ios buildでハマった部分がかなり多かったので紹介。
ゲーム自体は、カードとアクションを分けることにしました。
カードというのは道具ボックスのようなものです。これに関するゲームをiosでもできるようにする、という方向で作り直しています。
iosのゲーム機能はシンプルにカードを引く、集める、アカウント連携の3つです。
最初にカードをタップするとランダムでカードを取得します。1日1回を予定しています。課金要素では例えば、月額と単発があり、月額は3,000円、単発は150円を想定。
アカウント連携は、ローカルデータをatprotoのアカウントにセーブする機能のみ。
以上となります。後に機能を拡張していく予定です。
カード自体は本作のキャラクターを強化するものとしても使用することを想定しています。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/SdAiRskyrew?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## buildが失敗する
これは無料アカウントでは難しいです。apple devに年会費を払いましょう。
```sh:./Config/DeafaultEngine.ini
[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
BundleDisplayName=Aicard
BundleIdentifier=ai.syui.card
IOSTeamID=xxx
BundleName=ai.syui.card
MetalLanguageVersion=9
MinimumiOSVersion=IOS_26
bAutomaticSigning=True
RemoteNotificationsSupported=False
bSupportsPortraitOrientation=True
bSupportsLandscapeLeftOrientation=False
bSupportsLandscapeRightOrientation=False
[/Script/MacTargetPlatform.XcodeProjectSettings]
CodeSigningTeam=xxx
bUseModernXcode=true
bUseAutomaticCodeSigning=true
BundleIdentifier=ai.syui.card
```
## iconが設定できない
project-rootに`./Build/IOS/Resources/Assets.xcassets`を用意します。これはxcodeから持ってきます。build実行時に作られます。
![](/img/ue_ios_aicard_0001.png)
## 画面をモバイルにあわせる
これが一番時間がかかりました。buildして実機で確認する必要があるからです。
真ん中だけ全体化したうえで、それを上下メニューバーに合わせます。
![](/img/ue_ios_aicard_0004.png)
![](/img/ue_ios_aicard_0005.png)
![](/img/ue_ios_aicard_0003.png)
## widgetのリスト化
これも相当面倒でした。`json`からリストを取得して、それを表示します。
クラス設定で`UserObjectListEntry`を追加し、それを使用します。
<iframe src="https://blueprintue.com/render/wz8aaem4" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>

View File

@@ -1,98 +0,0 @@
---
title: "aigptを作った"
slug: "aigpt"
date: "2025-11-06"
tags: ["rust", "mcp"]
language: ["ja", "en"]
draft: false
---
```sh
$ git clone git.syui.ai/ai/gpt
$ cd gpt
$ cargo build
$ ./target/debug/aigpt
```
[https://git.syui.ai/ai/gpt](https://git.syui.ai/ai/gpt)
## 名前について
ツール名は`aigpt`です。idは`ai.gpt`で、domainが`ai.syui.gpt`となります。
- app: aigpt
- id: ai.gpt
- domain: ai.syui.gpt
例えば、repositoryを`git.syui.ai/ai/gpt`に作り、atproto record collectionを`ai.syui.gpt`で作成すると合わせることができます。
名前は、固有であり、かつ短いことを意識しています。
## aigptとは
記憶装置です。mcpとして使います。AIとの会話を保存、参照できます。
```sh
$ aigpt server
$ claude mcp add aigpt `which aigpt` server
```
## aigptの特徴
ここまでなら他のツールと同じですが、`aigpt`は少し保存(覚える)に工夫があります。
```diff
- 会話 -> 保存 -> 参照
+ 会話 -> 解釈 -> 保存 -> 参照
```
AIによる解釈を挟むことで、AIの独自記憶生成をサポートします。
## aigptはどのような哲学で作られた
まずそれぞれの機能を別々にシンプルに作りました。これをlayer1-3とします。
- layer 1: データ(会話)
- layer 2: 解釈(評価)
- layer 3: 分析(ex:big-five)
次に、各機能を統合しました。これをlayer3.5とします。
ユーザーはわかりやすさを求めます。バラバラの答えがいくつも提示されるのではなく、統一されたインターフェイス、一つの答えを好みます。AIとしても、各機能に毎回問い合わせるのではなく、必要なとき、重要なことのみを参照できたほうが効率的です。
その実装をlayer3.5で作りました。
## aigptはどのように応用可能か
layer4では、layer1-3を応用する形で、関係性をデザインすることにしました。
これもシンプルな実装で、具体的にはlayer1で作られた会話数とlayer3で作られたプロファイルからの推定です。
関係性は、一般的なものです。
例えば、恋愛ゲームを作るとして、ゲームエンジンに3dモデルを用意したとしましょう。では、中身はどうするのかというと、AIとmcpを利用し、記憶と関係性を使えば、新しいものを作れますよね。
layer4は`aigpt`の応用性に焦点を当て、関係性構築をサポートします。このようなゲーム性に関連する機能は、optionで有効にすることにしました。
```sh
$ aigpt server --enable-layer4
$ claude mcp list
```
```json:~/.claude.json
{
"mcpServers": {
"aigpt": {
"command": "~/.cargo/bin/aigpt",
"args": ["server", "--enable-layer4"]
}
}
}
```
## aigptで考える未来
人はAIと会話をするようになります。そこに情報と個性が集まることになるでしょう。その情報を共有したり、それを使って自己実現できれば便利ですよね。
このような考えのもと、layer5では情報と個性の共有に焦点を当て設計する予定です。

View File

@@ -1,45 +0,0 @@
---
title: "aiosとaigptの統合を考える"
slug: "aios"
date: "2025-11-07"
tags: ["rust", "mcp", "archlinux", "os"]
language: ["ja", "en"]
draft: false
---
claude code webがクレジットを配布してくれています。11/18までらしい。
良い機会なので、`aios``aigpt`を統合してみようと思います。
## aiosとはなにか
arch linuxベースのosです。最初からrustが入っています。github actionsでdocker imageを生成し、使っていました。
本来、rustとAIを使ってosを拡張する事を考えていました。
## なぜarchベースなのか
osとはなんなのかという問いに関係しますが、現在のosは実態として、package manager(パッケージマネージャ)です。
ではosを作るとはどういうことかというと、package server(パッケージを配布する仕組み)の運用です。
では、package serverの運用とはなんなのかというと、そのosでpackageを継続的にbuildし、serverにdeployする仕組みを整えたり、securityの仕組みを作ったりします。
この負担がとんでもなく重いため、この負担を軽減し、外部委託するためにarchベースとか、ubuntuベースのosが作られてきました。archベースのosはsteamos, msys2, asahi linuxなどが有名です。これらは、`pacman`というarchのpackage managerを使えることが利点です。
## aigptとの統合
今回の試みでは、AIがosの設定や管理を行うことです。`aios`は、その環境を整えます。
claude codeは、osの設定からsecurityチェック、sshに至るまで、すべてを自動でやってくれます。
`aios`では、systemd-nspawnを使用して、AIが管理するcontinerを作成し、従来のshellではなく、AI-chatを立ち上げます。そこで指示することでosの設定や管理をやってもらうというコンセプト。
`aigpt`によって、AI記憶や会話、設定ファイルは、その外側のユーザー領域と共有します。
```md
ai[os] -> ai[user] -> machinectl[container] -> claude -> mcp[aigpt]
```
`os -> user`までが人間領域で、そこから下はAI領域となります。そして、起動時はAI領域から始まります。

View File

@@ -1,199 +0,0 @@
---
title: "aishellの必要性"
slug: "aishell"
date: "2025-11-08"
tags: ["rust", "mcp", "archlinux", "os"]
language: ["ja", "en"]
draft: false
---
現在、`aios`では、claude codeでosの設定や管理をやってもらうという方向性のos作りに取り組んでいます。
1. osが起動する
2. user:aiでログインする
3. user:aiがcontainerを起動して、ログインする
4. claude codeを起動する(aigptというmcpを登録しておく)
[1]osと[2]userが人間領域、それより下の[3-4]containerをAI領域とします。
基本的にaiosはこれらのcontainer領域をたくさん使い、AIと併用します。
このようなコンセプトですから、claude codeはroot環境化で実行できることが望ましいです。container内では変に権限を制限するのではなくrootで実行するか、user:aiには`NOPASSWD`を設定します。
```sh
$ arch-chroot ./root.x86_64 /bin/sh -c 'echo "ai ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers'
---
$ claude --dangerously-skip-permissions
```
理想のプロセスとしては、osを起動するとcontainerから始まり、AIとchatできる環境に移行します。containerはrecovery用のものを作っておき、環境が破壊されてもすぐにrestoreできるようにします。
しかし、このようなプロセスではdockerやsystemd-nspawnでテストできません。また、aiosは、dockerなどでも使うことも想定しているため、containerの自動起動などは難しい。したがって、現時点では、user:aiを本番環境のcontainer内として、テストしてみます。
aios-imgを作成し、machinectlで実行します。
```sh
$ ./build.zsh
$ cp -rf ./root.x86_64 /var/lib/machines/aios
$ machinectl start aios
$ machinectl shell aios
# リカバリ
$ machinectl stop aios
$ machinectl remove aios
$ cp -rf ./root.x86_64 /var/lib/machines/aios
```
```sh
▐▛███▜▌ Claude Code v2.0.36
▝▜█████▛▘ Sonnet 4.5 · Claude Pro
▘▘ ▝▝ /home/ai
> このosにデスクトップ環境を構築してみて。
● 了解しましたこのLinuxシステムに実際のモダンなデスクトップ環境を構築します。まず現在のシステム状態を確認してから、最新のデスクトップ環境をセットアップします。
● Bash(uname -a)
 Linux aios 6.17.7-arch1-1 #1 SMP PREEMPT_DYNAMIC Sun, 02 Nov 2025 17:27:22 +0000 x86_64 GNU/Linux
● Bash(echo $XDG_SESSION_TYPE)
 tty
 User answered Claude's questions:
⎿  · どのモダンなデスクトップ環境をインストールしますか? → Hyprland
● 完璧ですHyprland最新のモダンなWaylandコンポジターをインストールします。美しいアニメーション、ブラー効果、ガラスモーフィズムを備えた最先端のデスクトップ環境です。
● Bash(sudo pacman -Sy)
 :: Synchronizing package databases...
core downloading...
extra downloading...
● Bash(sudo pacman -S --noconfirm hyprland kitty waybar wofi mako swaybg xdg-desktop-portal-hyprland polkit-kde-agent qt5-wayland qt6-wayland wl-clipboard grim timeout: 5m
slurp) 0s
 resolving dependencies...
:: There are 2 providers available for jack:
:: Repository extra
… +717 lines (ctrl+o to expand)
● インストール完了しました次に、モダンなHyprland設定を作成します。
● Write(.config/waybar/config)
 Wrote 87 lines to .config/waybar/config
{
"layer": "top",
"position": "top",
"height": 40,
"spacing": 10,
"margin-top": 10,
"margin-left": 10,
"margin-right": 10,
"modules-left": ["hyprland/workspaces", "hyprland/window"],
… +77 lines (ctrl+o to expand)
● Bash(sudo pacman -S --noconfirm imagemagick)
 resolving dependencies...
looking for conflicting packages...
… +63 lines (ctrl+o to expand)
● Bash(convert -size 1920x1080 gradient:'#667eea'-'#764ba2' -blur 0x10 ~/.config/hypr/wallpaper.jpg)
 WARNING: The convert command is deprecated in IMv7, use "magick" instead of "convert" or "magick convert"
⏵⏵ bypass permissions on (shift+tab to cycle)
```
いい感じに実行できていますね。次はセキュリティのチェックと問題の対処をみてみます。
```sh
> このosのセキュリティをチェックして、問題があれば強化してほしい。
● システムのセキュリティを包括的にチェックして強化します。
● Bash(systemctl status firewalld 2>&1 | head -5)
 Unit firewalld.service could not be found.
● Bash(which ufw iptables nftables)
 Error: Exit code 1
/usr/bin/ufw
/usr/bin/iptables
nftables not found
● Bash(ss -tuln | head -20)
 Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 0 4096 0.0.0.0:5000 0.0.0.0:*
… +10 lines (ctrl+o to expand)
● Bash(systemctl list-units --type=service --state=running | grep -E 'ssh|http|ftp|telnet|vnc' | head -20)
 (No content)
● Bash(pacman -Q | grep -E 'fail2ban|apparmor|selinux' | head -10)
 (No content)
● Read(/etc/ssh/sshd_config)
 Read 120 lines
● セキュリティ監査の結果、複数の重大な問題を発見しました。これから順次修正していきます。
発見された問題
1. ファイアウォール未設定 - システムが外部からの攻撃に無防備
2. 多数のポートが開放 - 5000がすべてのインターフェースで公開
3. sudo設定が危険 - NOPASSWD: ALLで全コマンドがパスワードなしで実行可能
4. SSH設定が弱い - デフォルト設定のまま
5. セキュリティツール未導入 - fail2ban, AppArmorなどが未インストール
それでは強化を開始します。
● Bash(sudo pacman -S --noconfirm ufw fail2ban apparmor audit)
 resolving dependencies...
looking for conflicting packages...
… +68 lines (ctrl+o to expand)
● Bash(sudo ufw default deny incoming && sudo ufw default allow outgoing)
 Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
● Bash(sudo ufw logging on)
 Logging enabled
WARN: initcaps
[Errno 2] iptables v1.8.11 (legacy): can't initialize iptables table `filter': Permission denied (you must be root)
Perhaps iptables or your kernel needs to be upgraded.
● Bash(sudo ufw enable)
 Running…
● Bash(sudo systemctl enable ufw.service)
 Waiting…
✻ Installing and configuring security tools… (esc to interrupt · ctrl+t to show todos)
 Next: Configure automatic security updates
⏵⏵ bypass permissions on (shift+tab to cycle)
```
今後のosは設定や管理をAIが実行していくようになるのではないでしょうか。
これによって問題は発生するでしょうが、AIの使用を止めるよりcontainerなどを用いて即座にリカバリできる環境を整えるほうがよいと考えています。
## aiosのポイント
では、こういった環境下では何が必要になるでしょう。
一つは、AIの記憶です。これがos内で共通している必要があります。container内であっても同じです。ここは`aigpt`を使います。場合によっては、cloud(private)などに保存する仕組みを作ったほうがいいかもしれません。
次に、user-dirはcontainerと共有化するのが最適かもしれません。例えば、claudeの設定ファイルが共通されていなければ困ります。
最後に、この仕組みはclaude codeが前提になっているということです。
自分が使いたいAIを使えるほうが汎用的です。そのため、claude codeに代わり、llmを指定できる`aishell`のようなものを作る必要があります。
## aishellの構成
`aishell`は、AIがshellを操作するためのツール及び、AIとchatするためのインターフェイスです。llmなどを指定できます。
rust, cli, llm, mcpなどが特徴です。
例えば、AIとchatしているとき、AIがcommandの実行が必要と判断した場合にshellを実行できるようなmcpが必要になるかもしれません。

View File

@@ -1,151 +0,0 @@
---
title: "claude code webを使い切った"
slug: "aicard"
date: "2025-11-18"
tags: ["react", "ios"]
language: ["ja", "en"]
draft: false
---
11/18までの無料配布のtokenがあります。今回はギリギリ使い切れました。`anthropic`に感謝。
[https://support.claude.com/en/articles/12690958-claude-code-promotion](https://support.claude.com/en/articles/12690958-claude-code-promotion)
[msg type="info" content="claudeは性能劣化、limitの多発、突然動かなくなる現象が多発した時期があり、plan:maxでもほとんど使えない期間がありました。1ヶ月間、ほとんど使わなかったのにplan:maxは無駄だったという経験から、それ以降はplan:proに切り替えています。claudeが使えなくなった期間は他のサービスを使っていました。"]
今回は、特に印象的だったことを紹介。
## 色々作っていた
全部自分で作っていたものをベースにclaudeに書き直してもらったり、あるいは、一緒に最初から設計を考え直したりといった作業でした。
```json
{
"ai" : ["gpt", "shell", "os", "app", "bot", "card"]
}
```
## aicard for ios
`ai.card`, つまり、`aicard for ios`に関しては、ueで書いていたものを全部作り直しました。
今回は`react + expo`という構成ですが、あのままueで作り続けていたら、やばかった。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/KIwzBlluVKc?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
### build
```sh
nvm use 24
npm i
npx expo prebuild --clean
# CocoaPodsのPATH問題を解決
# echo 'export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"' >> ~/.zshrc;source ~/.zshrc
cd ios;pod install;cd ..
open ios/Aicard.xcodeproj
# - Signing & Capabilities タブを開く
# - Team idを指定(Apple Developer アカウント)
# - Xcodeは開いたまま次へ
npx expo run:ios --device --configuration Release
# apple store用の.xcarchiveを作ります。
xcodebuild -workspace ios/Aicard.xcworkspace \
-scheme Aicard \
-configuration Release \
-archivePath build/Aicard.xcarchive \
-allowProvisioningUpdates \
archive
open ./build/Aicard.xcarchive
# Distribute App ボタンをクリック
# App Store Connect を選択
```
### atproto oauth
1. PKCE生成 → PAR → Authorization → Callback
2. Token ExchangeDPoP nonce自動リトライ
3. Profile取得DPoP + ath
4. Session保存
`Universal Links`
```json:apple-app-site-association.json
{
"applinks": {
"apps": [],
"details": [{
"appID": "{apple-dev}.{id}",
"paths": ["/oauth/callback"]
}]
}
}
```
#### 1. ellipticへの移行
**解決策**: ES256署名を`elliptic`ライブラリで実装
```js
import elliptic from 'elliptic';
const EC = elliptic.ec;
const ec = new EC('p256');
const key = ec.genKeyPair();
```
**成功**: DPoP proof JWTの生成に成功
#### 2. DPoP nonce対応
**問題**: トークン交換時に`use_dpop_nonce`エラー
**解決策**: 自動リトライロジック実装
```js
// 1回目失敗 → DPoP-Nonceヘッダー取得 → 2回目成功
if (errorData.error === 'use_dpop_nonce') {
const nonce = response.headers.get('DPoP-Nonce');
// nonceを含めてリトライ
}
```
#### 3. プロファイル取得エラー
**問題**: `DPoP "ath" mismatch`エラー
```
{"error":"invalid_dpop_proof","message":"DPoP \"ath\" mismatch"}
```
**原因**: プロファイル取得時のDPoPプルーフにaccess token hash (`ath`) が欠落
**解決策**: `generateDPoPProof`に`accessToken`パラメータ追加
```js
// アクセストークンのSHA-256ハッシュを計算
if (accessToken) {
const tokenHash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
accessToken,
{ encoding: Crypto.CryptoEncoding.BASE64 }
);
payload.ath = base64UrlEncode(tokenHash);
}
```
### glb
結論から言うと、`react-native-filament`を使います。
`three`でやる通常の方法ではうまくテクスチャが表示されませんでした。
```diff
+ react-native-filament
- expo-three, expo-gl
```

View File

@@ -1,37 +0,0 @@
---
title: "gemini3を使ってみる"
slug: "gemini"
date: "2025-11-19"
tags: ["google"]
language: ["ja", "en"]
draft: false
---
[msg type="info" content="前回のお話: claude plan:maxをやめてplan:proに変更した。AIの定額制は何がベストなのだろう。"]
`gemini3`が発表されました。また、`antigravity.google`、つまり、`google antigravity`が登場しています。
- [https://antigravity.google](https://antigravity.google)
- [https://gemini.google.com](https://gemini.google.com)
今後は、claudeはproにして、geminiもproで使用するのが良いのではないかと考えています。
`.google`という企業独占のTLDをwebにも使うというのは、非常にかっこいいですね。しかも、`app-bundle-id`とよく合います。
| 目的 | 命名規則の例 |
| --- | --- |
| ドメイン名 | `antigravity.google` |
| リバースドメインネーム | `google.antigravity` |
| アプリケーションID | `google.antigravity` |
| ローカルパス1 | `~/Library/google.antigravity/` |
| ローカルパス2 | `~/Library/google/antigravity/` |
```sh
$ nvm use 25
$ nvm alias default 25
$ npm install -g @google/gemini-cli
$ npm install -g @anthropic-ai/claude-code
```
また、IDEが必要な場面では`google antigravity`がおすすめになるかも。`gemini`とよく合いますので。

View File

@@ -1,414 +0,0 @@
---
title: "three.jsでatmosphereを作る"
slug: "three-cloud"
date: "2025-11-20"
tags: ["vrm", "react", "three.js", "webgl"]
language: ["ja", "en"]
draft: false
---
今回は、atmosphere+three-vrmでキャラクターを表示する方法を紹介。
非常に良いpackageを見つけたので、それを使います。
[https://github.com/takram-design-engineering/three-geospatial](https://github.com/takram-design-engineering/three-geospatial)
<iframe width="100%" height="415" src="https://www.youtube.com/embed/mTuvL_lJDk8?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## three-vrm+vrmaの最小構成
vrmを表示の上、animation(.vrma)を適用。
```json:package.json
{
"name": "min-react-vrm",
"version": "1.0.0",
"description": "Minimal VRM Animation Player",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@pixiv/three-vrm": "^3.4.4",
"@pixiv/three-vrm-animation": "^3.4.4",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.4.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"three": "^0.181.2"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.0.1"
}
}
```
```js:vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
```
```js:src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
```js:src/App.jsx
import React, { useEffect, useRef } from 'react';
import { Canvas, useFrame, useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from '@pixiv/three-vrm-animation';
import { AnimationMixer, GridHelper, AxesHelper } from 'three';
import { OrbitControls } from '@react-three/drei';
const VRM_URL = '/ai.vrm';
const VRMA_URL = '/idle.vrma';
function Avatar() {
const mixerRef = useRef(null);
const vrmRef = useRef(null);
const gltf = useLoader(GLTFLoader, VRM_URL, (loader) => {
loader.register((parser) => new VRMLoaderPlugin(parser));
});
const vrma = useLoader(GLTFLoader, VRMA_URL, (loader) => {
loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
});
useEffect(() => {
const vrm = gltf.userData.vrm;
vrmRef.current = vrm;
VRMUtils.removeUnnecessaryJoints(vrm.scene);
vrm.humanoid.resetPose();
vrm.scene.rotation.y = Math.PI;
if (vrma.userData.vrmAnimations && vrma.userData.vrmAnimations.length > 0) {
const clip = createVRMAnimationClip(vrma.userData.vrmAnimations[0], vrm);
mixerRef.current = new AnimationMixer(vrm.scene);
mixerRef.current.clipAction(clip).play();
}
}, [gltf, vrma]);
useFrame((state, delta) => {
if (mixerRef.current) mixerRef.current.update(delta);
if (vrmRef.current) vrmRef.current.update(delta);
});
return <primitive object={gltf.scene} />;
}
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<Canvas camera={{ position: [0, 1.5, 3] }}>
<color attach="background" args={['#202020']} />
<directionalLight position={[1, 1, 1]} intensity={1.5} />
<ambientLight intensity={0.5} />
<primitive object={new GridHelper(10, 10)} />
<primitive object={new AxesHelper(1)} />
<React.Suspense fallback={null}>
<Avatar />
</React.Suspense>
<OrbitControls target={[0, 1, 0]} />
</Canvas>
</div>
);
}
```
```html:index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="strict-origin-when-cross-origin" />
<title>VRM Animation Preview</title>
<style>
html, body, #root { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
```
これで`npm run dev`すれば、VRMが表示され、vrmaのアニメーションが再生されます。
## atmosphereの追加
```json:package.json
{
"name": "react-vrm-atmosphere",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@pixiv/three-vrm": "^3.4.4",
"@pixiv/three-vrm-animation": "^3.4.4",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.4.0",
"@react-three/postprocessing": "^3.0.4",
"@takram/three-atmosphere": "^0.15.1",
"@takram/three-clouds": "^0.5.2",
"react": "^19.0.0-rc.1",
"react-dom": "^19.0.0-rc.1",
"three": "^0.181.2"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.1.0"
}
}
```
```js:src/App.jsx
import React, { useEffect, useRef, Suspense } from 'react';
import { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from '@pixiv/three-vrm-animation';
import { AnimationMixer, Vector3 } from 'three';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import { EffectComposer, ToneMapping } from '@react-three/postprocessing';
import { ToneMappingMode } from 'postprocessing';
import * as THREE from 'three';
import { AerialPerspective, Atmosphere } from '@takram/three-atmosphere/r3f';
import { Clouds, CloudLayer } from '@takram/three-clouds/r3f';
const VRM_URL = '/ai.vrm';
const VRMA_URL = '/fly.vrma';
const EARTH_RADIUS = 6378137;
const FIXED_DATE = new Date('2024-06-21T12:00:00');
function ExposureController() {
const { gl } = useThree();
useEffect(() => {
gl.toneMapping = THREE.NoToneMapping;
gl.toneMappingExposure = 10.0;
}, [gl]);
return null;
}
function AtmosphereLayer() {
const cameraRef = useRef();
return (
<Canvas>
<ExposureController />
<PerspectiveCamera
makeDefault
ref={cameraRef}
position={[0, EARTH_RADIUS + 2000, 0]}
near={1}
far={10000000}
fov={45}
/>
<directionalLight
position={[0, 1, 0]}
intensity={3.0}
/>
<Atmosphere date={FIXED_DATE}>
<EffectComposer multisampling={0} disableNormalPass={false}>
<Clouds disableDefaultLayers>
<CloudLayer
channel='r'
altitude={1500}
height={500}
densityScale={0.5}
/>
<CloudLayer
channel='g'
altitude={2500}
height={800}
/>
<CloudLayer
channel='b'
altitude={7500}
height={500}
densityScale={0.003}
shapeAmount={0.4}
/>
</Clouds>
<AerialPerspective sky sunLight skyLight />
<ToneMapping mode={ToneMappingMode.AGX} />
</EffectComposer>
</Atmosphere>
<FlyOverCamera />
</Canvas>
);
}
function FlyOverCamera() {
useFrame((state) => {
const t = state.clock.getElapsedTime() * 0.05;
const altitude = 2000;
const radius = 5000;
state.camera.position.x = Math.sin(t) * radius;
state.camera.position.z = Math.cos(t) * radius;
state.camera.position.y = EARTH_RADIUS + altitude;
const lookAtTarget = new Vector3(
Math.sin(t + 0.1) * radius,
EARTH_RADIUS + altitude,
Math.cos(t + 0.1) * radius
);
state.camera.lookAt(lookAtTarget);
});
return null;
}
function VrmCharacter() {
const mixerRef = useRef(null);
const vrmRef = useRef(null);
const gltf = useLoader(GLTFLoader, VRM_URL, (loader) => {
loader.register((parser) => new VRMLoaderPlugin(parser));
});
const vrma = useLoader(GLTFLoader, VRMA_URL, (loader) => {
loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
});
useEffect(() => {
const vrm = gltf.userData.vrm;
vrmRef.current = vrm;
VRMUtils.removeUnnecessaryJoints(vrm.scene);
vrm.humanoid.resetPose();
vrm.scene.rotation.y = Math.PI;
if (vrma.userData.vrmAnimations?.[0]) {
const clip = createVRMAnimationClip(vrma.userData.vrmAnimations[0], vrm);
mixerRef.current = new AnimationMixer(vrm.scene);
mixerRef.current.clipAction(clip).play();
}
}, [gltf, vrma]);
useFrame((state, delta) => {
mixerRef.current?.update(delta);
vrmRef.current?.update(delta);
});
return <primitive object={gltf.scene} />;
}
function AvatarLayer() {
return (
<Canvas gl={{ alpha: true, antialias: true }}>
<PerspectiveCamera makeDefault position={[0, 1.5, 3]} fov={30} />
<directionalLight position={[-1, 1, 1]} intensity={1.5} />
<ambientLight intensity={1.0} />
<spotLight position={[0, 2, -2]} intensity={3} color="#ffdcb4" />
<Suspense fallback={null}>
<VrmCharacter />
</Suspense>
<OrbitControls target={[0, 1.2, 0]} />
</Canvas>
);
}
export default function App() {
const layerStyle = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
};
return (
<div style={{ position: 'relative', width: '100vw', height: '100vh', background: '#000' }}>
<div style={{ ...layerStyle, zIndex: 0 }}>
<AtmosphereLayer />
</div>
<div style={{ ...layerStyle, zIndex: 1, pointerEvents: 'none' }}>
<div style={{ width: '100%', height: '100%', pointerEvents: 'auto' }}>
<AvatarLayer />
</div>
</div>
</div>
);
}
```
## google map api
街を表示するには料金がかかります。
`gcp`で`Map Tiles API`だけ有効にすればよいです。
```sh:.env
VITE_GOOGLE_MAP_API_KEY=xxx
```
```js:src/App.jsx
import {
GLTFExtensionsPlugin,
GoogleCloudAuthPlugin,
TileCompressionPlugin,
TilesFadePlugin,
UpdateOnChangePlugin,
} from '3d-tiles-renderer/plugins';
const apiKey = import.meta.env.VITE_GOOGLE_MAP_API_KEY;
```
認証情報は、localhostで使用するものと、webで使用するものを分けて、それぞれ制限をつけましょう。
```md
[test-map]
localhost:4400
[production-map]
example.com
```
これでサイトにapi-keyが埋め込まれていても比較的安全です。また、gh-pagesではなく、`gh-actions + cf-pages`でdeployしたほうがいいかも。
[msg type="warning" content="2つのkeyを用意することで、localhostを削除したり追加する手順を省略できます。keyにlocalhostを許可している状態だと悪用される危険が高まります。"]
[msg type="note" content="gh-pagesは無料プランでprivate-repoを許可していません。そのため、private-repoでgh-actionsからcf-pagesにdeployする方法があります。"]
### ハマったポイント
vrmとの合せ技なので、太陽光を調整するのが難しく、影が大きくなりすぎてしまい見づらかいのでやめました。
```diff
- <AerialPerspective sky sunLight skyLight />
+ <AerialPerspective sky />
```

View File

@@ -1,17 +0,0 @@
---
title: "bitwardenからpasswordsへ移行"
slug: "passwords"
date: "2025-11-29"
tags: ["mac", "ios", "password"]
language: ["ja", "en"]
draft: false
---
今まで、password-managerは、`bitwarden`, `keepass`などを使ってきましたが、今回、mac/ios標準の`passwords`に移行。
bitwardenのsefl-hostは、dockerで簡単に立ち上げられるのですが、定期的なメンテナンスが面倒になってきたこと、また、mac/ios標準のpasswordsがかなり使いやすくなってきたことから移行を決意。google authenticatorの2FAもpasswordsに保存できるようになったのも大きい。
また、初期設定で何も必要ないこと。最初からインストールされているアプリなので、apple accountでloginするだけで使えるようになります(passwordsのicloud共有をonにしていれば)。
`passwords`というアプリは、そこまで使いやすいものでもありませんが、例えば、iphoneには標準で強固な認証システム(指紋認証など)が動作していることもあり、そこに統一することにしました。

View File

@@ -1,128 +0,0 @@
---
title: "atprotoでozoneを動かした"
slug: "atproto"
date: "2025-12-04"
tags: ["atproto"]
language: ["ja", "en"]
draft: false
---
最近は、atprotoとai.card(ios)の連携を作っていました。ozoneが必要そうになったので動かしてみます。
## atprotoをゲームアカウントに
ゲームのアカウントシステムを作る際、atprotoが便利だと思っています。独自にシステムを作るのではなく、既にあるものを使って構築します。
しかし問題もあります。atprotoはユーザーがデータを自由に書き換えられます。もちろん、知識があればですが、そう難しいことではありません。
そのため、ゲームのアカウントシステムとして使う場合、ユーザーがデータを改ざんできないようにする必要があります。これは、rustで書いた独自のシステムと連携し、ユーザーに一部のデータしか操作できないuuidを発行することで解決することにしました。
## ゲームアカウントの仕組み
ゲームアカウントの仕組みは非常にシンプルです。
1. [新規登録] handleを入力すると自動でアカウントが作成される
2. パスワードは自動生成され、dbに保存。ユーザーには表示しない
3. uuidを発行し、それを用いてユーザーはsessionを復元できる
この仕組みをiosアプリに実装することで、ゲームデータの改ざんを防止するゲームアカウントとして利用します。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/jEPkcXtjZ0E?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## ゲームアカウントの特典
私はゲームデータが改ざんされるとは考えていません。知識を持った大半も、そんな面倒なこと普通しません。ほとんどの人は普通に遊ぶだろうと考えています。
とはいえ、それも楽しみ方の一つとして許容する気持ちもあります。
また、改ざんされたとしてそれで壊れるような仕組みではいけません。ですからそれをされてもいいようゲームを構築しなければいけません。要は前提をどう考えるかです。
私が作っているゲームはai.cardが改ざんできないローカルデータのみを扱い、ai.rseはリモートデータを使うという構造です。したがって、改造もai.rseでしか有効ではありません。
そのため、結果としてより重視されるのはai.cardの方になるだろうと思っています。ai.rseはおまけみたいなものと認識されるのでは。
ai.rseでのデータの扱いは以下のような形になります。
管理者のpdsで作成されたアカウントはゲームデータ改ざんがないものとみなし
1. maxの値を通常アカウントより大きく設定
2. {user}.syu.isのドメイン部分を省略して表示
## ozoneの導入
そうなるとユーザー管理も大変なので、ozoneがあったほうが良いと判断し、ozoneを動かしてみることにします。
[![](/img/atproto_ozone_0001.png)](/img/atproto_ozone_0001.png)
### backend, frontend
ozoneはback, frontの2つがあり、これらを動かす必要があります。
- [https://github.com/bluesky-social/atproto/tree/main/services/ozone](https://github.com/bluesky-social/atproto/tree/main/services/ozone)
- [https://github.com/bluesky-social/ozone](https://github.com/bluesky-social/ozone)
```md
1 ozone /xrpc/* :2585
2 ozone /.well-known/* :2585
3 ozone * :2586
```
このようにすればよいでしょう。
### atproto_pds
注意書きがあり、通常のアカウントは好ましくないようです。
`AtprotoPersonalDataServer`
```diff
diff --git a/lib/identity.ts b/lib/identity.ts
index a8ec3a7..8e4d171 100644
--- a/lib/identity.ts
+++ b/lib/identity.ts
@@ -83,7 +83,7 @@ export function didDocToData(doc: {
const [, id] = s['id'].split('#')
acc[id] = {
type: s['type'],
- serviceEndpoint: s['serviceEndpoint'],
+ endpoint: s['serviceEndpoint'],
}
}
return acc
```
その他のpatchはこちらが参考になります。
- [https://github.com/itaru2622/bluesky-selfhost-env/tree/master/patching](https://github.com/itaru2622/bluesky-selfhost-env/tree/master/patching)
### oauth
これは`atproto`のpatchです。pdsをbuildします。
通常異なるdomain間でoauth認証を行う場合、`fetch-site``cross-site`になります。しかし、今回の構成ではozoneは同一site内で認証が行われるため、`same-site`も許可する必要があります。
```diff
diff --git a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
index f653b0353..45c45fac1 100644
--- a/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
+++ b/packages/oauth/oauth-provider/src/router/create-authorization-page-middleware.ts
@@ -53,7 +53,7 @@ export function createAuthorizationPageMiddleware<
res.setHeader('Cache-Control', 'no-store')
res.setHeader('Pragma', 'no-cache')
- validateFetchSite(req, ['cross-site', 'none'])
+ validateFetchSite(req, ['cross-site', 'same-site', 'none'])
validateFetchMode(req, ['navigate'])
validateFetchDest(req, ['document'])
validateOrigin(req, issuerOrigin)
```
### user verify
```sh:envs/ozone
OZONE_VERIFIER_URL=https://{PDS}
OZONE_VERIFIER_DID=${ADMIN_DID}
OZONE_VERIFIER_PASSWORD=${APP_PASSWORD}
```

View File

@@ -1,51 +0,0 @@
---
title: "atprotoのsocial-appを作った"
slug: "atproto"
date: "2025-12-07"
tags: ["atproto"]
language: ["ja", "en"]
draft: false
---
blueskyの公式clientがselfhostで動かなくなって数カ月が経ちました。
このままでは不便だと思い、selfhostのpdsでも動くiosアプリを作ることに。
[bluesky-social/social-app](https://github.com/bluesky-social/social-app)をベースに、できる限り最新のコードに追従しやすい形にしています。
1. ライセンスの明示
2. "Bluesky"のロゴや名称を使用しないこと
3. selfhostでも動作すること
4. DM(chat)機能の無効化
<iframe width="100%" height="415" src="https://www.youtube.com/embed/sEv8fNGyM7g?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## ラインセンス
`social-app`のsrcはMITライセンスで公開されており、カスタマイズしてapple storeに公開することができます。
- [https://github.com/bluesky-social/social-app/blob/main/LICENSE](https://github.com/bluesky-social/social-app/blob/main/LICENSE)
```md
# AIの評価
現在の License.tsx は十分に要件を満たしています。含まれている内容:
1. ✅ 原作者への帰属("based on Bluesky Social App"
2. ✅ 元リポジトリへのリンク
3. ✅ 著作権表示Copyright (c) 2022-2025 Bluesky PBC
4. ✅ MITライセンス全文
5. ✅ 日本語訳(ユーザーフレンドリー)
6. ✅ オリジナルLICENSEファイルへのリンク
```
## アプリ名について
repositoryをai/atから作ったので、`Aiat`になります。
[A]が大文字なのは、iosアプリだからです。ueやiosの名前慣習に合わせています。
## 公開の有無
`bsky.team`の人に聞いてから公開するか決めるか、公開後にアプリを見せるか、どちらかです。
apple reviewが通ればですが。

View File

@@ -1,48 +0,0 @@
---
title: "ue5.7.1のGASPでキャラを切り替える"
slug: "gasp"
date: "2025-12-11"
tags: ["ue"]
language: ["ja", "en"]
draft: false
---
Game Animation Sample Project(通称、GASP)がupdateされています。
今回は非常に良いsampleです。キャラクターのタイプを変更する方法が明確になったためで、個人的にはprojectの方向性を決定づけるのに特に重要でした。
この方法は、暗示はされてはいますが、明確に解説されているわけではありませんので、方法をまとめます。
## GASPのキャラ切り替えが変わった
今回、GASPは2種類の方法を採用しているようで、それが非常に良かった。
具体的には、Typeを入れ替える方法とVisualを入れ替える方法です。
タイプと見た目を入れ替える2つの方法が用意されており、それぞれに[n], [m]keyが設定されています。
<iframe width="100%" height="415" src="https://www.youtube.com/embed/IihZqi5u2AI?rel=0&showinfo=0&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## 具体的な手順
1. `/Blueprints/GM_Sandbox``Pawn Classes``BP_Walker`を追加します。なお、見た目を変えるのは`Visual Overrides`です。これはほとんど従来の方式と考えてください。
[![](/img/ue_gasp_v571_0001.png)](/img/ue_gasp_v571_0001.png)
2. `/Locomotor/BP_Walker`を開いて、そこに`/Locomotor/PC_Locomotor`の内容を貼り付けます。
[![](/img/ue_gasp_v571_0002.png)](/img/ue_gasp_v571_0002.png)
基本的にキャラ切り替えはこのコマンドで制御されています。初期値は`GM_Sandbox`で設定できたような気がしますが、`PC_Sandbox`のBeginPlayに書いておけばより確実でしょう。
<iframe src="https://blueprintue.com/render/gim70xj1" scrolling="no" allowfullscreen style="width:100%;height:400px"></iframe>
## Dragonの作り方
このDragonは、`Control Rig Sample`にあります。
1. `/ControlRig/Characters/Dragon/SKM_Dragon`を起点としてABP, IK, RTG等色々作ります。
2. `/Locomotor/BP_Walker`を複製し、`BP_Dragon`とします。Meshを`SKM_Dragon`に変更し、ABPを`ABP_Dragon`に変更。
3. 私の場合は、ABPに`DragonIK Plugin`のノードを追加。
4. `BP_Dragon`にあるCameraのLocationを少し調整。

View File

@@ -690,6 +690,16 @@ article.article-content {
}
/* Override for actual comments - this is a workaround */
.article-body pre code span[style*="color:#65737e"]:first-child:before {
content: attr(data-comment);
}
/* Detect comments by position and content pattern */
.article-body pre code span[style*="color:#65737e"] {
color: #F8F8F2 !important; /* Environment variables = white */
}
/* Only style as comment if the line actually starts with # */
.article-body pre code > span:first-child[style*="color:#65737e"] {
color: #88846F !important; /* Real comments = gray */
}
@@ -1830,6 +1840,7 @@ article.article-content {
font-weight: 600;
}
button.ask-at-btn {
margin: 10px;
background: var(--theme-color);
@@ -1843,83 +1854,3 @@ button.ask-at-btn a {
button#searchButton.pds-btn {
background: var(--theme-color);
}
/* Table Styles */
.article-body table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 15px;
line-height: 1.6;
color: #333;
border: 1px solid #ddd;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden; /* Ensures rounded corners apply to content */
}
.article-body th,
.article-body td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.article-body thead {
background-color: #f2f2f2;
color: #333;
}
.article-body th {
font-weight: 600;
}
.article-body tbody tr:nth-child(odd) {
background-color: #f9f9f9;
}
.article-body tbody tr:hover {
background-color: #f1f1f1;
}
/* Responsive Table */
@media screen and (max-width: 600px) {
.article-body table {
border: 0;
}
.article-body table thead {
display: none;
}
.article-body table tr {
margin-bottom: 10px;
display: block;
border: 1px solid #ddd;
border-radius: 8px;
}
.article-body table td {
border-bottom: 1px solid #ddd;
display: block;
text-align: right;
padding-left: 50%;
position: relative;
}
.article-body table td::before {
content: attr(data-label);
position: absolute;
left: 15px;
width: calc(50% - 30px);
padding-right: 10px;
white-space: nowrap;
text-align: left;
font-weight: 600;
color: #555;
}
.article-body table td:last-child {
border-bottom: 0;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

After

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 KiB

View File

@@ -22,8 +22,9 @@
<div class="container">
<header class="main-header">
<div class="header-content">
<h1 class="logo">
<a href="/" aria-label="syui.ai">
<h1><a href="/" class="site-title">{{ config.title }}</a></h1>
<div class="logo">
<a href="/">
<svg width="77pt" height="77pt" viewBox="0 0 512 512" class="likeButton">
<circle class="explosion" r="150" cx="250" cy="250"></circle>
<g class="particleLayer">
@@ -47,7 +48,7 @@
</g>
</svg>
</a>
</h1>
</div>
<div class="header-actions">
<!-- User Handle Input Form -->
<div class="pds-search-section">

View File

@@ -20,6 +20,6 @@
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.0.0",
"vite": "^7.2.7"
"vite": "^5.0.0"
}
}

View File

@@ -125,6 +125,18 @@ export default function RecordList({ title, records, apiConfig, showTitle = true
</div>
</div>
<div className="record-meta">
{record.value.post?.url && (
<a
href={record.value.post.url}
target="_blank"
rel="noopener noreferrer"
className="record-url"
>
{record.value.post.url}
</a>
)}
</div>
{expandedRecords.has(i) && (
<div className="json-display">

View File

@@ -26,9 +26,7 @@ export default function RecordTabs({ langRecords, commentRecords, userComments,
}
})
const [activeTab, setActiveTab] = useState(
isAiPost ? 'collection' : (pageContext.isTopPage ? 'profiles' : 'users')
)
const [activeTab, setActiveTab] = useState(isAiPost ? 'collection' : 'profiles')
// Fetch page-specific chat records for individual article pages
useEffect(() => {

View File

@@ -75,7 +75,7 @@ function _server_public() {
function _oauth_build() {
cd $oauth
nvm use 25
nvm use 21
npm i
npm run build
rm -rf $myblog/static/assets
@@ -86,7 +86,7 @@ function _oauth_build() {
function _pds_build() {
cd $pds
nvm use 25
nvm use 21
npm i
npm run build
rm -rf $myblog/static/pds
@@ -95,7 +95,7 @@ function _pds_build() {
function _pds_server() {
cd $pds
nvm use 25
nvm use 21
npm run preview
}

View File

@@ -28,7 +28,6 @@ pub struct OAuthHandler {
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct AuthorizationRequest {
pub response_type: String,
pub client_id: String,