(import scheme (chicken base) (chicken random) (only srfi-1 filter any) (only srfi-197 chain) (prefix sdl2 "sdl2:") (prefix sdl2-ttf "ttf:") (prefix sdl2-image "img:") downstroke-engine downstroke-world downstroke-physics downstroke-input downstroke-entity downstroke-assets downstroke-sound) ;; ── Constants ──────────────────────────────────────────────────────────────── (define +screen-width+ 600) (define +screen-height+ 400) (define +spawn-interval+ 60) ;; ── State ──────────────────────────────────────────────────────────────────── (define *frame-count* 0) ;; ── Entity factories ───────────────────────────────────────────────────────── (define (make-player) (list #:type 'player #:x 280 #:y 360 #:width 16 #:height 16 #:vx 0 #:vy 0)) (define (make-bullet x y) (list #:type 'bullet #:x x #:y y #:width 4 #:height 8 #:vx 0 #:vy -6)) (define (make-enemy x) (list #:type 'enemy #:x x #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 2)) ;; ── Collision ──────────────────────────────────────────────────────────────── (define (entities-overlap? a b) (aabb-overlap? (entity-ref a #:x 0) (entity-ref a #:y 0) (entity-ref a #:width 0) (entity-ref a #:height 0) (entity-ref b #:x 0) (entity-ref b #:y 0) (entity-ref b #:width 0) (entity-ref b #:height 0))) (define (find-dead entities) (let ((bullets (filter (lambda (e) (eq? (entity-type e) 'bullet)) entities)) (enemies (filter (lambda (e) (eq? (entity-type e) 'enemy)) entities))) (filter (lambda (e) (case (entity-type e) ((bullet) (any (lambda (en) (entities-overlap? e en)) enemies)) ((enemy) (any (lambda (b) (entities-overlap? e b)) bullets)) (else #f))) entities))) (define (in-bounds? e) (let ((y (entity-ref e #:y 0))) (and (> y -20) (< y (+ +screen-height+ 20))))) ;; ── Update helpers ─────────────────────────────────────────────────────────── (define (player-vx input) (cond ((input-held? input 'left) -4) ((input-held? input 'right) 4) (else 0))) (define (clamp-player-x player) (entity-set player #:x (max 0 (min (- +screen-width+ 16) (entity-ref player #:x 0))))) (define (update-player player input scene) (let ((updated (chain player (entity-set _ #:vx (player-vx input)) (apply-velocity-x _) (clamp-player-x _)))) (when (input-pressed? input 'a) (play-sound 'shoot) (scene-add-entity scene (make-bullet (+ (entity-ref updated #:x 0) 6) 340))) updated)) (define (move-projectile e) (chain e (entity-set _ #:x (+ (entity-ref e #:x 0) (entity-ref e #:vx 0))) (entity-set _ #:y (+ (entity-ref e #:y 0) (entity-ref e #:vy 0))))) (define (maybe-spawn-enemy! scene) (when (zero? (modulo *frame-count* +spawn-interval+)) (scene-add-entity scene (make-enemy (+ 20 (* (pseudo-random-integer 28) 20)))))) ;; ── Render ─────────────────────────────────────────────────────────────────── (define (entity-color type) (case type ((player) (sdl2:make-color 255 255 255 255)) ((bullet) (sdl2:make-color 255 255 0 255)) ((enemy) (sdl2:make-color 255 50 50 255)) (else (sdl2:make-color 100 100 100 255)))) (define (draw-shmup-entity renderer e) (let ((x (inexact->exact (floor (entity-ref e #:x 0)))) (y (inexact->exact (floor (entity-ref e #:y 0)))) (w (entity-ref e #:width 16)) (h (entity-ref e #:height 16))) (set! (sdl2:render-draw-color renderer) (entity-color (entity-type e))) (sdl2:render-fill-rect! renderer (sdl2:make-rect x y w h)))) ;; ── Game ───────────────────────────────────────────────────────────────────── (define *game* (make-game title: "Demo: Shoot-em-up" width: +screen-width+ height: +screen-height+ preload: (lambda (game) (init-audio!) (load-sounds! '((shoot . "demo/assets/jump.wav")))) create: (lambda (game) (game-scene-set! game (make-scene entities: (list (make-player)) tilemap: #f camera: (make-camera x: 0 y: 0) tileset-texture: #f camera-target: #f))) update: (lambda (game dt) (set! *frame-count* (+ *frame-count* 1)) (let* ((input (game-input game)) (scene (game-scene game)) (player (car (scene-entities scene))) (player (update-player player input scene))) (maybe-spawn-enemy! scene) ;; Replace player, then move all projectiles (scene-entities-set! scene (cons player (filter (lambda (e) (not (eq? (entity-type e) 'player))) (scene-entities scene)))) (scene-update-entities scene (lambda (e) (if (eq? (entity-type e) 'player) e (move-projectile e)))) ;; Remove bullet/enemy collisions, then out-of-bounds (let ((dead (find-dead (scene-entities scene)))) (scene-filter-entities scene (lambda (e) (not (memq e dead))))) (scene-filter-entities scene (lambda (e) (or (eq? (entity-type e) 'player) (in-bounds? e)))))) render: (lambda (game) (for-each (cut draw-shmup-entity (game-renderer game) <>) (scene-entities (game-scene game)))))) (game-run! *game*)