Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pnpm-lock.yaml
src/styles/globals.css
src/components/ui
src/content/
src/styles/globals.css
public/
*.md
19 changes: 14 additions & 5 deletions src/components/MemberCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import MemberAvatar from '@/components/MemberAvatar';
import type { Member } from '@/types/Member';
import Icon from './Icon.astro';
import { cn } from '@/lib/utils';
interface Props {
items: Member[];
layout?: 'author';
Expand All @@ -21,11 +22,19 @@ const { items, layout } = Astro.props;
{
items.map(member => (
<Card
className={`w-full ${layout !== 'author' && 'md:w-[calc(calc(100%/3)-0.8rem)]'} ${layout === 'author' && 'relative py-3 pl-[calc(20vw+1rem)]'}`}
className={cn(
'w-full',
layout === 'author'
? 'relative min-h-36 py-3 pl-[calc(20vw+1rem)]'
: 'md:w-[calc(calc(100%/3)-0.8rem)]'
)}
key={member.name}
>
<CardHeader
className={`${layout === 'author' && 'absolute left-3 top-1/2 h-[20vw] w-[20vw] -translate-y-1/2 justify-center p-0'}`}
className={cn(
layout === 'author' &&
'absolute left-3 top-1/2 h-[20vw] w-[20vw] -translate-y-1/2 justify-center p-0'
)}
>
<MemberAvatar
client:load
Expand All @@ -37,8 +46,8 @@ const { items, layout } = Astro.props;
fallbackText={member.name}
/>
</CardHeader>
<CardContent className={`${layout === 'author' && 'px-3 pb-3'}`}>
<CardTitle className={`${layout === 'author' && 'text-lg'}`}>
<CardContent className={cn(layout === 'author' && 'px-3 pb-3')}>
<CardTitle className={cn(layout === 'author' && 'text-lg')}>
{layout === 'author' && 'Author: '}
{member.name}
</CardTitle>
Expand All @@ -49,7 +58,7 @@ const { items, layout } = Astro.props;
)}
</CardContent>
{member.links && (
<CardFooter className={`${layout === 'author' && 'px-3 pb-0'}`}>
<CardFooter className={cn(layout === 'author' && 'px-3 pb-0')}>
<ul class="flex flex-col gap-1">
{member.links.map(link => (
<li>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import type { HTMLAttributes } from 'react';

import { cn } from '@/lib/utils';

Expand All @@ -24,7 +24,7 @@ const badgeVariants = cva(
);

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
Expand Down
6 changes: 3 additions & 3 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from 'react';
import { forwardRef, type ButtonHTMLAttributes } from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';

Expand Down Expand Up @@ -34,12 +34,12 @@ const buttonVariants = cva(
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
Expand Down
301 changes: 301 additions & 0 deletions src/content/blog/interface-tips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
---
title: ゲーム開発で例えるinterface
pubDate: '2025-11-24'
description: ゲーム開発で例えるinterface
author: Millin
tags: [Tech, C#]
---

こんにちは!ゲームエンジニアのMillinです。

今回はinterfaceについてゲーム開発での活用例を交えて解説したいと思います。

今回、例題のコードはC#で書きますが、たいていの言語で同様の活用ができるかと思います。

## インターフェイスとは

特定の関数やプロパティを持つことを明示する型です。

以下のように記述し、定義したInterface型の変数から関数やプロパティを呼び出すことができます。

```cs
interface ITest
{
int Value { get; }
void Test();
}

class Foo : ITest
{
int value;
public int Value => value;

public void Test()
{
value = 3;
Console.WriteLine("Hello, World!");
}
}

class Program
{
static void Main(string[] args)
{
ITest test = new Foo();
//Hello, World!が表示される。
test.Test();
//3が表示される。
Console.WriteLine(test.Value);
}
}
```

変数を持つことはできません。(C#ではコンパイルエラーになります。)

```cs
interface ITest
{
int test;
void Test();
}
```

interfaceに定義された関数やプロパティがinterfaceを適用したクラスにない場合もコンパイルエラーになります。

```cs
interface ITest
{
void Test();
}

//interfaceで定義されているTest()がない
class Foo : ITest
{
}
```

継承と違い複数のインターフェースをクラスに適用することができます。(C++などの多重継承できる言語は除く)

```cs
interface ITest
{
void Test();
}

interface ITest2
{
void Test2();
}

class Foo : ITest, ITest2
{
public void Test()
{
Console.WriteLine("Hello, World!");
}

public void Test2()
{
Console.WriteLine("Hello, World!");
}
}
```

## ゲームでの活用例

以下のような仕様を想定します。(この段階ではInterfaceは必要ありません)

- すべてのクラスはObjectを継承する
- キャラクター・弾があり、弾はキャラクターにダメージを与える。

```cs
class Object
{
}

class Character : Object
{
//体力
int hp;

public void Damage()
{
hp--;
}
}

class Bullet : Object
{
public void Hit(Character character)
{
character.Damage();
}
}
```

シンプルですね。ここで以下の追加仕様があった場合のinterfaceを使用しない場合と使用した場合の違いを比較してみます。

- 弾で壊せる箱(BreakableBox)・壊せない箱(Box)がある。
- 箱は持ち運ぶことができる。

### インターフェースを使用しない場合1(Bullet内でキャストして分岐)

```cs
class Object
{
}

class Character : Object
{
//体力
int hp;

public void Damage()
{
hp--;
}
}

class Bullet : Object
{
public void Hit(Object obj)
{
if(obj is Character character)
{
character.Damage();
}
else if( obj is BreakableBox breakableBox)
{
breakableBox.Damage();
}
}
}

class BoxBase : Object
{
public void Carry()
{
//運ぶ処理
}
}

class Box : BoxBase
{
}

class BreakableBox : BoxBase
{
public void Damage()
{
//壊れる処理
}
}
```

Bullet内でCharacterとBreakableBoxの二つに依存し、多態性が全く実現できていない状態になります。

### インターフェースを使用しない場合2(ObjectにDamage処理を持たせる)

```cs
class Object
{
public virtual void Damage()
{
}
}

class Character : Object
{
//体力
int hp;

public override void Damage()
{
hp--;
}
}

class Bullet : Object
{
public void Hit(Object obj)
{
obj.Damage();
}
}

class BoxBase : Object
{
public void Carry()
{
//運ぶ処理
}
}

class Box : BoxBase
{
}

class BreakableBox : BoxBase
{
public override void Damage()
{
//壊れる処理
}
}
```

特定のサブクラス(BreakableBox や Character)のための処理が基底に書かれるのは継承関係として正しくないです。

### インターフェースを使用した場合

```cs
interface IDamageable
{
void Damage();
}

class Object
{
}

class Character : Object, IDamageable
{
//体力
int hp;

public void Damage()
{
hp--;
}
}

class Bullet : Object
{
public void Hit(IDamageable damageable)
{
damageable.Damage();
}
}

class BoxBase : Object
{
public void Carry()
{
//運ぶ処理
}
}

class Box : BoxBase
{
}

class BreakableBox : BoxBase, IDamageable
{
public void Damage()
{
//壊れる処理
}
}
```

多態性や継承関係が適切に保たれます!
Loading