🤔 Para Refletir : "Números são apenas números, não estrague seu dia por causa deles." - Ricky O Bardo

Recriando Jogos Antigos no Game Maker Studio 1.4

Fala pessoal! Nosso projeto de Remake da 1ª Dungeon de "The Legend of Zelda" está quaaaaaaaase pronto! Eu diria que se uma pessoa passasse pela sala sem prestar muita atenção e o gameplay abaixo estivesse rolando na TV, ela poderia até confundir com o jogo original:

O que foi incluso neste último update?
*Inimigo: Keese;
*Inimigo: Gel;
*Inimigo: Goriya;
*Item: Rúpia (Coletável com 5 unidades);
*Item: Recarga de Coração;
*Item: Chave Pequena;
*Item: Bumerangue;
*Item: Bomba;
*Item: Bússola;
*Item: Mapa;
*Portas Trancadas;
*Paredes Quebráveis (com bomba);
*NPC: Velho Sábio;
*Suporte para controle de XBOX.

O que falta ser feito?
*Inimigo: Wallmaster;
*Inimigo: Aquamentus;
*Item: Arco;
*Item: Fragmento da Triforce.

Itens que iam ser feitos, mas decidi descartá-los, pois acredito que fogem do escopo deste projeto:
*Fada;
*Relógio.

Códigos utilizados:

obj_player_start.png
OBJETO "PLAYER START" (obj_player_start)

Código:
///CREATE
//*******************************************
vspeed=-1.5;
image_speed=0.1;
alarm[0]=30;
//___________________________________________

///ALARM0
//*******************************************
instance_create(x,y,obj_player);
instance_destroy();
//___________________________________________

///DRAW
//*******************************************
//desenhar a si mesmo
draw_self();

//desenhar HUD
draw_set_font(FONT);

draw_set_color(c_black);
draw_rectangle(view_xview,view_yview,view_xview+256,view_yview+64,false);
draw_sprite(spr_HUD,0,view_xview,view_yview);
draw_set_color(c_white);

for(i=0;i<global.player_max_HP/2;i++)//corações (valor máximo de HP)
   draw_sprite(spr_empty_heart,0,view_xview+176+(i*8),view_yview+48);

for(i=0;i<global.player_HP;i++)//corações (exibir HP atual)
{
    if i mod 2==0
        SPR=spr_heart_A;
    else
        SPR=spr_heart_B;
    draw_sprite(SPR,0,view_xview+176+(i*4),view_yview+48);
}

//exibir quantidade de chaves
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39,"X"+string(global.keys));

//exibir quantidade de rúpias
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39-16,"X"+string(global.rupies));

//exibir quantidade de bombas
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39+9,"X"+string(global.bombs));

//exibir slot equipado do inventário
draw_set_color(c_white);

if  global.inv_slot_selected!=1
    draw_text(view_xview+128,view_yview+48,string(global.inv_slot_selected));
else
    draw_text(view_xview+129,view_yview+48,string(global.inv_slot_selected));

if global.inv_slot[global.inv_slot_selected]=="boomerang"
    draw_sprite(spr_boomerang_atk,0,view_xview+124,view_yview+32);

if global.inv_slot[global.inv_slot_selected]=="bomb"
    draw_sprite(spr_bomb_item,0,view_xview+124,view_yview+32);
//___________________________________________

obj_player.png
OBJETO "PLAYER" (obj_player)
Código:
///CREATE
//*******************************************
ATK=false;
CAN_ATK=true;
CAN_USE_ITEM=true;
WALK_SPD=0;
WALK_MAX_SPD=1.5;
kb_dir=-1;
instance_create(x,y,obj_HUD);
global.player_HP=6;
global.player_max_HP=6;
global.keys=0;
global.rupies=0;
global.rupies_buffer=0;
global.bombs=0;
image_speed=0;
hurt=false;
X_SPD=0;
Y_SPD=0;
can_walk=true;
alarm[1]=7;
//___________________________________________

///ALARM0
//*******************************************
visible=true;
hurt=false;
//___________________________________________

///ALARM1
//*******************************************
if hurt==true
{
    if visible==false
        visible=true;
    else
        visible=false;
}
alarm[1]=7;
//___________________________________________

///ALARM2
//*******************************************
X_SPD=0;
Y_SPD=0;
can_walk=true;
gamepad_set_vibration(0,0,0);
//___________________________________________

///ALARM3
//*******************************************
//disparar projétil quando estiver com HP cheio
if sprite_index==spr_player_atk_up
{
    if global.player_HP==global.player_max_HP
    {
        if !instance_exists(obj_sword_proj)
        and !instance_exists(obj_sword_proj_part)
        {
            PROJ=instance_create(x+6,y+8,obj_sword_proj);
            PROJ.direction=90;
        }
    }
}
if sprite_index==spr_player_atk_down
{
    if global.player_HP==global.player_max_HP
    {
        if !instance_exists(obj_sword_proj)
        {
            PROJ=instance_create(x+9,y+8,obj_sword_proj);
            PROJ.direction=270;
        }
    }
}
if sprite_index==spr_player_atk_left
{
    if global.player_HP==global.player_max_HP
    {
        if !instance_exists(obj_sword_proj)
        {
            PROJ=instance_create(x+8,y+10,obj_sword_proj);
            PROJ.direction=180;
        }
    }
}
if sprite_index==spr_player_atk_right
{
    if global.player_HP==global.player_max_HP
    {
        if !instance_exists(obj_sword_proj)
        {
            PROJ=instance_create(x+8,y+9,obj_sword_proj);
            PROJ.direction=0;
        }
    }
}
//___________________________________________

///ALARM4
//*******************************************
audio_resume_sound(bg_theme);
//___________________________________________

///ALARM5
//*******************************************
if global.rupies_buffer>0
{
    audio_stop_sound(sfx_rupy);
    audio_play_sound(sfx_rupy,3,false);
    global.rupies_buffer-=1;
    global.rupies+=1;
    alarm[5]=3;
}
//___________________________________________

///ALARM6
//*******************************************
BOOMERANG=false;
//___________________________________________

///STEP
//*******************************************
//movimento e colisão com paredes
if can_walk==true
{
    if !instance_exists(obj_intro)
    {
        if ATK==false
        {
            if BT_UP
            {
                if WALK_SPD<WALK_MAX_SPD
                    WALK_SPD+=0.25;        
                sprite_index=spr_player_up;
                image_speed=0.1;
                if !place_meeting(x,y-2,obj_collision)
                    y-=WALK_SPD;
            }
            else if BT_DOWN
            {
                if WALK_SPD<WALK_MAX_SPD
                    WALK_SPD+=0.25;    
                sprite_index=spr_player_down;
                image_speed=0.1;
                if !place_meeting(x,y+2,obj_collision)
                    y+=WALK_SPD;
            }
            else if BT_LEFT
            {
                if WALK_SPD<WALK_MAX_SPD
                    WALK_SPD+=0.25;    
                sprite_index=spr_player_left;
                image_speed=0.1;

                if !place_meeting(x-2,y,obj_collision)
                    x-=WALK_SPD;
            }
            else if BT_RIGHT
            {
                if WALK_SPD<WALK_MAX_SPD
                    WALK_SPD+=0.25;    
                sprite_index=spr_player_right;
                image_speed=0.1;

                if !place_meeting(x+2,y,obj_collision)
                    x+=WALK_SPD;
            }
            else if ATK==false
            {
                image_speed=0;
                WALK_SPD=0;
            }
        }
    }
}
//alternar tela cheia
if keyboard_check_pressed(ord("F"))
{
    if !window_get_fullscreen()
    {
        window_set_fullscreen(true);
        window_set_cursor(cr_none);
    }
    else
    {
        window_set_fullscreen(false);
        window_set_cursor(cr_arrow);
    }
}
else if can_walk==false
{
    x=xprevious;
    y=yprevious;
}
//colisão com inimigos
if place_meeting(x,y,obj_enemy)
{
    if hurt==false
    {
        gamepad_set_vibration(0,1,1);
        can_walk=false;
        image_speed=0;
        audio_play_sound(sfx_player_dmg,3,false);
        global.player_HP-=1;
        ENEMY=instance_nearest(x,y,obj_enemy);
        kb_dir=round((point_direction(x,y,ENEMY.x,ENEMY.y)/90))*90;
        if kb_dir==0
        or kb_dir==360
            X_SPD=-2;
        if kb_dir==180
            X_SPD=2;
        if kb_dir==90
            Y_SPD=2;
        if kb_dir==270
            Y_SPD=-2;    
        alarm[0]=room_speed;
        alarm[1]=7;
        alarm[2]=7;    
        hurt=true;
    }
}
if place_meeting(x,y,obj_goriya)
{
    if hurt==false
    {
        gamepad_set_vibration(0,1,1);
        can_walk=false;
        image_speed=0;
        audio_play_sound(sfx_player_dmg,3,false);
        global.player_HP-=2;
        ENEMY=instance_nearest(x,y,obj_enemy);
        kb_dir=round((point_direction(x,y,ENEMY.x,ENEMY.y)/90))*90;
        if kb_dir==0
        or kb_dir==360
            X_SPD=-2;
        if kb_dir==180
            X_SPD=2;
        if kb_dir==90
            Y_SPD=2;
        if kb_dir==270
            Y_SPD=-2;    
        alarm[0]=room_speed;
        alarm[1]=7;
        alarm[2]=7;    
        hurt=true;
    }
}
if !place_meeting(x+X_SPD,y,obj_collision)
    x+=X_SPD;
if !place_meeting(x,y+Y_SPD,obj_collision)
    y+=Y_SPD;
//som de baixo HP
if global.player_HP<=2
{
    if !audio_is_playing(sfx_low_health)
        audio_play_sound(sfx_low_health,3,true);
}
else
    audio_stop_sound(sfx_low_health);
//atacar
if BT_A_pressed
{
    if ATK==false
    and CAN_ATK==true
    and !instance_exists(obj_p_boomerang)
    {
        alarm[3]=15;
        X1=x div 256*256;
        Y1=y div 176*176;
        X2=X1+256;
        Y2=Y1+176;
        if collision_rectangle(X1,Y1,X2,Y2,obj_NPC_message,false,false)
        {
            if obj_NPC_message.txt==obj_NPC_message.TXT
                audio_play_sound(sfx_sword,3,false);
        }
        else
            audio_play_sound(sfx_sword,3,false);
        ATK=true;
    }
}
if ATK==true
{
    image_speed=0.18;
    if sprite_index==spr_player_up
    {
        sprite_index=spr_player_atk_up;
        instance_create(x,y,obj_atk_mask_up);
    }
    if sprite_index==spr_player_down
    {
        sprite_index=spr_player_atk_down;
        instance_create(x,y,obj_atk_mask_down);
    }
    if sprite_index==spr_player_left
    {
        sprite_index=spr_player_atk_left;
        instance_create(x,y,obj_atk_mask_left);
    }
    if sprite_index==spr_player_right
    {
        sprite_index=spr_player_atk_right;
        instance_create(x,y,obj_atk_mask_right);
    }
}
//usar item equipado
if BT_B_pressed
and CAN_USE_ITEM==true
{
    if ATK==false
    {
        if global.inv_slot[global.inv_slot_selected]=="boomerang"
        and !instance_exists(obj_p_boomerang)
        {
            alarm[6]=15;
            boomerang=instance_create(x,y,obj_p_boomerang);
            boomerang.FATHER=id;
            if sprite_index==spr_player_left
                boomerang.direction=180;
            if sprite_index==spr_player_right
                boomerang.direction=0;
            if sprite_index==spr_player_up
                boomerang.direction=90;
            if sprite_index==spr_player_down
                boomerang.direction=270;
            boomerang.speed=4;
        }
        if global.inv_slot[global.inv_slot_selected]=="bomb"
        and !instance_exists(obj_bomb)
        and global.bombs>0
        {
            global.bombs-=1;
            if sprite_index==spr_player_left
                instance_create(x-8,y,obj_bomb);
            if sprite_index==spr_player_right
                instance_create(x+8,y,obj_bomb);
            if sprite_index==spr_player_up
                instance_create(x,y-8,obj_bomb);
            if sprite_index==spr_player_down
                instance_create(x,y+8,obj_bomb);
        }
    }
}
//morrer
if global.player_HP<=0
{
    audio_stop_sound(sfx_low_health);
    DEATH=instance_create(x,y,obj_player_death_A);
    DEATH.sprite_index=sprite_index;
    DEATH.image_index=image_index;
    instance_destroy();
}
//___________________________________________

///END STEP
//*******************************************
//câmera
if round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    X1=xstart div 256*256;
    Y1=ystart div 176*176;
    X2=X1+256;
    Y2=Y1+176;
 
    if collision_rectangle(X1,Y1,X2,Y2,obj_NPC_message,false,false)
    {
        if obj_NPC_message.txt!=obj_NPC_message.TXT
        {
            can_walk=false;
            CAN_ATK=false;
            sprite_index=spr_player_left;
        }
        else
        {
            can_walk=true;
            CAN_ATK=true;  
        }
    }
    else
    {
        can_walk=true;
        CAN_ATK=true;
    }
}
else
{
    if ATK==true
        ATK=false;
  
    image_speed=0.1;
    if sprite_index==spr_player_left
        x-=0.42;  
    if sprite_index==spr_player_right
        x+=0.2;
    if sprite_index==spr_player_up
        y-=0.42;  
    if sprite_index==spr_player_down
        y+=0.2;          
  
    can_walk=false;
    CAN_ATK=false;
}

view_xview=lerp(view_xview,obj_player.x div 256*256,0.08);  
view_yview=lerp(view_yview,obj_player.y div 176*176 - 64,0.08);

//desativar teclado ao perder o foco da janela
if !window_has_focus()
    io_clear();
 
//limite de rúpias
clamp(global.rupies,0,255);
//___________________________________________

///ANIMATION END
*********************************************
if ATK==true
    ATK=false;
 
if sprite_index==spr_player_atk_up
    sprite_index=spr_player_up;
if sprite_index==spr_player_atk_down
    sprite_index=spr_player_down;  
if sprite_index==spr_player_atk_left
    sprite_index=spr_player_left;  
if sprite_index==spr_player_atk_right
    sprite_index=spr_player_right;
//___________________________________________

obj_HUD.png
OBJETO "HUD" (obj_HUD)
Código:
///CREATE
//*******************************************
globalvar FONT;
FONT=font_add_sprite_ext(spr_font,"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ,'.?!&-",true,1);
SPR=-1;
IMG=0;
alarm[0]=15;
//___________________________________________

///ALARM0
//*******************************************
if IMG==1
    IMG=0;
else
    IMG=1;
alarm[0]=15;
//___________________________________________

///DRAW
//*******************************************
//desenhar HUD
draw_set_font(FONT);

draw_set_color(c_black);
draw_rectangle(view_xview,view_yview,view_xview+256,view_yview+64,false);
draw_sprite(spr_HUD,0,view_xview,view_yview);
draw_set_color(c_white);

draw_text(view_xview+16,view_yview+4+8,"LEVEL-1");
 
for(i=0;i<global.player_max_HP/2;i++)//corações (valor máximo de HP)
    draw_sprite(spr_empty_heart,0,view_xview+176+(i*8),view_yview+48);

for(i=0;i<global.player_HP;i++)//corações (exibir HP atual)
{
    if i mod 2==0
        SPR=spr_heart_A;
    else
        SPR=spr_heart_B;

    draw_sprite(SPR,0,view_xview+176+(i*4),view_yview+48);
}

//exibir quantidade de chaves
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39,"X"+string(global.keys));

//exibir quantidade de rúpias
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39-16,"X"+string(global.rupies));

//exibir quantidade de bombas
draw_set_color(c_white);
draw_text(view_xview+96,view_yview+39+9,"X"+string(global.bombs));

//exibir mapa
if global.MAP==true
    draw_sprite(spr_map,0,view_xview+24,view_yview+24+8);

if global.COMP==true
    draw_sprite(spr_map_tri_pos,IMG,view_xview+24+(5*8),view_yview+24+8+(4));

if instance_exists(obj_player)
{
    XX=obj_player.x div 256;
    YY=obj_player.y div 176;
}
draw_sprite(spr_map_player_pos,0,view_xview+24+(XX*8),view_yview+24+8+(YY*4));

//exibir slot equipado do inventário
draw_set_color(c_white);
if  global.inv_slot_selected!=1
    draw_text(view_xview+128,view_yview+48,string(global.inv_slot_selected));
else
    draw_text(view_xview+129,view_yview+48,string(global.inv_slot_selected));
 
if global.inv_slot[global.inv_slot_selected]=="boomerang"
    draw_sprite(spr_boomerang_atk,0,view_xview+124,view_yview+32);
if global.inv_slot[global.inv_slot_selected]=="bomb"
    draw_sprite(spr_bomb_item,0,view_xview+124,view_yview+32);
//___________________________________________

obj_keese.png
OBJETO "KEESE" (obj_keese)
Código:
///CREATE
//*******************************************
image_speed=0;
image_index=irandom_range(0,1);
HP=0.5;
hurt=false;
DIR_PLUS=choose(true,false);
direction=irandom(360);
SPD=0;
max_speed=1;
min_speed=0;
direction=0;
MOVE=false;
alarm[0]=room_speed;
alarm[1]=room_speed*2;
//___________________________________________

///ALARM0
//*******************************************
if MOVE==false
{
    MOVE=true;
    direction=irandom(360);
}
else
    MOVE=false;
if x>view_xview
and x<view_xview+256
and y>view_yview+64
and y<view_yview+240 //checar se está dentro da tela
{
    if MOVE==true
        alarm[0]=room_speed*10;
    else
        alarm[0]=room_speed*5;
}
else
    alarm[0]=room_speed;
//___________________________________________

///ALARM1
//*******************************************
if DIR_PLUS==true
    DIR_PLUS=false;
else
    DIR_PLUS=true;
alarm[1]=room_speed*2;
//___________________________________________

///STEP
//*******************************************
if x>view_xview
and x<view_xview+256
and y>view_yview+64
and y<view_yview+240 //checar se está dentro da tela
{
    if round(view_xview)==round(view_xview/256)*256
    and round(view_yview+64)==round(view_yview/176)*176
    //checar se a câmera está alinhada com a sala
    {
        if MOVE==true
            if SPD<max_speed
                SPD+=0.025;
        if MOVE==false
            if SPD>min_speed
                SPD-=0.025;        
        image_speed=lerp(image_speed,SPD/2,0.05);      
        if image_speed>0.25
            image_speed=0.25;
        if DIR_PLUS==true
            direction+=SPD;
        else
            direction-=SPD;
        if x-8<view_xview
        {
            x=xprevious;
            direction=choose(45,0,315);
        }
        if x+8>view_xview+256
        {
            x=xprevious;
            direction=choose(135,180,225);
        }
        if y+8>view_yview+240
        {
            y=yprevious;
            direction=choose(45,90,135);
        }
        if y-8<view_yview+64
        {
            y=yprevious;
            direction=choose(315,270,135);
        }
        x+=lengthdir_x(SPD,direction);
        y+=lengthdir_y(SPD,direction);
    }
    else
    {
        instance_create(xstart,ystart,obj_keese);
        instance_destroy();
    }
}
else
{
    SPD=0;
    x=xstart;
    y=ystart;
    image_index=0;
    image_speed=0;
    alarm[0]=room_speed;
}
//receber dano
if collision_rectangle(x-8,y-8,x+8,y+8,obj_atk_mask_parent,false,false)
and round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    audio_stop_sound(sfx_enemy_dmg);
    audio_play_sound(sfx_enemy_dmg,3,false);
 
    image_speed=0;
    HP-=1;
 
    DMG=instance_nearest(x,y,obj_atk_mask_parent);
    with DMG
        instance_destroy();
}

if collision_rectangle(x-8,y-8,x+8,y+8,obj_sword_proj,false,false)
and !collision_rectangle(x-8,y-8,x+8,y+8,obj_atk_mask_parent,false,false)
and round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    audio_stop_sound(sfx_enemy_dmg);
    audio_play_sound(sfx_enemy_dmg,3,false);
 
    image_speed=0;
    HP-=1;
 
    DMG=instance_nearest(x,y,obj_sword_proj);
 
    TL=instance_create(x,y,obj_sword_proj_part);
    TR=instance_create(x,y,obj_sword_proj_part);
    BL=instance_create(x,y,obj_sword_proj_part);
    BR=instance_create(x,y,obj_sword_proj_part);    
 
    TL.sprite_index=spr_sword_proj_part_TL;
    TR.sprite_index=spr_sword_proj_part_TR;
    BL.sprite_index=spr_sword_proj_part_BL;
    BR.sprite_index=spr_sword_proj_part_BR;
 
    TL.direction=135;
    TR.direction=45;
    BL.direction=225;
    BR.direction=315;
 
    with DMG
        instance_destroy();
}

//morrer
if HP<=0
{
    instance_create(x-8,y-8,obj_enemy_death);
 
    if global.player_HP<global.player_max_HP
    {
        RAND=irandom_range(1,5);
        if RAND==1
            instance_create(x,y,obj_heart_refill);
    }
    else
    {
        RAND=irandom_range(1,10);
        if RAND==1
            instance_create(x-8,y-8,obj_5_rupies);
    }
    instance_destroy();
}
//___________________________________________

///END STEP
//*******************************************
if !(x>view_xview
and x<view_xview+256
and y>view_yview+64
and y<view_yview+240) //checar se está fora da tela
    alarm[0]=room_speed;
//___________________________________________

///COLLISION WITH "obj_p_boomerand"
//*******************************************
audio_stop_sound(sfx_enemy_dmg);
audio_play_sound(sfx_enemy_dmg,3,false);
HP-=1;
other.speed=-4;
//___________________________________________

///COLLISION WITH "obj_explosion"
//*******************************************
audio_stop_sound(sfx_enemy_dmg);
audio_play_sound(sfx_enemy_dmg,3,false);
HP=0;
//___________________________________________

///OUTSIDE VIEW 0
//*******************************************
instance_create(xstart,ystart,obj_keese);
instance_destroy();
//___________________________________________

///DRAW
//*******************************************
if round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
    draw_sprite(sprite_index,image_index,x,y);
else
{
    draw_sprite_ext(spr_enemy_spawn,0,x-8,y-8,1,1,0,c_white,random_range(0,1));
    alarm[0]=room_speed;
}
//___________________________________________

sprite0.png
OBJETO "CONTROLES" (obj_controls)
Código:
///CREATE
//*******************************************
globalvar BT_UP, BT_DOWN, BT_LEFT, BT_RIGHT, BT_A_pressed, BT_B_pressed, SWITCH_pressed;
BT_UP=false;
BT_DOWN=false;
BT_LEFT=false;
BT_RIGHT=false;
BT_A_pressed=false;
BT_B_pressed=false;
SWITCH_pressed=false;
global.can_SWITCH=true;
//___________________________________________

///STEP
//*******************************************
if keyboard_check(vk_up)
or keyboard_check(ord("W"))
or gamepad_button_check(0,gp_padu)
    BT_UP=true;
else
    BT_UP=false;

if keyboard_check(vk_down)
or keyboard_check(ord("S"))
or gamepad_button_check(0,gp_padd)
    BT_DOWN=true;
else
    BT_DOWN=false;
 
if keyboard_check(vk_left)
or keyboard_check(ord("A"))
or gamepad_button_check(0,gp_padl)
    BT_LEFT=true;
else
    BT_LEFT=false;
if keyboard_check(vk_right)
or keyboard_check(ord("D"))
or gamepad_button_check(0,gp_padr)
    BT_RIGHT=true;
else
    BT_RIGHT=false;
if keyboard_check_pressed(ord("X"))
or keyboard_check_pressed(ord("L"))
or gamepad_button_check_pressed(0,gp_face2)
    BT_A_pressed=true;
else
    BT_A_pressed=false;
if keyboard_check_pressed(ord("Z"))
or keyboard_check_pressed(ord("K"))
or gamepad_button_check_pressed(0,gp_face1)
    BT_B_pressed=true;
else
    BT_B_pressed=false;
 
if keyboard_check_pressed(vk_tab)
or keyboard_check_pressed(vk_lshift)
or gamepad_button_check_pressed(0,gp_shoulderr)
    SWITCH_pressed=true;
else
    SWITCH_pressed=false;
//trocar slot equipado do inventário
if SWITCH_pressed
and global.can_SWITCH==true
{
    audio_play_sound(sfx_text,3,false);
    if global.inv_slot_selected==1
        global.inv_slot_selected=2;
    else if global.inv_slot_selected==2
        global.inv_slot_selected=3;
    else if global.inv_slot_selected==3
        global.inv_slot_selected=1;
//___________________________________________

Obs.: maioria dos inimigos compartilham o mesmo algoritmo, com leves alterações. Por conta disso, apenas o código fonte do inimigo "keese" (morcego) foi compartilhado, pois é o que mais difere dos demais.

Por enquanto é só isso... Até a próxima atualização, que provavelmente já incluirá o jogo pronto com link para download do executável ^^
 
Última edição:
Fala pessoal!
O projeto de Remake da primeira Dungeon de "The Legend of Zelda" está CONCLUÍDO!
Vejam abaixo o resultado:

Inspirado no que @Rafael_Sol_MAKER disse, decidi fazer algo meio diferente, um Remake com um "spin"... Uma surpresinha para vocês! Eu também inlcuí uma versão "fofa", que é basicamente o mesmo jogo, mas com os gráficos mais bonitinhos, tirados do "Link's Awakening" e "Oracle of Seasons/Ages", do Gameboy Color:

CÓDIGOS USADOS:

AQUAMENTUS.png
OBJETO AQUAMENTUS (obj_aquamentus):
Código:
///CREATE
//---------------------------------------------------
ATK=false;
hurt=false;
HP=6;

speed=0.2;

image_speed=0.1;

direction=choose(90,270,180,0);
alarm[0]=room_speed*(irandom_range(2,4));
alarm[1]=room_speed*3;
//____________________________________
///ALARM0
//---------------------------------------------------
if instance_exists(obj_player)
{
    player_DIR=point_direction(x,y,obj_player.x,obj_player.y) div 90*90;
    direction=choose(90,270,180,0,player_DIR);
}
else
    direction=choose(90,270,180,0);
 
alarm[0]=room_speed*(irandom_range(2,4));
//____________________________________

///ALARM1
//---------------------------------------------------
if instance_exists(obj_player)
{
    if obj_player.x<x
    {
        AUDIO=choose(sfx_roar_01,sfx_roar_02);
        audio_play_sound(AUDIO,3,false);
        audio_play_sound(sfx_fire_ball,3,false);              
        ATK=true;
        instance_create(x-12,y-16,obj_fire_ball);
        instance_create(x-12,y-16,obj_fire_ball_U);
        instance_create(x-12,y-16,obj_fire_ball_D);
    }
}
alarm[1]=room_speed*5;
alarm[2]=room_speed;
//____________________________________

///ALARM2
//---------------------------------------------------
ATK=false;
//____________________________________

///ALARM3
//---------------------------------------------------
hurt=false;
image_speed=0.1;
//____________________________________

///STEP
//---------------------------------------------------
//colisão
if place_meeting(x+1,y,obj_collision)
or place_meeting(x-1,y,obj_collision)
or place_meeting(x,y+1,obj_collision)
or place_meeting(x,y-1,obj_collision)
    direction=choose(90,270,180,0);

//atacar
if ATK==true
    sprite_index=spr_aquamentus_atk;
else
    sprite_index=spr_aquamentus_idle;
 
//receber dano
if place_meeting(x,y,obj_atk_mask_parent)
and round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    if hurt==false
    {
        audio_stop_sound(sfx_roar_01);
        audio_stop_sound(sfx_roar_02);
        audio_stop_sound(sfx_roar_03);
        audio_play_sound(sfx_roar_03,3,false);
 
        audio_stop_sound(sfx_enemy_dmg);
        audio_play_sound(sfx_enemy_dmg,3,false);
 
        image_speed=0;
        HP-=1;
 
        DMG=instance_nearest(x,y,obj_atk_mask_parent);
        with DMG
            instance_destroy();
 
        alarm[3]=10;
        hurt=true;
    }
}

if place_meeting(x,y,obj_sword_proj)
and !place_meeting(x,y,obj_atk_mask_parent)
and round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    if hurt==false
    {
        audio_stop_sound(sfx_roar_01);
        audio_stop_sound(sfx_roar_02);
        audio_stop_sound(sfx_roar_03);
        audio_play_sound(sfx_roar_03,3,false);
 
        audio_stop_sound(sfx_enemy_dmg);
        audio_play_sound(sfx_enemy_dmg,3,false);
 
        image_speed=0;
        HP-=1;
 
        DMG=instance_nearest(x,y,obj_sword_proj);
 
        TL=instance_create(x,y,obj_sword_proj_part);
        TR=instance_create(x,y,obj_sword_proj_part);
        BL=instance_create(x,y,obj_sword_proj_part);
        BR=instance_create(x,y,obj_sword_proj_part);  
   
        TL.sprite_index=spr_sword_proj_part_TL;
        TR.sprite_index=spr_sword_proj_part_TR;
        BL.sprite_index=spr_sword_proj_part_BL;
        BR.sprite_index=spr_sword_proj_part_BR;
 
        TL.direction=135;
        TR.direction=45;
        BL.direction=225;
        BR.direction=315;
 
        with DMG
            instance_destroy();
 
        alarm[3]=10;
        hurt=true;
    }
}

if place_meeting(x,y,obj_arrow_ATK)
and round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176
//checar se a câmera está alinhada com a sala
{
    if hurt==false
    {
        audio_stop_sound(sfx_roar_01);
        audio_stop_sound(sfx_roar_02);
        audio_stop_sound(sfx_roar_03);
        audio_play_sound(sfx_roar_03,3,false);
 
        audio_stop_sound(sfx_enemy_dmg);
        audio_play_sound(sfx_enemy_dmg,3,false);
 
        image_speed=0;
        HP-=2;
 
        DMG=instance_nearest(x,y,obj_arrow_ATK);
        with DMG
            instance_destroy();
       
        alarm[3]=10;
        hurt=true;
    }
}
 
//morrer
if HP<=0
{
    with obj_aquamentus_wall
        instance_destroy();
 
    global.boss_defeated=true;
 
    instance_create(x-8,y-8,obj_enemy_death);
    instance_create(1184+8,256+8,obj_heart_container);
    instance_destroy();
}
//____________________________________

///END STEP
//---------------------------------------------------
while place_meeting(x+1,y,obj_collision)
{
    x-=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x-1,y,obj_collision)
{
    x+=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x,y+1,obj_collision)
{
    y-=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x,y-1,obj_collision)
{
    y+=0.1;
    direction=choose(90,270,180,0);
}


while place_meeting(x+1,y,obj_mob_collision)
{
    x-=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x-1,y,obj_mob_collision)
{
    x+=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x,y+1,obj_mob_collision)
{
    y-=0.1;
    direction=choose(90,270,180,0);
}
while place_meeting(x,y-1,obj_mob_collision)
{
    y+=0.1;
    direction=choose(90,270,180,0);
}
//____________________________________

///COLLISION WITH "obj_p_boomerang"
//---------------------------------------------------
audio_stop_sound(sfx_boomerang_hit);
audio_play_sound(sfx_boomerang_hit,3,false);
other.speed=-4;
//____________________________________

///COLLISION WITH "obj_explosion"
//---------------------------------------------------
if hurt==false
and other.image_index==0
{
    audio_stop_sound(sfx_roar_01);
    audio_stop_sound(sfx_roar_02);
    audio_stop_sound(sfx_roar_03);
    audio_play_sound(sfx_roar_03,3,false);
 
    audio_stop_sound(sfx_enemy_dmg);
    audio_play_sound(sfx_enemy_dmg,3,false);
    HP-=4;
 
    alarm[3]=10;
    hurt=true;
}
//____________________________________

///OUTSIVE VIEW 0
//---------------------------------------------------
instance_create(x,y,obj_aquamentus);
instance_destroy();
//____________________________________

///DRAW
//---------------------------------------------------
if hurt==false
    draw_self();
else
    draw_sprite(spr_aquamentus_dmg,image_index,x,y);
//____________________________________


WALLMASTER.png
OBJETO WALLMASTER (obj_wallmaster):
Código:
///CREATE
//---------------------------------------------------
frozen=false;
HP=2;
hurt=false;
next_x=0;
next_y=0;
HSPD=0;
VSPD=0;
X_SPD=0;
Y_SPD=0;
image_speed=0.1;
vspeed=-0.25;
alarm[0]=64;

// Define a grade de caminho
GRID = mp_grid_create(view_xview, view_yview, 256 div 16 - 1, 240 div 16 - 1, 16, 16);
PATH=path_add();

// Configura a grade com as colisões
mp_grid_add_instances(GRID, obj_collision, true);
//____________________________________
///ALARM0
//---------------------------------------------------
vspeed=0;
//____________________________________
///ALARM1
//---------------------------------------------------
hurt=false;
X_SPD=0;
Y_SPD=0;
//____________________________________

///ALARM2
//---------------------------------------------------
frozen=false;
//____________________________________
///STEP
//---------------------------------------------------
if !place_meeting(x,y,obj_wallmaster)
    image_alpha=1;

if vspeed==0
and instance_exists(obj_player)
{
    if mp_grid_path(GRID, PATH, x, y, obj_player_target.x div 16*16 +8, obj_player_target.y div 16*16 +8, false)
    {
        mp_potential_step(obj_player_target.x,obj_player_target.y,0,false);
 
        next_x = path_get_point_x(PATH, 1);
        next_y = path_get_point_y(PATH, 1);
 
        DIR=point_direction(x,y,next_x,next_y) div 90*90;
 
        if  x==(x div 16*16)+8
        and y==(y div 16*16)+8
        {
            if DIR==0
            or DIR==360
            {
                HSPD=0.25;
                VSPD=0;
            }
            else
            if DIR==90
            {
                VSPD=-0.25;
                HSPD=0;
            }
            else
            if DIR==180
            {
                HSPD=-0.25;
                VSPD=0;
            }
            else
            if DIR==270
            {
                VSPD=0.25;
                HSPD=0;
            }
        }
        else
        {
            if HSPD==0
                x=x div 16*16 +8;
            if VSPD==0
                y=y div 16*16 +8;
        }
    }
}

if frozen
    image_speed=0;
else
    image_speed=0.1;

if !hurt
and !frozen
{
    x+=HSPD;
    y+=VSPD;
}

//receber dano
if vspeed==0
{
    if collision_rectangle(x-8,y-8,x+8,y+8,obj_atk_mask_parent,false,false)
    and round(view_xview)==round(view_xview/256)*256
    and round(view_yview+64)==round(view_yview/176)*176
    //checar se a câmera está alinhada com a sala
    {
        if hurt==false
        {
            audio_stop_sound(sfx_enemy_dmg);
            audio_play_sound(sfx_enemy_dmg,3,false);
 
            HP-=1;
 
            DMG=instance_nearest(x,y,obj_player_target);
            kb_dir=round((point_direction(x,y,DMG.x,DMG.y)/90))*90;
            with obj_atk_mask_parent
                instance_destroy();
 
            if kb_dir==0
            or kb_dir==360
                X_SPD=-3;
            if kb_dir==180
                X_SPD=3;
            if kb_dir==90
                Y_SPD=3;
            if kb_dir==270
                Y_SPD=-3;
     
            alarm[1]=10;
            hurt=true;
        }
    }
 
    if collision_rectangle(x-8,y-8,x+8,y+8,obj_sword_proj,false,false)
    and !collision_rectangle(x-8,y-8,x+8,y+8,obj_atk_mask_parent,false,false)
    and round(view_xview)==round(view_xview/256)*256
    and round(view_yview+64)==round(view_yview/176)*176
    //checar se a câmera está alinhada com a sala
    {
        if hurt==false
        {
            audio_stop_sound(sfx_enemy_dmg);
            audio_play_sound(sfx_enemy_dmg,3,false);
 
            HP-=1;
       
            DMG=instance_nearest(x,y,obj_sword_proj);
            kb_dir=round((point_direction(x+8,y+8,DMG.x,DMG.y)/90))*90;
 
            TL=instance_create(x,y,obj_sword_proj_part);
            TR=instance_create(x,y,obj_sword_proj_part);
            BL=instance_create(x,y,obj_sword_proj_part);
            BR=instance_create(x,y,obj_sword_proj_part);  
 
            TL.sprite_index=spr_sword_proj_part_TL;
            TR.sprite_index=spr_sword_proj_part_TR;
            BL.sprite_index=spr_sword_proj_part_BL;
            BR.sprite_index=spr_sword_proj_part_BR;
 
            TL.direction=135;
            TR.direction=45;
            BL.direction=225;
            BR.direction=315;
 
            with DMG
                instance_destroy();
 
            if kb_dir==0
            or kb_dir==360
                X_SPD=-3;
            if kb_dir==180
                X_SPD=3;
            if kb_dir==90
                Y_SPD=3;
            if kb_dir==270
                Y_SPD=-3;
     
            alarm[1]=10;
            hurt=true;
        }
    }
 
    if collision_rectangle(x-8,y-8,x+8,y+8,obj_arrow_ATK,false,false)
    and round(view_xview)==round(view_xview/256)*256
    and round(view_yview+64)==round(view_yview/176)*176
    //checar se a câmera está alinhada com a sala
    {
        if hurt==false
        {
            audio_stop_sound(sfx_enemy_dmg);
            audio_play_sound(sfx_enemy_dmg,3,false);
 
            HP-=2;
       
            DMG=instance_nearest(x,y,obj_arrow_ATK);
            kb_dir=round((point_direction(x+8,y+8,DMG.x,DMG.y)/90))*90;
            with DMG
                instance_destroy();
 
            if kb_dir==0
            or kb_dir==360
                X_SPD=-3;
            if kb_dir==180
                X_SPD=3;
            if kb_dir==90
                Y_SPD=3;
            if kb_dir==270
                Y_SPD=-3;
     
            alarm[1]=10;
            hurt=true;
        }
    }
}

if !place_meeting(x+X_SPD,y,obj_collision)
and !place_meeting(x+X_SPD,y,obj_mob_collision)
    x+=X_SPD;
if !place_meeting(x,y+Y_SPD,obj_collision)
and !place_meeting(x,y+Y_SPD,obj_mob_collision)
    y+=Y_SPD;

//morrer
if HP<=0
{
    instance_create(x-8,y-8,obj_enemy_death);
 
    if global.player_HP<global.player_max_HP
    {
        RAND=irandom_range(1,5);
        if RAND==1
            instance_create(x,y,obj_heart_refill);
    }
    else
    {
        RAND=irandom_range(1,10);
        if RAND==1
            instance_create(x-8,y-8,obj_5_rupies);
    }

    instance_destroy();
}
//____________________________________
///END STEP
//---------------------------------------------------
if !(round(view_xview)==round(view_xview/256)*256
and round(view_yview+64)==round(view_yview/176)*176)
//checar se a câmera NÃO está alinhada com a sala
{
    with obj_wallmaster_spawner
        hp=10;
    instance_destroy();
}

if vspeed==0
{
    while place_meeting(x+1,y,obj_collision)
    {
        HSPD=0;
        VSPD=0;
        x-=0.1;
    }
    while place_meeting(x-1,y,obj_collision)
    {
        HSPD=0;
        VSPD=0;
        x+=0.1;
    }
    while place_meeting(x,y+1,obj_collision)
    {
        HSPD=0;
        VSPD=0;
        y-=0.1;
    }
    while place_meeting(x,y-1,obj_collision)
    {
        HSPD=0;
        VSPD=0;
        y+=0.1;
    }
 
    while place_meeting(x+1,y,obj_mob_collision)
    {
        HSPD=0;
        VSPD=0;
        x-=0.1;
    }
    while place_meeting(x-1,y,obj_mob_collision)
    {
        HSPD=0;
        VSPD=0;
        x+=0.1;
    }
    while place_meeting(x,y+1,obj_mob_collision)
    {
        HSPD=0;
        VSPD=0;
        y-=0.1;
    }
    while place_meeting(x,y-1,obj_mob_collision)
    {
        HSPD=0;
        VSPD=0;
        y+=0.1;
    }
}
//____________________________________

///COLLISION WITH "obj_p_boomerang"
//---------------------------------------------------
if vspeed==0
{
    frozen=true;
    audio_stop_sound(sfx_boomerang_hit);
    audio_play_sound(sfx_boomerang_hit,3,false);
 
    DMG=instance_nearest(x,y,obj_p_boomerang);
    kb_dir=round((point_direction(x,y,DMG.x+8,DMG.y+8)/90))*90;
 
    if other.speed>0
    {
        if kb_dir==0
        or kb_dir==360
        {
            if X_SPD==0
                X_SPD=-3;
        }
        if kb_dir==180
        {
            if X_SPD==0
                X_SPD=3;
        }
        if kb_dir==90
        {
            if Y_SPD==0
                Y_SPD=3;
        }
        if kb_dir==270
        {
            if Y_SPD==0
                Y_SPD=-3;
        }
    }
 
 
    other.speed=-4;
 
    alarm[1]=10;
    alarm[2]=room_speed*5;
}
//____________________________________

///COLLISION WITH "obj_explosion"
//---------------------------------------------------
audio_stop_sound(sfx_enemy_dmg);
audio_play_sound(sfx_enemy_dmg,3,false);
HP=0;
//____________________________________

///OUTSIDE VIEW 0
//---------------------------------------------------
with obj_wallmaster_spawner
    hp=10;

instance_destroy();
//____________________________________

///DRAW
//---------------------------------------------------
if !place_meeting(x,y,obj_collision)
{
    if hurt==false
    {
        if alarm[2]>0
        and alarm[2]<room_speed
            draw_sprite(sprite_index,image_index,x+choose(-0.5,0.5),y);
        else
            draw_self();
    }
    else
        draw_sprite_ext(spr_wallmaster_dmg,image_index,x,y,1,1,0,c_white,1);
}
else
{
    if hurt==false
        draw_sprite_ext(sprite_index,image_index,x,y,1,1,0,c_white,random_range(0,1));
    else
        draw_sprite_ext(spr_wallmaster_dmg,image_index,x,y,1,1,0,c_white,random_range(0,1));
}
//____________________________________

E como prometido, segue o link para download no itch.io - DOWNLOAD

Controles:

Teclado - Layout A:

[], [], [], []: Mover o Player;
[X]: Usar Item do Slot "A" (atacar com a espada);
[Z]: Usar Item do Slot "B";
[TAB]: Trocar Item do Slot "B".

Teclado - Layout B:
[W], [A], [S], [D]: Mover o Player;
[L]: Usar Item do Slot "A" (atacar com a espada);
[K]: Usar Item do Slot "B";
[SHIFT]: Trocar Item do Slot "B".

Controle de XBOX:
[], [], [], []: Mover o Player;
[B]: Usar Item do Slot "A" (atacar com a espada);
[A]: Usar Item do Slot "B";
[RB]: Trocar Item do Slot "B".

Você pode alternar entre "Tela Cheia" e "Janela" pressionando a tecla [F].

(Obs.: Eu sei que usar o botão 'B' para o slot 'A' e o botão 'A' para o slot 'B' parece confuso, mas estou respeitando o posicionamento dos botões do controle de Nintendo, e não o do controle de XBOX)
 
Última edição:
Fala pessoal, tudo bem? Bom, como eu tinha prometido, finalmente dei início ao Projeto de Remake da primeira fase de Wolfenstein 3D. Até o momento já consegui recriar o mapa, ou ao menos todo o posicionamento das paredes e objetos decorativos! Segue exemplo no vídeo abaixo:

Fazer isso não foi tarefa fácil, pois pelo que eu vi, não existe muita documentação online sobre o layout dos diferentes níveis desse jogo. Eu tive que analisar vídeos de gameplay como referência e usar o editor de níveis do jogo original para checar o posicionamento correto de tudo:
Bgoe9D.png

Quem já trabalhou com a Engine do Game Maker deve saber que recriar gráficos "2.5D" (fake 3D) não é nada assim tão complexo, pois temos inúmeras funções que basicamente simulam o mesmo efeito de raycasting utilizado por Wolfenstein 3D. Mas em vários projetos que utilizam essas funções, vejo as pessoas usando o comando "d3d_draw_block()" para desenhar às paredes. Ele funciona, de fato, mas algo que tornava o Wolfenstein 3D tão visualmente impressionante para a época, era o uso de duas texturas praticamente iguais, exceto que uma delas era "levemente" mais escura, para assim simular um sutil efeito de iluminação:
WOLF3D_TEXTURES.png

Para fazer uso deste mesmo efeito, tive que criar meu próprio script para desenhar os blocos com ambas as variantes da mesma textura:
Código:
//scr_draw_block(_texture1,_img1,_texture2,_img2);

_texture1=argument0;
_img1=argument1;
_texture2=argument2;
_img2=argument3;

texture1=sprite_get_texture(_texture1,_img1);
texture2=sprite_get_texture(_texture2,_img2);

if instance_exists(obj_player)
{
    PL_X=obj_player.x;
    PL_Y=obj_player.y;
    LEN_PL_X_LEFT=PL_X+lengthdir_x(32000,obj_player.direction-80);
    LEN_PL_Y_LEFT=PL_Y+lengthdir_y(32000,obj_player.direction-80);
    LEN_PL_X_RIGHT=PL_X+lengthdir_x(32000,obj_player.direction+80);
    LEN_PL_Y_RIGHT=PL_Y+lengthdir_y(32000,obj_player.direction+80);        

    if distance_to_object(obj_player)<=100
    {
        d3d_draw_wall(x+64,y,64,x,y,0,texture1,1,1); //top
        d3d_draw_wall(x,y+64,64,x+64,y+64,0,texture1,1,1); //bottom
        d3d_draw_wall(x,y,64,x,y+64,0,texture2,1,1); //left
        d3d_draw_wall(x+64,y+64,64,x+64,y,0,texture2,1,1); //right
    }
    else if distance_to_object(obj_player)<2000
    {
        if point_in_triangle(x+32,y+32,PL_X,PL_Y,LEN_PL_X_LEFT,LEN_PL_Y_LEFT,LEN_PL_X_RIGHT,LEN_PL_Y_RIGHT)
        {
            d3d_draw_wall(x+64,y,64,x,y,0,texture1,1,1); //top
            d3d_draw_wall(x,y+64,64,x+64,y+64,0,texture1,1,1); //bottom
            d3d_draw_wall(x,y,64,x,y+64,0,texture2,1,1); //left
            d3d_draw_wall(x+64,y+64,64,x+64,y,0,texture2,1,1); //right    
        }
    }
}

A função pede os argumentos com o ID de cada textura utilizada e sua sub-imagem, tornando assim possível desenhar um bloco tridimensional, com diferentes texturas para o eixo vertical e horizontal. O restante do código é apenas uma otimização para checar QUANDO os blocos devem ser desenhados, já que desenhá-los todos ao mesmo tempo causa um grande impacto no desempenho.
Primeiro eu uso o comando distance_to_object() para desenhar qualquer bloco que esteja a pelo menos 100 pixels de distância do jogador, depois uso o point_in_triangle() para checar se as coordenadas dos demais blocos estão dentro do "cone de visão" do jogador. Sendo assim, blocos fora do campo de visão do jogador não serão desenhados.
WOLF3D_FOV.png

É bem possível que eu não termine esse projeto até o ano que vem, levando em conta que terei que me preparar para às festas deste fim de ano... Mas quis deixar registrado aqui meu progresso até o presente momento 😅

Desejo à todos um ótimo Natal e uma excelente virada de ano! Vejo vocês em 2024 ;)
 
Última edição:
Fala pessoal, tudo bem? Como estão? Espero que tenham passado uma ótima virada de ano!

Pois bem, trago algumas atualizações sobre o projeto de remake da primeira fase de Wolfenstein 3D. Agora todos os itens coletáveis já foram posicionados no mapa, além dos guardas nazistas. Assistindo ao vídeo abaixo, já dá pra notar uma boa semelhança com o jogo original:


Eu pensei que programar a inteligência artificial dos guardas seria mais difícil, mas estudando um pouco mais o algoritmo do jogo original, percebi que eles conseguiram realizar muito utilizando pouco! Cada guarda possui 3 estados comportamentais:

1 - Idle (inativo): O guarda permanece parado, apenas observando a área em que está e mantendo o seu posto.
2 - Roam (ronda): O guarda caminha em uma direção aleatória, um valor randômico entre 0 e 360, representando o grau de ângulo.
3 - Seek (busca): O guarda ativamente tenta encontrar a rota de menor custo entre sua posição atual e a posição do jogador.

Após detectar o jogador, o guarda fica alternando entre os estados Roam e Seek.

O difícil mesmo foi desenhar as sprites, alterando elas para a perspectiva da câmera. Cada guarda possui 8 sprites diferentes, baseadas na direção para a qual estão apontando:

GUARD_strip8.png


Para calcular a diferença entre o ângulo de sua direção atual e o ângulo da câmera, o seguinte algoritmo foi usado:
Código:
//CREATE EVENT

SPR_ANGLE[0] = spr_guard_up_idle;
SPR_ANGLE[1] = spr_guard_up_right_idle;
SPR_ANGLE[2] = spr_guard_right_idle;
SPR_ANGLE[3] = spr_guard_down_right_idle;;
SPR_ANGLE[4] = spr_guard_down_idle;
SPR_ANGLE[5] = spr_guard_down_left_idle;
SPR_ANGLE[6] = spr_guard_left_idle;
SPR_ANGLE[7] = spr_guard_up_left_idle;
SPR_ANGLE[8] = spr_guard_up_idle;

DIR = 0;
IMG = 0;
ATK_IMG = 0;
INDEX = 0;
final_angle = 0;
final_angle2 = 0;

//STEP EVENT

if agro==false
{
    IMG=0;

    SPR_ANGLE[0] = spr_guard_up_idle;
    SPR_ANGLE[1] = spr_guard_up_right_idle;
    SPR_ANGLE[2] = spr_guard_right_idle;
    SPR_ANGLE[3] = spr_guard_down_right_idle;;
    SPR_ANGLE[4] = spr_guard_down_idle;
    SPR_ANGLE[5] = spr_guard_down_left_idle;
    SPR_ANGLE[6] = spr_guard_left_idle;
    SPR_ANGLE[7] = spr_guard_up_left_idle;
    SPR_ANGLE[8] = spr_guard_up_idle;
}
else
{
    if IMG<3
        IMG+=0.08;
    else
        IMG=0

    SPR_ANGLE[0] = spr_guard_up_walk;
    SPR_ANGLE[1] = spr_guard_up_right_walk;
    SPR_ANGLE[2] = spr_guard_right_walk;
    SPR_ANGLE[3] = spr_guard_down_right_walk;;
    SPR_ANGLE[4] = spr_guard_down_walk;
    SPR_ANGLE[5] = spr_guard_down_left_walk;
    SPR_ANGLE[6] = spr_guard_left_walk;
    SPR_ANGLE[7] = spr_guard_up_left_walk;
    SPR_ANGLE[8] = spr_guard_up_walk;
}

p_DIR = obj_player.direction;
angle1 = p_DIR - DIR;

final_angle1 = round(angle1 / 45);

if final_angle1 <= 8 && final_angle1 >=0
    final_angle2 = final_angle1;
else if final_angle1 > 8
    final_angle2 = final_angle1 - 8;
else if final_angle1 < 0
    final_angle2 = final_angle1 + 8;

INDEX = final_angle2;

//DRAW EVENT

draw_set_alpha_test(true);

d3d_transform_set_identity();

d3d_transform_add_rotation_x(90);
d3d_transform_add_rotation_z(point_direction(x, y, obj_player.XX, obj_player.YY)+90);
d3d_transform_add_translation(x, y, 22);
if hurt==false
{
    if atk==false
        draw_sprite_ext(SPR_ANGLE[INDEX], round(IMG), 0, 0, 1, 1, 0, c_white, 1);
    else
        draw_sprite_ext(spr_guard_ATK, round(ATK_IMG), 0, 0, 1, 1, 0, c_white, 1);       
}
else
{
    if hp>0
        draw_sprite_ext(spr_guard_hurt, 0, 0, 0, 1, 1, 0, c_white, 1);
    else
        draw_sprite_ext(spr_guard_dying, 0, 0, 0, 1, 1, 0, c_white, 1);   
}
      
d3d_transform_set_identity();

draw_set_alpha_test(false);

Tudo o que falta agora é criar os cães de guarda... admito que não estou muito animado em programar esta parte :cry:
Enquanto combater soldados nazistas num joguinho retrô soa como algo "justificável", não sei se posso dizer o mesmo quanto a atacar um animal que só está fazendo o que foi treinado.

Mas enfim, por enquanto é só isso. Provavelmente já na próxima atualização terei um build jogável e compartilharei aqui o link para download. Até lá ;)
 
Pois bem pessoal, trago aqui meu terceiro e último post sobre o Remake de Wolfenstein 3D. Mas antes de atualizá-los, gostaria de dizer algo. Reparei que não recebi comentários nem reações em meus últimos updates, e não posso negar que faz sentido, levando em conta o assunto em questão. Não estou incomodado com isso, é justificável que os membros aqui do fórum não queiram se envolver neste tópico, e eu respeito isso. Bom, eu finalmente vou tratar do elefante na sala, já que sinto que falhei em apresentar um pouco de contexto para quem não conhece a série Wolfenstein. Os vilões deste jogo são soldados nazistas. O último "chefão" é, literalmente, o próprio Adolf Hitler... apesar do jogo contar uma narrativa fictícia, ela é baseada em eventos muito REAIS, que afetaram drasticamente o curso de nossa história.

O protagonista, B.J. Blazkowicz, é um espião Polonês com descendência judaica, que foi capturado pelo grupo nazista e aprisionado dentro do "Castelo Wolfenstein". É por isso que o jogo inicia com você apontando uma arma na direção de um soldado morto, dando a entender que você foi o responsável por executá-lo, possivelmente tendo roubado sua arma logo após ele entrar em sua cela.

IMG.png


A partir daí, seu objetivo é escapar do Castelo, enfrentando os demais soldados pelo caminho. É também por este motivo que o jogo exibe tantas imagens de suásticas e retratos de Hitler nas paredes, você está literalmente dentro da 'sede' nazista. Também não é coinsidência que a raça escolhida pelos desenvolvedores para caracterizar os cães de guarda seja "Pastor Alemão".

Enfim, eu disse tudo isso apenas para que se compreenda, que apesar do jogo apresentar todo este conteúdo, o mesmo não faz apologia ao nazismo.

Isso não muda o fato de que este conteúdo, por si só, é muito desconfortável para a grande maioria das pessoas, independente de como a narrativa seja retratada. É exatamente por este motivo que eu também fiz uma versão deste remake com os assets censurados, encontrados no port de Wolfenstein 3D para o Super Nintendo. Todas as referências ao nazismo foram removidas, e até mesmo os cães foram substituídos por ratos.

IMG.png


IMG.png


Outro ponto que me causou demasiado desconforto, é a música que toca na tela de título da versão original do jogo. Quando eu era mais jovem, admito já ter cantarolado esta melodia, sem fazer ideia de que ela é um rendição em MIDI do hino nazista. Mesmo eu zelando em recriar os jogos o mais fielmente possível aos originais, não posso negar que ainda acho esta escolha feita pela ID Software de um extremo mau gosto. Evitando gerar desinformação, e acabar fazendo com que outros indivíduos pensem que esta é apenas a música tema de um jogo retrô sem reconhecerem sua verdadeira origem, decidi substituí-la em meu remake.

Agora tratando-se das atualizações em si, muita coisa foi alterada desde meu último post. Em primeiro lugar, alterei a forma que os blocos (paredes) deixam de ser renderizados, para melhorar o desempenho. Antes eu estava renderizando todos os blocos dentro do FOV (campo de visão) do jogador, o que não é assim tão eficiente, já que este mesmo FOV é bastante amplo, e não elimina blocos que estão omitidos por paredes mais próximas à câmera. Em vez disso, estou utilizando uma técnica que foi bastante adotada por jogos da era do Playstation 1, como o Silent Hill, por exemplo. "Névoa" ou "Fog", é usada para limitar a distância que blocos são visíveis ao jogador. Se um bloco é sobreposto pela névoa, eu simplesmente paro de executar o comando para desenhá-lo, e como podem ver no exemplo abaixo, a transição ficou bem sutil.

GIF_1.gif


Algumas adições estéticas foram feitas para fazer o jogo parecer um pouco mais moderno. É possível ativar/desativar cada uma delas individualmente através do menu de opções.

Head Bobbing (Balançar Cabeça) - A câmera sobe e desce conforme o jogador se movimenta;

GIF_2.gif



Weapon Bobbing (Balançar Armas) - A mão do jogador se move levemente conforme ele anda;

GIF_3.gif



Footstep Sounds (Sons de Passo) - Torna audível o som dos passos do jogador;

Blood Particles (Partículas de Sangue) - Inimigos jorram sangue no chão e nas paredes ao serem atacados;

GIF_4.gif



Crosshair (Mira) - Exibe uma retícula em forma de cruz no meio da tela, para tornar mais fácil centralizar os alvos;

GIF_5.gif



High Resolution (Alta Resolução) - Exibe às texturas com mais nitidez.

GIF_6.gif



Peço perdão caso eu não tenha entrado muito em detalhes quanto à programação em si. Com projetos como este, eu costumo tentar programar tudo à olho nu, mas como Wolfenstein 3D utiliza algorítmos consideravelmente complexos para randomizar o dano e a chance dos inimigos acertarem os disparos, tive que usar o código fonte original como referência. Para quem não sabe, o código fonte de Wolfenstein 3D foi disponibilizado ao público pela ID Software em 1995 e é acessível através deste link - https://github.com/id-Software/wolf3d


E por fim, segue o Link para download do remake em minha página do itch.io - https://dougmr.itch.io/wolfenstein-3d-1st-floor-remake


Observação: Este jogo pode não funcionar em computadores com Placa de Vídeo integrada, em vez de uma dedicada. Esse é um problema comum em jogos 3D feitos em versões mais antigas do Game Maker Studio. Se você receber uma mensagem de erro dizendo "Can not create vertex buffer[...]", este é o motivo.
 
Última edição:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Draw Event
for object obj_door_H:

Fatal Error: Can not create vertex buffer of size 147456 bytes
6036 vertex buffers allocated with total size of 842160 KB
at gml_Object_obj_door_H_Draw_0
############################################################################################
Infelizmente não conseguirei jogar. Uma pena que Wolfenstein 3D não rodou aqui, por acaso coloca-lo online no itchio resolveria esse tipo de problema? Já joguei por exemplo uma versão portada do Tomb Raider para navegador com esse mesmo notebook que eu tenho. Sobre a censura em jogos antigos/produtos, bom, sou um pouco contra, acho que o produto foi feito para aquela época com um intuito especifico, tirar isso de um produto para mim seria deixa-lo incompleto, imagine (filmes por exemplo) assistir Bastardos Inglórios sem os simbolismos no visual e falas dos personagens. É apenas uma curta opinião minha sobre censura, nada contra sua decisão ou como parece que hoje temos de deixar tudo claro para qualquer pessoa para não gerar confusão e interpretações erradas, como acabei de fazer nessa última frase desse parágrafo. xD

Eu gostava desse estilo de jogo, jogava bastante Zero Tolerance e Doom, achava interessante os mapas labirínticos, mal sabia que existiu a possibilidade de uma continuação, que infelizmente não foi para frente, porém ainda é possível jogar a build, até consegui jogar, só que há muitos bugs nela e os gráficos não pareciam polidos ou com a paleta correta nos personagens.
1706877431339.png

1706877292953.png


Li e vi sua proposta em recriar jogos famosos, acredito que seja uma ótima forma de estudo, gostaria de recriar alguns jogos/coiceitos de arte limitados pela época ou "finalizar" essa build por exemplo, porém são outros tempos e me falta tempo+motivação a dominar outras áreas do gamedev, então acabo por comodidade ficando na área artística.

Desejo boa sorte no projeto e não pare, continue, faça as coisas do que mais gosta e sente prazer.
 
Última edição:
@Victor Sena , obrigado pelo apoio ^^

Eu não acredito que os comandos que estou usando em meu código fonte são suportados quando exportados para HTML5, e com o acúmulo de projetos que estou trabalhando no momento, não sei se terei tempo para fazer um novo port.

Seu notebook provavelmente é capaz de rodar jogos bem superiores à esse, não é culpa da sua placa de vídeo, e sim da versão da engine que estou utilizando, o GameMaker Studio v1.4.1567. Especificamente com jogos 3D, ela dá esse problema em computadores com placa de vídeo integrada.

Se algum dia eu fizer o update para o Game Maker Studio 2, acredito que eu consiga abrir este projeto nele e compilar de forma à resolver este problema, mas no momento não estou com condições de investir dinheiro numa nova engine.

Se tratando desse gênero "retrô" de FPS, eu gosto tanto que continuo jogando até hoje ^^
Na época eu jogava bastante o "DOOM" e "DOOM II", principalmente os mapas feitos pela comunidade, mas também tenho acompanhado jogos mais recentes nesse mesmo estilo, como o "Warhammer 40k: Boltgun", "Cultic" e "Dusk". Recomendo muito, caso goste do gênero ou do estilo artístico.

Quanto ao tempo para dedicar à projetos como esse, admito, é difícil mesmo! Por isso que normalmente me foco em recriar apenas à primeira fase/nível. Mas é legal que você ainda dedique parte de seu tempo na área artística, muita gente acaba abandonando seus hobbies por falta de tempo.

Já tentou criar algum mod, ou pacote de recursos, com suas próprias texturas e sprites para algum jogo? Acho que seria uma forma bem legal de manter seu hobby ativo! O importante é não abandonar sua paixão ;)

Forte abraço!
 
Fazer isso não foi tarefa fácil, pois pelo que eu vi, não existe muita documentação online sobre o layout dos diferentes níveis desse jogo. Eu tive que analisar vídeos de gameplay como referência e usar o editor de níveis do jogo original para checar o posicionamento correto de tudo:
Essa parte realmente deve ter sido complicado mesmo com o editor mostrando os posicionamentos, isso realmente me chamou a atenção, boa jogada usar esse método.
 
Essa parte realmente deve ter sido complicado mesmo com o editor mostrando os posicionamentos, isso realmente me chamou a atenção, boa jogada usar esse método.
é, esse não foi o único obstáculo que encontrei, mas definitivamente foi um dos maiores desafios na hora de recriar este jogo. Também tive dificuldade em encontrar o áudio para a versão censurada. Como todas as referências ao nazismo foram removidas da versão para o Super Nintendo, os soldados não falam alemão ao detectarem você, mas sim inglês.

Eu tive que emular a ROM do jogo em meu computador e executar o seguinte comando para habilitar o menu de teste sonoro:
"Segure R no controle 1, ligue o SNES e solte R na tela de título, depois pressione imediatamente baixo e select."

A partir daí eu gravei os efeitos sonoros por conta própria.
 
Voltar
Topo