Unit gameObjectsSprite

The code of gameObjectsSprite

unit gameObjectsSprite;
{
    Copyright (c) 2011 Christopher Winward
    
    Licensed under the Apache License, Version 2.0 (the "License"); you may not 
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, sdl, sdl_image, gameVariables, gl, glu;

type TSpriteStats = record
  Alpha : integer; //Alpha blending
  rotationValue, zoomValue : double; //Rotating and zooming
end;

type TSprite = class(TObject)
  private
    x, y : S16; //This is where the sprite will blit
    baseImageStr : PChar; //The location of the bmp sprite image
    spriteWidth, spriteHeight : U16; //For loading the entire image
    frameWidth, frameHeight, frameNum, numFrames, animNum, numAnims, frameToLoop : U16; //For drawing specific animation segments
    transparent : boolean; //Whether to draw the topleft pixel colour or not
    Alpha : integer; //Alpha blending
    colour : PColour;
    rotationValue, zoomValue : F64; //Rotating and zooming
    glSprite : GLUInt;

  public
    procedure Init(baseImageStrX : string; transparentX : boolean = false);
    procedure SetXY(xx,yy : real; Xrelative : boolean = true; Yrelative : boolean = true);
    procedure SetFrameSize(widthX1, heightX1, widthX, heightX : U16);
    procedure SetAnimations(numFramesX, numAnimsX : U16);
    procedure SetFrame(frameNumX : U8; relative : boolean = false);
    procedure SetAlpha(value : U16; relative  :boolean = false);
    procedure SetColour(rx, gx, bx : U8);
    procedure SetRotation(value : double; relative : boolean = false);
    procedure SetZoom(value : double; relative : boolean = false);
    procedure SetAnim(animNumX, numFramesX : U16; frameToLoopX : U16 = 1);
    function GetStats : TSpriteStats;
    function GetFrameWidth : U16;
    function GetFrameHeight : U16;
    function GetGlSprite : GLUInt;
    procedure DrawSprite;
end;

type PSprite = ^TSprite;

type spriteRecord = record
  sprName : string;
  ID : U16;
  //glSprSprite : GLUint;
  Sprite : TSprite;
end;

type PSpriteRecord = ^spriteRecord;

var
  spriteBank : array[1 .. maxSprites] of SpriteRecord;
  //scaleVal : F32;

procedure LoadSpriteArray(nameX : string; transparentX : boolean);
function GetSprRecord(nameX : string) : PSpriteRecord;
function LoadGLTextures(fileLoad : PChar) : GLUInt;

implementation

procedure TSprite.Init(baseImageStrX : string; transparentX : boolean = false);
begin
  x := 0;
  y := 0;

  baseImageStr := PChar(baseImageStrX);
  transparent := transparentX;

  frameNum := 1;
  animNum := 1;
  numFrames := 1;
  numAnims := 1;
  frameToLoop := 1;
  frameWidth := 1;
  frameHeight := 1;

  Alpha := 255; //Totally opaque
  rotationValue := 0;
  zoomValue := 1; //Zoom to 1x

  NEW(colour);
  colour^.r := 255;
  colour^.g := 255;
  colour^.b := 255;
end;

procedure TSprite.SetXY(xx, yy : real; Xrelative : boolean = true; Yrelative : boolean = true);
begin
  if (XRelative) then
    begin
      x += round(xx);
    end
  else
    begin
      x := round(xx);
    end;

  if (YRelative) then
    begin
      y += round(yy);
    end
  else
    begin
      y := round(yy);
    end;
end;

procedure TSprite.SetFrameSize(widthX1, heightX1, widthX, heightX : U16);
begin
  spriteWidth := widthX1;
  spriteHeight := heightX1;
  frameWidth := widthX;
  frameHeight := heightX;
end;

procedure TSprite.SetAnimations(numFramesX, numAnimsX : U16);
begin
  numFrames := numFramesX;
  numAnims := numAnimsX;

  if (numFrames < 1) then numFrames := 1;
  if (numAnims < 1) then numAnims := 1;
end;

procedure TSprite.SetFrame(frameNumX : U8; relative : boolean = false);
begin
  if (relative = true) then
    begin
      frameNum += frameNumX
    end
  else
    begin
      frameNum := frameNumX
    end;
end;

procedure TSprite.SetAlpha(value : U16; relative : boolean = false);
begin
  if (relative) then
    begin
      Alpha += value;
    end
  else
    begin
      Alpha := value;
    end;
end;

procedure TSprite.SetColour(rx, gx, bx : U8);
begin
  colour^.r := rx;
  colour^.g := gx;
  colour^.b := bx;
end;

procedure TSprite.SetRotation(value : double; relative : boolean = false);
begin
  if (relative) then
    begin
      rotationValue += value;
    end
  else
    begin
      rotationValue := value;
    end;
end;

procedure TSprite.SetZoom(value : double; relative : boolean = false);
begin
  if (relative) then
    begin
      zoomValue += value;
    end
  else
    begin
      zoomValue := value;
    end;
end;

procedure TSprite.SetAnim(animNumX, numFramesX : U16; frameToLoopX : U16 = 1);
begin
  frameNum := 1;
  animNum := animNumX;
  numFrames := numFramesX;
  frameToLoop := frameToLoopX;
end;

function TSprite.GetStats : TSpriteStats;
begin
  result.Alpha := Alpha;
  result.rotationValue := rotationValue;
  result.zoomValue := zoomValue;
end;

function TSprite.GetFrameWidth : U16;
begin
  result := FrameWidth;
end;

function TSprite.GetFrameHeight : U16;
begin
  result := FrameHeight;
end;

function TSprite.GetGlSprite : GLUInt;
begin
  result := GlSprite;
end;

procedure TSprite.DrawSprite;
var
  frameStartX, frameStartY, frameEndX, frameEndY : F64;
begin
  if (Alpha > 255) then
    Alpha := 255;
  if (Alpha < 0) then
    Alpha := 0;
  if (Alpha > 0) then
    begin
      //1. READ THE COMMENTS IN ORDER OF NUMBER

      glPushMatrix; //Enter the Matrix //2. This is like saying "save the current state, and create and go to
                                       //3. a new state that is identical to the current one. Check the glPopMatrix comment
      glLoadIdentity;
           //FOR ANIMATION, MAKE A 2D ARRAY OF TEXTURES, SPLIT INTO ANIMATION TREE AND FRAME

      glPushMatrix; //7.This is all the important stuff. For some weird reason, OpenGL performs a few commands backwards. Read on below
      glTranslateF(x + (getFrameWidth div 2), y + (getFrameHeight div 2), 0); //11. Translate back into the normal place.

      glScalef(zoomValue, zoomValue, 1); //17. Scale the sprite as appropriate.
      glRotatef(rotationValue, 0.0, 0.0, 1.0); //16. Rotate the sprite around your angle, in this case, rotationValue around the Z axis
      glTranslateF(- x -(getFrameWidth div 2), - y -(getFrameHeight div 2), 0); //15. Rotation is always performed around the origin (0,0,0). So move the position to (0,0,0).

      frameStartX := (2 + frameNum + (frameNum * FrameWidth)) / spriteWidth;
      frameEndX := frameStartX + ((frameWidth - 1) / SpriteWidth);

      frameStartY := (2 + animNum - 1 + ((animNum - 1) * FrameHeight)) / spriteHeight;
      frameEndY := frameStartY + ((frameHeight - 2) / SpriteHeight);

      //glColor3f(1,0,0);

      if (transparent) then
        begin
          glEnable(GL_Blend);
          glBlendFunc(GL_DST_COLOR, GL_ZERO);
          //8. Bind the current gl texture to the alpha mask
          glBindTexture( GL_TEXTURE_2D, spriteBank[hashString(uppercase(baseImageStr + '_alpha'), maxSprites)].Sprite.glSprite );
          glBegin(GL_QUADS); //9. Get ready to draw quads (squares)
            glTexCoord2f(frameStartX, frameStartY );
            glVertex3f(x, y, 0); //10. Go to the top left of the texture, and then set a vertex at the top left of where the sprite should go.
            glTexCoord2f(frameEndX, frameStartY);
            glVertex3f(x + getFrameWidth, y, 0); //11. Draw the top right
            glTexCoord2f(frameEndX, frameEndY);
            glVertex3f(x + getFrameWidth, y + getFrameHeight, 0); //12. Bottom right
            glTexCoord2f(frameStartX, frameEndY);
            glVertex3f(x, y + getFrameHeight, 0); //13. Bottom left
          glEnd();
          glBlendFunc(gl_one, gl_one);
        end;
      //14. Now do the same but for the actual sprite.
      //writeln(hashString(baseImageStr,maxSprites));
      glBindTexture( GL_TEXTURE_2D, spriteBank[hashString(baseImageStr,maxSprites)].Sprite.glSprite );
      glColor3f(colour^.r / 255, colour^.g / 255, colour^.b / 255);
      glBegin(GL_QUADS);
        glTexCoord2f(frameStartX, frameStartY);
        glVertex3f(x, y, 0);
        glTexCoord2f(frameEndX, frameStartY);
        glVertex3f(x + getFrameWidth, y, 0);
        glTexCoord2f(frameEndX, frameEndY);
        glVertex3f(x + getFrameWidth, y + getFrameHeight, 0);
        glTexCoord2f(frameStartX, frameEndY);
        glVertex3f(x, y + getFrameHeight, 0);
      glEnd();
      if (transparent) then
        glDisable(GL_Blend);
      glPopMatrix;
      glPopMatrix; //Leave the Matrix //4. This is like saying "We're done with the current state, forget everything about
    end;                              //5. it and go back to the state we were at when we performed glPushMatrix"
end;                                //6. By state, I'm referring to like rotations, translations, scales and other gl commands

procedure LoadSpriteArray(nameX : string; transparentX : boolean);
var
  inputSpriteName : string;
  spriteAddress : U16;
  pushNum : U16;
begin
  inputSpriteName := uppercase(nameX);
  spriteAddress := hashString(inputSpriteName,maxSprites);
  pushNum := 0;

  while (not (spriteBank[spriteAddress].sprName = '') and not (spriteBank[spriteAddress].sprName = inputSpriteName)) do
    begin
      inc(pushNum);
      writeln('Pushing for load ', inputSpriteName);
      spriteAddress := hashString(inputSpriteName, maxSprites) + pushNum;
    end;

  if (pushNum > 0) then
    begin
      writeln(spriteAddress,' compared to ', hashString(inputSpriteName,maxSprites));
    end;

  if (not (spriteBank[spriteAddress].sprName = inputSpriteName)) then
    begin
      with spriteBank[spriteAddress] do
        begin
          sprName := inputSpriteName;
          ID := spriteAddress;
          sprite := TSprite.create;
          sprite.Init(nameX, transparentX);
          sprite.glSprite := LoadGLTextures(PChar('images\' + nameX + '.png'));
        end;
      writeln('Sprite ', inputSpriteName, '.png loaded at ', spriteAddress);
    end
  else
    begin
      //writeln(inputSpriteName,' already exists in the bank at position #', spriteAddress);
    end;
end;

function GetSprRecord(nameX : string) : PSpriteRecord;
var
  inputSpriteName : string;
  spriteAddress, pushNum : U16;
begin
  inputSpriteName := uppercase(nameX);
  spriteAddress := hashString(inputSpriteName, maxSprites);
  pushNum := 0;
  while (not (spriteBank[spriteAddress].sprName = inputSpriteName)) do
    begin
      inc(pushNum);
      spriteAddress := hashString(inputSpriteName, maxSprites) + pushNum;
    end;
  result := @spriteBank[spriteAddress];
end;

// Load Bitmaps And Convert To Textures
function LoadGLTextures(fileLoad:PChar) : GLUInt;
var
  // Create storage space for the texture
  TextureImage : PSDL_Surface;
  OutputTexture : GLUInt;
begin
  // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
  TextureImage := IMG_Load(fileLoad);
  if (rightstr(fileLoad, 3) = 'bmp') then
    TextureImage := SDL_LoadBMP(fileLoad);
  //sdl_setalpha(textureImage,0,0);
  if ( TextureImage <> nil ) then
    begin
      // Create Texture
      glGenTextures(1, @OutputTexture);
      // Typical Texture Generation Using Data From The Bitmap
      glBindTexture(GL_TEXTURE_2D, OutputTexture);

      // Generate The Texture
      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage^.w,
                   TextureImage^.h, 0, GL_RGB,
                   GL_UNSIGNED_BYTE, TextureImage^.pixels);
      // Linear Filtering
      // scale linearly when image bigger than texture
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      // scale linearly when image smaller than texture
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    end
  else
    begin
      writeln('Error #GL0002 loading ', fileLoad);
      readln;
      halt;
    end;

  // Free up any memory we may have used
  if (TextureImage <> nil ) then
    SDL_FreeSurface(TextureImage);

  result := outputTexture;
end;

end.

Remarks

Could you write a game that uses routines in these SpaceShooter units?

Programming - a skill for life!

by Christopher Winward: L6 Age ~16