Bomberman MiniGame: Difference between revisions

From Graal Bible
No edit summary
No edit summary
 
(22 intermediate revisions by the same user not shown)
Line 1: Line 1:
Welcome to the Bomberman Minigame Tutorial!
Welcome to the Bomberman Minigame Tutorial!


=== Intro: ===
= BomberMan =
BomberMan is a multiplayer game (usually 4 players). Each player can drop bombs that can destroy the grey pillars making a path to other players. The objective of the game is to eliminate the other players with the exploding bombs. Power ups are also available when destroying pillars (increase of speed, explosion power and number of bombs allowed to place at a time).
 
==== Intro ====
BomberMan is a multiplayer game (usually 4 players). Each player can drop bombs that, upon explosion,  destroy the grey pillars making a path to other players. The objective of the game is to eliminate other players with the exploding bombs. Power ups are also sometimes dropped when pillars are destroyed (increase of speed, explosion power and number of bombs allowed to place at a time).
[[File:Bomberman cover.png|none|thumb|675x675px]]
[[File:Bomberman cover.png|none|thumb|675x675px]]
Assets will be available for you to download and follow the tuto.
Assets Bundle : <nowiki>https://www.graalonline.com/playerworlds/downloads/file?name=bombermanMinigame.unitypackage</nowiki>
 
'''bombermanprefabs'''
 
[[File:Bomberman prefabs.png|frameless]]
 
and '''bombermanlevel1'''


Since this game is meant to be multiplayer, we will be placing the GameObjects in NPCs for them to be visible to all the players. (more info on that here: [[Placing GameObjects in NPC]])
[[File:Bomberman level prefab.png|frameless]]


First, we'll have the weapon that starts up the game and places the NPCs (''found under '''weapon: Merlin/BomberMan/Game''''')
Since this game is meant to be multiplayer, we will be placing the GameObjects in NPCs for them to be visible to all the players and triggerring the server every time we want to place them. (more info on that here: [[Placing GameObjects in NPC]])
 
First, we have the weapon that launches the game and places the NPCs (''found under '''weapon: 3D/Samples/BomberMan/Game''''')
 
We've implemented a graalscript UI (which is not covered in this section, considering you know graalscript) that allows us to launch the game;
  function onActionServerside(cmd, x, y) {
  function onActionServerside(cmd, x, y) {
   if (cmd == "bomb") {
   if (cmd == "bomb") {
     temp.pnpc = putnpc2(x, y, "");
     temp.pnpc = putnpc2(x, y, "");
     temp.pnpc.join("merlin_bomberman_bomb");
     temp.pnpc.join("merlin_bomberman_bomb");
    //temp.pnpc.DropBomb(explvl);
   }
   }
   if (cmd == "level") {
   if (cmd == "level") {
Line 23: Line 36:
  function onCreated() {
  function onCreated() {
   echo(this.name @ " OnCreated");
   echo(this.name @ " OnCreated");
  level.explosionpower = 4; //for powerups with explosion
  level.numberofbombs = 2; //number of bombs allowed to place at a time
  }
  }
   
   
  public function Load() {
  public function Load() {
  level.explosionpower = 3; //for powerups with explosion
  level.numberofbombs = 2; //number of bombs allowed to place at a time
  level.speed = 7; //player default speed
 
   echo(this.name @ " LOAD");
   echo(this.name @ " LOAD");
   Object::destroy(this.bomberManParentNode);
   Object::destroy(this.bomberManParentNode);
Line 81: Line 96:
   this.hit = RaycastHit::create();
   this.hit = RaycastHit::create();
   done = Physics::Raycast(player.gameobject.transform.position, Vector3::Down, this.hit, 100);
   done = Physics::Raycast(player.gameobject.transform.position, Vector3::Down, this.hit, 100);
   sleep(0.05);
   //sleep(0.05);
   triggerserver("gui", this.name, "bomb", this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
   triggerserver("gui", this.name, "bomb", this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
  }
  }
Here we trigger the server every time we want to place a GameObject.


In the function start, we trigger the server with the command "'''level'''" to load the bomberman map by placing an NPC and joining it with the class "'''merlin_bomberman_level'''".
the '''''Load()''''' function is called when the minigame is launched (Devtools -> Minigames -> BomberMan).
 
'''level.explosionpower = 3;''' is how much tiles the explosion is going to cover. (at the beginning it's 3)
 
'''level.numberofbombs = 2;''' is the number of bombs a player is allowed to place at a time. (at the beginning it's 2)
 
In the function '''''start()''''', we trigger the server with the command "'''level'''" to load the bomberman map by placing an NPC and joining it with the class "'''3d_samples_bomberman_level'''".


Once we start the game, the player is not allowed to Jump: '''PLAYERMOVEMENT.canJump = false;'''  
Once we start the game, the player is not allowed to Jump: '''PLAYERMOVEMENT.canJump = false;'''  
Line 92: Line 112:
And pressing space will be used to drop a bomb, '''''function onKeyPressed()''''' checks if space is pressed and calls '''''DropBomb()'''.''
And pressing space will be used to drop a bomb, '''''function onKeyPressed()''''' checks if space is pressed and calls '''''DropBomb()'''.''


===== <u>New : RayCast</u> =====
==== <u>New: RayCast:</u> ====
Think of RayCast as '''''triggeraction()''''' that triggers NPCs at given (x,y). Similarly, '''''RayCast''''' casts a ray given a position, a direction and a length, that detects colliders.
Think of RayCast as '''''triggeraction()''''' that triggers NPCs at a given (x,y). Similarly, '''''RayCast''''' casts a ray given a position (origin), a direction and a length, detecting colliders.
{| class="wikitable"
{| class="wikitable"
!Raycast(Vector3 origin, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
!Raycast(Vector3 origin, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
Line 101: Line 121:
''(More info here: https://docs.unity3d.com/ScriptReference/Physics.Raycast.html<nowiki/>)''
''(More info here: https://docs.unity3d.com/ScriptReference/Physics.Raycast.html<nowiki/>)''


Here, since the bomberman level is a grid and the floor is made of squares. We want the player to drop the bomb exactly in one square and not in between two. Therefore, when space is pressed, we RayCast with the PlayerGameObject as '''origin''', with a Vector3::Down '''direction''', '''''this.hit''''' as the '''output''' and 100 for '''length'''. The other parameters are kept at default.
Here, since the bomberman level is a grid and the floor is made of squares. We want the player to drop the bomb exactly in one square and not in between two. Therefore, when space is pressed, we RayCast with the PlayerGameObject as '''origin''', with a Vector3::Down '''direction''', '''''this.hit''''' as the '''output''' and 100 for '''length'''. The other parameters are kept as default.


This will return the collider of the GameObject underneath the player, which is the square tile.
This will return the collider of the GameObject underneath the player, which is one square tile.


We extract the tile's position from '''''this.hit''''':
We extract the tile's position from '''''this.hit''''':
{| class="wikitable"
{| class="wikitable"
!this.hit.transform.gameobject.transform.position.x
!this.hit.transform.gameobject.transform.position.x / y
|}
|}
And we trigger the server, with the command "bomb" and the coordinates, to place a bomb NPC;
And we trigger the server, with the command "bomb" and the coordinates, to place a bomb NPC;
{| class="wikitable"
!triggerserver("gui", this.name, "bomb", this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
|}
  function onActionServerside(cmd, x, y) {
  function onActionServerside(cmd, x, y) {
   if (cmd == "bomb") {
   if (cmd == "bomb") {
Line 120: Line 143:
   }
   }
  }
  }
Now, on to the classes!
'''"3d_samples_bomberman_level" Class:'''
function onCreated() {
  //Set a 10 minute timer to automatically destroy the levelprefab
  scheduleEvent(120, "DestLvl"); 
}
//Destroy if the server restarts
function onInitialized() {
  this.destroy();
}
function onPlayerChats() {
  if (player.chat == "dest") onDestLvl();
}
function onDestLvl() {
  this.destroy();
}
//#CLIENTSIDE
function onCreated() {
  (@"3D/Dev/AssetManager").loadAssetBundle("bombermanlevel1");
}
function onAssetBundleDownloaded(bundlename){
  if (bundlename == "bombermanlevel1") {
    player.chat = "bombermanlevel loaded";
    temp.prefab = GameObject::fromassetbundle("bombermanlevel1", "assets/bombermantest/big level 2.prefab");
    this.bomberlevel = Object::Instantiate(Type::GameObject, temp.prefab, this.gameobject.transform, false);
    this.bomberlevel.transform.parent = this.gameobject.transform;
    this.bomberlevel.transform.localposition = v3(-15,0,0);
    this.bomberlevel.transform.localscale = v3(1,1,1);
  }
}
function onPlayerChats() {
  if (player.chat == "dest") Object::Destroy(this.gameobject);
}
Here we place the level GameObject in an NPC ([[Placing GameObjects in NPC]]) and we only load and instantiate the level the usual way ([[Uploading and Loading AssetBundles]]).
'''"3d_samples_bomberman_bomb" Class:'''
function onCreated() {
  //echo("merlin_testbomb");
  scheduleEvent(3600, "RemoveBomb");
}
//Destroy if the server restarts
function onInitialized() {
  this.destroy();
}
function onPlayerChats() {
  if (player.chat == "/destroy") {
    onRemoveBomb();
  }
}
function onRemoveBomb() {
  this.destroy();
}
//#CLIENTSIDE
function onCreated() {
  this.dropped = false;
  this.powerup = randomstring({"bomb", "explosion", "speed"});
 
  DropBomb();
}
function DropBomb() {
  if (level.numberofbombs == 0) return;
  --level.numberofbombs;
 
  (@"3D/Dev/AssetManager").loadAssetBundle("bombermanprefabs");
  (@"3D/Dev/AssetManager").loadAssetBundle("staffstick");
  this.speedprefab = GameObject::fromassetbundle("staffstick","assets/johntest/staffstick_era.prefab");
  this.explosionprefab = GameObject::fromassetbundle("bombermanprefabs", "assets/bomberman/prefabs/explosion.prefab");
  this.prefab = GameObject::fromassetbundle("bombermanprefabs", "assets/bomberman/prefabs/bomb.prefab");
  this.bomb = Object::instantiate(Type::GameObject, this.prefab, this.gameobject.transform, false);
  this.bomb.transform.parent = this.gameobject.transform;
  this.bomb.transform.localeulerangles = v3(0, 0, 0);
  this.bomb.transform.localposition = v3(0, 0.5, 0);
  scheduleEvent(3, "Explode");
}
function onExplode() {
  echo("onexplode");
 
  echo ("prefab type=" @ this.explosionprefab.objecttype());
  this.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
  echo ("explosion type=" @ this.explosion.objecttype());
  this.explosion.transform.parent = this.gameobject.transform;
  this.explosion.transform.localposition = v3(0, 0, 0);
  this.explosion.transform.localscale = v3(1,1,1);
 
  Object::Destroy(this.bomb, .3f);
 
  this.exploded = true;
  CreateExplosion(Vector3::forward);
  CreateExplosion(Vector3::right);
  CreateExplosion(Vector3::back);
  CreateExplosion(Vector3::left);
 
  ++level.numberofbombs;
}
function CreateExplosion(direction) {
  for (temp.i = 1; temp.i < level.explosionpower; temp.i++) {
    this.hit = RaycastHit::create();
    done = Physics::Raycast(this.bomb.transform.position.Add(v3(0,0.5,0)), direction, this.hit, temp.i);
     
    if (!done || this.hit.transform.gameobject.name.starts("assets/bomberman/prefabs/explosion.prefab") || this.hit.transform.gameobject.name.starts("TPlayer")){
      temp.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
      temp.explosion.transform.localposition = v3(0,0,0).Add(direction.Mult(temp.i));
      Object::Destroy(temp.explosion, 0.7);
    }
    if (this.hit.transform.gameobject.name.starts("E_Bomber_Pilar_01_")|| this.hit.transform.gameobject.transform.parent.gameobject.name.starts("E_Bomber_Pilar_01_")) {
      if (!this.dropped) {
        if (rand(0,1) < 0.2) {
          this.dropped = true;
          //drop power up
          droppowerup(this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
        }
      }
      Object::Destroy(this.hit.transform.gameobject.transform.parent.gameobject);
      Object::Destroy(this.hit.transform.gameobject);
      Object::Destroy(temp.explosion, 0.7);
      temp.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
      temp.explosion.transform.position = this.hit.transform.position;
    }
  }
}
function droppowerup(x,z) {
  if (this.powerup == "bomb") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.prefab, this.gameobject.transform, false);
  }
  if (this.powerup == "explosion") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
  }
  if (this.powerup == "speed") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.speedprefab, this.gameobject.transform, false);
  }
  this.powerupGO.transform.position = v3(x, 0.5, z);
  this.powerupGO.transform.scale = v3(0.5,0.5,0.5);
  sphereCollider = this.powerupGO.AddComponent(Type::SphereCollider);
  sphereCollider.size = v3(1, 10, 1);
}
public function onActionGrab() {
  player.chat = this.powerup;
  Object::Destroy(this.gameobject);
  if (this.powerup == "bomb") {
    ++level.numberofbombs;
  }
  if (this.powerup == "explosion") {
    ++level.explosionpower;
  }
  if (this.powerup == "speed") {
    PLAYERMOVEMENT.setSpeed(7);
  }
}
Again, we are loading and instantiating the prefabs in an NPC.
In '''''function onCreated()''''':
* '''''this.dropped''''' variable is used to allow every bomb to drop only one powerup.
* '''''this.powerup''''' variable is the name of the powerup, chosen randomly each time a bomb is dropped.
'''''DropBomb()''''' is responsible for loading, instantiating and placing the bomb. The player can drop 2 bombs at a time at the start of the game. Therefore, we begin by checking if the player can drop bombs. We decrease the variable '''''level.numberofbombs''''' when a bomb is dropped and decrease it when the bomb explodes.
After positioning the bomb, we schedule event to create the explosion after 3 seconds of placing the bomb;
{| class="wikitable"
!scheduleEvent(3, "Explode");
|}
'''''onExplode()''''' function instantiates an explosion under the bomb;
{| class="wikitable"
!this.explosion.transform.localposition = v3(0, 0, 0);
|}
and calls '''''CreateExplosion(direction);''''' in all the four directions (forward, right, left, back) to instantiate explosions in a cross shape around the bomb.
'''''CreateExplosion(direction)''''' casts a Ray in all the four directions with the given length ('''level.explosionpower'''; the number of tiles it covers) and the bomb as the origin.
{| class="wikitable"
!this.bomb.transform.position.Add(v3(0,0.5,0));
|}we add a '''''Vector3 (0, 0.5, 0)''''' to increase the Y of the '''RayCast''' for it to hit the  grey pillars and not the ground.
The two conditional blocks after are used to check if the collider hit by the raycast is a pillar or not.
if it's a Grey Pillar, we destroy it. If not then it's empty space and we instantiate an explosion.
When a Pillar is destroyed, there's a 20% chance it will drop a powerup, by calling the function '''''droppowerup(x,z)''''' with (x,z) the coordinates of the Pillar.
'''''droppowerup(x,z)''''' will check '''''this.powerup''''' and create and place the appropriate GameObject. It also adds a collider Component to be triggered onActionGrab; 
==== <u>New: onActionGrab for NPCs:</u> ====
For this to work, the GameObject is required to have a collider.
And '''''public function onActionGrab()''''' needs to be implemented.
Here, we destroy the powerup GameObject and give the player the powerup.
(''weapon: '''3D/Player/Grab''''' for more info)

Latest revision as of 09:17, 22 November 2021

Welcome to the Bomberman Minigame Tutorial!

BomberMan

Intro

BomberMan is a multiplayer game (usually 4 players). Each player can drop bombs that, upon explosion, destroy the grey pillars making a path to other players. The objective of the game is to eliminate other players with the exploding bombs. Power ups are also sometimes dropped when pillars are destroyed (increase of speed, explosion power and number of bombs allowed to place at a time).

Bomberman cover.png

Assets Bundle : https://www.graalonline.com/playerworlds/downloads/file?name=bombermanMinigame.unitypackage

bombermanprefabs

Bomberman prefabs.png

and bombermanlevel1

Bomberman level prefab.png

Since this game is meant to be multiplayer, we will be placing the GameObjects in NPCs for them to be visible to all the players and triggerring the server every time we want to place them. (more info on that here: Placing GameObjects in NPC)

First, we have the weapon that launches the game and places the NPCs (found under weapon: 3D/Samples/BomberMan/Game)

We've implemented a graalscript UI (which is not covered in this section, considering you know graalscript) that allows us to launch the game;

function onActionServerside(cmd, x, y) {
  if (cmd == "bomb") {
    temp.pnpc = putnpc2(x, y, "");
    temp.pnpc.join("merlin_bomberman_bomb");
    //temp.pnpc.DropBomb(explvl);
  }
  if (cmd == "level") {
     temp.bombernpc = putnpc2(x, y, "");
     temp.bombernpc.join("merlin_bomberman_level");
  }
}

//#CLIENTSIDE
function onCreated() {
  echo(this.name @ " OnCreated");
}

public function Load() {
  level.explosionpower = 3; //for powerups with explosion
  level.numberofbombs = 2; //number of bombs allowed to place at a time
  level.speed = 7; //player default speed
  
  echo(this.name @ " LOAD");
  Object::destroy(this.bomberManParentNode);
  if (isObject("bomberman_Quit")) {
    "bomberman_Quit".destroy();
  }
  new GuiButtonCtrl("bomberman_Quit") {
    profile = GuiBlueButtonProfile;
    x = 600;
    y = 200;
    width = 150;
    height = 30;
    text = "bombermanQuit";
  }

  temp.warpLevel = "only_ground.nw";
  temp.warpX = 4.65; 
  temp.warpY = 21.33; 
  temp.warpZ = 0.7; 
  WARPMANAGER.Warp(temp.warpX, temp.warpY ,temp.warpZ ,temp.warpLevel);
  sleep(0.5);
  start(); 
}

function bomberman_Quit.onAction() {
  BomberManQuit();
}

public function BomberManQuit() {
  if (isObject("bomberman_Quit")) {
    "bomberman_Quit".destroy();
  }
  PLAYERMOVEMENT.UnFreeze();
  PLAYERMOVEMENT.canJump = true;
  this.GAMEON = false;
}

function start() {
  PLAYERMOVEMENT.canJump = false;
  this.GAMEON = true;
  triggerserver("gui", this.name, "level", -60 , 14);
}

function onKeyPressed(keycode, keychar) {
  if (this.GAMEON == true) {
    if (keycode == "32, ,57") {
      DropBomb();
    }
  }
}

function DropBomb() {
  this.hit = RaycastHit::create();
  done = Physics::Raycast(player.gameobject.transform.position, Vector3::Down, this.hit, 100);
  //sleep(0.05);
  triggerserver("gui", this.name, "bomb", this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
}

the Load() function is called when the minigame is launched (Devtools -> Minigames -> BomberMan).

level.explosionpower = 3; is how much tiles the explosion is going to cover. (at the beginning it's 3)

level.numberofbombs = 2; is the number of bombs a player is allowed to place at a time. (at the beginning it's 2)

In the function start(), we trigger the server with the command "level" to load the bomberman map by placing an NPC and joining it with the class "3d_samples_bomberman_level".

Once we start the game, the player is not allowed to Jump: PLAYERMOVEMENT.canJump = false;

And pressing space will be used to drop a bomb, function onKeyPressed() checks if space is pressed and calls DropBomb().

New: RayCast:

Think of RayCast as triggeraction() that triggers NPCs at a given (x,y). Similarly, RayCast casts a ray given a position (origin), a direction and a length, detecting colliders.

Raycast(Vector3 origin, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);

Returns: Boolean indicating if the RayCast hit a Collider.

(More info here: https://docs.unity3d.com/ScriptReference/Physics.Raycast.html)

Here, since the bomberman level is a grid and the floor is made of squares. We want the player to drop the bomb exactly in one square and not in between two. Therefore, when space is pressed, we RayCast with the PlayerGameObject as origin, with a Vector3::Down direction, this.hit as the output and 100 for length. The other parameters are kept as default.

This will return the collider of the GameObject underneath the player, which is one square tile.

We extract the tile's position from this.hit:

this.hit.transform.gameobject.transform.position.x / y

And we trigger the server, with the command "bomb" and the coordinates, to place a bomb NPC;

triggerserver("gui", this.name, "bomb", this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
function onActionServerside(cmd, x, y) {
  if (cmd == "bomb") {
    temp.pnpc = putnpc2(x, y, "");
    temp.pnpc.join("merlin_bomberman_bomb");
  }
  if (cmd == "level") {
     temp.bombernpc = putnpc2(x, y, "");
     temp.bombernpc.join("merlin_bomberman_level");
  }
}

Now, on to the classes!

"3d_samples_bomberman_level" Class:

function onCreated() {
  //Set a 10 minute timer to automatically destroy the levelprefab
  scheduleEvent(120, "DestLvl");  
}
//Destroy if the server restarts

function onInitialized() {
  this.destroy();
}

function onPlayerChats() {
  if (player.chat == "dest") onDestLvl();
}

function onDestLvl() {
  this.destroy();
}

//#CLIENTSIDE
function onCreated() {
  (@"3D/Dev/AssetManager").loadAssetBundle("bombermanlevel1");
}

function onAssetBundleDownloaded(bundlename){
  if (bundlename == "bombermanlevel1") {
    player.chat = "bombermanlevel loaded";
    temp.prefab = GameObject::fromassetbundle("bombermanlevel1", "assets/bombermantest/big level 2.prefab");
    this.bomberlevel = Object::Instantiate(Type::GameObject, temp.prefab, this.gameobject.transform, false);
    this.bomberlevel.transform.parent = this.gameobject.transform;
    this.bomberlevel.transform.localposition = v3(-15,0,0);
    this.bomberlevel.transform.localscale = v3(1,1,1);
  }
}

function onPlayerChats() {
  if (player.chat == "dest") Object::Destroy(this.gameobject);
}

Here we place the level GameObject in an NPC (Placing GameObjects in NPC) and we only load and instantiate the level the usual way (Uploading and Loading AssetBundles).

"3d_samples_bomberman_bomb" Class:

function onCreated() {
  //echo("merlin_testbomb");
  scheduleEvent(3600, "RemoveBomb");
}

//Destroy if the server restarts
function onInitialized() {
  this.destroy();
}

function onPlayerChats() {
  if (player.chat == "/destroy") {
    onRemoveBomb();
  }
}

function onRemoveBomb() {
  this.destroy();
}

//#CLIENTSIDE
function onCreated() {
  this.dropped = false;
  this.powerup = randomstring({"bomb", "explosion", "speed"});
  
  DropBomb();
}

function DropBomb() {
  if (level.numberofbombs == 0) return;
  --level.numberofbombs;
  
  (@"3D/Dev/AssetManager").loadAssetBundle("bombermanprefabs");
  (@"3D/Dev/AssetManager").loadAssetBundle("staffstick");
  this.speedprefab = GameObject::fromassetbundle("staffstick","assets/johntest/staffstick_era.prefab");
  this.explosionprefab = GameObject::fromassetbundle("bombermanprefabs", "assets/bomberman/prefabs/explosion.prefab");
  this.prefab = GameObject::fromassetbundle("bombermanprefabs", "assets/bomberman/prefabs/bomb.prefab");
  this.bomb = Object::instantiate(Type::GameObject, this.prefab, this.gameobject.transform, false);
  this.bomb.transform.parent = this.gameobject.transform;
  this.bomb.transform.localeulerangles = v3(0, 0, 0);
  this.bomb.transform.localposition = v3(0, 0.5, 0);

  scheduleEvent(3, "Explode");
}

function onExplode() {
  echo("onexplode");
  
  echo ("prefab type=" @ this.explosionprefab.objecttype());
  this.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
  echo ("explosion type=" @ this.explosion.objecttype());
  this.explosion.transform.parent = this.gameobject.transform;
  this.explosion.transform.localposition = v3(0, 0, 0);
  this.explosion.transform.localscale = v3(1,1,1);
  
  Object::Destroy(this.bomb, .3f);
  
  this.exploded = true;

  CreateExplosion(Vector3::forward);
  CreateExplosion(Vector3::right);
  CreateExplosion(Vector3::back);
  CreateExplosion(Vector3::left);
  
  ++level.numberofbombs;
}

function CreateExplosion(direction) {
  for (temp.i = 1; temp.i < level.explosionpower; temp.i++) {
    this.hit = RaycastHit::create();
    done = Physics::Raycast(this.bomb.transform.position.Add(v3(0,0.5,0)), direction, this.hit, temp.i);
     
    if (!done || this.hit.transform.gameobject.name.starts("assets/bomberman/prefabs/explosion.prefab") || this.hit.transform.gameobject.name.starts("TPlayer")){
      temp.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
      temp.explosion.transform.localposition = v3(0,0,0).Add(direction.Mult(temp.i));
      Object::Destroy(temp.explosion, 0.7);
    }
    if (this.hit.transform.gameobject.name.starts("E_Bomber_Pilar_01_")|| this.hit.transform.gameobject.transform.parent.gameobject.name.starts("E_Bomber_Pilar_01_")) {
      if (!this.dropped) {
        if (rand(0,1) < 0.2) {
          this.dropped = true;
          //drop power up
          droppowerup(this.hit.transform.gameobject.transform.position.x, this.hit.transform.gameobject.transform.position.z);
        }
      }
      Object::Destroy(this.hit.transform.gameobject.transform.parent.gameobject);
      Object::Destroy(this.hit.transform.gameobject);
      Object::Destroy(temp.explosion, 0.7);
      temp.explosion = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
      temp.explosion.transform.position = this.hit.transform.position;
    }
  }
}

function droppowerup(x,z) {
  if (this.powerup == "bomb") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.prefab, this.gameobject.transform, false);
  }
  if (this.powerup == "explosion") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.explosionprefab, this.gameobject.transform, false);
  }
  if (this.powerup == "speed") {
    this.powerupGO = Object::instantiate(Type::GameObject, this.speedprefab, this.gameobject.transform, false);
  }
  this.powerupGO.transform.position = v3(x, 0.5, z);
  this.powerupGO.transform.scale = v3(0.5,0.5,0.5);

  sphereCollider = this.powerupGO.AddComponent(Type::SphereCollider);
  sphereCollider.size = v3(1, 10, 1);
}

public function onActionGrab() {
  player.chat = this.powerup;
  Object::Destroy(this.gameobject);
  if (this.powerup == "bomb") {
    ++level.numberofbombs;
  }
  if (this.powerup == "explosion") {
    ++level.explosionpower;
  }
  if (this.powerup == "speed") {
    PLAYERMOVEMENT.setSpeed(7);
  }
}

Again, we are loading and instantiating the prefabs in an NPC.

In function onCreated():

  • this.dropped variable is used to allow every bomb to drop only one powerup.
  • this.powerup variable is the name of the powerup, chosen randomly each time a bomb is dropped.


DropBomb() is responsible for loading, instantiating and placing the bomb. The player can drop 2 bombs at a time at the start of the game. Therefore, we begin by checking if the player can drop bombs. We decrease the variable level.numberofbombs when a bomb is dropped and decrease it when the bomb explodes.

After positioning the bomb, we schedule event to create the explosion after 3 seconds of placing the bomb;

scheduleEvent(3, "Explode");

onExplode() function instantiates an explosion under the bomb;

this.explosion.transform.localposition = v3(0, 0, 0);

and calls CreateExplosion(direction); in all the four directions (forward, right, left, back) to instantiate explosions in a cross shape around the bomb.

CreateExplosion(direction) casts a Ray in all the four directions with the given length (level.explosionpower; the number of tiles it covers) and the bomb as the origin.

this.bomb.transform.position.Add(v3(0,0.5,0));

we add a Vector3 (0, 0.5, 0) to increase the Y of the RayCast for it to hit the grey pillars and not the ground.

The two conditional blocks after are used to check if the collider hit by the raycast is a pillar or not.

if it's a Grey Pillar, we destroy it. If not then it's empty space and we instantiate an explosion.

When a Pillar is destroyed, there's a 20% chance it will drop a powerup, by calling the function droppowerup(x,z) with (x,z) the coordinates of the Pillar.

droppowerup(x,z) will check this.powerup and create and place the appropriate GameObject. It also adds a collider Component to be triggered onActionGrab;

New: onActionGrab for NPCs:

For this to work, the GameObject is required to have a collider.

And public function onActionGrab() needs to be implemented.

Here, we destroy the powerup GameObject and give the player the powerup.

(weapon: 3D/Player/Grab for more info)