前回からの続き
前回の続きから始めます。そのため前回の「pygameを用いて簡単な格闘ゲームを自作してみた1」を見ていないかたは先にそちらを見てください。
今回の目標
前回はステージを作成しました。作成したとはいってもただ真っ白いpygame windowに緑色のブロックを表示するだけなので大したことはしてないですが‥
今回は戦闘用ステージ上にキャラクターを表示しようと思います。ただ表示するだけではつまらないので表示したキャラを操作できるようにします。左右の移動とジャンプの実装を今回の目標とします。
操作キャラの表示
いよいよ操作キャラクターを表示します。
プロジェクト名/
├ main.py
└ game_parts/
├ __init__.py
├ stage.py
├ fighter.py
├ control.py
import pygame
from game_parts import stage, fighter, control
pygame.init()
SURFACE = pygame.display.set_mode((1500, 900))
FPSCLOCK = pygame.time.Clock()
STAGE_POS = [0, 550] # <= 表示するステージの位置
def main():
player1_pos = [1000, 470] # <= 操作キャラの位置
player1_size = [50, 80] # <= 操作キャラの大きさ [横幅, 縦幅]
move1 = [False, False, False, False] # <= [上方向に移動、下方向に移動、右方向に移動、左方向に移動]
jump_speed1 = 20 # <= ジャンプの初速度
contact1 = [False, False, False, False] # <= [キャラクターと地面との接触判定、頭上のオブジェクトとの接触判定、右側とオブジェクトとの接触判定、左側とオブジェクトの接触判定
action1 = [False, False, False, False] # <= [弱攻撃、必殺技、ガード、掴み]
while True:
# 背景を真っ白に設定する
SURFACE.fill((250, 250, 250))
# プレイヤー1のキャラクター操作
move1, action1 = control.control_character(move1, action1, contact1)
# 戦闘ステージを作成
stage.base_stage(STAGE_POS)
# プレイヤー1の操作キャラの表示、行動制御
player_1 = fighter.Fighter(player1_pos, player1_size, 16, 10, contact1)
player_1.figure()
player1_pos, jump_speed1, move1 = player_1.move(jump_speed1, move1)
contact1 = player_1.contact_judgment()
player1_pos = player_1.position_corr(STAGE_POS)
# 画面を更新する
pygame.display.update()
# 画面の更新を30fps(1秒間に30枚画面が切り替わる)に設定する
FPSCLOCK.tick(30)
if __name__ == '__main__':
main()
import pygame
import main
class Fighter:
def __init__(self, position, size, gravity, move_speed, contact):
self.pos_x = position[0]
self.pos_y = position[1]
self.width = size[0]
self.height = size[1]
self.gravity = gravity
self.move_speed = move_speed
self.contact = contact
def figure(self):
"""
キャラクターの大きさを決め、表示する
"""
rect = (self.pos_x, self.pos_y, self.width, self.height)
pygame.draw.rect(main.SURFACE, (255, 0, 0), rect)
def move(self, jump_speed, player_move):
"""
キャラクターの移動を制限する
Parameters
----------
jump_speed : (int) ジャンプして一定時間後の速度を表す
player_move : プレイヤー操作(上方向、下方向、右方向、左方向)を表す
Returns
-------
変化後のキャラクターの位置情報、一定時間後のジャンプ速度、プレイヤー操作(上方向、下方向、右方向、左方向)を表す
"""
# ジャンプボタンを押した時キャラクターをジャンプさせる。ジャンプし終えた後は重力によって落下する
if player_move[0]:
self.pos_y -= jump_speed
player_move[0] = False if not jump_speed else player_move[0]
jump_speed = 20 if not jump_speed else jump_speed - 2
elif not self.contact[0]:
self.pos_y += self.gravity
# 操作キャラを左右に動かす
if player_move[2] and not self.contact[2]:
self.pos_x += self.move_speed
elif player_move[3] and not self.contact[3]:
self.pos_x -= self.move_speed
return [self.pos_x, self.pos_y], jump_speed, player_move
def contact_judgment(self):
"""
操作キャラとステージとの接触判定、各接触方向に対して接触した場合のみCONTACTの各接触方向の要素にTrueを返す
Returns
-------
(list) 変更したcontactを返す
"""
if self.pos_x == 1450:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[2] = True
elif 0 < self.pos_x < 1450:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[2] = False
self.contact[3] = False
elif self.pos_x == 0:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[3] = True
return self.contact
def position_corr(self, stage_pos):
"""
操作キャラとステージとの位置補正
Parameters
----------
stage_pos : 対象ステージの位置
Returns
-------
補正後の操作キャラの位置(y軸方向)
"""
if self.contact[0] and (self.pos_y + self.height - stage_pos[1]) != 0:
self.pos_y = stage_pos[1] - self.height
return [self.pos_x, self.pos_y]
fighter.pyの解説
def __init__(self, position, size, gravity, move_speed, contact):
self.pos_x = position[0]
self.pos_y = position[1]
self.width = size[0]
self.height = size[1]
self.gravity = gravity
self.move_speed = move_speed
self.contact = contact
__init__メソッドはインスタンスを生成した段階で働く部分なのですがFighterクラスの中で共通して使う変数をここでまとめました。
def figure(self):
"""
キャラクターの大きさを決め、表示する
"""
rect = (self.pos_x, self.pos_y, self.width, self.height)
pygame.draw.rect(main.SURFACE, (255, 0, 0), rect)
ここでは簡単なキャラクターを作成し、描画されます。描画の仕方は前回、ステージを描画したときとほぼ同じなので説明は不要ですね。
def move(self, jump_speed, player_move):
"""
キャラクターの移動を制限する
Parameters
----------
jump_speed : (int) ジャンプして一定時間後の速度を表す
player_move : プレイヤー操作(上方向、下方向、右方向、左方向)を表す
Returns
-------
変化後のキャラクターの位置情報、一定時間後のジャンプ速度、プレイヤー操作(上方向、下方向、右方向、左方向)を表す
"""
# ジャンプボタンを押した時キャラクターをジャンプさせる。ジャンプし終えた後は重力によって落下する
if player_move[0]:
self.pos_y -= jump_speed
player_move[0] = False if not jump_speed else player_move[0]
jump_speed = 20 if not jump_speed else jump_speed - 2
elif not self.contact[0]:
self.pos_y += self.gravity
# 操作キャラを左右に動かす
if player_move[2] and not self.contact[2]:
self.pos_x += self.move_speed
elif player_move[3] and not self.contact[3]:
self.pos_x -= self.move_speed
return [self.pos_x, self.pos_y], jump_speed, player_move
最初のif文で上方向に跳んでいるのですがjump_speedの値を固定にしていきなり高くジャンプするのはあまりにも操作がしづらかったのでjump_speedの値を徐々に減らしつつ少しづつ上昇するようにしました。
その後、上昇しきった後重力に従って落下するのですが本来物理法則を考えれば落下する際、徐々に加速するはずです。しかし、今回は等速で落下するようにしました。一回のジャンプで跳ぶ高さはそんなに高く設定してないのでそこまで細かく再現しなくても良いかなという安易な発想です。
そして左右の操作ですが操作キャラが画面端に移動した際、それ以上先には進まないようにself.contactの値も条件に入れています。
def contact_judgment(self):
"""
操作キャラとステージとの接触判定、各接触方向に対して接触した場合のみCONTACTの各接触方向の要素にTrueを返す
Returns
-------
(list) 変更したcontactを返す
"""
if self.pos_x == 1450:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[2] = True
elif 0 < self.pos_x < 1450:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[2] = False # 2019/10/29 修正
self.contact[3] = False # 2019/10/29 修正
elif self.pos_x == 0:
self.contact[0] = True if 550 <= self.pos_y + self.height else False
self.contact[3] = True
return self.contact
上記のコードが操作キャラと前回作成したステージとの接触判定になります。
ステージの長さは横に1500あるので操作キャラの位置が0以上1500以下の範囲を移動するように設定している
def position_corr(self, stage_pos):
"""
操作キャラとステージとの位置補正
Parameters
----------
stage_pos : 対象ステージの位置
Returns
-------
補正後の操作キャラの位置(y軸方向)
"""
if self.contact[0] and (self.pos_y + self.height - stage_pos[1]) != 0:
self.pos_y = stage_pos[1] - self.height
return [self.pos_x, self.pos_y]
上記のコードは操作キャラとステージとが接触したときにキャラの位置がずれてステージにめりこんだりするのを回避するコード。
このコードがあるのと無いのとでは以下のように変わる(黒丸の部分に注目)
位置補正コードが無い時:
上記の違いはジャンプをしてから着地をした時にはっきりと分かる。
import sys
import pygame
from pygame.locals import QUIT, KEYDOWN, KEYUP, K_q, K_w, K_e, K_c, \
K_u, K_i, K_o, K_p
def control_character(player_move, action, contact):
"""
対象キャラを操作する
Parameters
----------
player_move : (list) 対象キャラを移動させる
action : (list) 攻撃、ガード、掴み
contact : (list) 操作キャラとステージの接触判定
Returns
-------
変更した移動、攻撃の判定を返す
"""
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if pygame.key.get_pressed()[K_w]:
player_move[0] = True if contact[0] else player_move[0]
if pygame.key.get_pressed()[K_c]:
player_move[1] = True
if pygame.key.get_pressed()[K_q]:
player_move[3] = True
if pygame.key.get_pressed()[K_e]:
player_move[2] = True
if pygame.key.get_pressed()[K_u]:
action[0] = True
if pygame.key.get_pressed()[K_i]:
action[1] = True
if pygame.key.get_pressed()[K_o]:
action[2] = True
if pygame.key.get_pressed()[K_p]:
action[3] = True
elif event.type == KEYUP:
if event.key == K_c:
player_move[1] = False
if event.key == K_q:
player_move[3] = False
if event.key == K_e:
player_move[2] = False
if event.key == K_u:
action[0] = False
if event.key == K_i:
action[1] = False
if event.key == K_o:
action[2] = False
if event.key == K_p:
action[3] = False
return player_move, action
上記のcontrol.pyの解説
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
上記のコードは前回main.pyファイルの中に書いたコードです。キャラクターを操作するためにpygame.event.get()が必要になるためcontrol.pyファイルに移動しました。
elif event.type == KEYDOWN:
if pygame.key.get_pressed()[K_w]:
player_move[0] = True if contact[0] else player_move[0]
if pygame.key.get_pressed()[K_c]:
player_move[1] = True
if pygame.key.get_pressed()[K_q]:
player_move[3] = True
if pygame.key.get_pressed()[K_e]:
player_move[2] = True
if pygame.key.get_pressed()[K_u]:
action[0] = True
if pygame.key.get_pressed()[K_i]:
action[1] = True
if pygame.key.get_pressed()[K_o]:
action[2] = True
if pygame.key.get_pressed()[K_p]:
action[3] = True
上記のコードは操作キャラを移動させたり、攻撃、ガード、掴みをさせたりするコードです。対象のキーを入力し、move1,action1内の特定の要素をTrueにすることでその要素に対応した行動を取らせることができる。
ここでなぜ直接対応した行動をとる処理を書かないのか?
例えば:
if pygame.key.get_pressed()[K_q]:
player_pos[0] -= 5
といった処理を書かないのか。
その理由は先ほどの例のコードだとQキーを最初に入力した時しかplayer_pos[0] -= 5の処理がされないからです。本来長押ししている間は常にplayer_pos[0] -= 5の処理をし続けて欲しいのですがそれができない。
ただ、実は以下のコードを使って長押しを認識させることができる。
pygame.key.set_repeat(5, 5)
上記のコードをmain.pyのpygame.init()の次に追加すれば可能
しかし、このコードだとジャンプの入力をした際、長押しでいくらでも画面上方向に上昇し続けてしまうため今回の格ゲー制作には不都合です。
そのためmove1,action1を一回挟んで特定の要素がTrueだったらそれに対応した行動をFighterクラスの各関数でそれぞれ処理をするようにしました。
elif event.type == KEYUP:
if event.key == K_c:
player_move[1] = False
if event.key == K_q:
player_move[3] = False
if event.key == K_e:
player_move[2] = False
if event.key == K_u:
action[0] = False
if event.key == K_i:
action[1] = False
if event.key == K_o:
action[2] = False
if event.key == K_p:
action[3] = False
上記のコードでは押しているキーを離したときにTrueだった要素をFalseに変換するコードです。先ほどの話でいくとmove, actionのどれかの要素がTrueの場合対応した行動をとるためその行動をキャンセルするときはFalseに戻さないといけません。なので長押ししているキーを離した時にキャンセルされるようにしたのが上のコードになります。
ここまで進むと以下の画像のようにキャラが表示されかつ動かすことができる。
キャラ表示:
今回はここまで次回は今のところ敵キャラを表示してその敵キャラに攻撃できる処理を書いていこうかなと考えています。
説明が分かりにくい箇所があったかもしれませんがここまで読んでいただきありがとうございました。
では、また