HTML5 Game Tutorial - phaser
First Game
HTML5 Game Tutorial
기본설정
게임을 구성하는 기본적인 설정입니다.
var config = {
type: Phaser.AUTO,
width: fn_screenWidth(),
height: 600,
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
function preload (){}
function create (){}
function update (){}
게임오브젝트 로드하기
function preload ()
{
//this.load.setBaseURL('https://labs.phaser.io');
this.load.image('sky', '{% static 'img/phaser/assets/tutorial/sky.png' %}' );
this.load.image('ground', '{% static 'img/phaser/assets/tutorial/platform.png' %}' );
this.load.image('star', '{% static 'img/phaser/assets/tutorial/star.png' %}' );
this.load.image('bomb', '{% static 'img/phaser/assets/tutorial/bomb.png' %}' );
this.load.spritesheet('dude','{% static 'img/phaser/assets/tutorial/dude.png' %}', { frameWidth: 32, frameHeight: 48 });
}
function create (){
this.add.image(400, 300, 'sky');
//this.add.image(0, 0, 'sky').setOrigin(0,0); //왼쪽 상단
}
init() 메서드 – 장면 의 특정 매개변수를 초기화하는 데 사용되면 호출됩니다. 이 방법이 항상 사용되는 것은 아닙니다.
preload() 메서드 - 모든 자산 사전 로드가 수행됩니다. 이것은 Phaser가 모든 이미지, 모든 오디오 파일 및 게임에서 사용할 수 있는 기타 외부 파일을 등을 지연없이 사용할 수 있도록 메모리에 로드됩니다.
create() 메서드 - 한 번 호출되며 실제로 스프라이트를 생성하고 화면에 표시하는 곳입니다.
update() 메서드 - 이 과정의 뒷부분에서 이 메서드를 살펴보겠지만 게임 플레이 중에 각 프레임에서 호출됩니다.
이미지가 800x600 이므로 오브젝트 중심을 기준으로 배치 되기 때문에 400x300으로 주시면 됩니다.
800x600, 0x0 등 으로 변경 후 테스트 해보시기 바랍니다.
setOrigin(0,0)을 사용하여 이미지 시작위치를 왼쪽 상단으로 변경할 수 있습니다.
지형지물 만들기
var config = {
...
...
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
...
...
};
var platforms; //지형지물
function create (){
this.add.image(400, 300, 'sky');
//this.add.image(0, 0, 'sky').setOrigin(0,0); //왼쪽 상단
platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
platforms.create(600, 400, 'ground');
platforms.create(50, 250, 'ground');
platforms.create(750, 220, 'ground');
}
config의 physics속성: 물리효과를 사용할 수 있게 설정에 추가합니다.
this.physics.add.staticGroup() : physics staticGroup에 추가되는 오브젝트는 물리효과를 적용합니다.
setScale(2):크기 2배 적용
refreshBody(): 게임오브젝트의 변경사항을 적용
게임 플레이어 만들기
var player; //플레이어
function create (){
...
...
...
player = this.physics.add.sprite(100, 350, 'dude');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
this.physics.add.collider(player, platforms);
}
this.physics.add.sprite(100, 350, 'dude') : 100, 350 위치에 dude 캐릭터를 생성합니다.
setBounce : 캐릭터에 바운스(충돌시 튕기는) 효과를 줍니다.
setCollideWorldBounds : 캐릭터가 월드 영역을 넘어갈 수 없도록 합니다.
this.anims.create : 애미메이션을 생성하여 스프라이트 시트로 되어 있는 캐릭터의 좌측, 턴, 우측 달리기에 대한 초당 프레임 설정을 합니다.
physics.add.collider : 콜라이더 설정을 통해 플레이어와 플랫폼(지형)과 충돌을 감지하도록 설정합니다.
키보드 적용하기
var cursors; //키보드
function create (){
...
...
//input keyboard
cursors = this.input.keyboard.createCursorKeys();
...
...
}
function update (){
if (cursors.left.isDown){
player.setVelocityX(-160);
player.anims.play('left', true);
}else if (cursors.right.isDown){
player.setVelocityX(160);
player.anims.play('right', true);
}else{
player.setVelocityX(0);
player.anims.play('turn');
}
if (cursors.up.isDown && player.body.touching.down){
player.setVelocityY(-320);
}
}
this.input.keyboard.createCursorKeys() : 키보드 기능을 사용하도록 설정합니다.
update 함수에서 매 프레임 감지하며 키보드가 눌러졌는지 체크합니다.
setVelocityX : 캐릭터의 가로 속도를 설정합니다.
setVelocityY : 캐릭터의 세로 속도를 설정합니다.
cursors.up.isDown && player.body.touching.down : up키를 눌럿을때 바닥인지 체크하여 한번만 위로 설정한 만큼 이동하며, 중력에 의해 바닥으로 착지합니다.
스타 오브젝트 만들기
var stars; //스타
function create (){
...
...
...
stars = this.physics.add.group({
key: 'star',
repeat: 11,
setXY: { x: 12, y: 0, stepX: 70 }
});
stars.children.iterate(function (child) {
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});
this.physics.add.collider(stars, platforms);
this.physics.add.overlap(player, stars, collectStar, null, this);
}
function collectStar (player, star){
star.disableBody(true, true);
}
physics.add.group : 동적 물리 그룹을 만듭니다.
key: 스타이미지
repeat: 반복 횟수
setXY: x, y 좌표 단계별 x 추가
collider : 스타와 지형과 물리 충돌을 감지
overlap : 플레이어와 스타가 충돌했는지 감지하여 collectStar()를 호출합니다.
stars.children.iterate : 생성된 모든 스타에게 랜덤하게 바운스 효과를 줍니다.
star.disableBody() : 스타오브젝트를 비활성화하여 제거합니다.
별 수집 점수 표시
var score = 0; //점수
var scoreText;
function create (){
...
...
...
scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
}
function collectStar (player, star){
star.disableBody(true, true);
score +=1;
scoreText.setText('Score: '+score);
}
add.text : 화면에 텍스트를 추가한다.
scoreText : 화면의 텍스트를 다시 그려줍니다.
폭탕 공 만들기
var bombs;
var gameOver = false;
function create (){
...
...
...
bombs = this.physics.add.group();
this.physics.add.collider(bombs, platforms);
this.physics.add.collider(player, bombs, hitBomb, null, this);
}
function collectStar (player, star){
...
...
...
if (stars.countActive(true) === 0)
{
// A new batch of stars to collect
stars.children.iterate(function (child) {
child.enableBody(true, child.x, 0, true, true);
});
var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
var bomb = bombs.create(x, 16, 'bomb');
bomb.setBounce(1);
bomb.setCollideWorldBounds(true);
bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
bomb.allowGravity = false;
}
}
function hitBomb (player, bomb){
this.physics.pause();
player.setTint(0xff0000);
player.anims.play('turn');
gameOver = true;
}
physics.add.group() : 동적 물리 그룹으로 생성
add.collider : 폭탄공과 지형 감지와 플레이와 폭탄공 충돌시 hitbomb 함수 호출
stars.countActive : 남은 스타갯수를 체크합니다. 0이 되면 스타를 재생성하면 현재 플레이어의 반대편에 폭탄공을 생성합니다.
생명력 추가
var life = 1;
var lifeText = 0;
function hitBomb (player, bomb){
if(life <= 0 ){
this.physics.pause();
player.setTint(0xff0000);
player.anims.play('turn');
gameOver = true;
}else{
life = life-1;
lifeText.setText('Life: '+life);
}
}
생명력을 추가하고 폭탄 공과 충돌시 생명력 1회 차감
오디오 추가
//audio
var musicAD;
var collectOggAD;
var deathAD;
function preload (){
this.load.audio('forest', 'https://actioncall.github.io/actioncall/assets/audio/forest.mp3');
this.load.audio('hit', 'https://actioncall.github.io/actioncall/assets/audio/achievement.ogg');
this.load.audio('death', 'https://actioncall.github.io/actioncall/assets/audio/death.ogg');
...
...
}
function create (){
...
...
musicAD = this.sound.add('forest');
collectOggAD = this.sound.add('hit');
deathAD = this.sound.add('death');
musicAD.play();
musicAD.setLoop(true);
...
...
this.input.keyboard.on('keydown-' + 'O', function (event) { changeMusic();});
}
function changeMusic() {
if (!music.isPaused){music.pause();}
else{music.resume();}
}
function collectStar (player, star){
...
...
collectOggAD.play();
}
function hitBomb (player, bomb){
if(life <= 0 ){
deathAD.play();
...
...
}
}
musicAD : 배경음악
collectOggAD : 스타 먹을때 효과음
deathAD : 죽을때
O(키보드 O) : 소리 정지 및 시작
스피드 아이템추가
...
...
var playerSpeed = 200;
var buffSpeed = 1;
var potionbuff = 0;
var buffText = 0;
function preload (){
...
...
this.load.image('gPotion', 'https://actioncall.github.io/actioncall/assets/img/potion_long_green.png' );
}
function create (){
...
...
item = this.physics.add.group();
if(getRandomInt()>1){
item.create(getRandomWidth(),0,'gPotion');
}
this.physics.add.collider(item, platforms);
this.physics.add.overlap(player, item, collectItem, null, this);
buffText = this.add.text(16, 50, 'Buff: 0', { fontSize: '16px', fill: '#000' });
...
...
}
function getRandomWidth(){
return Math.floor(Math.random() * (start_star_width* max_star));
}
function getRandomInt(){
return Math.floor(Math.random() * 11);
}
function update (){
if(potionbuff > 0){
potionbuff -= 1;
buffText.setText('Buff: '+potionbuff);
}else{
buffSpeed = 1;
}
...
...
}
function collectItem(player,item){
collectOggAD.play();
item.disableBody(true, true);
buffSpeed = 2.5;
potionbuff = 1000;
}
function collectStar (player, star){
...
...
if (stars.countActive(true) === 0)
{
...
...
if(getRandomInt()>5){
item.children.iterate(function (child) {
child.enableBody(true, start_star_width*max_star, 0, true, true);
});
}
}
}
...
...
포션 획득시 potionbuff 를 1000 증가시키고 update()에서 1씩 감소시켜 0이 될때까지 스피드 버프 적용
가상 조이스틱 추가하기
function preload (){
...
...
this.load.plugin('rexvirtualjoystickplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexvirtualjoystickplugin.min.js', true);
}
function create (){
...
...
this.joyStick = this.plugins.get('rexvirtualjoystickplugin').add(this, {
x: sc_width/2,
y: 530,
radius: 100,
base: this.add.circle(0, 0, 70, 0x00009300),
thumb: this.add.circle(0, 0, 10, 0x000000),
// dir: '8dir', // 'up&down'|0|'left&right'|1|'4dir'|2|'8dir'|3
// forceMin: 16,
// enable: true
});
cursorKeys = this.joyStick.createCursorKeys();
this.input.keyboard.on('keydown-' + 'M', function (event) { changeMusic();});
this.input.keyboard.on('keydown-' + 'UP', function (event) { Move('up') });
this.input.keyboard.on('keydown-' + 'LEFT', function (event) { Move('left') });
this.input.keyboard.on('keydown-' + 'RIGHT', function (event) { Move('right') });
this.input.keyboard.on('keydown-' + 'DOWN', function (event) { Move('down') });
this.input.keyboard.on('keydown-' + 'SPACE', function (event) { Move('up') });
}
function update (){
for (var name in cursorKeys) {
if (cursorKeys[name].isDown) {
Move(name);
break;
}else{
Move('stop');
}
}
}
createCursorKeys를 키보드와 가상조이스틱에서 같이 사용하면 둘중 하나는 인식이 안됩니다.
그래서 키보드는 input.keyboard.on 으로 변경
전체 소스
{% extends "base/base.html" %}
{% load static %}
{% load extr_blog %}
{% block content %}
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 id="title_text" class="h3 mb-0 text-gray-800"><span id="{{pageInfo.id}}">{{pageInfo.title}}</span></h1>
</div>
<!-- python_content_top -->
{% include 'base/adsense/ad_content_top.html' %}
<!-- Content Row -->
<div class="row">
<div id="body_col_1" class="col-lg-8 mb-1">
<!-- c -->
<div class="card shadow mb-4">
{%for list in dataList %}
<div class="card-header py-3 card-header-idx{{forloop.counter}}">
<h2 class="m-1 font-weight-bold text-dark"><span id="{{list.id}}">{{list.sub_title}}</span></h2>
<!-- sns share start -->
{% if forloop.counter == 1 %}
{% include 'base/sharing.html' %}
{% endif %}
<!-- sns share end -->
</div>
<div class="card-body">
{% if list.iframe_pg_nm %}
<div class="text-center">
<iframe id="phaser_iframe" width="100%" height="{{list.iframe_h}}" marginwidth="0" marginheight="0" scrolling ="yes" src="{% url 'phaser:example' list.iframe_pg_nm %}"></iframe>
</div>
{% endif %}
<div class="mb-2">
{{ list.content_body|formatted_markdown }}
</div>
</div>
{% endfor %}
<!-- sns share start -->
{% comment %} <div class="card-body">
{ include 'base/sharing.html' }
</div> {% endcomment %}
<!-- sns share end -->
</div>
</div>
<div id="body_col_2" class="col-lg-4">
<div id="right_menu" class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2 mb-3" style="display:none;">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col">
{% if pageInfo.get_previous %}
<div>
<span class="h6 mb-1 font-weight-bold">
<a href="{{pageInfo.get_previous.get_absolute_url}}">{{pageInfo.get_previous}}</a>
</span>
</div>
{% endif %}
<hr class="d-none d-md-block">
<div> <span class="h6 mb-1 font-weight-bold anchor_link rightmenu_link_idx0 active"><a href="#{{pageInfo.id}}">{{pageInfo.title}}</a></span></div>
{% for list in dataList %}
<div>
<span class="h6 mb-1 anchor_link rightmenu_link_idx{{forloop.counter}}">
<a href="#{{list.id}}"> <i class="fas fa-arrow-circle-right fa-xs ml-3"></i> {{list.sub_title}}</a>
</span>
</div>
{% endfor %}
<hr class="d-none d-md-block">
<span class="ml-3"><i class="fas fa-pen"></i> <a href="#commentForm">comment 바로가기</a></span>
<hr class="d-none d-md-block">
{% if pageInfo.get_next %}
<div>
<span class="h6 mb-1 font-weight-bold">
<a href="{{pageInfo.get_next.get_absolute_url}}">{{pageInfo.get_next}}</a>
</span>
</div>
{% endif %}
</div><!--<div class="col mr-2">-->
</div>
</div>
</div>
<!-- pythonblog_content_top -->
{% include 'base/adsense/ad_right.html' %}
</div>
</div>
</div>
<!-- END Row -->
{% endblock %}