Sidebar Menu

Projects

  • Dashboard
  • Research Project
  • Milestones
  • Repository
  • Tasks
  • Time Tracking
  • Designs
  • Forum
  • Users
  • Activities

Login

  • Login
  • Webmail
  • Admin
  • Downloads
  • Research

Twitter

Tweets by stumathews
Stuart Mathews
  • Home
  • Blog
  • Code
  • Running
  • Gaming
  • Research
  • About
    • Portfolio
    • Info

Christmas Period Tinkering

Details
Category: Game Development
By Stuart Mathews
Stuart Mathews
08.Jan
Parent Category: Code
29 December 2021
Last Updated: 08 January 2022
Hits: 4919
  • Programming
  • Game development
  • Artificial Intelligence
  • C++
  • WPF
  • Math
  • C#
  • OpenGL

I had a very productive Christmas this year.

I spend a lot of it reading various interesting things. For example, I spend about 2 or 3 days reading WPF 2nd edition which I found very good. The nice thing about this particular book is that in the very beginning, i.e, Chapter 1, it pretty much gives you a crash course touching most of the things that get their own dedicated chapter later in the book. I subscribe to this way of learning.

I only got through 3 chapters but in the process, I also created a set of representative artefacts or examples that practise using the concepts, so this made the reading, well somewhat slower but more useful. I wrote a series of training of practise applications that incorporated aspects from basic Xaml theory to creating windows, controls and styling them. Also covered layout and resources. The main reason I wanted to read this text is that I've got some upcoming work that needs some level of proficiency in WPF, so I thought I'd capitalize on the Christmas break. This is pretty much the theme of most of my other Christmas projects.

I also spend some time reason up on 'Game Engine Toolset Development', which is old but in my opinion still relevant as it touches on building utilities for games. I've got a game that could do with expansion, and the expansion would really be improved by better levels and a level editor would be a good utility to build. 

Alongside the above I've also been re-learning OpenGL, however, this time using the most up-to-date version, i.e 4.5 (in my formal training, we covered OpenGL 3.x). I like OpenGL and the combination of graphics programming, c++ and math are very appealing to me. So, in this respect, I've been reading the OpenGL Super Bible. I managed to get through the first 5 chapters and again, like my approach used for my WPF work, I decided to catalogue a list of representative examples to supplement the theory. 

Also, I decided to take some of the work that I did in my 3D project (OpenGL 3.x) and abstract some of the functions I wrote into a stand-alone shared library. First, I tried to make a dynamic library (DLL) to see if that would help, but it only really is useful for exporting specific and standalone C functions, and I have complete classes that I want to package up, so I used a static library which works well.

I basically re-wrote my 3D project by taking out all the custom logic I created and putting them in the library and then got the 3D project to reference the logic instead of from the shared library. This worked well. As a result, the overall game vehicle, which is basically a Windows app with a message pump, handling messages, windows resize events and keyboard input became nice and sparse. This is great because I can use it as a template. This is actually what I did moving forward while reading the Super Bible.

I created a new example for each notable concept documented in the bible and used the template to base them on. As a result, I have a series of examples, all using the template, my shared library and another shared library I created that houses some of the 3rd party game technology stuff, that Id didn't write myself, but that I used in my 3D project (created by staff/community etc), and which can still be re-used in other projects, like these examples.

I covered aspects such as creating a simple OpenGL app, creating basic shaders, using gl_VertexID, drawing simple triangles, using/updating vertex attributes, using interface blocks (uniforms), basic fragment shader etc.

I gave it a break for a day and read the first 5 chapters of 'AI Game Development: Synthetic Creatures with Learning and Reactive Behaviours. This is an old book, and some of the stuff is very specific to a framework that has since disappeared. I'm hoping to gather some interesting points, however, there is a good chance that I'll drop it and move onto a more current AI book.

I came back to OpenGL and covered aspects like using basic buffers, filling vertex attributes from a buffer (very useful) and making some examples of how to interleave or create blocks of vertex attribute data.  It's pretty interesting stuff and it's the kind of thing that if you spend too much time in the theory, results in you forgetting the intricacies a couple of days or months later, so a practical approach is essential in my opinion. 

I also read C++ Today which is an O'Reilly book that outlines the improvements and revolution that C++ is going through at the moment, and one which I've particularly excited about. 

 So quite a productive, useful and enjoying Christmas as I've had in a long time. This is actually a summary of what I did:

Date Tasks
 Dec 23
  • Read WPF 2nd edition
    • Chapter 1: Hello, WPF
    • Chapter 2: Applications and Settings
    • Chapter 3: Layout
  • Created WpfStepByStep:
    • Wpf1 - Basic
    • Wpf2 - App.Run
    • Wpf3 - Imperative Custom Window
    • Wpf4 - Custom Window using Xaml
    • Wpf5 - No Main Entry Point
    • Wpf6 - Content control 1
    • Wpf7 - Content control 2
    • Wpf8 - Content control 3
    • Wpf9 - Content control 4
    • Wpf10 - Grid
    • Wpf11 - Grid + events
    • Wpf13 - Resources
    • Wpf14 - Styles
    • Wpf15 - Control Templates
    • Wpf16 - Graphics
 Dec 24
  • Read Game Engine Toolset Development
    • Chapter 1: What is a tool? What is a Toolset?
    • Chapter 2: Why use C# Why use .Net?
    • Chapter 3: Examples of commercial Toolsets
    • Chapter 4: Everything starts with a plan
    • Chapter 5: Development Phases of a Tool
 Dec 25
  •  Read OpenGL Super Bible chapters 1-5
 Dec 26
  •  Read AI Game Development: Synthetic Creatures with Learning and Reactive Behaviors
    • Chapters 1-5
Dec 27
  • Create a static library to house re-usable cgt functions I created
    • moved from the game into the library
    • The game still works fine 
  • Created a dynamic library
  • Refactored game to use new libraries
  • Re-read OpenGL Super Bible chapters 1-4 
    • Created Understanding-OpenGL apps:
      • OpenGL1 - base OpenGL App
      • OpenGL2 - Basic shaders
      • OpenGL3 - gl_VertexID
      • OpenGL4 - Triangle with gl_VertexID
      • OpenGL5 - Simple vertex attribute update
      • OpenGL6 - passing variables between shaders
      • OpenGL7 - interface block variables
      • OpenGL8 - tessellation
      • OpenGL9 - gl_FragCoord
      • OpenGL10 - Fragment Shader interpolates
      • OpenGL11- Empty compute shader
Dec 28
  • Created Understanding-OpenGL apps:
    • OpenGL12 - Basic Buffers
    • OpenGL13 - glMapBufferRange
    • OpenGL14 - filling vertex attributes from a buffer
    • OpenGL15 - filling multiple vertex attributes
    • OpenGL16 - interleaved vertex buffer 
    • OpenGL17 - Uniforms
    • OpenGL18 - Unform Blocks
Dec 29
  • Read C++ Today
    • Very interesting

Mazer Game Architecture Report

Details
Category: Game Development
By Stuart Mathews
Stuart Mathews
22.Nov
Parent Category: Code
22 November 2020
Last Updated: 28 September 2021
Hits: 3568

The final output of the Computer Games Architecture class was the creation of a game engine using MonoGame to create and model a game. I found this particularly useful and fun.

I created a game called MonoMazer which uses the game engine I put together. Here is the guide I put together for the game MonoMazer which describes the high-level architecture. The source code is in the link. A little demonstration of the prototype follows afterwards.

Through my journey in putting together the game idea I needed to design it, including the story and coming up with an elevator 'pitch' - these were also really interesting and are covered in Mazer Game Design and Network Security and Protocols, Packets and Prototypes.

I've also ported the original game idea from C# to Ruby in Ruby Mazer. 

The game as I designed, which was based on an outworld mining planet it hasn't been fully realised in the below prototypes, but hopefully when I have some more time I can create it with more assets, animation and content.

This is the prototype that I submitted for grading early in 2020:

 

 

 

 

Ruby Mazer

Details
Category: Game Development
By Stuart Mathews
Stuart Mathews
26.May
Parent Category: Code
26 May 2020
Last Updated: 21 June 2020
Hits: 5251
  • Game development
  • Math
  • Ruby

 Since Set Theory, Ruby and Upgrades I've been learning a lot more ruby. I decided that in order to progress from the theoretical underpinnings that my previous reading had laid, I needed to actually do something productive and practical. I like gaming so I thought why not create a game. So that's pretty much what I did this weekend.

The key aspects of the game were that it obviously needed to be in ruby so I could practise the various features in the languages. I wanted to create an interactive game somewhat like my mazer prototype in Time shifting, CS algorithms and Game Architecture.

One thing I did not get around to doing in the original mazer prototype was actually creating real, solvable mazes. The mazes in Mazer weren't based on any maze algorithm like prim's (they could not be solved) - they were just randomly generated rooms that I then decided to furthermore remove arbitrary walls from to create an impression of a sort of broken maze that served as the game world. The initial mockup was created a while back in Mazer Game Design and Network Security.

It worked because the game didn't need to solve the maze, but this time I wanted to generate new mazes each time, that was solvable and then base the gameplay on a need to solve the maze.

I ended up implementing prim's algorithm to generate the maze dynamically and used the procedure to solve the maze. Both strategies (generating and solving) are implemented in the ruby code here. 

module Algorithms
class Prims

  # Visits room that don't have any neighbours yet and links them
  def self.on(maze, start_at)
    room_queue = []
    room_queue << start_at
    while room_queue.any?
      room = room_queue.sample
      available_neighbours = room.neighbours.select {|k,v| v.links.empty? }
      if available_neighbours.any?
        side = available_neighbours.keys.sample
        neighbour = available_neighbours[side]
        room.link_with(side)
        case side
          when :top
            neighbour.link_with(:bottom)
          when :bottom
            neighbour.link_with(:top)
          when :left
            neighbour.link_with(:right)
          when :right
            neighbour.link_with(:left)
        end

        room_queue << neighbour 
      else
        room_queue.delete(room)
      end
    end

    # print_links(maze)

    # return the maze
    maze
  end

  def self.print_links(maze)
    maze.each { |r| 
      puts "room #{r.number} has #{r.neighbours.size} neighbours and #{r.links.size} links configured" 
      puts "links"
      r.links.each {|l| puts l}
    }

  end
end

class Maze
  # Solves the maze by visiting every room from the starting point
  # to the ending point
  def self.solve(rooms, player_room_n, exit_room_n)
    queue = []
    queue << rooms[player_room_n]
    found = player_room_n == exit_room_n
    while queue.any? && !found
      room = queue.sample
      room.visit
      found = room.number == exit_room_n
      queue += room.links.map { |k,v| v } if !found
      queue.delete(room) 
    end 
    found    
  end
end

end

The principle of the algorithm is simple: visit a room that has not been visited before and when you get there, visit one of its neighbouring rooms randomly until you've run out of rooms. This also works for the solving the maze, only you go until you find the maze room you're looking for - in my case the room with the 'exit' in it. 

The next thing I wanted to do was implement collision detection (because I quite like physics) which I did using basic math to determine the intersection between two rectangles (all my characters were bounded by a rectangle as seen here).

There is no native class in ruby for a rectangle and I wanted to use my own calculations so I created a model of a rectangle that corresponds to the 4 points A-B would be the two top points of the rectangle and C-D would be the bottom lower points (going clockwise around).

require_relative 'point'
require_relative 'player' 

# The basis of collision detection in the game
# Also represents the dimensions of any rectangle using the below model A->B->D->C
class Rect

  #  A-----B
  #  |     |
  #  |     |
  #  |     |
  #  C-----D
  attr_accessor :x, :y, :w, :h, :a, :b, :c, :d

  def initialize(x, y, w, h)
    @x, @y, @w, @h = x, y, w, h
    set_points(@x, @y, @w, @h)
  end


  # Each point in the rectangle is represented by a Point class 
  def set_points(x, y, w,h)
    ax = x
    ay = y
    bx = ax + w
    by = ay
    dx = bx
    dy = by + h
    cx = ax
    cy = ay + h
   
    # Update or set the points for this rect - useful when updating for movement of points(moving characters) 
    if @a
      @a.set(ax,ay)
    else
      @a = Point.new(ax,ay)
    end

    if @b
      @b.set(bx,by)
    else
      @b = Point.new(bx,by)
    end
    if @d
      @d.set(dx,dy)
    else
      @d = Point.new(dx,dy)
    end
    if @c
      @c.set(cx,cy)
    else
      @c = Point.new(cx,cy)
    end
  end

  # Main collision detection routine - check if two rectangles intersect
  def collides_with_rect?(rect_b)
    ax1 = @a.x
    ax2 = @d.x
    ay1 = @a.y
    ay2 = @d.y
    
    b = rect_b

    bx1 = b.a.x
    bx2 = b.d.x
    by1 = b.a.y
    by2 = b.d.y

    collision =  ax1 < bx2 &&
      ax2 > bx1 &&
      ay1 < by2 && 
      ay2 > by1
    collision
  end

  def eql?(other)
    self == other
  end

end

So then a rectangle is made up of these 4 points (still is presumably!) which I further abstracted as 4 Point instances(proves access to simple cartesian coordinate for a point).

The long and short of it is that I needed a way to model the pictures that I scribbled down on the piece of paper next to me into concepts that I could control and model in the game.

This became crucial for collision detection of two rectangles. The overall result was a simple routine using these points to calculate an intersection as shown above(collides_with_rect? function): 

ax1 < bx2 && ax2 > bx1 && ay1 < by2 && ay2 > by1

I also modelled the maze in a similar way that I've done before where each wall of the room is an independent entity/side that can be collided with and independently removed - for example, if the player wants to destroy it(like in the original) or if the maze algorithm determines that two neighbouring cells should be linked - we remove the wall to form a passage.

Ultimately I didn't have enough time to implement the physics that would use it but decided to rather use the collision detection routine in another way: if you touched the walls(ie intersected) then your game points are reduced by the duration that you touch the wall. That's a game designer under time constraints for you...I wanted to be done by the end of the long weekend.

 

This is what it looks like.

The overall framework that controlled the game loop, much like MonoGame was a ruby gem called Gosu, which is a 2D library written in Ruby. It provided the hooks/methods for implementing the drawing and logic routines and manages the input control. This allowed me to focus on the gameplay. Other interesting aspects afforded by Gosu is support for png/jpg and sound assets in the form of Gosu::Image and Gosu::Music/Sound. (The game features background music and backdrop image)

Gosu also provides some drawing primitives to draw a rectangle using simple cartesian co-ordinates but surprisingly no way of modelling a rectangle as a class, such as SDL_Rect in SDL.

This is the entry point of the game and you can see how it all falls together, creating the maze, drawing the characters, handling input etc:

require './lib/player'
require './lib/utils'
require './lib/algorithms'
require './lib/room_builder'
require './lib/player_builder'
require './lib/audio_building'
require './lib/options'

class Game < Gosu::Window
  include RoomBuilding
  include PlayerBuilding
  include AudioBuilding
  extend Utils
  
  BACKGROUND = Utils.media_path('background.jpg') 
  @@points = 0
  @@level = 1
  
  # Initial game initialization and setup
  def initialize(width=800, height=600, options = { :fullscreen => false })
    super
    self.caption = 'Mazer Platformer in Ruby!'
    
    GameOptions::set_options(:show_solution => false)
    
    @background = Gosu::Image.new(BACKGROUND, {:tileable => false} )
    @success_sound = Gosu::Sample.new(Utils.media_path('complete.mp3'))
    @edge_sound = Gosu::Sample.new(Utils.media_path('buzzer.mp3'))
    @room_width = 50
    @room_height = 50
    
    set_cols_rows

    # -- Diagnostics -- 
    # @room_width = 100
    # @room_height = 100
    # @rows = 4
    # @cols = 4

    play_music
    create_level
  end

  def set_cols_rows
    @rows = height/@room_height
    @cols = width/@room_width
  end

  def create_level 
    # We only want to generate solvable mazes
    solved = false
    until solved do
      create_rooms(@rows, @cols, @room_width, @room_height)

      player_start_room_n = @rooms.sample.number
      exit_room_n = @rooms.sample.number

      @player = create_player :cube, @room_width, @room_height, @rows, @cols, player_start_room_n
      @exit = create_player :exit, @room_width, @room_height, @rows, @cols, exit_room_n
      
      # Generate a maze (randomly links rooms together i.e removes walls to make those links)
      Algorithms::Prims.on(@rooms, @rooms.sample)

      # Solve it to make sure its solvable, otherwise do it it again
      solved = Algorithms::Maze.solve(@rooms, player_start_room_n, exit_room_n)
    end
  end

  # Updates the game every frame
  def update

    # Get input from the player
    move_player(:up) if button_down?(Gosu::KbUp)
    move_player(:down) if button_down?(Gosu::KbDown)
    move_player(:left) if button_down?(Gosu::KbLeft)
    move_player(:right) if button_down?(Gosu::KbRight)

    # Update player and NPCs

    @player.update
    @exit.update

    # Check for player/room collisions 
    # reduce points on collision with walls
    @rooms.each { |room|
      if room.collides_with_rect?(@player.Rect)
        @@points -= 1
      end
    }

    # Check for win condition
    # spawn a new level if you've found the exit point
    if @player.Rect.collides_with_rect?(@exit.Rect)
      generate_next_level
    end

  end

  def generate_next_level
      @@level += 1
      @@points += 1000
      @success_sound.play
      @room_width -= 1 
      @room_height -= 1
      set_cols_rows
      create_level
  end


  # Draws the game every frame
  def draw
    @background.draw(0,0,0)
    
    @rooms.each  { |room| room.draw }
    @player.draw
    @exit.draw

    @hud = Gosu::Image.from_text(self, stats, Gosu::default_font_name, 30) 
    @hud.draw(10, 10, 0)
  end

  # Input Control
  def button_down(id)

    # Cheat - press 'r' to skip to next level
    if id == Gosu::KbR
      generate_next_level
    end

    options = GameOptions::get_options

    # Show/hide maze solution
    if id == Gosu::KbS
      show_solution = options[:show_solution]
      options.merge!({ :show_solution =>!show_solution })
      GameOptions::set_options(options)
    end 
    close if id == Gosu::KbEscape
  end

  def move_player(direction)
    case direction
    when :up 
      @player.move_up
    when :down
      @player.move_down
    when :left
      @player.move_left
    when :right
      @player.move_right
    end
  end

  # Called before update() if button is released
  def button_up(id)

  end

  # Determines if its needed to draw an new frame
  def needs_redraw?
    true
  end

  def stats
    "fps:#{Gosu.fps} Level: #{@@level} Points: #{@@points}"
    # "Level: #{@@level} Points: #{@@points}"
  end

end

puts "Gosu version=#{Gosu::VERSION}"
puts "License=#{Gosu::LICENSES}"
game = Game.new

# Enters the main game loop
game.show

I was able to animate the player sprite, however, ran out of time to get the logic in place to control the drawing of the appropriate frame for the player's direction (something like I here in this) so in the interest of time - I also cut this feature and instead implemented a non-animated player abstraction rendering simple coloured cubes (white for the player and red for the exit point). 

The premise of the game evolved such that you need to connect objects on either side of the maze, you can obviously move in order to get to the other object however you've got the maze to content with. If you touch the walls, it will zap points from you, and a clear path exists to the other exit point by traversing through the maze(you just need to find it). Once you capture the object, you get 1000 points and you then progress to the next maze which is incrementally smaller. As you level up, the need to pay attention to your surrounding so it's more difficult.

The player and the exit point are randomly chosen and then the maze algorithm will build a maze and then we'll try and solve it between the two random points. If we can't(not all mazes are solvable), then we re-create the maze and place the characters again until we come across a solvable maze and then that becomes the level.

If you don't believe that maze is solvable - I didn't in the beginning, you can press a cheat key and show the maze solution on-screen - this retraces the prior solving algorithm and shows all rooms that were inspected for the exit object until it was found. That's how I could be sure that the reason I couldn't solve the mazes was that I really couldn't solve the mazes(not that they were unsolvable!)

In theory, the game never ends - it just keeps generating smaller and smaller mazes. You quit when you want to - I didn't actually implement a die condition! So, in theory, you'd just get into negative points if you keep touching walls, infinity otherwise. A simple objective would be to go for as long as possible and record which level you ended up at and your total accumulated points, you could then compare it with other's results to see who's better!

I wanted to actually finish this game so I decided that this was it, no more features - get it playable, presentable and go to bed. There are of course other things I could add to it like new backgrounds and music on each level, animated characters as well as walls that you could not go through. The list continues...

I also learnt how RSpec works. I implemented some tests to ensure the correctness of my room generation. For example, I wanted to ensure that my rows were indexed at row 0 and my columns started at col 0. How do you test this behaviour...

 example 'every row reports consequtive columns' do
    for r in 0..ROWS-1 do
      for c in 0..COLS-1 do
        expect(subject[r*COLS+c].col).to eq(c)
      end
    end
  end

  example 'every row is consequative' do
    for r in 0..ROWS-1 do
      for c in 0..COLS-1 do
        expect(subject[r*COLS+c].row).to eq(r)
      end
    end
  end

I also decided to model my rooms as a grid of ROWSxCOLS. Did I use a 2-dimensional array like a normal person? No. 

In hindsight, it would have probably been easier, however working out the logic to represent a 2D array as a 1D array was fun and in actual fact, this is how 2D arrays are laid out in memory anyway. It's just the language that nicely provides accessors to get at rows and cols in the familiar array[row][col] notation.

I remember my lecturer going through that in my C programming course. Something must have stuck.

More Articles …

  1. Protocols, Packets and Prototypes
  2. Pleasure And the Execution of Thoughfulness
  3. Some more Direct X10
  4. Animated aliens and alternatives algorithms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Page 3 of 9

Blog RSS Feed