Code Sketch
yoi
Category: Programming
import java.awt.BasicStroke
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.GridLayout
import java.awt.Insets
import java.awt.Point
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.Transferable
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Files
import javax.imageio.ImageIO
import javax.swing.BorderFactory
import javax.swing.DefaultListModel
import javax.swing.JButton
import javax.swing.JComboBox
import javax.swing.JColorChooser
import javax.swing.JFileChooser
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.JMenuItem
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.JPopupMenu
import javax.swing.JScrollPane
import javax.swing.JSlider
import javax.swing.JSplitPane
import javax.swing.JTabbedPane
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.Timer
import javax.swing.TransferHandler
import javax.swing.WindowConstants
import javax.swing.SwingUtilities
import scala.collection.mutable.ArrayBuffer
// ============================================================
// GAME FORGE X1 - TOP 1 ORIGINAL NO-CODE GAME STUDIO
// Build games visually. No game-code typing required.
// ============================================================
val STUDIO_W: Int = 1500
val STUDIO_H: Int = 900
val CANVAS_W: Int = 1040
val CANVAS_H: Int = 640
case class ForgeObject(
id: Int,
var name: String,
var kind: String,
var x: Int,
var y: Int,
var w: Int,
var h: Int,
var color: Color,
var solid: Boolean,
var visible: Boolean,
var animation: String,
var speed: Int,
var hp: Int,
var damage: Int,
var points: Int,
var image: BufferedImage
)
val objects = ArrayBuffer[ForgeObject]()
val undoStates = ArrayBuffer[ArrayBuffer[ForgeObject]]()
val redoStates = ArrayBuffer[ArrayBuffer[ForgeObject]]()
var nextObjectId: Int = 1
var selectedIndex: Int = -1
var draggingIndex: Int = -1
var dragOffX: Int = 0
var dragOffY: Int = 0
var gameRunning: Boolean = false
var editorMode: Boolean = true
var playX: Double = 80.0
var playY: Double = 500.0
var playVX: Double = 0.0
var playVY: Double = 0.0
var moveLeft: Boolean = false
var moveRight: Boolean = false
var moveUp: Boolean = false
var moveDown: Boolean = false
var jumpReady: Boolean = true
var playScore: Int = 0
var playLives: Int = 3
var playTime: Double = 0.0
var gameWon: Boolean = false
var selectedTheme: String = "NEON CITY"
var selectedTemplate: String = "PLATFORMER"
var gridEnabled: Boolean = true
var snapEnabled: Boolean = false
var currentTool: String = "SELECT"
var statusText: String = "READY"
var mainFrame: JFrame = null
var studioCanvas: ForgeCanvas = null
var objectList: JList[String] = null
var objectListModel: DefaultListModel[String] = null
var inspector: JTextArea = null
var searchField: JTextField = null
var templateBox: JComboBox[String] = null
var themeBox: JComboBox[String] = null
var statusLabel: JLabel = null
// ============================================================
// COLORS / BUTTONS
// ============================================================
def uiButton(text: String): JButton = {
val b = new JButton(text)
b.setFocusable(false)
b.setMargin(new Insets(4, 8, 4, 8))
b.setFont(new Font("Arial", Font.BOLD, 11))
b
}
def setStatus(s: String): Unit = {
statusText = s
if (statusLabel != null) statusLabel.setText(" " + s)
}
// ============================================================
// OBJECT COPY / HISTORY
// ============================================================
def cloneObject(o: ForgeObject): ForgeObject = {
var img: BufferedImage = null
if (o.image != null) {
img = new BufferedImage(o.image.getWidth, o.image.getHeight, BufferedImage.TYPE_INT_ARGB)
val ig = img.createGraphics()
ig.drawImage(o.image, 0, 0, null)
ig.dispose()
}
o.copy(image = img)
}
def snapshot(): ArrayBuffer[ForgeObject] = {
val out = ArrayBuffer[ForgeObject]()
var i = 0
while (i < objects.length) {
out += cloneObject(objects(i))
i += 1
}
out
}
def saveHistory(): Unit = {
undoStates += snapshot()
redoStates.clear()
while (undoStates.length > 40) undoStates.remove(0)
}
def restoreState(state: ArrayBuffer[ForgeObject]): Unit = {
objects.clear()
var i = 0
while (i < state.length) {
objects += cloneObject(state(i))
i += 1
}
selectedIndex = -1
refreshEditor()
studioCanvas.repaint()
}
def undo(): Unit = {
if (undoStates.nonEmpty) {
redoStates += snapshot()
val s = undoStates.remove(undoStates.length - 1)
restoreState(s)
setStatus("UNDO")
} else setStatus("NOTHING TO UNDO")
}
def redo(): Unit = {
if (redoStates.nonEmpty) {
undoStates += snapshot()
val s = redoStates.remove(redoStates.length - 1)
restoreState(s)
setStatus("REDO")
} else setStatus("NOTHING TO REDO")
}
// ============================================================
// TEMPLATES
// ============================================================
def resetScene(): Unit = {
saveHistory()
objects.clear()
nextObjectId = 1
selectedIndex = -1
playScore = 0
playLives = 3
playTime = 0.0
gameRunning = false
gameWon = false
playVX = 0.0
playVY = 0.0
setStatus("SCENE CLEARED")
refreshEditor()
if (studioCanvas != null) studioCanvas.repaint()
}
def addObject(
name: String,
kind: String,
x: Int,
y: Int,
w: Int,
h: Int,
color: Color,
solid: Boolean,
animation: String = "NONE",
speed: Int = 2,
hp: Int = 1,
damage: Int = 1,
points: Int = 0,
image: BufferedImage = null
): Unit = {
saveHistory()
objects += ForgeObject(
nextObjectId,
name,
kind,
x,
y,
w,
h,
color,
solid,
true,
animation,
speed,
hp,
damage,
points,
image
)
nextObjectId += 1
selectedIndex = objects.length - 1
refreshEditor()
studioCanvas.repaint()
setStatus("ADDED " + name)
}
def makeTemplate(name: String): Unit = {
resetScene()
selectedTemplate = name
if (name == "PLATFORMER") {
addObject("PLAYER", "PLAYER", 70, 520, 58, 58, new Color(60,190,255), false, "BOUNCE", 3)
addObject("GROUND", "PLATFORM", 30, 595, 970, 32, new Color(70,95,125), true)
addObject("PLATFORM 1", "PLATFORM", 170, 480, 190, 24, new Color(105,130,160), true)
addObject("PLATFORM 2", "PLATFORM", 450, 410, 170, 24, new Color(105,130,160), true)
addObject("PLATFORM 3", "PLATFORM", 730, 500, 160, 24, new Color(105,130,160), true)
addObject("COIN 1", "COIN", 255, 435, 30, 30, new Color(255,215,50), false, "BOUNCE", 3, 1, 0, 100)
addObject("COIN 2", "COIN", 520, 365, 30, 30, new Color(255,215,50), false, "BOUNCE", 3, 1, 0, 100)
addObject("ENEMY", "ENEMY", 610, 540, 52, 52, new Color(240,80,95), true, "FLOAT", 2, 3, 1, 0)
addObject("FINISH", "GOAL", 915, 535, 48, 48, new Color(70,255,150), false, "BOUNCE", 2, 1, 0, 500)
} else if (name == "OBBY") {
addObject("PLAYER", "PLAYER", 60, 535, 55, 55, new Color(255,125,50), false, "BOUNCE", 3)
addObject("GROUND", "PLATFORM", 25, 600, 950, 30, new Color(70,85,110), true)
addObject("JUMP 1", "PLATFORM", 160, 500, 110, 22, new Color(100,155,255), true)
addObject("JUMP 2", "PLATFORM", 320, 430, 110, 22, new Color(100,155,255), true)
addObject("JUMP 3", "PLATFORM", 490, 360, 110, 22, new Color(100,155,255), true)
addObject("JUMP 4", "PLATFORM", 660, 450, 110, 22, new Color(100,155,255), true)
addObject("SPIKE 1", "SPIKE", 280, 570, 48, 30, new Color(240,80,90), true, "NONE", 0, 1, 1, 0)
addObject("SPIKE 2", "SPIKE", 610, 570, 48, 30, new Color(240,80,90), true, "NONE", 0, 1, 1, 0)
addObject("FINISH", "GOAL", 875, 540, 52, 52, new Color(70,255,150), false, "BOUNCE", 2, 1, 0, 500)
} else if (name == "TOP DOWN") {
addObject("PLAYER", "PLAYER", 480, 290, 55, 55, new Color(65,195,255), false, "BOUNCE", 2)
addObject("WALL 1", "WALL", 150, 160, 260, 24, new Color(85,105,135), true)
addObject("WALL 2", "WALL", 630, 160, 260, 24, new Color(85,105,135), true)
addObject("WALL 3", "WALL", 150, 460, 260, 24, new Color(85,105,135), true)
addObject("WALL 4", "WALL", 630, 460, 260, 24, new Color(85,105,135), true)
addObject("GEM", "GEM", 930, 300, 34, 34, new Color(100,245,255), false, "FLOAT", 3, 1, 0, 250)
addObject("ENEMY A", "ENEMY", 280, 290, 50, 50, new Color(250,80,90), false, "FLOAT", 3, 2, 1, 0)
addObject("FINISH", "GOAL", 45, 300, 52, 52, new Color(70,255,150), false, "BOUNCE", 2, 1, 0, 500)
} else if (name == "RACING") {
addObject("CAR", "CAR", 90, 520, 82, 42, new Color(245,60,65), false, "BOUNCE", 2)
addObject("ROAD", "ROAD", 30, 250, 950, 320, new Color(48,52,60), true)
addObject("TRAFFIC A", "TRAFFIC", 330, 320, 76, 40, new Color(40,150,255), false, "NONE", 2)
addObject("TRAFFIC B", "TRAFFIC", 590, 440, 76, 40, new Color(255,200,50), false, "NONE", 3)
addObject("CHECKPOINT", "CHECKPOINT", 690, 290, 28, 240, new Color(90,200,255), false, "FLOAT", 1)
addObject("FINISH", "GOAL", 900, 350, 46, 120, new Color(70,255,150), false, "NONE", 0, 1, 0, 1000)
} else if (name == "SANDBOX") {
addObject("PLAYER", "PLAYER", 480, 500, 58, 58, new Color(95,200,255), false, "FLOAT", 2)
addObject("BLOCK A", "BLOCK", 150, 450, 100, 100, new Color(150,100,255), true)
addObject("BLOCK B", "BLOCK", 330, 360, 100, 100, new Color(90,210,150), true)
addObject("BLOCK C", "BLOCK", 650, 420, 100, 100, new Color(255,140,80), true)
addObject("COIN", "COIN", 820, 280, 30, 30, new Color(255,215,50), false, "BOUNCE", 3, 1, 0, 100)
addObject("PORTAL", "PORTAL", 80, 100, 52, 70, new Color(190,100,255), false, "FLOAT", 2, 1, 0, 700)
} else {
addObject("PLAYER", "PLAYER", 80, 500, 58, 58, new Color(80,200,255), false, "BOUNCE", 3)
addObject("GROUND", "PLATFORM", 20, 600, 980, 30, new Color(70,95,125), true)
addObject("GOAL", "GOAL", 900, 530, 50, 50, new Color(70,255,150), false, "BOUNCE", 2, 1, 0, 500)
}
refreshEditor()
studioCanvas.repaint()
setStatus(name + " TEMPLATE READY")
}
// ============================================================
// TOOL / INSPECTOR
// ============================================================
def deleteSelected(): Unit = {
if (selectedIndex >= 0 && selectedIndex < objects.length) {
saveHistory()
objects.remove(selectedIndex)
selectedIndex = -1
refreshEditor()
studioCanvas.repaint()
setStatus("OBJECT DELETED")
}
}
def moveSelected(dx: Int, dy: Int): Unit = {
if (selectedIndex >= 0 && selectedIndex < objects.length) {
saveHistory()
val o = objects(selectedIndex)
o.x = math.max(0, math.min(CANVAS_W - o.w, o.x + dx))
o.y = math.max(0, math.min(CANVAS_H - o.h, o.y + dy))
refreshInspector()
studioCanvas.repaint()
}
}
def selectedObject(): ForgeObject = {
if (selectedIndex >= 0 && selectedIndex < objects.length) objects(selectedIndex) else null
}
def refreshInspector(): Unit = {
if (inspector == null) return
val o = selectedObject()
if (o == null) {
inspector.setText("NO OBJECT SELECTED\n\nSelect an object in the canvas or Scene list.\n\nNO-CODE EDITOR:\n- Drag objects with mouse\n- Change animation buttons\n- Resize with properties tools\n- Import images\n- Test instantly")
} else {
inspector.setText(
"OBJECT INSPECTOR\n\n" +
"NAME: " + o.name + "\n" +
"TYPE: " + o.kind + "\n" +
"POSITION: " + o.x + ", " + o.y + "\n" +
"SIZE: " + o.w + " x " + o.h + "\n" +
"SOLID: " + o.solid + "\n" +
"VISIBLE: " + o.visible + "\n" +
"ANIMATION: " + o.animation + "\n" +
"ANIMATION SPEED: " + o.speed + "\n" +
"HP: " + o.hp + "\n" +
"DAMAGE: " + o.damage + "\n" +
"POINTS: " + o.points + "\n" +
"IMAGE: " + (o.image != null) + "\n\n" +
"PLAY TEST CONTROLS:\n" +
"A / LEFT = move left\n" +
"D / RIGHT = move right\n" +
"W / UP / SPACE = jump\n" +
"S / DOWN = move down in top-down mode\n" +
"R = restart\n" +
"ESC = stop test"
)
}
}
def refreshEditor(): Unit = {
if (objectListModel == null) return
objectListModel.clear()
var i = 0
while (i < objects.length) {
val o = objects(i)
objectListModel.addElement((i + 1) + ". " + o.name + " [" + o.kind + "]")
i += 1
}
if (selectedIndex >= 0 && selectedIndex < objects.length) objectList.setSelectedIndex(selectedIndex)
refreshInspector()
}
// ============================================================
// IMAGE / CLIPBOARD
// ============================================================
def pasteImageFromClipboard(): BufferedImage = {
try {
val clipboard = java.awt.Toolkit.getDefaultToolkit.getSystemClipboard
val t = clipboard.getContents(null)
if (t != null && t.isDataFlavorSupported(DataFlavor.imageFlavor)) {
val imageObj = t.getTransferData(DataFlavor.imageFlavor)
imageObj match {
case img: java.awt.Image =>
val w = math.max(1, img.getWidth(null))
val h = math.max(1, img.getHeight(null))
val bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB)
val g = bi.createGraphics()
g.drawImage(img, 0, 0, null)
g.dispose()
bi
case _ => null
}
} else null
} catch {
case _: Throwable => null
}
}
def importImage(): Unit = {
val chooser = new JFileChooser()
chooser.setDialogTitle("IMPORT GAME ART / CHARACTER / BACKGROUND")
if (chooser.showOpenDialog(mainFrame) == JFileChooser.APPROVE_OPTION) {
try {
val img = ImageIO.read(chooser.getSelectedFile)
if (img != null) {
val name = chooser.getSelectedFile.getName
addObject(name, "IMAGE", 140, 180, math.min(180, img.getWidth), math.min(180, img.getHeight), Color.WHITE, false, "FLOAT", 2, 1, 0, 0, img)
setStatus("IMAGE IMPORTED")
}
} catch {
case _: Throwable => setStatus("IMAGE IMPORT FAILED")
}
}
}
def pasteImage(): Unit = {
val img = pasteImageFromClipboard()
if (img != null) {
addObject("Pasted Image", "IMAGE", 160, 160, math.min(220, img.getWidth), math.min(180, img.getHeight), Color.WHITE, false, "FLOAT", 2, 1, 0, 0, img)
setStatus("IMAGE PASTED")
} else setStatus("NO IMAGE IN CLIPBOARD")
}
// ============================================================
// PLAY MODE
// ============================================================
def playerObject(): ForgeObject = {
var i = 0
var out: ForgeObject = null
while (i < objects.length && out == null) {
if (objects(i).kind == "PLAYER" || objects(i).kind == "CAR") out = objects(i)
i += 1
}
out
}
def startTest(): Unit = {
val p = playerObject()
if (p == null) {
setStatus("ADD A PLAYER OR CAR FIRST")
return
}
editorMode = false
gameRunning = true
gameWon = false
playScore = 0
playLives = 3
playTime = 0.0
playVX = 0.0
playVY = 0.0
playX = p.x.toDouble
playY = p.y.toDouble
var i = 0
while (i < objects.length) {
if (objects(i).kind == "COIN" || objects(i).kind == "GEM" || objects(i).kind == "ENEMY" || objects(i).kind == "GOAL" || objects(i).kind == "SPIKE" || objects(i).kind == "CHECKPOINT") objects(i).visible = true
i += 1
}
studioCanvas.requestFocusInWindow()
setStatus("PLAY TEST RUNNING")
studioCanvas.repaint()
}
def stopTest(): Unit = {
gameRunning = false
editorMode = true
setStatus("PLAY TEST STOPPED")
studioCanvas.repaint()
}
def restartTest(): Unit = {
gameRunning = false
editorMode = true
startTest()
}
def rectHit(a: ForgeObject, x: Double, y: Double, w: Double, h: Double): Boolean = {
a.visible &&
x + w > a.x && x < a.x + a.w &&
y + h > a.y && y < a.y + a.h
}
def updatePlay(dt: Double): Unit = {
if (!gameRunning) return
playTime += dt
if (selectedTemplate == "RACING") {
if (moveLeft) playVX = -5.0
else if (moveRight) playVX = 5.0
else playVX *= 0.88
playX += playVX
playY += playVY
} else if (selectedTemplate == "TOP DOWN" || selectedTemplate == "SANDBOX") {
playVX = 0.0
playVY = 0.0
if (moveLeft) playVX = -4.0
if (moveRight) playVX = 4.0
if (moveUp) playVY = -4.0
if (moveDown) playVY = 4.0
playX += playVX
playY += playVY
} else {
if (moveLeft) playVX = -4.2
else if (moveRight) playVX = 4.2
else playVX *= 0.78
if (moveUp && jumpReady) {
playVY = -11.0
jumpReady = false
}
playVY += 0.55
playX += playVX
playY += playVY
}
playX = math.max(0.0, math.min(CANVAS_W - 60.0, playX))
playY = math.max(0.0, math.min(CANVAS_H - 60.0, playY))
if (selectedTemplate != "TOP DOWN" && selectedTemplate != "SANDBOX" && playY >= CANVAS_H - 100) {
playY = CANVAS_H - 100
playVY = 0.0
jumpReady = true
}
var i = 0
while (i < objects.length) {
val o = objects(i)
if (o.visible) {
if (o.solid && o.kind != "ENEMY" && o.kind != "SPIKE" && rectHit(o, playX, playY, 58, 58)) {
if (playVY > 0 && playY + 58 - playVY <= o.y + 12) {
playY = o.y - 58
playVY = 0.0
jumpReady = true
} else if (playVX > 0) {
playX = o.x - 58
playVX = 0.0
} else if (playVX < 0) {
playX = o.x + o.w
playVX = 0.0
}
}
if ((o.kind == "COIN" || o.kind == "GEM") && rectHit(o, playX, playY, 58, 58)) {
o.visible = false
playScore += o.points
}
if ((o.kind == "ENEMY" || o.kind == "SPIKE") && rectHit(o, playX, playY, 58, 58)) {
o.visible = false
playLives -= 1
playX = 70.0
playY = 500.0
playVX = 0.0
playVY = 0.0
if (playLives <= 0) {
gameRunning = false
editorMode = true
gameWon = false
setStatus("GAME OVER - PRESS TEST GAME")
}
}
if ((o.kind == "GOAL" || o.kind == "PORTAL") && rectHit(o, playX, playY, 58, 58)) {
playScore += o.points
gameRunning = false
editorMode = true
gameWon = true
setStatus("YOU WIN! SCORE " + playScore)
}
}
i += 1
}
if (playTime > 300.0) {
gameRunning = false
editorMode = true
setStatus("TIME UP")
}
}
// ============================================================
// RENDERING
// ============================================================
class ForgeCanvas extends JPanel {
setPreferredSize(new Dimension(CANVAS_W, CANVAS_H))
setFocusable(true)
setBackground(Color.BLACK)
override def paintComponent(g0: Graphics): Unit = {
super.paintComponent(g0)
val g = g0.asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val bg1 = if (selectedTheme == "FOREST") new Color(36,70,45) else if (selectedTheme == "OCEAN") new Color(24,75,100) else if (selectedTheme == "SUNSET") new Color(110,65,70) else if (selectedTheme == "SPACE") new Color(20,20,55) else new Color(24,38,72)
val bg2 = if (selectedTheme == "FOREST") new Color(12,25,18) else if (selectedTheme == "OCEAN") new Color(8,30,45) else if (selectedTheme == "SUNSET") new Color(40,18,28) else if (selectedTheme == "SPACE") new Color(8,8,25) else new Color(8,12,25)
g.setPaint(new GradientPaint(0,0,bg1,0,CANVAS_H,bg2))
g.fillRect(0,0,CANVAS_W,CANVAS_H)
if (selectedTheme == "NEON CITY") {
g.setColor(new Color(80,120,180,25))
var bx = 20
while (bx < CANVAS_W) {
g.fillRect(bx, 90 + ((bx * 17) % 220), 75, 260)
bx += 110
}
}
if (selectedTheme == "SPACE") {
g.setColor(new Color(255,255,255,120))
var s = 0
while (s < 90) {
val sx = (s * 97) % CANVAS_W
val sy = (s * 53) % CANVAS_H
g.fillRect(sx, sy, 2, 2)
s += 1
}
}
if (gridEnabled) {
g.setColor(new Color(255,255,255,18))
var gx = 0
while (gx < CANVAS_W) { g.drawLine(gx,0,gx,CANVAS_H); gx += 40 }
var gy = 0
while (gy < CANVAS_H) { g.drawLine(0,gy,CANVAS_W,gy); gy += 40 }
}
var i = 0
val pulse = System.nanoTime().toDouble / 1000000000.0
while (i < objects.length) {
drawObject(g, objects(i), pulse)
i += 1
}
if (gameRunning) {
val p = playerObject()
val pw = if (p == null) 58 else p.w
val ph = if (p == null) 58 else p.h
g.setColor(new Color(80,220,255,120))
g.drawOval(playX.toInt, playY.toInt, pw, ph)
g.setColor(new Color(5,10,20,215))
g.fillRoundRect(16,16,470,56,14,14)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 15))
g.drawString("PLAY TEST SCORE " + playScore + " LIVES " + playLives + " TIME " + playTime.toInt, 30, 51)
}
if (editorMode && selectedIndex >= 0 && selectedIndex < objects.length) {
val o = objects(selectedIndex)
g.setColor(new Color(255,255,255,180))
g.setStroke(new BasicStroke(2.0f))
g.drawRect(o.x - 5, o.y - 5, o.w + 10, o.h + 10)
g.setStroke(new BasicStroke(1.0f))
g.setColor(new Color(255,255,255))
g.fillRect(o.x - 8, o.y - 8, 8, 8)
g.fillRect(o.x + o.w, o.y - 8, 8, 8)
g.fillRect(o.x - 8, o.y + o.h, 8, 8)
g.fillRect(o.x + o.w, o.y + o.h, 8, 8)
}
if (gameWon) {
g.setColor(new Color(0,0,0,185))
g.fillRect(0,0,CANVAS_W,CANVAS_H)
g.setColor(new Color(80,255,170))
g.setFont(new Font("Arial", Font.BOLD, 50))
g.drawString("YOU WIN!", 375, 300)
g.setFont(new Font("Arial", Font.PLAIN, 20))
g.setColor(Color.WHITE)
g.drawString("Score: " + playScore, 445, 340)
g.drawString("Press TEST GAME to play again", 370, 380)
}
}
def drawObject(g: Graphics2D, o: ForgeObject, pulse: Double): Unit = {
if (!o.visible) return
val bob = if (o.animation == "BOUNCE") (math.sin(pulse * (1.2 + o.speed * 0.25) + o.id) * 5.0).toInt else 0
val sway = if (o.animation == "FLOAT") (math.sin(pulse * (0.8 + o.speed * 0.18) + o.id) * 8.0).toInt else 0
val xx = o.x + sway
val yy = o.y + bob
if (o.image != null) {
g.drawImage(o.image, xx, yy, o.w, o.h, null)
} else if (o.kind == "PLAYER" || o.kind == "CHARACTER" || o.kind == "NPC") {
g.setColor(new Color(0,0,0,70))
g.fillOval(xx+6, yy+o.h-10, math.max(4,o.w-12), 10)
g.setColor(o.color)
g.fillRoundRect(xx,yy,o.w,o.h,18,18)
g.setColor(Color.WHITE)
g.fillOval(xx + o.w/4, yy + o.h/5, 10, 10)
g.fillOval(xx + o.w/2 + 2, yy + o.h/5, 10, 10)
g.setColor(Color.DARK_GRAY)
g.drawLine(xx + o.w/2 - 5, yy + o.h/2 + 10, xx + o.w/2 + 12, yy + o.h/2 + 10)
} else if (o.kind == "CAR" || o.kind == "TRAFFIC") {
g.setColor(new Color(0,0,0,70))
g.fillOval(xx+5, yy+o.h-3, o.w-10, 9)
g.setColor(o.color)
g.fillRoundRect(xx,yy,o.w,o.h,14,14)
g.setColor(new Color(150,220,255))
g.fillRoundRect(xx+o.w/4,yy+5,o.w/2,o.h/2,8,8)
g.setColor(Color.BLACK)
g.fillOval(xx+10,yy+o.h-5,12,12)
g.fillOval(xx+o.w-22,yy+o.h-5,12,12)
} else if (o.kind == "COIN" || o.kind == "GEM") {
g.setColor(new Color(o.color.getRed(),o.color.getGreen(),o.color.getBlue(),60))
g.fillOval(xx-7,yy-7,o.w+14,o.h+14)
g.setColor(o.color)
g.fillOval(xx,yy,o.w,o.h)
g.setColor(Color.WHITE)
g.drawOval(xx,yy,o.w,o.h)
} else if (o.kind == "GOAL" || o.kind == "PORTAL") {
g.setColor(new Color(80,255,160,50))
g.fillOval(xx-8,yy-8,o.w+16,o.h+16)
g.setColor(o.color)
g.fillOval(xx,yy,o.w,o.h)
g.setColor(Color.WHITE)
g.drawOval(xx,yy,o.w,o.h)
} else if (o.kind == "SPIKE") {
g.setColor(o.color)
val p = new java.awt.geom.Path2D.Double()
p.moveTo(xx,yy+o.h)
p.lineTo(xx+o.w/2.0,yy)
p.lineTo(xx+o.w,yy+o.h)
p.closePath()
g.fill(p)
g.setColor(Color.WHITE)
g.draw(p)
} else if (o.kind == "ROAD") {
g.setColor(o.color)
g.fillRoundRect(xx,yy,o.w,o.h,20,20)
g.setColor(new Color(240,220,110,170))
var lx = xx + 20
while (lx < xx + o.w - 20) {
g.fillRect(lx, yy + o.h/2 - 3, 50, 6)
lx += 90
}
} else if (o.kind == "CHECKPOINT") {
g.setColor(new Color(80,200,255,55))
g.fillRoundRect(xx,yy,o.w,o.h,20,20)
g.setColor(o.color)
g.drawRoundRect(xx,yy,o.w,o.h,20,20)
} else {
g.setColor(o.color)
g.fillRoundRect(xx,yy,o.w,o.h,12,12)
g.setColor(new Color(255,255,255,80))
g.drawRoundRect(xx,yy,o.w,o.h,12,12)
}
}
}
// ============================================================
// EXPORT PLAYABLE KOJO SOURCE
// ============================================================
def q(s: String): String = {
if (s == null) "" else s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "")
}
def makeGeneratedGameSource(): String = {
val sb = new StringBuilder()
sb.append("import java.awt._\n")
sb.append("import java.awt.event._\n")
sb.append("import javax.swing._\n")
sb.append("import scala.collection.mutable.ArrayBuffer\n\n")
sb.append("val W=1100\n")
sb.append("val H=700\n")
sb.append("var run=true\n")
sb.append("var won=false\n")
sb.append("var score=0\n")
sb.append("var lives=3\n")
sb.append("var px=80.0\n")
sb.append("var py=500.0\n")
sb.append("var vx=0.0\n")
sb.append("var vy=0.0\n")
sb.append("var left=false\n")
sb.append("var right=false\n")
sb.append("var jump=false\n")
sb.append("case class O(kind:String,name:String,x:Int,y:Int,w:Int,h:Int,c:Color,var active:Boolean)\n")
sb.append("val os=ArrayBuffer[O]()\n")
var i = 0
while (i < objects.length) {
val o = objects(i)
sb.append("os += O(\"")
sb.append(q(o.kind))
sb.append("\",\"")
sb.append(q(o.name))
sb.append("\",")
sb.append(o.x + "," + o.y + "," + o.w + "," + o.h + ",new Color(" + o.color.getRed + "," + o.color.getGreen + "," + o.color.getBlue + "," + o.color.getAlpha + "),true)\n")
i += 1
}
sb.append("def hit(o:O,x:Double,y:Double,w:Double,h:Double):Boolean=o.active&&x+w>o.x&&x<o.x+o.w&&y+h>o.y&&y<o.y+o.h\n")
sb.append("class Board extends JPanel with KeyListener{setFocusable(true);addKeyListener(this);override def paintComponent(g0:Graphics):Unit={super.paintComponent(g0);val g=g0.asInstanceOf[Graphics2D];g.setPaint(new GradientPaint(0,0,new Color(35,60,100),0,H,new Color(8,12,24)));g.fillRect(0,0,W,H);var i=0;while(i<os.length){val o=os(i);if(o.active){if(o.kind==\"PLAYER\"||o.kind==\"CHARACTER\"){g.setColor(o.c);g.fillRoundRect(o.x,o.y,o.w,o.h,16,16)}else if(o.kind==\"COIN\"||o.kind==\"GEM\"){g.setColor(o.c);g.fillOval(o.x,o.y,o.w,o.h)}else if(o.kind==\"GOAL\"||o.kind==\"PORTAL\"){g.setColor(o.c);g.drawOval(o.x,o.y,o.w,o.h)}else{g.setColor(o.c);g.fillRoundRect(o.x,o.y,o.w,o.h,10,10)}};i+=1};g.setColor(new Color(5,10,18,210));g.fillRoundRect(12,12,330,48,12,12);g.setColor(Color.WHITE);g.setFont(new Font(\"Arial\",Font.BOLD,14));g.drawString(\"SCORE \"+score+\" LIVES \"+lives,26,42);if(!run){g.setFont(new Font(\"Arial\",Font.BOLD,38));g.drawString(if(won)\"YOU WIN!\" else \"GAME OVER\",420,310)}};override def keyPressed(e:KeyEvent):Unit={val k=e.getKeyCode;if(k==KeyEvent.VK_A||k==KeyEvent.VK_LEFT)left=true;if(k==KeyEvent.VK_D||k==KeyEvent.VK_RIGHT)right=true;if(k==KeyEvent.VK_SPACE||k==KeyEvent.VK_W||k==KeyEvent.VK_UP)jump=true;if(k==KeyEvent.VK_R){run=true;won=false;score=0;lives=3}};override def keyReleased(e:KeyEvent):Unit={val k=e.getKeyCode;if(k==KeyEvent.VK_A||k==KeyEvent.VK_LEFT)left=false;if(k==KeyEvent.VK_D||k==KeyEvent.VK_RIGHT)right=false};override def keyTyped(e:KeyEvent):Unit={}}\n")
sb.append("val b=new Board\n")
sb.append("val f=new JFrame(\"GAME FORGE GENERATED GAME\");f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);f.setSize(W,H);f.setLocationRelativeTo(null);f.add(b);f.setVisible(true);b.requestFocusInWindow()\n")
sb.append("val t=new Timer(16,new ActionListener{override def actionPerformed(e:ActionEvent):Unit={if(run){if(left)vx=-4.5 else if(right)vx=4.5 else vx*=0.82;px+=vx;if(jump&&math.abs(vy)<1.0)vy=-11.0;jump=false;vy+=0.55;py+=vy;if(py>H-95){py=H-95;vy=0};if(px<0)px=0;if(px>W-70)px=W-70;var i=0;while(i<os.length){val o=os(i);if(o.active&&o.kind==\"COIN\"&&hit(o,px,py,58,58)){o.active=false;score+=100};if(o.active&&(o.kind==\"ENEMY\"||o.kind==\"SPIKE\")&&hit(o,px,py,58,58)){o.active=false;lives-=1;px=80;py=500;vx=0;vy=0;if(lives<=0)run=false};if(o.active&&(o.kind==\"GOAL\"||o.kind==\"PORTAL\")&&hit(o,px,py,58,58)){won=true;run=false;score+=500};i+=1}};b.repaint()}});t.start()\n")
sb.toString()
}
def exportPlayableGame(): Unit = {
if (objects.isEmpty) {
JOptionPane.showMessageDialog(mainFrame, "Build something first.")
return
}
val chooser = new JFileChooser()
chooser.setSelectedFile(new File("MyGameForgeGame.kojo"))
if (chooser.showSaveDialog(mainFrame) == JFileChooser.APPROVE_OPTION) {
var f = chooser.getSelectedFile
if (!f.getName.toLowerCase.endsWith(".kojo")) f = new File(f.getAbsolutePath + ".kojo")
Files.write(f.toPath, makeGeneratedGameSource().getBytes("UTF-8"))
JOptionPane.showMessageDialog(mainFrame, "Playable KOJO game created!\n\n" + f.getAbsolutePath)
setStatus("PLAYABLE GAME EXPORTED")
}
}
// ============================================================
// BUILD UI
// ============================================================
def refreshSearchResults(): Unit = {
if (objectListModel == null) return
val query = if (searchField == null) "" else searchField.getText.trim.toLowerCase
objectListModel.clear()
var i = 0
while (i < objects.length) {
val o = objects(i)
val text = o.name + " " + o.kind
if (query.isEmpty || text.toLowerCase.indexOf(query) >= 0) objectListModel.addElement((i + 1) + ". " + o.name + " [" + o.kind + "]")
i += 1
}
}
// ============================================================
// MAIN WINDOW
// ============================================================
studioCanvas = new ForgeCanvas
objectListModel = new DefaultListModel[String]()
objectList = new JList[String](objectListModel)
objectList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
objectList.setFont(new Font("Monospaced", Font.PLAIN, 13))
inspector = new JTextArea()
inspector.setEditable(false)
inspector.setFont(new Font("Monospaced", Font.PLAIN, 12))
inspector.setMargin(new Insets(8,8,8,8))
searchField = new JTextField(13)
templateBox = new JComboBox[String](Array("PLATFORMER","OBBY","TOP DOWN","RACING","SANDBOX"))
themeBox = new JComboBox[String](Array("NEON CITY","FOREST","OCEAN","SUNSET","SPACE"))
statusLabel = new JLabel(" READY")
statusLabel.setFont(new Font("Arial", Font.BOLD, 12))
val topBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5))
val newBtn = uiButton("NEW SCENE")
val undoBtn = uiButton("UNDO")
val redoBtn = uiButton("REDO")
val importBtn = uiButton("IMPORT IMAGE")
val pasteBtn = uiButton("PASTE IMAGE")
val testBtn = uiButton("TEST GAME")
val stopBtn = uiButton("STOP TEST")
val exportBtn = uiButton("EXPORT .KOJO")
val gridBtn = uiButton("GRID ON/OFF")
val snapBtn = uiButton("SNAP ON/OFF")
val helpBtn = uiButton("HOW TO BUILD")
topBar.add(newBtn)
topBar.add(undoBtn)
topBar.add(redoBtn)
topBar.add(importBtn)
topBar.add(pasteBtn)
topBar.add(testBtn)
topBar.add(stopBtn)
topBar.add(exportBtn)
topBar.add(gridBtn)
topBar.add(snapBtn)
topBar.add(helpBtn)
topBar.add(new JLabel(" TEMPLATE:"))
topBar.add(templateBox)
topBar.add(new JLabel(" THEME:"))
topBar.add(themeBox)
val library = new JPanel(new BorderLayout(4,4))
val libTop = new JPanel(new FlowLayout(FlowLayout.LEFT,4,4))
libTop.add(new JLabel("SEARCH"))
libTop.add(searchField)
val searchBtn = uiButton("SEARCH")
libTop.add(searchBtn)
val allBtn = uiButton("ALL")
libTop.add(allBtn)
library.add(libTop, BorderLayout.NORTH)
library.add(new JScrollPane(objectList), BorderLayout.CENTER)
val addPanel = new JPanel(new GridLayout(0,2,4,4))
addPanel.setBorder(BorderFactory.createTitledBorder("ADD OBJECTS"))
def addTool(text: String, kind: String, color: Color, solid: Boolean, w: Int, h: Int): Unit = {
val b = uiButton(text)
b.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val px = 120 + ((objects.length * 83) % 700)
val py = 120 + ((objects.length * 47) % 400)
addObject(text, kind, px, py, w, h, color, solid, if (kind == "COIN" || kind == "GOAL") "BOUNCE" else "NONE", 2, if (kind == "ENEMY") 3 else 1, if (kind == "SPIKE" || kind == "ENEMY") 1 else 0, if (kind == "COIN") 100 else if (kind == "GEM") 250 else if (kind == "GOAL" || kind == "PORTAL") 500 else 0)
}
})
addPanel.add(b)
}
addTool("BOY / PLAYER", "PLAYER", new Color(65,190,255), false, 58, 58)
addTool("GIRL / CHARACTER", "CHARACTER", new Color(245,120,190), false, 58, 58)
addTool("NPC", "NPC", new Color(180,160,255), false, 54, 54)
addTool("PET", "NPC", new Color(180,220,130), false, 46, 46)
addTool("CAR", "CAR", new Color(240,65,70), false, 84, 42)
addTool("TRAFFIC", "TRAFFIC", new Color(65,145,255), false, 76, 40)
addTool("PLATFORM", "PLATFORM", new Color(100,125,155), true, 180, 24)
addTool("WALL", "WALL", new Color(75,90,112), true, 32, 180)
addTool("BLOCK", "BLOCK", new Color(140,105,230), true, 90, 90)
addTool("ROAD", "ROAD", new Color(48,52,60), true, 360, 110)
addTool("COIN", "COIN", new Color(255,215,50), false, 30, 30)
addTool("GEM", "GEM", new Color(90,235,255), false, 32, 32)
addTool("ENEMY", "ENEMY", new Color(245,75,90), true, 52, 52)
addTool("SPIKE", "SPIKE", new Color(235,80,90), true, 48, 30)
addTool("CHECKPOINT", "CHECKPOINT", new Color(80,205,255), false, 30, 180)
addTool("FINISH", "GOAL", new Color(70,255,150), false, 52, 52)
addTool("PORTAL", "PORTAL", new Color(190,100,255), false, 52, 70)
val toolsScroll = new JScrollPane(addPanel)
toolsScroll.setPreferredSize(new Dimension(300, 370))
val libraryTop = new JPanel(new BorderLayout())
libraryTop.add(library, BorderLayout.CENTER)
libraryTop.add(toolsScroll, BorderLayout.SOUTH)
val actions = new JPanel(new FlowLayout(FlowLayout.LEFT,4,4))
val leftBtn = uiButton("? MOVE")
val rightBtn = uiButton("MOVE ?")
val upBtn = uiButton("? UP")
val downBtn = uiButton("? DOWN")
val bounceBtn = uiButton("BOUNCE")
val floatBtn = uiButton("FLOAT")
val noneAnimBtn = uiButton("STATIC")
val colorBtn = uiButton("COLOR")
val deleteBtn = uiButton("DELETE")
val leftSize = uiButton("WIDTH -")
val rightSize = uiButton("WIDTH +")
val upSize = uiButton("HEIGHT -")
val downSize = uiButton("HEIGHT +")
val actionButtons = Array[ JButton ](leftBtn,rightBtn,upBtn,downBtn,bounceBtn,floatBtn,noneAnimBtn,colorBtn,deleteBtn,leftSize,rightSize,upSize,downSize)
var ab = 0
while (ab < actionButtons.length) { actions.add(actionButtons(ab)); ab += 1 }
val inspectorPanel = new JPanel(new BorderLayout(4,4))
inspectorPanel.add(new JLabel("INSPECTOR / NO-CODE PROPERTIES", SwingConstants.CENTER), BorderLayout.NORTH)
inspectorPanel.add(new JScrollPane(inspector), BorderLayout.CENTER)
val leftSide = new JPanel(new BorderLayout(4,4))
leftSide.setPreferredSize(new Dimension(310, 760))
leftSide.add(libraryTop, BorderLayout.CENTER)
leftSide.add(inspectorPanel, BorderLayout.SOUTH)
val center = new JPanel(new BorderLayout(4,4))
center.add(studioCanvas, BorderLayout.CENTER)
center.add(actions, BorderLayout.SOUTH)
val rightText = new JTextArea()
rightText.setEditable(false)
rightText.setFont(new Font("Arial", Font.PLAIN, 14))
rightText.setLineWrap(true)
rightText.setWrapStyleWord(true)
rightText.setText(
"GAME FORGE X1 ? TOP 1 NO-CODE STUDIO\n\n" +
"BUILD WITHOUT TYPING GAME CODE\n\n" +
"1. Choose a template.\n" +
"2. Add characters, cars, enemies, coins, walls, roads and finish objects.\n" +
"3. Import or paste your own images.\n" +
"4. Drag objects directly on the canvas.\n" +
"5. Select an object and edit position, size, color, animation and collision.\n" +
"6. Press TEST GAME and immediately play your scene.\n" +
"7. Press EXPORT .KOJO to create a runnable game source file.\n\n" +
"BEST IDEA:\nBuild a racing game, platformer, obby, top-down adventure or sandbox by placing objects.\n\n" +
"CONTROLS IN TEST:\nA / LEFT, D / RIGHT, W / UP / SPACE, S / DOWN, R = restart, ESC = stop.\n\n" +
"IMAGE WORKFLOW:\nCopy an image -> PASTE IMAGE.\nOr choose IMPORT IMAGE.\nThe image becomes a movable game object.\n\n" +
"This studio creates original games from your own scene design."
)
val guideTab = new JPanel(new BorderLayout())
guideTab.add(new JScrollPane(rightText), BorderLayout.CENTER)
val tabs = new JTabbedPane()
tabs.addTab("SCENE BUILDER", new JPanel(new BorderLayout()))
tabs.setComponentAt(0, new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, center))
tabs.addTab("HOW TO CREATE", guideTab)
def showHowToCreate(): Unit = {
val help = new JTextArea()
help.setEditable(false)
help.setFont(new Font("Arial", Font.PLAIN, 15))
help.setLineWrap(true)
help.setWrapStyleWord(true)
help.setText(
"GAME FORGE X1 ? HOW TO CREATE A GAME WITHOUT CODE\n\n" +
"STEP 1 ? START\nChoose NEW SCENE or a ready template.\n\n" +
"STEP 2 ? PLAYER\nAdd BOY / PLAYER, GIRL / CHARACTER, CAR or another actor.\n\n" +
"STEP 3 ? WORLD\nPlace platforms, walls, roads, blocks and checkpoints with the mouse.\n\n" +
"STEP 4 ? GAMEPLAY\nAdd coins, gems, enemies, spikes and a FINISH / PORTAL.\n\n" +
"STEP 5 ? YOUR ART\nCopy an image and press PASTE IMAGE, or use IMPORT IMAGE.\n\n" +
"STEP 6 ? ANIMATION\nSelect an object, then BOUNCE, FLOAT or STATIC.\n\n" +
"STEP 7 ? EDIT\nDrag any object. Use MOVE, WIDTH, HEIGHT and COLOR tools.\n\n" +
"STEP 8 ? TEST\nPress TEST GAME. The same scene becomes a playable test.\n\n" +
"STEP 9 ? FINISH\nWhen the game feels right, press EXPORT .KOJO.\n\n" +
"IMPORTANT:\nThis is a visual no-code builder. The editor generates the playable source when you export. You do not need to type the generated game code yourself.\n\n" +
"BEST GAME TYPES:\nPlatformer ? Obby ? Racing ? Top Down ? Sandbox ? Coin Collector ? Puzzle ? Adventure\n\n" +
"The system uses original Java2D/Swing visuals and does not download online game assets."
)
JOptionPane.showMessageDialog(mainFrame, new JScrollPane(help), "HOW TO CREATE A GAME", JOptionPane.INFORMATION_MESSAGE)
}
// ============================================================
// BUTTON EVENTS
// ============================================================
newBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = resetScene() })
undoBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = undo() })
redoBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = redo() })
importBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = importImage() })
pasteBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = pasteImage() })
testBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = startTest() })
stopBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = stopTest() })
exportBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = exportPlayableGame() })
gridBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { gridEnabled = !gridEnabled; studioCanvas.repaint(); setStatus("GRID " + (if (gridEnabled) "ON" else "OFF")) } })
snapBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { snapEnabled = !snapEnabled; setStatus("SNAP " + (if (snapEnabled) "ON" else "OFF")) } })
helpBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = showHowToCreate() })
searchBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = refreshSearchResults() })
allBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { searchField.setText(""); refreshSearchResults() } })
searchField.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = refreshSearchResults() })
templateBox.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val t = templateBox.getSelectedItem.asInstanceOf[String]; makeTemplate(t) } })
themeBox.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { selectedTheme = themeBox.getSelectedItem.asInstanceOf[String]; studioCanvas.repaint(); setStatus("THEME " + selectedTheme) } })
objectList.addListSelectionListener(new javax.swing.event.ListSelectionListener {
override def valueChanged(e: javax.swing.event.ListSelectionEvent): Unit = {
val idx = objectList.getSelectedIndex
if (idx >= 0 && idx < objects.length) {
selectedIndex = idx
refreshInspector()
studioCanvas.repaint()
}
}
})
leftBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = moveSelected(-10,0) })
rightBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = moveSelected(10,0) })
upBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = moveSelected(0,-10) })
downBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = moveSelected(0,10) })
bounceBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){ saveHistory(); o.animation="BOUNCE"; refreshInspector(); studioCanvas.repaint() } } })
floatBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){ saveHistory(); o.animation="FLOAT"; refreshInspector(); studioCanvas.repaint() } } })
noneAnimBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){ saveHistory(); o.animation="NONE"; refreshInspector(); studioCanvas.repaint() } } })
deleteBtn.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = deleteSelected() })
leftSize.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){saveHistory();o.w=math.max(8,o.w-10);refreshInspector();studioCanvas.repaint()} } })
rightSize.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){saveHistory();o.w=math.min(CANVAS_W-o.x,o.w+10);refreshInspector();studioCanvas.repaint()} } })
upSize.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){saveHistory();o.h=math.max(8,o.h-10);refreshInspector();studioCanvas.repaint()} } })
downSize.addActionListener(new ActionListener { override def actionPerformed(e: ActionEvent): Unit = { val o=selectedObject(); if(o!=null){saveHistory();o.h=math.min(CANVAS_H-o.y,o.h+10);refreshInspector();studioCanvas.repaint()} } })
colorBtn.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val o = selectedObject()
if (o != null) {
val c = JColorChooser.showDialog(mainFrame, "CHOOSE OBJECT COLOR", o.color)
if (c != null) { saveHistory(); o.color = c; refreshInspector(); studioCanvas.repaint(); setStatus("COLOR CHANGED") }
}
}
})
// ============================================================
// DRAG / DROP EDITOR
// ============================================================
studioCanvas.addMouseListener(new MouseAdapter {
override def mousePressed(e: MouseEvent): Unit = {
if (gameRunning) return
var best = -1
var i = objects.length - 1
while (i >= 0 && best < 0) {
val o = objects(i)
if (o.visible && new java.awt.Rectangle(o.x - 8, o.y - 8, o.w + 16, o.h + 16).contains(e.getPoint)) best = i
i -= 1
}
if (best >= 0) {
selectedIndex = best
draggingIndex = best
dragOffX = e.getX - objects(best).x
dragOffY = e.getY - objects(best).y
objectList.setSelectedIndex(best)
refreshInspector()
studioCanvas.repaint()
}
}
override def mouseReleased(e: MouseEvent): Unit = {
if (draggingIndex >= 0) {
saveHistory()
draggingIndex = -1
}
}
})
studioCanvas.addMouseMotionListener(new MouseMotionAdapter {
override def mouseDragged(e: MouseEvent): Unit = {
if (!gameRunning && draggingIndex >= 0 && draggingIndex < objects.length) {
val o = objects(draggingIndex)
var nx = e.getX - dragOffX
var ny = e.getY - dragOffY
if (snapEnabled) { nx = (nx / 20) * 20; ny = (ny / 20) * 20 }
o.x = math.max(0, math.min(CANVAS_W - o.w, nx))
o.y = math.max(0, math.min(CANVAS_H - o.h, ny))
refreshInspector()
studioCanvas.repaint()
}
}
})
// ============================================================
// KEYBOARD INPUT
// ============================================================
studioCanvas.addKeyListener(new KeyAdapter {
override def keyPressed(e: KeyEvent): Unit = {
val k = e.getKeyCode
if (k == KeyEvent.VK_A || k == KeyEvent.VK_LEFT) moveLeft = true
if (k == KeyEvent.VK_D || k == KeyEvent.VK_RIGHT) moveRight = true
if (k == KeyEvent.VK_W || k == KeyEvent.VK_UP || k == KeyEvent.VK_SPACE) moveUp = true
if (k == KeyEvent.VK_S || k == KeyEvent.VK_DOWN) moveDown = true
if (k == KeyEvent.VK_R && gameRunning) restartTest()
if (k == KeyEvent.VK_ESCAPE && gameRunning) stopTest()
}
override def keyReleased(e: KeyEvent): Unit = {
val k = e.getKeyCode
if (k == KeyEvent.VK_A || k == KeyEvent.VK_LEFT) moveLeft = false
if (k == KeyEvent.VK_D || k == KeyEvent.VK_RIGHT) moveRight = false
if (k == KeyEvent.VK_W || k == KeyEvent.VK_UP || k == KeyEvent.VK_SPACE) moveUp = false
if (k == KeyEvent.VK_S || k == KeyEvent.VK_DOWN) moveDown = false
}
})
// ============================================================
// MAIN FRAME
// ============================================================
mainFrame = new JFrame("GAME FORGE X1 - TOP 1 NO-CODE GAME STUDIO")
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
mainFrame.setSize(STUDIO_W, STUDIO_H)
mainFrame.setMinimumSize(new Dimension(1200, 750))
mainFrame.setLocationRelativeTo(null)
mainFrame.setLayout(new BorderLayout(5,5))
mainFrame.add(topBar, BorderLayout.NORTH)
mainFrame.add(tabs, BorderLayout.CENTER)
mainFrame.add(statusLabel, BorderLayout.SOUTH)
val gameLoop = new Timer(16, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updatePlay(0.016)
studioCanvas.repaint()
}
})
gameLoop.start()
// ============================================================
// INITIAL SCENE
// ============================================================
makeTemplate("PLATFORMER")
mainFrame.setVisible(true)
studioCanvas.requestFocusInWindow()
refreshEditor()
refreshInspector()
setStatus("GAME FORGE X1 READY - BUILD WITHOUT CODE")