🔗目次
🔗 まえがき
皆さんどうもこんにちは、株式会社ECN所属インターンのFuseです。
前回は動物を実装して、喰ったり喰わせたりしました。
これで一区切り完成はしているのですが、せっかくなので生き物たちに個体ごとの個性をつけたいと思います。
色を使ったアイデアには、こちらの動画シリーズを参考にしました。
この章で実装する内容
- 動物・植物問わず、生物は「色」を持つ。
- 色に応じてパラメーターに倍率がかかる。
- 分裂時、RGBの割り振りがランダムに変化する
- RGB値の合計は常に変わらない
🔗 この章での完成品
↑目次に戻る
🔗 ソースコード
🔗 基底クラスに手を加える
actor/baselife
import pygame
import random
from actor.baseactor import *
class BaseLife(BaseActor):
# 初期化関数の引数にcolorが追加されています。
def __init__(
self, x: int, y: int, maxhealth: int, energy=1, color=(85, 85, 85)
) -> None:
self.maxhealth = maxhealth
self.health = maxhealth
self.energy = energy
self.color = color
super().__init__(x, y)
def step(self, screen: pygame.surface) -> None:
self.health -= 0.3
self.maxhealth -= 0.2
if self.health <= 0:
self.die()
super().step(screen)
def heal(self, value: int):
self.health = min(self.health + value, self.maxhealth)
def die(self):
self.health = 0
self.maxhealth = 0
self.alive = False
def eaten(self):
self.energy = 0
self.die()
# ここから追加分です
def calc_first_color(self):
r = 0
g = 0
b = 0
for _ in range(400):
rand = random.randint(0, 2)
if rand == 0:
r += 1
elif rand == 1:
g += 1
elif rand == 2:
b += 1
return (r, g, b)
def calc_next_color(self, parentcol: tuple):
r = parentcol[0] - 10
g = parentcol[1] - 10
b = parentcol[2] - 10
for _ in range(30):
rand = random.randint(0, 2)
if rand == 0:
r += 1
elif rand == 1:
g += 1
elif rand == 2:
b += 1
return (r, g, b)
まずは生き物たちの基底クラスであるBaseLifeから。
プロパティに色を表すタプルを格納するcolor
を追加し、
400ポイントを割り振って色の初期値を決定する関数calc_first_color
と
RGBそれぞれから10ポイント引いて30ポイントを振り直す親の色から子供の色を計算する関数calc_next_color
を作りました。
これで動植物問わず色を保持することができます。
🔗 植物をカラフルにする
生き物に色を保持させることができたので、次はその色を植物の描画とパラメータに反映させていきます。
actor/plant.py
import pygame
import random
from actor.baselife import BaseLife
from actor.nourishment import Nourishment
class Plant(BaseLife):
def __init__(self, x: int, y: int, color=None) -> None:
if color == None:
color = self.calc_first_color() # 色が指定されてなければランダムに割り振る
self.splittime = int(pygame.math.lerp(30, 5, color[0] / 255)) # 赤は増殖速度
self.splitrange = int(pygame.math.lerp(400, 900, color[2] / 255)) # 青は増殖範囲
super().__init__(
x, y, pygame.math.lerp(50, 200, color[1] / 255), 1, color
) # 緑は体力
def step(self, screen: pygame.surface, actors: list):
pygame.draw.polygon(
screen,
self.color,
(
(self.pos.x - 20, self.pos.y + 30),
(self.pos.x, self.pos.y - 40),
(self.pos.x + 20, self.pos.y + 30),
),
) # 色を描画に反映
self.splittime -= 1
if self.splittime == 0:
n = list(
filter(
lambda x: isinstance(x, Nourishment)
and (self.pos.x - x.pos.x) ** 2 + (self.pos.y - x.pos.y) ** 2
<= self.splitrange**2,
actors,
)
)
if len(n) > 0:
item = n[random.randint(0, len(n) - 1)]
item.alive = False
actors.append(
Plant(item.pos.x, item.pos.y, self.calc_next_color(self.color))
) # 増殖の際、子は自分の色に近い色になる
self.splittime = int(pygame.math.lerp(30, 5, self.color[0] / 255))
super().step(screen)
次に植物がカラフルになれるようにしていきます。
- 赤→移動速度
- 緑→寿命兼腹持ち
- 青→視界半径
といったように影響が出ます。
色の度合いによってパラメータを変える処理はpygame.math.lerp
というおあつらえ向きの関数があるのでそちらを使います。
Lerpって?
線形補間に使われる関数で、a
,b
,t
を指定すると、t
の値に応じてa
からb
の間の値が返ってくる。
t<=0
のときはa
が返ってきますし1<=t
のときはb
が返ってきます。
0<t<1
のときはa+(b-a)*(t)
が返ってきます。
🔗 動物をカラフルにする
植物の次は、動物にも同等の作業を行っていきます。
actor/baseanimal.py
import pygame
import random
from actor.baseactor import pygame
from actor.baselife import BaseLife
class BaseAnimal(BaseLife):
def __init__(
self,
x: int,
y: int,
maxhealth: int,
energy: int,
sight: int,
speed: int,
color=None,
) -> None:
if color == None:
color = self.calc_first_color()
self.vec = pygame.math.Vector2(1, 1).rotate(random.random() * 360) # 移動方向ベクトル
self.sight: int = sight * pygame.math.lerp(0.75, 2.5, color[2] / 255) # 視界の半径
self.speed: float = speed * pygame.math.lerp(
0.75, 3, color[0] / 255
) # 移動速度(最大)
self.speedgear: float = 1 # 移動速度倍率
self.target: BaseLife = None # 追いかけている対象
super().__init__(
x, y, maxhealth * pygame.math.lerp(0.75, 2.5, color[1] / 255), energy, color
)
def step(self, screen: pygame.surface, actors: list) -> None:
self.health -= self.speedgear # 速度倍率に応じ体力減少
if self.pos.y <= 20 and self.vec.y < 0:
self.vec.y *= -1
if self.pos.y >= 940 and self.vec.y > 0:
self.vec.y *= -1
if self.pos.x <= 20 and self.vec.x < 0:
self.vec.x *= -1
if self.pos.x >= 1260 and self.vec.x > 0:
self.vec.x *= -1
self.pos += self.vec.normalize() * self.speedgear * self.speed
return super().step(screen)
def get_insight(self, target: type, actors: list):
# 視界内のtarget型のアクターのリストを返す\
return list(
filter(
lambda x: (self.pos.x - x.pos.x) ** 2 + (self.pos.y - x.pos.y) ** 2
<= self.sight**2
and isinstance(x, target),
actors,
)
)
まずは基底クラスであるBaseAnimalクラスに手を入れます。
動物では、
- 赤→移動速度
- 緑→寿命兼腹持ち
- 青→視界半径
といったように影響が出ます。
動物ごとにパラメータが変わるので、植物とは違い渡された値に倍率で掛けてます。
それ以外は別に特筆することはないです。
何か書こうと思いましたが、書きたいものはすべて植物の解説に書いてしまいました…
actor/sheep.py
import pygame
from actor.baseanimal import BaseAnimal
class Sheep(BaseAnimal):
def __init__(self, x: int, y: int, color=None) -> None:
super().__init__(x, y, 200, 4, 200, 1.5, color)
self.speedgear = 0.5
self.escapetime = 0 # 逃亡状態が解除される時間
self.vdangervec = pygame.Vector2(0, 0)
def step(self, screen: pygame.surface, actors: list):
if self.time > 45: # 重なり防止のため、最初の1.5秒はランダム方向に直進させる
from actor.wolf import Wolf
from actor.plant import Plant
self.speedgear = 0.8
danger = self.get_insight(Wolf, actors) # 視界内の天敵のリスト
verydanger = filter(
lambda x: x.target == self, self.get_insight(Wolf, actors)
) # 自分を狙う天敵のリスト
food = self.get_insight(Plant, actors) # 視界内の飯のリスト
dangervec = pygame.Vector2(0, 0)
foodvec = pygame.Vector2(0, 0)
for v in danger:
dangervec += (self.pos - v.pos) * (
self.sight / max(1, (v.pos - self.pos).magnitude())
)
if dangervec.length() > 0:
dangervec = dangervec.normalize()
for v in verydanger:
self.vdangervec += self.pos - v.pos
self.escapetime = self.time + 15 # 逃亡開始
if self.escapetime > self.time:
self.speedgear = 1
else:
self.vdangervec = pygame.Vector2(0, 0) # 逃亡終了
if self.vdangervec.length() > 0:
self.vdangervec = self.vdangervec.normalize() * 5
if len(food) > 0:
food.sort(key=lambda x: (self.pos - x.pos).length()) # 距離が短い順に並べる
foodvec += (food[0].pos - self.pos) * (
self.sight / max(1, (food[0].pos - self.pos).magnitude())
)
self.target = food[0]
foodvec = foodvec.normalize() * 3
if self.target != None: # 捕食対象を追いかけているなら
if (self.pos - self.target.pos).length() < 20: # 至近距離なら
self.energy += self.target.energy
self.target.eaten()
self.heal(300)
if self.energy >= 8: # 十分なエネルギーがあるなら
while self.energy >= 8:
self.energy -= 4
actors.append(
Sheep(
self.pos.x,
self.pos.y,
self.calc_next_color(self.color),
)
)
if (foodvec + dangervec + self.vdangervec).length() > 0:
self.vec = (foodvec + dangervec + self.vdangervec).normalize()
pygame.draw.rect(screen, self.color, (self.pos.x - 20, self.pos.y - 20, 40, 40))
super().step(screen, actors)
actor/wolf.py
import pygame
from actor.baseanimal import *
class Wolf(BaseAnimal):
def __init__(self, x: int, y: int, color=None) -> None:
super().__init__(x, y, 400, 20, 200, 1.5, color)
self.speedgear = 0.5
def step(self, screen: pygame.surface, actors: list):
from actor.sheep import Sheep
if self.time > 45:
self.speedgear = 0.3
food = self.get_insight(Sheep, actors) # 視界内の飯のリスト
foodvec = pygame.Vector2(0, 0)
if self.health / max(self.maxhealth, 0.1) < 1 and len(food) > 0:
food.sort(key=lambda x: (self.pos - x.pos).length())
foodvec += (food[0].pos - self.pos) * (
self.sight / max(1, (food[0].pos - self.pos).magnitude())
)
self.target = food[0]
if foodvec.length() > 0:
foodvec = foodvec.normalize()
self.speedgear = 1.0
if self.target != None:
if (self.pos - self.target.pos).length() < 20:
self.energy += self.target.energy
self.target.eaten()
self.heal(300)
if self.energy >= 40:
while self.energy >= 40:
self.energy -= 20
actors.append(
Wolf(
self.pos.x,
self.pos.y,
self.calc_next_color(self.color),
)
)
if (foodvec).length() > 0:
self.vec = (foodvec).normalize()
pygame.draw.polygon(
screen,
self.color,
(
self.pos + (20, 0),
self.pos + (0, 20),
self.pos + (-20, 0),
self.pos + (0, -20),
),
)
super().step(screen, actors)
最後に動物の各クラスに手を入れていきます。
やることは同じなので2ファイル同時に解説します。
やることは大きく分けて3つくらい。
__init__
の引数にcolorを追加しsuper().__init__
にバケツリレー- 描画時に
self.color
の値を使うようにする - 分裂時に
self.color
を基にランダムで変化した値を子の生成時に渡す
うち2つは植物と同じですが、逆に言えばこれだけお手軽な手段で生き物に多様性を持たせられるというわけです。
とにかく、これで生き物たちがカラフルかつ多種多様になりました!
🔗 あとがき
どうでしたか?多種多様な動物たちが群れを作って動き回る姿は愛らしいものがありますね。
ソースコードも配布しておりますので、皆さんもぜひ改造して遊んでみてはいかがでしょうか?
それでは、また別の記事でお会いしましょう!
↑目次に戻る
🔗 参考動画群
株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。
ビジネスの最初から最後までサポートを行い
お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。
注意!
この記事は前回・前々回の記事を読んでいることを前提に作られています。
まだ読んでいない方は下のリンクからどうぞ。