Vice2D: A minimalist, event-based game library for VC:MP

Started by DMWarrior, Apr 11, 2020, 11:35 PM

Previous topic - Next topic

DMWarrior

Vice2D


Vice2D is a game library made for VC:MP. It should provide enough features for manipulating GUISprites and GUILabels, including rotation, position, collision detection and moving towards angle or position, and more.

Unfortunately, this library is just too big to be posted here directly, so I had to put it somewhere else. You can find a link for it right below (complete with syntax highlight):

Vice2D: A minimalist, event-based game library for VC:MP

Examples:


Hello, world


This will create a simple sprite on top-left position of screen. Use arrow keys to move the sprite. Link to image used by this example can be found written in the code.
[noae][noae]// Create core...
local game = Vice2D.Core().setDefaultKeys();

//
// Create spritesheet and image...
// A link to the image file is provided below:
//
// https://i.ibb.co/8dVGb3v/spritesheet.png
//
// To understand why the last values are 0.25 and 0.2:
//
// 256 / 64 = 4 (number of columns)
// 320 / 64 = 5 (number of rows)
//
// 1 / 4 = 0.25 (that's the uvX)
// 1 / 5 = 0.2 (that's the uvY)
//
local sst_player = Vice2D.Spritesheet(256, 320, 64, 64, 0.25, 0.2);
local spr_player = Vice2D.Image("spritesheet.png", sst_player);

// Create sprite...
local obj_player = Vice2D.Sprite(64, 64);
      obj_player.setImage(spr_player);
      obj_player.setFrames([0, 1, 2, 3]);

/**
 * @event update
 */
obj_player.addEventListener("update", function() {
  local speed = 4;
  if(this.input.held("up")   ) { this.y -= speed; }
  if(this.input.held("down") ) { this.y += speed; }
  if(this.input.held("left") ) { this.x -= speed; }
  if(this.input.held("right")) { this.x += speed; }
});

// Add player to core:
game.push(obj_player);

/**
 * @event Script::ScriptProcess
 */
function Script::ScriptProcess() {
  game.loop();
}

/**
 * @event KeyBind::OnDown
 *
 * @param {instance} key Key pressed.
 */
function KeyBind::OnDown(key) {
  game.input.read(key, true);
}

/**
 * @event KeyBind::OnUp
 *
 * @param {instance} key Key released.
 */
function KeyBind::OnUp(key) {
  game.input.read(key, false);
}
[/noae][/noae]

Arcade game


This one is a little more interesting. It was made to show a little of what this library can do. Use arrow keys to move the character and space bar to attack. Links to images used by this example can be found written in the code.
[noae][noae]// This is the core. All sprites must be added here.
local game = Vice2D.Core().setDefaultKeys();
      game.canvas.setViewport(0, 0, 512, 512);
      game.canvas.centerViewport();

// Fonts are set here...
local fon_counter    = Vice2D.Font(null, 192);
      fon_counter.AddFlags(GUI_FFLAG_BOLD | GUI_FFLAG_OUTLINE | GUI_ALIGN_CENTERH | GUI_ALIGN_CENTERV);
      fon_counter.Colour     = Colour(0, 0, 0);
      fon_counter.TextColour = Colour(83, 194, 113);
      fon_counter.Size       = VectorScreen(game.canvas.viewport.width, game.canvas.viewport.height);

// Spritesheets are set here...
local sst_background = Vice2D.Spritesheet(512, 512, 512, 512,     1,     1);
local sst_sword      = Vice2D.Spritesheet(768, 128, 128, 128, 0.166,     1);
local sst_particles  = Vice2D.Spritesheet(640, 128, 128, 128,   0.2,     1);
local sst_characters = Vice2D.Spritesheet(512, 384,  64,  96, 0.125,  0.25);

//
// Images are set here...
// A link to each image file is provided below:
//
// https://i.ibb.co/TcW2zRR/background.png
// https://i.ibb.co/MR0B8hw/sword.png
// https://i.ibb.co/7NSMm0g/characters.png
// https://i.ibb.co/Ntk18KQ/particles.png
//
local spr_background = Vice2D.Image("background.png", sst_background);
local spr_sword      = Vice2D.Image("sword.png",           sst_sword);
local spr_player     = Vice2D.Image("characters.png", sst_characters);
local spr_devil      = Vice2D.Image("characters.png", sst_characters);
local spr_skull      = Vice2D.Image("characters.png", sst_characters);
local spr_wizard     = Vice2D.Image("characters.png", sst_characters);
local spr_stone      = Vice2D.Image("characters.png", sst_characters);
local spr_particles  = Vice2D.Image("particles.png",   sst_particles);

/**
 * @class Particles
 * @extends Vice2D.Sprite
 */
class Particles extends Vice2D.Sprite {
  /**
   * @constructor
   */
  constructor() {
    base.constructor(128, 128);
    this.setImage(spr_particles);
    this.setFrames([0, 0, 1, 2, 3, 4]);
    this.setFrameSpeed(0);
    this.setVisible(false);
  }

  /**
   * Show particles on a specific position.
   *
   * @param {number} x X position.
   * @param {number}y Y position.
   */
  function show(x, y) {
    this.setFrame(1);
    this.setPosition(x, y);
    this.setFrameSpeed(0.5);
    this.setVisible(true);
  }

  /**
   * @event update
   */
  function update() {
    if(this.getFrameIndex() == 0) {
      this.setFrameSpeed(0);
      this.setVisible(false);
    }
  }
}

// These particles will appear when one of characters dies.
local obj_particles = Particles();

// Player's sword. It will be used later in the code...
local obj_sword = Vice2D.Sprite(128, 128);
      obj_sword.setImage(spr_sword);
      obj_sword.setFrames([0, 1, 2, 3, 4]);
      obj_sword.setFrameSpeed(0.5);
      obj_sword.hitbox.top = 64;

/**
 * @class Character
 * @extends Vice2D.Sprite
 */
class Character extends Vice2D.Sprite {
  /** Movement speed. */
  speed = 2;

  /** Mark true if you want to control the character. */
  playable = false;

  /** Determine character movement (true if running, false if idle). */
  running = false;

  /** If it's not playable, set a target to chase. If it is, set the sword. */
  target = null;

  /** Determine if character is dead or alive. */
  dead = null;

  /** Respawn time (-0.01). */
  respawnTime = 1.0;

  /** Enemies that may hurt the character. */
  enemies = [];

  /** Determine if character is attacking. */
  attacking = false;

  /** Cooldown time to attack. The player will not inflict any damage while on cooldown. */
  attackCooldown = 0.0;

  /** Respawn protection. The character will not take any damage. */
  protectionCooldown = 0.0;

  /** Player score. */
  score = 0;

  /**
   * @constructor
   *
   * @param {Vice2D.Image} image Character image.
   * @param {number} frames Animation frames.
   * @param {number} frameSpeed Animation speed.
   * @param {number} speed Movement speed.
   * @param {boolean} playable Mark true if you want to control the character.
   * @param {Vice2D.Sprite} target If it's not playable, set a target to chase. If it is, set the sword.
   */
  constructor(image, frames, frameSpeed, speed, playable, target) {
    base.constructor(64, 96);

    this.setImage(image);
    this.setFrames(frames);
    this.setFrameSpeed(frameSpeed);

    this.hitbox.top = 88;
    this.speed      = speed;
    this.playable   = playable;
    this.target     = target;

    this.spawn();
  }

  /**
   * Prevent character from moving out of bounds.
   */
  function stayWithinBoundaries() {
    // Left side of the boundary:
    if(this.x < 0) {
      this.x = 0;
    }

    // Right side of the boundary:
    else if(this.x + this.width > this.canvas.viewport.width) {
      this.x = this.canvas.viewport.width - this.width;
    }

    // Above the boundary (giving space for walls):
    if(this.y < -32) {
      this.y = -32;
    }

    // Below the boundary (giving space for walls):
    else if(this.y + this.height > (this.canvas.viewport.height - 64)) {
      this.y = (this.canvas.viewport.height - 64) - this.height;
    }
  }

  /**
   * Kills the character.
   */
  function die() {
    obj_particles.show(this.x - 32, this.y);

    this.setVisible(false);
    this.setPosition(0, 0);

    this.dead = true;
    this.respawnTime = 1.0;
    this.protectionCooldown = 1.0;
  }

  /**
   * Controls the events after the character's death.
   */
  function handleDeath() {
    // Control respawn time...
    if(this.respawnTime > 0.0) {
      this.respawnTime -= 0.01;
    }

    // Respawn character on a random location:
    else {
      this.setVisible(true);
      this.spawn();
      this.dead = false;
      this.protectionCooldown = 1.0;
      this.score = 0;
    }
  }

  /**
   * Spawn the character.
   */
  function spawn() {
    // The player always spawn on center of the arena...
    if(this.playable) {
      local center = this.canvas.getViewportCenter();
      this.setPosition(center.x - (this.width / 2), center.y - (this.height / 2) - 32);
    }

    // Enemies will respawn on random positions of the arena...
    else {
      local spawnX = (1.0 * this.canvas.viewport.width  * rand() / RAND_MAX).tointeger();
      local spawnY = (1.0 * this.canvas.viewport.height * rand() / RAND_MAX).tointeger();
      local speed  = (1.0 * 6.0 * rand() / RAND_MAX);

      if(speed <= 1.0) {
        speed = 1.0;
      }

      this.speed = speed;
      this.setPosition(spawnX, spawnY);
    }
  }

  /**
   * Controls the blinking effect while on spawn protection.
   */
  function handleSpawnProtection() {
    if(this.protectionCooldown > 0.0) {
      this.protectionCooldown -= 0.01;
      this.setVisible(this.frame % 2 == 0);
    }
    else {
      this.setVisible(true);
    }
  }

  /**
   * Controls attacks and position of player's sword.
   */
  function handleSword() {
    // This function can't continue without the target (sword):
    if(this.target == null) {
      return false;
    }

    // Mirror sword according to the player:
    this.target.setMirrored(this._mirrored);
    this.target.setVisible(true);

    // Position, animate and handle sword when it's being used...
    if(this.attacking) {
      this.target.setPosition(this.x, this.y);
      this.target.setFrames([1, 2, 3, 4]);
      this.target.setFrameSpeed(0.5);
      this.target.x -= this._mirrored? 64: 0;
    }

    // Stop sword when the player is not attacking...
    else {
      this.target.setPosition(this.x - 32, this.y - 32);
      this.target.setFrames([0]);
      this.target.setFrameSpeed(0.0);
      this.target.setFrame(0);
    }

    // Control attack cooldown:
    if(this.attackCooldown > 0.0) {
      this.attackCooldown -= 0.1;
    }

    // The sword will only deal damage on 2 specific frames...
    this.target.setCollisionsEnabled(this.target.frame == 2 || this.target.frame == 3);
  }

  /**
   * Handle controls for playable character.
   */
  function handlePlayer() {
    // This value will change if the character moves:
    this.running = false;

    // Move up...
    if(this.input.held("up")) {
      this.running = true;
      this.y -= this.speed;
    }

    // Move down...
    if(this.input.held("down")) {
      this.running = true;
      this.y += this.speed;
    }

    // Move left...
    if(this.input.held("left")) {
      this.running = true;
      this.setMirrored(true);
      this.x -= speed;
    }

    // Move right...
    if(this.input.held("right")) {
      this.running = true;
      this.setMirrored(false);
      this.x += speed;
    }

    // Attack...
    this.attacking = this.input.held("space");

    // Check for collisions...
    foreach(index, value in this.enemies) {
      if(this.intersect(value) && !this.dead && !value.dead && this.protectionCooldown <= 0.0) {
        this.target.setVisible(false);
        this.die();
        break;
      }
    }
  }

  /**
   * Handle controls for enemies.
   */
  function handleAI() {
    // Since this function does involve chasing a player, it needs a target to continue:
    if(this.target == null) {
      return false;
    }

    // Chase and look at the direction of player:
    if(!this.target.dead) {
      this.moveTo(this.target.x, this.target.y, this.speed);
      this.setMirrored(this.x > this.target.x);
    }

    // Check for collisions...
    if(this.intersect(this.target.target) && !this.dead && !this.target.dead && this.target.attackCooldown <= 0.0) {
      this.target.attackCooldown = 1.0;
      this.target.score += 1;
      this.die();
    }
  }

  /**
   * @event update
   */
  function update() {
    // Handle respawn if it's dead:
    if(this.dead) {
      this.handleDeath();
      return false;
    }

    // Keep character inbounds:
    this.stayWithinBoundaries();

    // Events for player...
    if(this.playable) {
      this.handleSword();
      this.handleSpawnProtection();
      this.handlePlayer();
    }

    // Events for enemies...
    else {
      this.handleAI();
    }
  }
}

// Background (arena)...
local obj_background = Vice2D.Sprite(512, 512).setImage(spr_background);

// Score counter...
local lab_counter = Vice2D.Label([fon_counter], "0");
      lab_counter.setPosition(0, -32);

// The player (the sword is passed as a target)...
local obj_player = Character(spr_player,  [0, 1, 2, 3, 4, 5, 6, 7], 0.2, 2, true, obj_sword);

// The enemies...
local obj_devil  = Character(spr_devil,   [ 8,  9, 10, 11, 12, 13, 14, 15], 0.2,   1, false, obj_player);
local obj_skull  = Character(spr_skull,   [16, 17, 18, 19, 20, 21, 22, 23], 0.2, 1.5, false, obj_player);
local obj_wizard = Character(spr_wizard,  [24, 25, 26, 27]                , 0.2,   2, false, obj_player);
local obj_stone  = Character(spr_stone,   [28, 29, 30, 31]                , 0.2, 2.5, false, obj_player);

// Let the player know who are the enemies so we can check collisions against them:
obj_player.enemies = [obj_devil, obj_skull, obj_wizard, obj_stone];

// The background is added first. We want it to appear below all the other objects.
game.push(obj_background);

// The score counter is added later. Characters will run above it, making it
// look like it's part of the floor.
game.push(lab_counter);

// Then, we add the enemies. They will chase the player around the arena.
game.push(obj_devil);
game.push(obj_skull);
game.push(obj_wizard);
game.push(obj_stone);

// Add the sword and the player.
game.push(obj_sword);
game.push(obj_player);

// And finally, we add the particles.
game.push(obj_particles);

/**
 * @event update
 */
game.addEventListener("update", function() {
  lab_counter.setText(obj_player.score.tostring());
});

/**
 * @event Script::ScriptProcess
 */
function Script::ScriptProcess() {
  game.loop();
}

/**
 * @event KeyBind::OnDown
 *
 * @param {instance} key Key pressed.
 */
function KeyBind::OnDown(key) {
  game.input.read(key, true);
}

/**
 * @event KeyBind::OnUp
 *
 * @param {instance} key Key released.
 */
function KeyBind::OnUp(key) {
  game.input.read(key, false);
}
[/noae][/noae]

The game looks like this:


Documentation:


All comments in the code are written in portuguese. Documentation is available in HTML and can be found on the links below.

Vice2D documentation in portuguese
Vice2D documentation in english (Google Translate)

Why?


After playing around with client-side scripting, I was thinking if wouldn't be nice to have effects and animations on screen when something happens during the game.

Sure, you can place some GUI elements here and there and you'll have a custom HUD. But what about the details? Imagine having a weapon selection where icons scale when you change a weapon, a firework effect when you win a prize, or even a new radar with animated blips! Unfortunately, GUI elements don't really look like they were intended to be moved around like that, and their styles don't seem to fit very well with what I was wanting to do. So I decided to look for another way of doing it.

Then, I decided to write a game library for VC:MP and see what could be done with it.

Uses


Although is not perfect, you should be able to create some cool games with it. Examples of games include casinos (poker, blackjack, slot machines, etc), arcade games (to play on the arcade machines inside Kaufman Cabs and pizza restaurants) or just extra mechanics for your gamemode.

Effects and animations, of course, are also possible. Take a little time to learn, throw some sprites into a core and just play around with it!

Xmair


Credits to Boystang!

VU Full Member | VCDC 6 Coordinator & Scripter | EG A/D Contributor | Developer of VCCNR | Developer of KTB | Ex-Scripter of EAD

Sebastian


Murdock


NicusorN5

This is better than the 2D games I used to make on Scratch XD

(Glad I'm using Monogame now btw)

MatheuS

if( !sucess ) tryAgain();
Thanks to the VCMP community. It was the happiest period of my life.


vitovc

hastebin.com links are dead. good I am making backups of valuable things
https://www.mediafire.com/file/qpor7ez6q1d7e7e/Vice2D.zip/file


...::: vice city :::...
Useful things for vcmp: Relative position and finding an angle (in 3d), 3d line (like laser)