본문 바로가기

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 %}
현재글 : HTML5 Game Tutorial - phaser
Comments
Login:

Copyright © PythonBlog 2021 - 2022 All rights reserved
Mail : PYTHONBLOG