土鍋で雑多煮

UnityでXR・ゲーム開発をしています。学んだことや備忘録、趣味の記録などを書いていきます。

MENU

2025年の振り返りと2026年の抱負

どうも、土鍋です。

年越えちゃいましたが、ようやく修論が落ち着いたので、2025年の振り返りをしました。

2025年振り返り

1月

第一志望内定

第一志望の会社から内定が出ました。

インターンは学部2年から行ってたし、長い学生生活が価値あるものだったと証明された気がして報われた気分になりました。

最終面接が金曜で土日挟んで月曜に内定を頂けたので、土日はめちゃくちゃもやもやしてました。 不安すぎて色々調べてると最終面接終わったその日に合格を貰ったみたいな話も見つけちゃったりで、なんで連絡無いんだーとなってましたね。

その土日はその会社が関わってる映画見に行って、タペストリー買ったりしてました。落ちたらこれ見るたびに思い出しそ〜…だけど絶対受かると信じて買いました。

2月

AndroidXR勉強会

iwakenlab.connpass.com

iwakenlab.connpass.com

当時は実機も出てないのでエミュレーターで試すしかない上に、挙動も不安定でしたが、そんな中、日本でほぼ初めてイベントできたのはでかかったなと思います。 自分も初めてKotlinを触ったりいい機会になりました。

Googleの立野さんも来ていただいて、話せないことも多い中たくさん質問に回答していただけたのは、今考えると超貴重な機会でした。

にじフェス

初めてのリアルイベント

EXゲマズ好きなんですが、ライブ現地は当たらず…

友達は現地当たったので、分かれて僕は横のカフェで悲しみの配信見ておりました…

↓これ、ARすごくないですか!?

3月

イワケンラボ大LT祭の運営代表

イワケンラボには唯一、一年に一回全メンバーが必ず参加してLTするイベントがあり、それの運営代表をしました。LTセッションは4レーンあり、それぞれ司会やタイムキーパーがいます。さらにその全セッションを閲覧できるめちゃバース会場があり、その会場に展示するパネルなどの作成もあります。ポスターやConnpassのヘッダーのデザイン、広報、二次会会場手配などなど細かい仕事すべてを取り仕切るのですが、かなり苦労しましたね…。

イベント自体はみなさんのご協力もあり無事成功したのでよかったです。

iwakenlab.connpass.com

自分はサークル代表としてある程度近い仕事はこなしてましたが、あんまり得意ではないのかなと実感しました。何が苦手だったんだろう。
人に頼るのが苦手なので、いろんな人にタスクを丸投げするのができなかったのが原因かな。

ただ、一定レベルこういうスキルは持っていて重宝すると思うので、今後も小さいイベントなどは企画したり、運営サポートなどはしていきたいです。

パフォーマーフェス

さくたまさんのARライブアプリのUI部分をお手伝い。
僕はパフォーマンスできるものがないとぼやいていたのを覚えていてくださって声をかけてくれました。ありがたい😭

あと当日はチケット受付やったりしてた。ARライブももちろん、Quest3を10数台ライブハウスで装着したり、透過スクリーン自作したりしたイベントなんて唯一だっただろうなあ…

僕もなにかパフォーマンスできるものほしいなあ。VFXGraphとか触るか…

4月

内定者バイト

すでにリリースされて運用している大規模タイトルに関わるのは初めてですべてが新鮮でした。

内容はもちろん書けないですが

5月

万博展示

大阪万博にイワケンラボで出展することができました。(尽力してくれたヘリンさん・イワケンさんありがとうございます) 自分は2024年に開発したScramble Battleを持っていきました。

youtu.be

マルチプレイゲームなのですが、会場のネット環境等の問題で接続がうまくいかないことが頻発し、回転率が悪かったのが反省点ですね…

あと万博は普通に楽しめました!

今思うと5月に行っておいてよかったですね。8月9月は駆け込みラッシュで予約取れないわめっちゃ混んでるわで大変だったと聞くので。

個人的に好きだったのはトルクメニスタンとnullnullですね!

内定者バイト

内容は書けないです。

6月

内定者バイト

内容は(

7月

研究の出張前の追い込み開発と学会の論文執筆に追われてた気がします。

CEDEC

初めてCEDEC行きました。すれ違う方々、みんな名の知れた企業の方でひえ~っとなってた。

北海道出張

共同研究の関連で北海道へ出張。

360°VRシアター見学させていただきました。

やはり海鮮がうまい。

イワケンラボメンバーのA-kunさんに有名なトリトンに連れて行ってもらった

これは夜パフェ

趣味である鉄道旅(もとい廃線旅)で旧手宮線へ。

8月

サイバーエージェント x A-PxL ゲーム座談会

SGEの人と雑談で面白い大学ある?となったので、母校会津大の自分が代表していたサークルを紹介して、イベントを開催しました!

開発合宿 → IwakenLab.展

毎年恒例、イワケンラボ開発合宿。 TOKYO NODE XR HACKATHONに向けて開発していた作品をここで一旦アルファ版作り切りました。(相変わらず徹夜ですけどね…)

今年のテーマは展示。ということでNEUUさんをお借りして展示会を開催。

研究出張で人生初海外へ

学生のうちにやっておきたかった海外へ行くというのを達成できてよかった。

不安していたほどトラブルもなく(研究はより迷走したが)、海外への苦手意識はなくなりました。 買い物は全部クレカで済むし、英語も中学生レベルで十分通用しましたしね。あと困ってたら話しかけてくれる。

ただ、やっぱ住むなら日本がいいなと改めて感じました。

食べ物のバリエーション、街の構造、生活への価値観は大きな違いがあるなと。

9月

研究室合宿でハッカソン主催

例年、研究室合宿は機械学習系アルゴリズムコンテストなのですが、自分が発起人でハッカソンを実施しました。

去年までは自分のようなUnityだけやってる人間には何も動きようがなかったので提案しましたが、案外盛り上がってくれたかな~と勝手に思ってます。

教授陣も評価が難しいという点はありましたが、わりと楽しんでくれたっぽいです。

TOKYO NODE XR HACKATHON

かっつーさん主導で集まったチームでPICSYという貨幣体系をベースとした新たな都市SNS体験的なものを想像して作りました。

結果としては次のステージには行けず。

苗を植えて、いいねのハンドトラッキングで育つ木というのはアイデアとしては良かった気はするけど、やはりビジュアルがほかと比べて弱かったですね。

コンセプトも5分のピッチじゃ伝わりきらない複雑さで、体験してようやく分かる面白さは今回のハッカソンとはマッチしなかったなあ。

バーチャルリアリティ学会大会発表

去年も発表しましたが今年も発表。

相変わらずオフ会でしたね。イワケンラボやUTvのメンバーがそこら中にいました。

10月

修論に向けて研究に集中しましたが、研究が行き詰まったりやり直しになったりで、体調とメンタルがずっと落ちていた気がします。

確実になんか手は動かしてた気はするけど記憶が全然ない。

11月

技術書典出店

イワケンラボ合同本の一章担当しました。ほんとはXRかUnity関連が良かったけど研究にかかりっきりで結局研究の内容抜粋になってしまいました。

26卒就活体験談LT主催

就活を始めるメンバーが多かったのでイワケンラボ内部向けに開催。
就活の話をあんまり聞いたこと無いメンバーから色々おもしろいエピソードが出て楽しかったですね~。

12月

色々活動する気力が復活してきた12月。

XRKaigi、U/dayに一般参加して、VisionOS TCは運営のお手伝いをしました。

プラスしてアドベントカレンダー4記事、unityroomにゲーム公開、もちろん修論、そしてした2つのハッカソン。

12月だけ勢いがすごい。

限界開発合宿

会津大同期が主催してくれた、マジで身内だけで楽しむだけのハッカソン。

社会人になってひりつき足りねーなとなってやることがこれというのが会津大生らしくてホント好き。

↓そのときのブログ

donabenabe.hatenablog.com

そして、大晦日に合宿で作ったものをリリースしました!

unityroom.com

  1. 2つの煩悩からより煩悩だと思う方を選ぶ
  2. 選んだ傾向から煩悩タイプ診断
  3. 自分の煩悩を書く
  4. みんなのジャッジで最強の煩悩が決まる!!

という内容。

しばらくはバックエンド動かすので、ぜひどしどし投稿してほしい!

結婚式withAR

イワケンさん結婚おめでとうございます!!という意味も込めて参加。

XR Kaigiでイワケンラボメンバーのはまちゃんから誘いを受けてチーム結成。
VisionOS TCの運営打ち上げでデザイナーのわたさんを勧誘し、当日Kentyさんを巻き込んでハッカソンに臨みました。

↓作ったもの

物理的なカセットを模したカードに声を吹き込んで、夫婦に送ろうという作品。

この物理的なカードが箔押しや紙の素材までこだわられていてデザイナーさんの発想力に度肝を抜かれました。

FGO 終章

FGOは高校生の時からずっとやっていたのですが、ここ3年くらい離れてました。
ただ、どうやら本当にストーリーが終わってしまうみたいだぞということが8月に発表されたので、放置していたストーリーをガーッとやりましたが、改めてFGOのストーリーの重厚さとキャラクターたちの個性に圧倒されました。

こういうストーリーやキャラクターへの愛溢れるゲーム開発に関わりたいなあ。

↓やっている人なら分かる東京駅

まとめ

2024年はハッカソンやらなんやらに出て8作品くらい制作していたのと比べるとちょっと今年はあんまり動けなかったな~という気持ち。

↓去年の振り返り

donabenabe.hatenablog.com

修論が色々迷走してしまって、それにかなり追い込まれていた感ありますね。

修論はなんとか提出をさきほど終えました(ほんとは去年振り返り出したかった)

ちなみに今年の目標は「代表作」だったんですが、だめでしたね…

ただ、大学時代の同期とインディーゲーム作り始めたり、なんだかんだ初めてunityroomで作ったゲームを一般公開したり(今まで限定公開のみ)、布石は打てたかな?(精一杯の強がり)

2026年の抱負

2026年は無事に卒業できればついに4月から社会人になる!

自分の持ち味はここぞと言うときにガッと締切までに全部作りきる、みたいな能力なんですが、ハッカソンでは良くてもこれを会社でずっと続けるわけにはいきません。
徹夜も社会人になったら仕事でするわけには行きません。

そこで社会人での目標は「見積もり力を鍛える」にしようと思います。

見積もり力は社会人スキルとして超重要だと思います。このタスクはいつまでに終わりますというのを自分の力を過信して伝えるとチームのあらゆる人に迷惑をかけます。

これは「期待値コントロール」という面においても、最重要です。上がった期待を裏切るのは、期待してないけど妥当な結果のときよりダメージがでかいです。 正直、自分ならできると思っちゃいがちですが、それは無理をすればできるというケースが多くて、それを多用するのは体的にも信頼貯金的にも良くないです。

余裕をもった能力分析をして、宣言して、こなす、を積み重ねて信頼を勝ち取っていきたい。

ただ、自分の能力を引き上げるのは、見積もりを立てる上でも、その見積もりを相手の期待以上かつ絶対に達成できる内容で立てられるようになるのにも必要なので、インプットはもちろん増やしていきたいですね。

個人活動としての目標は「代表作」を続けます。

まずはインディーゲーム即売会に出店するのを目標に一本ゲームを妥当なクオリティで作りきりたいです。

ということで、2025年はお世話になりました。

2026年もよろしくお願いします。

自分の学生時代を彩ってくれたハッカソンでいつも意識してること

この記事はサイバーエージェント 26卒内定者エンジニア アドベントカレンダーの22日目の記事です。

26卒ゲームクライアントエンジニアの土鍋と申します。

私はハッカソンに参加するのが好きで何度も参加しています。自分で主催したりするほど好きで、自分の学生時代を彩ってくれたな~と思っています(学生終わっちゃう…😭)。

こないだも参加しました

一年前にハッカソンでの技術的なルーティンの話をしたので、今回は意識してることについて書いてみました。

↓一年前の記事

donabenabe.hatenablog.com

アイデア

ハッカソンは一にも二にもアイデアが最重要です。

最終プレゼンは大抵3分とか5分とかかなり短いので話せることに限りはあるし、1,2日くらいで完成させるには複雑なものは基本的に無理です。

あれもこれも盛り込みたいけど、少なくともコンセプトは一貫性のある一つにしたい。

とにかくテーマに対する納得感とインパクトが大切なので、テーマに対して「そのアプローチがあったか」とか「その方向性で着目するか」とかそういう驚きを与えられるかがキーコンセプトになってくると思います。

ハッカソンの目的次第ですけど、尖った技術だからといって評価されることは少ない気がします。 ただ、最後の参加する理由にも書いていますが、触りたかった技術で一つプロダクトを完成させる絶好の機会でもあるので、そこにも価値があると感じています。

チーム決め

メンバーは事前に仲間内で決めていくのも面白いですが、当日のノリで組むのも楽しいです。

また、ハッカソンはとにかくプレゼンでのインパクトを重視されます。基本的にエンジニアが多くなりがちですが、デザイナーがいるならぜひチームに引き込みたいですね。

エンジニアから見える視点には限りがあります。アイデアもごくごく狭い視点からしか見れないです。ぜひ、異業種の人と組んでおもしろいアイデアを出したいですね。

開発方針

基本開発期間中は全員で集まる時間は少なくしたいです。ミーティングは迷走すると無限に終わらないので、開発開始冒頭で仕様を決めきって、あとは各々で誰にも相談せずに実装できるのが理想です。

特に自分の場合、毎度良くないと思いながら徹夜しているのですが、徹夜すると当たり前ですが思考力が鈍ります。そんなときにアイデア練り直したり仕様変更したら必ず失敗します。

マインド

結局いつもできなくなるんですが、「焦らない」ことを意識してます。

焦っているのは態度に出てしまうし、それが波及して全員焦ってしまうこともあります。

ハッカソンは大抵技術的な壁に当たることがあります。

そういった壁がチームで現れたら、すぐにアイデアを実現できる代替案を考えて提案するなど、焦りの種を潰す方向で動いています。

といってもチームメンバーのそういう問題は解決できても、自分がその壁にぶつかると周りを見れなくなることがあるので、もしそうなっていたら肩を叩いて気づかせてあげてほしいです。

なぜハッカソンに出るのか

当たり前ですがハッカソンは参加してしまえば、必ず作品ができあがります。 特に自分の場合は飽き性で一人では一本の作品を作り上げることが苦手というのもあり、ハッカソンをとても重視していました。

技術的にチャレンジできるし、新たな面白い人達との出会いもある。

僕の学生時代はハッカソン・コンテストによって彩られていたな…と今は修論に追われながら振り返りました。

【Unity】UI Toolkitでレーダーチャートを作ってみる【Painter2D】

この記事はサイバーエージェント 26卒内定者エンジニア アドベントカレンダーの19日目の記事です。

26卒ゲームクライアントエンジニアの土鍋と申します。

先日書いたブログ
donabenabe.hatenablog.com

にて開発した作品のUIはすべてUI Toolkitで実装したのですが、ここでレーダーチャートの実装が必要になりました。

uGUIでのレーダーチャートの実装は見かけましたが、UI Toolkitで実装している人はいなかったのでやってみました。

実装

※Unity2023.2からUI ToolkitのUxmlFactory/UxmlTraitsの代替として、[UxmlElement] [UxmlAttribute]が追加され、Unity 6.4ではUxmlFactory/UxmlTraitsは完全に削除されるようなので注意。

参考
https://discussions.unity.com/t/planned-breaking-changes-in-unity-6-4/1682100

自作VisualElementをUI Builderで使う

partialでVisualElementを継承し、[UxmlElement]attributeをつけるだけでUI Builderから参照できるようになります。

[UxmlElement]
public partial class SevenSinsChart : VisualElement

プロパティ

プロパティも[UxmlAttribute]をつけるだけで実装可能です。

以前のUxmlTraitsではUxmlStringAttributeDescriptionなど独特なクラスを使用しなければならなかったので、相当使いやすいですね。

// ラベル文字サイズ
[UxmlAttribute]
public float LabelFontSize
{
    get => _labelFontSize;
    set
    {
        _labelFontSize = value;
        // 既に生成されているラベルに即時適用
        foreach (var lbl in _labels) lbl.style.fontSize = value;
    }
}
private float _labelFontSize = 30f;

// 文字色
[UxmlAttribute]
public Color LabelColor
{
    get => _labelColor;
    set
    {
        _labelColor = value;
        foreach (var lbl in _labels) lbl.style.color = value;
    }
}
private Color _labelColor = Color.white;

// 煩悩ラベル
[UxmlAttribute]
public string[] Sins
{
    get => _sins;
    set
    {
        _sins = value;
        OnValuesChanged();
    }
}
private string[] _sins = new string[] { "傲慢", "嫉妬", "憤怒", "怠惰", "強欲", "暴食", "色欲" };

// グリッドステップ数
[UxmlAttribute]
public int GridSteps 
{
    get => _gridSteps;
    set
    {
        _gridSteps = value;
        OnValuesChanged();
    }
}
private int _gridSteps = 5;

// データ
[UxmlAttribute]
public float[] Values
{
    get => _values;
    set
    { 
        _values = value;
        OnValuesChanged();
    }
}
private float[] _values = new float[7];

レーダーチャート描画

UI ToolkitではMeshGenerationContextによってメッシュを描画することができます。今回はその中でもベクターグラフィックスをするPainter2Dを利用して、レーダーチャートを描画する。

参考
light11.hatenadiary.com

プロパティの数値に変化があった際に実行するメソッド。
ラベルが減ったり増えたりするので動的にUSS用のクラスを付与して上げる必要があります。

あるいは普通にフォントもプロパティでやっても良かったかも

private void OnValuesChanged()
{
    // 現在のレーダーチャートをクリア
    _labels.ForEach(lbl => Remove(lbl));
    _labels.Clear();

    // ラベル再生成
    foreach (var text in Sins)
    {
        var lbl = new Label(text);
        
        // USS用クラス追加
        lbl.AddToClassList("chart-label");
        
        lbl.style.position = Position.Absolute;
        
        // プロパティから適用
        lbl.style.fontSize = _labelFontSize;
        lbl.style.color = _labelColor;
        
        // 中心合わせ
        lbl.style.translate = new Translate(new Length(-50, LengthUnit.Percent), new Length(-50, LengthUnit.Percent), 0);
        
        Add(lbl);
        _labels.Add(lbl);
    }
    
    // 強制再描画指示
    MarkDirtyRepaint();
}

generateVisualContentのコールバックでレーダーチャートの再描画処理を実行します。

public SevenSinsChart()
{
    generateVisualContent += OnGenerateVisualContent;
}

private void OnGenerateVisualContent(MeshGenerationContext ctx)
{
    float width = contentRect.width;
    float height = contentRect.height;
    if (width < 1 || height < 1) return;

    var painter = ctx.painter2D;
    var center = new Vector2(width / 2f, height / 2f);
    
    // ラベルはみ出し防止のため0.8倍
    var radius = Mathf.Min(width, height) / 2f * 0.8f; 
    
    int vertexCount = Sins.Length;
    float angleStep = 360f / vertexCount;
    float startAngle = -90f;

    // --- グリッド ---
    painter.lineWidth = 5.0f;
    painter.strokeColor = new Color(0.5f, 0.5f, 0.5f, 1f);

    for (int step = 1; step <= GridSteps; step++)
    {
        float r = radius * (step / (float)GridSteps);
        painter.BeginPath();
        for (int i = 0; i < vertexCount; i++)
        {
            Vector2 pos = CalculatePoint(center, r, startAngle + i * angleStep);
            if (i == 0) painter.MoveTo(pos); else painter.LineTo(pos);
        }
        painter.ClosePath();
        painter.Stroke();
    }

    // --- 軸線 ---
    painter.BeginPath();
    for (int i = 0; i < vertexCount; i++)
    {
        Vector2 edgePos = CalculatePoint(center, radius, startAngle + i * angleStep);
        painter.MoveTo(center);
        painter.LineTo(edgePos);
    }
    painter.Stroke();

    // --- データ ---
    painter.fillColor = new Color(0f, 1f, 1f, 0.3f);
    painter.strokeColor = Color.cyan;
    painter.lineWidth = 2.0f;
    
    painter.BeginPath();
    for (int i = 0; i < vertexCount; i++)
    {
        float val = (_values != null && i < _values.Length) ? Mathf.Clamp01(_values[i]) : 0f;
        Vector2 pos = CalculatePoint(center, radius * val, startAngle + i * angleStep);
        
        if (i == 0) painter.MoveTo(pos); else painter.LineTo(pos);
    }
    painter.ClosePath();
    painter.Fill();
    painter.Stroke();

    // --- ラベル位置更新 ---
    float labelRadius = radius * 1.15f; 
    for (int i = 0; i < vertexCount; i++)
    {
        Vector2 labelPos = CalculatePoint(center, labelRadius, startAngle + i * angleStep);
        _labels[i].style.left = labelPos.x;
        _labels[i].style.top = labelPos.y;
    }
}

/// <summary>
/// 中心点(center)から半径r、角度angleDeg(deg)の位置を計算する
/// </summary>
/// <param name="center">中心点</param>
/// <param name="r">半径</param>
/// <param name="angleDeg">角度</param>
/// <returns>計算結果の位置</returns>
private Vector2 CalculatePoint(Vector2 center, float r, float angleDeg)
{
    float rad = angleDeg * Mathf.Deg2Rad;
    return new Vector2(center.x + r * Mathf.Cos(rad), center.y + r * Mathf.Sin(rad));
}

これでUI Builder上でパラメータ操作してレーダーチャートを組めるようになりました。

使い方

今回は表示時に真ん中から徐々に大きくなるアニメーションをLitMotionを使用して作ってみました。

public class RadarChartAnimator
{
    private SevenSinsChart _radarChart;

    private float[] _tempValues;

    public void Initialize(VisualElement root)
    {
        _radarChart = root.Q<SevenSinsChart>("SevenSinsChart");
        _radarChart.Values = new float[] { 0f, 0f, 0f, 0f, 0f, 0f, 0f };
    }

    /// <summary>
    /// チャートの値をアニメーションさせる関数
    /// </summary>
    /// <param name="endValues">最終的な値の配列</param>
    /// <param name="duration">アニメーション時間</param>
    public void AnimateChart(float[] endValues, float duration)
    {
        // 配列の長さチェック
        if (_radarChart.Values == null || _radarChart.Values.Length != endValues.Length)
        {
            _radarChart.Values = new float[endValues.Length];
        }

        // 開始時点の値をコピーしておく
        float[] startValues = (float[])_radarChart.Values.Clone();

        // 作業用配列の初期化
        if (_tempValues == null || _tempValues.Length != endValues.Length)
        {
            _tempValues = new float[endValues.Length];
        }

        // 0 -> 1 の値を遷移させる
        LMotion.Create(0f, 1f, duration)
            .WithEase(Ease.OutBack)
            .Bind(t =>
            {
                // t (0.0~1.0) に基づいて、各要素をLerp(線形補間)する
                for (int i = 0; i < endValues.Length; i++)
                {
                    _tempValues[i] = Mathf.Lerp(startValues[i], endValues[i], t);
                }

                // チャートに値をセット
                _radarChart.Values = _tempValues;
            });
    }
}

完成

コード

コード全体

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

[UxmlElement]
public partial class SevenSinsChart : VisualElement
{
    private float _labelFontSize = 30f; // デフォルトサイズ
    private Color _labelColor = Color.white;
    private string[] _sins = new string[] { "傲慢", "嫉妬", "憤怒", "怠惰", "強欲", "暴食", "色欲" };
    private int _gridSteps = 5;

    // ラベル文字サイズ
    [UxmlAttribute]
    public float LabelFontSize
    {
        get => _labelFontSize;
        set
        {
            _labelFontSize = value;
            // 既に生成されているラベルに即時適用
            foreach (var lbl in _labels) lbl.style.fontSize = value;
        }
    }

    // 文字色
    [UxmlAttribute]
    public Color LabelColor
    {
        get => _labelColor;
        set
        {
            _labelColor = value;
            foreach (var lbl in _labels) lbl.style.color = value;
        }
    }
    
    // 煩悩ラベル
    [UxmlAttribute]
    public string[] Sins
    {
        get => _sins;
        set
        {
            _sins = value;
            OnValuesChanged();
        }
    }
    
    // グリッドステップ数
    [UxmlAttribute]
    public int GridSteps 
    {
        get => _gridSteps;
        set
        {
            _gridSteps = value;
            OnValuesChanged();
        }
    }
    
    // --- データ ---
    private float[] _values = new float[7];
    
    [UxmlAttribute]
    public float[] Values
    {
        get => _values;
        set { _values = value; OnValuesChanged();}
    }

    // 内部保持用
    private List<Label> _labels = new List<Label>();
    
    public SevenSinsChart()
    {
        generateVisualContent += OnGenerateVisualContent;
    }
    
    private void OnValuesChanged()
    {
        // 現在のレーダーチャートをクリア
        _labels.ForEach(lbl => Remove(lbl));
        _labels.Clear();

        // ラベル再生成
        foreach (var text in Sins)
        {
            var lbl = new Label(text);
            
            // USS用クラス追加
            lbl.AddToClassList("chart-label");
            
            lbl.style.position = Position.Absolute;
            
            // プロパティから適用
            lbl.style.fontSize = _labelFontSize;
            lbl.style.color = _labelColor;
            
            // 中心合わせ
            lbl.style.translate = new Translate(new Length(-50, LengthUnit.Percent), new Length(-50, LengthUnit.Percent), 0);
            
            Add(lbl);
            _labels.Add(lbl);
        }
        
        // 強制再描画指示
        MarkDirtyRepaint();
    }

    private void OnGenerateVisualContent(MeshGenerationContext ctx)
    {
        float width = contentRect.width;
        float height = contentRect.height;
        if (width < 1 || height < 1) return;

        var painter = ctx.painter2D;
        var center = new Vector2(width / 2f, height / 2f);
        
        // ラベルはみ出し防止のため0.8倍
        var radius = Mathf.Min(width, height) / 2f * 0.8f; 
        
        int vertexCount = Sins.Length;
        float angleStep = 360f / vertexCount;
        float startAngle = -90f;

        // --- グリッド ---
        painter.lineWidth = 5.0f;
        painter.strokeColor = new Color(0.5f, 0.5f, 0.5f, 1f);

        for (int step = 1; step <= GridSteps; step++)
        {
            float r = radius * (step / (float)GridSteps);
            painter.BeginPath();
            for (int i = 0; i < vertexCount; i++)
            {
                Vector2 pos = CalculatePoint(center, r, startAngle + i * angleStep);
                if (i == 0) painter.MoveTo(pos); else painter.LineTo(pos);
            }
            painter.ClosePath();
            painter.Stroke();
        }

        // --- 軸線 ---
        painter.BeginPath();
        for (int i = 0; i < vertexCount; i++)
        {
            Vector2 edgePos = CalculatePoint(center, radius, startAngle + i * angleStep);
            painter.MoveTo(center);
            painter.LineTo(edgePos);
        }
        painter.Stroke();

        // --- データ ---
        painter.fillColor = new Color(0f, 1f, 1f, 0.3f);
        painter.strokeColor = Color.cyan;
        painter.lineWidth = 2.0f;
        
        painter.BeginPath();
        for (int i = 0; i < vertexCount; i++)
        {
            float val = (_values != null && i < _values.Length) ? Mathf.Clamp01(_values[i]) : 0f;
            Vector2 pos = CalculatePoint(center, radius * val, startAngle + i * angleStep);
            
            if (i == 0) painter.MoveTo(pos); else painter.LineTo(pos);
        }
        painter.ClosePath();
        painter.Fill();
        painter.Stroke();

        // --- ラベル位置更新 ---
        float labelRadius = radius * 1.15f; 
        for (int i = 0; i < vertexCount; i++)
        {
            Vector2 labelPos = CalculatePoint(center, labelRadius, startAngle + i * angleStep);
            _labels[i].style.left = labelPos.x;
            _labels[i].style.top = labelPos.y;
        }
    }

    /// <summary>
    /// 中心点(center)から半径r、角度angleDeg(deg)の位置を計算する
    /// </summary>
    /// <param name="center">中心点</param>
    /// <param name="r">半径</param>
    /// <param name="angleDeg">角度</param>
    /// <returns>計算結果の位置</returns>
    private Vector2 CalculatePoint(Vector2 center, float r, float angleDeg)
    {
        float rad = angleDeg * Mathf.Deg2Rad;
        return new Vector2(center.x + r * Mathf.Cos(rad), center.y + r * Mathf.Sin(rad));
    }
}

趣味限界開発合宿のすゝめ

この記事は Iwaken Lab. Advent Calendar 2025 15日目の記事です。

先日趣味で友達と限界開発合宿をしてきたので記録として記事を書きました。

詳細な技術的な解説は後日また別記事で書きますのでお待ち下さい。

[追記] ↓書きました

donabenabe.hatenablog.com

事の経緯

例年、会津大生&OBたちでクリスマスらへんで集まって各々徹夜開発する会をやっているのですが、今年はログハウス借りてチーム開発をすることに…

レギュレーションは「事前開発はアイデア出しまで、コードは会場ついてから」といういい感じの限界具合。チームは4vs4の2チーム。

開発テーマは「正月」

↑身内だけでやるのにイベントページまである。やるからには抜かりなくやる同期に尊敬。

作ったもの

「煩悩ジャッジ」

表示された2つの煩悩からより煩悩だと思う方を選んでいくと、選んだ傾向から7つの大罪のどの煩悩タイプか選ばれるというゲーム。

そうしてユーザーたちが遊んでいると最終的に全国最強の煩悩ランキングTop108が作られる!

煩悩ジャッジ時に一つ煩悩を投稿できるので、自分の煩悩が最強煩悩ランキング上位を狙えるかも?

という内容。

スクショ

タイトル画面

ジャッジ画面

煩悩投稿画面

10個煩悩ジャッジが終わったら一つ投稿可能。

結果画面

レーダーチャートで煩悩の傾向をわかりやすく。

ランキング画面

煩悩が煩悩過ぎたのでテストデータで…

アイデア

今回のテーマは「正月」

除夜の鐘 → 108の煩悩 → 最強の煩悩を決めたい

という経緯で決定。

MBTIとか、ガチャ的な要素があると楽しそうということで、10個煩悩ジャッジした傾向から一体キャラクターが選ばれるという要素も組み込んだ。

↓7つの大罪をモチーフとしたキャラクターたち

デザイナーのチームメンバーに無茶言って作ってもらいました

ありがとうありがとう~

技術スタック

チームはバックエンド×2、Unityエンジニア、デザイナーの構成

自分はUnityエンジニアなのでフロントの話しかできませんが、開発内容的にUIだけで実装できそうな内容だったので、全部UI Toolkitで実装してみました。

結構開発してて詰まりポイントがあったのでそこら辺は後日記事を書きます。

フロントの設計としては画面ごとにMVP(ModelViewPresenter)を組んでますが、通常のuGUIですと、少なくともViewはMonoBehaviorで作りますが、今回はUIToolkitなのでそれもなし。

すべての画面の起点にあるMonoBehaviorクラス以外は素のC#クラスで実装してみました。

つまりエディタ上はみんなだいすきDefault Skybox

処理フロー

バックエンドに2つの煩悩の組み合わせを要求し、選択した煩悩を送り返す。その煩悩の大罪をカウントし、最終結果で表示している。

煩悩投稿は自由記述で、大罪は選択式。本当はAIに文章から大罪選ばせてもいい気がしたけど、MVP(MinimumValuableProduct)的には選択でいいやということでこの実装。

ランキングはバックエンド側でソートが行われているので、クライアントは受け取ってるだけ。

限界開発の一コマ

今回の会場。いい雰囲気すぎる。

開会式。主催のnaoyaくん。

バーベキュー!!

毎年恒例プレゼント交換会

早朝の野菜スープ。こういうときにさっと料理作れるやつに憧れる。

締めのラーメン

感想

わざわざ休日にログハウス借りてまでやることが限界開発だなんて中々キモいことを趣味でできる仲間たちがいて感謝。
ただ「すゝめ」というタイトルにしたけど別に人に進められる内容でもないな…
今回作った作品は正月前に公開できたらしたいです。

最後に、主催してくれたnaoyaくんありがとう

SimpleRTK(ZED-F9P)による位置情報データをUARTで別マイコンに送る

この記事はサイバーエージェント 26卒内定者エンジニア アドベントカレンダーの5日目の記事です。

26卒内定者の土鍋と申します。
私はゲームクライアントエンジニアなのですが、なぜか研究関連で触ったGNSSについて書きました。

今回使用したSimpleRTKというモジュールからスマホに座標データを送りたかったのですが、そのままケーブルを繋ぐorPC経由で通信するのは外で使用するうえで不便なので、Bluetoothで通信するのが高速かつセッティングもしやすいと考えました。
そこでこの記事ではGNSSからUARTでESP32に送りBLE通信を行うための前提知識と前段階処理について書きました。

前提知識

GNSS (Global Navigation Satellite System、全球測位衛星システム)は、人工衛星を使って地球上の位置を測定するシステムの総称です。よく聞くGPSとはアメリカのGNSSのことを示すので、その他の衛星を利用する場合はGNSSと呼ぶほうが正確です。

RTK-GNSS (Real-Time Kinematic GNSS) は、GNSS 信号を用いて高精度な位置情報をリアルタイムで取得するための技術です。RTK-GNSS モジュールは、基地局と移動局の2 つのユニットで構成され、基地局が受信したGNSS 信号を元に、移動局の位置を高精度で算出します。

使用する機材

  • SimpleRTK Starter Kit
  • ESP32
  • u-center

u-center は、u-blox 社のGNSS モジュールを設定変更・デバッグするためのツールです。RTK-GNSS モジュールの設定やデータの確認ができます。

UARTで別のマイコンに送る

配線

SimpleRTKボードでUARTを使用するには3.3V outピンとIOREFピンをケーブルで接続する必要があります。

u-centerでSimpleRTKボードからシリアル通信するプロトコルの設定

メニューからコンフィグを開き、UBX>CFG>PRTでポート設定を開き、以下のように設定する。

Target:UART1
Protocol in: RTCM
Protocol out: NMEA
Baudrate: 115200

マイコン側のコード

Blutooth通信をするESP32で座標を受け取るためのコードを書いていきます。

HardwareSerial mySerial1(1);

void setup() {
    Serial.begin(115200);
    mySerial1.begin(115200,SERIAL_8N1,32,33);//RX=32, TX=33

void loop() {
    String tempMessage = mySerial1.readStringUntil('$');
    if (tempMessage.startsWith("GNRMC")) {
        Serial.println(tempMessage);
    }
}

SerialはUSBシリアル通信で予約済みなので上記のようにハードウェアシリアルか、
ESP32標準のUARTを使用する場合、Serial1.begin(115200)を使用することで送られてきたデータを読めます。

NMEAフォーマットの読み方

送られてくるデータフォーマット→ NMEAフォーマット

開始文字’$’から改行までのセンテンスで構成されるフォーマットで、
今回使用するのは"GNRMC"で始まるセンテンスです。
GNは複合GNSSデータのことで、RMCは推奨最小GNSSデータであることを示しています。

シリアル通信で送られてきたデータ

3606.47038 14006.02287
→ 36度6.47038分 140度6.02287分
→ 分を60で割って足す(度のみで表す)
→ 36.1078396 140.10038116

u-centerで表示されたデータ

36.10783680 140.10038347

ほぼ一致したので問題なく位置情報は送られてきている。

※座標は研究室の場所です。

まとめ

今回は取得したGNSS情報をBluetoothでスマホに送る前段階処理について書きました。

サイバーエージェントではゲームクライアントエンジニアなので、働いたら全くもってハードウェアを扱わないんですけどね…
次はUnityの記事書くぞ

普段の個人開発ではやっぱ物理的なものがあると展示映えするので、軽くマイコン周りの知識あるだけでも幅が広がっていいなーと思ってます。

【XREAL SDK 3.0】XREALでImageTrackingが動かなかった特大な罠

どうも、土鍋です。
タイトルの通りですが、XREAL SDKでImageTrackingが動かなくて2日くらい悩まされていたんですが、ボタン一個で解決しました。

結論

これでInstallを押してしまうと動きません。

愚痴

いやー空のプロジェクト作って動いたんで、既存プロジェクトで原因究明色々苦労しました。どのライブラリ影響してるのか一個一個抜いてビルドしてデバッグしてたんですが、動かない…

XREALグラスつなぐ都合上、有線でAndroid Logcat使えないのでワイアレスデバッグする必要があるんですが、その使い方知れたのはよかったかな。

まあよく考えたらドキュメントにInstallを押してとないので押した自分が悪いんですが、いかにも押してほしそうなボタンがあったら押しちゃいますよね…

参考

zenn.dev

【Unity】BLEアセット導入後、AndroidManifestのNo activityエラーによる起動不可能問題解消

エラー内容

assetstore.unity.com

このBLEアセットを入れると作られるAndroid Manifestをそのまま使用すると

DeploymentOperationFailedException: No activity in the manifest with action MAIN and category LAUNCHER. Try launching the application manually on the device

このようなエラーが出て、アプリのビルドはできているのに起動できないという問題に当たりました。

解決方法

PlayerSettings → Application Entry PointがデフォルトだとGameActivityなので、Activityに変更する必要がありました。