Initial commit

master
mil 4 years ago
commit 809335c869

2
.gitignore vendored

@ -0,0 +1,2 @@
*~
\#*\#

@ -0,0 +1,37 @@
* Break
Breakout clone with some future plans.
** building, running
- install quicklisp
- place the repository in a directory listed in =quicklisp:*local-project-directories*=
- start emacs and slime. SBCL is the only CL implementation tested.
- run =(ql:quickload :break)= and switch to the =:break= package.
- run =(cepl:repl)= to start CEPL
- create a pad and assign it to =*pad*=
- =(run-loop)= starts the game loop and =(stop-loop)= stops it. Consider not
recompiling the functions while the loop is running because you'll lose access to the
closure.
** lisp libraries used and where to find them
- [[https://github.com/cbaggers/cepl][CEPL]] ([[http://techsnuffle.com/cepl/api.html][Documentation]]) :: Lispy way to work with OpenGL
- [[https://github.com/cbaggers/rtg-math][rtg-math]] ([[http://techsnuffle.com/rtg-math/rtg-math-reference.html][Documentation]]) :: Lisp-side math library
- [[https://github.com/cbaggers/varjo][vari and varjo]] ([[http://techsnuffle.com/varjo/vari-reference.html][language documentation]] [[http://techsnuffle.com/varjo/varjo-reference.html][compiler documentation]]) :: Shader language and
its compiler
** things to do
Note: DONE doesn't mean the subheading's content is irrelevant.
*** DONE Decouple coordinate system from rendering resolutionn
The playable are has is 320 x 240 in size. The size is defined in a global variable,
and parts of the code that need it read it. Entity positions and sizes are not
adjusted if the size changes, but otherwise it can be adjusted at runtime.
*** TODO Remove global variables
State relevant to any given scene should be possible to keep in an object representing
the scene itself. All update methods should take the context as an argument, rather
than accessing global variables defined after the methods.
*** TODO Add a menu system and a scene loader
A menu is a kind of scene.
Scenes should probably be on a stack so that you can push/pop them to arbitrary
depths.
*** TODO Implement keybindings

@ -0,0 +1,16 @@
;;;; break.asd
(asdf:defsystem #:break
:description "Breakout clone"
:author "Mil <mil@bloome.rs>"
:license "GPL"
:version "0.0.1"
:serial t
:depends-on (#:cepl #:rtg-math.vari #:cepl.sdl2 #:swank #:livesupport #:cepl.skitter.sdl2 #:dirt)
:components ((:file "package")
(:file "render")
(:file "utils")
(:file "objects")
(:file "break")
(:file "scene")
))

@ -0,0 +1,48 @@
;;;; break.lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This file should be the only one that manages program state.
;;;
(in-package #:break)
(defparameter *entities* nil)
(defparameter *pad* nil)
(defparameter *balls* nil)
(defparameter *tiles* nil)
(defparameter *internal-size* (v! 320 240))
(defparameter *projection-matrix* (rtg-math.projection:orthographic-v2 *internal-size* 1.0 100.0))
(defparameter *viewport* (make-viewport))
(defun update-entities ()
(let ((ents (cons *pad* (concatenate 'list *balls* *tiles* *entities*))))
(map nil #'update ents)))
(defun window-size-callback (size &rest ignored)
(declare (ignore ignored))
(setf (viewport-dimensions (current-viewport))
(list (aref size 0) (aref size 1)))
(setf *projection-matrix* (update-matrix size)))
(defun step-game ()
(with-viewport *viewport*
(labels ((draw-ent (ent)
(draw-entity ent *projection-matrix*)))
(step-host)
(update-repl-link)
(clear)
(update-entities)
(draw-entity *pad* *projection-matrix*)
(map nil #'draw-ent *balls*)
(map nil #'draw-ent *tiles*)
(map nil #'draw-ent *entities*)
(swap))))
(let ((running nil))
(defun run-loop ()
(setf running t)
(whilst-listening-to
((#'window-size-callback (window 0) :size))
(loop :while (and running (not (shutting-down-p))) :do
(continuable (step-game)))))
(defun stop-loop ()
(setf running nil)))

@ -0,0 +1,215 @@
;;;; objects.lisp
(in-package :break)
(defgeneric update (entity))
(defgeneric kill (entity))
(defgeneric bounding-box (entity))
(defgeneric overlap-p (entity1 entity2))
;;; Base class for everything that needs to be drawn.
(defclass entity ()
((e-stream :initform nil :initarg :e-stream :accessor e-stream)
(position :initform (v! 0 0 0) :initarg :pos :accessor pos)
(scale :initform (v! 1 1 1) :initarg :scale :accessor scale)
(size :initform (v! 1 1) :initarg :size :accessor size)))
(defun make-entity (x y color position)
(make-instance 'entity
:e-stream (make-square-e-stream x y color)
:pos position))
(defmethod update ((entity entity))
(setf (pos entity)
(make-array
3
:element-type 'single-float
:initial-contents (map 'vector (lambda (scalar) (- (/ scalar 2)))
(concatenate 'vector
(viewport-internal-size (current-viewport))
'(-0.0))))))
(defmethod kill ((entity entity))
(map nil (lambda (arr) (if (consp arr)
(free (car arr))
(free arr)))
(buffer-stream-gpu-arrays (e-stream entity)))
(free-buffer-stream (e-stream entity)))
(defun draw-entity (entity projection-matrix)
(let ((to-world (m4:* (m4:translation (pos entity))
(m4:scale (scale entity))))
(to-view projection-matrix
;;(rtg-math.projection:orthographic 320.0 240.0 1.0 100.0)
))
(map-g #'render (e-stream entity) :to-view to-view :to-world to-world)))
;;; Background images
(defclass bg-image (entity)
((alignment :initform :center :initarg :alignment :accessor alignment)
(index :initform 1 :initarg :index :accessor index)))
(defun make-bg-image (alignment index color)
(make-instance 'bg-image
:alignment alignment
:index index
:e-stream (make-square-e-stream 320.0 240.0 color)
:pos (v! 0 0 0)))
(defmethod update ((bg-image bg-image))
(let ((internal-size (viewport-internal-size (current-viewport))))
(setf (pos bg-image)
(case (alignment bg-image)
(:center (v! (rtg-math.vector2:*s (rtg-math.vector2:/s *internal-size* 2.0) -1.0)
(index bg-image)))
(:topright (v!
(rtg-math.vector2:-
(rtg-math.vector2:/s internal-size 2.0)
*internal-size*)
(index bg-image)))
(:bottomleft (v!
(rtg-math.vector2:-
(rtg-math.vector2:/s internal-size 2.0)
internal-size)
(index bg-image)))
))))
;;; paddle
(defclass pad (entity)
((size :initform (v! 100 5) :initarg :size :accessor size)))
(defun make-pad ()
(make-instance 'pad
:e-stream (make-square-e-stream 100.0 5.0 (v! 0 0 1 1))
:pos (v! 0 -100 0)
:size (v! 100.0 5.0)))
(defmethod (setf size) (new-size (pad pad))
(setf (slot-value pad 'size) new-size)
(setf (slot-value pad 'scale) (v! (v2:/ (size pad) (v! 100 5)))))
(defmethod update ((pad pad))
(setf (aref (pos pad) 0) ; position should be in pixels, not SDL2 coords
(- (aref (mouse-pos (mouse)) 0) (/ 320 2))))
;;; balls
(defclass ball (entity)
((direction :initform (v! 1 1 0) :initarg :direction :accessor dir)
(speed :initform 1.0 :initarg :speed :accessor spd)
(radius :initform 2.5 :initarg :rad :accessor rad)))
(defun make-ball (pos dir)
(make-instance 'ball
:e-stream (make-square-e-stream 5 5 (v! 1 1 0 1))
:pos (or pos (v! 0 0 0))
:rad 2.5
:direction (or dir (v! 0.5 0.5 0))))
(defun turn-ball (ball &key direction)
"Returns the new direction the ball should move to.
Setf elsewhere."
(if (equal direction :v)
(v3:* (v! -1 1 1) (dir ball))
(v3:* (v! 1 -1 1) (dir ball))))
(defun turn-ball-at-edge (ball)
"Stickiness is not accounted for.
Bounding boxes are not accounted for."
(if (< (/ (aref *internal-size* 0) 2) (abs (aref (pos ball) 0)))
(setf (dir ball) (turn-ball ball :direction :v)))
(if (< (/ (aref *internal-size* 1) 2) (abs (aref (pos ball) 1)))
(setf (dir ball) (turn-ball ball :direction :h)))
(dir ball))
(defun split-ball (ball &key way)
(let ((new-ball (make-ball (pos ball) (v! 1 1 0))))
(setf (dir new-ball) (turn-ball ball :direction way))
(setf *balls* (cons new-ball *balls*))))
(defun kick-balls ()
(map nil (lambda (ball)
(setf (dir ball)
(normalize-v3 (v! (- (random 100) 50)
(- (random 100) 50)
0))))
*balls*))
(defmethod update ((ball ball))
(let ((overlap (overlap-p *pad* ball)))
(if overlap
(progn
(setf (dir ball) (normalize-v3
(v! (v2:* (v! 2 1)
(v2:/ overlap (v2:/S (size *pad*) 2.0)))
0)))
(setf (aref (dir ball) 1) (abs (aref (dir ball) 1))))))
(setf (pos ball) (v3:+ (pos ball) (v3:*s (turn-ball-at-edge ball)
(spd ball)))))
;;; tiles
(defclass tile (entity)
((life :initform 1 :initarg :life :accessor life)
(size :initform (v! 20 10) :initarg :size :accessor size)))
(defun make-tile (pos &optional life)
(make-instance 'tile
:e-stream (make-square-e-stream 20 10 (v! 0 0.5 0.5 1))
:pos pos
:size (v! 20 10)
:life (or life 1)))
(defmethod update ((tile tile))
(map nil (lambda (ball)
(let ((overlap (overlap-p tile ball)))
(if overlap
(progn
(setf (dir ball)
(if (= (abs (aref (v2:/ overlap (v2:/S (size tile) 2.0)) 1))
1.0)
(turn-ball ball :direction :h)
(turn-ball ball :direction :v)))
(decf (life tile))
(if (= (life tile) 0)
(progn
(setf *tiles* (remove tile *tiles*))
(kill tile)))))))
*balls*))
;;; Collision magic
(defmethod bounding-box ((entity entity))
(let ((y (v! (aref (pos entity) 0)
(+ (aref (size entity) 0)
(aref (pos entity) 0))))
(x (v! (aref (pos entity) 1)
(+ (aref (size entity) 1)
(aref (pos entity) 1)))))
(v! y x)))
(defmethod overlap-p ((ent1 entity) (ent2 entity))
"AABB bounding box checking. Currently unused."
(labels ((test-y (entity1 entity2)
(or (in-range (aref (bounding-box entity1) 0)
(subseq (bounding-box entity2) 0 2))
(in-range (aref (bounding-box entity1) 1)
(subseq (bounding-box entity2) 0 2))))
(test-x (entity1 entity2)
(or (in-range (aref (bounding-box entity1) 2)
(subseq (bounding-box entity2) 2))
(in-range (aref (bounding-box entity1) 3)
(subseq (bounding-box entity2) 2)))))
(and (test-y ent1 ent2)
(test-x ent1 ent2))))
(defmethod overlap-p ((ent1 entity) (ball ball))
(let* ((ball-center (v2:+S (subseq (pos ball) 0 2) (rad ball)))
(ent-center (v2:+ (subseq (pos ent1) 0 2) (v2:/S (size ent1) 2.0)))
(point (clamp-v2 (v2:*S (v2:/S (size ent1) 2.0) -1.0)
(v2:/S (size ent1) 2.0)
(v2:- ball-center ent-center)))
(diff (v2:- ball-center (v2:+ ent-center point))))
;; (format t "~a, ~a, ~a, ~a, ~a" (sqrt (+ (expt (aref diff 0) 2)
;; (expt (aref diff 1) 2)))
;; point ball-center ent-center (rad ball))
(if (> (rad ball) (sqrt (+ (expt (aref diff 0) 2)
(expt (aref diff 1) 2))))
point
nil)))

@ -0,0 +1,9 @@
;;;; package.lisp
(defpackage #:break
(:use #:cl
#:cepl
#:vari
#:rtg-math
#:livesupport
#:cepl.skitter))

@ -0,0 +1,58 @@
;;;; render.lisp
(in-package :break)
(defstruct-g pos-col
(posiiton :vec3 :accessor pos)
(color :vec4 :accessor col))
(defun-g vert ((vert pos-col) &uniform (to-view :mat4) (to-world :mat4))
(values (* to-view
(* to-world
(v! (pos vert) 1.0)))
(col vert)))
(defun-g frag ((color :vec4))
color)
(defpipeline-g render ()
(vert pos-col)
(frag :vec4))
(defun make-square-e-stream (x y color)
(let* ((verts (make-gpu-array `((,(v! x y 0) ,color)
(,(v! 0 y 0) ,color)
(,(v! 0 0 0) ,color)
(,(v! x 0 0) ,color))
:element-type 'pos-col :dimensions 4))
(indices (make-gpu-array '(0 1 2 0 2 3)
:dimensions 6 :element-type :unsigned-short)))
(make-buffer-stream verts :index-array indices)))
(defun viewport-internal-size (viewport)
(let* ((viewport-width (viewport-resolution-x viewport))
(viewport-height (viewport-resolution-y viewport))
(internal-width (aref *internal-size* 0))
(internal-height (aref *internal-size* 1))
(viewport-aspect-ratio (/ viewport-width viewport-height)))
(if (> viewport-aspect-ratio (/ internal-width internal-height))
(v! (* internal-height viewport-aspect-ratio)
internal-height)
(v! internal-width
(/ internal-width viewport-aspect-ratio)))))
(defun update-matrix (window-size)
(let* ((window-width (aref window-size 0))
(window-height (aref window-size 1))
(internal-width (aref *internal-size* 0))
(internal-height (aref *internal-size* 1))
(window-aspect-ratio (reduce #'/ window-size))
(new-size (if (> window-aspect-ratio (/ internal-width internal-height))
(v! (* internal-height window-aspect-ratio)
internal-height) ; wider, preserve height
(v! internal-width
(/ internal-width window-aspect-ratio)))))
;; (format t "Matrix arguments: ~a ~a.~%" (aref new-size 0) (aref new-size 1))
(rtg-math.projection:orthographic
(aref new-size 0) (aref new-size 1)
1.0 100.0)))

@ -0,0 +1,14 @@
;;;; scene.lisp
(in-package :break)
(defun init-scene (scene)
(map nil #'kill *balls*)
(map nil #'kill *tiles*)
(setf *balls* (list (make-ball nil nil)))
(setf *tiles* (map 'list #'make-tile scene))
(step-game))
(defvar *test-scene*
(list
(v! 100 100 0) (v! 80 100 0) (v! 60 100 0)
(v! 100 90 0) (v! 100 80 0) (v! 100 70 0)))

@ -0,0 +1,18 @@
;;;; utils.lisp
(in-package :break)
(defun in-range (num vec)
(and (not (< num (aref vec 0)))
(not (> num (aref vec 1)))))
(defun clamp-v2 (min max vec)
(v! (clamp (aref min 0) (aref max 0) (aref vec 0))
(clamp (aref min 1) (aref max 1) (aref vec 1))))
(defun magnitude (vec)
(sqrt (apply #'+ (map 'list
(lambda (s) (expt s 2))
vec))))
(defun normalize-v3 (vec)
(v3:/S vec (magnitude vec)))
Loading…
Cancel
Save