В данной статье используется среда разработки DrRacket. Для начала рассмотрим связь конечного автомата и игрового процесса. Объект управления в игре можно представить в виде конечного автомата. Рассмотрим программу, моделирующую светофор. Этот пример был описан в предыдущей статье.

Переходом в другое устойчивое состояние является переключение сигнала светофора. Диаграмму состояний можно изобразить в следующем виде.

image

Для того, чтобы создать светофор, нарисуем окружность в центре пустой сцены.

#lang racket
(require picturing-programs)
(define (DOT s)  (circle 100 "solid" s))

s — это переменная, отвечающая за цвет. Переход в другое состояние можно представить
следующей конструкцией:

(define (traffic-light-next s)
  (cond
    [(string=? "red" s) "green"]
    [(string=? "green" s) "yellow"]
    [(string=? "yellow" s) "red"]))

Для того, чтобы промоделировать переключение сигнала, используем функцию big-bang.

(big-bang "red"
          [on-tick traffic-light-next 1]
          [to-draw DOT])   
    

Теперь светофор переходит в следующее устойчивое состояние 1 раз в секунду. Управляющие воздействие в играх также могут оказывать платформы, препятствия, враги и т.д. Например, в некоторых играх по ходу движения объект может «перескакивать» на следующий канвас, т.е. приблизившись к границе канваса, объект исчезает и появляется на противоположной границе. Условие перехода («перескакивания») определяется сравнением координат объекта и края канваса.

 [(> (+ x DELTA) WIDTH)  0]

Если условие не выполнено, остаёмся на том же экране.

(cond
      [(> (+ x dx) WIDTH)  0]
       [else (+ x dx)]        )

Движение определяется приращением DELTA к координате x. Напишем программу целиком:

#lang racket
(require 2htdp/image)
(require 2htdp/universe)
(define WIDTH 100)
(define DELTA 1)
(define BALL (circle 5 "solid" "red"))
(define MT   (empty-scene WIDTH 10))
 (define (main x0)
  (big-bang x0
    [to-draw render]
    [on-tick bounce]))
(define (bounce x)
      (cond
      [(> (+ x DELTA) WIDTH)  0]
       [else (+ x DELTA)]  ))
 (define (render x)
  (place-image BALL   x 5 MT))
(main 50)

На этой странице представлены примеры программ, использующих big-bang. Запустить программы можно в режиме «Начинающий студент», добавив необходимые пакеты.

Далее напишем программу, в которой управление объектом осуществляется
клавишами «left» и «right».

Здесь нам понадобится функция для обработкой клавиш.

#lang racket
(require 2htdp/image)
(require 2htdp/universe)
(define BACKGROUND (empty-scene 100 100))
(define DOT (circle 10 "solid" "red"))
(define (place-dot-at x)
  (place-image DOT x 50 BACKGROUND))
(define (change-func p k) ; обработка клавиш
  (cond
    [(string=? "left" k)
     (- p 5)]
    [(string=? "right" k)
     (+ p 5)]
    [else p]))
( big-bang 50
[to-draw place-dot-at] 
[on-key change-func]   )

Если же нам также необходимо обрабатывать нажатия клавиш «up», «down», то координаты объекта следует хранить в структуре вида:

(define-struct posn (x y))
(define INIT-WORLD (make-posn 100 100))

Напишем программу, в которой объект может перемещаться по горизонтали и вертикали.

#lang racket
(require picturing-programs) 
(define WIDTH 200)
(define HEIGHT 200)
(define BACKGROUND     (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define-struct posn (x y))            ;объявляем структуру
(define INIT-WORLD (make-posn 100 100) )
(define (change-current-world-key current-world a-key-event) ;обработка "событий" клавиатуры 
  (cond
    [(key=? a-key-event "up")
     (make-posn (posn-x current-world) (- (posn-y current-world) 5))]
    [(key=? a-key-event "down")
    (make-posn (posn-x current-world) (+ (posn-y current-world) 5))]
    [(key=? a-key-event "left")
     (make-posn (-(posn-x current-world)5) (posn-y current-world) )]
    [(key=? a-key-event "right")
    (make-posn (+(posn-x current-world)5) (posn-y current-world) )]
    [else current-world]))
(define (redraw current-world)
  (place-image obj1
               (posn-x current-world)
               (posn-y current-world)
               BACKGROUND))
(big-bang INIT-WORLD
(on-key change-current-world-key)
(on-draw redraw) )

Да, но обычно в играх присутствует несколько объектов. Напишем программу, в которой присутствует два объекта. Используем две структуры: world и posn.

#lang racket
(require picturing-programs) 
(define WIDTH 300)
(define HEIGHT 300)
(define BACKGROUND     (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define obj2 (circle 10 "solid" "green"))
(define-struct posn (x y))
(define-struct world [obj1 obj2])
; Инициализация параметров в структуре world
(define INIT-WORLD (make-world (make-posn 100 100) (make-posn 200 100)))
(define (draw-game world)
   (place-image 
      obj1
      (posn-x (world-obj1 world))
      (posn-y (world-obj1 world))
      (place-image 
         obj2
         (posn-x (world-obj2 world))
         (posn-y (world-obj2 world))
         BACKGROUND)))
(big-bang   INIT-WORLD
  [to-draw draw-game])

Теперь для того, чтобы управлять одним из объектов, добавим функцию обработки клавиш.

#lang racket
(require picturing-programs) 
(define WIDTH 300)
(define HEIGHT 300)
(define BACKGROUND (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define obj2 (circle 10 "solid" "green"))
(define-struct posn (x y))
(define-struct world [obj1 obj2])
(define INIT-WORLD (make-world (make-posn 50 150) (make-posn 250 150)))
(define (change-current-world-key current-world a-key-event)
  (make-world (change-obj1 (world-obj1 current-world) a-key-event)
              (world-obj2 current-world)))
(define (change-obj1 a-posn a-key-event)
  (cond
    [(key=? a-key-event "up")
     (make-posn (posn-x a-posn) (- (posn-y a-posn) 5))]
    [(key=? a-key-event "down")
     (make-posn (posn-x a-posn) (+ (posn-y a-posn) 5))]
    [(key=? a-key-event "left")
     (make-posn (-(posn-x a-posn) 5) (posn-y a-posn) )]
    [(key=? a-key-event "right")
     (make-posn (+(posn-x a-posn)5) (posn-y a-posn) )]
    [else a-posn]))
(define (draw-game world)
   (place-image 
      obj1
      (posn-x (world-obj1 world))
      (posn-y (world-obj1 world))
     (place-image 
        obj2
        (posn-x (world-obj2 world))
        (posn-y (world-obj2 world))
         BACKGROUND)))
(big-bang   INIT-WORLD
(on-key change-current-world-key)
  [to-draw draw-game] )

Более подробно о создании игр можно прочитать в книге How to Design Worlds.
Поделиться с друзьями
-->

Комментарии (5)


  1. TheShock
    18.06.2017 19:10

    s — это переменная, отвечающая за цвет. Переход в другое состояние можно представить
    следующей конструкцией
    (define (traffic-light-next s)
      (cond
        [(string=? "red" s) "green"]
        [(string=? "green" s) "yellow"]
        [(string=? "yellow" s) "red"]))
    

    Но ведь светлофор работает в режиме
    Зеленый => Желтый => Красный =>  Желтый => повторить
    

    А не в режиме
    Зеленый => Желтый => Красный => повторить
    


    То есть Зеленый и Красный переходят в Желтый. А вот Желтый может перейти и в Зеленый и в Красный.

    Выглядит, словно вы подгоняете реальность под ограничения архитектуры. Ведь функцию только от текущего состояния написать легче, чем от текущего+предыдущего. И это на столь легком примере!


    1. TheShock
      18.06.2017 19:13

      Кстати, я, конечно, понимаю, что в данном конкректном примере это очень легко пофиксить введя два разных «желтых»: «yellow-before-red» и «yellow-before-green» и машина состояний останется такой же простой, но это ведь абстрактный пример, а в играх очень редко бывает возможность обойтись таким простым решением.


      1. kez
        19.06.2017 13:08
        +1

        Следующее состояние светофора основывается на нескольких предыдущих, поэтому нужно хранить список (хотя бы двух предыдущих) состояний и явно передавать его в функцию:


        (define (next-state prev-states)
          (let ((curr-state (first prev-states)))
            (cond
             ((eqv? curr-state 'red) 'yellow)
             ((eqv? curr-state 'yellow)
              (if (eqv? (second prev-states) 'red)
                  'green
                  'red))
             ((eqv? curr-state 'green) 'red)))
          )

        > (next-state '(yellow green))
        ;Value: red
        
        > (next-state '(yellow red))
        ;Value: green


  1. Luke0208
    18.06.2017 23:04

    Почитать про Lisp и Scheme очень приятно, спасибо.


  1. Wolf4D
    18.06.2017 23:20

    Любопытная статья. Вспомнилась замечательная игра Abuse, где в движок был встроен интерпретатор, а вся внутриигровая логика была реализована на лежащем в почти открытом видел LISP-е. Отдельные товарищи переписывали скролл-шутер в тетрис :)