1 /* 2 ******************************************************************************************* 3 * Dgame (a D game framework) - Copyright (c) Randy Schütt 4 * 5 * This software is provided 'as-is', without any express or implied warranty. 6 * In no event will the authors be held liable for any damages arising from 7 * the use of this software. 8 * 9 * Permission is granted to anyone to use this software for any purpose, 10 * including commercial applications, and to alter it and redistribute it 11 * freely, subject to the following restrictions: 12 * 13 * 1. The origin of this software must not be misrepresented; you must not claim 14 * that you wrote the original software. If you use this software in a product, 15 * an acknowledgment in the product documentation would be appreciated but is 16 * not required. 17 * 18 * 2. Altered source versions must be plainly marked as such, and must not be 19 * misrepresented as being the original software. 20 * 21 * 3. This notice may not be removed or altered from any source distribution. 22 ******************************************************************************************* 23 */ 24 module Dgame.Graphics.Spritesheet; 25 26 private { 27 import Dgame.Graphics.Texture; 28 import Dgame.Graphics.Sprite; 29 import Dgame.Math.Rect; 30 import Dgame.System.Clock; 31 } 32 33 /** 34 * SpriteSheet extends Sprite and has, besides the texture, 35 * even a viewport and acts as a Texture Atlas. 36 * With slideViewport the viewport slides over the current row of the texture atlas. 37 * With setRow the current row can be changed (increased, decreased). 38 * 39 * Author: rschuett 40 */ 41 class Spritesheet : Sprite { 42 /** 43 * The Grid 44 */ 45 enum Grid : ubyte { 46 None = 0, /// No Grid is used 47 Row = 1, /// Only Rows are used 48 Column = 2, /// Only Columns are used 49 Both = Row | Column /// Both, Columns <b>and</b> Rows are used 50 } 51 52 /** 53 * Set or get the current row. 54 * This only matters, if you slide without Grid.Column. 55 */ 56 ubyte row; 57 58 private: 59 short _loopCount; 60 ushort _passedLoops; 61 size_t _lastTick; 62 ushort _tickOffset; 63 64 ShortRect _texView; 65 66 protected: 67 override float[8] _getTextureCoordinates() const pure nothrow { 68 float tx = 0f; 69 float ty = 0f; 70 float tw = 1f; 71 float th = 1f; 72 73 if (!this._texView.isZero()) { 74 tx = (0f + this._texView.x) / super._tex.width; 75 ty = (0f + this._texView.y) / super._tex.height; 76 77 if (!this._texView.isCollapsed()) { 78 tw = (0f + this._texView.width) / this._tex.width; 79 th = (0f + this._texView.height) / this._tex.height; 80 } 81 } 82 83 return [tx, ty, tx + tw, ty, tx + tw, ty + th, tx, ty + th]; 84 } 85 86 public: 87 /** 88 * Returns the current Clip Rect 89 */ 90 override ShortRect getClipRect() const pure nothrow { 91 ushort[2] clipSize = void; 92 93 if (this._texView.isCollapsed()) 94 clipSize = [super._tex.width, super._tex.height]; 95 else 96 clipSize = [this._texView.width, this._texView.height]; 97 98 return ShortRect(cast(short) super.position.x, cast(short) super.position.y, 99 clipSize[0], clipSize[1]); 100 } 101 102 final: 103 /** 104 * CTor 105 */ 106 this(Texture tex, short lc = -1) { 107 super(tex); 108 109 this.setLoopCount(lc); 110 } 111 112 /** 113 * CTor 114 */ 115 this(Texture tex, ref const ShortRect texView, short lc = -1) { 116 this(tex, lc); 117 118 this.setTextureRect(texView); 119 } 120 121 /** 122 * CTor 123 * 124 * Rvalue version 125 */ 126 this(Texture tex, const ShortRect texView, short lc = -1) { 127 this(tex, texView, lc); 128 } 129 130 /** 131 * Set a Texture Rect. 132 * This indicates which area of the Texture is drawn. 133 */ 134 void setTextureRect(ref const ShortRect texView) { 135 this._texView = texView; 136 } 137 138 /** 139 * Rvalue version 140 */ 141 void setTextureRect(const ShortRect texView) { 142 this.setTextureRect(texView); 143 } 144 145 /** 146 * Fetch the current Texture Rect, so that you can modify it, if you want to. 147 * 148 * Example: 149 * ---- 150 * Spritesheet s = new Spritesheet(...); 151 * // A lot of code 152 * s.fetchTextureRect().collapse(); 153 * ---- 154 */ 155 inout(ShortRect)* fetchTextureRect() inout pure nothrow { 156 return &this._texView; 157 } 158 159 /** 160 * Set the current loop count. 161 * This specif how often the whole Animation is played. 162 * A value of < 0 means: infinite playing. 163 */ 164 void setLoopCount(short loopCount) pure nothrow { 165 this._loopCount = loopCount; 166 this._passedLoops = 0; 167 } 168 169 /** 170 * Set the current tick offset. 171 * This is the offset between each animation / slide. 172 * Default is 0. 173 */ 174 void setTickOffset(ushort offset) pure nothrow { 175 this._tickOffset = offset; 176 } 177 178 /** 179 * Execute the animation N times where N is the number of the current loop count. 180 * If N is < 0, the animation runs infinite. 181 * 182 * Returns if the loop is still running. 183 * 184 * See: Grid 185 */ 186 bool execute(Grid grid = Grid.Both) { 187 if (this._loopCount < 0 || this._loopCount > this._passedLoops) { 188 this.slideTextureRect(grid); 189 190 return true; 191 } 192 193 return false; 194 } 195 196 /** 197 * Slide/move the current Viewport of the Texture. 198 * So the next area of the Texture atlas will be drawn. 199 * With grid you can decide if both, x and y, or only one of them are updated. 200 * Default both are updated. 201 * 202 * See: Grid 203 */ 204 void slideTextureRect(Grid grid = Grid.Both) in { 205 assert(this._tex !is null, "No Texture."); 206 } body { 207 if ((this._lastTick + this._tickOffset) > Clock.getTicks()) 208 return; 209 210 this._lastTick = Clock.getTicks(); 211 212 const short w = this._texView.width; 213 const short h = this._texView.height; 214 215 if ((grid & Grid.Column) == 0) { 216 // to avoid a cast... 217 this._texView.y = this.row; 218 this._texView.y *= h; 219 } 220 221 if (grid & Grid.Row) { 222 if ((this._texView.x + w) < super._tex.width) 223 this._texView.x += w; 224 else { 225 this._texView.x = 0; 226 if ((grid & Grid.Column) == 0) 227 this._passedLoops++; 228 } 229 } 230 231 if (grid & Grid.Column && this._texView.x == 0) { 232 if ((this._texView.y + h) < super._tex.height) 233 this._texView.y += h; 234 else { 235 this._texView.y = 0; 236 this._passedLoops++; 237 } 238 } 239 } 240 }