Jump to content
  • Advertisement
  • entries
    8
  • comments
    3
  • views
    1004

About this blog

Challenging myself to make a browser based First Person Survival game using Node and ThreeJS inspired by the classic Midwinter for the Amiga 500.

I'm a SAP programmer by day (so not much of a programmer some might argue! 😂) with very little experience of game dev.  Once created a mobile game called Lux which was fairly rubbish and pretty much stuck to doing music for indie devs since as a hobby.  Prior to that I got into C# and XNA which was pretty good fun until Microsoft canned it.  Dabbled with Unity a bit but wanted to have a laugh creating a game from scratch using nothing but Node and ThreeJS.

So why not drop by and have a laugh at me striving and struggling to create a game of a scale that is well beyond anything a solo inexperienced dev should ever attempt!  🤯 

image.thumb.png.f6691f4eefa355f2bab2407b6b44543e.png

Entries in this blog

State Changes

Games usually (if not always) require some way to manage state changes... and I'm sure most of you (if not all of you) know far more about State Machines than I do.  And I'm certain that I could learn a heck of lot from reading up about the subject to build a state machine that works beautifully and makes my code look amazing etc etc. Pfft.. never mind all that... I'm building this game 'off the cuff' as it were, making it up as I go along and following the principle of 'I build what I need when I need it and only insofar that it adequately fulfils the requirements at that time'.  I don't try to plan ahead (not in any granular sense anyway), I'm not building a reusable one-size-fits-all game engine, I'm not trying to make the code beautfiul, or win any awards or even make any money from the darn thing.  It just needs to perform well enough for what I want it to do. So my immediate requirement is that I have a way to manage the player switching from walking to running to whatever.  If I can use it elsewhere for other things then great... and I'll be honest, I do like reusable code so I tend to naturally sway toward that.  What I'm trying to avoid is getting myself stuck in a rut, spending weeks/months deliberating over the smallest details because it's got to be 'perfect' and then realising I've still got 99.5% of the game to build!  Quick and dirty is OK in my world. I often approach things from a top-down perspective.  This boils down to: 'How do I want to instruct the computer to do x, y or z?' So for this particular requirement, how do I want to instruct the game that the player can change from walking to running and running to walking, or walking/running to falling (assuming I make that a player state - which I do), but not from sleeping to running for example?  Hell, I don't even know all the states that I want yet, but these are the ones I have a feel for so far: Walking Running Skiiing Driving Falling Drowning Sleeping Eating Introducing 'When' I thought it might be nice to be able to write something like this in my player setup: // Configure valid player state transitions When( this.playerState ).changes().from( PLAYER_STATES.WALKING ).to( PLAYER_STATES.RUNNING ).then( function () { } ); When( this.playerState ).changes().from( PLAYER_STATES.RUNNING ).to( PLAYER_STATES.WALKING ).then( function () { } ); When( this.playerState ).changes().from( PLAYER_STATES.WALKING ).to( PLAYER_STATES.SKIING ).then( function () { } ); When( this.playerState ).changes().from( PLAYER_STATES.SKIING ).to( PLAYER_STATES.WALKING ).then( function () { } ); When( this.playerState ).changes().from( PLAYER_STATES.WALKING, PLAYER_STATES.RUNNING, PLAYER_STATES.SKIING ).to( PLAYER_STATES.FALLING ).then( function () { } ); There's probably a library for something like this out there, but heck, where's the fun in that?!   So I create a new 'Stateful' object that represents a state (in this case the playerState) and it's allowed transitions and a 'When' function so I can write the code exactly as above: const Stateful = function () { } Stateful.isStateful = function ( obj ) { return obj.constructor && obj.constructor.name === Stateful.name; } Stateful.areEqual = function ( v1, v2 ) { return v1.equals ? v1.equals( v2 ) : v1 == v2; } Stateful.prototype = { constructor: Stateful, set: function ( v ) { let newState = typeof ( v ) === "function" ? new v() : v; for ( let i = 0; i < this.transitions.length; i++ ) { let transition = this.transitions[i]; if ( transition && typeof ( transition.callback ) === "function" ) { let fromMatch = Stateful.areEqual( transition.vFrom, this ); let toMatch = Stateful.areEqual( transition.vTo, newState ); if ( fromMatch && toMatch ) { // We can only change to the new state if a valid transition exists. this.previousState = Object.assign( Object.create( {} ), this ); Object.assign( this, newState ); transition.callback( this.previousState, this ); } } } }, transitions: Object.create( Object.assign( Array.prototype, { from: function ( vFrom ) { this.vFrom = typeof ( vFrom ) === "function" ? new vFrom() : vFrom; return this; }, to: function ( vTo ) { this.vTo = typeof ( vTo ) === "function" ? new vTo() : vTo; return this; }, remove: function ( fn ) { this.vFrom = this.vFrom === undefined ? { equals: function () { return true; } } : this.vFrom; this.vTo = this.vTo === undefined ? { equals: function () { return true; } } : this.vTo; for ( let i = 0; i < this.length; i++ ) { let transition = this[i]; let fromMatch = Stateful.areEqual( this.vFrom, transition.vFrom ); let toMatch = Stateful.areEqual( this.vTo, transition.vTo ); let fnMatch = fn === undefined ? true : transition.callback == fn; if ( fromMatch && toMatch & fnMatch ) { delete this[i]; } } } } ) ) } function When( statefulObj ) { if ( !Stateful.isStateful( statefulObj ) ) { throw "Argument must be a Stateful object"; } return { changes: function () { return { from: function ( ...vFrom ) { this.vFrom = vFrom; return this; }, to: function ( ...vTo ) { this.vTo = vTo; return this; }, then: function ( fn ) { if ( typeof ( fn ) === "function" ) { this.vFrom = this.vFrom === undefined ? [true] : this.vFrom; this.vTo = this.vTo === undefined ? [true] : this.vTo; for ( let i = 0; i < this.vFrom.length; i++ ) { for ( let j = 0; j < this.vTo.length; j++ ) { statefulObj.transitions.push( { vFrom: typeof ( this.vFrom[i] ) === "function" ? new this.vFrom[i]() : this.vFrom[i], vTo: typeof ( this.vTo[j] ) === "function" ? new this.vTo[j]() : this.vTo[j], callback: fn } ); } } } else { throw "Supplied argument must be a function"; } } }; } } } I drop the aforementioned 'When' statements into my Player setup and remove the old 'If' statements that were previously controlling changes between walking and running and insert the new playerState.set() calls where appropriate. e.g. "run": ( pc, keyup ) => { if ( keyup ) { _this.player.playerState.set( PLAYER_STATES.WALKING ); } else { _this.player.playerState.set( PLAYER_STATES.RUNNING ); } } And it seems to work!  (Yes I was actually surprised by that) 😂 TheBerg-StateChanges.mp4 p.s. I've switched to using Bandicam for screen capture as it seems far superior to what I was using previously.

Greedy Goblin

Greedy Goblin

Slopey McSlopeface (part 2)

Having played around with the character movement a bit more I realised I was doing many things wrong.  Not that I had ever intended to do things perfectly, but the slope handling just wasn't up to scratch.  I had acceleration working, but deceleration didn't due to the way I had built things.  So I decided to do a little bit of an overhaul of the player movement system.  Nothing too major it turns out but I now have both acceleration and deceleration working very nicely and it gives a much smoother feel to the controls.  It's subtle but necessary in my opinion. There is one aspect of slopes that I haven't tackled yet though and that is falling, tumbling or sliding on steep slopes.  As I'm not using any fancy physics engine my player character is essentially glued to the terrain like it's on rails.  It's not really an issue in so far as jumping is concerned because I don't intend to have any jumping ability in the game.  However, I decided that I didn't want to artificially prevent the player from walking off the edge of a cliff so I need some form of falling action (and tumbling if you run down a steep slope too fast - potentially injuring yourself). So what I've done is add forces into my CollisionBody class to represent an accelerant force due to gravity.  But to keep my brain from imploding over the quirks of simulating physics (and so as not to have to implement a full-blown physics engine) I decided to half keep the on-rails aspect by doing this: if ( obj.collisionBody.position.y <= targetPos.y ) { obj.collisionBody.position.y = targetPos.y; obj.collisionBody.resetForces(); } else { // Apply gravity obj.collisionBody.applyForce( new Force( DEFAULT_VECTORS.down, _this.gAcceleration ) ); } Where 'targetPos' is the ground position.  So if the player is in contact with the ground or moves below it, then they player will just snap back to the precise point on the terrain.  It's only when the player goes above the terrain (into the air) that gravity comes into play.  This helps keep things nice and simple and gives a nice feel to the movement. Oh and I've also added in some 'head-bobbing' to add a bit of realism to the movement.  Overall it gives a really nice result.... and I can now fall off cliffs... You may also notice that the camera rotation is now much smoother too as I now 'slerp' between rotations rather than doing absolute movements, although my screen recording software doesn't do it justice in the video above.

Greedy Goblin

Greedy Goblin

Look what I found....

Haha!  Take a look what I uncovered the other day while digging through my books.... It even came with a good old floppy disk!  (I couldn't even use it if I wanted to now)... This book was my very first on 3D graphics programming, before 3D graphics card were even a thing (for the mainstream masses anyway).  Published in 1994, it's a book that served as a really good primer to understanding 3D geometry and programming even if I never bothered with the later chapters on curved geometry.  All the code samples were in C (not even C++) and there was no such mention of things such as texture mapping (although it did cover shadows, reflections and refraction!).  It took you right from the beginning; from the equation of a straight line, translations, matrices etc to projections (viewing pyramid) and clipping algorithms.  Great stuff! I still find it useful now as I re-read the introductory chapters and refresh myself on a few basics. My other go-to book is "Real Time Collision Detection" by Christer Ericson - an absolute diamond of a book! Do you guys and girls have any books that you rely on or hold in high regard for your game development?  It would be interesting to know if anyone has ever seen or read the Georg Glaeser book above. Anyway, I'll soon have another update on The Berg, but it's bed time for me now.  Night all.  

Greedy Goblin

Greedy Goblin

Slopey McSlopeface (part 1)

Hmmm, I love sausage rolls.  Oh sorry, just having a tasty sausage roll for my lunch while thinking about the problem of... slopes. So I have this nice terrain an' all, that I can now walk around on.  But those hills aren't exactly challenging my player character.  I breeze up those as easily as I descend a vertical drop.  Why is that?  Because I'm just making the player follow the terrain like it's on rails, that's why!  No fancy pants physics engines here guv. I need to make the player ascend steep inclines more slowly than flat terrain and also come to a halt against any vertical, or near-vertical inclines.  I also need to make the player fall or tumble if they attempt to go down any incline too steep... but that's for another blog. I first messed around with attempting to stop the player if they tried to walk up anything with a gradient over a certain threshold... that didn't work out too well.  Lots of jittery movement ensued.  Then I realised my approach was all wrong!  When I climb a steep hill, I don't just come to a dead stop in reality, I just move more slowly as I ascend.  So this just became a case of factoring in the terrain gradient to my player speed... simples! // Test slope in direction of travel let direction = new THREE.Vector3().copy( this.collisionBody.resolveMoves().normalize() ); let p1 = new THREE.Vector3().copy( this.position ); let intersection1 = _this.terrain.heightAt( p1.x, p1.z ); let p2 = new THREE.Vector3().addVectors( this.position, direction ); let intersection2 = _this.terrain.heightAt( p2.x, p2.z ); let grad = intersection2.h - intersection1.h; _this.playerController.currentSpeed = Math.clamp( -_this.playerController.DEFAULT_SPEEDS.WALKING * grad + _this.playerController.DEFAULT_SPEEDS.WALKING, 0, 2 ); Works a treat.... except (sigh) when I'm up against a near vertical incline.  I can still climb those, just a bit more slowly (sigh).  In fact, no, this doesn't look right at all.  The graident should be so large when approaching a near vertical incline that it should reduce my speed to 0.  But for some reason I get this.... TheBerg-SlopeyMcSlopeface-01.mp4 Much head scratching ensued! Then I realised it must be something to do with the order of processing.  The way I handle movement is through a "CollisionBody" object which is attached to the player.  I call a "move" function on the CollisionBody which basically stores it in an array until the "PhysicsManager" decides to resolve it (reminder: there is no fancy physics going on at all yet... I just decided to call it a PhysicsManager).  The thing is, my PlayerController was calling "move" before I had changed my speed to zero, so the movement vectors were already baked in (I know, I know, this is getting complicated already!) and changing the speed did nothing.  Each alternate frame I was then resetting my speed back to normal walking speed.... You know, it's only when I write all that down I see just how crazy dumb it was!  😂 So I decided on an alternative approach.  Instead of setting an absolute velocity vector when calling CollisionBody.move I now pass a direction vector and speed as a new Velocity object... however... the speed doesn't have to be a number... it can be a function!  Thereby I can delay the resolution of the velocity until absolutely necessary (i.e. when the PhysicsManager needs to apply the moves).  It may sound complicated but I quite like it.  I can then make any other adjustments to the speed (not velocity) during the frame update and it will take effect when the PhysicsManager resolves everything! e.g. PlayerController handles movement -> player.collisionBody.move( new Velocity( direction, () => player.speed ) Player object "update" tests for slopes and adjusts speed accordingly PhysicsManager "update" resolves all player.collisionBody movements, in turn resolving all Velocity values using the new updated speed rather than the speed as it was in step 1 So how does this work out now? TheBerg-SlopeyMcSlopeface-02.mp4 Better... if you watch very closely you might notice I've added in a subtle acceleration too which gives a smoother feel to the player movement (later I'll add in head bobbing).  However, I'm still not entirely happy with it.  When you stop the player doesn't decelerate, but comes to an abrupt stop... which is fine but feels a little unnatural.  Near vertical inclines are also handled better, although I still find with the "right" movements I can still climb them so I definitely need to work on this more. Acceleration is done by adding in a targetSpeed property on the Player, so the actual speed will accelerate towards that speed until it is reached.  It does decelerate too as the incline increases, but when you lift your key from the keyboard the direction vector is reset to {0, 0, 0} so the speed has no effect.  I'll have a think a bit more about this later.  Ideas and suggestions are welcome!  

Greedy Goblin

Greedy Goblin

Water water everywhere... and not a drop to drink!

Oh boy... my fave part... simulating water!  You'll see on my YouTube channel I'm not new to this...   But let's not get carried away... I don't need water looking anywhere near as realistic for 'The Berg'.  A simple sub-divided plane and another vertex displacement shader will do the trick... The problem I have is that I get 'hung up' on water.  I can fiddle for hours tweaking this and that.  MUST NOT DO THAT THIS TIME!! ... Several days later... float step = viewSize / subdivisions; vec2 pp = vec2(playerPos.x, -playerPos.z); vec2 adjPlayerPos = floor(pp / step) * step; vec2 remainderPP = pp - adjPlayerPos; float uvScale = viewSize / mapSize; vec2 uvOffset = vec2(adjPlayerPos + (mapSize / 2.) - (viewSize / 2.)) / mapSize; vec2 newUV = uvOffset + uv * uvScale; float dOffset = 0.001; vec2 depths = vec2(0); depths.x = clamp(nSeaLevel - texture2D(heightMap, newUV + vec2(-dOffset, -dOffset)).r, 0., nSeaLevel) / nSeaLevel; depths.y = clamp(nSeaLevel - texture2D(heightMap, newUV + vec2(dOffset, dOffset)).r, 0., nSeaLevel) / nSeaLevel; float avDepth = (depths.x + depths.y) / 2.; float displace = 0.; vec2 waveOrigin[3]; waveOrigin[0] = vec2(0.5, 0.5); waveOrigin[1] = vec2(0.3, 0.25); waveOrigin[2] = vec2(0.9, 0.75); float waveOriginDist[3]; waveOriginDist[0] = distance( waveOrigin[0], newUV ); waveOriginDist[1] = distance( waveOrigin[1], newUV ); waveOriginDist[2] = distance( waveOrigin[2], newUV ); vec2 t; for (int i = 0; i < 3; i++) { displace += cos( waveFreq * ( 1. / float(i + 1) ) * waveOriginDist[i] + (time * 2.)) * avDepth; displace += cos( waveFreq * avDepth * 2.5 * waveOriginDist[i] - (time * 1.5) ) * 0.5; t += normalize(newUV - waveOrigin[i]) * cos(time * 0.1); } displace *= avDepth; t *= 10.; vec2 adjVertPos = vec2( position.x - remainderPP.x, position.y - remainderPP.y ); vec3 newPos = vec3( adjVertPos.x + t.x, adjVertPos.y + t.y, displace * heightScale ); vec3 transformed = newPos; What... have... I... done...? What kind of monster have I created? ... 🤣 Just kidding....  

Greedy Goblin

Greedy Goblin

 

Feeling the ice beneath my feet

Ok, so far the terrain looks pretty good, but I can't walk on it.  How the heck do I do that? Two options as I see it... Try and use the heightmap to figure out what the height is at my current position and just get the camera to follow that. Generate collision geometry from the heightmap and then do ray-triangle based collision detection.  Yeah yeah, I'm sure I could just find some free resources/JS library on t'interweb that will do all this gubbins for me and give me a full blown phsyics engine to boot... but where's the fun in that?!  Oh yeah, by the way, did I mention I'm nuts. After much thought and general mucking around I opted for the 2nd option.  To create a geometry object that would contain all the vertices/faces for the terrain.  I don't need to render it so no need to create a mesh (can you imagine the insanity of rendering a mesh of this scale in the browser on a low-end device... madness I tell you, madness!).  I could potentially, at a future date, even stream the bit of collision geometry that I need from the Node back-end on the fly to speed things up further. For now though I'll just create the geometry in memory on the client-side and use that.  It's just an array of vertices after all. this.generateCollisionMesh = function () { // Render the height map texture off-screen and read the pixel data into an array let texSize = _this.heightMap.image.width; _this.pickingScene = new THREE.Scene(); _this.renderTarget = new THREE.WebGLRenderTarget( texSize, texSize ); _this.pickingCamera = new THREE.OrthographicCamera( 0, texSize, 0, texSize, 0.1, 10 ); _this.renderTarget.texture.minFilter = THREE.NearestFilter; _this.renderTarget.texture.magFilter = THREE.NearestFilter; let mapGeom = new THREE.PlaneBufferGeometry( texSize, texSize, 1, 1 ); let mapMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide } ); mapMaterial.map = _this.heightMap; let mapMesh = new THREE.Mesh( mapGeom, mapMaterial ); mapMesh.position.set( texSize / 2, texSize / 2, -1 ); mapMesh.rotation.x = Math.PI; _this.pickingScene.add( mapMesh ); let pixelData = new Uint8Array( 4 * Math.pow( texSize, 2 ) ); _this.renderer.render( _this.pickingScene, _this.pickingCamera, _this.renderTarget ); let ctx = _this.renderer.getContext(); ctx.readPixels( 0, 0, texSize, texSize, ctx.RGBA, ctx.UNSIGNED_BYTE, pixelData ); // Create a plane geometry that we can modify to create the collision mesh let tSubdivisions = ( _this.mapSize / _this.viewSize ) * _this.subdivisions; _this.collisionGeom = new THREE.PlaneGeometry( _this.mapSize, _this.mapSize, tSubdivisions, tSubdivisions ); let mapHalfSize = _this.mapSize / 2; for ( let i = 0; i < _this.collisionGeom.vertices.length; i++ ) { let v = _this.collisionGeom.vertices[i]; let tx = ( ( ( v.x + mapHalfSize ) / _this.mapSize ) * texSize ); let ty = ( ( ( v.y + mapHalfSize ) / _this.mapSize ) * texSize ); let pdIdx = Math.clamp( Math.floor( tx + ( ty * texSize ) ) * 4, 0, pixelData.length ); if ( isNaN( pdIdx ) || isNaN( pixelData[pdIdx] ) ) { continue; } v.z = ( pixelData[pdIdx] / 255 ) * _this.maxHeight; } _this.collisionGeom.verticesNeedUpdate = true; _this.collisionGeom.normalsNeedUpdate = true; _this.collisionGeom.computeVertexNormals(); _this.collisionGeom.computeFaceNormals(); _this.collisionGeom.computeBoundingBox(); } I don't even know if I need those 5 lines at the end but I'll leave them in for good measure. p.s. It took me a few days to get this right as the collision geometry just wasn't quite matching up to the actual rendered mesh.  Turns out it was because I'd not set: _this.renderTarget.texture.minFilter = THREE.NearestFilter; _this.renderTarget.texture.magFilter = THREE.NearestFilter; Oh my wasted life!!! 😡 Terrain collision geometry now working so I created a little function to test the height at a given point... this.heightAt = function ( x, y ) { function rayTriangleIntersection( p, ray, v1, v2, v3 ) { let ab = new THREE.Vector3().subVectors( v2, v1 ); let ac = new THREE.Vector3().subVectors( v3, v1 ); let n = new THREE.Vector3().crossVectors( ab, ac ); let d = ray.dot( n ); if ( d <= 0 ) return false; let ap = new THREE.Vector3().subVectors( p, v1 ); let t = -ap.dot( n ); if ( t < 0 ) return false; let e = new THREE.Vector3().crossVectors( ray, ap ); let u, v, w; v = ac.dot( e ); if ( v < 0 || v > d ) return false; w = -ab.dot( e ); if ( w < 0 || v + w > d ) return false; let ood = 1.0 / d; t *= ood; v *= ood; w *= ood; u = 1.0 - v - w; let pRay = ray.multiplyScalar( t ); return { point: pRay.add( p ), normal: new THREE.Vector3( u, v, w ) }; } let halfSize = _this.collisionGeom.boundingBox.max.x; let mapSubdiv = _this.mapSize / _this.viewSize * _this.subdivisions; let localPos = new THREE.Vector3( x, y, 0 ); localPos.add( new THREE.Vector3( halfSize, halfSize, 0 ) ); let t = localPos.divideScalar( halfSize * 2 ); t.multiplyScalar( mapSubdiv ); // Determine a 'grid' position from the local position let g = new THREE.Vector2( Math.floor( t.x ), Math.floor( t.y ) ); // And determine which half of the grid square the co-ords are in let faceOffset = ( 1 - Math.frac( t.y ) <= Math.frac( t.x ) ) ? 1 : 0; // Use this to calculate the face array index. let idx = ( g.x + ( g.y * mapSubdiv ) ) * 2 + faceOffset; let face = _this.collisionGeom.faces[idx]; if ( !face ) return; // Then use the associated vertices to calc the intersection let v1 = _this.collisionGeom.vertices[face.a]; let v2 = _this.collisionGeom.vertices[face.b]; let v3 = _this.collisionGeom.vertices[face.c]; let p = rayTriangleIntersection( new THREE.Vector3( x, -y, 0 ), new THREE.Vector3( 0, 0, 1 ), v1, v2, v3 ); return p === false ? p : { h: p.point.z, normal: face.normal }; } I'm sure I could code that better.... but really, I can't be bothered.... it works....  

Greedy Goblin

Greedy Goblin

Adding atmosphere and a mini-map

After some extensive refactoring of my messy code (it's still a mess but at least it's refuctored mess now) I decided to add a bit of atmosphere to the game by adding some fog... which also, quite conveniently, hides the edge of the terrain geometry so you don't see it 'popping' in.  😂 I also built a mini-map class to display in the top-left corner of the screen.  Again, easier said than done.  I had no idea how to go about creating HUD elements with ThreeJS... no... idea!  I mean, I've literally only just picked up ThreeJS so I have very little idea what I'm doing with it (disclaimer: I did once upon a time spend considerable time with XNA and the Sunburn rendering engine so I'm not a total noob to 3D rendering).  I eventually decided to create a separate ThreeJS renderer, scene and all that gubbins just to render the mini-map... possible overkill?  Yeah well, so what... it works. Next I need to make the player/camera follow the terrain.... aww what?... how does that work when using a displacement shader (??!?)... the terrain vertices ain't baked in man!  Next blog....

Greedy Goblin

Greedy Goblin

Into The Berg

So here we are.  I'm not a seasoned blogger, or skilled game dev or any such thing.  Social media is, in my opinion, a bit of a pain, so don't be expecting regular daily updates or anything like that.  They'll happen when they happen. This project (The Berg) is just my idea of fun in my spare time.  I didn't want to use any fancy game engines or integrate a full-blown physics engine.  The idea is to keep it retro, as simple as possible while still creating something that a) looks fairly decent and b) is fun(ish) to play. I have no idea how this is going to turn out, or if it will ever get completed but we'll all have a good old laugh at my incompetance along the way I'm sure.  😜 Anyway, so I already got part way in to building this thing and thought 'Hey, here's a really dumb idea, why not blog about it for a laugh and waste more of my precious spare time!'.  Awesome! So what I have so far is a simple Node/ExpressJS/ThreeJS set up in VS Code and I've created the island geometry from a 2D texture passed into a custom vertex shader which takes a flat subdivided plane and applies a displacement to each vertex based on the appropriate UV co-ords.  I want to keep a retro look and feel so this isn't some smooth terrain; it's full of lovely flat shaded triangle-y goodness. So I created a terrain height map in Gimp (with the help of an online island generator tool for the contour -> http://exupero.org/hazard/post/islands/). The real size is 2048 x 2048.  I want to create something on the larger scale here! Once my Node/ThreeJS setup was up and running, I built a Terrain class (this is all in Javascript by the way) and set about loading the texture using the ThreeJS loaders and passing it through to my terrain vertex displacement shader. P.S.  The terrain shader took me a while to figure out.  Initially it didn't work quite as expected but eventually I got there.  Essentially I pass the player position to the shader and only sample a smaller portion of the heighmap depending on what view distance I want.  So I'm only ever rendering a small sub-section of the heightmap which changes as the player moves.  This was easier said than done... in my head at least. In the end it looks something like this... float step = viewSize / subdivisions; vec2 pp = vec2(playerPos.x, -playerPos.z); vec2 adjPlayerPos = floor(pp / step) * step; vec2 remainderPP = pp - adjPlayerPos; float uvScale = viewSize / mapSize; vec2 uvOffset = vec2(adjPlayerPos + (mapSize / 2.) - (viewSize / 2.)) / mapSize; vec2 newUV = uvOffset + uv * uvScale; vec4 dm = texture2D(heightMap, newUV); vec3 newPos = vec3( position.x - remainderPP.x, position.y - remainderPP.y, dm.r * heightScale ); vec3 transformed = newPos; Sheesh!  That looks... err... complicated, but it gives a great result....      

Greedy Goblin

Greedy Goblin

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!