Simple Platformer Engine

This is the place to post any code that you want to share with the community. Only completed scripts should be posted here.
Note: posts here are not necessarily endorsed by the community, and may represent amateur or even bad practices.

Simple Platformer Engine

Postby Yambam » Thu Jul 07, 2016 11:47 am

Simple Platformer Engine

This is a simple platformer engine with an image-based collision engine and basic platformer mechanics such as jumping, wall jumping, double jumping and walking up/down ramps. If you modify the external terrain sprite, use a black color for drawing platforms (converted to grass/dirt automatically in-game) and use red for dangerous elements/obstacles, such as spikes and mines.

Download link: http://host-a.net/u/YambamYambam2/Graphformer.zip
Last edited by micseydel on Thu Jul 07, 2016 4:13 pm, edited 1 time in total.
Reason: Initial post lock. Moved to completed scripts.
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby Yambam » Fri Jul 08, 2016 2:03 pm

Screenshot:
Image

Notable changes:
  • Now uses delta timing, so the gameplay is consistent on different specs of computers (most notably RPi 2/3, running at 19 FPS, same gameplay/character movement speed).
  • Added a new unfinished level.

P.S. Can I edit my previous posts, or do I have to wait until I have made a certain number of posts?
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby ichabod801 » Fri Jul 08, 2016 2:30 pm

Yambam wrote:P.S. Can I edit my previous posts, or do I have to wait until I have made a certain number of posts?

The mods lock your first post, you can edit posts after the first. It relates to problems we've had with people trying to cheat on their CS homework.
Due to the reasons discussed here we will be moving to python-forum.io on October 1st, 2016.
This forum will be locked down and no one will be able to post/edit/create threads, etc. here from thereafter. Please create an account at the new site to continue discussion.
ichabod801
 
Posts: 688
Joined: Sat Feb 09, 2013 12:54 pm
Location: Outside Washington DC

Re: Simple Platformer Engine

Postby Yambam » Fri Jul 08, 2016 4:09 pm

ichabod801 wrote:
Yambam wrote:P.S. Can I edit my previous posts, or do I have to wait until I have made a certain number of posts?

The mods lock your first post, you can edit posts after the first. It relates to problems we've had with people trying to cheat on their CS homework.

Ah I see. What do you think of my engine though? :)
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby ichabod801 » Fri Jul 08, 2016 7:04 pm

Yambam wrote:Ah I see. What do you think of my engine though? :)


I didn't mess with it. I don't download unknown zip files.
Due to the reasons discussed here we will be moving to python-forum.io on October 1st, 2016.
This forum will be locked down and no one will be able to post/edit/create threads, etc. here from thereafter. Please create an account at the new site to continue discussion.
ichabod801
 
Posts: 688
Joined: Sat Feb 09, 2013 12:54 pm
Location: Outside Washington DC

Re: Simple Platformer Engine

Postby Yambam » Fri Jul 08, 2016 8:36 pm

ichabod801 wrote:
Yambam wrote:Ah I see. What do you think of my engine though? :)


I didn't mess with it. I don't download unknown zip files.


Sorry about that. Here are the contents of the zip. game.py:
Code: Select all
import pygame
#import pygame.mixer
import math
from random import random

#pygame.mixer.pre_init(44100, -16, 2, 2048)
#pygame.mixer.init()

pygame.init()
gameHUD = pygame.Surface((640,480),pygame.SRCALPHA)
gameDisplay = pygame.display.set_mode((640,480))
pygame.display.set_caption('Graphformer')
pygame.mouse.set_visible(0)
clock = pygame.time.Clock()

black = (0,0,0)
white = (255,255,255)
background_color = (72,205,255)

gameDisplay.fill(background_color)

useSurf = pygame.Surface([640,480], pygame.SRCALPHA, 32)
useSurf = useSurf.convert_alpha()

_shape_count=0

_shape_sprite = [0]*1000
_shape_w = [0]*1000
_shape_h = [0]*1000
_shape_xoffset = [0]*1000
_shape_yoffset = [0]*1000
_shape_col_start = [0]*1000
_shape_col_num = [0]*10000
_shape_data = [[0 for x in range(480)] for y in range(15000)]
_shape_data_colors = [[0 for x in range(480)] for y in range(15000)]

keyboard_keys = [0]*256
keyboard_keys_prev = [0]*256

fps_elements = [0]*60
fps_pos = 0

alt_wall_jump = True

def collInitImage(image,center):
   "Initialize an image for collision detection."
   global gameDisplay,_shape_sprite,_shape_w,_shape_h,_shape_xoffset,_shape_yoffset,_shape_count,_shape_col_start,_shape_col_num,_shape_data,_shape_data_colors
   
   _shape_sprite[_shape_count]=image
   _shape_w[_shape_count]=image.get_width()
   _shape_h[_shape_count]=image.get_height()
   _shape_xoffset[_shape_count]=image.get_width()/2 if center else 0
   _shape_yoffset[_shape_count]=image.get_height()/2 if center else 0
   if _shape_count>=1:
      _shape_col_start[_shape_count]=_shape_col_start[_shape_count-1]+_shape_w[_shape_count-1]
   else:
      _shape_col_start[_shape_count]=0
   
   for xx in range(_shape_col_start[_shape_count],_shape_col_start[_shape_count]+_shape_w[_shape_count]):
      _shape_col_num[xx]=0
      inside=0
      col = (0,0,0,0)
      colp = col
      for yy in range(0,_shape_h[_shape_count]):
         col = image.get_at((xx-_shape_col_start[_shape_count],yy))
         r=1
         while(r>0):
            if col[3]==255 and col!=(255,255,255,255) and not (yy!=0 and col!=colp and r==1):
               if not inside:
                  inside=1
                  _shape_data[xx][_shape_col_num[xx]]=yy
                  _shape_data_colors[xx][_shape_col_num[xx]]=col
                  _shape_col_num[xx]+=1
            else:
               if inside:
                  col=image.get_at((xx-_shape_col_start[_shape_count],yy-1))
                  inside=0
                  _shape_data[xx][_shape_col_num[xx]]=yy
                  _shape_data_colors[xx][_shape_col_num[xx]]=col
                  if _shape_data[xx][_shape_col_num[xx]]-_shape_data[xx][_shape_col_num[xx]-1]>=1:
                     vertical_line = pygame.Surface((1, _shape_data[xx][_shape_col_num[xx]]-_shape_data[xx][_shape_col_num[xx]-1]), pygame.SRCALPHA)
                     vertical_line.fill(col)
                     gameDisplay.blit(vertical_line,(xx,_shape_data[xx][_shape_col_num[xx]-1]))
                  _shape_col_num[xx]+=1
               if yy!=0 and col!=colp and r==1:
                  r += 1
            colp = col
            r -= 1
      if inside:
         inside=0
         _shape_data[xx][_shape_col_num[xx]]=yy
         _shape_data_colors[xx][_shape_col_num[xx]]=col
         if _shape_data[xx][_shape_col_num[xx]]-_shape_data[xx][_shape_col_num[xx]-1]>=1:
            vertical_line = pygame.Surface((1, _shape_data[xx][_shape_col_num[xx]]-_shape_data[xx][_shape_col_num[xx]-1]), pygame.SRCALPHA)
            vertical_line.fill(col)
            gameDisplay.blit(vertical_line,(xx,_shape_data[xx][_shape_col_num[xx]-1]))
         _shape_col_num[xx]+=1
   
   _shape_count+=1
   return _shape_count-1

def collCheck(instance,x,y,obj,color=(-1,-1,-1)):
   "Check for a collision between objects."
   global gameDisplay,instances,_shape_sprite,_shape_w,_shape_h,_shape_xoffset,_shape_yoffset,_shape_count,_shape_col_start,_shape_col_num,_shape_data,_shape_data_colors
   xx=x-_shape_xoffset[instance.shape]
   yy=y-_shape_yoffset[instance.shape]
   
   w=_shape_w[instance.shape]
   h=_shape_h[instance.shape]
   
   for inst in instances:
      if inst!=instance and inst==obj:
         if inst.y+_shape_yoffset[inst.shape]>=yy+h:
            continue
         
         _h=_shape_h[inst.shape]
         if yy>=inst.y+_shape_yoffset[inst.shape]+_h:
            continue
         
         _w=_shape_w[inst.shape]
         __x=int(inst.x-_shape_xoffset[instance.shape]-xx+_shape_col_start[instance.shape])
         for _x in range(_shape_col_start[inst.shape],_shape_col_start[inst.shape]+_w):
            if __x<_shape_col_start[instance.shape]:
               __x+=1
               continue
            if __x>=_shape_col_start[instance.shape]+w:
               break
            if _shape_col_num[__x]==0:
               __x+=1
               continue
            
            _i=1
            for i in range(1,_shape_col_num[_x],2):
               if inst.y-_shape_yoffset[inst.shape]+_shape_data[_x][i-1]>yy+_shape_data[__x][_i]:
                  _break = 0
                  for _i in range(_i,_shape_col_num[__x],2):
                     if y-_shape_yoffset[inst.shape]+_shape_data[__x][i-1]<=yy+_shape_data[__x][_i]:
                        _i += 1
                        _break = 1
                        break
                  if _i >= _shape_col_num[__x] or _break:
                     _i += 1
                     break
               if inst.y-_shape_yoffset[inst.shape]+_shape_data[_x][i]>=yy+_shape_data[__x][_i-1]:
                  #pygame.draw.line(gameHUD,(random()*255,random()*255,255),(_x-_shape_xoffset[inst.shape]-_shape_col_start[inst.shape]+inst.x,inst.y-_shape_yoffset[inst.shape]+_shape_data[_x][i-1]),(_x-_shape_xoffset[inst.shape]-_shape_col_start[inst.shape]+inst.x,inst.y-_shape_yoffset[inst.shape]+_shape_data[_x][i]))
                  if color[0]==-1 or _shape_data_colors[_x][i]==color:
                     return True
            
            __x+=1
   
   return False

running = True
sprPlayerR = pygame.image.load('player.png').convert_alpha()
sprPlayerL = pygame.transform.flip(sprPlayerR.copy(),1,0)
sprPlayerMask = pygame.image.load('player_mask.png').convert_alpha()
bgClouds = pygame.image.load('clouds.png').convert_alpha()
bgForeground = pygame.image.load('foreground.png').convert_alpha()
sprTerrain = pygame.image.load('terrain.png').convert_alpha()
shpTerrain = collInitImage(sprTerrain,0)

_w=_shape_w[shpTerrain]
for _x in range(_shape_col_start[shpTerrain],_shape_col_start[shpTerrain]+_w):
   for i in range(0,_shape_col_num[_x],2):
      if _shape_data_colors[_x][i]==(0,0,0):
         vertical_line = pygame.Surface((1,_shape_data[_x][i+1]-_shape_data[_x][i]+random()*2), pygame.SRCALPHA)
         vertical_line.fill((128+32*(random()),32,0))
         sprTerrain.blit(vertical_line,(_x-_shape_col_start[shpTerrain],_shape_data[_x][i]))
         
         vertical_line = pygame.Surface((1,3+random()*2), pygame.SRCALPHA)
         vertical_line.fill((32*(_x%8),255,0))
         sprTerrain.blit(vertical_line,(_x-_shape_col_start[shpTerrain],_shape_data[_x][i]))

shpPlayer = collInitImage(sprPlayerMask,0)
shpTerrain = collInitImage(sprTerrain,0)

#sndJump = pygame.mixer.Sound('jump.wav')
#sndGrass = pygame.mixer.Sound('grass.wav')
#sndDeath = pygame.mixer.Sound('death.wav')
#sndSlide = pygame.mixer.Sound('slide4.wav')

fullscreen = False
viewX = 0
viewY = 0
viewW = 640
viewH = 480

class Instance(object):
   x = 0.0
   y = 0.0
   sprite = 0
   shape = 0
   hspeed = 0.0
   vspeed = 0.0
   visible = 1
   center_origin = 0

   # The class "constructor" - It's actually an initializer
   def __init__(self, x, y, sprite, shape, center_origin):
      self.x = x
      self.y = y
      self.sprite = sprite
      self.shape = shape
      self.center_origin = center_origin

instances = []
instances.append(Instance(0,0,sprTerrain,shpTerrain,0))
instances.append(Instance(-320,0,sprPlayerMask,shpPlayer,0))
instances.append(Instance(320,240,sprPlayerR,shpPlayer,0))
objTerrain = instances[0]
objMouse = instances[1]
objPlayer = instances[2]

objPlayer.candj = True
objPlayer.slowdown = False

ct = float(pygame.time.get_ticks())
pt = ct-1000/60

while running:
   pt = ct
   ct = float(pygame.time.get_ticks())
   if ct==pt:
      ct+=1000/120
   
   fps_elements[fps_pos] = ct-pt
   fps_pos = (fps_pos+1)%60
   
   if True or fps_pos==1:
      fps = 0.0
      for i in range(0,60):
         fps += fps_elements[i]
      fps = 60000/max(.001,fps)
   pygame.display.set_caption('Graphformer - FPS: '+str(round(fps*10)/10)+"/60")

   for i in range(0,255):
      keyboard_keys_prev[i] = keyboard_keys[i]
   
   # Events
   for event in pygame.event.get():
      if event.type == pygame.QUIT or keyboard_keys[1] or keyboard_keys[9]:
         running = False
      elif event.type == pygame.MOUSEMOTION:
         objMouse.x = viewX + int(event.pos[0])
         objMouse.y = viewY + int(event.pos[1])
         #if collCheck(objMouse,objMouse.x,objMouse.y,objTerrain):
         #   objMouse.visible = random()<.5
         #else:
         #   objMouse.visible = 1
      elif event.type == pygame.MOUSEBUTTONDOWN:
         objPlayer.x = viewX + event.pos[0]
         objPlayer.y = viewY + event.pos[1] - sprPlayerR.get_height()
         objPlayer.vspeed = 0
      elif event.type == pygame.KEYDOWN:
         keyboard_keys[event.scancode] = 1
         #print(event.scancode)
         if event.scancode == 87 or event.scancode == 95:
            display = (640,480)
            if pygame.display.get_driver()=='x11':
               pygame.display.toggle_fullscreen()
            else:
               acopy=gameDisplay.copy()                   
            if fullscreen:
               gameDisplay=pygame.display.set_mode(display)
            else:
               gameDisplay=pygame.display.set_mode(display, pygame.FULLSCREEN)
               fullscreen=not fullscreen
               gameDisplay.blit(acopy,(0,0))
               pygame.display.update()
      elif event.type == pygame.KEYUP:
         keyboard_keys[event.scancode] = 0
      elif event.type == pygame.ACTIVEEVENT:
         _do_ = 'nothing'
      elif event.type == pygame.VIDEOEXPOSE:
         print('Game loaded!')
      else:
         print(event)
   
   # Step
   if ct>1000 and not collCheck(objPlayer,objPlayer.x,objPlayer.y+1,objTerrain):
      objPlayer.vspeed += 0.125*max(0.0001,(ct-pt)/1000*60)
      #print(.125/max(.0001,1000/60/(ct-pt)*1000/60/(ct-pt)))
      #print(objPlayer.vspeed)
   if alt_wall_jump and objPlayer.vspeed>1 and (collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain) or collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain)):
      objPlayer.vspeed = .5*objPlayer.vspeed+.5*1
      #if not objPlayer.slowdown:
      #   sndSlide.play()
      objPlayer.slowdown = True
   else:
      objPlayer.slowdown = False
   if not collCheck(objPlayer,objPlayer.x,objPlayer.y+objPlayer.vspeed*(ct-pt)/1000*60,objTerrain):
      objPlayer.y += objPlayer.vspeed*(ct-pt)/1000*60
   else:
      while not collCheck(objPlayer,objPlayer.x,objPlayer.y+math.copysign(1,objPlayer.vspeed),objTerrain):
         objPlayer.y += math.copysign(1,objPlayer.vspeed)
      if objPlayer.vspeed>0:
         #sndGrass.play()
         objPlayer.candj = True
      objPlayer.vspeed = 0
   if collCheck(objPlayer,objPlayer.x,objPlayer.y+1,objTerrain,(255,0,0,255)):
      #sndDeath.play()
      objPlayer.x = 320
      objPlayer.y = 240
      objPlayer.vspeed = 0
   
   # Camera movement
   viewX += (objPlayer.x-viewW/2-viewX)/10
   viewX = min(max(viewX,0),sprTerrain.get_width()-viewW)
   viewY += (objPlayer.y-viewH/2-viewY)/10
   viewY = min(max(viewY,0),sprTerrain.get_height()-viewH)
   
   # Player movement
   free_under = not collCheck(objPlayer,objPlayer.x,objPlayer.y+1,objTerrain)
   if keyboard_keys[77] or keyboard_keys[114]:
      objPlayer.sprite = sprPlayerR
      if objPlayer.hspeed<3:
         objPlayer.hspeed = min(objPlayer.hspeed+.125,3)
   if keyboard_keys[75] or keyboard_keys[113]:
      objPlayer.sprite = sprPlayerL
      if objPlayer.hspeed>-3:
         objPlayer.hspeed = max(objPlayer.hspeed-.125,-3)
   if (keyboard_keys[72] and not keyboard_keys_prev[72]) or (keyboard_keys[111] and not keyboard_keys_prev[111]):
      if not collCheck(objPlayer,objPlayer.x,objPlayer.y-1,objTerrain):
         if not free_under:
            objPlayer.vspeed = -5
            #sndJump.play()
         else:
            if collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain) and not collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain) and (alt_wall_jump or (keyboard_keys[75] or keyboard_keys[113])):
               objPlayer.hspeed=3
               objPlayer.vspeed=-3
               #sndJump.play()
            elif collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain) and not collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain) and (alt_wall_jump or (keyboard_keys[77] or keyboard_keys[114])):
               objPlayer.hspeed=-3
               objPlayer.vspeed=-3
               #sndJump.play()
            elif collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain) and collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain) and (keyboard_keys[77] or keyboard_keys[114] or keyboard_keys[75] or keyboard_keys[113]):
               objPlayer.vspeed=-3
               #sndJump.play()
            elif objPlayer.candj:
               objPlayer.vspeed=-4
               if keyboard_keys[75] or keyboard_keys[113]:
                  objPlayer.hspeed-=1.5
               if keyboard_keys[77] or keyboard_keys[114]:
                  objPlayer.hspeed+=1.5
               #sndJump.play()
               objPlayer.candj=False
   
   if objPlayer.hspeed>0:
      for i in range(0,int(math.ceil(objPlayer.hspeed*(ct-pt)*60/1000))):
         if i==0 and math.floor(objPlayer.hspeed)!=objPlayer.hspeed:
            step1 = objPlayer.hspeed-math.floor(objPlayer.hspeed)
            step2 = 2*step1
         else:
            step1 = 1
            step2 = 2
         if not free_under and not collCheck(objPlayer,objPlayer.x+1,objPlayer.y+2,objTerrain) and collCheck(objPlayer,objPlayer.x+1,objPlayer.y+3,objTerrain):
            objPlayer.x += step1
            objPlayer.y += step2
         elif not free_under and not collCheck(objPlayer,objPlayer.x+1,objPlayer.y+1,objTerrain) and collCheck(objPlayer,objPlayer.x+1,objPlayer.y+2,objTerrain):
            objPlayer.x += step1
            objPlayer.y += step1
         elif not collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain):
            objPlayer.x += step1
         elif not free_under and not collCheck(objPlayer,objPlayer.x+1,objPlayer.y-1,objTerrain) and collCheck(objPlayer,objPlayer.x+1,objPlayer.y,objTerrain):
            objPlayer.x += step1
            objPlayer.y -= step1
         elif not free_under and not collCheck(objPlayer,objPlayer.x+1,objPlayer.y-2,objTerrain) and collCheck(objPlayer,objPlayer.x+1,objPlayer.y-1,objTerrain):
            objPlayer.x += step1
            objPlayer.y -= step2
         else:
            objPlayer.hspeed=0
            break
      if not (keyboard_keys[77] or keyboard_keys[114] or keyboard_keys[75] or keyboard_keys[113]):
         objPlayer.hspeed = max(objPlayer.hspeed-.3,0)
   if objPlayer.hspeed<0:
      for i in range(0,int(math.ceil(-objPlayer.hspeed*(ct-pt)*60/1000))):
         if i==0 and math.floor(objPlayer.hspeed)!=objPlayer.hspeed:
            step1 = -objPlayer.hspeed-math.floor(-objPlayer.hspeed)
            step2 = 2*step1
         else:
            step1 = 1
            step2 = 2
         if not free_under and not collCheck(objPlayer,objPlayer.x-1,objPlayer.y+2,objTerrain) and collCheck(objPlayer,objPlayer.x-1,objPlayer.y+3,objTerrain):
            objPlayer.x -= step1
            objPlayer.y += step2
         elif not free_under and not collCheck(objPlayer,objPlayer.x-1,objPlayer.y+1,objTerrain) and collCheck(objPlayer,objPlayer.x-1,objPlayer.y+2,objTerrain):
            objPlayer.x -= step1
            objPlayer.y += step1
         elif not collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain):
            objPlayer.x -= step1
         elif not free_under and not collCheck(objPlayer,objPlayer.x-1,objPlayer.y-1,objTerrain) and collCheck(objPlayer,objPlayer.x-1,objPlayer.y,objTerrain):
            objPlayer.x -= step1
            objPlayer.y -= step1
         elif not free_under and not collCheck(objPlayer,objPlayer.x-1,objPlayer.y-2,objTerrain) and collCheck(objPlayer,objPlayer.x-1,objPlayer.y-1,objTerrain):
            objPlayer.x -= step1
            objPlayer.y -= step2
         else:
            objPlayer.hspeed=0
            break
      if not (keyboard_keys[77] or keyboard_keys[114] or keyboard_keys[75] or keyboard_keys[113]):
         objPlayer.hspeed = min(objPlayer.hspeed+.3,0)
   
   # Draw
   gameDisplay.fill(background_color)
   for i in range(-1,3):
      gameDisplay.blit(bgClouds,(bgClouds.get_width()*i+(pygame.time.get_ticks()*60/1000)%bgClouds.get_width()-viewX*.5,-viewY*.5))
   for inst in instances:
      if inst.visible:
         gameDisplay.blit(inst.sprite,(inst.x-inst.center_origin*_shape_w[inst.shape]/2-viewX,inst.y-inst.center_origin*_shape_h[inst.shape]/2-viewY))
         #pygame.draw.circle(gameDisplay,(0,0,0),(inst.x,inst.y),4,1)
   gameDisplay.blit(bgForeground,(-viewX,-viewY))
   gameDisplay.blit(gameHUD,(0,0))
   gameHUD.set_alpha(0)
   
   # Refresh
   pygame.display.update()
   clock.tick(60)

pygame.quit()
quit()


All the sound code has been commented out for you.

Here are the images: http://imgur.com/a/fw39F
Unfortunately, the filenames have been messed up, they are, from top to bottom: terrain.png, clouds.png, foreground.png, player.png, player_mask.png.
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby Yambam » Sat Jul 09, 2016 8:05 pm

ichabod801 wrote:I don't download unknown zip files.

I made a BitBucket account! Do you want to download my project from my repository? https://bitbucket.org/Yambam/graphformer

There's not much activity around this board of the forum...
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby Yambam » Mon Jul 11, 2016 7:24 am

Doesn't this actually belong in Game development BTW? There is a demo with levels included with the engine.
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am

Re: Simple Platformer Engine

Postby Yambam » Sun Jul 17, 2016 5:08 pm

Hello ichabod. How do PMs actually work? I tried to contact you twice, but all messages are still in my outbox...
Image
User avatar
Yambam
 
Posts: 7
Joined: Thu Jul 07, 2016 9:46 am


Return to Completed Scripts

Who is online

Users browsing this forum: No registered users and 3 guests