Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feature/wombat animation #361

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
72680f6
Added initial framework for animating wombat movement
Aug 1, 2017
7e9a53b
Started work on cell-based animation
Aug 1, 2017
d916d68
Added wombat locating ability
Aug 8, 2017
9267eab
Merge branch 'develop' into feature/wombat-animation
Aug 8, 2017
2542bc0
Fixed merge issue
Aug 8, 2017
e3968f6
Added function to remove the animated items
Aug 8, 2017
a3a2ab2
Updated with initial data structure for an animation object
Aug 8, 2017
7b93779
More animation work
Aug 9, 2017
19b38f1
Animations slightly working, need to tweak some data structures to ma…
Aug 9, 2017
040ed74
Animations working - wrapping logic and frame-redrawing needs work
Aug 9, 2017
0b4856a
Changed an atom to use reduce
Aug 9, 2017
761f9b8
Beginning work on animating every frame
Aug 9, 2017
5b5880c
refactoring for animation to work differently
Aug 9, 2017
9e0d221
Animations re-implemented in a much simpler way
Aug 10, 2017
f8801ae
Fix build issues
Aug 10, 2017
c9b5065
Fixed formatting issue
Aug 10, 2017
13947f5
Updated a function to be more clear
Aug 10, 2017
d37c819
Slightly better drawing
Aug 10, 2017
e7ac7cb
Refactored to use a 1d array for drawing the canvas
Aug 10, 2017
d98d5dc
Fixed long lines
Aug 10, 2017
c3be54f
Fixed a couple styling issues
Aug 10, 2017
5605e94
Initial wrapping support
Aug 11, 2017
c2d2621
Wrapping animated on second half
Aug 11, 2017
c0811a8
First part of wrapping done
Aug 11, 2017
e8cff8e
Added exit animation
Aug 11, 2017
2bdfc70
Updated arena to be drawn with draw-arena-animated
Aug 11, 2017
3944c05
Removed draw-arena-canvas
Aug 11, 2017
61f9c37
Changed some -1 to dec
Aug 11, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 200 additions & 16 deletions src/cljs/wombats_web_client/components/arena.cljs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
(ns wombats-web-client.components.arena
(:require [re-frame.core :as re-frame]
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[wombats-web-client.utils.canvas :as canvas]))

(defonce spritesheet-png "/images/spritesheet.png")
(defonce frame-time 20)

(defn subscribe-to-spritesheet
[img-name callback]
Expand Down Expand Up @@ -300,25 +302,207 @@

(js/console.log "Unhandled: " cell-type))))

(defn arena
"Renders the arena on a canvas element, and subscribes to arena updates"
[arena canvas-id]
(let [canvas-element (.getElementById js/document canvas-id)]
(when-not (nil? canvas-element)
;; Calculate the width and height of each cell
(def height (/ (canvas/height canvas-element) (count arena)))
(def width (/ (canvas/width canvas-element) (count (get arena 0))))

;; Iterate through all of the arena rows
(doseq [[y row] (map-indexed vector arena)]
(doseq [[x cell] (map-indexed vector row)]

(def x-coord (* x width))
(def y-coord (* y height))
(defn- draw-arena-canvas
"Given a canvas element and the arena, draw the canvas"
[{:keys [arena
canvas-element]}]
;; Calculate the width and height of each cell
(let [height (/ (canvas/height canvas-element) (count arena))
width (/ (canvas/width canvas-element) (count (get arena 0)))]

;; Iterate through all of the arena rows
(doseq [[y row] (map-indexed vector arena)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think this could still be done cleaner by combining draw-arena-canvas into draw-arena-canvas-animated.

I think the best way would be to sort all of the elements by the order that they should be drawn (such that the animated elements are always drawn after the non-animated). You would need to merge in their x/y coordinates. Then you can simply draw the element at the correct location. Does that make sense?

(doseq [[x cell] (map-indexed vector row)]
(let
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put x-coord on this line.

[x-coord (* x width)
y-coord (* y height)]

(draw-cell cell
x-coord
y-coord
width
height
canvas-element))))))

(defn in?
"Return true if coll contains elem"
[elem coll]
(some #(= elem %) coll))

(defn- get-step-size
"Calculate the size of the step, as well as whether it is forward or back"
[start end dimension-key]
(let [start-pos (dimension-key start)
end-pos (dimension-key end)]
(- end-pos start-pos)))

(defn- get-direction-key
"Given the start and the end dimension object, calculate which direction
has movement"
[start end]
(if (pos? (Math/abs (- (:x start) (:x end))))
Copy link
Contributor

@dehli dehli Aug 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is a bit confusing and would probably be easier to follow like:

(defn- get-direction-key
  [{x0 :x} {x1 :x}] ;; you could also use [start end] here 
  (if (= x0 x1)     ;; and then pull out the values here.
    :y
    :x))

:x
:y))

(defn get-animated-coord
"Uses the progress of the animation to
calculate where the wombats/zakano should be placed"
[{:keys [x y width height direction-key animation-progress step-size]}]
{:x (if (= direction-key :x)
(* width (+ x (* (:progress animation-progress) step-size)))
(* width x))

:y (if (= direction-key :y)
(* height (+ y (* (:progress animation-progress) step-size)))
(* height y))})

(defn- draw-arena-canvas-animated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method draws all cells (not just animated). If so, it would make sense to rename this method.

"Given a canvas element and the arena - skip the animated items"
[{:keys [arena
canvas-element
animated
animation-progress]}]

;; Calculate the width and height of each cell
(let [height (/ (canvas/height canvas-element) (count arena))
width (/ (canvas/width canvas-element) (count (get arena 0)))
animated-x (map #(get-in % [:end :x]) animated)
animated-y (map #(get-in % [:end :y]) animated)
animated-types (map #(get-in % [:cell :contents :type]) animated)]
;; Iterate through all of the arena rows - draw non-animated items
(doseq [[y row] (map-indexed vector arena)]
(doseq [[x cell] (map-indexed vector row)]
(let
[x-coord (* x width)
y-coord (* y height)]
(when-not
(and (in? x animated-x)
(in? y animated-y)
(in? (get-in cell [:contents :type]) animated-types))
(draw-cell cell
x-coord
y-coord
width
height
canvas-element)))))

;; run through animated items and apply the animation to them
(doseq [item animated]
(let [item-start (:start item)
item-end (:end item)
direction-key (get-direction-key item-start item-end)
step-size (/ (get-step-size item-start
item-end
direction-key) frame-time)
cell (:cell item)
x (get-in item [:start :x])
y (get-in item [:start :y])
new-coords (get-animated-coord
{:x x
:y y
:width width
:height height
:direction-key direction-key
:animation-progress animation-progress
:step-size step-size})]
(when (<= (Math/abs (get-step-size item-start
item-end
direction-key)) 1)
(draw-cell cell
(:x new-coords)
(:y new-coords)
width
height
canvas-element)))))

;; this when subtracts one from the end because the animation runs once
;; before it gets a callback
(when (<= (:progress animation-progress) (dec (:end animation-progress)))
(.requestAnimationFrame js/window
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner to just do a forceUpdate every animation frame so that the arena completely rerenders.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to do this - would you replace the .requestAnimationFrame ? and what would I call force-update on to make sure I could increment the animation progress?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm thinking (might not actually work) is doing the following:

(.requestAnimationFrame js/window
                        (.forceUpdate this))

Then you're triggering a rerender every animation frame so you don't need to have any logic inside of the callback. Does that make sense?

#(draw-arena-canvas-animated
{:arena arena
:canvas-element canvas-element
:animated animated
:animation-progress
(update-in animation-progress
[:progress]
inc)}))))

(defn arena
"Renders the arena on a canvas element, and subscribes to arena updates"
[arena canvas-id]
(let [canvas-element (.getElementById js/document canvas-id)]
(when-not (nil? canvas-element)

(draw-arena-canvas {:arena arena
:canvas-element canvas-element}))))

(defn- add-locs
"Add local :x and :y coordinates to arena matrix"
[arena]
(map-indexed
(fn [y row] (map-indexed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is off in this function.

(fn [x tile] (assoc tile :x x :y y))
row))
arena))

(defn- filter-arena
"Filter the arena to return only nodes that contain one of the supplied types"
([arena] (flatten arena))
([arena filters]
(let [node-list (flatten arena)]
(filter #(in? (get-in % [:contents :type]) filters) node-list))))

(defn- get-uuid
[item]
(get-in item [:contents :uuid]))

(defn- dimensions
[item]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function isn't needed since item already has the proper attributes.

Copy link
Contributor Author

@elibosley elibosley Oct 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does - but I need the previous dimensions to be stored somewhere - I think I figured it was easier to just pull both at once. Would you suggest putting all of that information on the :cell attribute for the vector?

Answered down below

{:x (:x item)
:y (:y item)})

(defn- create-animations-vector
"Input is two vectors of flatten-item responses,
output is a vector of the animations"
[prev-coords next-coords]
(remove nil?
(reduce (fn [out-vec prev]
(conj out-vec
(reduce
(fn [obj next] ;; could use (first (filter (fn ...)))
(if (and (= (get-uuid next) (get-uuid prev))
(not= (dimensions prev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Down here, you could use another function you write that just compares the prev and next.

(dimensions next)))
{:start (dimensions prev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then down here, you'd pass

{:start prev
 :end next
 :animated true}

And you can completely get rid of cell.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you answered my question up above - thanks 👍

:end (dimensions next)
:progress (dimensions prev)
:cell next}
obj))
nil next-coords))) [] prev-coords)))


(defn arena-history
"Renders the arena on a canvas element, given the frames item and an index,
allowing for animation between the frames"
[{:keys [frames-vec frames-idx view-mode canvas-id]}]
(let [prev-frame (get-in @frames-vec
[(dec @frames-idx)
:game/frame
:frame/arena])
next-frame (get-in @frames-vec
[@frames-idx
:game/frame
:frame/arena])
;; Get the coordinates of the animated items on the grid
prev-coords (filter-arena (add-locs prev-frame) [:zakano :wombat])
next-coords (filter-arena (add-locs next-frame) [:zakano :wombat])
canvas-element (.getElementById js/document canvas-id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use ref instead of (.getElementById

animations (create-animations-vector prev-coords next-coords)
animation-progress {:progress 0
:end frame-time}]
(when-not (nil? canvas-element)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just use (when canvas-element

(draw-arena-canvas-animated {:arena next-frame
:canvas-element canvas-element
:animated animations
:animation-progress animation-progress}))))
10 changes: 6 additions & 4 deletions src/cljs/wombats_web_client/components/simulator/arena.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
;; Main Method
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn render
"Takes the simulator data object and the view mode key and renders the arena"
[simulator-data simulator-view-mode]
(defn render [simulator-frames simulator-index simulator-view-mode]

(arena/arena (simulator-view-mode @simulator-data) canvas-id)
(arena/arena-history
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in component-did-update and component-did-mount so it's called after renders.

{:frames-vec simulator-frames
:frames-idx simulator-index
:view-mode simulator-view-mode
:canvas-id canvas-id})
[:canvas {:id canvas-id}])
2 changes: 1 addition & 1 deletion src/cljs/wombats_web_client/panels/simulator.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
[{:keys
[simulator-view-mode simulator-data simulator-frames simulator-index]}]
[:div.left-pane {:id "left-pane"}
[simulator-arena/render simulator-data simulator-view-mode]
[simulator-arena/render simulator-frames simulator-index simulator-view-mode]
[simulator-controls/render simulator-data simulator-frames simulator-index]])

(defn- render
Expand Down