A collection of creative-tech projects. Developed from early 2020 to present, using digital parts including Unity, Python, ESP32, etc.
To design a calculator that is "informal".
My idea is to make the interface and interaction to be different. I firstly designed a bubble and sea
<script>
document.addEventListener('DOMContentLoaded', function () {
const balls = document.querySelectorAll('.ball');
const calculateArea = document.querySelector('.calculate-area');
const calculateNumberLine = document.querySelector('.calculate-number-line');
const hoverIndicator = document.querySelector('.hover-indicator');
const friction = 0.98;
const gravity = 0.0;
const bounceFactor = 0.7;
const collisionBuffer = 0.9;
const rotationDamping = 0.3;
let isDragging = false;
let dragBall = null;
let offsetX, offsetY;
let isInCalculateArea = false;
balls.forEach(ball => {
ball.velocityX = (Math.random() - 0.5) * 10;
ball.velocityY = (Math.random() - 0.5) * 10;
ball.rotation = 0;
ball.rotationSpeed = 0;
ball.style.left = `${Math.random() * 90}%`;
ball.style.top = `${Math.random() * 90}%`;
ball.addEventListener('mousedown', function (e) {
isDragging = true;
dragBall = ball;
offsetX = e.clientX - ball.offsetLeft;
offsetY = e.clientY - ball.offsetTop;
});
ball.addEventListener('touchstart', function (e) {
isDragging = true;
dragBall = ball;
let touch = e.touches[0];
offsetX = touch.clientX - ball.offsetLeft;
offsetY = touch.clientY - ball.offsetTop;
});
});
document.addEventListener('mousemove', function (e) {
if (isDragging && dragBall) {
dragBall.style.left = `${e.clientX - offsetX}px`;
dragBall.style.top = `${e.clientY - offsetY}px`;
dragBall.velocityX = 0;
dragBall.velocityY = 0;
checkBallInCalculateArea(dragBall);
}
});
document.addEventListener('touchmove', function (e) {
if (isDragging && dragBall) {
let touch = e.touches[0];
dragBall.style.left = `${touch.clientX - offsetX}px`;
dragBall.style.top = `${touch.clientY - offsetY}px`;
dragBall.velocityX = 0;
dragBall.velocityY = 0;
checkBallInCalculateArea(dragBall);
}
});
document.addEventListener('mouseup', function () {
if (isInCalculateArea) {
addToCalculateLine(dragBall);
}
isDragging = false;
dragBall = null;
isInCalculateArea = false;
hoverIndicator.style.opacity = '0%'; // 重置透明度
});
document.addEventListener('touchend', function () {
if (isInCalculateArea) {
addToCalculateLine(dragBall);
}
isDragging = false;
dragBall = null;
isInCalculateArea = false;
hoverIndicator.style.opacity = '0%'; // 重置透明度
});
function resolveCollision(ball1, ball2) {
let dx = ball2.offsetLeft - ball1.offsetLeft;
let dy = ball2.offsetTop - ball1.offsetTop;
let distance = Math.sqrt(dx * dx + dy * dy);
let minDistance = ball1.offsetWidth / 2 + ball2.offsetWidth / 2;
let overlap = (minDistance - distance) * collisionBuffer;
let tx = (overlap * dx) / distance;
let ty = (overlap * dy) / distance;
ball1.style.left = `${ball1.offsetLeft - tx / 2}px`;
ball1.style.top = `${ball1.offsetTop - ty / 2}px`;
ball2.style.left = `${ball2.offsetLeft + tx / 2}px`;
ball2.style.top = `${ball2.offsetTop + ty / 2}px`;
let tempX = ball1.velocityX;
let tempY = ball1.velocityY;
ball1.velocityX = ball2.velocityX * collisionBuffer;
ball1.velocityY = ball2.velocityY * collisionBuffer;
ball2.velocityX = tempX * collisionBuffer;
ball2.velocityY = tempY * collisionBuffer;
// 更新旋转速度
ball1.rotationSpeed += (ball2.velocityX - ball1.velocityX) * collisionBuffer;
ball2.rotationSpeed += (ball1.velocityX - ball2.velocityX) * collisionBuffer;
}
function checkCollision(ball1, ball2) {
let dx = ball1.offsetLeft - ball2.offsetLeft;
let dy = ball1.offsetTop - ball2.offsetTop;
let distance = Math.sqrt(dx * dx + dy * dy);
let minDistance = ball1.offsetWidth / 2 + ball2.offsetWidth / 2;
return distance < minDistance;
}
function checkBallInCalculateArea(ball) {
let ballRect = ball.getBoundingClientRect();
let areaRect = calculateArea.getBoundingClientRect();
if (
ballRect.left >= areaRect.left &&
ballRect.right <= areaRect.right &&
ballRect.top >= areaRect.top &&
ballRect.bottom <= areaRect.bottom
) {
isInCalculateArea = true;
hoverIndicator.style.opacity = '100%'; // 设置透明度为50%
} else {
isInCalculateArea = false;
hoverIndicator.style.opacity = '0%'; // 重置透明度
}
}
function addToCalculateLine(ball) {
if (!ball) return;
if (ball.textContent === '=') {
calculateResult();
} else if (ball.textContent === 'AC') {
calculateNumberLine.textContent = '';
} else {
calculateNumberLine.textContent += ball.textContent;
}
}
function calculateResult() {
try {
let result = eval(calculateNumberLine.textContent);
calculateNumberLine.textContent = result;
} catch (e) {
calculateNumberLine.textContent = 'Error';
}
}
function moveBall(ball) {
if (isDragging && ball === dragBall) return;
let parentRect = ball.parentElement.getBoundingClientRect();
ball.velocityY += gravity;
ball.velocityX *= friction;
ball.velocityY *= friction;
let newLeft = ball.offsetLeft + ball.velocityX;
let newTop = ball.offsetTop + ball.velocityY;
let hitBoundary = false;
if (newLeft < 0 || newLeft + ball.offsetWidth > parentRect.width) {
ball.velocityX *= -bounceFactor;
hitBoundary = true;
}
if (newTop < 0 || newTop + ball.offsetHeight > parentRect.height) {
ball.velocityY *= -bounceFactor;
hitBoundary = true;
}
if (hitBoundary) {
ball.rotationSpeed *= rotationDamping;
}
ball.style.left = `${Math.max(0, Math.min(parentRect.width - ball.offsetWidth, newLeft))}px`;
ball.style.top = `${Math.max(0, Math.min(parentRect.height - ball.offsetHeight, newTop))}px`;
ball.rotation += ball.rotationSpeed;
ball.style.transform = `rotate(${ball.rotation}deg)`;
ball.rotationSpeed *= friction;
balls.forEach(otherBall => {
if (ball !== otherBall && checkCollision(ball, otherBall)) {
resolveCollision(ball, otherBall);
}
});
}
setInterval(() => {
balls.forEach(ball => {
moveBall(ball);
});
}, 10);
});
</script>
I want the player to have a feeling of touch, so the particles are needed to move away when the hand "touches" the canvas, like a pond of fish. This is how the system works:
I focused specifically on enhancing the interaction between the mouse and the dots. I coded the update method within the Dot class to dynamically adjust each dot's position when the mouse moves close to it. This involved calculating the distance between the mouse and each dot, and if this distance fell below 100 pixels, I applied a formula to move the dot directly away from the mouse, thus maintaining a minimum distance.
void update() {
// calculate the distance between the mouse and the dot
float dist = sqrt(sq(mouseX - x) + sq(mouseY - y));
// if the distance is less than the minimum distance, move the dot away from the mouse
if (dist < minDist) {
float angle = atan2(mouseY - y, mouseX - x);
x -= cos(angle) * (minDist - dist);
y -= sin(angle) * (minDist - dist);
}
// update the dot position based on velocity
x += vx;
y += vy;
// wrap the dot around the canvas if it goes offscreen
if (x < 0) x = width;
if (x > width) x = 0;
if (y < 0) y = height;
if (y > height) y = 0;
}
This project uses Tiktok's user scenarios as the main body to conduct research and uses VR as a medium to provide players with a closed virtual playing room, experience dazzling visual sensations, and complex operations to stimulate players' resistance.
To create an auto stamping machine which stamps a smile face when a paper is put.
I created a script to control a servo motor based on ADC readings. If the ADC value exceeds 2200, the motor executes a specific movement sequence, limited to once per second to prevent overactivation. Below this threshold, the script displays a smiley face on the interface, indicating normal conditions.
def perform_action():
global last_action_time
if time.time() - last_action_time < 1:
return
servo.move(current_position + 15)
time.sleep(2)
servo.move(current_position - 10)
time.sleep(1)
servo.move(current_position)
time.sleep(1)
last_action_time = time.time()
def loop():
adc_val = adc1.read()
if adc_val > 2200:
perform_action()
else:
label0.setText(":)")
Tt is the same as a selector app, but physical lol.
I programmed a servo motor to rotate based on ADC readings(front pressed button) and the servo rotates by 45 degrees whenever the ADC value exceeds the threshold. A global last_angle variable tracks the servo's position, and a pause after each rotation allows the movement to complete. The rotation continues for a random duration between 2 to 4 seconds, after which the servo resets to a neutral position.
void update() {
// calculate the distance between the mouse and the dot
float dist = sqrt(sq(mouseX - x) + sq(mouseY - y));
// if the distance is less than the minimum distance, move the dot away from the mouse
if (dist < minDist) {
float angle = atan2(mouseY - y, mouseX - x);
x -= cos(angle) * (minDist - dist);
y -= sin(angle) * (minDist - dist);
}
// update the dot position based on velocity
x += vx;
y += vy;
// wrap the dot around the canvas if it goes offscreen
if (x < 0) x = width;
if (x > width) x = 0;
if (y < 0) y = height;
if (y > height) y = 0;
}