--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
You should also read this article (in fact the whole series). It basically describes the same thing, but it goes a bit more in detail.
I’d also be interested how one could implement such a gameloop in Elm. It seems very difficult…
--
This Javascript snippet looks like pretty much what we want. Can we create a native js function that takes an elm function? (In this case update and render)
I know we can pass around values to js, but can we pass over functions?
var now,
dt = 0,
last = timestamp(),
step = 1/60;
function frame() {
now = timestamp();
dt = dt + Math.min(1, (now - last) / 1000);
while(dt > step) {
dt = dt - step;
update(step);
}
render(dt);
last = now;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
Am Dienstag, 1. Juli 2014 03:20:03 UTC+2 schrieb JasonJAyalaP:
I thought we already did animation frame?
Well this implementation doesn’t solve most problems we have with the current implementation.
While I agree that it is better than SetTimout, it would only really work well for animations, not game logic.
The snippet I posted before shows a nice logic loop / render loop combination, but it doesn’t really fit in the concept of signals. We would need two signals that way, one for rendering and one for logic.
However, I don’t know if we could then combine those nicely in elm.
Fixed is the fixed time variable, used in the logic function to do deterministic updates.
The loop function expresses the functionality the OP and that blog post you linked to describe as the best way: accumulate timesteps, when the accumulated value is higher than the fixed time value do an update, subtract the fixed time from the accumulated value, loop again.
Focus on the loop function, ignore the rest of the code. That's just to have a compiling example.
That won’t solve the main problem we have, that is to have a (strictly) constant deltaTime value for the logic loop. Both every and fps do not provide a constant, deterministic deltaTime value. (Because both setInterval or setTimeout are absolutely not reliable)
This works fine for many games, but for games with multiplayer, physics based games or games that react to music, a non deterministic deltaTime leads to too many problems.
Rendering is done with requestAnimationFrame
Does that mean that no matter how fast (or slow) we think we are drawing to main, it always updates at the same rate? (Which would be a good thing)
So for me it seems like the best solution would be to change the fps signal to be this part of the js snippet:
var now,
dt = 0
,
last = timestamp(),
step = 1/60; // here we would plug in the argument of fps x: step = 1/x
function frame() {
now = timestamp();
dt = dt + Math.min(1, (now - last) / 1000);
while
(dt > step) {
dt = dt - step;
update(step); // This should trigger the fps signal.
// The argument 'step' isn't really necessary
// (because we know it's value), but for simplicity and
// backwards compatibility we could still provide it.
}
// Elm would render the scene for us here. Using dt, Elm could now linearly interpolate
// between the last frame and the current one.
render(dt);
last = now;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
Rendering is done with requestAnimationFrameDoes that mean that no matter how fast (or slow) we think we are drawing to main, it always updates at the same rate? (Which would be a good thing)
To keep it in line with the existing API, I proposed an "every frame" signal in the other thread I linked that used requestAnimationFrame instead of setInterval.
Ultimately I ended up just using "every millisecond" (which actually just runs every 16ms - on each frame - on Chrome and Firefox anyway), so a new API might not be necessary.
Dylan Sale
--
Yeah you still need to implement an accumulator for the "every millisecond" or "every frame" solution. I agree that locking it to the render (requestAnimationFrame) is best because you avoid the interval timer becoming out of sync with the frames. I'm not sure that actually happens in modern browsers, but even if it doesn't, it is relying on nonstandard behaviour.
Dylan Sale
I tried adapting Jeff's demo to use WebGL and linear interpolation for a comparison. I'm not sure how to measure the framerate, but at least I can't notice any stuttering. The movement doesn't look too great, but I assume that's because of the high contrast between the triangle and the background.Here's the code:-- Try adding the ability to crouch or to land on top of the crate.import Graphics.Element exposing (..)import Http exposing (..)import Keyboardimport Math.Vector2 exposing (Vec2)import Math.Vector3 exposing (..)import Math.Vector3 as V3import Math.Matrix4 exposing (..)import Task exposing (Task)import Textimport Time exposing (..)import WebGL exposing (..)import Window-- MODELtype alias Person ={ position : Vec3, velocity : Vec3}type alias Inputs =( Bool, {x:Int, y:Int}, Float )eyeLevel : FloateyeLevel = 2defaultPerson : PersondefaultPerson ={ position = vec3 0 eyeLevel -10, velocity = vec3 0 0 0}-- UPDATEupdate : Inputs -> Person -> Personupdate (isJumping, directions, dt) person =person|> walk directions|> jump isJumping|> gravity dt|> physics dtwalk : { x:Int, y:Int } -> Person -> Personwalk directions person =if getY person.position > eyeLevel thenpersonelseletvx = toFloat -directions.xvz = toFloat directions.yin{ person |velocity <- vec3 vx (getY person.velocity) vz}jump : Bool -> Person -> Personjump isJumping person =if not isJumping || getY person.position > eyeLevel thenpersonelselet(vx,_,vz) = toTuple person.velocityin{ person |velocity <- vec3 vx 2 vz}physics : Float -> Person -> Personphysics dt person =letposition =person.position `add` V3.scale dt person.velocity(x,y,z) = toTuple positionin{ person |position <-if y < eyeLevel then vec3 x eyeLevel z else position}gravity : Float -> Person -> Persongravity dt person =if getY person.position <= eyeLevel thenpersonelseletv = toRecord person.velocityin{ person |velocity <- vec3 v.x (v.y - 2 * dt) v.z}-- SIGNALSworld : Maybe Texture -> Mat4 -> List Entityworld maybeTexture perspective =case maybeTexture ofNothing ->[]Just tex ->[entity vertexShader fragmentShader crate { crate=tex, perspective=perspective }]main : Signal Elementmain =letperson =Signal.foldp update defaultPerson inputsentities =Signal.map2 worldtexture.signal(Signal.map2 perspective Window.dimensions person)inSignal.map2 view Window.dimensions entitiestexture : Signal.Mailbox (Maybe Texture)texture =Signal.mailbox Nothingport fetchTexture : Task WebGL.Error ()port fetchTexture =loadTexture "/texture/woodCrate.jpg"`Task.andThen` \tex -> Signal.send texture.address (Just tex)inputs : Signal Inputsinputs =letdt = Signal.map (\t -> t/500) (fps 25)inSignal.map3 (,,) Keyboard.space Keyboard.arrows dt|> Signal.sampleOn dt-- VIEWperspective : (Int,Int) -> Person -> Mat4perspective (w,h) person =mul (makePerspective 45 (toFloat w / toFloat h) 0.01 100)(makeLookAt person.position (person.position `add` k) j)view : (Int,Int) -> List Entity -> Elementview (w,h) entities =layers[ webgl (w,h) entities, container w 100 position message]position =midLeftAt (absolute 40) (relative 0.5)message : Elementmessage =leftAligned <| Text.monospace <| Text.fromString <|"Walk around with a first person perspective.\n"++ "Arrows keys to move, space bar to jump."-- Define the mesh for a cratetype alias Vertex ={ position : Vec3, coord : Vec3}crate : List (Triangle Vertex)crate =List.concatMap rotatedFace [ (0,0), (90,0), (180,0), (270,0), (0,90), (0,-90) ]rotatedFace : (Float,Float) -> List (Triangle Vertex)rotatedFace (angleXZ,angleYZ) =letx = makeRotate (degrees angleXZ) jy = makeRotate (degrees angleYZ) it = x `mul` yinList.map (WebGL.map (\v -> {v | position <- transform t v.position })) faceface : List (Triangle Vertex)face =lettopLeft = Vertex (vec3 -1 1 1) (vec3 0 1 0)topRight = Vertex (vec3 1 1 1) (vec3 1 1 0)bottomLeft = Vertex (vec3 -1 -1 1) (vec3 0 0 0)bottomRight = Vertex (vec3 1 -1 1) (vec3 1 0 0)in[ (topLeft,topRight,bottomLeft), (bottomLeft,topRight,bottomRight)]-- ShadersvertexShader : Shader { position:Vec3, coord:Vec3 } { u | perspective:Mat4 } { vcoord:Vec2 }vertexShader = [glsl|attribute vec3 position;attribute vec3 coord;uniform mat4 perspective;varying vec2 vcoord;void main () {gl_Position = perspective * vec4(position, 1.0);vcoord = coord.xy;}|]fragmentShader : Shader {} { u | crate:Texture } { vcoord:Vec2 }fragmentShader = [glsl|precision mediump float;uniform sampler2D crate;varying vec2 vcoord;void main () {gl_FragColor = texture2D(crate, vcoord);}|]